diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index e997c0bc68cde..3d9de2d35b500 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -540,6 +540,11 @@ Elastic. |Add tagging capability to saved objects +|{kib-repo}blob/{branch}/x-pack/plugins/screenshotting/README.md[screenshotting] +|This plugin provides functionality to take screenshots of the Kibana pages. +It uses Chromium and Puppeteer underneath to run the browser in headless mode. + + |{kib-repo}blob/{branch}/x-pack/plugins/searchprofiler/README.md[searchprofiler] |The search profiler consumes the Profile API by sending a search API with profile: true enabled in the request body. The response contains diff --git a/docs/settings/url-drilldown-settings.asciidoc b/docs/settings/url-drilldown-settings.asciidoc index 702829ec34dcc..36dbabbe7fe1e 100644 --- a/docs/settings/url-drilldown-settings.asciidoc +++ b/docs/settings/url-drilldown-settings.asciidoc @@ -6,16 +6,13 @@ Configure the URL drilldown settings in your `kibana.yml` configuration file. -[cols="2*<"] -|=== -| [[external-URL-policy]] `externalUrl.policy` - | Configures the external URL policies. URL drilldowns respect the global *External URL* service, which you can use to deny or allow external URLs. +[[external-URL-policy]] `externalUrl.policy`:: +Configures the external URL policies. URL drilldowns respect the global *External URL* service, which you can use to deny or allow external URLs. By default all external URLs are allowed. -|=== - -For example, to allow external URLs only to the `example.com` domain with the `https` scheme, except for the `danger.example.com` sub-domain, ++ +For example, to allow only external URLs to the `example.com` domain with the `https` scheme, except for the `danger.example.com` sub-domain, which is denied even when `https` scheme is used: - ++ ["source","yml"] ----------- externalUrl.policy: @@ -25,4 +22,3 @@ externalUrl.policy: host: example.com protocol: https ----------- - diff --git a/package.json b/package.json index 157a77a0e73fb..bd38699b6966a 100644 --- a/package.json +++ b/package.json @@ -197,7 +197,7 @@ "axios": "^0.21.1", "base64-js": "^1.3.1", "brace": "0.11.1", - "broadcast-channel": "^4.5.0", + "broadcast-channel": "^4.7.0", "chalk": "^4.1.0", "cheerio": "^1.0.0-rc.10", "chokidar": "^3.4.3", @@ -568,6 +568,7 @@ "@types/kbn__config-schema": "link:bazel-bin/packages/kbn-config-schema/npm_module_types", "@types/kbn__crypto": "link:bazel-bin/packages/kbn-crypto/npm_module_types", "@types/kbn__dev-utils": "link:bazel-bin/packages/kbn-dev-utils/npm_module_types", + "@types/kbn__docs-utils": "link:bazel-bin/packages/kbn-docs-utils/npm_module_types", "@types/kbn__i18n": "link:bazel-bin/packages/kbn-i18n/npm_module_types", "@types/kbn__i18n-react": "link:bazel-bin/packages/kbn-i18n-react/npm_module_types", "@types/license-checker": "15.0.0", @@ -677,7 +678,7 @@ "babel-plugin-add-module-exports": "^1.0.4", "babel-plugin-istanbul": "^6.1.1", "babel-plugin-require-context-hook": "^1.0.0", - "babel-plugin-styled-components": "^1.13.3", + "babel-plugin-styled-components": "^2.0.2", "babel-plugin-transform-react-remove-prop-types": "^0.4.24", "backport": "^5.6.6", "callsites": "^3.1.0", diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel index fd12bb0e291d8..02e8bcf8ea144 100644 --- a/packages/BUILD.bazel +++ b/packages/BUILD.bazel @@ -87,6 +87,7 @@ filegroup( "//packages/kbn-config-schema:build_types", "//packages/kbn-crypto:build_types", "//packages/kbn-dev-utils:build_types", + "//packages/kbn-docs-utils:build_types", "//packages/kbn-i18n:build_types", "//packages/kbn-i18n-react:build_types", ], diff --git a/packages/kbn-cli-dev-mode/src/get_server_watch_paths.test.ts b/packages/kbn-cli-dev-mode/src/get_server_watch_paths.test.ts index 332042efb9eca..25bc59bf78458 100644 --- a/packages/kbn-cli-dev-mode/src/get_server_watch_paths.test.ts +++ b/packages/kbn-cli-dev-mode/src/get_server_watch_paths.test.ts @@ -66,7 +66,7 @@ it('produces the right watch and ignore list', () => { /x-pack/test/plugin_functional/plugins/resolver_test/target/**, /x-pack/test/plugin_functional/plugins/resolver_test/scripts/**, /x-pack/test/plugin_functional/plugins/resolver_test/docs/**, - /x-pack/plugins/reporting/chromium, + /x-pack/plugins/screenshotting/chromium, /x-pack/plugins/security_solution/cypress, /x-pack/plugins/apm/scripts, /x-pack/plugins/apm/ftr_e2e, diff --git a/packages/kbn-cli-dev-mode/src/get_server_watch_paths.ts b/packages/kbn-cli-dev-mode/src/get_server_watch_paths.ts index ba283730ebcac..acfc9aeecdc80 100644 --- a/packages/kbn-cli-dev-mode/src/get_server_watch_paths.ts +++ b/packages/kbn-cli-dev-mode/src/get_server_watch_paths.ts @@ -56,7 +56,7 @@ export function getServerWatchPaths({ pluginPaths, pluginScanDirs }: Options) { /\.(md|sh|txt)$/, /debug\.log$/, ...pluginInternalDirsIgnore, - fromRoot('x-pack/plugins/reporting/chromium'), + fromRoot('x-pack/plugins/screenshotting/chromium'), fromRoot('x-pack/plugins/security_solution/cypress'), fromRoot('x-pack/plugins/apm/scripts'), fromRoot('x-pack/plugins/apm/ftr_e2e'), // prevents restarts for APM cypress tests diff --git a/packages/kbn-docs-utils/BUILD.bazel b/packages/kbn-docs-utils/BUILD.bazel index a39f5bc9c404e..edfd3ee96c181 100644 --- a/packages/kbn-docs-utils/BUILD.bazel +++ b/packages/kbn-docs-utils/BUILD.bazel @@ -1,9 +1,10 @@ -load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project") -load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") -load("//src/dev/bazel:index.bzl", "jsts_transpiler") +load("@npm//@bazel/typescript:index.bzl", "ts_config") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") +load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") PKG_BASE_NAME = "kbn-docs-utils" PKG_REQUIRE_NAME = "@kbn/docs-utils" +TYPES_PKG_REQUIRE_NAME = "@types/kbn__docs-utils" SOURCE_FILES = glob( [ @@ -77,7 +78,7 @@ ts_project( js_library( name = PKG_BASE_NAME, srcs = NPM_MODULE_EXTRA_FILES, - deps = RUNTIME_DEPS + [":target_node", ":tsc_types"], + deps = RUNTIME_DEPS + [":target_node"], package_name = PKG_REQUIRE_NAME, visibility = ["//visibility:public"], ) @@ -96,3 +97,20 @@ filegroup( ], visibility = ["//visibility:public"], ) + +pkg_npm_types( + name = "npm_module_types", + srcs = SRCS, + deps = [":tsc_types"], + package_name = TYPES_PKG_REQUIRE_NAME, + tsconfig = ":tsconfig", + visibility = ["//visibility:public"], +) + +filegroup( + name = "build_types", + srcs = [ + ":npm_module_types", + ], + visibility = ["//visibility:public"], +) diff --git a/packages/kbn-docs-utils/package.json b/packages/kbn-docs-utils/package.json index dcff832583f59..84fc3ccb0cded 100644 --- a/packages/kbn-docs-utils/package.json +++ b/packages/kbn-docs-utils/package.json @@ -4,7 +4,6 @@ "license": "SSPL-1.0 OR Elastic License 2.0", "private": "true", "main": "target_node/index.js", - "types": "target_types/index.d.ts", "kibana": { "devOnly": true } diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 41c4d3bdd1b35..1de3a8a1b3976 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -117,3 +117,4 @@ pageLoadAssetSize: dataViewManagement: 5000 reporting: 57003 visTypeHeatmap: 25340 + screenshotting: 17017 diff --git a/packages/kbn-storybook/src/lib/theme_switcher.tsx b/packages/kbn-storybook/src/lib/theme_switcher.tsx index 3d6f7999545a0..8cc805ee2e494 100644 --- a/packages/kbn-storybook/src/lib/theme_switcher.tsx +++ b/packages/kbn-storybook/src/lib/theme_switcher.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React from 'react'; +import React, { useCallback, useEffect } from 'react'; import { Icons, IconButton, TooltipLinkList, WithTooltip } from '@storybook/components'; import { useGlobals } from '@storybook/api'; @@ -17,14 +17,52 @@ type Link = ArrayItem['links']>; const defaultTheme = 'v8.light'; export function ThemeSwitcher() { - const [globals, updateGlobals] = useGlobals(); - const selectedTheme = globals.euiTheme; + const [{ euiTheme: selectedTheme }, updateGlobals] = useGlobals(); - if (!selectedTheme) { - updateGlobals({ euiTheme: defaultTheme }); - } + const selectTheme = useCallback( + (themeId: string) => { + updateGlobals({ euiTheme: themeId }); + }, + [updateGlobals] + ); - function Menu({ onHide }: { onHide: () => void }) { + useEffect(() => { + if (!selectedTheme) { + selectTheme(defaultTheme); + } + }, [selectTheme, selectedTheme]); + + return ( + ( + + )} + > + {/* @ts-ignore Remove when @storybook has moved to @emotion v11 */} + + + + + ); +} + +const ThemeSwitcherTooltip = React.memo( + ({ + onHide, + onChangeSelectedTheme, + selectedTheme, + }: { + onHide: () => void; + onChangeSelectedTheme: (themeId: string) => void; + selectedTheme: string; + }) => { const links = [ { id: 'v8.light', @@ -38,8 +76,8 @@ export function ThemeSwitcher() { (link): Link => ({ ...link, onClick: (_event, item) => { - if (item.id !== selectedTheme) { - updateGlobals({ euiTheme: item.id }); + if (item.id != null && item.id !== selectedTheme) { + onChangeSelectedTheme(item.id); } onHide(); }, @@ -49,18 +87,4 @@ export function ThemeSwitcher() { return ; } - - return ( - } - > - {/* @ts-ignore Remove when @storybook has moved to @emotion v11 */} - - - - - ); -} +); diff --git a/src/dev/build/tasks/install_chromium.js b/src/dev/build/tasks/install_chromium.js index ad60019ea81a4..2bcceb33fad00 100644 --- a/src/dev/build/tasks/install_chromium.js +++ b/src/dev/build/tasks/install_chromium.js @@ -6,10 +6,8 @@ * Side Public License, v 1. */ -import { first } from 'rxjs/operators'; - // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { installBrowser } from '../../../../x-pack/plugins/reporting/server/browsers/install'; +import { install } from '../../../../x-pack/plugins/screenshotting/server/utils'; export const InstallChromium = { description: 'Installing Chromium', @@ -22,13 +20,23 @@ export const InstallChromium = { // revert after https://github.com/elastic/kibana/issues/109949 if (target === 'darwin-arm64') continue; - const { binaryPath$ } = installBrowser( - log, - build.resolvePathForPlatform(platform, 'x-pack/plugins/reporting/chromium'), + const logger = { + get: log.withType.bind(log), + debug: log.debug.bind(log), + info: log.info.bind(log), + warn: log.warning.bind(log), + trace: log.verbose.bind(log), + error: log.error.bind(log), + fatal: log.error.bind(log), + log: log.write.bind(log), + }; + + await install( + logger, + build.resolvePathForPlatform(platform, 'x-pack/plugins/screenshotting/chromium'), platform.getName(), platform.getArchitecture() ); - await binaryPath$.pipe(first()).toPromise(); } }, }; diff --git a/src/dev/code_coverage/ingest_coverage/__tests__/enumerate_patterns.test.js b/src/dev/code_coverage/ingest_coverage/__tests__/enumerate_patterns.test.js index beb90cd372718..40d36ed46ea34 100644 --- a/src/dev/code_coverage/ingest_coverage/__tests__/enumerate_patterns.test.js +++ b/src/dev/code_coverage/ingest_coverage/__tests__/enumerate_patterns.test.js @@ -16,16 +16,17 @@ const log = new ToolingLog({ }); describe(`enumeratePatterns`, () => { - it(`should resolve x-pack/plugins/reporting/server/browsers/extract/unzip.ts to kibana-reporting`, () => { + it(`should resolve x-pack/plugins/screenshotting/server/browsers/extract/unzip.ts to kibana-screenshotting`, () => { const actual = enumeratePatterns(REPO_ROOT)(log)( - new Map([['x-pack/plugins/reporting', ['kibana-reporting']]]) + new Map([['x-pack/plugins/screenshotting', ['kibana-screenshotting']]]) ); - expect( - actual[0].includes( - 'x-pack/plugins/reporting/server/browsers/extract/unzip.ts kibana-reporting' - ) - ).toBe(true); + expect(actual).toHaveProperty( + '0', + expect.arrayContaining([ + 'x-pack/plugins/screenshotting/server/browsers/extract/unzip.ts kibana-screenshotting', + ]) + ); }); it(`should resolve src/plugins/charts/common/static/color_maps/color_maps.ts to kibana-app`, () => { const actual = enumeratePatterns(REPO_ROOT)(log)( diff --git a/src/plugins/presentation_util/public/components/controls/control_group/component/control_frame_component.tsx b/src/plugins/presentation_util/public/components/controls/control_group/component/control_frame_component.tsx index f94d2f8fee0dc..bdc3b2978f888 100644 --- a/src/plugins/presentation_util/public/components/controls/control_group/component/control_frame_component.tsx +++ b/src/plugins/presentation_util/public/components/controls/control_group/component/control_frame_component.tsx @@ -70,6 +70,7 @@ export const ControlFrame = ({ customPrepend, enableActions, embeddableId }: Con openConfirm(ControlGroupStrings.management.deleteControls.getSubtitle(), { @@ -131,6 +132,7 @@ export const ControlFrame = ({ customPrepend, enableActions, embeddableId }: Con <> {embeddable && enableActions && floatingActions} { direction="row" responsive={false} alignItems="center" + data-test-subj="controls-group" + data-shared-items-count={idsInOrder.length} > { aria-label={ControlGroupStrings.management.getManageButtonTitle()} iconType="gear" color="text" - data-test-subj="inputControlsSortingButton" + data-test-subj="controls-sorting-button" onClick={() => { const flyoutInstance = openFlyout( forwardAllContext( @@ -198,7 +200,7 @@ export const ControlGroup = () => { ) : ( <> - +

{ControlGroupStrings.emptyState.getCallToAction()}

diff --git a/src/plugins/presentation_util/public/components/controls/control_group/component/control_group_sortable_item.tsx b/src/plugins/presentation_util/public/components/controls/control_group/component/control_group_sortable_item.tsx index 0ef9c4b7f115a..f4c28e840556a 100644 --- a/src/plugins/presentation_util/public/components/controls/control_group/component/control_group_sortable_item.tsx +++ b/src/plugins/presentation_util/public/components/controls/control_group/component/control_group_sortable_item.tsx @@ -76,6 +76,9 @@ const SortableControlInner = forwardRef< return ( - + {ControlTypeEditor && ( @@ -105,6 +105,7 @@ export const ControlEditor = ({ )} { @@ -147,6 +148,7 @@ export const ControlEditor = ({ { onCancel(); @@ -158,6 +160,7 @@ export const ControlEditor = ({ { if (getControlTypes().length > 1) { setIsControlTypePopoverOpen(!isControlTypePopoverOpen); @@ -132,15 +125,17 @@ export const CreateControlButton = ({ isIconButton }: { isIconButton: boolean }) createNewControl(getControlTypes()[0]); }; + const commonButtonProps = { + onClick: onCreateButtonClick, + color: 'primary' as EuiButtonIconColor, + 'data-test-subj': 'controls-create-button', + 'aria-label': ControlGroupStrings.management.getManageButtonTitle(), + }; + const createControlButton = isIconButton ? ( - + ) : ( - + {ControlGroupStrings.emptyState.getAddControlButtonTitle()} ); @@ -153,6 +148,7 @@ export const CreateControlButton = ({ isIconButton }: { isIconButton: boolean }) { setIsControlTypePopoverOpen(false); createNewControl(type); @@ -169,6 +165,7 @@ export const CreateControlButton = ({ isIconButton }: { isIconButton: boolean }) isOpen={isControlTypePopoverOpen} panelPaddingSize="none" anchorPosition="downLeft" + data-test-subj="control-type-picker" closePopover={() => setIsControlTypePopoverOpen(false)} > diff --git a/src/plugins/presentation_util/public/components/controls/control_group/editor/edit_control.tsx b/src/plugins/presentation_util/public/components/controls/control_group/editor/edit_control.tsx index 549d3c51b6e34..eb628049f7c93 100644 --- a/src/plugins/presentation_util/public/components/controls/control_group/editor/edit_control.tsx +++ b/src/plugins/presentation_util/public/components/controls/control_group/editor/edit_control.tsx @@ -132,6 +132,7 @@ export const EditControlButton = ({ embeddableId }: { embeddableId: string }) => return ( editControl()} diff --git a/src/plugins/presentation_util/public/components/controls/control_group/editor/editor_constants.ts b/src/plugins/presentation_util/public/components/controls/control_group/editor/editor_constants.ts index 812f794efc8c3..814e2a08cd931 100644 --- a/src/plugins/presentation_util/public/components/controls/control_group/editor/editor_constants.ts +++ b/src/plugins/presentation_util/public/components/controls/control_group/editor/editor_constants.ts @@ -14,18 +14,22 @@ export const DEFAULT_CONTROL_WIDTH: ControlWidth = 'auto'; export const CONTROL_WIDTH_OPTIONS = [ { id: `auto`, + 'data-test-subj': 'control-editor-width-auto', label: ControlGroupStrings.management.controlWidth.getAutoWidthTitle(), }, { id: `small`, + 'data-test-subj': 'control-editor-width-small', label: ControlGroupStrings.management.controlWidth.getSmallWidthTitle(), }, { id: `medium`, + 'data-test-subj': 'control-editor-width-medium', label: ControlGroupStrings.management.controlWidth.getMediumWidthTitle(), }, { id: `large`, + 'data-test-subj': 'control-editor-width-large', label: ControlGroupStrings.management.controlWidth.getLargeWidthTitle(), }, ]; diff --git a/src/plugins/presentation_util/public/components/controls/control_types/index.ts b/src/plugins/presentation_util/public/components/controls/control_types/index.ts new file mode 100644 index 0000000000000..141e9f9b4d55f --- /dev/null +++ b/src/plugins/presentation_util/public/components/controls/control_types/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './options_list'; diff --git a/src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list_component.tsx b/src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list_component.tsx index 43026a67eb946..1c79d1ce3e9b0 100644 --- a/src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list_component.tsx +++ b/src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list_component.tsx @@ -44,7 +44,9 @@ export const OptionsListComponent = ({ actions: { replaceSelection }, } = useReduxEmbeddableContext(); const dispatch = useEmbeddableDispatch(); - const { controlStyle, selectedOptions, singleSelect } = useEmbeddableSelector((state) => state); + const { controlStyle, selectedOptions, singleSelect, id } = useEmbeddableSelector( + (state) => state + ); // useStateObservable to get component state from Embeddable const { availableOptions, loading } = useStateObservable( @@ -90,6 +92,7 @@ export const OptionsListComponent = ({ 'optionsList--filterBtnSingle': controlStyle !== 'twoLine', 'optionsList--filterBtnPlaceholder': !selectedOptionsCount, })} + data-test-subj={`optionsList-control-${id}`} onClick={() => setIsPopoverOpen((openState) => !openState)} isSelected={isPopoverOpen} numActiveFilters={selectedOptionsCount} diff --git a/src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list_popover_component.tsx b/src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list_popover_component.tsx index a84d0460e9299..4aae049a5d446 100644 --- a/src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list_popover_component.tsx +++ b/src/plugins/presentation_util/public/components/controls/control_types/options_list/options_list_popover_component.tsx @@ -63,6 +63,7 @@ export const OptionsListPopover = ({ disabled={showOnlySelected} onChange={(event) => updateSearchString(event.target.value)} value={searchString} + data-test-subj="optionsList-control-search-input" /> @@ -74,6 +75,7 @@ export const OptionsListPopover = ({ size="s" color="danger" iconType="eraser" + data-test-subj="optionsList-control-clear-all-selections" aria-label={OptionsListStrings.popover.getClearAllSelectionsButtonTitle()} onClick={() => dispatch(clearSelections({}))} /> @@ -102,11 +104,16 @@ export const OptionsListPopover = ({
-
+
{!showOnlySelected && ( <> {availableOptions?.map((availableOption, index) => ( { diff --git a/src/plugins/presentation_util/public/components/controls/index.ts b/src/plugins/presentation_util/public/components/controls/index.ts index dbea24336699d..c110bc348498d 100644 --- a/src/plugins/presentation_util/public/components/controls/index.ts +++ b/src/plugins/presentation_util/public/components/controls/index.ts @@ -7,4 +7,5 @@ */ export * from './control_group'; +export * from './control_types'; export * from './types'; diff --git a/src/plugins/presentation_util/public/components/data_view_picker/data_view_picker.tsx b/src/plugins/presentation_util/public/components/data_view_picker/data_view_picker.tsx index 7b285944840c8..2911ae7a1e687 100644 --- a/src/plugins/presentation_util/public/components/data_view_picker/data_view_picker.tsx +++ b/src/plugins/presentation_util/public/components/data_view_picker/data_view_picker.tsx @@ -47,6 +47,7 @@ export function DataViewPicker({ return ( setPopoverIsOpen(!isPopoverOpen)} fullWidth {...colorProp} @@ -68,7 +69,7 @@ export function DataViewPicker({ ownFocus >
- + {i18n.translate('presentationUtil.dataViewPicker.changeDataViewTitle', { defaultMessage: 'Data view', })} @@ -86,6 +87,7 @@ export function DataViewPicker({ key: id, label: title, value: id, + 'data-test-subj': `data-view-picker-${title}`, checked: id === selectedDataViewId ? 'on' : undefined, }))} onChange={(choices) => { diff --git a/src/plugins/presentation_util/public/components/field_picker/field_picker.tsx b/src/plugins/presentation_util/public/components/field_picker/field_picker.tsx index fd83eeb0c8895..ebfbb24e7c390 100644 --- a/src/plugins/presentation_util/public/components/field_picker/field_picker.tsx +++ b/src/plugins/presentation_util/public/components/field_picker/field_picker.tsx @@ -88,6 +88,7 @@ export const FieldPicker = ({ return ( onSearchChange(event.currentTarget.value)} placeholder={searchPlaceholder} diff --git a/test/functional/apps/dashboard/dashboard_controls_integration.ts b/test/functional/apps/dashboard/dashboard_controls_integration.ts new file mode 100644 index 0000000000000..789d66fab6c86 --- /dev/null +++ b/test/functional/apps/dashboard/dashboard_controls_integration.ts @@ -0,0 +1,216 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import expect from '@kbn/expect'; + +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const retry = getService('retry'); + const security = getService('security'); + const queryBar = getService('queryBar'); + const pieChart = getService('pieChart'); + const filterBar = getService('filterBar'); + const esArchiver = getService('esArchiver'); + const testSubjects = getService('testSubjects'); + const kibanaServer = getService('kibanaServer'); + const dashboardAddPanel = getService('dashboardAddPanel'); + const { dashboardControls, timePicker, common, dashboard, header } = getPageObjects([ + 'dashboardControls', + 'timePicker', + 'dashboard', + 'common', + 'header', + ]); + + describe('Dashboard controls integration', () => { + before(async () => { + await esArchiver.load('test/functional/fixtures/es_archiver/dashboard/current/kibana'); + await security.testUser.setRoles(['kibana_admin', 'test_logstash_reader', 'animals']); + await kibanaServer.uiSettings.replace({ + defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', + }); + await common.navigateToApp('dashboard'); + await dashboardControls.enableControlsLab(); + await common.navigateToApp('dashboard'); + await dashboard.preserveCrossAppState(); + await dashboard.gotoDashboardLandingPage(); + await dashboard.clickNewDashboard(); + await timePicker.setDefaultDataRange(); + }); + + it('shows the empty control callout on a new dashboard', async () => { + await testSubjects.existOrFail('controls-empty'); + }); + + describe('Options List Control creation and editing experience', async () => { + it('can add a new options list control from a blank state', async () => { + await dashboardControls.createOptionsListControl({ fieldName: 'machine.os.raw' }); + expect(await dashboardControls.getControlsCount()).to.be(1); + }); + + it('can add a second options list control with a non-default data view', async () => { + await dashboardControls.createOptionsListControl({ + dataViewTitle: 'animals-*', + fieldName: 'sound.keyword', + }); + expect(await dashboardControls.getControlsCount()).to.be(2); + + // data views should be properly propagated from the control group to the dashboard + expect(await filterBar.getIndexPatterns()).to.be('logstash-*,animals-*'); + }); + + it('renames an existing control', async () => { + const secondId = (await dashboardControls.getAllControlIds())[1]; + + const newTitle = 'wow! Animal sounds?'; + await dashboardControls.editExistingControl(secondId); + await dashboardControls.controlEditorSetTitle(newTitle); + await dashboardControls.controlEditorSave(); + expect(await dashboardControls.doesControlTitleExist(newTitle)).to.be(true); + }); + + it('can change the data view and field of an existing options list', async () => { + const firstId = (await dashboardControls.getAllControlIds())[0]; + await dashboardControls.editExistingControl(firstId); + + await dashboardControls.optionsListEditorSetDataView('animals-*'); + await dashboardControls.optionsListEditorSetfield('animal.keyword'); + await dashboardControls.controlEditorSave(); + + // when creating a new filter, the ability to select a data view should be removed, because the dashboard now only has one data view + await testSubjects.click('addFilter'); + await testSubjects.missingOrFail('filterIndexPatternsSelect'); + await filterBar.ensureFieldEditorModalIsClosed(); + }); + + it('deletes an existing control', async () => { + const firstId = (await dashboardControls.getAllControlIds())[0]; + + await dashboardControls.removeExistingControl(firstId); + expect(await dashboardControls.getControlsCount()).to.be(1); + }); + + after(async () => { + const controlIds = await dashboardControls.getAllControlIds(); + for (const controlId of controlIds) { + await dashboardControls.removeExistingControl(controlId); + } + }); + }); + + describe('Interact with options list on dashboard', async () => { + before(async () => { + await dashboardAddPanel.addVisualization('Rendering-Test:-animal-sounds-pie'); + + await dashboardControls.createOptionsListControl({ + dataViewTitle: 'animals-*', + fieldName: 'sound.keyword', + title: 'Animal Sounds', + }); + }); + + it('Shows available options in options list', async () => { + const controlIds = await dashboardControls.getAllControlIds(); + await dashboardControls.optionsListOpenPopover(controlIds[0]); + await retry.try(async () => { + expect(await dashboardControls.optionsListPopoverGetAvailableOptionsCount()).to.be(8); + }); + await dashboardControls.optionsListEnsurePopoverIsClosed(controlIds[0]); + }); + + it('Can search options list for available options', async () => { + const controlIds = await dashboardControls.getAllControlIds(); + await dashboardControls.optionsListOpenPopover(controlIds[0]); + await dashboardControls.optionsListPopoverSearchForOption('meo'); + await retry.try(async () => { + expect(await dashboardControls.optionsListPopoverGetAvailableOptionsCount()).to.be(1); + expect(await dashboardControls.optionsListPopoverGetAvailableOptions()).to.eql(['meow']); + }); + await dashboardControls.optionsListPopoverClearSearch(); + await dashboardControls.optionsListEnsurePopoverIsClosed(controlIds[0]); + }); + + it('Applies dashboard query to options list control', async () => { + await queryBar.setQuery('isDog : true '); + await queryBar.submitQuery(); + await dashboard.waitForRenderComplete(); + await header.waitUntilLoadingHasFinished(); + + const controlIds = await dashboardControls.getAllControlIds(); + await dashboardControls.optionsListOpenPopover(controlIds[0]); + await retry.try(async () => { + expect(await dashboardControls.optionsListPopoverGetAvailableOptionsCount()).to.be(5); + expect(await dashboardControls.optionsListPopoverGetAvailableOptions()).to.eql([ + 'ruff', + 'bark', + 'grrr', + 'bow ow ow', + 'grr', + ]); + }); + + await queryBar.setQuery(''); + await queryBar.submitQuery(); + }); + + it('Applies dashboard filters to options list control', async () => { + await filterBar.addFilter('sound.keyword', 'is one of', ['bark', 'bow ow ow', 'ruff']); + await dashboard.waitForRenderComplete(); + await header.waitUntilLoadingHasFinished(); + + const controlIds = await dashboardControls.getAllControlIds(); + await dashboardControls.optionsListOpenPopover(controlIds[0]); + await retry.try(async () => { + expect(await dashboardControls.optionsListPopoverGetAvailableOptionsCount()).to.be(3); + expect(await dashboardControls.optionsListPopoverGetAvailableOptions()).to.eql([ + 'ruff', + 'bark', + 'bow ow ow', + ]); + }); + + await filterBar.removeAllFilters(); + }); + + it('Can select multiple available options', async () => { + const controlIds = await dashboardControls.getAllControlIds(); + await dashboardControls.optionsListOpenPopover(controlIds[0]); + await dashboardControls.optionsListPopoverSelectOption('hiss'); + await dashboardControls.optionsListPopoverSelectOption('grr'); + await dashboardControls.optionsListEnsurePopoverIsClosed(controlIds[0]); + }); + + it('Selected options appear in control', async () => { + const controlIds = await dashboardControls.getAllControlIds(); + const selectionString = await dashboardControls.optionsListGetSelectionsString( + controlIds[0] + ); + expect(selectionString).to.be('hiss, grr'); + }); + + it('Applies options list control options to dashboard', async () => { + expect(await pieChart.getPieSliceCount()).to.be(2); + }); + + it('Applies options list control options to dashboard by default on open', async () => { + await dashboard.gotoDashboardLandingPage(); + await header.waitUntilLoadingHasFinished(); + await dashboard.clickUnsavedChangesContinueEditing('New Dashboard'); + await header.waitUntilLoadingHasFinished(); + expect(await pieChart.getPieSliceCount()).to.be(2); + + const controlIds = await dashboardControls.getAllControlIds(); + const selectionString = await dashboardControls.optionsListGetSelectionsString( + controlIds[0] + ); + expect(selectionString).to.be('hiss, grr'); + }); + }); + }); +} diff --git a/test/functional/apps/dashboard/index.ts b/test/functional/apps/dashboard/index.ts index c9a62447f223a..73a8754982e4f 100644 --- a/test/functional/apps/dashboard/index.ts +++ b/test/functional/apps/dashboard/index.ts @@ -72,6 +72,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./full_screen_mode')); loadTestFile(require.resolve('./dashboard_filter_bar')); loadTestFile(require.resolve('./dashboard_filtering')); + loadTestFile(require.resolve('./dashboard_controls_integration')); loadTestFile(require.resolve('./panel_expand_toggle')); loadTestFile(require.resolve('./dashboard_grid')); loadTestFile(require.resolve('./view_edit')); diff --git a/test/functional/page_objects/dashboard_page_controls.ts b/test/functional/page_objects/dashboard_page_controls.ts new file mode 100644 index 0000000000000..2603608eebee9 --- /dev/null +++ b/test/functional/page_objects/dashboard_page_controls.ts @@ -0,0 +1,254 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { WebElementWrapper } from 'test/functional/services/lib/web_element_wrapper'; +import { OPTIONS_LIST_CONTROL } from '../../../src/plugins/presentation_util/common/controls/'; +import { ControlWidth } from '../../../src/plugins/presentation_util/public/components/controls'; + +import { FtrService } from '../ftr_provider_context'; + +export class DashboardPageControls extends FtrService { + private readonly log = this.ctx.getService('log'); + private readonly find = this.ctx.getService('find'); + private readonly retry = this.ctx.getService('retry'); + private readonly common = this.ctx.getPageObject('common'); + private readonly header = this.ctx.getPageObject('header'); + private readonly settings = this.ctx.getPageObject('settings'); + private readonly testSubjects = this.ctx.getService('testSubjects'); + + /* ----------------------------------------------------------- + General controls functions + ----------------------------------------------------------- */ + + public async enableControlsLab() { + await this.header.clickStackManagement(); + await this.settings.clickKibanaSettings(); + await this.settings.toggleAdvancedSettingCheckbox('labs:dashboard:dashboardControls'); + } + + public async expectControlsEmpty() { + await this.testSubjects.existOrFail('controls-empty'); + } + + public async getAllControlIds() { + const controlFrames = await this.testSubjects.findAll('control-frame'); + const ids = await Promise.all( + controlFrames.map(async (controlFrame) => await controlFrame.getAttribute('data-control-id')) + ); + this.log.debug('Got all control ids:', ids); + return ids; + } + + public async getAllControlTitles() { + const titleObjects = await this.testSubjects.findAll('control-frame-title'); + const titles = await Promise.all( + titleObjects.map(async (title) => (await title.getVisibleText()).split('\n')[0]) + ); + this.log.debug('Got all control titles:', titles); + return titles; + } + + public async doesControlTitleExist(title: string) { + const titles = await this.getAllControlTitles(); + return Boolean(titles.find((currentTitle) => currentTitle.indexOf(title))); + } + + public async getControlsCount() { + const allTitles = await this.getAllControlTitles(); + return allTitles.length; + } + + public async openCreateControlFlyout(type: string) { + this.log.debug(`Opening flyout for ${type} control`); + await this.testSubjects.click('controls-create-button'); + if (await this.testSubjects.exists('control-type-picker')) { + await this.testSubjects.click(`create-${type}-control`); + } + await this.retry.try(async () => { + await this.testSubjects.existOrFail('control-editor-flyout'); + }); + } + + /* ----------------------------------------------------------- + Individual controls functions + ----------------------------------------------------------- */ + + // Control Frame functions + public async getControlElementById(controlId: string): Promise { + const errorText = `Control frame ${controlId} could not be found`; + let controlElement: WebElementWrapper | undefined; + await this.retry.try(async () => { + const controlFrames = await this.testSubjects.findAll('control-frame'); + const framesWithIds = await Promise.all( + controlFrames.map(async (frame) => { + const id = await frame.getAttribute('data-control-id'); + return { id, element: frame }; + }) + ); + const foundControlFrame = framesWithIds.find(({ id }) => id === controlId); + if (!foundControlFrame) throw new Error(errorText); + controlElement = foundControlFrame.element; + }); + if (!controlElement) throw new Error(errorText); + return controlElement; + } + + public async hoverOverExistingControl(controlId: string) { + const elementToHover = await this.getControlElementById(controlId); + await this.retry.try(async () => { + await elementToHover.moveMouseTo(); + await this.testSubjects.existOrFail(`control-action-${controlId}-edit`); + }); + } + + public async editExistingControl(controlId: string) { + this.log.debug(`Opening control editor for control: ${controlId}`); + await this.hoverOverExistingControl(controlId); + await this.testSubjects.click(`control-action-${controlId}-edit`); + } + + public async removeExistingControl(controlId: string) { + this.log.debug(`Removing control: ${controlId}`); + await this.hoverOverExistingControl(controlId); + await this.testSubjects.click(`control-action-${controlId}-delete`); + await this.common.clickConfirmOnModal(); + } + + // Options list functions + public async optionsListGetSelectionsString(controlId: string) { + this.log.debug(`Getting selections string for Options List: ${controlId}`); + const controlElement = await this.getControlElementById(controlId); + return (await controlElement.getVisibleText()).split('\n')[1]; + } + + public async optionsListOpenPopover(controlId: string) { + this.log.debug(`Opening popover for Options List: ${controlId}`); + await this.testSubjects.click(`optionsList-control-${controlId}`); + await this.retry.try(async () => { + await this.testSubjects.existOrFail(`optionsList-control-available-options`); + }); + } + + public async optionsListEnsurePopoverIsClosed(controlId: string) { + this.log.debug(`Opening popover for Options List: ${controlId}`); + await this.testSubjects.click(`optionsList-control-${controlId}`); + await this.testSubjects.waitForDeleted(`optionsList-control-available-options`); + } + + public async optionsListPopoverAssertOpen() { + await this.retry.try(async () => { + if (!(await this.testSubjects.exists(`optionsList-control-available-options`))) { + throw new Error('options list popover must be open before calling selectOption'); + } + }); + } + + public async optionsListPopoverGetAvailableOptionsCount() { + this.log.debug(`getting available options count from options list`); + const availableOptions = await this.testSubjects.find(`optionsList-control-available-options`); + return +(await availableOptions.getAttribute('data-option-count')); + } + + public async optionsListPopoverGetAvailableOptions() { + this.log.debug(`getting available options count from options list`); + const availableOptions = await this.testSubjects.find(`optionsList-control-available-options`); + return (await availableOptions.getVisibleText()).split('\n'); + } + + public async optionsListPopoverSearchForOption(search: string) { + this.log.debug(`searching for ${search} in options list`); + await this.optionsListPopoverAssertOpen(); + await this.testSubjects.setValue(`optionsList-control-search-input`, search); + } + + public async optionsListPopoverClearSearch() { + this.log.debug(`clearing search from options list`); + await this.optionsListPopoverAssertOpen(); + await this.find.clickByCssSelector('.euiFormControlLayoutClearButton'); + } + + public async optionsListPopoverSelectOption(availableOption: string) { + this.log.debug(`selecting ${availableOption} from options list`); + await this.optionsListPopoverAssertOpen(); + await this.testSubjects.click(`optionsList-control-selection-${availableOption}`); + } + + public async optionsListPopoverClearSelections() { + this.log.debug(`clearing all selections from options list`); + await this.optionsListPopoverAssertOpen(); + await this.testSubjects.click(`optionsList-control-clear-all-selections`); + } + + /* ----------------------------------------------------------- + Control editor flyout + ----------------------------------------------------------- */ + + // Generic control editor functions + public async controlEditorSetTitle(title: string) { + this.log.debug(`Setting control title to ${title}`); + await this.testSubjects.setValue('control-editor-title-input', title); + } + + public async controlEditorSetWidth(width: ControlWidth) { + this.log.debug(`Setting control width to ${width}`); + await this.testSubjects.click(`control-editor-width-${width}`); + } + + public async controlEditorSave() { + this.log.debug(`Saving changes in control editor`); + await this.testSubjects.click(`control-editor-save`); + } + + public async controlEditorCancel() { + this.log.debug(`Canceling changes in control editor`); + await this.testSubjects.click(`control-editor-cancel`); + } + + // Options List editor functions + public async createOptionsListControl({ + dataViewTitle, + fieldName, + width, + title, + }: { + title?: string; + fieldName: string; + width?: ControlWidth; + dataViewTitle?: string; + }) { + this.log.debug(`Creating options list control ${title ?? fieldName}`); + await this.openCreateControlFlyout(OPTIONS_LIST_CONTROL); + + if (dataViewTitle) await this.optionsListEditorSetDataView(dataViewTitle); + if (fieldName) await this.optionsListEditorSetfield(fieldName); + if (title) await this.controlEditorSetTitle(title); + if (width) await this.controlEditorSetWidth(width); + + await this.controlEditorSave(); + } + + public async optionsListEditorSetDataView(dataViewTitle: string) { + this.log.debug(`Setting options list data view to ${dataViewTitle}`); + await this.testSubjects.click('open-data-view-picker'); + await this.retry.try(async () => { + await this.testSubjects.existOrFail('data-view-picker-title'); + }); + await this.testSubjects.click(`data-view-picker-${dataViewTitle}`); + } + + public async optionsListEditorSetfield(fieldName: string, shouldSearch: boolean = false) { + this.log.debug(`Setting options list field to ${fieldName}`); + if (shouldSearch) { + await this.testSubjects.setValue('field-search-input', fieldName); + } + await this.retry.try(async () => { + await this.testSubjects.existOrFail(`field-picker-select-${fieldName}`); + }); + await this.testSubjects.click(`field-picker-select-${fieldName}`); + } +} diff --git a/test/functional/page_objects/index.ts b/test/functional/page_objects/index.ts index cda2c7de44d3b..826c4b78d1d0f 100644 --- a/test/functional/page_objects/index.ts +++ b/test/functional/page_objects/index.ts @@ -30,12 +30,14 @@ import { VegaChartPageObject } from './vega_chart_page'; import { SavedObjectsPageObject } from './management/saved_objects_page'; import { LegacyDataTableVisPageObject } from './legacy/data_table_vis'; import { IndexPatternFieldEditorPageObject } from './management/indexpattern_field_editor_page'; +import { DashboardPageControls } from './dashboard_page_controls'; export const pageObjects = { common: CommonPageObject, console: ConsolePageObject, context: ContextPageObject, dashboard: DashboardPageObject, + dashboardControls: DashboardPageControls, discover: DiscoverPageObject, error: ErrorPageObject, header: HeaderPageObject, diff --git a/x-pack/.gitignore b/x-pack/.gitignore index 9a02a9e552b40..0e0e9aba84467 100644 --- a/x-pack/.gitignore +++ b/x-pack/.gitignore @@ -6,7 +6,7 @@ /test/functional/apps/**/reports/session /test/reporting/configs/failure_debug/ /plugins/reporting/.chromium/ -/plugins/reporting/chromium/ +/plugins/screenshotting/chromium/ /plugins/reporting/.phantom/ /.aws-config.json /.env diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index b51363f1b7006..aac29086fe53d 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -46,6 +46,7 @@ "xpack.reporting": ["plugins/reporting"], "xpack.rollupJobs": ["plugins/rollup"], "xpack.runtimeFields": "plugins/runtime_fields", + "xpack.screenshotting": "plugins/screenshotting", "xpack.searchProfiler": "plugins/searchprofiler", "xpack.security": "plugins/security", "xpack.server": "legacy/server", diff --git a/x-pack/examples/reporting_example/kibana.json b/x-pack/examples/reporting_example/kibana.json index 716c6ea29c2a0..94780f1df0b36 100644 --- a/x-pack/examples/reporting_example/kibana.json +++ b/x-pack/examples/reporting_example/kibana.json @@ -10,5 +10,6 @@ }, "description": "Example integration code for applications to feature reports.", "optionalPlugins": [], - "requiredPlugins": ["reporting", "developerExamples", "navigation", "screenshotMode", "share"] + "requiredPlugins": ["reporting", "developerExamples", "navigation", "screenshotMode", "share"], + "requiredBundles": ["screenshotting"] } diff --git a/x-pack/examples/reporting_example/public/containers/main.tsx b/x-pack/examples/reporting_example/public/containers/main.tsx index c6723c9839197..5f6cd816e9db3 100644 --- a/x-pack/examples/reporting_example/public/containers/main.tsx +++ b/x-pack/examples/reporting_example/public/containers/main.tsx @@ -39,7 +39,8 @@ import type { JobParamsPDFV2, JobParamsPNGV2, } from '../../../../plugins/reporting/public'; -import { constants, ReportingStart } from '../../../../plugins/reporting/public'; +import { LayoutTypes } from '../../../../plugins/screenshotting/public'; +import { ReportingStart } from '../../../../plugins/reporting/public'; import { REPORTING_EXAMPLE_LOCATOR_ID } from '../../common'; @@ -87,7 +88,7 @@ export const Main = ({ basename, reporting, screenshotMode }: ReportingExampleAp const getPDFJobParamsDefault = (): JobAppParamsPDF => { return { layout: { - id: constants.LAYOUT_TYPES.PRESERVE_LAYOUT, + id: LayoutTypes.PRESERVE_LAYOUT, }, relativeUrls: ['/app/reportingExample#/intended-visualization'], objectType: 'develeloperExample', @@ -99,7 +100,7 @@ export const Main = ({ basename, reporting, screenshotMode }: ReportingExampleAp return { version: '8.0.0', layout: { - id: constants.LAYOUT_TYPES.PRESERVE_LAYOUT, + id: LayoutTypes.PRESERVE_LAYOUT, }, locatorParams: [ { id: REPORTING_EXAMPLE_LOCATOR_ID, version: '0.5.0', params: { myTestState: {} } }, @@ -114,7 +115,7 @@ export const Main = ({ basename, reporting, screenshotMode }: ReportingExampleAp return { version: '8.0.0', layout: { - id: constants.LAYOUT_TYPES.PRESERVE_LAYOUT, + id: LayoutTypes.PRESERVE_LAYOUT, }, locatorParams: { id: REPORTING_EXAMPLE_LOCATOR_ID, @@ -131,7 +132,7 @@ export const Main = ({ basename, reporting, screenshotMode }: ReportingExampleAp return { version: '8.0.0', layout: { - id: constants.LAYOUT_TYPES.PRESERVE_LAYOUT, + id: LayoutTypes.PRESERVE_LAYOUT, }, locatorParams: { id: REPORTING_EXAMPLE_LOCATOR_ID, @@ -148,7 +149,7 @@ export const Main = ({ basename, reporting, screenshotMode }: ReportingExampleAp return { version: '8.0.0', layout: { - id: print ? constants.LAYOUT_TYPES.PRINT : constants.LAYOUT_TYPES.PRESERVE_LAYOUT, + id: print ? LayoutTypes.PRINT : LayoutTypes.PRESERVE_LAYOUT, dimensions: { // Magic numbers based on height of components not rendered on this screen :( height: 2400, diff --git a/x-pack/plugins/apm/server/deprecations/index.ts b/x-pack/plugins/apm/server/deprecations/index.ts index 06d04eb037d73..6c6567440f267 100644 --- a/x-pack/plugins/apm/server/deprecations/index.ts +++ b/x-pack/plugins/apm/server/deprecations/index.ts @@ -51,7 +51,7 @@ export function getDeprecations({ }), message: i18n.translate('xpack.apm.deprecations.message', { defaultMessage: - 'Running the APM Server binary directly is considered a legacy option and is deprecated since 7.16. Switch to APM Server managed by an Elastic Agent instead. Read our documentation to learn more.', + 'Running the APM Server binary directly is considered a legacy option and will be deprecated and removed in the future.', }), documentationUrl: `https://www.elastic.co/guide/en/apm/server/${docBranch}/apm-integration.html`, level: 'warning', @@ -68,7 +68,7 @@ export function getDeprecations({ }), i18n.translate('xpack.apm.deprecations.steps.switch', { defaultMessage: - 'Click "Switch to data streams". You will be guided through the process', + 'Click "Switch to Elastic Agent". You will be guided through the process', }), ], }, diff --git a/x-pack/plugins/cases/common/index.ts b/x-pack/plugins/cases/common/index.ts index d38b1a779981c..41bba2ee2194d 100644 --- a/x-pack/plugins/cases/common/index.ts +++ b/x-pack/plugins/cases/common/index.ts @@ -5,11 +5,28 @@ * 2.0. */ -// TODO: https://github.com/elastic/kibana/issues/110896 -/* eslint-disable @kbn/eslint/no_export_all */ - -export * from './constants'; -export * from './api'; -export * from './ui/types'; -export * from './utils/connectors_api'; -export * from './utils/user_actions'; +// Careful of exporting anything from this file as any file(s) you export here will cause your page bundle size to increase. +// If you're using functions/types/etc... internally or within integration tests it's best to import directly from their paths +// than expose the functions/types/etc... here. You should _only_ expose functions/types/etc... that need to be shared with other plugins here. + +// When you do have to add things here you might want to consider creating a package such as kbn-cases-constants to share with +// other plugins instead as packages are easier to break down and you do not have to carry the cost of extra plugin weight on +// first download since the other plugins/areas of your code can directly pull from the package in their async imports. +// For example, constants below could eventually be in a "kbn-cases-constants" instead. +// See: https://docs.elastic.dev/kibana-dev-docs/key-concepts/platform-intro#public-plugin-api + +export { CASES_URL, SECURITY_SOLUTION_OWNER, ENABLE_CASE_CONNECTOR } from './constants'; + +export { CommentType, CaseStatuses, getCasesFromAlertsUrl, throwErrors } from './api'; + +export type { + SubCase, + Case, + Ecs, + CasesContextValue, + CaseViewRefreshPropInterface, +} from './ui/types'; + +export { StatusAll } from './ui/types'; + +export { getCreateConnectorUrl, getAllConnectorsUrl } from './utils/connectors_api'; diff --git a/x-pack/plugins/cases/common/utils/connectors_api.ts b/x-pack/plugins/cases/common/utils/connectors_api.ts index f9f85bbfb0127..3ab8f856d925e 100644 --- a/x-pack/plugins/cases/common/utils/connectors_api.ts +++ b/x-pack/plugins/cases/common/utils/connectors_api.ts @@ -9,7 +9,7 @@ * Actions and connectors API endpoint helpers */ -import { ACTION_URL, ACTION_TYPES_URL, CONNECTORS_URL } from '../../common'; +import { ACTION_URL, ACTION_TYPES_URL, CONNECTORS_URL } from '../../common/constants'; /** * diff --git a/x-pack/plugins/cases/public/common/lib/kibana/hooks.ts b/x-pack/plugins/cases/public/common/lib/kibana/hooks.ts index 49d03d44a3a4f..08eb2ebf3df7a 100644 --- a/x-pack/plugins/cases/public/common/lib/kibana/hooks.ts +++ b/x-pack/plugins/cases/public/common/lib/kibana/hooks.ts @@ -10,7 +10,7 @@ import moment from 'moment-timezone'; import { useCallback, useEffect, useState } from 'react'; import { i18n } from '@kbn/i18n'; -import { DEFAULT_DATE_FORMAT, DEFAULT_DATE_FORMAT_TZ } from '../../../../common'; +import { DEFAULT_DATE_FORMAT, DEFAULT_DATE_FORMAT_TZ } from '../../../../common/constants'; import { AuthenticatedUser } from '../../../../../security/common/model'; import { convertToCamelCase } from '../../../containers/utils'; import { StartServices } from '../../../types'; diff --git a/x-pack/plugins/cases/public/common/mock/register_connectors.ts b/x-pack/plugins/cases/public/common/mock/register_connectors.ts index 42e7cd4a85e40..b86968e4bf801 100644 --- a/x-pack/plugins/cases/public/common/mock/register_connectors.ts +++ b/x-pack/plugins/cases/public/common/mock/register_connectors.ts @@ -8,7 +8,7 @@ import { TriggersAndActionsUIPublicPluginStart } from '../../../../triggers_actions_ui/public'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { actionTypeRegistryMock } from '../../../../triggers_actions_ui/public/application/action_type_registry.mock'; -import { CaseActionConnector } from '../../../common'; +import { CaseActionConnector } from '../../../common/ui/types'; const getUniqueActionTypeIds = (connectors: CaseActionConnector[]) => new Set(connectors.map((connector) => connector.actionTypeId)); diff --git a/x-pack/plugins/cases/public/common/mock/test_providers.tsx b/x-pack/plugins/cases/public/common/mock/test_providers.tsx index d016dce48a24e..c076ca28c9318 100644 --- a/x-pack/plugins/cases/public/common/mock/test_providers.tsx +++ b/x-pack/plugins/cases/public/common/mock/test_providers.tsx @@ -10,7 +10,8 @@ import { merge } from 'lodash'; import { euiDarkVars } from '@kbn/ui-shared-deps-src/theme'; import { I18nProvider } from '@kbn/i18n-react'; import { ThemeProvider } from 'styled-components'; -import { CasesContextValue, DEFAULT_FEATURES, SECURITY_SOLUTION_OWNER } from '../../../common'; +import { DEFAULT_FEATURES, SECURITY_SOLUTION_OWNER } from '../../../common/constants'; +import { CasesContextValue } from '../../../common/ui/types'; import { CasesProvider } from '../../components/cases_context'; import { createKibanaContextProviderMock } from '../lib/kibana/kibana_react.mock'; import { FieldHook } from '../shared_imports'; diff --git a/x-pack/plugins/cases/public/common/user_actions/parsers.test.ts b/x-pack/plugins/cases/public/common/user_actions/parsers.test.ts index c6d13cc41686c..e2d24bf19f3d4 100644 --- a/x-pack/plugins/cases/public/common/user_actions/parsers.test.ts +++ b/x-pack/plugins/cases/public/common/user_actions/parsers.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ConnectorTypes, noneConnectorId } from '../../../common'; +import { ConnectorTypes, noneConnectorId } from '../../../common/api'; import { parseStringAsConnector, parseStringAsExternalService } from './parsers'; describe('user actions utility functions', () => { diff --git a/x-pack/plugins/cases/public/common/user_actions/parsers.ts b/x-pack/plugins/cases/public/common/user_actions/parsers.ts index dfea22443aa51..0384a97124c54 100644 --- a/x-pack/plugins/cases/public/common/user_actions/parsers.ts +++ b/x-pack/plugins/cases/public/common/user_actions/parsers.ts @@ -12,7 +12,7 @@ import { noneConnectorId, CaseFullExternalService, CaseUserActionExternalServiceRt, -} from '../../../common'; +} from '../../../common/api'; export const parseStringAsConnector = ( id: string | null, diff --git a/x-pack/plugins/cases/public/components/add_comment/index.test.tsx b/x-pack/plugins/cases/public/components/add_comment/index.test.tsx index c15722a3ec354..f1167504628c4 100644 --- a/x-pack/plugins/cases/public/components/add_comment/index.test.tsx +++ b/x-pack/plugins/cases/public/components/add_comment/index.test.tsx @@ -12,7 +12,8 @@ import { noop } from 'lodash/fp'; import { TestProviders } from '../../common/mock'; -import { CommentRequest, CommentType, SECURITY_SOLUTION_OWNER } from '../../../common'; +import { CommentRequest, CommentType } from '../../../common/api'; +import { SECURITY_SOLUTION_OWNER } from '../../../common/constants'; import { usePostComment } from '../../containers/use_post_comment'; import { AddComment, AddCommentProps, AddCommentRefObject } from '.'; import { CasesTimelineIntegrationProvider } from '../timeline_context'; diff --git a/x-pack/plugins/cases/public/components/add_comment/index.tsx b/x-pack/plugins/cases/public/components/add_comment/index.tsx index 37f21e0949288..83bd187e7863a 100644 --- a/x-pack/plugins/cases/public/components/add_comment/index.tsx +++ b/x-pack/plugins/cases/public/components/add_comment/index.tsx @@ -17,7 +17,7 @@ import { EuiButton, EuiFlexItem, EuiFlexGroup, EuiLoadingSpinner } from '@elasti import styled from 'styled-components'; import { isEmpty } from 'lodash'; -import { CommentType } from '../../../common'; +import { CommentType } from '../../../common/api'; import { usePostComment } from '../../containers/use_post_comment'; import { Case } from '../../containers/types'; import { EuiMarkdownEditorRef, MarkdownEditorForm } from '../markdown_editor'; diff --git a/x-pack/plugins/cases/public/components/add_comment/schema.tsx b/x-pack/plugins/cases/public/components/add_comment/schema.tsx index 9693219dd5196..3e32c8a938b68 100644 --- a/x-pack/plugins/cases/public/components/add_comment/schema.tsx +++ b/x-pack/plugins/cases/public/components/add_comment/schema.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { CommentRequestUserType } from '../../../common'; +import { CommentRequestUserType } from '../../../common/api'; import { FIELD_TYPES, fieldValidators, FormSchema } from '../../common/shared_imports'; import * as i18n from './translations'; diff --git a/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx b/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx index bf02202ff83b2..85e33402ebe45 100644 --- a/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx @@ -20,7 +20,9 @@ import { connectorsMock, } from '../../containers/mock'; -import { CaseStatuses, CaseType, SECURITY_SOLUTION_OWNER, StatusAll } from '../../../common'; +import { StatusAll } from '../../../common/ui/types'; +import { CaseStatuses, CaseType } from '../../../common/api'; +import { SECURITY_SOLUTION_OWNER } from '../../../common/constants'; import { getEmptyTagValue } from '../empty_value'; import { useDeleteCases } from '../../containers/use_delete_cases'; import { useGetCases } from '../../containers/use_get_cases'; diff --git a/x-pack/plugins/cases/public/components/all_cases/all_cases_list.tsx b/x-pack/plugins/cases/public/components/all_cases/all_cases_list.tsx index 58c17695d0dfe..b3631155f1b6e 100644 --- a/x-pack/plugins/cases/public/components/all_cases/all_cases_list.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/all_cases_list.tsx @@ -13,15 +13,12 @@ import classnames from 'classnames'; import { Case, - CaseStatuses, - CaseType, - CommentRequestAlertType, CaseStatusWithAllStatus, FilterOptions, SortFieldCase, SubCase, - caseStatuses, -} from '../../../common'; +} from '../../../common/ui/types'; +import { CaseStatuses, CaseType, CommentRequestAlertType, caseStatuses } from '../../../common/api'; import { SELECTABLE_MESSAGE_COLLECTIONS } from '../../common/translations'; import { useGetCases } from '../../containers/use_get_cases'; import { usePostComment } from '../../containers/use_post_comment'; diff --git a/x-pack/plugins/cases/public/components/all_cases/columns.tsx b/x-pack/plugins/cases/public/components/all_cases/columns.tsx index c30ddd199fc49..684b9644a7879 100644 --- a/x-pack/plugins/cases/public/components/all_cases/columns.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/columns.tsx @@ -22,16 +22,14 @@ import { import { RIGHT_ALIGNMENT } from '@elastic/eui/lib/services'; import styled from 'styled-components'; +import { Case, DeleteCase, SubCase } from '../../../common/ui/types'; import { CaseStatuses, CaseType, CommentType, CommentRequestAlertType, - DeleteCase, - Case, - SubCase, ActionConnector, -} from '../../../common'; +} from '../../../common/api'; import { getEmptyTagValue } from '../empty_value'; import { FormattedRelativePreferenceDate } from '../formatted_date'; import { CaseDetailsLink } from '../links'; diff --git a/x-pack/plugins/cases/public/components/all_cases/count.tsx b/x-pack/plugins/cases/public/components/all_cases/count.tsx index eb33cf1069a9b..1f6e71c377ee6 100644 --- a/x-pack/plugins/cases/public/components/all_cases/count.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/count.tsx @@ -7,7 +7,7 @@ import React, { FunctionComponent, useEffect } from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { CaseStatuses } from '../../../common'; +import { CaseStatuses } from '../../../common/api'; import { Stats } from '../status'; import { useGetCasesStatus } from '../../containers/use_get_cases_status'; diff --git a/x-pack/plugins/cases/public/components/all_cases/expanded_row.tsx b/x-pack/plugins/cases/public/components/all_cases/expanded_row.tsx index 2b43fbf63095e..4719c2ce3db82 100644 --- a/x-pack/plugins/cases/public/components/all_cases/expanded_row.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/expanded_row.tsx @@ -10,7 +10,7 @@ import { EuiBasicTable } from '@elastic/eui'; import styled from 'styled-components'; import { Case, SubCase } from '../../containers/types'; import { CasesColumns } from './columns'; -import { AssociationType } from '../../../common'; +import { AssociationType } from '../../../common/api'; type ExpandedRowMap = Record | {}; diff --git a/x-pack/plugins/cases/public/components/all_cases/helpers.ts b/x-pack/plugins/cases/public/components/all_cases/helpers.ts index ca5b2e422c15c..f84f19d3030ae 100644 --- a/x-pack/plugins/cases/public/components/all_cases/helpers.ts +++ b/x-pack/plugins/cases/public/components/all_cases/helpers.ts @@ -6,7 +6,7 @@ */ import { filter } from 'lodash/fp'; -import { AssociationType, CaseStatuses, CaseType } from '../../../common'; +import { AssociationType, CaseStatuses, CaseType } from '../../../common/api'; import { Case, SubCase } from '../../containers/types'; import { statuses } from '../status'; diff --git a/x-pack/plugins/cases/public/components/all_cases/index.test.tsx b/x-pack/plugins/cases/public/components/all_cases/index.test.tsx index 681bb65870c1e..9decb3a58f831 100644 --- a/x-pack/plugins/cases/public/components/all_cases/index.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/index.test.tsx @@ -16,7 +16,7 @@ import { useGetReporters } from '../../containers/use_get_reporters'; import { useGetActionLicense } from '../../containers/use_get_action_license'; import { useConnectors } from '../../containers/configure/use_connectors'; import { useKibana } from '../../common/lib/kibana'; -import { CaseStatuses } from '../../../common'; +import { CaseStatuses } from '../../../common/api'; import { casesStatus, connectorsMock, useGetCasesMockState } from '../../containers/mock'; import { registerConnectorsToMockActionRegistry } from '../../common/mock/register_connectors'; import { useGetCases } from '../../containers/use_get_cases'; diff --git a/x-pack/plugins/cases/public/components/all_cases/selector_modal/all_cases_selector_modal.test.tsx b/x-pack/plugins/cases/public/components/all_cases/selector_modal/all_cases_selector_modal.test.tsx index fb062fe101db5..33eddeccb59b2 100644 --- a/x-pack/plugins/cases/public/components/all_cases/selector_modal/all_cases_selector_modal.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/selector_modal/all_cases_selector_modal.test.tsx @@ -11,7 +11,7 @@ import { mount } from 'enzyme'; import { AllCasesSelectorModal } from '.'; import { TestProviders } from '../../../common/mock'; import { AllCasesList } from '../all_cases_list'; -import { SECURITY_SOLUTION_OWNER } from '../../../../common'; +import { SECURITY_SOLUTION_OWNER } from '../../../../common/constants'; jest.mock('../../../methods'); jest.mock('../all_cases_list'); diff --git a/x-pack/plugins/cases/public/components/all_cases/selector_modal/all_cases_selector_modal.tsx b/x-pack/plugins/cases/public/components/all_cases/selector_modal/all_cases_selector_modal.tsx index 227dd88c6f5a2..5db6531d8e140 100644 --- a/x-pack/plugins/cases/public/components/all_cases/selector_modal/all_cases_selector_modal.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/selector_modal/all_cases_selector_modal.tsx @@ -15,12 +15,8 @@ import { EuiModalHeaderTitle, } from '@elastic/eui'; import styled from 'styled-components'; -import { - Case, - CaseStatusWithAllStatus, - CommentRequestAlertType, - SubCase, -} from '../../../../common'; +import { Case, SubCase, CaseStatusWithAllStatus } from '../../../../common/ui/types'; +import { CommentRequestAlertType } from '../../../../common/api'; import * as i18n from '../../../common/translations'; import { AllCasesList } from '../all_cases_list'; export interface AllCasesSelectorModalProps { diff --git a/x-pack/plugins/cases/public/components/all_cases/status_filter.test.tsx b/x-pack/plugins/cases/public/components/all_cases/status_filter.test.tsx index 5d975c51c6569..5471c03a6f181 100644 --- a/x-pack/plugins/cases/public/components/all_cases/status_filter.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/status_filter.test.tsx @@ -9,7 +9,8 @@ import React from 'react'; import { mount } from 'enzyme'; import { waitFor } from '@testing-library/react'; -import { CaseStatuses, StatusAll } from '../../../common'; +import { StatusAll } from '../../../common/ui/types'; +import { CaseStatuses } from '../../../common/api'; import { StatusFilter } from './status_filter'; const stats = { diff --git a/x-pack/plugins/cases/public/components/all_cases/status_filter.tsx b/x-pack/plugins/cases/public/components/all_cases/status_filter.tsx index bb54fbe410951..71359c2e50582 100644 --- a/x-pack/plugins/cases/public/components/all_cases/status_filter.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/status_filter.tsx @@ -8,7 +8,7 @@ import React, { memo } from 'react'; import { EuiSuperSelect, EuiSuperSelectOption, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { Status, statuses } from '../status'; -import { CaseStatusWithAllStatus, StatusAll } from '../../../common'; +import { CaseStatusWithAllStatus, StatusAll } from '../../../common/ui/types'; interface Props { stats: Record; diff --git a/x-pack/plugins/cases/public/components/all_cases/table.tsx b/x-pack/plugins/cases/public/components/all_cases/table.tsx index 40d61007f9056..94a44add3402f 100644 --- a/x-pack/plugins/cases/public/components/all_cases/table.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/table.tsx @@ -18,7 +18,7 @@ import styled from 'styled-components'; import { CasesTableUtilityBar } from './utility_bar'; import { LinkButton } from '../links'; -import { AllCases, Case, FilterOptions } from '../../../common'; +import { AllCases, Case, FilterOptions } from '../../../common/ui/types'; import * as i18n from './translations'; import { useCreateCaseNavigation } from '../../common/navigation'; diff --git a/x-pack/plugins/cases/public/components/all_cases/table_filters.test.tsx b/x-pack/plugins/cases/public/components/all_cases/table_filters.test.tsx index f71009a37b747..2d14ffe5738ca 100644 --- a/x-pack/plugins/cases/public/components/all_cases/table_filters.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/table_filters.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { mount } from 'enzyme'; -import { CaseStatuses } from '../../../common'; +import { CaseStatuses } from '../../../common/api'; import { TestProviders } from '../../common/mock'; import { useGetTags } from '../../containers/use_get_tags'; import { useGetReporters } from '../../containers/use_get_reporters'; diff --git a/x-pack/plugins/cases/public/components/all_cases/table_filters.tsx b/x-pack/plugins/cases/public/components/all_cases/table_filters.tsx index 47ab4cb210778..e1ed709e0d93f 100644 --- a/x-pack/plugins/cases/public/components/all_cases/table_filters.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/table_filters.tsx @@ -10,7 +10,8 @@ import { isEqual } from 'lodash/fp'; import styled from 'styled-components'; import { EuiFlexGroup, EuiFlexItem, EuiFieldSearch, EuiFilterGroup } from '@elastic/eui'; -import { CaseStatuses, CaseStatusWithAllStatus, StatusAll } from '../../../common'; +import { StatusAll, CaseStatusWithAllStatus } from '../../../common/ui/types'; +import { CaseStatuses } from '../../../common/api'; import { FilterOptions } from '../../containers/types'; import { useGetTags } from '../../containers/use_get_tags'; import { useGetReporters } from '../../containers/use_get_reporters'; diff --git a/x-pack/plugins/cases/public/components/all_cases/utility_bar.tsx b/x-pack/plugins/cases/public/components/all_cases/utility_bar.tsx index 26430482bc067..b6ab44517bb66 100644 --- a/x-pack/plugins/cases/public/components/all_cases/utility_bar.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/utility_bar.tsx @@ -15,7 +15,7 @@ import { UtilityBarText, } from '../utility_bar'; import * as i18n from './translations'; -import { AllCases, Case, DeleteCase, FilterOptions } from '../../../common'; +import { AllCases, Case, DeleteCase, FilterOptions } from '../../../common/ui/types'; import { getBulkItems } from '../bulk_actions'; import { isSelectedCasesIncludeCollections } from './helpers'; import { useDeleteCases } from '../../containers/use_delete_cases'; diff --git a/x-pack/plugins/cases/public/components/app/types.ts b/x-pack/plugins/cases/public/components/app/types.ts index 9c825ad95618a..ebe174c095fa7 100644 --- a/x-pack/plugins/cases/public/components/app/types.ts +++ b/x-pack/plugins/cases/public/components/app/types.ts @@ -6,7 +6,7 @@ */ import { MutableRefObject } from 'react'; -import { Ecs, CaseViewRefreshPropInterface } from '../../../common'; +import { Ecs, CaseViewRefreshPropInterface } from '../../../common/ui/types'; import { CasesNavigation } from '../links'; import { CasesTimelineIntegration } from '../timeline_context'; diff --git a/x-pack/plugins/cases/public/components/bulk_actions/index.tsx b/x-pack/plugins/cases/public/components/bulk_actions/index.tsx index 751a45a706ef7..c8dbe2adaca0b 100644 --- a/x-pack/plugins/cases/public/components/bulk_actions/index.tsx +++ b/x-pack/plugins/cases/public/components/bulk_actions/index.tsx @@ -8,7 +8,8 @@ import React from 'react'; import { EuiContextMenuItem } from '@elastic/eui'; -import { CaseStatuses, CaseStatusWithAllStatus } from '../../../common'; +import { CaseStatusWithAllStatus } from '../../../common/ui/types'; +import { CaseStatuses } from '../../../common/api'; import { statuses } from '../status'; import * as i18n from './translations'; import { Case } from '../../containers/types'; diff --git a/x-pack/plugins/cases/public/components/case_action_bar/actions.tsx b/x-pack/plugins/cases/public/components/case_action_bar/actions.tsx index ada2b61c816db..4cad00535d165 100644 --- a/x-pack/plugins/cases/public/components/case_action_bar/actions.tsx +++ b/x-pack/plugins/cases/public/components/case_action_bar/actions.tsx @@ -11,7 +11,7 @@ import * as i18n from '../case_view/translations'; import { useDeleteCases } from '../../containers/use_delete_cases'; import { ConfirmDeleteCaseModal } from '../confirm_delete_case'; import { PropertyActions } from '../property_actions'; -import { Case } from '../../../common'; +import { Case } from '../../../common/ui/types'; import { CaseService } from '../../containers/use_get_case_user_actions'; import { useAllCasesNavigation } from '../../common/navigation'; diff --git a/x-pack/plugins/cases/public/components/case_action_bar/helpers.test.ts b/x-pack/plugins/cases/public/components/case_action_bar/helpers.test.ts index ed5832d19b4da..f04ef94405db8 100644 --- a/x-pack/plugins/cases/public/components/case_action_bar/helpers.test.ts +++ b/x-pack/plugins/cases/public/components/case_action_bar/helpers.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { CaseStatuses } from '../../../common'; +import { CaseStatuses } from '../../../common/api'; import { basicCase } from '../../containers/mock'; import { getStatusDate, getStatusTitle } from './helpers'; diff --git a/x-pack/plugins/cases/public/components/case_action_bar/helpers.ts b/x-pack/plugins/cases/public/components/case_action_bar/helpers.ts index 35cfdae3abe21..b26c33b0fd009 100644 --- a/x-pack/plugins/cases/public/components/case_action_bar/helpers.ts +++ b/x-pack/plugins/cases/public/components/case_action_bar/helpers.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { CaseStatuses } from '../../../common'; +import { CaseStatuses } from '../../../common/api'; import { Case } from '../../containers/types'; import { statuses } from '../status'; diff --git a/x-pack/plugins/cases/public/components/case_action_bar/index.tsx b/x-pack/plugins/cases/public/components/case_action_bar/index.tsx index 9b326f3216084..ac81dfea2fd93 100644 --- a/x-pack/plugins/cases/public/components/case_action_bar/index.tsx +++ b/x-pack/plugins/cases/public/components/case_action_bar/index.tsx @@ -16,7 +16,8 @@ import { EuiFlexItem, EuiIconTip, } from '@elastic/eui'; -import { Case, CaseStatuses, CaseType } from '../../../common'; +import { Case } from '../../../common/ui/types'; +import { CaseStatuses, CaseType } from '../../../common/api'; import * as i18n from '../case_view/translations'; import { FormattedRelativePreferenceDate } from '../formatted_date'; import { Actions } from './actions'; diff --git a/x-pack/plugins/cases/public/components/case_action_bar/status_context_menu.test.tsx b/x-pack/plugins/cases/public/components/case_action_bar/status_context_menu.test.tsx index 93ecf4df997d2..4a67eada2e00d 100644 --- a/x-pack/plugins/cases/public/components/case_action_bar/status_context_menu.test.tsx +++ b/x-pack/plugins/cases/public/components/case_action_bar/status_context_menu.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { mount } from 'enzyme'; -import { CaseStatuses } from '../../../common'; +import { CaseStatuses } from '../../../common/api'; import { StatusContextMenu } from './status_context_menu'; describe('SyncAlertsSwitch', () => { diff --git a/x-pack/plugins/cases/public/components/case_action_bar/status_context_menu.tsx b/x-pack/plugins/cases/public/components/case_action_bar/status_context_menu.tsx index ab86f589bfdd0..193ef4a708e38 100644 --- a/x-pack/plugins/cases/public/components/case_action_bar/status_context_menu.tsx +++ b/x-pack/plugins/cases/public/components/case_action_bar/status_context_menu.tsx @@ -7,7 +7,7 @@ import React, { memo, useCallback, useMemo, useState } from 'react'; import { EuiPopover, EuiContextMenuPanel, EuiContextMenuItem } from '@elastic/eui'; -import { caseStatuses, CaseStatuses } from '../../../common'; +import { caseStatuses, CaseStatuses } from '../../../common/api'; import { Status } from '../status'; import { CHANGE_STATUS } from '../all_cases/translations'; diff --git a/x-pack/plugins/cases/public/components/case_view/helpers.test.tsx b/x-pack/plugins/cases/public/components/case_view/helpers.test.tsx index bf5a9fe5d0a22..e398c5edad145 100644 --- a/x-pack/plugins/cases/public/components/case_view/helpers.test.tsx +++ b/x-pack/plugins/cases/public/components/case_view/helpers.test.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import { AssociationType, CommentType, SECURITY_SOLUTION_OWNER } from '../../../common'; +import { AssociationType, CommentType } from '../../../common/api'; +import { SECURITY_SOLUTION_OWNER } from '../../../common/constants'; import { Comment } from '../../containers/types'; import { getManualAlertIdsWithNoRuleId } from './helpers'; diff --git a/x-pack/plugins/cases/public/components/case_view/helpers.ts b/x-pack/plugins/cases/public/components/case_view/helpers.ts index ab26b132e0489..7f3924ef2564c 100644 --- a/x-pack/plugins/cases/public/components/case_view/helpers.ts +++ b/x-pack/plugins/cases/public/components/case_view/helpers.ts @@ -6,7 +6,7 @@ */ import { isEmpty } from 'lodash'; -import { CommentType } from '../../../common'; +import { CommentType } from '../../../common/api'; import { Comment } from '../../containers/types'; export const getManualAlertIdsWithNoRuleId = (comments: Comment[]): string[] => { diff --git a/x-pack/plugins/cases/public/components/case_view/index.test.tsx b/x-pack/plugins/cases/public/components/case_view/index.test.tsx index aaf4928703896..daa3ad4416200 100644 --- a/x-pack/plugins/cases/public/components/case_view/index.test.tsx +++ b/x-pack/plugins/cases/public/components/case_view/index.test.tsx @@ -27,7 +27,7 @@ import { useGetCaseUserActions } from '../../containers/use_get_case_user_action import { useConnectors } from '../../containers/configure/use_connectors'; import { connectorsMock } from '../../containers/configure/mock'; import { usePostPushToService } from '../../containers/use_post_push_to_service'; -import { CaseType, ConnectorTypes } from '../../../common'; +import { CaseType, ConnectorTypes } from '../../../common/api'; import { useKibana } from '../../common/lib/kibana'; jest.mock('../../containers/use_update_case'); diff --git a/x-pack/plugins/cases/public/components/case_view/index.tsx b/x-pack/plugins/cases/public/components/case_view/index.tsx index 2b78c31242ba6..c436547c9e2bd 100644 --- a/x-pack/plugins/cases/public/components/case_view/index.tsx +++ b/x-pack/plugins/cases/public/components/case_view/index.tsx @@ -9,15 +9,8 @@ import React, { useCallback, useEffect, useMemo, useState, useRef, MutableRefObj import styled from 'styled-components'; import { EuiFlexGroup, EuiFlexItem, EuiLoadingContent, EuiLoadingSpinner } from '@elastic/eui'; -import { - CaseStatuses, - CaseAttributes, - CaseType, - Case, - CaseConnector, - Ecs, - CaseViewRefreshPropInterface, -} from '../../../common'; +import { Case, Ecs, CaseViewRefreshPropInterface } from '../../../common/ui/types'; +import { CaseStatuses, CaseAttributes, CaseType, CaseConnector } from '../../../common/api'; import { HeaderPage } from '../header_page'; import { EditableTitle } from '../header_page/editable_title'; import { TagList } from '../tag_list'; diff --git a/x-pack/plugins/cases/public/components/cases_context/index.tsx b/x-pack/plugins/cases/public/components/cases_context/index.tsx index 588bda245b044..ecc5719cb81b7 100644 --- a/x-pack/plugins/cases/public/components/cases_context/index.tsx +++ b/x-pack/plugins/cases/public/components/cases_context/index.tsx @@ -7,7 +7,8 @@ import React, { useState, useEffect } from 'react'; import { merge } from 'lodash'; -import { CasesContextValue, DEFAULT_FEATURES } from '../../../common'; +import { CasesContextValue } from '../../../common/ui/types'; +import { DEFAULT_FEATURES } from '../../../common/constants'; import { DEFAULT_BASE_PATH } from '../../common/navigation'; import { useApplication } from './use_application'; diff --git a/x-pack/plugins/cases/public/components/configure_cases/__mock__/index.tsx b/x-pack/plugins/cases/public/components/configure_cases/__mock__/index.tsx index 983e32ba508fb..49ac373724336 100644 --- a/x-pack/plugins/cases/public/components/configure_cases/__mock__/index.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/__mock__/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { ActionTypeConnector, ConnectorTypes } from '../../../../common'; +import { ActionTypeConnector, ConnectorTypes } from '../../../../common/api'; import { ActionConnector } from '../../../containers/configure/types'; import { UseConnectorsResponse } from '../../../containers/configure/use_connectors'; import { ReturnUseCaseConfigure } from '../../../containers/configure/use_configure'; diff --git a/x-pack/plugins/cases/public/components/configure_cases/connectors.test.tsx b/x-pack/plugins/cases/public/components/configure_cases/connectors.test.tsx index 9bbddfae2f9bd..7a6bca518ac3e 100644 --- a/x-pack/plugins/cases/public/components/configure_cases/connectors.test.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/connectors.test.tsx @@ -13,7 +13,7 @@ import { Connectors, Props } from './connectors'; import { TestProviders } from '../../common/mock'; import { ConnectorsDropdown } from './connectors_dropdown'; import { connectors, actionTypes } from './__mock__'; -import { ConnectorTypes } from '../../../common'; +import { ConnectorTypes } from '../../../common/api'; import { useKibana } from '../../common/lib/kibana'; import { registerConnectorsToMockActionRegistry } from '../../common/mock/register_connectors'; diff --git a/x-pack/plugins/cases/public/components/configure_cases/connectors.tsx b/x-pack/plugins/cases/public/components/configure_cases/connectors.tsx index b7bf7c322f76e..11026acde2bf6 100644 --- a/x-pack/plugins/cases/public/components/configure_cases/connectors.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/connectors.tsx @@ -21,7 +21,7 @@ import * as i18n from './translations'; import { ActionConnector, CaseConnectorMapping } from '../../containers/configure/types'; import { Mapping } from './mapping'; -import { ActionTypeConnector, ConnectorTypes } from '../../../common'; +import { ActionTypeConnector, ConnectorTypes } from '../../../common/api'; import { DeprecatedCallout } from '../connectors/deprecated_callout'; import { isDeprecatedConnector } from '../utils'; diff --git a/x-pack/plugins/cases/public/components/configure_cases/connectors_dropdown.tsx b/x-pack/plugins/cases/public/components/configure_cases/connectors_dropdown.tsx index c7ce3c5b3c4b6..af518e3c773b6 100644 --- a/x-pack/plugins/cases/public/components/configure_cases/connectors_dropdown.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/connectors_dropdown.tsx @@ -9,7 +9,7 @@ import React, { useMemo } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiIconTip, EuiSuperSelect } from '@elastic/eui'; import styled from 'styled-components'; -import { ConnectorTypes } from '../../../common'; +import { ConnectorTypes } from '../../../common/api'; import { ActionConnector } from '../../containers/configure/types'; import * as i18n from './translations'; import { useKibana } from '../../common/lib/kibana'; diff --git a/x-pack/plugins/cases/public/components/configure_cases/index.test.tsx b/x-pack/plugins/cases/public/components/configure_cases/index.test.tsx index 55983df8f347d..918252369c26b 100644 --- a/x-pack/plugins/cases/public/components/configure_cases/index.test.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/index.test.tsx @@ -26,7 +26,7 @@ import { useConnectorsResponse, useActionTypesResponse, } from './__mock__'; -import { ConnectorTypes } from '../../../common'; +import { ConnectorTypes } from '../../../common/api'; import { actionTypeRegistryMock } from '../../../../triggers_actions_ui/public/application/action_type_registry.mock'; jest.mock('../../common/lib/kibana'); diff --git a/x-pack/plugins/cases/public/components/configure_cases/index.tsx b/x-pack/plugins/cases/public/components/configure_cases/index.tsx index 6b19fd911d10d..44c1979aa0fda 100644 --- a/x-pack/plugins/cases/public/components/configure_cases/index.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/index.tsx @@ -11,7 +11,7 @@ import styled, { css } from 'styled-components'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiCallOut, EuiLink } from '@elastic/eui'; -import { SUPPORTED_CONNECTORS } from '../../../common'; +import { SUPPORTED_CONNECTORS } from '../../../common/constants'; import { useKibana } from '../../common/lib/kibana'; import { useConnectors } from '../../containers/configure/use_connectors'; import { useActionTypes } from '../../containers/configure/use_action_types'; diff --git a/x-pack/plugins/cases/public/components/configure_cases/utils.ts b/x-pack/plugins/cases/public/components/configure_cases/utils.ts index 6597417b5068a..d7de06e9c5aee 100644 --- a/x-pack/plugins/cases/public/components/configure_cases/utils.ts +++ b/x-pack/plugins/cases/public/components/configure_cases/utils.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ConnectorTypeFields, ConnectorTypes } from '../../../common'; +import { ConnectorTypeFields, ConnectorTypes } from '../../../common/api'; import { CaseField, ActionType, diff --git a/x-pack/plugins/cases/public/components/connector_selector/form.tsx b/x-pack/plugins/cases/public/components/connector_selector/form.tsx index 71a65ae030d9d..05db3474fdb99 100644 --- a/x-pack/plugins/cases/public/components/connector_selector/form.tsx +++ b/x-pack/plugins/cases/public/components/connector_selector/form.tsx @@ -12,7 +12,7 @@ import styled from 'styled-components'; import { FieldHook, getFieldValidityAndErrorMessage } from '../../common/shared_imports'; import { ConnectorsDropdown } from '../configure_cases/connectors_dropdown'; -import { ActionConnector } from '../../../common'; +import { ActionConnector } from '../../../common/api'; interface ConnectorSelectorProps { connectors: ActionConnector[]; diff --git a/x-pack/plugins/cases/public/components/connectors/card.test.tsx b/x-pack/plugins/cases/public/components/connectors/card.test.tsx index 384442814ffef..7a07e87a1da4c 100644 --- a/x-pack/plugins/cases/public/components/connectors/card.test.tsx +++ b/x-pack/plugins/cases/public/components/connectors/card.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { mount } from 'enzyme'; -import { ConnectorTypes } from '../../../common'; +import { ConnectorTypes } from '../../../common/api'; import { useKibana } from '../../common/lib/kibana'; import { connectors } from '../configure_cases/__mock__'; import { ConnectorCard } from './card'; diff --git a/x-pack/plugins/cases/public/components/connectors/card.tsx b/x-pack/plugins/cases/public/components/connectors/card.tsx index ec4b52c54f707..9870c77fda743 100644 --- a/x-pack/plugins/cases/public/components/connectors/card.tsx +++ b/x-pack/plugins/cases/public/components/connectors/card.tsx @@ -9,7 +9,7 @@ import React, { memo, useMemo } from 'react'; import { EuiCard, EuiFlexGroup, EuiFlexItem, EuiIcon, EuiLoadingSpinner } from '@elastic/eui'; import styled from 'styled-components'; -import { ConnectorTypes } from '../../../common'; +import { ConnectorTypes } from '../../../common/api'; import { useKibana } from '../../common/lib/kibana'; import { getConnectorIcon } from '../utils'; diff --git a/x-pack/plugins/cases/public/components/connectors/case/alert_fields.tsx b/x-pack/plugins/cases/public/components/connectors/case/alert_fields.tsx index a330ae339b338..7cd9b5f6a367c 100644 --- a/x-pack/plugins/cases/public/components/connectors/case/alert_fields.tsx +++ b/x-pack/plugins/cases/public/components/connectors/case/alert_fields.tsx @@ -12,7 +12,7 @@ import styled from 'styled-components'; import { EuiCallOut, EuiSpacer } from '@elastic/eui'; import { ActionParamsProps } from '../../../../../triggers_actions_ui/public/types'; -import { CommentType } from '../../../../common'; +import { CommentType } from '../../../../common/api'; import { CaseActionParams } from './types'; import { ExistingCase } from './existing_case'; diff --git a/x-pack/plugins/cases/public/components/connectors/fields_form.tsx b/x-pack/plugins/cases/public/components/connectors/fields_form.tsx index 062695fa41cc2..56c56436c08c7 100644 --- a/x-pack/plugins/cases/public/components/connectors/fields_form.tsx +++ b/x-pack/plugins/cases/public/components/connectors/fields_form.tsx @@ -11,7 +11,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; import { CaseActionConnector } from '../types'; import { ConnectorFieldsProps } from './types'; import { getCaseConnectors } from '.'; -import { ConnectorTypeFields } from '../../../common'; +import { ConnectorTypeFields } from '../../../common/api'; interface Props extends Omit, 'connector'> { connector: CaseActionConnector | null; diff --git a/x-pack/plugins/cases/public/components/connectors/index.ts b/x-pack/plugins/cases/public/components/connectors/index.ts index 3aa10c56dd8e9..0d5e33a818d3a 100644 --- a/x-pack/plugins/cases/public/components/connectors/index.ts +++ b/x-pack/plugins/cases/public/components/connectors/index.ts @@ -17,7 +17,7 @@ import { ServiceNowSIRFieldsType, ResilientFieldsType, SwimlaneFieldsType, -} from '../../../common'; +} from '../../../common/api'; export { getActionType as getCaseConnectorUi } from './case'; diff --git a/x-pack/plugins/cases/public/components/connectors/jira/case_fields.tsx b/x-pack/plugins/cases/public/components/connectors/jira/case_fields.tsx index 6aff81f380015..b9326a08330cd 100644 --- a/x-pack/plugins/cases/public/components/connectors/jira/case_fields.tsx +++ b/x-pack/plugins/cases/public/components/connectors/jira/case_fields.tsx @@ -10,7 +10,7 @@ import { map } from 'lodash/fp'; import { EuiFormRow, EuiSelect, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import * as i18n from './translations'; -import { ConnectorTypes, JiraFieldsType } from '../../../../common'; +import { ConnectorTypes, JiraFieldsType } from '../../../../common/api'; import { useKibana } from '../../../common/lib/kibana'; import { ConnectorFieldsProps } from '../types'; import { useGetIssueTypes } from './use_get_issue_types'; diff --git a/x-pack/plugins/cases/public/components/connectors/jira/index.ts b/x-pack/plugins/cases/public/components/connectors/jira/index.ts index d59d20177c14d..afb53ffcb87cf 100644 --- a/x-pack/plugins/cases/public/components/connectors/jira/index.ts +++ b/x-pack/plugins/cases/public/components/connectors/jira/index.ts @@ -8,7 +8,7 @@ import { lazy } from 'react'; import { CaseConnector } from '../types'; -import { ConnectorTypes, JiraFieldsType } from '../../../../common'; +import { ConnectorTypes, JiraFieldsType } from '../../../../common/api'; import * as i18n from './translations'; export * from './types'; diff --git a/x-pack/plugins/cases/public/components/connectors/jira/search_issues.tsx b/x-pack/plugins/cases/public/components/connectors/jira/search_issues.tsx index 61866d126dfd7..a9ed87fa81346 100644 --- a/x-pack/plugins/cases/public/components/connectors/jira/search_issues.tsx +++ b/x-pack/plugins/cases/public/components/connectors/jira/search_issues.tsx @@ -9,7 +9,7 @@ import React, { useMemo, useEffect, useCallback, useState, memo } from 'react'; import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; import { useKibana } from '../../../common/lib/kibana'; -import { ActionConnector } from '../../../../common'; +import { ActionConnector } from '../../../../common/api'; import { useGetIssues } from './use_get_issues'; import { useGetSingleIssue } from './use_get_single_issue'; import * as i18n from './translations'; diff --git a/x-pack/plugins/cases/public/components/connectors/jira/use_get_fields_by_issue_type.tsx b/x-pack/plugins/cases/public/components/connectors/jira/use_get_fields_by_issue_type.tsx index d762c9d3aaf20..f3d14f02ca1f3 100644 --- a/x-pack/plugins/cases/public/components/connectors/jira/use_get_fields_by_issue_type.tsx +++ b/x-pack/plugins/cases/public/components/connectors/jira/use_get_fields_by_issue_type.tsx @@ -7,7 +7,7 @@ import { useState, useEffect, useRef } from 'react'; import { HttpSetup, IToasts } from 'kibana/public'; -import { ActionConnector } from '../../../../common'; +import { ActionConnector } from '../../../../common/api'; import { getFieldsByIssueType } from './api'; import { Fields } from './types'; import * as i18n from './translations'; diff --git a/x-pack/plugins/cases/public/components/connectors/jira/use_get_issue_types.tsx b/x-pack/plugins/cases/public/components/connectors/jira/use_get_issue_types.tsx index 6f409f1ddef8d..6322b59527e4e 100644 --- a/x-pack/plugins/cases/public/components/connectors/jira/use_get_issue_types.tsx +++ b/x-pack/plugins/cases/public/components/connectors/jira/use_get_issue_types.tsx @@ -7,7 +7,7 @@ import { useState, useEffect, useRef } from 'react'; import { HttpSetup, IToasts } from 'kibana/public'; -import { ActionConnector } from '../../../../common'; +import { ActionConnector } from '../../../../common/api'; import { getIssueTypes } from './api'; import { IssueTypes } from './types'; import * as i18n from './translations'; diff --git a/x-pack/plugins/cases/public/components/connectors/jira/use_get_issues.tsx b/x-pack/plugins/cases/public/components/connectors/jira/use_get_issues.tsx index e4b6f5e4dea01..f4ab31c9daa2d 100644 --- a/x-pack/plugins/cases/public/components/connectors/jira/use_get_issues.tsx +++ b/x-pack/plugins/cases/public/components/connectors/jira/use_get_issues.tsx @@ -8,7 +8,7 @@ import { isEmpty, debounce } from 'lodash/fp'; import { useState, useEffect, useRef } from 'react'; import { HttpSetup, ToastsApi } from 'kibana/public'; -import { ActionConnector } from '../../../../common'; +import { ActionConnector } from '../../../../common/api'; import { getIssues } from './api'; import { Issues } from './types'; import * as i18n from './translations'; diff --git a/x-pack/plugins/cases/public/components/connectors/jira/use_get_single_issue.tsx b/x-pack/plugins/cases/public/components/connectors/jira/use_get_single_issue.tsx index e26940a40d39f..857b07e41d2f2 100644 --- a/x-pack/plugins/cases/public/components/connectors/jira/use_get_single_issue.tsx +++ b/x-pack/plugins/cases/public/components/connectors/jira/use_get_single_issue.tsx @@ -7,7 +7,7 @@ import { useState, useEffect, useRef } from 'react'; import { HttpSetup, ToastsApi } from 'kibana/public'; -import { ActionConnector } from '../../../../common'; +import { ActionConnector } from '../../../../common/api'; import { getIssue } from './api'; import { Issue } from './types'; import * as i18n from './translations'; diff --git a/x-pack/plugins/cases/public/components/connectors/mock.ts b/x-pack/plugins/cases/public/components/connectors/mock.ts index 663b397e6f4fe..2882622b29269 100644 --- a/x-pack/plugins/cases/public/components/connectors/mock.ts +++ b/x-pack/plugins/cases/public/components/connectors/mock.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { SwimlaneConnectorType } from '../../../common'; +import { SwimlaneConnectorType } from '../../../common/api'; export const connector = { id: '123', diff --git a/x-pack/plugins/cases/public/components/connectors/resilient/case_fields.tsx b/x-pack/plugins/cases/public/components/connectors/resilient/case_fields.tsx index 44f06f92093dd..9dc76fb48cf17 100644 --- a/x-pack/plugins/cases/public/components/connectors/resilient/case_fields.tsx +++ b/x-pack/plugins/cases/public/components/connectors/resilient/case_fields.tsx @@ -21,7 +21,7 @@ import { useGetIncidentTypes } from './use_get_incident_types'; import { useGetSeverity } from './use_get_severity'; import * as i18n from './translations'; -import { ConnectorTypes, ResilientFieldsType } from '../../../../common'; +import { ConnectorTypes, ResilientFieldsType } from '../../../../common/api'; import { ConnectorCard } from '../card'; const ResilientFieldsComponent: React.FunctionComponent> = diff --git a/x-pack/plugins/cases/public/components/connectors/resilient/index.ts b/x-pack/plugins/cases/public/components/connectors/resilient/index.ts index 8a429c0dea091..0da7448e62a65 100644 --- a/x-pack/plugins/cases/public/components/connectors/resilient/index.ts +++ b/x-pack/plugins/cases/public/components/connectors/resilient/index.ts @@ -8,7 +8,7 @@ import { lazy } from 'react'; import { CaseConnector } from '../types'; -import { ConnectorTypes, ResilientFieldsType } from '../../../../common'; +import { ConnectorTypes, ResilientFieldsType } from '../../../../common/api'; import * as i18n from './translations'; export * from './types'; diff --git a/x-pack/plugins/cases/public/components/connectors/resilient/use_get_incident_types.tsx b/x-pack/plugins/cases/public/components/connectors/resilient/use_get_incident_types.tsx index 530b56de8796d..588e2ee715a88 100644 --- a/x-pack/plugins/cases/public/components/connectors/resilient/use_get_incident_types.tsx +++ b/x-pack/plugins/cases/public/components/connectors/resilient/use_get_incident_types.tsx @@ -7,7 +7,7 @@ import { useState, useEffect, useRef } from 'react'; import { HttpSetup, ToastsApi } from 'kibana/public'; -import { ActionConnector } from '../../../../common'; +import { ActionConnector } from '../../../../common/api'; import { getIncidentTypes } from './api'; import * as i18n from './translations'; diff --git a/x-pack/plugins/cases/public/components/connectors/resilient/use_get_severity.tsx b/x-pack/plugins/cases/public/components/connectors/resilient/use_get_severity.tsx index 8753e3926ffe5..1d647ca1848fe 100644 --- a/x-pack/plugins/cases/public/components/connectors/resilient/use_get_severity.tsx +++ b/x-pack/plugins/cases/public/components/connectors/resilient/use_get_severity.tsx @@ -7,7 +7,7 @@ import { useState, useEffect, useRef } from 'react'; import { HttpSetup, ToastsApi } from 'kibana/public'; -import { ActionConnector } from '../../../../common'; +import { ActionConnector } from '../../../../common/api'; import { getSeverity } from './api'; import * as i18n from './translations'; diff --git a/x-pack/plugins/cases/public/components/connectors/servicenow/index.ts b/x-pack/plugins/cases/public/components/connectors/servicenow/index.ts index 88afd902ccf60..1c466d08e9bcb 100644 --- a/x-pack/plugins/cases/public/components/connectors/servicenow/index.ts +++ b/x-pack/plugins/cases/public/components/connectors/servicenow/index.ts @@ -12,7 +12,7 @@ import { ConnectorTypes, ServiceNowITSMFieldsType, ServiceNowSIRFieldsType, -} from '../../../../common'; +} from '../../../../common/api'; import * as i18n from './translations'; export const getServiceNowITSMCaseConnector = (): CaseConnector => ({ diff --git a/x-pack/plugins/cases/public/components/connectors/servicenow/servicenow_itsm_case_fields.tsx b/x-pack/plugins/cases/public/components/connectors/servicenow/servicenow_itsm_case_fields.tsx index e24b25065a1c8..521b8609b4eac 100644 --- a/x-pack/plugins/cases/public/components/connectors/servicenow/servicenow_itsm_case_fields.tsx +++ b/x-pack/plugins/cases/public/components/connectors/servicenow/servicenow_itsm_case_fields.tsx @@ -10,7 +10,7 @@ import { EuiFormRow, EuiSelect, EuiSpacer, EuiFlexGroup, EuiFlexItem } from '@el import * as i18n from './translations'; import { ConnectorFieldsProps } from '../types'; -import { ConnectorTypes, ServiceNowITSMFieldsType } from '../../../../common'; +import { ConnectorTypes, ServiceNowITSMFieldsType } from '../../../../common/api'; import { useKibana } from '../../../common/lib/kibana'; import { ConnectorCard } from '../card'; import { useGetChoices } from './use_get_choices'; diff --git a/x-pack/plugins/cases/public/components/connectors/servicenow/servicenow_sir_case_fields.tsx b/x-pack/plugins/cases/public/components/connectors/servicenow/servicenow_sir_case_fields.tsx index d502b7382664b..095393adb77cb 100644 --- a/x-pack/plugins/cases/public/components/connectors/servicenow/servicenow_sir_case_fields.tsx +++ b/x-pack/plugins/cases/public/components/connectors/servicenow/servicenow_sir_case_fields.tsx @@ -8,7 +8,7 @@ import React, { useCallback, useEffect, useMemo, useState, useRef } from 'react'; import { EuiFormRow, EuiSelect, EuiFlexGroup, EuiFlexItem, EuiCheckbox } from '@elastic/eui'; -import { ConnectorTypes, ServiceNowSIRFieldsType } from '../../../../common'; +import { ConnectorTypes, ServiceNowSIRFieldsType } from '../../../../common/api'; import { useKibana } from '../../../common/lib/kibana'; import { ConnectorFieldsProps } from '../types'; import { ConnectorCard } from '../card'; diff --git a/x-pack/plugins/cases/public/components/connectors/servicenow/use_get_choices.test.tsx b/x-pack/plugins/cases/public/components/connectors/servicenow/use_get_choices.test.tsx index 9f88da9f35eb5..950b17d6f784f 100644 --- a/x-pack/plugins/cases/public/components/connectors/servicenow/use_get_choices.test.tsx +++ b/x-pack/plugins/cases/public/components/connectors/servicenow/use_get_choices.test.tsx @@ -8,7 +8,7 @@ import { renderHook } from '@testing-library/react-hooks'; import { useKibana } from '../../../common/lib/kibana'; -import { ActionConnector } from '../../../../common'; +import { ActionConnector } from '../../../../common/api'; import { choices } from '../mock'; import { useGetChoices, UseGetChoices, UseGetChoicesProps } from './use_get_choices'; import * as api from './api'; diff --git a/x-pack/plugins/cases/public/components/connectors/servicenow/use_get_choices.tsx b/x-pack/plugins/cases/public/components/connectors/servicenow/use_get_choices.tsx index 2c6181dd08eb1..fa8e648a0981e 100644 --- a/x-pack/plugins/cases/public/components/connectors/servicenow/use_get_choices.tsx +++ b/x-pack/plugins/cases/public/components/connectors/servicenow/use_get_choices.tsx @@ -7,7 +7,7 @@ import { useState, useEffect, useRef } from 'react'; import { HttpSetup, IToasts } from 'kibana/public'; -import { ActionConnector } from '../../../../common'; +import { ActionConnector } from '../../../../common/api'; import { getChoices } from './api'; import { Choice } from './types'; import * as i18n from './translations'; diff --git a/x-pack/plugins/cases/public/components/connectors/swimlane/case_fields.test.tsx b/x-pack/plugins/cases/public/components/connectors/swimlane/case_fields.test.tsx index 1a035d92611bd..cca74b83ddb80 100644 --- a/x-pack/plugins/cases/public/components/connectors/swimlane/case_fields.test.tsx +++ b/x-pack/plugins/cases/public/components/connectors/swimlane/case_fields.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; -import { SwimlaneConnectorType } from '../../../../common'; +import { SwimlaneConnectorType } from '../../../../common/api'; import Fields from './case_fields'; import * as i18n from './translations'; import { swimlaneConnector as connector } from '../mock'; diff --git a/x-pack/plugins/cases/public/components/connectors/swimlane/case_fields.tsx b/x-pack/plugins/cases/public/components/connectors/swimlane/case_fields.tsx index b6370504edbb6..a7e584f7c22e2 100644 --- a/x-pack/plugins/cases/public/components/connectors/swimlane/case_fields.tsx +++ b/x-pack/plugins/cases/public/components/connectors/swimlane/case_fields.tsx @@ -9,7 +9,7 @@ import React, { useMemo } from 'react'; import { EuiCallOut } from '@elastic/eui'; import * as i18n from './translations'; -import { ConnectorTypes, SwimlaneFieldsType } from '../../../../common'; +import { ConnectorTypes, SwimlaneFieldsType } from '../../../../common/api'; import { ConnectorFieldsProps } from '../types'; import { ConnectorCard } from '../card'; import { connectorValidator } from './validator'; diff --git a/x-pack/plugins/cases/public/components/connectors/swimlane/index.ts b/x-pack/plugins/cases/public/components/connectors/swimlane/index.ts index bd2eaae9e0174..394b93b961004 100644 --- a/x-pack/plugins/cases/public/components/connectors/swimlane/index.ts +++ b/x-pack/plugins/cases/public/components/connectors/swimlane/index.ts @@ -8,7 +8,7 @@ import { lazy } from 'react'; import { CaseConnector } from '../types'; -import { ConnectorTypes, SwimlaneFieldsType } from '../../../../common'; +import { ConnectorTypes, SwimlaneFieldsType } from '../../../../common/api'; import * as i18n from './translations'; export const getCaseConnector = (): CaseConnector => { diff --git a/x-pack/plugins/cases/public/components/connectors/swimlane/validator.test.ts b/x-pack/plugins/cases/public/components/connectors/swimlane/validator.test.ts index 552d988c26330..c8cb142232972 100644 --- a/x-pack/plugins/cases/public/components/connectors/swimlane/validator.test.ts +++ b/x-pack/plugins/cases/public/components/connectors/swimlane/validator.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { SwimlaneConnectorType } from '../../../../common'; +import { SwimlaneConnectorType } from '../../../../common/api'; import { swimlaneConnector as connector } from '../mock'; import { isAnyRequiredFieldNotSet, connectorValidator } from './validator'; diff --git a/x-pack/plugins/cases/public/components/connectors/swimlane/validator.ts b/x-pack/plugins/cases/public/components/connectors/swimlane/validator.ts index 4ead75e5854f9..90d9946d4adb8 100644 --- a/x-pack/plugins/cases/public/components/connectors/swimlane/validator.ts +++ b/x-pack/plugins/cases/public/components/connectors/swimlane/validator.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { SwimlaneConnectorType } from '../../../../common'; +import { SwimlaneConnectorType } from '../../../../common/api'; import { ValidationConfig } from '../../../common/shared_imports'; import { CaseActionConnector } from '../../types'; diff --git a/x-pack/plugins/cases/public/components/connectors/types.ts b/x-pack/plugins/cases/public/components/connectors/types.ts index 8bc978152b796..66e5d519ac752 100644 --- a/x-pack/plugins/cases/public/components/connectors/types.ts +++ b/x-pack/plugins/cases/public/components/connectors/types.ts @@ -12,10 +12,10 @@ import { ActionType as ThirdPartySupportedActions, CaseField, ConnectorTypeFields, -} from '../../../common'; +} from '../../../common/api'; import { CaseActionConnector } from '../types'; -export type { ThirdPartyField as AllThirdPartyFields } from '../../../common'; +export type { ThirdPartyField as AllThirdPartyFields } from '../../../common/api'; export interface ThirdPartyField { label: string; diff --git a/x-pack/plugins/cases/public/components/create/connector.tsx b/x-pack/plugins/cases/public/components/create/connector.tsx index 84695d4011f55..aa0eb024a3b0d 100644 --- a/x-pack/plugins/cases/public/components/create/connector.tsx +++ b/x-pack/plugins/cases/public/components/create/connector.tsx @@ -8,7 +8,7 @@ import React, { memo, useCallback, useMemo, useEffect } from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { ConnectorTypes, ActionConnector } from '../../../common'; +import { ConnectorTypes, ActionConnector } from '../../../common/api'; import { UseField, useFormData, diff --git a/x-pack/plugins/cases/public/components/create/flyout/create_case_flyout.tsx b/x-pack/plugins/cases/public/components/create/flyout/create_case_flyout.tsx index e77f72929ecd8..eeebcb29ed2a9 100644 --- a/x-pack/plugins/cases/public/components/create/flyout/create_case_flyout.tsx +++ b/x-pack/plugins/cases/public/components/create/flyout/create_case_flyout.tsx @@ -10,7 +10,7 @@ import styled, { createGlobalStyle } from 'styled-components'; import { EuiFlyout, EuiFlyoutHeader, EuiTitle, EuiFlyoutBody } from '@elastic/eui'; import * as i18n from '../translations'; -import { Case } from '../../../../common'; +import { Case } from '../../../../common/ui/types'; import { CreateCaseForm } from '../form'; export interface CreateCaseFlyoutProps { diff --git a/x-pack/plugins/cases/public/components/create/form.tsx b/x-pack/plugins/cases/public/components/create/form.tsx index 2c775cb5fd86d..396c72fa54c0d 100644 --- a/x-pack/plugins/cases/public/components/create/form.tsx +++ b/x-pack/plugins/cases/public/components/create/form.tsx @@ -23,7 +23,7 @@ import { Tags } from './tags'; import { Connector } from './connector'; import * as i18n from './translations'; import { SyncAlertsToggle } from './sync_alerts_toggle'; -import { ActionConnector, CaseType } from '../../../common'; +import { ActionConnector, CaseType } from '../../../common/api'; import { Case } from '../../containers/types'; import { CasesTimelineIntegration, CasesTimelineIntegrationProvider } from '../timeline_context'; import { InsertTimeline } from '../insert_timeline'; diff --git a/x-pack/plugins/cases/public/components/create/form_context.test.tsx b/x-pack/plugins/cases/public/components/create/form_context.test.tsx index 15ffa5376e418..6e406386b48ef 100644 --- a/x-pack/plugins/cases/public/components/create/form_context.test.tsx +++ b/x-pack/plugins/cases/public/components/create/form_context.test.tsx @@ -10,7 +10,7 @@ import { mount, ReactWrapper } from 'enzyme'; import { act, waitFor } from '@testing-library/react'; import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; -import { ConnectorTypes } from '../../../common'; +import { ConnectorTypes } from '../../../common/api'; import { useKibana } from '../../common/lib/kibana'; import { TestProviders } from '../../common/mock'; import { usePostCase } from '../../containers/use_post_case'; diff --git a/x-pack/plugins/cases/public/components/create/form_context.tsx b/x-pack/plugins/cases/public/components/create/form_context.tsx index a513056ba31a5..b76a4640507be 100644 --- a/x-pack/plugins/cases/public/components/create/form_context.tsx +++ b/x-pack/plugins/cases/public/components/create/form_context.tsx @@ -14,7 +14,7 @@ import { usePostPushToService } from '../../containers/use_post_push_to_service' import { useConnectors } from '../../containers/configure/use_connectors'; import { Case } from '../../containers/types'; -import { CaseType } from '../../../common'; +import { CaseType } from '../../../common/api'; import { UsePostComment, usePostComment } from '../../containers/use_post_comment'; import { useCasesContext } from '../cases_context/use_cases_context'; import { useCasesFeatures } from '../cases_context/use_cases_features'; diff --git a/x-pack/plugins/cases/public/components/create/mock.ts b/x-pack/plugins/cases/public/components/create/mock.ts index fb00f114f480c..321194826e484 100644 --- a/x-pack/plugins/cases/public/components/create/mock.ts +++ b/x-pack/plugins/cases/public/components/create/mock.ts @@ -5,12 +5,8 @@ * 2.0. */ -import { - CasePostRequest, - CaseType, - ConnectorTypes, - SECURITY_SOLUTION_OWNER, -} from '../../../common'; +import { CasePostRequest, CaseType, ConnectorTypes } from '../../../common/api'; +import { SECURITY_SOLUTION_OWNER } from '../../../common/constants'; import { choices } from '../connectors/mock'; export const sampleTags = ['coke', 'pepsi']; diff --git a/x-pack/plugins/cases/public/components/create/schema.tsx b/x-pack/plugins/cases/public/components/create/schema.tsx index 57cf2f63a3fd2..435ebefd943ca 100644 --- a/x-pack/plugins/cases/public/components/create/schema.tsx +++ b/x-pack/plugins/cases/public/components/create/schema.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import { CasePostRequest, ConnectorTypeFields, MAX_TITLE_LENGTH } from '../../../common'; +import { CasePostRequest, ConnectorTypeFields } from '../../../common/api'; +import { MAX_TITLE_LENGTH } from '../../../common/constants'; import { FIELD_TYPES, fieldValidators, diff --git a/x-pack/plugins/cases/public/components/edit_connector/helpers.test.ts b/x-pack/plugins/cases/public/components/edit_connector/helpers.test.ts index e20d6b37258bc..e1cc8cefcafb8 100644 --- a/x-pack/plugins/cases/public/components/edit_connector/helpers.test.ts +++ b/x-pack/plugins/cases/public/components/edit_connector/helpers.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { CaseUserActionConnector, ConnectorTypes } from '../../../common'; +import { CaseUserActionConnector, ConnectorTypes } from '../../../common/api'; import { CaseUserActions } from '../../containers/types'; import { getConnectorFieldsFromUserActions } from './helpers'; diff --git a/x-pack/plugins/cases/public/components/edit_connector/helpers.ts b/x-pack/plugins/cases/public/components/edit_connector/helpers.ts index b97035c458aca..c6027bb7b570e 100644 --- a/x-pack/plugins/cases/public/components/edit_connector/helpers.ts +++ b/x-pack/plugins/cases/public/components/edit_connector/helpers.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ConnectorTypeFields } from '../../../common'; +import { ConnectorTypeFields } from '../../../common/api'; import { CaseUserActions } from '../../containers/types'; import { parseStringAsConnector } from '../../common/user_actions'; diff --git a/x-pack/plugins/cases/public/components/edit_connector/index.tsx b/x-pack/plugins/cases/public/components/edit_connector/index.tsx index 25cb17cdd8c98..efee35a8ba134 100644 --- a/x-pack/plugins/cases/public/components/edit_connector/index.tsx +++ b/x-pack/plugins/cases/public/components/edit_connector/index.tsx @@ -21,7 +21,8 @@ import styled from 'styled-components'; import { isEmpty, noop } from 'lodash/fp'; import { FieldConfig, Form, UseField, useForm } from '../../common/shared_imports'; -import { ActionConnector, Case, ConnectorTypeFields } from '../../../common'; +import { Case } from '../../../common/ui/types'; +import { ActionConnector, ConnectorTypeFields } from '../../../common/api'; import { ConnectorSelector } from '../connector_selector/form'; import { ConnectorFieldsForm } from '../connectors/fields_form'; import { CaseUserActions } from '../../containers/types'; diff --git a/x-pack/plugins/cases/public/components/header_page/editable_title.tsx b/x-pack/plugins/cases/public/components/header_page/editable_title.tsx index 954ee0b031e51..43b210b5a5afb 100644 --- a/x-pack/plugins/cases/public/components/header_page/editable_title.tsx +++ b/x-pack/plugins/cases/public/components/header_page/editable_title.tsx @@ -19,7 +19,7 @@ import { EuiFormRow, } from '@elastic/eui'; -import { MAX_TITLE_LENGTH } from '../../../common'; +import { MAX_TITLE_LENGTH } from '../../../common/constants'; import * as i18n from './translations'; import { Title } from './title'; diff --git a/x-pack/plugins/cases/public/components/status/button.test.tsx b/x-pack/plugins/cases/public/components/status/button.test.tsx index a4d4a53ff4a62..32df83b4b2ddf 100644 --- a/x-pack/plugins/cases/public/components/status/button.test.tsx +++ b/x-pack/plugins/cases/public/components/status/button.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { mount } from 'enzyme'; -import { CaseStatuses } from '../../../common'; +import { CaseStatuses } from '../../../common/api'; import { StatusActionButton } from './button'; describe('StatusActionButton', () => { diff --git a/x-pack/plugins/cases/public/components/status/button.tsx b/x-pack/plugins/cases/public/components/status/button.tsx index 675d83c759bc7..c9dcd509c1002 100644 --- a/x-pack/plugins/cases/public/components/status/button.tsx +++ b/x-pack/plugins/cases/public/components/status/button.tsx @@ -8,7 +8,7 @@ import React, { memo, useCallback, useMemo } from 'react'; import { EuiButton } from '@elastic/eui'; -import { CaseStatuses, caseStatuses } from '../../../common'; +import { CaseStatuses, caseStatuses } from '../../../common/api'; import { statuses } from './config'; interface Props { diff --git a/x-pack/plugins/cases/public/components/status/config.ts b/x-pack/plugins/cases/public/components/status/config.ts index 0202507aa3721..6c5ff18ad977a 100644 --- a/x-pack/plugins/cases/public/components/status/config.ts +++ b/x-pack/plugins/cases/public/components/status/config.ts @@ -4,7 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { CaseStatuses, StatusAll } from '../../../common'; +import { StatusAll } from '../../../common/ui/types'; +import { CaseStatuses } from '../../../common/api'; import * as i18n from './translations'; import { AllCaseStatus, Statuses } from './types'; diff --git a/x-pack/plugins/cases/public/components/status/stats.test.tsx b/x-pack/plugins/cases/public/components/status/stats.test.tsx index b2da828da77b0..ea0f54bf8055b 100644 --- a/x-pack/plugins/cases/public/components/status/stats.test.tsx +++ b/x-pack/plugins/cases/public/components/status/stats.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { mount } from 'enzyme'; -import { CaseStatuses } from '../../../common'; +import { CaseStatuses } from '../../../common/api'; import { Stats } from './stats'; describe('Stats', () => { diff --git a/x-pack/plugins/cases/public/components/status/stats.tsx b/x-pack/plugins/cases/public/components/status/stats.tsx index 071ea43746fdc..98720ad75a656 100644 --- a/x-pack/plugins/cases/public/components/status/stats.tsx +++ b/x-pack/plugins/cases/public/components/status/stats.tsx @@ -7,7 +7,7 @@ import React, { memo, useMemo } from 'react'; import { EuiDescriptionList, EuiLoadingSpinner } from '@elastic/eui'; -import { CaseStatuses } from '../../../common'; +import { CaseStatuses } from '../../../common/api'; import { statuses } from './config'; export interface Props { diff --git a/x-pack/plugins/cases/public/components/status/status.test.tsx b/x-pack/plugins/cases/public/components/status/status.test.tsx index a685256741c43..9ea71dfd52393 100644 --- a/x-pack/plugins/cases/public/components/status/status.test.tsx +++ b/x-pack/plugins/cases/public/components/status/status.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { mount } from 'enzyme'; -import { CaseStatuses } from '../../../common'; +import { CaseStatuses } from '../../../common/api'; import { Status } from './status'; describe('Stats', () => { diff --git a/x-pack/plugins/cases/public/components/status/status.tsx b/x-pack/plugins/cases/public/components/status/status.tsx index 3c186313a151a..47c30a7761264 100644 --- a/x-pack/plugins/cases/public/components/status/status.tsx +++ b/x-pack/plugins/cases/public/components/status/status.tsx @@ -11,7 +11,7 @@ import { EuiBadge } from '@elastic/eui'; import { allCaseStatus, statuses } from './config'; import * as i18n from './translations'; -import { CaseStatusWithAllStatus, StatusAll } from '../../../common'; +import { CaseStatusWithAllStatus, StatusAll } from '../../../common/ui/types'; interface Props { disabled?: boolean; diff --git a/x-pack/plugins/cases/public/components/status/types.ts b/x-pack/plugins/cases/public/components/status/types.ts index f8115b8d692b3..0b4a1184633e1 100644 --- a/x-pack/plugins/cases/public/components/status/types.ts +++ b/x-pack/plugins/cases/public/components/status/types.ts @@ -6,7 +6,8 @@ */ import { EuiIconType } from '@elastic/eui/src/components/icon/icon'; -import { CaseStatuses, StatusAllType } from '../../../common'; +import { StatusAllType } from '../../../common/ui/types'; +import { CaseStatuses } from '../../../common/api'; export type AllCaseStatus = Record; diff --git a/x-pack/plugins/cases/public/components/types.ts b/x-pack/plugins/cases/public/components/types.ts index 71c846eb922d7..6d72a74fa5d81 100644 --- a/x-pack/plugins/cases/public/components/types.ts +++ b/x-pack/plugins/cases/public/components/types.ts @@ -5,4 +5,4 @@ * 2.0. */ -export type { CaseActionConnector } from '../../common'; +export type { CaseActionConnector } from '../../common/ui/types'; diff --git a/x-pack/plugins/cases/public/components/use_create_case_modal/create_case_modal.test.tsx b/x-pack/plugins/cases/public/components/use_create_case_modal/create_case_modal.test.tsx index 7c88d9425bd7e..9ee4a78b7d817 100644 --- a/x-pack/plugins/cases/public/components/use_create_case_modal/create_case_modal.test.tsx +++ b/x-pack/plugins/cases/public/components/use_create_case_modal/create_case_modal.test.tsx @@ -10,7 +10,7 @@ import { mount } from 'enzyme'; import { CreateCaseModal } from './create_case_modal'; import { TestProviders } from '../../common/mock'; -import { SECURITY_SOLUTION_OWNER } from '../../../common'; +import { SECURITY_SOLUTION_OWNER } from '../../../common/constants'; import { CreateCase } from '../create'; jest.mock('../create', () => ({ diff --git a/x-pack/plugins/cases/public/components/use_create_case_modal/index.tsx b/x-pack/plugins/cases/public/components/use_create_case_modal/index.tsx index 2e9f3559d98d0..afae43b462a5b 100644 --- a/x-pack/plugins/cases/public/components/use_create_case_modal/index.tsx +++ b/x-pack/plugins/cases/public/components/use_create_case_modal/index.tsx @@ -6,7 +6,7 @@ */ import React, { useState, useCallback, useMemo } from 'react'; -import { Case } from '../../../common'; +import { Case } from '../../../common/ui/types'; import { CreateCaseModal } from './create_case_modal'; export interface UseCreateCaseModalProps { diff --git a/x-pack/plugins/cases/public/components/use_push_to_service/index.test.tsx b/x-pack/plugins/cases/public/components/use_push_to_service/index.test.tsx index 19c0bf58de7bf..cf81b5195a961 100644 --- a/x-pack/plugins/cases/public/components/use_push_to_service/index.test.tsx +++ b/x-pack/plugins/cases/public/components/use_push_to_service/index.test.tsx @@ -12,7 +12,7 @@ import { render, screen } from '@testing-library/react'; import '../../common/mock/match_media'; import { usePushToService, ReturnUsePushToService, UsePushToService } from '.'; import { TestProviders } from '../../common/mock'; -import { CaseStatuses, ConnectorTypes } from '../../../common'; +import { CaseStatuses, ConnectorTypes } from '../../../common/api'; import { usePostPushToService } from '../../containers/use_post_push_to_service'; import { basicPush, actionLicenses } from '../../containers/mock'; import { useGetActionLicense } from '../../containers/use_get_action_license'; diff --git a/x-pack/plugins/cases/public/components/use_push_to_service/index.tsx b/x-pack/plugins/cases/public/components/use_push_to_service/index.tsx index c1ba6f1fbeb25..b079a9d3d1b3d 100644 --- a/x-pack/plugins/cases/public/components/use_push_to_service/index.tsx +++ b/x-pack/plugins/cases/public/components/use_push_to_service/index.tsx @@ -19,7 +19,8 @@ import { getCaseClosedInfo, } from './helpers'; import * as i18n from './translations'; -import { Case, CaseConnector, ActionConnector, CaseStatuses } from '../../../common'; +import { Case } from '../../../common/ui/types'; +import { CaseConnector, ActionConnector, CaseStatuses } from '../../../common/api'; import { CaseServices } from '../../containers/use_get_case_user_actions'; import { ErrorMessage } from './callout/types'; diff --git a/x-pack/plugins/cases/public/components/user_action_tree/helpers.test.tsx b/x-pack/plugins/cases/public/components/user_action_tree/helpers.test.tsx index 841f0d36bbf17..5c0d64857d0cb 100644 --- a/x-pack/plugins/cases/public/components/user_action_tree/helpers.test.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/helpers.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { mount } from 'enzyme'; -import { CaseStatuses, ConnectorTypes } from '../../../common'; +import { CaseStatuses, ConnectorTypes } from '../../../common/api'; import { basicPush, getUserAction } from '../../containers/mock'; import { getLabelTitle, diff --git a/x-pack/plugins/cases/public/components/user_action_tree/helpers.tsx b/x-pack/plugins/cases/public/components/user_action_tree/helpers.tsx index a7b4624835882..6dd4032d7cdce 100644 --- a/x-pack/plugins/cases/public/components/user_action_tree/helpers.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/helpers.tsx @@ -16,15 +16,15 @@ import { import React, { useContext } from 'react'; import classNames from 'classnames'; import { ThemeContext } from 'styled-components'; +import { Comment } from '../../../common/ui/types'; import { CaseFullExternalService, ActionConnector, CaseStatuses, CommentType, - Comment, CommentRequestActionsType, noneConnectorId, -} from '../../../common'; +} from '../../../common/api'; import { CaseUserActions } from '../../containers/types'; import { CaseServices } from '../../containers/use_get_case_user_actions'; import { parseStringAsConnector, parseStringAsExternalService } from '../../common/user_actions'; diff --git a/x-pack/plugins/cases/public/components/user_action_tree/index.test.tsx b/x-pack/plugins/cases/public/components/user_action_tree/index.test.tsx index e9cd556706646..94d0ca413192a 100644 --- a/x-pack/plugins/cases/public/components/user_action_tree/index.test.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/index.test.tsx @@ -22,7 +22,7 @@ import { } from '../../containers/mock'; import { UserActionTree } from '.'; import { TestProviders } from '../../common/mock'; -import { Ecs } from '../../../common'; +import { Ecs } from '../../../common/ui/types'; const fetchUserActions = jest.fn(); const onUpdateField = jest.fn(); diff --git a/x-pack/plugins/cases/public/components/user_action_tree/index.tsx b/x-pack/plugins/cases/public/components/user_action_tree/index.tsx index 6197303a8d7ce..b5b76f36013c5 100644 --- a/x-pack/plugins/cases/public/components/user_action_tree/index.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/index.tsx @@ -25,16 +25,14 @@ import * as i18n from './translations'; import { useUpdateComment } from '../../containers/use_update_comment'; import { useCurrentUser } from '../../common/lib/kibana'; import { AddComment, AddCommentRefObject } from '../add_comment'; +import { Case, CaseUserActions, Ecs } from '../../../common/ui/types'; import { ActionConnector, ActionsCommentRequestRt, AlertCommentRequestRt, - Case, - CaseUserActions, CommentType, ContextTypeUserRt, - Ecs, -} from '../../../common'; +} from '../../../common/api'; import { CaseServices } from '../../containers/use_get_case_user_actions'; import { parseStringAsExternalService } from '../../common/user_actions'; import { OnUpdateFields } from '../case_view'; diff --git a/x-pack/plugins/cases/public/components/user_action_tree/user_action_alert_comment_event.test.tsx b/x-pack/plugins/cases/public/components/user_action_tree/user_action_alert_comment_event.test.tsx index 73a61ed3afd5f..858b54038286d 100644 --- a/x-pack/plugins/cases/public/components/user_action_tree/user_action_alert_comment_event.test.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/user_action_alert_comment_event.test.tsx @@ -11,7 +11,7 @@ import { mount } from 'enzyme'; import { TestProviders } from '../../common/mock'; import { useKibana } from '../../common/lib/kibana'; import { AlertCommentEvent } from './user_action_alert_comment_event'; -import { CommentType } from '../../../common'; +import { CommentType } from '../../../common/api'; const props = { alertId: 'alert-id-1', diff --git a/x-pack/plugins/cases/public/components/user_action_tree/user_action_alert_comment_event.tsx b/x-pack/plugins/cases/public/components/user_action_tree/user_action_alert_comment_event.tsx index 8f405caa153f1..4236691a16bb2 100644 --- a/x-pack/plugins/cases/public/components/user_action_tree/user_action_alert_comment_event.tsx +++ b/x-pack/plugins/cases/public/components/user_action_tree/user_action_alert_comment_event.tsx @@ -10,7 +10,7 @@ import { isEmpty } from 'lodash'; import { EuiText, EuiLoadingSpinner } from '@elastic/eui'; import * as i18n from './translations'; -import { CommentType } from '../../../common'; +import { CommentType } from '../../../common/api'; import { LinkAnchor } from '../links'; import { RuleDetailsNavigation } from './helpers'; diff --git a/x-pack/plugins/cases/public/components/utils.ts b/x-pack/plugins/cases/public/components/utils.ts index 82d2682e65fad..1fafe5afe6990 100644 --- a/x-pack/plugins/cases/public/components/utils.ts +++ b/x-pack/plugins/cases/public/components/utils.ts @@ -6,7 +6,7 @@ */ import { IconType } from '@elastic/eui'; -import { ConnectorTypes } from '../../common'; +import { ConnectorTypes } from '../../common/api'; import { FieldConfig, ValidationConfig } from '../common/shared_imports'; import { StartPlugins } from '../types'; import { connectorValidator as swimlaneConnectorValidator } from './connectors/swimlane/validator'; diff --git a/x-pack/plugins/cases/public/containers/__mocks__/api.ts b/x-pack/plugins/cases/public/containers/__mocks__/api.ts index 96e75a96ca115..843a9d81d8013 100644 --- a/x-pack/plugins/cases/public/containers/__mocks__/api.ts +++ b/x-pack/plugins/cases/public/containers/__mocks__/api.ts @@ -28,14 +28,14 @@ import { respReporters, tags, } from '../mock'; +import { ResolvedCase } from '../../../common/ui/types'; import { CasePatchRequest, CasePostRequest, CommentRequest, User, CaseStatuses, - ResolvedCase, -} from '../../../common'; +} from '../../../common/api'; export const getCase = async ( caseId: string, diff --git a/x-pack/plugins/cases/public/containers/api.test.tsx b/x-pack/plugins/cases/public/containers/api.test.tsx index 654ade308ed44..c83f5601da64b 100644 --- a/x-pack/plugins/cases/public/containers/api.test.tsx +++ b/x-pack/plugins/cases/public/containers/api.test.tsx @@ -7,13 +7,8 @@ import { KibanaServices } from '../common/lib/kibana'; -import { - CASES_URL, - ConnectorTypes, - CommentType, - CaseStatuses, - SECURITY_SOLUTION_OWNER, -} from '../../common'; +import { ConnectorTypes, CommentType, CaseStatuses } from '../../common/api'; +import { CASES_URL, SECURITY_SOLUTION_OWNER } from '../../common/constants'; import { deleteCases, diff --git a/x-pack/plugins/cases/public/containers/api.ts b/x-pack/plugins/cases/public/containers/api.ts index 14f617b19db52..81bd6b39be5fd 100644 --- a/x-pack/plugins/cases/public/containers/api.ts +++ b/x-pack/plugins/cases/public/containers/api.ts @@ -7,15 +7,12 @@ import { assign, omit } from 'lodash'; +import { StatusAll, ResolvedCase } from '../../common/ui/types'; import { - CASE_REPORTERS_URL, - CASE_STATUS_URL, - CASE_TAGS_URL, CasePatchRequest, CasePostRequest, CaseResponse, CaseResolveResponse, - CASES_URL, CasesFindResponse, CasesResponse, CasesStatusResponse, @@ -29,16 +26,19 @@ import { getCaseUserActionUrl, getSubCaseDetailsUrl, getSubCaseUserActionUrl, - StatusAll, - SUB_CASE_DETAILS_URL, - SUB_CASES_PATCH_DEL_URL, SubCasePatchRequest, SubCaseResponse, SubCasesResponse, User, - ResolvedCase, -} from '../../common'; - +} from '../../common/api'; +import { + CASE_REPORTERS_URL, + CASE_STATUS_URL, + CASE_TAGS_URL, + CASES_URL, + SUB_CASE_DETAILS_URL, + SUB_CASES_PATCH_DEL_URL, +} from '../../common/constants'; import { getAllConnectorTypesUrl } from '../../common/utils/connectors_api'; import { KibanaServices } from '../common/lib/kibana'; diff --git a/x-pack/plugins/cases/public/containers/configure/__mocks__/api.ts b/x-pack/plugins/cases/public/containers/configure/__mocks__/api.ts index ea4b92706b4d1..10cfde0c5ef9c 100644 --- a/x-pack/plugins/cases/public/containers/configure/__mocks__/api.ts +++ b/x-pack/plugins/cases/public/containers/configure/__mocks__/api.ts @@ -10,7 +10,7 @@ import { CasesConfigureRequest, ActionConnector, ActionTypeConnector, -} from '../../../../common'; +} from '../../../../common/api'; import { ApiProps } from '../../types'; import { CaseConfigure } from '../types'; diff --git a/x-pack/plugins/cases/public/containers/configure/api.test.ts b/x-pack/plugins/cases/public/containers/configure/api.test.ts index 141b5e3711ceb..a315a455ec2a2 100644 --- a/x-pack/plugins/cases/public/containers/configure/api.test.ts +++ b/x-pack/plugins/cases/public/containers/configure/api.test.ts @@ -19,7 +19,8 @@ import { caseConfigurationResposeMock, caseConfigurationCamelCaseResponseMock, } from './mock'; -import { ConnectorTypes, SECURITY_SOLUTION_OWNER } from '../../../common'; +import { ConnectorTypes } from '../../../common/api'; +import { SECURITY_SOLUTION_OWNER } from '../../../common/constants'; import { KibanaServices } from '../../common/lib/kibana'; const abortCtrl = new AbortController(); diff --git a/x-pack/plugins/cases/public/containers/configure/api.ts b/x-pack/plugins/cases/public/containers/configure/api.ts index 1fd358e4dae9d..32202afc34881 100644 --- a/x-pack/plugins/cases/public/containers/configure/api.ts +++ b/x-pack/plugins/cases/public/containers/configure/api.ts @@ -10,14 +10,13 @@ import { getAllConnectorTypesUrl } from '../../../common/utils/connectors_api'; import { ActionConnector, ActionTypeConnector, - CASE_CONFIGURE_CONNECTORS_URL, - CASE_CONFIGURE_URL, CasesConfigurePatch, CasesConfigureRequest, CasesConfigureResponse, CasesConfigurationsResponse, getCaseConfigurationDetailsUrl, -} from '../../../common'; +} from '../../../common/api'; +import { CASE_CONFIGURE_CONNECTORS_URL, CASE_CONFIGURE_URL } from '../../../common/constants'; import { KibanaServices } from '../../common/lib/kibana'; import { ApiProps } from '../types'; diff --git a/x-pack/plugins/cases/public/containers/configure/mock.ts b/x-pack/plugins/cases/public/containers/configure/mock.ts index a5483e524e92d..bbcf420324c83 100644 --- a/x-pack/plugins/cases/public/containers/configure/mock.ts +++ b/x-pack/plugins/cases/public/containers/configure/mock.ts @@ -11,8 +11,8 @@ import { CasesConfigureResponse, CasesConfigureRequest, ConnectorTypes, - SECURITY_SOLUTION_OWNER, -} from '../../../common'; +} from '../../../common/api'; +import { SECURITY_SOLUTION_OWNER } from '../../../common/constants'; import { CaseConfigure, CaseConnectorMapping } from './types'; export const mappings: CaseConnectorMapping[] = [ diff --git a/x-pack/plugins/cases/public/containers/configure/types.ts b/x-pack/plugins/cases/public/containers/configure/types.ts index 5ee09add196bd..55401a2fbfd2c 100644 --- a/x-pack/plugins/cases/public/containers/configure/types.ts +++ b/x-pack/plugins/cases/public/containers/configure/types.ts @@ -15,7 +15,7 @@ import { CasesConfigure, ClosureType, ThirdPartyField, -} from '../../../common'; +} from '../../../common/api'; export type { ActionConnector, diff --git a/x-pack/plugins/cases/public/containers/configure/use_configure.test.tsx b/x-pack/plugins/cases/public/containers/configure/use_configure.test.tsx index 1814020de8465..1c9139b913617 100644 --- a/x-pack/plugins/cases/public/containers/configure/use_configure.test.tsx +++ b/x-pack/plugins/cases/public/containers/configure/use_configure.test.tsx @@ -15,7 +15,7 @@ import { } from './use_configure'; import { mappings, caseConfigurationCamelCaseResponseMock } from './mock'; import * as api from './api'; -import { ConnectorTypes } from '../../../common'; +import { ConnectorTypes } from '../../../common/api'; import { TestProviders } from '../../common/mock'; const mockErrorToast = jest.fn(); diff --git a/x-pack/plugins/cases/public/containers/configure/use_configure.tsx b/x-pack/plugins/cases/public/containers/configure/use_configure.tsx index afac625c7682e..21c6e9e0b388e 100644 --- a/x-pack/plugins/cases/public/containers/configure/use_configure.tsx +++ b/x-pack/plugins/cases/public/containers/configure/use_configure.tsx @@ -10,7 +10,7 @@ import { getCaseConfigure, patchCaseConfigure, postCaseConfigure } from './api'; import * as i18n from './translations'; import { ClosureType, CaseConfigure, CaseConnector, CaseConnectorMapping } from './types'; -import { ConnectorTypes } from '../../../common'; +import { ConnectorTypes } from '../../../common/api'; import { useToasts } from '../../common/lib/kibana'; import { useCasesContext } from '../../components/cases_context/use_cases_context'; diff --git a/x-pack/plugins/cases/public/containers/mock.ts b/x-pack/plugins/cases/public/containers/mock.ts index f7d1daabd60ea..92fa8caa3ac5b 100644 --- a/x-pack/plugins/cases/public/containers/mock.ts +++ b/x-pack/plugins/cases/public/containers/mock.ts @@ -7,6 +7,8 @@ import { ActionLicense, AllCases, Case, CasesStatus, CaseUserActions, Comment } from './types'; +import { isCreateConnector, isPush, isUpdateConnector } from '../../common/utils/user_actions'; +import { ResolvedCase } from '../../common/ui/types'; import { AssociationType, CaseUserActionConnector, @@ -20,14 +22,10 @@ import { CommentResponse, CommentType, ConnectorTypes, - ResolvedCase, - isCreateConnector, - isPush, - isUpdateConnector, - SECURITY_SOLUTION_OWNER, UserAction, UserActionField, -} from '../../common'; +} from '../../common/api'; +import { SECURITY_SOLUTION_OWNER } from '../../common/constants'; import { UseGetCasesState, DEFAULT_FILTER_OPTIONS, DEFAULT_QUERY_PARAMS } from './use_get_cases'; export { connectorsMock } from './configure/mock'; diff --git a/x-pack/plugins/cases/public/containers/use_bulk_update_case.test.tsx b/x-pack/plugins/cases/public/containers/use_bulk_update_case.test.tsx index 67f202e6adbad..d00b361828a6e 100644 --- a/x-pack/plugins/cases/public/containers/use_bulk_update_case.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_bulk_update_case.test.tsx @@ -6,7 +6,7 @@ */ import { renderHook, act } from '@testing-library/react-hooks'; -import { CaseStatuses } from '../../common'; +import { CaseStatuses } from '../../common/api'; import { useUpdateCases, UseUpdateCases } from './use_bulk_update_case'; import { basicCase } from './mock'; import * as api from './api'; diff --git a/x-pack/plugins/cases/public/containers/use_bulk_update_case.tsx b/x-pack/plugins/cases/public/containers/use_bulk_update_case.tsx index 449ca0ab77f13..715b0c611c3b8 100644 --- a/x-pack/plugins/cases/public/containers/use_bulk_update_case.tsx +++ b/x-pack/plugins/cases/public/containers/use_bulk_update_case.tsx @@ -6,7 +6,7 @@ */ import { useCallback, useReducer, useRef, useEffect } from 'react'; -import { CaseStatuses } from '../../common'; +import { CaseStatuses } from '../../common/api'; import * as i18n from './translations'; import { patchCasesStatus } from './api'; import { BulkUpdateStatus, Case } from './types'; diff --git a/x-pack/plugins/cases/public/containers/use_delete_cases.test.tsx b/x-pack/plugins/cases/public/containers/use_delete_cases.test.tsx index 691af580b333a..307dc0941e398 100644 --- a/x-pack/plugins/cases/public/containers/use_delete_cases.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_delete_cases.test.tsx @@ -7,7 +7,7 @@ import { renderHook, act } from '@testing-library/react-hooks'; -import { CaseType } from '../../common'; +import { CaseType } from '../../common/api'; import { useDeleteCases, UseDeleteCase } from './use_delete_cases'; import * as api from './api'; diff --git a/x-pack/plugins/cases/public/containers/use_get_action_license.tsx b/x-pack/plugins/cases/public/containers/use_get_action_license.tsx index e4ea6d05011a7..7618f8c06d9ae 100644 --- a/x-pack/plugins/cases/public/containers/use_get_action_license.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_action_license.tsx @@ -11,7 +11,7 @@ import { useToasts } from '../common/lib/kibana'; import { getActionLicense } from './api'; import * as i18n from './translations'; import { ActionLicense } from './types'; -import { ConnectorTypes } from '../../common'; +import { ConnectorTypes } from '../../common/api'; export interface ActionLicenseState { actionLicense: ActionLicense | null; diff --git a/x-pack/plugins/cases/public/containers/use_get_case_user_actions.tsx b/x-pack/plugins/cases/public/containers/use_get_case_user_actions.tsx index 36d600c3f1c9d..d3864097f5fee 100644 --- a/x-pack/plugins/cases/public/containers/use_get_case_user_actions.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_case_user_actions.tsx @@ -9,13 +9,8 @@ import { isEmpty, uniqBy } from 'lodash/fp'; import { useCallback, useEffect, useState, useRef } from 'react'; import deepEqual from 'fast-deep-equal'; -import { - CaseFullExternalService, - CaseConnector, - CaseExternalService, - CaseUserActions, - ElasticUser, -} from '../../common'; +import { ElasticUser, CaseUserActions, CaseExternalService } from '../../common/ui/types'; +import { CaseFullExternalService, CaseConnector } from '../../common/api'; import { getCaseUserActions, getSubCaseUserActions } from './api'; import * as i18n from './translations'; import { convertToCamelCase } from './utils'; diff --git a/x-pack/plugins/cases/public/containers/use_get_cases.test.tsx b/x-pack/plugins/cases/public/containers/use_get_cases.test.tsx index 97de7a9073269..99fbf48665138 100644 --- a/x-pack/plugins/cases/public/containers/use_get_cases.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_cases.test.tsx @@ -7,7 +7,8 @@ import React from 'react'; import { renderHook, act } from '@testing-library/react-hooks'; -import { CaseStatuses, SECURITY_SOLUTION_OWNER } from '../../common'; +import { CaseStatuses } from '../../common/api'; +import { SECURITY_SOLUTION_OWNER } from '../../common/constants'; import { DEFAULT_FILTER_OPTIONS, DEFAULT_QUERY_PARAMS, diff --git a/x-pack/plugins/cases/public/containers/use_get_cases_status.test.tsx b/x-pack/plugins/cases/public/containers/use_get_cases_status.test.tsx index 93da0ecbd14ae..2e3e42255145d 100644 --- a/x-pack/plugins/cases/public/containers/use_get_cases_status.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_cases_status.test.tsx @@ -11,7 +11,7 @@ import { useGetCasesStatus, UseGetCasesStatus } from './use_get_cases_status'; import { casesStatus } from './mock'; import * as api from './api'; import { TestProviders } from '../common/mock'; -import { SECURITY_SOLUTION_OWNER } from '../../common'; +import { SECURITY_SOLUTION_OWNER } from '../../common/constants'; jest.mock('./api'); jest.mock('../common/lib/kibana'); diff --git a/x-pack/plugins/cases/public/containers/use_get_reporters.test.tsx b/x-pack/plugins/cases/public/containers/use_get_reporters.test.tsx index 21f9352a7cbc0..38d47d3aa9cbb 100644 --- a/x-pack/plugins/cases/public/containers/use_get_reporters.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_reporters.test.tsx @@ -11,7 +11,7 @@ import { useGetReporters, UseGetReporters } from './use_get_reporters'; import { reporters, respReporters } from './mock'; import * as api from './api'; import { TestProviders } from '../common/mock'; -import { SECURITY_SOLUTION_OWNER } from '../../common'; +import { SECURITY_SOLUTION_OWNER } from '../../common/constants'; jest.mock('./api'); jest.mock('../common/lib/kibana'); diff --git a/x-pack/plugins/cases/public/containers/use_get_reporters.tsx b/x-pack/plugins/cases/public/containers/use_get_reporters.tsx index 881933419d60b..ce8aa4b961c23 100644 --- a/x-pack/plugins/cases/public/containers/use_get_reporters.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_reporters.tsx @@ -8,7 +8,7 @@ import { useCallback, useEffect, useState, useRef } from 'react'; import { isEmpty } from 'lodash/fp'; -import { User } from '../../common'; +import { User } from '../../common/api'; import { getReporters } from './api'; import * as i18n from './translations'; import { useToasts } from '../common/lib/kibana'; diff --git a/x-pack/plugins/cases/public/containers/use_get_tags.test.tsx b/x-pack/plugins/cases/public/containers/use_get_tags.test.tsx index b2bf4737356cc..2607129e5655d 100644 --- a/x-pack/plugins/cases/public/containers/use_get_tags.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_tags.test.tsx @@ -11,7 +11,7 @@ import { useGetTags, UseGetTags } from './use_get_tags'; import { tags } from './mock'; import * as api from './api'; import { TestProviders } from '../common/mock'; -import { SECURITY_SOLUTION_OWNER } from '../../common'; +import { SECURITY_SOLUTION_OWNER } from '../../common/constants'; jest.mock('./api'); jest.mock('../common/lib/kibana'); diff --git a/x-pack/plugins/cases/public/containers/use_post_case.test.tsx b/x-pack/plugins/cases/public/containers/use_post_case.test.tsx index d2b638b4c846f..5d5b6ced44afc 100644 --- a/x-pack/plugins/cases/public/containers/use_post_case.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_post_case.test.tsx @@ -8,7 +8,8 @@ import { renderHook, act } from '@testing-library/react-hooks'; import { usePostCase, UsePostCase } from './use_post_case'; import * as api from './api'; -import { ConnectorTypes, SECURITY_SOLUTION_OWNER } from '../../common'; +import { ConnectorTypes } from '../../common/api'; +import { SECURITY_SOLUTION_OWNER } from '../../common/constants'; import { basicCasePost } from './mock'; jest.mock('./api'); diff --git a/x-pack/plugins/cases/public/containers/use_post_case.tsx b/x-pack/plugins/cases/public/containers/use_post_case.tsx index f13c250b96a3e..dc23c503b333b 100644 --- a/x-pack/plugins/cases/public/containers/use_post_case.tsx +++ b/x-pack/plugins/cases/public/containers/use_post_case.tsx @@ -6,7 +6,7 @@ */ import { useReducer, useCallback, useRef, useEffect } from 'react'; -import { CasePostRequest } from '../../common'; +import { CasePostRequest } from '../../common/api'; import { postCase } from './api'; import * as i18n from './translations'; import { Case } from './types'; diff --git a/x-pack/plugins/cases/public/containers/use_post_comment.test.tsx b/x-pack/plugins/cases/public/containers/use_post_comment.test.tsx index 8a86d9becdfde..dd9d73cff9bae 100644 --- a/x-pack/plugins/cases/public/containers/use_post_comment.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_post_comment.test.tsx @@ -7,7 +7,8 @@ import { renderHook, act } from '@testing-library/react-hooks'; -import { CommentType, SECURITY_SOLUTION_OWNER } from '../../common'; +import { CommentType } from '../../common/api'; +import { SECURITY_SOLUTION_OWNER } from '../../common/constants'; import { usePostComment, UsePostComment } from './use_post_comment'; import { basicCaseId, basicSubCaseId } from './mock'; import * as api from './api'; diff --git a/x-pack/plugins/cases/public/containers/use_post_comment.tsx b/x-pack/plugins/cases/public/containers/use_post_comment.tsx index 2d4437826092a..d796c5035ff9d 100644 --- a/x-pack/plugins/cases/public/containers/use_post_comment.tsx +++ b/x-pack/plugins/cases/public/containers/use_post_comment.tsx @@ -6,7 +6,7 @@ */ import { useReducer, useCallback, useRef, useEffect } from 'react'; -import { CommentRequest } from '../../common'; +import { CommentRequest } from '../../common/api'; import { postComment } from './api'; import * as i18n from './translations'; diff --git a/x-pack/plugins/cases/public/containers/use_post_push_to_service.test.tsx b/x-pack/plugins/cases/public/containers/use_post_push_to_service.test.tsx index 18e3c4be493b8..dedde459ad557 100644 --- a/x-pack/plugins/cases/public/containers/use_post_push_to_service.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_post_push_to_service.test.tsx @@ -9,7 +9,7 @@ import { renderHook, act } from '@testing-library/react-hooks'; import { usePostPushToService, UsePostPushToService } from './use_post_push_to_service'; import { pushedCase } from './mock'; import * as api from './api'; -import { CaseConnector, ConnectorTypes } from '../../common'; +import { CaseConnector, ConnectorTypes } from '../../common/api'; jest.mock('./api'); jest.mock('../common/lib/kibana'); diff --git a/x-pack/plugins/cases/public/containers/use_post_push_to_service.tsx b/x-pack/plugins/cases/public/containers/use_post_push_to_service.tsx index f4cf5b012e84f..90f1fbe212a02 100644 --- a/x-pack/plugins/cases/public/containers/use_post_push_to_service.tsx +++ b/x-pack/plugins/cases/public/containers/use_post_push_to_service.tsx @@ -6,7 +6,7 @@ */ import { useReducer, useCallback, useRef, useEffect } from 'react'; -import { CaseConnector } from '../../common'; +import { CaseConnector } from '../../common/api'; import { pushCase } from './api'; import * as i18n from './translations'; diff --git a/x-pack/plugins/cases/public/containers/use_update_case.tsx b/x-pack/plugins/cases/public/containers/use_update_case.tsx index afdc33bcc25e4..42e861d300341 100644 --- a/x-pack/plugins/cases/public/containers/use_update_case.tsx +++ b/x-pack/plugins/cases/public/containers/use_update_case.tsx @@ -9,7 +9,8 @@ import { useReducer, useCallback, useRef, useEffect } from 'react'; import { useToasts } from '../common/lib/kibana'; import { patchCase, patchSubCase } from './api'; -import { UpdateKey, UpdateByKey, CaseStatuses } from '../../common'; +import { UpdateKey, UpdateByKey } from '../../common/ui/types'; +import { CaseStatuses } from '../../common/api'; import * as i18n from './translations'; import { createUpdateSuccessToaster } from './utils'; diff --git a/x-pack/plugins/cases/public/containers/use_update_comment.test.tsx b/x-pack/plugins/cases/public/containers/use_update_comment.test.tsx index 14cc4dfab3599..836ec10e608a6 100644 --- a/x-pack/plugins/cases/public/containers/use_update_comment.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_update_comment.test.tsx @@ -11,7 +11,7 @@ import { useUpdateComment, UseUpdateComment } from './use_update_comment'; import { basicCase, basicCaseCommentPatch, basicSubCaseId } from './mock'; import * as api from './api'; import { TestProviders } from '../common/mock'; -import { SECURITY_SOLUTION_OWNER } from '../../common'; +import { SECURITY_SOLUTION_OWNER } from '../../common/constants'; jest.mock('./api'); jest.mock('../common/lib/kibana'); diff --git a/x-pack/plugins/cases/public/containers/utils.ts b/x-pack/plugins/cases/public/containers/utils.ts index 458899e5f53c9..938724a632dcb 100644 --- a/x-pack/plugins/cases/public/containers/utils.ts +++ b/x-pack/plugins/cases/public/containers/utils.ts @@ -32,7 +32,7 @@ import { CasePatchRequest, CaseResolveResponse, CaseResolveResponseRt, -} from '../../common'; +} from '../../common/api'; import { AllCases, Case, UpdateByKey } from './types'; import * as i18n from './translations'; diff --git a/x-pack/plugins/cases/public/plugin.ts b/x-pack/plugins/cases/public/plugin.ts index b6b9643ea5856..48371f65b49e1 100644 --- a/x-pack/plugins/cases/public/plugin.ts +++ b/x-pack/plugins/cases/public/plugin.ts @@ -15,7 +15,8 @@ import { getAllCasesSelectorModalLazy, getCreateCaseFlyoutLazy, } from './methods'; -import { CasesUiConfigType, ENABLE_CASE_CONNECTOR } from '../common'; +import { CasesUiConfigType } from '../common/ui/types'; +import { ENABLE_CASE_CONNECTOR } from '../common/constants'; /** * @public diff --git a/x-pack/plugins/cases/server/authorization/index.ts b/x-pack/plugins/cases/server/authorization/index.ts index c5208177554b5..057e85b460c2e 100644 --- a/x-pack/plugins/cases/server/authorization/index.ts +++ b/x-pack/plugins/cases/server/authorization/index.ts @@ -11,7 +11,7 @@ import { CASE_CONFIGURE_SAVED_OBJECT, CASE_SAVED_OBJECT, CASE_USER_ACTION_SAVED_OBJECT, -} from '../../common'; +} from '../../common/constants'; import { Verbs, ReadOperations, WriteOperations, OperationDetails } from './types'; export * from './authorization'; diff --git a/x-pack/plugins/cases/server/authorization/utils.test.ts b/x-pack/plugins/cases/server/authorization/utils.test.ts index 2afffbbb768b8..7717edfc909ef 100644 --- a/x-pack/plugins/cases/server/authorization/utils.test.ts +++ b/x-pack/plugins/cases/server/authorization/utils.test.ts @@ -6,7 +6,7 @@ */ import { nodeBuilder } from '@kbn/es-query'; -import { OWNER_FIELD } from '../../common'; +import { OWNER_FIELD } from '../../common/api'; import { combineFilterWithAuthorizationFilter, ensureFieldIsSafeForQuery, diff --git a/x-pack/plugins/cases/server/authorization/utils.ts b/x-pack/plugins/cases/server/authorization/utils.ts index f3a8512548430..ac88f96fb4e14 100644 --- a/x-pack/plugins/cases/server/authorization/utils.ts +++ b/x-pack/plugins/cases/server/authorization/utils.ts @@ -7,7 +7,7 @@ import { remove, uniq } from 'lodash'; import { nodeBuilder, KueryNode } from '@kbn/es-query'; -import { OWNER_FIELD } from '../../common'; +import { OWNER_FIELD } from '../../common/api'; export const getOwnersFilter = ( savedObjectType: string, diff --git a/x-pack/plugins/cases/server/client/attachments/add.ts b/x-pack/plugins/cases/server/client/attachments/add.ts index 84dbc8921f0e4..45de17a1202e7 100644 --- a/x-pack/plugins/cases/server/client/attachments/add.ts +++ b/x-pack/plugins/cases/server/client/attachments/add.ts @@ -21,19 +21,21 @@ import { LensServerPluginSetup } from '../../../../lens/server'; import { AlertCommentRequestRt, - CASE_COMMENT_SAVED_OBJECT, CaseResponse, CaseStatuses, CaseType, CommentRequest, CommentRequestRt, CommentType, - ENABLE_CASE_CONNECTOR, - MAX_GENERATED_ALERTS_PER_SUB_CASE, SubCaseAttributes, throwErrors, User, -} from '../../../common'; +} from '../../../common/api'; +import { + CASE_COMMENT_SAVED_OBJECT, + ENABLE_CASE_CONNECTOR, + MAX_GENERATED_ALERTS_PER_SUB_CASE, +} from '../../../common/constants'; import { buildCaseUserActionItem, buildCommentUserActionItem, diff --git a/x-pack/plugins/cases/server/client/attachments/client.ts b/x-pack/plugins/cases/server/client/attachments/client.ts index a07633c0dd38c..d71496b764824 100644 --- a/x-pack/plugins/cases/server/client/attachments/client.ts +++ b/x-pack/plugins/cases/server/client/attachments/client.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { AlertResponse, CommentResponse } from '../../../common'; +import { AlertResponse, CommentResponse } from '../../../common/api'; import { CasesClient } from '../client'; import { CasesClientInternal } from '../client_internal'; diff --git a/x-pack/plugins/cases/server/client/attachments/delete.ts b/x-pack/plugins/cases/server/client/attachments/delete.ts index 89d097c5334b7..aa4d16e41ca52 100644 --- a/x-pack/plugins/cases/server/client/attachments/delete.ts +++ b/x-pack/plugins/cases/server/client/attachments/delete.ts @@ -9,13 +9,12 @@ import Boom from '@hapi/boom'; import pMap from 'p-map'; import { SavedObject } from 'kibana/public'; +import { AssociationType, CommentAttributes } from '../../../common/api'; import { - AssociationType, CASE_SAVED_OBJECT, - CommentAttributes, MAX_CONCURRENT_SEARCHES, SUB_CASE_SAVED_OBJECT, -} from '../../../common'; +} from '../../../common/constants'; import { CasesClientArgs } from '../types'; import { buildCommentUserActionItem } from '../../services/user_actions/helpers'; import { createCaseError, checkEnabledCaseConnectorOrThrow } from '../../common'; diff --git a/x-pack/plugins/cases/server/client/attachments/get.ts b/x-pack/plugins/cases/server/client/attachments/get.ts index 9a06c5142600a..95bee2ce99eb3 100644 --- a/x-pack/plugins/cases/server/client/attachments/get.ts +++ b/x-pack/plugins/cases/server/client/attachments/get.ts @@ -18,9 +18,9 @@ import { CommentResponseRt, CommentsResponse, CommentsResponseRt, - ENABLE_CASE_CONNECTOR, FindQueryParams, -} from '../../../common'; +} from '../../../common/api'; +import { ENABLE_CASE_CONNECTOR } from '../../../common/constants'; import { createCaseError, checkEnabledCaseConnectorOrThrow, diff --git a/x-pack/plugins/cases/server/client/attachments/update.ts b/x-pack/plugins/cases/server/client/attachments/update.ts index b5e9e6c372355..0423a949f3fee 100644 --- a/x-pack/plugins/cases/server/client/attachments/update.ts +++ b/x-pack/plugins/cases/server/client/attachments/update.ts @@ -12,13 +12,8 @@ import { SavedObjectsClientContract, Logger } from 'kibana/server'; import { LensServerPluginSetup } from '../../../../lens/server'; import { checkEnabledCaseConnectorOrThrow, CommentableCase, createCaseError } from '../../common'; import { buildCommentUserActionItem } from '../../services/user_actions/helpers'; -import { - CASE_SAVED_OBJECT, - SUB_CASE_SAVED_OBJECT, - CaseResponse, - CommentPatchRequest, - CommentRequest, -} from '../../../common'; +import { CaseResponse, CommentPatchRequest, CommentRequest } from '../../../common/api'; +import { CASE_SAVED_OBJECT, SUB_CASE_SAVED_OBJECT } from '../../../common/constants'; import { AttachmentService, CasesService } from '../../services'; import { CasesClientArgs } from '..'; import { decodeCommentRequest } from '../utils'; diff --git a/x-pack/plugins/cases/server/client/cases/client.ts b/x-pack/plugins/cases/server/client/cases/client.ts index 09386431200ed..b2673eef33dd5 100644 --- a/x-pack/plugins/cases/server/client/cases/client.ts +++ b/x-pack/plugins/cases/server/client/cases/client.ts @@ -13,7 +13,7 @@ import { AllTagsFindRequest, AllReportersFindRequest, CasesByAlertId, -} from '../../../common'; +} from '../../../common/api'; import { CasesClient } from '../client'; import { CasesClientInternal } from '../client_internal'; import { diff --git a/x-pack/plugins/cases/server/client/cases/create.ts b/x-pack/plugins/cases/server/client/cases/create.ts index 488bc523f7796..cee245aae1306 100644 --- a/x-pack/plugins/cases/server/client/cases/create.ts +++ b/x-pack/plugins/cases/server/client/cases/create.ts @@ -21,9 +21,8 @@ import { CasePostRequest, CaseType, OWNER_FIELD, - ENABLE_CASE_CONNECTOR, - MAX_TITLE_LENGTH, -} from '../../../common'; +} from '../../../common/api'; +import { ENABLE_CASE_CONNECTOR, MAX_TITLE_LENGTH } from '../../../common/constants'; import { buildCaseUserActionItem } from '../../services/user_actions/helpers'; import { Operations } from '../../authorization'; diff --git a/x-pack/plugins/cases/server/client/cases/delete.ts b/x-pack/plugins/cases/server/client/cases/delete.ts index 4333535f17a24..df202f3fbd6ae 100644 --- a/x-pack/plugins/cases/server/client/cases/delete.ts +++ b/x-pack/plugins/cases/server/client/cases/delete.ts @@ -8,13 +8,8 @@ import pMap from 'p-map'; import { Boom } from '@hapi/boom'; import { SavedObject, SavedObjectsClientContract, SavedObjectsFindResponse } from 'kibana/server'; -import { - CommentAttributes, - ENABLE_CASE_CONNECTOR, - MAX_CONCURRENT_SEARCHES, - OWNER_FIELD, - SubCaseAttributes, -} from '../../../common'; +import { CommentAttributes, SubCaseAttributes, OWNER_FIELD } from '../../../common/api'; +import { ENABLE_CASE_CONNECTOR, MAX_CONCURRENT_SEARCHES } from '../../../common/constants'; import { CasesClientArgs } from '..'; import { createCaseError } from '../../common'; import { AttachmentService, CasesService } from '../../services'; diff --git a/x-pack/plugins/cases/server/client/cases/find.ts b/x-pack/plugins/cases/server/client/cases/find.ts index 282ff956b7a6f..b0b7ade7311ef 100644 --- a/x-pack/plugins/cases/server/client/cases/find.ts +++ b/x-pack/plugins/cases/server/client/cases/find.ts @@ -18,7 +18,7 @@ import { caseStatuses, CasesFindResponseRt, excess, -} from '../../../common'; +} from '../../../common/api'; import { createCaseError, transformCases } from '../../common'; import { constructQueryOptions } from '../utils'; diff --git a/x-pack/plugins/cases/server/client/cases/get.ts b/x-pack/plugins/cases/server/client/cases/get.ts index 653df1efd2daa..5d061d57bb155 100644 --- a/x-pack/plugins/cases/server/client/cases/get.ts +++ b/x-pack/plugins/cases/server/client/cases/get.ts @@ -25,11 +25,11 @@ import { AllReportersFindRequest, CasesByAlertIDRequest, CasesByAlertIDRequestRt, - ENABLE_CASE_CONNECTOR, CasesByAlertId, CasesByAlertIdRt, CaseAttributes, -} from '../../../common'; +} from '../../../common/api'; +import { ENABLE_CASE_CONNECTOR } from '../../../common/constants'; import { countAlertsForID, createCaseError, flattenCaseSavedObject } from '../../common'; import { CasesClientArgs } from '..'; import { Operations } from '../../authorization'; diff --git a/x-pack/plugins/cases/server/client/cases/mock.ts b/x-pack/plugins/cases/server/client/cases/mock.ts index 22520cea11014..5f677bdbf4a73 100644 --- a/x-pack/plugins/cases/server/client/cases/mock.ts +++ b/x-pack/plugins/cases/server/client/cases/mock.ts @@ -12,8 +12,8 @@ import { CaseUserActionsResponse, AssociationType, CommentResponseAlertsType, - SECURITY_SOLUTION_OWNER, -} from '../../../common'; +} from '../../../common/api'; +import { SECURITY_SOLUTION_OWNER } from '../../../common/constants'; import { BasicParams } from './types'; diff --git a/x-pack/plugins/cases/server/client/cases/push.ts b/x-pack/plugins/cases/server/client/cases/push.ts index 953f8b88c990b..55196e2ea2a3e 100644 --- a/x-pack/plugins/cases/server/client/cases/push.ts +++ b/x-pack/plugins/cases/server/client/cases/push.ts @@ -15,10 +15,10 @@ import { CaseStatuses, ExternalServiceResponse, CaseType, - ENABLE_CASE_CONNECTOR, CasesConfigureAttributes, CaseAttributes, -} from '../../../common'; +} from '../../../common/api'; +import { ENABLE_CASE_CONNECTOR } from '../../../common/constants'; import { buildCaseUserActionItem } from '../../services/user_actions/helpers'; import { createIncident, getCommentContextFromAttributes } from './utils'; diff --git a/x-pack/plugins/cases/server/client/cases/types.ts b/x-pack/plugins/cases/server/client/cases/types.ts index fb400675136ef..f1d56e7132bd1 100644 --- a/x-pack/plugins/cases/server/client/cases/types.ts +++ b/x-pack/plugins/cases/server/client/cases/types.ts @@ -19,7 +19,7 @@ import { PushToServiceApiParamsSIR as ServiceNowSIRPushToServiceApiParams, ServiceNowITSMIncident, } from '../../../../actions/server/builtin_action_types/servicenow/types'; -import { CaseResponse, ConnectorMappingsAttributes } from '../../../common'; +import { CaseResponse, ConnectorMappingsAttributes } from '../../../common/api'; export type Incident = JiraIncident | ResilientIncident | ServiceNowITSMIncident; export type PushToServiceApiParams = diff --git a/x-pack/plugins/cases/server/client/cases/update.ts b/x-pack/plugins/cases/server/client/cases/update.ts index 455665dc7012c..8fa7fb618645b 100644 --- a/x-pack/plugins/cases/server/client/cases/update.ts +++ b/x-pack/plugins/cases/server/client/cases/update.ts @@ -22,8 +22,6 @@ import { nodeBuilder } from '@kbn/es-query'; import { AssociationType, - CASE_COMMENT_SAVED_OBJECT, - CASE_SAVED_OBJECT, CasePatchRequest, CasesPatchRequest, CasesPatchRequestRt, @@ -33,14 +31,18 @@ import { CaseType, CommentAttributes, CommentType, - ENABLE_CASE_CONNECTOR, excess, + throwErrors, + CaseAttributes, +} from '../../../common/api'; +import { + CASE_COMMENT_SAVED_OBJECT, + CASE_SAVED_OBJECT, + ENABLE_CASE_CONNECTOR, MAX_CONCURRENT_SEARCHES, SUB_CASE_SAVED_OBJECT, - throwErrors, MAX_TITLE_LENGTH, - CaseAttributes, -} from '../../../common'; +} from '../../../common/constants'; import { buildCaseUserActions } from '../../services/user_actions/helpers'; import { getCaseToUpdate } from '../utils'; diff --git a/x-pack/plugins/cases/server/client/cases/utils.test.ts b/x-pack/plugins/cases/server/client/cases/utils.test.ts index 315e9966d347b..0fffa304f6a23 100644 --- a/x-pack/plugins/cases/server/client/cases/utils.test.ts +++ b/x-pack/plugins/cases/server/client/cases/utils.test.ts @@ -32,7 +32,7 @@ import { transformFields, } from './utils'; import { flattenCaseSavedObject } from '../../common'; -import { SECURITY_SOLUTION_OWNER } from '../../../common'; +import { SECURITY_SOLUTION_OWNER } from '../../../common/constants'; import { casesConnectors } from '../../connectors'; const formatComment = { diff --git a/x-pack/plugins/cases/server/client/cases/utils.ts b/x-pack/plugins/cases/server/client/cases/utils.ts index f5cf2fe4b3f51..e992c6e25fb4e 100644 --- a/x-pack/plugins/cases/server/client/cases/utils.ts +++ b/x-pack/plugins/cases/server/client/cases/utils.ts @@ -7,6 +7,7 @@ import { i18n } from '@kbn/i18n'; import { flow } from 'lodash'; +import { isPush } from '../../../common/utils/user_actions'; import { ActionConnector, CaseFullExternalService, @@ -21,8 +22,7 @@ import { CommentRequestAlertType, CommentRequestActionsType, CaseUserActionResponse, - isPush, -} from '../../../common'; +} from '../../../common/api'; import { ActionsClient } from '../../../../actions/server'; import { CasesClientGetAlertsResponse } from '../../client/alerts/types'; import { diff --git a/x-pack/plugins/cases/server/client/client.ts b/x-pack/plugins/cases/server/client/client.ts index bd4e36bb7c177..f85667bee7bc3 100644 --- a/x-pack/plugins/cases/server/client/client.ts +++ b/x-pack/plugins/cases/server/client/client.ts @@ -11,7 +11,7 @@ import { AttachmentsSubClient, createAttachmentsSubClient } from './attachments/ import { UserActionsSubClient, createUserActionsSubClient } from './user_actions/client'; import { CasesClientInternal, createCasesClientInternal } from './client_internal'; import { createSubCasesClient, SubCasesClient } from './sub_cases/client'; -import { ENABLE_CASE_CONNECTOR } from '../../common'; +import { ENABLE_CASE_CONNECTOR } from '../../common/constants'; import { ConfigureSubClient, createConfigurationSubClient } from './configure/client'; import { createStatsSubClient, StatsSubClient } from './stats/client'; import { createMetricsSubClient, MetricsSubClient } from './metrics/client'; diff --git a/x-pack/plugins/cases/server/client/configure/client.ts b/x-pack/plugins/cases/server/client/configure/client.ts index 791fcc70947db..5982e100180f3 100644 --- a/x-pack/plugins/cases/server/client/configure/client.ts +++ b/x-pack/plugins/cases/server/client/configure/client.ts @@ -30,10 +30,9 @@ import { excess, GetConfigureFindRequest, GetConfigureFindRequestRt, - MAX_CONCURRENT_SEARCHES, - SUPPORTED_CONNECTORS, throwErrors, -} from '../../../common'; +} from '../../../common/api'; +import { MAX_CONCURRENT_SEARCHES, SUPPORTED_CONNECTORS } from '../../../common/constants'; import { createCaseError } from '../../common'; import { CasesClientInternal } from '../client_internal'; import { CasesClientArgs } from '../types'; diff --git a/x-pack/plugins/cases/server/client/configure/create_mappings.ts b/x-pack/plugins/cases/server/client/configure/create_mappings.ts index 2e9280b968d20..58b0f74225cb4 100644 --- a/x-pack/plugins/cases/server/client/configure/create_mappings.ts +++ b/x-pack/plugins/cases/server/client/configure/create_mappings.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ConnectorMappingsAttributes } from '../../../common'; +import { ConnectorMappingsAttributes } from '../../../common/api'; import { ACTION_SAVED_OBJECT_TYPE } from '../../../../actions/server'; import { createCaseError } from '../../common'; import { CasesClientArgs } from '..'; diff --git a/x-pack/plugins/cases/server/client/configure/get_mappings.ts b/x-pack/plugins/cases/server/client/configure/get_mappings.ts index c080159488cf2..ab38ccb8f696b 100644 --- a/x-pack/plugins/cases/server/client/configure/get_mappings.ts +++ b/x-pack/plugins/cases/server/client/configure/get_mappings.ts @@ -6,7 +6,7 @@ */ import { SavedObjectsFindResponse } from 'kibana/server'; -import { ConnectorMappings } from '../../../common'; +import { ConnectorMappings } from '../../../common/api'; import { ACTION_SAVED_OBJECT_TYPE } from '../../../../actions/server'; import { createCaseError } from '../../common'; import { CasesClientArgs } from '..'; diff --git a/x-pack/plugins/cases/server/client/configure/types.ts b/x-pack/plugins/cases/server/client/configure/types.ts index aca3436c59082..c7ac49c9e94aa 100644 --- a/x-pack/plugins/cases/server/client/configure/types.ts +++ b/x-pack/plugins/cases/server/client/configure/types.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { CaseConnector } from '../../../common'; +import { CaseConnector } from '../../../common/api'; export interface MappingsArgs { connector: CaseConnector; diff --git a/x-pack/plugins/cases/server/client/configure/update_mappings.ts b/x-pack/plugins/cases/server/client/configure/update_mappings.ts index 43fe527facd52..c7232b26674ad 100644 --- a/x-pack/plugins/cases/server/client/configure/update_mappings.ts +++ b/x-pack/plugins/cases/server/client/configure/update_mappings.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ConnectorMappingsAttributes } from '../../../common'; +import { ConnectorMappingsAttributes } from '../../../common/api'; import { ACTION_SAVED_OBJECT_TYPE } from '../../../../actions/server'; import { createCaseError } from '../../common'; import { CasesClientArgs } from '..'; diff --git a/x-pack/plugins/cases/server/client/factory.ts b/x-pack/plugins/cases/server/client/factory.ts index 4f506b5e0b4f7..d657f1a3f4f48 100644 --- a/x-pack/plugins/cases/server/client/factory.ts +++ b/x-pack/plugins/cases/server/client/factory.ts @@ -12,7 +12,7 @@ import { ElasticsearchClient, } from 'kibana/server'; import { SecurityPluginSetup, SecurityPluginStart } from '../../../security/server'; -import { SAVED_OBJECT_TYPES } from '../../common'; +import { SAVED_OBJECT_TYPES } from '../../common/constants'; import { Authorization } from '../authorization/authorization'; import { GetSpaceFn } from '../authorization/types'; import { diff --git a/x-pack/plugins/cases/server/client/metrics/alert_details.ts b/x-pack/plugins/cases/server/client/metrics/alert_details.ts index 4bdd820d99ef6..5d25ab5dc1226 100644 --- a/x-pack/plugins/cases/server/client/metrics/alert_details.ts +++ b/x-pack/plugins/cases/server/client/metrics/alert_details.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { CaseMetricsResponse } from '../../../common'; +import { CaseMetricsResponse } from '../../../common/api'; import { MetricsHandler } from './types'; export class AlertDetails implements MetricsHandler { diff --git a/x-pack/plugins/cases/server/client/metrics/alerts_count.ts b/x-pack/plugins/cases/server/client/metrics/alerts_count.ts index 11e2d32db7ca2..aa0e945bc5fcf 100644 --- a/x-pack/plugins/cases/server/client/metrics/alerts_count.ts +++ b/x-pack/plugins/cases/server/client/metrics/alerts_count.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { CaseMetricsResponse } from '../../../common'; +import { CaseMetricsResponse } from '../../../common/api'; import { MetricsHandler } from './types'; export class AlertsCount implements MetricsHandler { diff --git a/x-pack/plugins/cases/server/client/metrics/client.ts b/x-pack/plugins/cases/server/client/metrics/client.ts index 527ce527d0cc2..c5420213f3f97 100644 --- a/x-pack/plugins/cases/server/client/metrics/client.ts +++ b/x-pack/plugins/cases/server/client/metrics/client.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { CaseMetricsResponse } from '../../../common'; +import { CaseMetricsResponse } from '../../../common/api'; import { CasesClient } from '../client'; import { CasesClientArgs } from '../types'; diff --git a/x-pack/plugins/cases/server/client/metrics/connectors.ts b/x-pack/plugins/cases/server/client/metrics/connectors.ts index 6ad5fcc056ee5..727b5576b4fa2 100644 --- a/x-pack/plugins/cases/server/client/metrics/connectors.ts +++ b/x-pack/plugins/cases/server/client/metrics/connectors.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { CaseMetricsResponse } from '../../../common'; +import { CaseMetricsResponse } from '../../../common/api'; import { MetricsHandler } from './types'; export class Connectors implements MetricsHandler { diff --git a/x-pack/plugins/cases/server/client/metrics/get_case_metrics.test.ts b/x-pack/plugins/cases/server/client/metrics/get_case_metrics.test.ts index 072525d080f0a..cd3c9204e3c03 100644 --- a/x-pack/plugins/cases/server/client/metrics/get_case_metrics.test.ts +++ b/x-pack/plugins/cases/server/client/metrics/get_case_metrics.test.ts @@ -6,7 +6,7 @@ */ import { getCaseMetrics } from './get_case_metrics'; -import { CaseAttributes, CaseResponse } from '../../../common'; +import { CaseAttributes, CaseResponse } from '../../../common/api'; import { createCasesClientMock } from '../mocks'; import { CasesClientArgs } from '../types'; import { createAuthorizationMock } from '../../authorization/mock'; diff --git a/x-pack/plugins/cases/server/client/metrics/get_case_metrics.ts b/x-pack/plugins/cases/server/client/metrics/get_case_metrics.ts index a64325da8453e..ed67b9a1a99cd 100644 --- a/x-pack/plugins/cases/server/client/metrics/get_case_metrics.ts +++ b/x-pack/plugins/cases/server/client/metrics/get_case_metrics.ts @@ -6,7 +6,7 @@ */ import { merge } from 'lodash'; -import { CaseMetricsResponseRt, CaseMetricsResponse } from '../../../common'; +import { CaseMetricsResponseRt, CaseMetricsResponse } from '../../../common/api'; import { Operations } from '../../authorization'; import { createCaseError } from '../../common'; import { CasesClient } from '../client'; diff --git a/x-pack/plugins/cases/server/client/metrics/lifespan.ts b/x-pack/plugins/cases/server/client/metrics/lifespan.ts index ed1470738b366..5302b610c7aa0 100644 --- a/x-pack/plugins/cases/server/client/metrics/lifespan.ts +++ b/x-pack/plugins/cases/server/client/metrics/lifespan.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { CaseMetricsResponse } from '../../../common'; +import { CaseMetricsResponse } from '../../../common/api'; import { CasesClient } from '../client'; import { MetricsHandler } from './types'; diff --git a/x-pack/plugins/cases/server/client/metrics/types.ts b/x-pack/plugins/cases/server/client/metrics/types.ts index 82038f76feaa2..7dd3b22821538 100644 --- a/x-pack/plugins/cases/server/client/metrics/types.ts +++ b/x-pack/plugins/cases/server/client/metrics/types.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { CaseMetricsResponse } from '../../../common'; +import { CaseMetricsResponse } from '../../../common/api'; export interface MetricsHandler { getFeatures(): Set; diff --git a/x-pack/plugins/cases/server/client/stats/client.ts b/x-pack/plugins/cases/server/client/stats/client.ts index b13a15c799f9d..fe97446a4891c 100644 --- a/x-pack/plugins/cases/server/client/stats/client.ts +++ b/x-pack/plugins/cases/server/client/stats/client.ts @@ -19,7 +19,7 @@ import { throwErrors, excess, CasesStatusRequestRt, -} from '../../../common'; +} from '../../../common/api'; import { Operations } from '../../authorization'; import { createCaseError } from '../../common'; import { constructQueryOptions } from '../utils'; diff --git a/x-pack/plugins/cases/server/client/sub_cases/client.ts b/x-pack/plugins/cases/server/client/sub_cases/client.ts index 9b0395bbcb3b6..055e97a78b4fe 100644 --- a/x-pack/plugins/cases/server/client/sub_cases/client.ts +++ b/x-pack/plugins/cases/server/client/sub_cases/client.ts @@ -10,17 +10,16 @@ import Boom from '@hapi/boom'; import { SavedObject } from 'kibana/server'; import { - CASE_SAVED_OBJECT, caseStatuses, CommentAttributes, - MAX_CONCURRENT_SEARCHES, SubCaseResponse, SubCaseResponseRt, SubCasesFindRequest, SubCasesFindResponse, SubCasesFindResponseRt, SubCasesPatchRequest, -} from '../../../common'; +} from '../../../common/api'; +import { CASE_SAVED_OBJECT, MAX_CONCURRENT_SEARCHES } from '../../../common/constants'; import { CasesClientArgs } from '..'; import { countAlertsForID, diff --git a/x-pack/plugins/cases/server/client/sub_cases/update.ts b/x-pack/plugins/cases/server/client/sub_cases/update.ts index 3f602f7979d1f..9a40eb1bd085d 100644 --- a/x-pack/plugins/cases/server/client/sub_cases/update.ts +++ b/x-pack/plugins/cases/server/client/sub_cases/update.ts @@ -19,12 +19,10 @@ import { import { nodeBuilder } from '@kbn/es-query'; import { AlertService, CasesService } from '../../services'; import { - CASE_COMMENT_SAVED_OBJECT, CaseStatuses, CommentAttributes, CommentType, excess, - SUB_CASE_SAVED_OBJECT, SubCaseAttributes, SubCasePatchRequest, SubCaseResponse, @@ -35,7 +33,8 @@ import { throwErrors, User, CaseAttributes, -} from '../../../common'; +} from '../../../common/api'; +import { CASE_COMMENT_SAVED_OBJECT, SUB_CASE_SAVED_OBJECT } from '../../../common/constants'; import { getCaseToUpdate } from '../utils'; import { buildSubCaseUserActions } from '../../services/user_actions/helpers'; import { diff --git a/x-pack/plugins/cases/server/client/typedoc_interfaces.ts b/x-pack/plugins/cases/server/client/typedoc_interfaces.ts index feeaa6b6dcb58..b1dd4c47219d8 100644 --- a/x-pack/plugins/cases/server/client/typedoc_interfaces.ts +++ b/x-pack/plugins/cases/server/client/typedoc_interfaces.ts @@ -30,7 +30,7 @@ import { SubCaseResponse, SubCasesFindResponse, SubCasesResponse, -} from '../../common'; +} from '../../common/api'; /** * These are simply to make typedoc not attempt to expand the type aliases. If it attempts to expand them diff --git a/x-pack/plugins/cases/server/client/types.ts b/x-pack/plugins/cases/server/client/types.ts index f6c97df4f8b71..e3d7b8a541b9d 100644 --- a/x-pack/plugins/cases/server/client/types.ts +++ b/x-pack/plugins/cases/server/client/types.ts @@ -7,7 +7,7 @@ import type { PublicMethodsOf } from '@kbn/utility-types'; import { SavedObjectsClientContract, Logger } from 'kibana/server'; -import { User } from '../../common'; +import { User } from '../../common/api'; import { Authorization } from '../authorization/authorization'; import { CaseConfigureService, diff --git a/x-pack/plugins/cases/server/client/user_actions/get.test.ts b/x-pack/plugins/cases/server/client/user_actions/get.test.ts index 302e069cde4d1..28f427efba443 100644 --- a/x-pack/plugins/cases/server/client/user_actions/get.test.ts +++ b/x-pack/plugins/cases/server/client/user_actions/get.test.ts @@ -5,7 +5,8 @@ * 2.0. */ -import { CaseUserActionResponse, SUB_CASE_SAVED_OBJECT } from '../../../common'; +import { CaseUserActionResponse } from '../../../common/api'; +import { SUB_CASE_SAVED_OBJECT } from '../../../common/constants'; import { SUB_CASE_REF_NAME } from '../../common'; import { extractAttributesWithoutSubCases } from './get'; diff --git a/x-pack/plugins/cases/server/client/user_actions/get.ts b/x-pack/plugins/cases/server/client/user_actions/get.ts index 660cf1b6a336e..e52c578cefd6d 100644 --- a/x-pack/plugins/cases/server/client/user_actions/get.ts +++ b/x-pack/plugins/cases/server/client/user_actions/get.ts @@ -9,9 +9,9 @@ import { SavedObjectReference, SavedObjectsFindResponse } from 'kibana/server'; import { CaseUserActionsResponse, CaseUserActionsResponseRt, - SUB_CASE_SAVED_OBJECT, CaseUserActionResponse, -} from '../../../common'; +} from '../../../common/api'; +import { SUB_CASE_SAVED_OBJECT } from '../../../common/constants'; import { createCaseError, checkEnabledCaseConnectorOrThrow, SUB_CASE_REF_NAME } from '../../common'; import { CasesClientArgs } from '..'; import { Operations } from '../../authorization'; diff --git a/x-pack/plugins/cases/server/client/utils.ts b/x-pack/plugins/cases/server/client/utils.ts index 87bf4d04b3e8f..1b9c7828a910f 100644 --- a/x-pack/plugins/cases/server/client/utils.ts +++ b/x-pack/plugins/cases/server/client/utils.ts @@ -13,19 +13,18 @@ import { identity } from 'fp-ts/lib/function'; import { pipe } from 'fp-ts/lib/pipeable'; import { nodeBuilder, fromKueryExpression, KueryNode } from '@kbn/es-query'; +import { CASE_SAVED_OBJECT, SUB_CASE_SAVED_OBJECT } from '../../common/constants'; import { + OWNER_FIELD, AlertCommentRequestRt, ActionsCommentRequestRt, - CASE_SAVED_OBJECT, CaseStatuses, CaseType, CommentRequest, ContextTypeUserRt, excess, - OWNER_FIELD, - SUB_CASE_SAVED_OBJECT, throwErrors, -} from '../../common'; +} from '../../common/api'; import { combineFilterWithAuthorizationFilter } from '../authorization/utils'; import { getIDsAndIndicesAsArrays, diff --git a/x-pack/plugins/cases/server/common/constants.ts b/x-pack/plugins/cases/server/common/constants.ts index eba0a64a5c0be..556f34c208314 100644 --- a/x-pack/plugins/cases/server/common/constants.ts +++ b/x-pack/plugins/cases/server/common/constants.ts @@ -5,7 +5,11 @@ * 2.0. */ -import { CASE_COMMENT_SAVED_OBJECT, CASE_SAVED_OBJECT, SUB_CASE_SAVED_OBJECT } from '../../common'; +import { + CASE_COMMENT_SAVED_OBJECT, + CASE_SAVED_OBJECT, + SUB_CASE_SAVED_OBJECT, +} from '../../common/constants'; /** * The name of the saved object reference indicating the action connector ID. This is stored in the Saved Object reference diff --git a/x-pack/plugins/cases/server/common/models/commentable_case.ts b/x-pack/plugins/cases/server/common/models/commentable_case.ts index 856d6378d5900..38c8aefa034e0 100644 --- a/x-pack/plugins/cases/server/common/models/commentable_case.ts +++ b/x-pack/plugins/cases/server/common/models/commentable_case.ts @@ -17,7 +17,6 @@ import { import { LensServerPluginSetup } from '../../../../lens/server'; import { AssociationType, - CASE_SAVED_OBJECT, CaseResponse, CaseResponseRt, CaseSettings, @@ -27,13 +26,16 @@ import { CommentPatchRequest, CommentRequest, CommentType, - MAX_DOCS_PER_PAGE, - SUB_CASE_SAVED_OBJECT, SubCaseAttributes, User, CommentRequestUserType, CaseAttributes, -} from '../../../common'; +} from '../../../common/api'; +import { + CASE_SAVED_OBJECT, + MAX_DOCS_PER_PAGE, + SUB_CASE_SAVED_OBJECT, +} from '../../../common/constants'; import { flattenCommentSavedObjects, flattenSubCaseSavedObject, transformNewComment } from '..'; import { AttachmentService, CasesService } from '../../services'; import { createCaseError } from '../error'; diff --git a/x-pack/plugins/cases/server/common/types.ts b/x-pack/plugins/cases/server/common/types.ts index 364be027221d0..7a0d46148cf26 100644 --- a/x-pack/plugins/cases/server/common/types.ts +++ b/x-pack/plugins/cases/server/common/types.ts @@ -6,7 +6,7 @@ */ import type { KueryNode } from '@kbn/es-query'; -import { SavedObjectFindOptions } from '../../common'; +import { SavedObjectFindOptions } from '../../common/api'; /** * This structure holds the alert ID and index from an alert comment diff --git a/x-pack/plugins/cases/server/common/utils.test.ts b/x-pack/plugins/cases/server/common/utils.test.ts index 51d787a0334a2..841831b70eac5 100644 --- a/x-pack/plugins/cases/server/common/utils.test.ts +++ b/x-pack/plugins/cases/server/common/utils.test.ts @@ -7,7 +7,7 @@ import { SavedObject, SavedObjectsFindResponse } from 'kibana/server'; import { lensEmbeddableFactory } from '../../../lens/server/embeddable/lens_embeddable_factory'; -import { SECURITY_SOLUTION_OWNER } from '../../common'; +import { SECURITY_SOLUTION_OWNER } from '../../common/constants'; import { AssociationType, CaseResponse, diff --git a/x-pack/plugins/cases/server/common/utils.ts b/x-pack/plugins/cases/server/common/utils.ts index ae14603d44567..3e69c031e447d 100644 --- a/x-pack/plugins/cases/server/common/utils.ts +++ b/x-pack/plugins/cases/server/common/utils.ts @@ -32,12 +32,12 @@ import { CommentsResponse, CommentType, ConnectorTypes, - ENABLE_CASE_CONNECTOR, SubCaseAttributes, SubCaseResponse, SubCasesFindResponse, User, -} from '../../common'; +} from '../../common/api'; +import { ENABLE_CASE_CONNECTOR } from '../../common/constants'; import { UpdateAlertRequest } from '../client/alerts/types'; import { parseCommentString, diff --git a/x-pack/plugins/cases/server/connectors/case/index.test.ts b/x-pack/plugins/cases/server/connectors/case/index.test.ts index 51c45bd25444e..e5d8dbf3bb38d 100644 --- a/x-pack/plugins/cases/server/connectors/case/index.test.ts +++ b/x-pack/plugins/cases/server/connectors/case/index.test.ts @@ -27,7 +27,7 @@ import { createCasesClientFactory, createCasesClientMock, } from '../../client/mocks'; -import { SECURITY_SOLUTION_OWNER } from '../../../common'; +import { SECURITY_SOLUTION_OWNER } from '../../../common/constants'; const services = actionsMock.createServices(); let caseActionType: CaseActionType; diff --git a/x-pack/plugins/cases/server/connectors/case/index.ts b/x-pack/plugins/cases/server/connectors/case/index.ts index e566ab7cacc3f..7c8cc9cc71273 100644 --- a/x-pack/plugins/cases/server/connectors/case/index.ts +++ b/x-pack/plugins/cases/server/connectors/case/index.ts @@ -13,8 +13,8 @@ import { CasePostRequest, CommentRequest, CommentType, - ENABLE_CASE_CONNECTOR, -} from '../../../common'; +} from '../../../common/api'; +import { ENABLE_CASE_CONNECTOR } from '../../../common/constants'; import { CaseExecutorParamsSchema, CaseConfigurationSchema, CommentSchemaType } from './schema'; import { CaseExecutorResponse, diff --git a/x-pack/plugins/cases/server/connectors/case/schema.ts b/x-pack/plugins/cases/server/connectors/case/schema.ts index b8e46fdf5aa8c..86ee34284a661 100644 --- a/x-pack/plugins/cases/server/connectors/case/schema.ts +++ b/x-pack/plugins/cases/server/connectors/case/schema.ts @@ -6,7 +6,7 @@ */ import { schema } from '@kbn/config-schema'; -import { CommentType, ConnectorTypes } from '../../../common'; +import { CommentType, ConnectorTypes } from '../../../common/api'; import { validateConnector } from './validators'; // Reserved for future implementation diff --git a/x-pack/plugins/cases/server/connectors/case/types.ts b/x-pack/plugins/cases/server/connectors/case/types.ts index a71007f0b4946..6a7dfd9c2e687 100644 --- a/x-pack/plugins/cases/server/connectors/case/types.ts +++ b/x-pack/plugins/cases/server/connectors/case/types.ts @@ -16,7 +16,7 @@ import { ConnectorSchema, CommentSchema, } from './schema'; -import { CaseResponse, CasesResponse } from '../../../common'; +import { CaseResponse, CasesResponse } from '../../../common/api'; export type CaseConfiguration = TypeOf; export type Connector = TypeOf; diff --git a/x-pack/plugins/cases/server/connectors/case/validators.ts b/x-pack/plugins/cases/server/connectors/case/validators.ts index 6ab4f3a21a24f..163959eec4a6a 100644 --- a/x-pack/plugins/cases/server/connectors/case/validators.ts +++ b/x-pack/plugins/cases/server/connectors/case/validators.ts @@ -6,7 +6,7 @@ */ import { Connector } from './types'; -import { ConnectorTypes } from '../../../common'; +import { ConnectorTypes } from '../../../common/api'; export const validateConnector = (connector: Connector) => { if (connector.type === ConnectorTypes.none && connector.fields !== null) { diff --git a/x-pack/plugins/cases/server/connectors/factory.ts b/x-pack/plugins/cases/server/connectors/factory.ts index d0ae7154fe5d9..40a6702f11b0f 100644 --- a/x-pack/plugins/cases/server/connectors/factory.ts +++ b/x-pack/plugins/cases/server/connectors/factory.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ConnectorTypes } from '../../common'; +import { ConnectorTypes } from '../../common/api'; import { ICasesConnector, CasesConnectorsMap } from './types'; import { getCaseConnector as getJiraCaseConnector } from './jira'; import { getCaseConnector as getResilientCaseConnector } from './resilient'; diff --git a/x-pack/plugins/cases/server/connectors/index.ts b/x-pack/plugins/cases/server/connectors/index.ts index ee7c692c1525b..b5dc1cc4a8ff9 100644 --- a/x-pack/plugins/cases/server/connectors/index.ts +++ b/x-pack/plugins/cases/server/connectors/index.ts @@ -12,7 +12,7 @@ import { ContextTypeAlertSchemaType, } from './types'; import { getActionType as getCaseConnector } from './case'; -import { CommentRequest, CommentType } from '../../common'; +import { CommentRequest, CommentType } from '../../common/api'; export * from './types'; export { transformConnectorComment } from './case'; diff --git a/x-pack/plugins/cases/server/connectors/jira/format.ts b/x-pack/plugins/cases/server/connectors/jira/format.ts index b281d94062f4d..e283aff4b4ce9 100644 --- a/x-pack/plugins/cases/server/connectors/jira/format.ts +++ b/x-pack/plugins/cases/server/connectors/jira/format.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ConnectorJiraTypeFields } from '../../../common'; +import { ConnectorJiraTypeFields } from '../../../common/api'; import { Format } from './types'; export const format: Format = (theCase, alerts) => { diff --git a/x-pack/plugins/cases/server/connectors/jira/types.ts b/x-pack/plugins/cases/server/connectors/jira/types.ts index 1941485ccecee..59d5741d381b9 100644 --- a/x-pack/plugins/cases/server/connectors/jira/types.ts +++ b/x-pack/plugins/cases/server/connectors/jira/types.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { JiraFieldsType } from '../../../common'; +import { JiraFieldsType } from '../../../common/api'; import { ICasesConnector } from '../types'; interface ExternalServiceFormatterParams extends JiraFieldsType { diff --git a/x-pack/plugins/cases/server/connectors/resilient/format.test.ts b/x-pack/plugins/cases/server/connectors/resilient/format.test.ts index 20ba0bc378934..5cfd089b9aa8d 100644 --- a/x-pack/plugins/cases/server/connectors/resilient/format.test.ts +++ b/x-pack/plugins/cases/server/connectors/resilient/format.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { CaseResponse } from '../../../common'; +import { CaseResponse } from '../../../common/api'; import { format } from './format'; describe('IBM Resilient formatter', () => { diff --git a/x-pack/plugins/cases/server/connectors/resilient/format.ts b/x-pack/plugins/cases/server/connectors/resilient/format.ts index ba82e2e8d1ea3..64b701731c33f 100644 --- a/x-pack/plugins/cases/server/connectors/resilient/format.ts +++ b/x-pack/plugins/cases/server/connectors/resilient/format.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ConnectorResilientTypeFields } from '../../../common'; +import { ConnectorResilientTypeFields } from '../../../common/api'; import { Format } from './types'; export const format: Format = (theCase, alerts) => { diff --git a/x-pack/plugins/cases/server/connectors/resilient/types.ts b/x-pack/plugins/cases/server/connectors/resilient/types.ts index 40cde0500280c..f895dccf65214 100644 --- a/x-pack/plugins/cases/server/connectors/resilient/types.ts +++ b/x-pack/plugins/cases/server/connectors/resilient/types.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ResilientFieldsType } from '../../../common'; +import { ResilientFieldsType } from '../../../common/api'; import { ICasesConnector } from '../types'; export type ResilientCaseConnector = ICasesConnector; diff --git a/x-pack/plugins/cases/server/connectors/servicenow/itsm_format.ts b/x-pack/plugins/cases/server/connectors/servicenow/itsm_format.ts index 1859ea1246f21..81a20d006c22e 100644 --- a/x-pack/plugins/cases/server/connectors/servicenow/itsm_format.ts +++ b/x-pack/plugins/cases/server/connectors/servicenow/itsm_format.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ConnectorServiceNowITSMTypeFields } from '../../../common'; +import { ConnectorServiceNowITSMTypeFields } from '../../../common/api'; import { ServiceNowITSMFormat } from './types'; export const format: ServiceNowITSMFormat = (theCase, alerts) => { diff --git a/x-pack/plugins/cases/server/connectors/servicenow/sir_format.test.ts b/x-pack/plugins/cases/server/connectors/servicenow/sir_format.test.ts index 706b9f2f23ab5..9b24dfa672bf4 100644 --- a/x-pack/plugins/cases/server/connectors/servicenow/sir_format.test.ts +++ b/x-pack/plugins/cases/server/connectors/servicenow/sir_format.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { CaseResponse } from '../../../common'; +import { CaseResponse } from '../../../common/api'; import { format } from './sir_format'; describe('SIR formatter', () => { diff --git a/x-pack/plugins/cases/server/connectors/servicenow/sir_format.ts b/x-pack/plugins/cases/server/connectors/servicenow/sir_format.ts index 02c9fe629f4f8..dae1045502460 100644 --- a/x-pack/plugins/cases/server/connectors/servicenow/sir_format.ts +++ b/x-pack/plugins/cases/server/connectors/servicenow/sir_format.ts @@ -5,7 +5,7 @@ * 2.0. */ import { get } from 'lodash/fp'; -import { ConnectorServiceNowSIRTypeFields } from '../../../common'; +import { ConnectorServiceNowSIRTypeFields } from '../../../common/api'; import { ServiceNowSIRFormat, SirFieldKey, AlertFieldMappingAndValues } from './types'; export const format: ServiceNowSIRFormat = (theCase, alerts) => { diff --git a/x-pack/plugins/cases/server/connectors/servicenow/types.ts b/x-pack/plugins/cases/server/connectors/servicenow/types.ts index b0e71cbe5e743..531786730ff9a 100644 --- a/x-pack/plugins/cases/server/connectors/servicenow/types.ts +++ b/x-pack/plugins/cases/server/connectors/servicenow/types.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ServiceNowITSMFieldsType } from '../../../common'; +import { ServiceNowITSMFieldsType } from '../../../common/api'; import { ICasesConnector } from '../types'; interface CorrelationValues { diff --git a/x-pack/plugins/cases/server/connectors/swimlane/format.test.ts b/x-pack/plugins/cases/server/connectors/swimlane/format.test.ts index 55cbbdb68691e..e72ca3d145c99 100644 --- a/x-pack/plugins/cases/server/connectors/swimlane/format.test.ts +++ b/x-pack/plugins/cases/server/connectors/swimlane/format.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { CaseResponse } from '../../../common'; +import { CaseResponse } from '../../../common/api'; import { format } from './format'; describe('Swimlane formatter', () => { diff --git a/x-pack/plugins/cases/server/connectors/swimlane/format.ts b/x-pack/plugins/cases/server/connectors/swimlane/format.ts index 9531e4099a4f4..48983d745150b 100644 --- a/x-pack/plugins/cases/server/connectors/swimlane/format.ts +++ b/x-pack/plugins/cases/server/connectors/swimlane/format.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ConnectorSwimlaneTypeFields } from '../../../common'; +import { ConnectorSwimlaneTypeFields } from '../../../common/api'; import { Format } from './types'; export const format: Format = (theCase) => { diff --git a/x-pack/plugins/cases/server/connectors/types.ts b/x-pack/plugins/cases/server/connectors/types.ts index 62b2c8e6f1551..3754dfd62b7c5 100644 --- a/x-pack/plugins/cases/server/connectors/types.ts +++ b/x-pack/plugins/cases/server/connectors/types.ts @@ -6,7 +6,7 @@ */ import { Logger } from 'kibana/server'; -import { CaseResponse, ConnectorMappingsAttributes } from '../../common'; +import { CaseResponse, ConnectorMappingsAttributes } from '../../common/api'; import { CasesClientGetAlertsResponse } from '../client/alerts/types'; import { CasesClientFactory } from '../client/factory'; import { RegisterActionType } from '../types'; diff --git a/x-pack/plugins/cases/server/plugin.ts b/x-pack/plugins/cases/server/plugin.ts index 9bbc7089c033c..21ee1313ddb11 100644 --- a/x-pack/plugins/cases/server/plugin.ts +++ b/x-pack/plugins/cases/server/plugin.ts @@ -13,7 +13,7 @@ import { PluginSetupContract as ActionsPluginSetup, PluginStartContract as ActionsPluginStart, } from '../../actions/server'; -import { APP_ID, ENABLE_CASE_CONNECTOR } from '../common'; +import { APP_ID, ENABLE_CASE_CONNECTOR } from '../common/constants'; import { initCaseApi } from './routes/api'; import { diff --git a/x-pack/plugins/cases/server/routes/api/__fixtures__/mock_saved_objects.ts b/x-pack/plugins/cases/server/routes/api/__fixtures__/mock_saved_objects.ts index 1551f0fa611b7..6bff6c7d21725 100644 --- a/x-pack/plugins/cases/server/routes/api/__fixtures__/mock_saved_objects.ts +++ b/x-pack/plugins/cases/server/routes/api/__fixtures__/mock_saved_objects.ts @@ -14,8 +14,8 @@ import { CommentAttributes, CommentType, ConnectorTypes, - SECURITY_SOLUTION_OWNER, -} from '../../../../common'; +} from '../../../../common/api'; +import { SECURITY_SOLUTION_OWNER } from '../../../../common/constants'; export const mockCases: Array> = [ { diff --git a/x-pack/plugins/cases/server/routes/api/__mocks__/request_responses.ts b/x-pack/plugins/cases/server/routes/api/__mocks__/request_responses.ts index f3e6bcd7fc9ff..b398f9cfd1ba8 100644 --- a/x-pack/plugins/cases/server/routes/api/__mocks__/request_responses.ts +++ b/x-pack/plugins/cases/server/routes/api/__mocks__/request_responses.ts @@ -5,7 +5,8 @@ * 2.0. */ -import { SECURITY_SOLUTION_OWNER, CasePostRequest, ConnectorTypes } from '../../../../common'; +import { CasePostRequest, ConnectorTypes } from '../../../../common/api'; +import { SECURITY_SOLUTION_OWNER } from '../../../../common/constants'; export const newCase: CasePostRequest = { title: 'My new case', diff --git a/x-pack/plugins/cases/server/routes/api/cases/alerts/get_cases.ts b/x-pack/plugins/cases/server/routes/api/cases/alerts/get_cases.ts index 3471c1dec6208..8a490e2f68bd0 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/alerts/get_cases.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/alerts/get_cases.ts @@ -10,7 +10,8 @@ import Boom from '@hapi/boom'; import { RouteDeps } from '../../types'; import { escapeHatch, wrapError } from '../../utils'; -import { CASE_ALERTS_URL, CasesByAlertIDRequest } from '../../../../../common'; +import { CasesByAlertIDRequest } from '../../../../../common/api'; +import { CASE_ALERTS_URL } from '../../../../../common/constants'; export function initGetCasesByAlertIdApi({ router, logger }: RouteDeps) { router.get( diff --git a/x-pack/plugins/cases/server/routes/api/cases/delete_cases.ts b/x-pack/plugins/cases/server/routes/api/cases/delete_cases.ts index 383f9b82706a4..1784a434292cc 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/delete_cases.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/delete_cases.ts @@ -9,7 +9,7 @@ import { schema } from '@kbn/config-schema'; import { RouteDeps } from '../types'; import { wrapError } from '../utils'; -import { CASES_URL } from '../../../../common'; +import { CASES_URL } from '../../../../common/constants'; export function initDeleteCasesApi({ router, logger }: RouteDeps) { router.delete( diff --git a/x-pack/plugins/cases/server/routes/api/cases/find_cases.ts b/x-pack/plugins/cases/server/routes/api/cases/find_cases.ts index 9b3d186ca0adc..8474d781a202a 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/find_cases.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/find_cases.ts @@ -5,7 +5,8 @@ * 2.0. */ -import { CASES_URL, CasesFindRequest } from '../../../../common'; +import { CasesFindRequest } from '../../../../common/api'; +import { CASES_URL } from '../../../../common/constants'; import { wrapError, escapeHatch } from '../utils'; import { RouteDeps } from '../types'; diff --git a/x-pack/plugins/cases/server/routes/api/cases/get_case.ts b/x-pack/plugins/cases/server/routes/api/cases/get_case.ts index 4d81b6d5e11b3..b9c50a86fb8fe 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/get_case.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/get_case.ts @@ -9,7 +9,7 @@ import { schema } from '@kbn/config-schema'; import { RouteDeps } from '../types'; import { wrapError } from '../utils'; -import { CASE_DETAILS_URL } from '../../../../common'; +import { CASE_DETAILS_URL } from '../../../../common/constants'; export function initGetCaseApi({ router, logger }: RouteDeps) { router.get( diff --git a/x-pack/plugins/cases/server/routes/api/cases/patch_cases.ts b/x-pack/plugins/cases/server/routes/api/cases/patch_cases.ts index e518d3717fcda..5cde28bcb01f9 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/patch_cases.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/patch_cases.ts @@ -7,7 +7,8 @@ import { escapeHatch, wrapError } from '../utils'; import { RouteDeps } from '../types'; -import { CASES_URL, CasesPatchRequest } from '../../../../common'; +import { CasesPatchRequest } from '../../../../common/api'; +import { CASES_URL } from '../../../../common/constants'; export function initPatchCasesApi({ router, logger }: RouteDeps) { router.patch( diff --git a/x-pack/plugins/cases/server/routes/api/cases/post_case.ts b/x-pack/plugins/cases/server/routes/api/cases/post_case.ts index 6ee94df007d64..df994f18c5bbd 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/post_case.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/post_case.ts @@ -8,7 +8,8 @@ import { wrapError, escapeHatch } from '../utils'; import { RouteDeps } from '../types'; -import { CASES_URL, CasePostRequest } from '../../../../common'; +import { CasePostRequest } from '../../../../common/api'; +import { CASES_URL } from '../../../../common/constants'; export function initPostCaseApi({ router, logger }: RouteDeps) { router.post( diff --git a/x-pack/plugins/cases/server/routes/api/cases/push_case.ts b/x-pack/plugins/cases/server/routes/api/cases/push_case.ts index 4c467c3840c2b..2b3e7954febfe 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/push_case.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/push_case.ts @@ -12,7 +12,8 @@ import { identity } from 'fp-ts/lib/function'; import { wrapError, escapeHatch } from '../utils'; -import { throwErrors, CasePushRequestParamsRt, CASE_PUSH_URL } from '../../../../common'; +import { throwErrors, CasePushRequestParamsRt } from '../../../../common/api'; +import { CASE_PUSH_URL } from '../../../../common/constants'; import { RouteDeps } from '../types'; export function initPushCaseApi({ router, logger }: RouteDeps) { diff --git a/x-pack/plugins/cases/server/routes/api/cases/reporters/get_reporters.ts b/x-pack/plugins/cases/server/routes/api/cases/reporters/get_reporters.ts index 109cac0d977ca..8e0d0640263ec 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/reporters/get_reporters.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/reporters/get_reporters.ts @@ -7,7 +7,8 @@ import { RouteDeps } from '../../types'; import { wrapError, escapeHatch } from '../../utils'; -import { CASE_REPORTERS_URL, AllReportersFindRequest } from '../../../../../common'; +import { AllReportersFindRequest } from '../../../../../common/api'; +import { CASE_REPORTERS_URL } from '../../../../../common/constants'; export function initGetReportersApi({ router, logger }: RouteDeps) { router.get( diff --git a/x-pack/plugins/cases/server/routes/api/cases/tags/get_tags.ts b/x-pack/plugins/cases/server/routes/api/cases/tags/get_tags.ts index 778261c048bf0..2afa96be95bc1 100644 --- a/x-pack/plugins/cases/server/routes/api/cases/tags/get_tags.ts +++ b/x-pack/plugins/cases/server/routes/api/cases/tags/get_tags.ts @@ -7,7 +7,8 @@ import { RouteDeps } from '../../types'; import { wrapError, escapeHatch } from '../../utils'; -import { CASE_TAGS_URL, AllTagsFindRequest } from '../../../../../common'; +import { AllTagsFindRequest } from '../../../../../common/api'; +import { CASE_TAGS_URL } from '../../../../../common/constants'; export function initGetTagsApi({ router, logger }: RouteDeps) { router.get( diff --git a/x-pack/plugins/cases/server/routes/api/comments/delete_all_comments.ts b/x-pack/plugins/cases/server/routes/api/comments/delete_all_comments.ts index 9c77b1814376f..a41d4683af2d0 100644 --- a/x-pack/plugins/cases/server/routes/api/comments/delete_all_comments.ts +++ b/x-pack/plugins/cases/server/routes/api/comments/delete_all_comments.ts @@ -8,7 +8,7 @@ import { schema } from '@kbn/config-schema'; import { RouteDeps } from '../types'; import { wrapError } from '../utils'; -import { CASE_COMMENTS_URL } from '../../../../common'; +import { CASE_COMMENTS_URL } from '../../../../common/constants'; export function initDeleteAllCommentsApi({ router, logger }: RouteDeps) { router.delete( diff --git a/x-pack/plugins/cases/server/routes/api/comments/delete_comment.ts b/x-pack/plugins/cases/server/routes/api/comments/delete_comment.ts index 6dfb188763aa1..f145fc62efc8a 100644 --- a/x-pack/plugins/cases/server/routes/api/comments/delete_comment.ts +++ b/x-pack/plugins/cases/server/routes/api/comments/delete_comment.ts @@ -9,7 +9,7 @@ import { schema } from '@kbn/config-schema'; import { RouteDeps } from '../types'; import { wrapError } from '../utils'; -import { CASE_COMMENT_DETAILS_URL } from '../../../../common'; +import { CASE_COMMENT_DETAILS_URL } from '../../../../common/constants'; export function initDeleteCommentApi({ router, logger }: RouteDeps) { router.delete( diff --git a/x-pack/plugins/cases/server/routes/api/comments/find_comments.ts b/x-pack/plugins/cases/server/routes/api/comments/find_comments.ts index c0e4d8901eec6..d4c65e6306a63 100644 --- a/x-pack/plugins/cases/server/routes/api/comments/find_comments.ts +++ b/x-pack/plugins/cases/server/routes/api/comments/find_comments.ts @@ -12,7 +12,8 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; -import { CASE_COMMENTS_URL, FindQueryParamsRt, throwErrors, excess } from '../../../../common'; +import { FindQueryParamsRt, throwErrors, excess } from '../../../../common/api'; +import { CASE_COMMENTS_URL } from '../../../../common/constants'; import { RouteDeps } from '../types'; import { escapeHatch, wrapError } from '../utils'; diff --git a/x-pack/plugins/cases/server/routes/api/comments/get_all_comment.ts b/x-pack/plugins/cases/server/routes/api/comments/get_all_comment.ts index 41a4b6f796655..b916e22c6b0ed 100644 --- a/x-pack/plugins/cases/server/routes/api/comments/get_all_comment.ts +++ b/x-pack/plugins/cases/server/routes/api/comments/get_all_comment.ts @@ -9,7 +9,7 @@ import { schema } from '@kbn/config-schema'; import { RouteDeps } from '../types'; import { wrapError } from '../utils'; -import { CASE_COMMENTS_URL } from '../../../../common'; +import { CASE_COMMENTS_URL } from '../../../../common/constants'; export function initGetAllCommentsApi({ router, logger }: RouteDeps) { router.get( diff --git a/x-pack/plugins/cases/server/routes/api/comments/get_comment.ts b/x-pack/plugins/cases/server/routes/api/comments/get_comment.ts index a3ba0d3f23c37..09805c00cb10a 100644 --- a/x-pack/plugins/cases/server/routes/api/comments/get_comment.ts +++ b/x-pack/plugins/cases/server/routes/api/comments/get_comment.ts @@ -9,7 +9,7 @@ import { schema } from '@kbn/config-schema'; import { RouteDeps } from '../types'; import { wrapError } from '../utils'; -import { CASE_COMMENT_DETAILS_URL } from '../../../../common'; +import { CASE_COMMENT_DETAILS_URL } from '../../../../common/constants'; export function initGetCommentApi({ router, logger }: RouteDeps) { router.get( diff --git a/x-pack/plugins/cases/server/routes/api/comments/patch_comment.ts b/x-pack/plugins/cases/server/routes/api/comments/patch_comment.ts index 687b568e67d7f..d6ac39f11b91e 100644 --- a/x-pack/plugins/cases/server/routes/api/comments/patch_comment.ts +++ b/x-pack/plugins/cases/server/routes/api/comments/patch_comment.ts @@ -13,7 +13,8 @@ import Boom from '@hapi/boom'; import { RouteDeps } from '../types'; import { escapeHatch, wrapError } from '../utils'; -import { CASE_COMMENTS_URL, CommentPatchRequestRt, throwErrors } from '../../../../common'; +import { CommentPatchRequestRt, throwErrors } from '../../../../common/api'; +import { CASE_COMMENTS_URL } from '../../../../common/constants'; export function initPatchCommentApi({ router, logger }: RouteDeps) { router.patch( diff --git a/x-pack/plugins/cases/server/routes/api/comments/post_comment.ts b/x-pack/plugins/cases/server/routes/api/comments/post_comment.ts index 44871f7f0c81c..1919aef7b72b4 100644 --- a/x-pack/plugins/cases/server/routes/api/comments/post_comment.ts +++ b/x-pack/plugins/cases/server/routes/api/comments/post_comment.ts @@ -9,7 +9,8 @@ import Boom from '@hapi/boom'; import { schema } from '@kbn/config-schema'; import { escapeHatch, wrapError } from '../utils'; import { RouteDeps } from '../types'; -import { CASE_COMMENTS_URL, ENABLE_CASE_CONNECTOR, CommentRequest } from '../../../../common'; +import { CASE_COMMENTS_URL, ENABLE_CASE_CONNECTOR } from '../../../../common/constants'; +import { CommentRequest } from '../../../../common/api'; export function initPostCommentApi({ router, logger }: RouteDeps) { router.post( diff --git a/x-pack/plugins/cases/server/routes/api/configure/get_configure.ts b/x-pack/plugins/cases/server/routes/api/configure/get_configure.ts index 59f136b971da4..8222ac8fe5690 100644 --- a/x-pack/plugins/cases/server/routes/api/configure/get_configure.ts +++ b/x-pack/plugins/cases/server/routes/api/configure/get_configure.ts @@ -7,7 +7,8 @@ import { RouteDeps } from '../types'; import { escapeHatch, wrapError } from '../utils'; -import { CASE_CONFIGURE_URL, GetConfigureFindRequest } from '../../../../common'; +import { CASE_CONFIGURE_URL } from '../../../../common/constants'; +import { GetConfigureFindRequest } from '../../../../common/api'; export function initGetCaseConfigure({ router, logger }: RouteDeps) { router.get( diff --git a/x-pack/plugins/cases/server/routes/api/configure/get_connectors.ts b/x-pack/plugins/cases/server/routes/api/configure/get_connectors.ts index 220481e8ff07e..46c110bbb8ba5 100644 --- a/x-pack/plugins/cases/server/routes/api/configure/get_connectors.ts +++ b/x-pack/plugins/cases/server/routes/api/configure/get_connectors.ts @@ -8,7 +8,7 @@ import { RouteDeps } from '../types'; import { wrapError } from '../utils'; -import { CASE_CONFIGURE_CONNECTORS_URL } from '../../../../common'; +import { CASE_CONFIGURE_CONNECTORS_URL } from '../../../../common/constants'; /* * Be aware that this api will only return 20 connectors diff --git a/x-pack/plugins/cases/server/routes/api/configure/patch_configure.ts b/x-pack/plugins/cases/server/routes/api/configure/patch_configure.ts index a50753413585b..e856a568f387a 100644 --- a/x-pack/plugins/cases/server/routes/api/configure/patch_configure.ts +++ b/x-pack/plugins/cases/server/routes/api/configure/patch_configure.ts @@ -11,12 +11,12 @@ import { fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; import { - CASE_CONFIGURE_DETAILS_URL, CaseConfigureRequestParamsRt, throwErrors, CasesConfigurePatch, excess, -} from '../../../../common'; +} from '../../../../common/api'; +import { CASE_CONFIGURE_DETAILS_URL } from '../../../../common/constants'; import { RouteDeps } from '../types'; import { wrapError, escapeHatch } from '../utils'; diff --git a/x-pack/plugins/cases/server/routes/api/configure/post_configure.ts b/x-pack/plugins/cases/server/routes/api/configure/post_configure.ts index b444ed119318d..ed4c3529f2ca0 100644 --- a/x-pack/plugins/cases/server/routes/api/configure/post_configure.ts +++ b/x-pack/plugins/cases/server/routes/api/configure/post_configure.ts @@ -10,7 +10,8 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; -import { CASE_CONFIGURE_URL, CasesConfigureRequestRt, throwErrors } from '../../../../common'; +import { CasesConfigureRequestRt, throwErrors } from '../../../../common/api'; +import { CASE_CONFIGURE_URL } from '../../../../common/constants'; import { RouteDeps } from '../types'; import { wrapError, escapeHatch } from '../utils'; diff --git a/x-pack/plugins/cases/server/routes/api/index.ts b/x-pack/plugins/cases/server/routes/api/index.ts index 70daef5b528d6..f844505369f93 100644 --- a/x-pack/plugins/cases/server/routes/api/index.ts +++ b/x-pack/plugins/cases/server/routes/api/index.ts @@ -37,7 +37,7 @@ import { initGetSubCaseApi } from './sub_case/get_sub_case'; import { initPatchSubCasesApi } from './sub_case/patch_sub_cases'; import { initFindSubCasesApi } from './sub_case/find_sub_cases'; import { initDeleteSubCasesApi } from './sub_case/delete_sub_cases'; -import { ENABLE_CASE_CONNECTOR } from '../../../common'; +import { ENABLE_CASE_CONNECTOR } from '../../../common/constants'; import { initGetCasesByAlertIdApi } from './cases/alerts/get_cases'; import { initGetAllAlertsAttachToCaseApi } from './comments/get_alerts'; import { initGetCaseMetricsApi } from './metrics/get_case_metrics'; diff --git a/x-pack/plugins/cases/server/routes/api/metrics/get_case_metrics.ts b/x-pack/plugins/cases/server/routes/api/metrics/get_case_metrics.ts index 27b9d139770ce..0cfad10b28316 100644 --- a/x-pack/plugins/cases/server/routes/api/metrics/get_case_metrics.ts +++ b/x-pack/plugins/cases/server/routes/api/metrics/get_case_metrics.ts @@ -10,7 +10,7 @@ import { schema } from '@kbn/config-schema'; import { RouteDeps } from '../types'; import { wrapError } from '../utils'; -import { CASE_METRICS_DETAILS_URL } from '../../../../common'; +import { CASE_METRICS_DETAILS_URL } from '../../../../common/constants'; export function initGetCaseMetricsApi({ router, logger }: RouteDeps) { router.get( diff --git a/x-pack/plugins/cases/server/routes/api/stats/get_status.ts b/x-pack/plugins/cases/server/routes/api/stats/get_status.ts index 469e32b466224..4f666c399d8fd 100644 --- a/x-pack/plugins/cases/server/routes/api/stats/get_status.ts +++ b/x-pack/plugins/cases/server/routes/api/stats/get_status.ts @@ -8,7 +8,8 @@ import { RouteDeps } from '../types'; import { escapeHatch, wrapError } from '../utils'; -import { CASE_STATUS_URL, CasesStatusRequest } from '../../../../common'; +import { CasesStatusRequest } from '../../../../common/api'; +import { CASE_STATUS_URL } from '../../../../common/constants'; export function initGetCasesStatusApi({ router, logger }: RouteDeps) { router.get( diff --git a/x-pack/plugins/cases/server/routes/api/sub_case/delete_sub_cases.ts b/x-pack/plugins/cases/server/routes/api/sub_case/delete_sub_cases.ts index 0fe436f2269b5..11b68b70390fe 100644 --- a/x-pack/plugins/cases/server/routes/api/sub_case/delete_sub_cases.ts +++ b/x-pack/plugins/cases/server/routes/api/sub_case/delete_sub_cases.ts @@ -8,7 +8,7 @@ import { schema } from '@kbn/config-schema'; import { RouteDeps } from '../types'; import { wrapError } from '../utils'; -import { SUB_CASES_PATCH_DEL_URL } from '../../../../common'; +import { SUB_CASES_PATCH_DEL_URL } from '../../../../common/constants'; export function initDeleteSubCasesApi({ router, logger }: RouteDeps) { router.delete( diff --git a/x-pack/plugins/cases/server/routes/api/sub_case/find_sub_cases.ts b/x-pack/plugins/cases/server/routes/api/sub_case/find_sub_cases.ts index 3049f05337b40..8ee5fa21c3a3e 100644 --- a/x-pack/plugins/cases/server/routes/api/sub_case/find_sub_cases.ts +++ b/x-pack/plugins/cases/server/routes/api/sub_case/find_sub_cases.ts @@ -12,7 +12,8 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; -import { SubCasesFindRequestRt, SUB_CASES_URL, throwErrors } from '../../../../common'; +import { SUB_CASES_URL } from '../../../../common/constants'; +import { SubCasesFindRequestRt, throwErrors } from '../../../../common/api'; import { RouteDeps } from '../types'; import { escapeHatch, wrapError } from '../utils'; diff --git a/x-pack/plugins/cases/server/routes/api/sub_case/get_sub_case.ts b/x-pack/plugins/cases/server/routes/api/sub_case/get_sub_case.ts index fea81524b526e..db3e29f5ed96e 100644 --- a/x-pack/plugins/cases/server/routes/api/sub_case/get_sub_case.ts +++ b/x-pack/plugins/cases/server/routes/api/sub_case/get_sub_case.ts @@ -9,7 +9,7 @@ import { schema } from '@kbn/config-schema'; import { RouteDeps } from '../types'; import { wrapError } from '../utils'; -import { SUB_CASE_DETAILS_URL } from '../../../../common'; +import { SUB_CASE_DETAILS_URL } from '../../../../common/constants'; export function initGetSubCaseApi({ router, logger }: RouteDeps) { router.get( diff --git a/x-pack/plugins/cases/server/routes/api/sub_case/patch_sub_cases.ts b/x-pack/plugins/cases/server/routes/api/sub_case/patch_sub_cases.ts index d3b24a1e3c06f..1fb260453d188 100644 --- a/x-pack/plugins/cases/server/routes/api/sub_case/patch_sub_cases.ts +++ b/x-pack/plugins/cases/server/routes/api/sub_case/patch_sub_cases.ts @@ -5,7 +5,8 @@ * 2.0. */ -import { SubCasesPatchRequest, SUB_CASES_PATCH_DEL_URL } from '../../../../common'; +import { SubCasesPatchRequest } from '../../../../common/api'; +import { SUB_CASES_PATCH_DEL_URL } from '../../../../common/constants'; import { RouteDeps } from '../types'; import { escapeHatch, wrapError } from '../utils'; diff --git a/x-pack/plugins/cases/server/routes/api/user_actions/get_all_user_actions.ts b/x-pack/plugins/cases/server/routes/api/user_actions/get_all_user_actions.ts index 39b277e2239ad..5944ff6176d78 100644 --- a/x-pack/plugins/cases/server/routes/api/user_actions/get_all_user_actions.ts +++ b/x-pack/plugins/cases/server/routes/api/user_actions/get_all_user_actions.ts @@ -9,7 +9,7 @@ import { schema } from '@kbn/config-schema'; import { RouteDeps } from '../types'; import { wrapError } from '../utils'; -import { CASE_USER_ACTIONS_URL, SUB_CASE_USER_ACTIONS_URL } from '../../../../common'; +import { CASE_USER_ACTIONS_URL, SUB_CASE_USER_ACTIONS_URL } from '../../../../common/constants'; export function initGetAllCaseUserActionsApi({ router, logger }: RouteDeps) { router.get( diff --git a/x-pack/plugins/cases/server/saved_object_types/cases.ts b/x-pack/plugins/cases/server/saved_object_types/cases.ts index 53c52c03afa12..b94b387798262 100644 --- a/x-pack/plugins/cases/server/saved_object_types/cases.ts +++ b/x-pack/plugins/cases/server/saved_object_types/cases.ts @@ -12,7 +12,7 @@ import { SavedObjectsExportTransformContext, SavedObjectsType, } from 'src/core/server'; -import { CASE_SAVED_OBJECT } from '../../common'; +import { CASE_SAVED_OBJECT } from '../../common/constants'; import { ESCaseAttributes } from '../services/cases/types'; import { handleExport } from './import_export/export'; import { caseMigrations } from './migrations'; diff --git a/x-pack/plugins/cases/server/saved_object_types/comments.ts b/x-pack/plugins/cases/server/saved_object_types/comments.ts index c950a432a3440..bc0993a345a5b 100644 --- a/x-pack/plugins/cases/server/saved_object_types/comments.ts +++ b/x-pack/plugins/cases/server/saved_object_types/comments.ts @@ -6,7 +6,7 @@ */ import { SavedObjectsType } from 'src/core/server'; -import { CASE_COMMENT_SAVED_OBJECT } from '../../common'; +import { CASE_COMMENT_SAVED_OBJECT } from '../../common/constants'; import { createCommentsMigrations, CreateCommentsMigrationsDeps } from './migrations'; export const createCaseCommentSavedObjectType = ({ diff --git a/x-pack/plugins/cases/server/saved_object_types/configure.ts b/x-pack/plugins/cases/server/saved_object_types/configure.ts index de478cae9326e..c9303b848dc23 100644 --- a/x-pack/plugins/cases/server/saved_object_types/configure.ts +++ b/x-pack/plugins/cases/server/saved_object_types/configure.ts @@ -6,7 +6,7 @@ */ import { SavedObjectsType } from 'src/core/server'; -import { CASE_CONFIGURE_SAVED_OBJECT } from '../../common'; +import { CASE_CONFIGURE_SAVED_OBJECT } from '../../common/constants'; import { configureMigrations } from './migrations'; export const caseConfigureSavedObjectType: SavedObjectsType = { diff --git a/x-pack/plugins/cases/server/saved_object_types/connector_mappings.ts b/x-pack/plugins/cases/server/saved_object_types/connector_mappings.ts index 479edcba21534..b9bb275f080cf 100644 --- a/x-pack/plugins/cases/server/saved_object_types/connector_mappings.ts +++ b/x-pack/plugins/cases/server/saved_object_types/connector_mappings.ts @@ -6,7 +6,7 @@ */ import { SavedObjectsType } from 'src/core/server'; -import { CASE_CONNECTOR_MAPPINGS_SAVED_OBJECT } from '../../common'; +import { CASE_CONNECTOR_MAPPINGS_SAVED_OBJECT } from '../../common/constants'; import { connectorMappingsMigrations } from './migrations'; export const caseConnectorMappingsSavedObjectType: SavedObjectsType = { diff --git a/x-pack/plugins/cases/server/saved_object_types/import_export/export.ts b/x-pack/plugins/cases/server/saved_object_types/import_export/export.ts index d089079314443..b8916c125c42f 100644 --- a/x-pack/plugins/cases/server/saved_object_types/import_export/export.ts +++ b/x-pack/plugins/cases/server/saved_object_types/import_export/export.ts @@ -12,15 +12,14 @@ import { SavedObjectsClientContract, SavedObjectsExportTransformContext, } from 'kibana/server'; +import { CaseUserActionAttributes, CommentAttributes } from '../../../common/api'; import { - CaseUserActionAttributes, CASE_COMMENT_SAVED_OBJECT, CASE_SAVED_OBJECT, CASE_USER_ACTION_SAVED_OBJECT, - CommentAttributes, MAX_DOCS_PER_PAGE, SAVED_OBJECT_TYPES, -} from '../../../common'; +} from '../../../common/constants'; import { createCaseError, defaultSortField } from '../../common'; import { ESCaseAttributes } from '../../services/cases/types'; diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/cases.test.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/cases.test.ts index 9020f65ae352c..9c2495cc7d6ad 100644 --- a/x-pack/plugins/cases/server/saved_object_types/migrations/cases.test.ts +++ b/x-pack/plugins/cases/server/saved_object_types/migrations/cases.test.ts @@ -9,10 +9,10 @@ import { SavedObjectSanitizedDoc } from 'kibana/server'; import { CaseAttributes, CaseFullExternalService, - CASE_SAVED_OBJECT, ConnectorTypes, noneConnectorId, -} from '../../../common'; +} from '../../../common/api'; +import { CASE_SAVED_OBJECT } from '../../../common/constants'; import { getNoneCaseConnector } from '../../common'; import { createExternalService, ESCaseConnectorWithId } from '../../services/test_utils'; import { caseConnectorIdMigration } from './cases'; diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/cases.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/cases.ts index 80f02fa3bf6a6..b74e12e630ddc 100644 --- a/x-pack/plugins/cases/server/saved_object_types/migrations/cases.ts +++ b/x-pack/plugins/cases/server/saved_object_types/migrations/cases.ts @@ -13,7 +13,7 @@ import { SavedObjectSanitizedDoc, } from '../../../../../../src/core/server'; import { ESConnectorFields } from '../../services'; -import { ConnectorTypes, CaseType } from '../../../common'; +import { ConnectorTypes, CaseType } from '../../../common/api'; import { transformConnectorIdToReference, transformPushConnectorIdToReference, diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/comments.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/comments.ts index d754aec636693..e67e1c8b59887 100644 --- a/x-pack/plugins/cases/server/saved_object_types/migrations/comments.ts +++ b/x-pack/plugins/cases/server/saved_object_types/migrations/comments.ts @@ -20,7 +20,7 @@ import { SavedObjectMigrationFn, SavedObjectMigrationMap, } from '../../../../../../src/core/server'; -import { CommentType, AssociationType } from '../../../common'; +import { CommentType, AssociationType } from '../../../common/api'; import { isLensMarkdownNode, LensMarkdownNode, diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/configuration.test.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/configuration.test.ts index 9ae0285598dbf..a05fe349412b1 100644 --- a/x-pack/plugins/cases/server/saved_object_types/migrations/configuration.test.ts +++ b/x-pack/plugins/cases/server/saved_object_types/migrations/configuration.test.ts @@ -7,11 +7,8 @@ import { SavedObjectSanitizedDoc } from 'kibana/server'; import { ACTION_SAVED_OBJECT_TYPE } from '../../../../actions/server'; -import { - CASE_CONFIGURE_SAVED_OBJECT, - ConnectorTypes, - SECURITY_SOLUTION_OWNER, -} from '../../../common'; +import { ConnectorTypes } from '../../../common/api'; +import { CASE_CONFIGURE_SAVED_OBJECT, SECURITY_SOLUTION_OWNER } from '../../../common/constants'; import { getNoneCaseConnector, CONNECTOR_ID_REFERENCE_NAME } from '../../common'; import { ESCaseConnectorWithId } from '../../services/test_utils'; import { ESCasesConfigureAttributes } from '../../services/configure/types'; diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/configuration.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/configuration.ts index f9937253e0d2f..b269948427025 100644 --- a/x-pack/plugins/cases/server/saved_object_types/migrations/configuration.ts +++ b/x-pack/plugins/cases/server/saved_object_types/migrations/configuration.ts @@ -11,7 +11,7 @@ import { SavedObjectUnsanitizedDoc, SavedObjectSanitizedDoc, } from '../../../../../../src/core/server'; -import { ConnectorTypes } from '../../../common'; +import { ConnectorTypes } from '../../../common/api'; import { addOwnerToSO, SanitizedCaseOwner } from '.'; import { transformConnectorIdToReference } from '../../services/user_actions/transform'; import { CONNECTOR_ID_REFERENCE_NAME } from '../../common'; diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/index.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/index.ts index b0f9c7d2145de..105692b667fb5 100644 --- a/x-pack/plugins/cases/server/saved_object_types/migrations/index.ts +++ b/x-pack/plugins/cases/server/saved_object_types/migrations/index.ts @@ -9,7 +9,7 @@ import { SavedObjectUnsanitizedDoc, SavedObjectSanitizedDoc, } from '../../../../../../src/core/server'; -import { SECURITY_SOLUTION_OWNER } from '../../../common'; +import { SECURITY_SOLUTION_OWNER } from '../../../common/constants'; export { caseMigrations } from './cases'; export { configureMigrations } from './configuration'; diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions.test.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions.test.ts index e71c8db0db694..e9ba80322c222 100644 --- a/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions.test.ts +++ b/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions.test.ts @@ -9,7 +9,8 @@ import { SavedObjectMigrationContext, SavedObjectSanitizedDoc } from 'kibana/server'; import { migrationMocks } from 'src/core/server/mocks'; -import { CaseUserActionAttributes, CASE_USER_ACTION_SAVED_OBJECT } from '../../../common'; +import { CaseUserActionAttributes } from '../../../common/api'; +import { CASE_USER_ACTION_SAVED_OBJECT } from '../../../common/constants'; import { createConnectorObject, createExternalService, diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions.ts index ed6b57ef647f9..a47104dfed5f7 100644 --- a/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions.ts +++ b/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions.ts @@ -14,7 +14,8 @@ import { SavedObjectMigrationContext, LogMeta, } from '../../../../../../src/core/server'; -import { ConnectorTypes, isCreateConnector, isPush, isUpdateConnector } from '../../../common'; +import { isPush, isUpdateConnector, isCreateConnector } from '../../../common/utils/user_actions'; +import { ConnectorTypes } from '../../../common/api'; import { extractConnectorIdFromJson } from '../../services/user_actions/transform'; import { UserActionFieldType } from '../../services/user_actions/types'; diff --git a/x-pack/plugins/cases/server/saved_object_types/sub_case.ts b/x-pack/plugins/cases/server/saved_object_types/sub_case.ts index f7cf2aa65f821..469b27d4b40ba 100644 --- a/x-pack/plugins/cases/server/saved_object_types/sub_case.ts +++ b/x-pack/plugins/cases/server/saved_object_types/sub_case.ts @@ -6,7 +6,7 @@ */ import { SavedObjectsType } from 'src/core/server'; -import { SUB_CASE_SAVED_OBJECT } from '../../common'; +import { SUB_CASE_SAVED_OBJECT } from '../../common/constants'; import { subCasesMigrations } from './migrations'; export const subCaseSavedObjectType: SavedObjectsType = { diff --git a/x-pack/plugins/cases/server/saved_object_types/user_actions.ts b/x-pack/plugins/cases/server/saved_object_types/user_actions.ts index 8fea9460e77c5..2af2fd4c7e883 100644 --- a/x-pack/plugins/cases/server/saved_object_types/user_actions.ts +++ b/x-pack/plugins/cases/server/saved_object_types/user_actions.ts @@ -6,7 +6,7 @@ */ import { SavedObjectsType } from 'src/core/server'; -import { CASE_USER_ACTION_SAVED_OBJECT } from '../../common'; +import { CASE_USER_ACTION_SAVED_OBJECT } from '../../common/constants'; import { userActionsMigrations } from './migrations'; export const caseUserActionSavedObjectType: SavedObjectsType = { diff --git a/x-pack/plugins/cases/server/scripts/sub_cases/index.ts b/x-pack/plugins/cases/server/scripts/sub_cases/index.ts index 28672160a0737..75a896a4b81fd 100644 --- a/x-pack/plugins/cases/server/scripts/sub_cases/index.ts +++ b/x-pack/plugins/cases/server/scripts/sub_cases/index.ts @@ -12,14 +12,8 @@ import yargs from 'yargs'; import { ToolingLog } from '@kbn/dev-utils'; import { KbnClient } from '@kbn/test'; -import { - CaseResponse, - CaseType, - CommentType, - ConnectorTypes, - CASES_URL, - SECURITY_SOLUTION_OWNER, -} from '../../../common'; +import { CaseResponse, CaseType, CommentType, ConnectorTypes } from '../../../common/api'; +import { CASES_URL, SECURITY_SOLUTION_OWNER } from '../../../common/constants'; import { ActionResult, ActionTypeExecutorResult } from '../../../../actions/common'; import { ContextTypeGeneratedAlertType, createAlertsString } from '../../connectors'; import { diff --git a/x-pack/plugins/cases/server/services/alerts/index.test.ts b/x-pack/plugins/cases/server/services/alerts/index.test.ts index 2c98da198fa07..3104b85e0b0b9 100644 --- a/x-pack/plugins/cases/server/services/alerts/index.test.ts +++ b/x-pack/plugins/cases/server/services/alerts/index.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { CaseStatuses } from '../../../common'; +import { CaseStatuses } from '../../../common/api'; import { AlertService } from '.'; import { elasticsearchServiceMock, loggingSystemMock } from 'src/core/server/mocks'; diff --git a/x-pack/plugins/cases/server/services/alerts/index.ts b/x-pack/plugins/cases/server/services/alerts/index.ts index ca7bfe66804f3..90e1c4631de37 100644 --- a/x-pack/plugins/cases/server/services/alerts/index.ts +++ b/x-pack/plugins/cases/server/services/alerts/index.ts @@ -9,7 +9,8 @@ import pMap from 'p-map'; import { isEmpty } from 'lodash'; import { ElasticsearchClient, Logger } from 'kibana/server'; -import { CaseStatuses, MAX_ALERTS_PER_SUB_CASE, MAX_CONCURRENT_SEARCHES } from '../../../common'; +import { CaseStatuses } from '../../../common/api'; +import { MAX_ALERTS_PER_SUB_CASE, MAX_CONCURRENT_SEARCHES } from '../../../common/constants'; import { AlertInfo, createCaseError } from '../../common'; import { UpdateAlertRequest } from '../../client/alerts/types'; import { diff --git a/x-pack/plugins/cases/server/services/attachments/index.ts b/x-pack/plugins/cases/server/services/attachments/index.ts index 95a66fd9af192..f4e858eb0ed4f 100644 --- a/x-pack/plugins/cases/server/services/attachments/index.ts +++ b/x-pack/plugins/cases/server/services/attachments/index.ts @@ -15,13 +15,15 @@ import { import type { KueryNode } from '@kbn/es-query'; import { AttributesTypeAlerts, - CASE_COMMENT_SAVED_OBJECT, CommentAttributes as AttachmentAttributes, CommentPatchAttributes as AttachmentPatchAttributes, + CommentType, +} from '../../../common/api'; +import { + CASE_COMMENT_SAVED_OBJECT, CASE_SAVED_OBJECT, MAX_DOCS_PER_PAGE, - CommentType, -} from '../../../common'; +} from '../../../common/constants'; import { ClientArgs } from '..'; import { buildFilter, combineFilters } from '../../client/utils'; diff --git a/x-pack/plugins/cases/server/services/cases/index.test.ts b/x-pack/plugins/cases/server/services/cases/index.test.ts index 8c71abe5bff4f..ce632ec0cf8a8 100644 --- a/x-pack/plugins/cases/server/services/cases/index.test.ts +++ b/x-pack/plugins/cases/server/services/cases/index.test.ts @@ -13,12 +13,8 @@ * connector.id. */ -import { - CaseAttributes, - CaseConnector, - CaseFullExternalService, - CASE_SAVED_OBJECT, -} from '../../../common'; +import { CaseAttributes, CaseConnector, CaseFullExternalService } from '../../../common/api'; +import { CASE_SAVED_OBJECT } from '../../../common/constants'; import { savedObjectsClientMock } from '../../../../../../src/core/server/mocks'; import { SavedObject, diff --git a/x-pack/plugins/cases/server/services/cases/index.ts b/x-pack/plugins/cases/server/services/cases/index.ts index 15e60c49768a5..ae0f11a873777 100644 --- a/x-pack/plugins/cases/server/services/cases/index.ts +++ b/x-pack/plugins/cases/server/services/cases/index.ts @@ -24,9 +24,17 @@ import { nodeBuilder, KueryNode } from '@kbn/es-query'; import { SecurityPluginSetup } from '../../../../security/server'; import { - AssociationType, CASE_COMMENT_SAVED_OBJECT, CASE_SAVED_OBJECT, + ENABLE_CASE_CONNECTOR, + MAX_CONCURRENT_SEARCHES, + MAX_DOCS_PER_PAGE, + SUB_CASE_SAVED_OBJECT, +} from '../../../common/constants'; +import { + OWNER_FIELD, + GetCaseIdsByAlertIdAggs, + AssociationType, CaseResponse, CasesFindRequest, CaseStatuses, @@ -34,17 +42,11 @@ import { caseTypeField, CommentAttributes, CommentType, - ENABLE_CASE_CONNECTOR, - GetCaseIdsByAlertIdAggs, - MAX_CONCURRENT_SEARCHES, - MAX_DOCS_PER_PAGE, - OWNER_FIELD, - SUB_CASE_SAVED_OBJECT, SubCaseAttributes, SubCaseResponse, User, CaseAttributes, -} from '../../../common'; +} from '../../../common/api'; import { defaultSortField, flattenCaseSavedObject, diff --git a/x-pack/plugins/cases/server/services/cases/transform.test.ts b/x-pack/plugins/cases/server/services/cases/transform.test.ts index 96312d00b37dd..7987823af18a0 100644 --- a/x-pack/plugins/cases/server/services/cases/transform.test.ts +++ b/x-pack/plugins/cases/server/services/cases/transform.test.ts @@ -17,7 +17,7 @@ import { transformUpdateResponseToExternalModel, } from './transform'; import { ACTION_SAVED_OBJECT_TYPE } from '../../../../actions/server'; -import { ConnectorTypes } from '../../../common'; +import { ConnectorTypes } from '../../../common/api'; import { getNoneCaseConnector, CONNECTOR_ID_REFERENCE_NAME, diff --git a/x-pack/plugins/cases/server/services/cases/transform.ts b/x-pack/plugins/cases/server/services/cases/transform.ts index e3609689871d2..cdb25a046895e 100644 --- a/x-pack/plugins/cases/server/services/cases/transform.ts +++ b/x-pack/plugins/cases/server/services/cases/transform.ts @@ -18,7 +18,7 @@ import { import { ACTION_SAVED_OBJECT_TYPE } from '../../../../actions/server'; import { ESCaseAttributes, ExternalServicesWithoutConnectorId } from './types'; import { CONNECTOR_ID_REFERENCE_NAME, PUSH_CONNECTOR_ID_REFERENCE_NAME } from '../../common'; -import { CaseAttributes, CaseFullExternalService } from '../../../common'; +import { CaseAttributes, CaseFullExternalService } from '../../../common/api'; import { findConnectorIdReference, transformFieldsToESModel, diff --git a/x-pack/plugins/cases/server/services/cases/types.ts b/x-pack/plugins/cases/server/services/cases/types.ts index 55c736b032590..eb47850eeef31 100644 --- a/x-pack/plugins/cases/server/services/cases/types.ts +++ b/x-pack/plugins/cases/server/services/cases/types.ts @@ -6,7 +6,7 @@ */ import * as rt from 'io-ts'; -import { CaseAttributes, CaseExternalServiceBasicRt } from '../../../common'; +import { CaseAttributes, CaseExternalServiceBasicRt } from '../../../common/api'; import { ESCaseConnector } from '..'; /** diff --git a/x-pack/plugins/cases/server/services/configure/index.test.ts b/x-pack/plugins/cases/server/services/configure/index.test.ts index 876cb7b21f81a..e14cdec1353ed 100644 --- a/x-pack/plugins/cases/server/services/configure/index.test.ts +++ b/x-pack/plugins/cases/server/services/configure/index.test.ts @@ -9,10 +9,9 @@ import { CaseConnector, CasesConfigureAttributes, CasesConfigurePatch, - CASE_CONFIGURE_SAVED_OBJECT, ConnectorTypes, - SECURITY_SOLUTION_OWNER, -} from '../../../common'; +} from '../../../common/api'; +import { CASE_CONFIGURE_SAVED_OBJECT, SECURITY_SOLUTION_OWNER } from '../../../common/constants'; import { savedObjectsClientMock } from '../../../../../../src/core/server/mocks'; import { SavedObject, diff --git a/x-pack/plugins/cases/server/services/configure/index.ts b/x-pack/plugins/cases/server/services/configure/index.ts index db6d033f23ca8..c438c72d9a775 100644 --- a/x-pack/plugins/cases/server/services/configure/index.ts +++ b/x-pack/plugins/cases/server/services/configure/index.ts @@ -14,11 +14,8 @@ import { } from 'kibana/server'; import { SavedObjectFindOptionsKueryNode, CONNECTOR_ID_REFERENCE_NAME } from '../../common'; -import { - CASE_CONFIGURE_SAVED_OBJECT, - CasesConfigureAttributes, - CasesConfigurePatch, -} from '../../../common'; +import { CasesConfigureAttributes, CasesConfigurePatch } from '../../../common/api'; +import { CASE_CONFIGURE_SAVED_OBJECT } from '../../../common/constants'; import { ACTION_SAVED_OBJECT_TYPE } from '../../../../actions/server'; import { transformFieldsToESModel, diff --git a/x-pack/plugins/cases/server/services/configure/types.ts b/x-pack/plugins/cases/server/services/configure/types.ts index f52e05a2ff9b5..3c4405e532e69 100644 --- a/x-pack/plugins/cases/server/services/configure/types.ts +++ b/x-pack/plugins/cases/server/services/configure/types.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { CasesConfigureAttributes } from '../../../common'; +import { CasesConfigureAttributes } from '../../../common/api'; import { ESCaseConnector } from '..'; /** diff --git a/x-pack/plugins/cases/server/services/connector_mappings/index.ts b/x-pack/plugins/cases/server/services/connector_mappings/index.ts index 0798b35a78a4c..19760e73ad65a 100644 --- a/x-pack/plugins/cases/server/services/connector_mappings/index.ts +++ b/x-pack/plugins/cases/server/services/connector_mappings/index.ts @@ -7,7 +7,8 @@ import { Logger, SavedObjectReference, SavedObjectsClientContract } from 'kibana/server'; -import { ConnectorMappings, CASE_CONNECTOR_MAPPINGS_SAVED_OBJECT } from '../../../common'; +import { CASE_CONNECTOR_MAPPINGS_SAVED_OBJECT } from '../../../common/constants'; +import { ConnectorMappings } from '../../../common/api'; import { SavedObjectFindOptionsKueryNode } from '../../common'; interface ClientArgs { diff --git a/x-pack/plugins/cases/server/services/connector_reference_handler.test.ts b/x-pack/plugins/cases/server/services/connector_reference_handler.test.ts index 4c42332d10627..8b0bc527f9909 100644 --- a/x-pack/plugins/cases/server/services/connector_reference_handler.test.ts +++ b/x-pack/plugins/cases/server/services/connector_reference_handler.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { noneConnectorId } from '../../common'; +import { noneConnectorId } from '../../common/api'; import { ConnectorReferenceHandler } from './connector_reference_handler'; describe('ConnectorReferenceHandler', () => { diff --git a/x-pack/plugins/cases/server/services/connector_reference_handler.ts b/x-pack/plugins/cases/server/services/connector_reference_handler.ts index 81e1541366ab5..833cba26f0d1e 100644 --- a/x-pack/plugins/cases/server/services/connector_reference_handler.ts +++ b/x-pack/plugins/cases/server/services/connector_reference_handler.ts @@ -6,7 +6,7 @@ */ import { SavedObjectReference } from 'kibana/server'; -import { noneConnectorId } from '../../common'; +import { noneConnectorId } from '../../common/api'; interface Reference { soReference?: SavedObjectReference; diff --git a/x-pack/plugins/cases/server/services/index.ts b/x-pack/plugins/cases/server/services/index.ts index c14e1fab4a410..9a43aa43c2874 100644 --- a/x-pack/plugins/cases/server/services/index.ts +++ b/x-pack/plugins/cases/server/services/index.ts @@ -6,7 +6,7 @@ */ import { SavedObjectsClientContract } from 'kibana/server'; -import { ConnectorTypes } from '../../common'; +import { ConnectorTypes } from '../../common/api'; export { CasesService } from './cases'; export { CaseConfigureService } from './configure'; diff --git a/x-pack/plugins/cases/server/services/test_utils.ts b/x-pack/plugins/cases/server/services/test_utils.ts index 07743eda61212..8df217c793f34 100644 --- a/x-pack/plugins/cases/server/services/test_utils.ts +++ b/x-pack/plugins/cases/server/services/test_utils.ts @@ -13,11 +13,10 @@ import { CaseFullExternalService, CaseStatuses, CaseType, - CASE_SAVED_OBJECT, ConnectorTypes, noneConnectorId, - SECURITY_SOLUTION_OWNER, -} from '../../common'; +} from '../../common/api'; +import { CASE_SAVED_OBJECT, SECURITY_SOLUTION_OWNER } from '../../common/constants'; import { ESCaseAttributes, ExternalServicesWithoutConnectorId } from './cases/types'; import { ACTION_SAVED_OBJECT_TYPE } from '../../../actions/server'; diff --git a/x-pack/plugins/cases/server/services/transform.test.ts b/x-pack/plugins/cases/server/services/transform.test.ts index b4346595e4998..f7f49d285b80c 100644 --- a/x-pack/plugins/cases/server/services/transform.test.ts +++ b/x-pack/plugins/cases/server/services/transform.test.ts @@ -6,7 +6,7 @@ */ import { ACTION_SAVED_OBJECT_TYPE } from '../../../actions/server'; -import { ConnectorTypes } from '../../common'; +import { ConnectorTypes } from '../../common/api'; import { createESJiraConnector, createJiraConnector } from './test_utils'; import { findConnectorIdReference, diff --git a/x-pack/plugins/cases/server/services/transform.ts b/x-pack/plugins/cases/server/services/transform.ts index 39351d3a4b50a..88511b75483f4 100644 --- a/x-pack/plugins/cases/server/services/transform.ts +++ b/x-pack/plugins/cases/server/services/transform.ts @@ -6,7 +6,7 @@ */ import { SavedObjectReference } from 'kibana/server'; -import { CaseConnector, ConnectorTypeFields } from '../../common'; +import { CaseConnector, ConnectorTypeFields } from '../../common/api'; import { ACTION_SAVED_OBJECT_TYPE } from '../../../actions/server'; import { getNoneCaseConnector } from '../common'; import { ESCaseConnector, ESConnectorFields } from '.'; diff --git a/x-pack/plugins/cases/server/services/user_actions/helpers.test.ts b/x-pack/plugins/cases/server/services/user_actions/helpers.test.ts index 7bcbaf58d0f6e..e528ca67ce4c2 100644 --- a/x-pack/plugins/cases/server/services/user_actions/helpers.test.ts +++ b/x-pack/plugins/cases/server/services/user_actions/helpers.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { UserActionField } from '../../../common'; +import { UserActionField } from '../../../common/api'; import { createConnectorObject, createExternalService, createJiraConnector } from '../test_utils'; import { buildCaseUserActionItem } from './helpers'; diff --git a/x-pack/plugins/cases/server/services/user_actions/helpers.ts b/x-pack/plugins/cases/server/services/user_actions/helpers.ts index e91b69f0995bd..420df2d1016db 100644 --- a/x-pack/plugins/cases/server/services/user_actions/helpers.ts +++ b/x-pack/plugins/cases/server/services/user_actions/helpers.ts @@ -10,17 +10,19 @@ import { get, isPlainObject, isString } from 'lodash'; import deepEqual from 'fast-deep-equal'; import { - CASE_COMMENT_SAVED_OBJECT, - CASE_SAVED_OBJECT, CaseUserActionAttributes, - OWNER_FIELD, - SUB_CASE_SAVED_OBJECT, SubCaseAttributes, User, UserAction, UserActionField, CaseAttributes, -} from '../../../common'; + OWNER_FIELD, +} from '../../../common/api'; +import { + CASE_COMMENT_SAVED_OBJECT, + CASE_SAVED_OBJECT, + SUB_CASE_SAVED_OBJECT, +} from '../../../common/constants'; import { isTwoArraysDifference } from '../../client/utils'; import { UserActionItem } from '.'; import { extractConnectorId } from './transform'; diff --git a/x-pack/plugins/cases/server/services/user_actions/index.test.ts b/x-pack/plugins/cases/server/services/user_actions/index.test.ts index c4a350f4ac015..a35fb8f1baba7 100644 --- a/x-pack/plugins/cases/server/services/user_actions/index.test.ts +++ b/x-pack/plugins/cases/server/services/user_actions/index.test.ts @@ -7,12 +7,8 @@ import { SavedObject, SavedObjectsFindResult } from 'kibana/server'; import { transformFindResponseToExternalModel, UserActionItem } from '.'; -import { - CaseUserActionAttributes, - CASE_USER_ACTION_SAVED_OBJECT, - UserAction, - UserActionField, -} from '../../../common'; +import { CaseUserActionAttributes, UserAction, UserActionField } from '../../../common/api'; +import { CASE_USER_ACTION_SAVED_OBJECT } from '../../../common/constants'; import { createConnectorObject, diff --git a/x-pack/plugins/cases/server/services/user_actions/index.ts b/x-pack/plugins/cases/server/services/user_actions/index.ts index 4f158862e3d63..72a4486c87fea 100644 --- a/x-pack/plugins/cases/server/services/user_actions/index.ts +++ b/x-pack/plugins/cases/server/services/user_actions/index.ts @@ -12,18 +12,15 @@ import { SavedObjectsFindResult, } from 'kibana/server'; +import { isCreateConnector, isPush, isUpdateConnector } from '../../../common/utils/user_actions'; +import { CaseUserActionAttributes, CaseUserActionResponse } from '../../../common/api'; import { CASE_SAVED_OBJECT, CASE_USER_ACTION_SAVED_OBJECT, - CaseUserActionAttributes, MAX_DOCS_PER_PAGE, SUB_CASE_SAVED_OBJECT, - CaseUserActionResponse, CASE_COMMENT_SAVED_OBJECT, - isCreateConnector, - isPush, - isUpdateConnector, -} from '../../../common'; +} from '../../../common/constants'; import { ClientArgs } from '..'; import { UserActionFieldType } from './types'; import { CASE_REF_NAME, COMMENT_REF_NAME, SUB_CASE_REF_NAME } from '../../common'; diff --git a/x-pack/plugins/cases/server/services/user_actions/transform.test.ts b/x-pack/plugins/cases/server/services/user_actions/transform.test.ts index 2d28770617094..82d491ad96c6e 100644 --- a/x-pack/plugins/cases/server/services/user_actions/transform.test.ts +++ b/x-pack/plugins/cases/server/services/user_actions/transform.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { noneConnectorId } from '../../../common'; +import { noneConnectorId } from '../../../common/api'; import { CONNECTOR_ID_REFERENCE_NAME, getNoneCaseConnector, diff --git a/x-pack/plugins/cases/server/services/user_actions/transform.ts b/x-pack/plugins/cases/server/services/user_actions/transform.ts index 93595374208a3..a453fec2c9e19 100644 --- a/x-pack/plugins/cases/server/services/user_actions/transform.ts +++ b/x-pack/plugins/cases/server/services/user_actions/transform.ts @@ -11,16 +11,14 @@ import * as rt from 'io-ts'; import { isString } from 'lodash'; import { SavedObjectReference } from '../../../../../../src/core/server'; +import { isCreateConnector, isPush, isUpdateConnector } from '../../../common/utils/user_actions'; import { CaseAttributes, CaseConnector, CaseConnectorRt, CaseExternalServiceBasicRt, - isCreateConnector, - isPush, - isUpdateConnector, noneConnectorId, -} from '../../../common'; +} from '../../../common/api'; import { CONNECTOR_ID_REFERENCE_NAME, getNoneCaseConnector, diff --git a/x-pack/plugins/fleet/server/plugin.ts b/x-pack/plugins/fleet/server/plugin.ts index 1e421fefce835..d800b6ece943f 100644 --- a/x-pack/plugins/fleet/server/plugin.ts +++ b/x-pack/plugins/fleet/server/plugin.ts @@ -371,8 +371,10 @@ export class FleetPlugin const fleetSetupPromise = (async () => { try { + // Fleet remains `available` during setup as to excessively delay Kibana's boot process. + // This should be reevaluated as Fleet's setup process is optimized and stabilized. this.fleetStatus$.next({ - level: ServiceStatusLevels.degraded, + level: ServiceStatusLevels.available, summary: 'Fleet is setting up', }); diff --git a/x-pack/plugins/infra/public/apps/common_providers.tsx b/x-pack/plugins/infra/public/apps/common_providers.tsx index 64867c5743d0d..39808ab7d3ac4 100644 --- a/x-pack/plugins/infra/public/apps/common_providers.tsx +++ b/x-pack/plugins/infra/public/apps/common_providers.tsx @@ -10,6 +10,7 @@ import React, { useMemo } from 'react'; import { EuiThemeProvider } from '../../../../../src/plugins/kibana_react/common'; import { KibanaContextProvider, + KibanaThemeProvider, useUiSetting$, } from '../../../../../src/plugins/kibana_react/public'; import { Storage } from '../../../../../src/plugins/kibana_utils/public'; @@ -44,7 +45,8 @@ export const CommonInfraProviders: React.FC<{ export const CoreProviders: React.FC<{ core: CoreStart; plugins: InfraClientStartDeps; -}> = ({ children, core, plugins }) => { + theme$: AppMountParameters['theme$']; +}> = ({ children, core, plugins, theme$ }) => { const { Provider: KibanaContextProviderForPlugin } = useMemo( () => createKibanaContextForPlugin(core, plugins), [core, plugins] @@ -52,7 +54,9 @@ export const CoreProviders: React.FC<{ return ( - {children} + + {children} + ); }; diff --git a/x-pack/plugins/infra/public/apps/logs_app.tsx b/x-pack/plugins/infra/public/apps/logs_app.tsx index b512b5ce4a176..6ebaf3e805d91 100644 --- a/x-pack/plugins/infra/public/apps/logs_app.tsx +++ b/x-pack/plugins/infra/public/apps/logs_app.tsx @@ -23,7 +23,7 @@ import { prepareMountElement } from './common_styles'; export const renderApp = ( core: CoreStart, plugins: InfraClientStartDeps, - { element, history, setHeaderActionMenu }: AppMountParameters + { element, history, setHeaderActionMenu, theme$ }: AppMountParameters ) => { const storage = new Storage(window.localStorage); @@ -36,6 +36,7 @@ export const renderApp = ( history={history} plugins={plugins} setHeaderActionMenu={setHeaderActionMenu} + theme$={theme$} />, element ); @@ -51,11 +52,12 @@ const LogsApp: React.FC<{ plugins: InfraClientStartDeps; setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; storage: Storage; -}> = ({ core, history, plugins, setHeaderActionMenu, storage }) => { + theme$: AppMountParameters['theme$']; +}> = ({ core, history, plugins, setHeaderActionMenu, storage, theme$ }) => { const uiCapabilities = core.application.capabilities; return ( - + { const storage = new Storage(window.localStorage); @@ -37,6 +37,7 @@ export const renderApp = ( plugins={plugins} setHeaderActionMenu={setHeaderActionMenu} storage={storage} + theme$={theme$} />, element ); @@ -52,11 +53,12 @@ const MetricsApp: React.FC<{ plugins: InfraClientStartDeps; setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; storage: Storage; -}> = ({ core, history, plugins, setHeaderActionMenu, storage }) => { + theme$: AppMountParameters['theme$']; +}> = ({ core, history, plugins, setHeaderActionMenu, storage, theme$ }) => { const uiCapabilities = core.application.capabilities; return ( - + ( - -
{wrappedStory()}
-
- ), + (wrappedStory) =>
{wrappedStory()}
, + decorateWithGlobalStorybookThemeProviders, ], parameters: { layout: 'padded', diff --git a/x-pack/plugins/infra/public/components/data_search_progress.stories.tsx b/x-pack/plugins/infra/public/components/data_search_progress.stories.tsx index 7a4966ebeb6f2..0efbfb25c9749 100644 --- a/x-pack/plugins/infra/public/components/data_search_progress.stories.tsx +++ b/x-pack/plugins/infra/public/components/data_search_progress.stories.tsx @@ -8,17 +8,14 @@ import { PropsOf } from '@elastic/eui'; import { Meta, Story } from '@storybook/react/types-6-0'; import React from 'react'; -import { EuiThemeProvider } from '../../../../../src/plugins/kibana_react/common'; +import { decorateWithGlobalStorybookThemeProviders } from '../test_utils/use_global_storybook_theme'; import { DataSearchProgress } from './data_search_progress'; export default { title: 'infra/dataSearch/DataSearchProgress', decorators: [ - (wrappedStory) => ( - -
{wrappedStory()}
-
- ), + (wrappedStory) =>
{wrappedStory()}
, + decorateWithGlobalStorybookThemeProviders, ], parameters: { layout: 'padded', diff --git a/x-pack/plugins/infra/public/components/loading/__examples__/index.stories.tsx b/x-pack/plugins/infra/public/components/loading/__examples__/index.stories.tsx index b43991f4b9df4..161708b2bd358 100644 --- a/x-pack/plugins/infra/public/components/loading/__examples__/index.stories.tsx +++ b/x-pack/plugins/infra/public/components/loading/__examples__/index.stories.tsx @@ -5,10 +5,17 @@ * 2.0. */ -import { storiesOf } from '@storybook/react'; +import { Meta, Story } from '@storybook/react/types-6-0'; import React from 'react'; import { InfraLoadingPanel } from '..'; +import { decorateWithGlobalStorybookThemeProviders } from '../../../test_utils/use_global_storybook_theme'; -storiesOf('infra/InfraLoadingPanel', module).add('example', () => ( - -)); +export default { + title: 'infra/InfraLoadingPanel', + decorators: [ + (wrappedStory) =>
{wrappedStory()}
, + decorateWithGlobalStorybookThemeProviders, + ], +} as Meta; + +export const LoadingPanel: Story = () => ; diff --git a/x-pack/plugins/infra/public/components/log_stream/log_stream.stories.mdx b/x-pack/plugins/infra/public/components/log_stream/log_stream.stories.mdx index 82b02059ecc1f..f11430586764d 100644 --- a/x-pack/plugins/infra/public/components/log_stream/log_stream.stories.mdx +++ b/x-pack/plugins/infra/public/components/log_stream/log_stream.stories.mdx @@ -4,12 +4,12 @@ import { delay } from 'rxjs/operators'; import { I18nProvider } from '@kbn/i18n-react'; import { KBN_FIELD_TYPES } from '../../../../../../src/plugins/data/public'; -import { EuiThemeProvider } from '../../../../../../src/plugins/kibana_react/common'; import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; import { LOG_ENTRIES_SEARCH_STRATEGY } from '../../../common/search_strategies/log_entries/log_entries'; import { createIndexPatternMock, createIndexPatternsMock } from '../../hooks/use_kibana_index_patterns.mock'; import { DEFAULT_SOURCE_CONFIGURATION } from '../../test_utils/source_configuration'; import { generateFakeEntries, ENTRIES_EMPTY } from '../../test_utils/entries'; +import { decorateWithGlobalStorybookThemeProviders } from '../../test_utils/use_global_storybook_theme'; import { LogStream } from './'; @@ -145,13 +145,12 @@ export const Template = (args) => ; decorators={[ (story) => ( - - - {story()} - - + + {story()} + ), + decorateWithGlobalStorybookThemeProviders, ]} /> @@ -172,7 +171,8 @@ To use the component your plugin needs to follow certain criteria: - Ensure `"infra"` and `"data"` are specified as a `requiredPlugins` in your plugin's `kibana.json`. - Ensure the `` component is mounted inside the hierachy of a [`kibana-react` provider](https://github.com/elastic/kibana/blob/b2d0aa7b7fae1c89c8f9e8854ae73e71be64e765/src/plugins/kibana_react/README.md#L45). At a minimum, the kibana-react provider must pass `http` (from core start services) and `data` (from core plugin start dependencies). -- Ensure the `` component is mounted inside the hierachy of a [`EuiThemeProvider`](https://github.com/elastic/kibana/blob/main/src/plugins/kibana_react/common/eui_styled_components.tsx). +- Ensure the `` component is mounted inside the hierachy of a [`KibanaThemeProvider`](https://github.com/elastic/kibana/blob/31d2db035c905fb5819fa6dc2354f3be795a34cf/src/plugins/kibana_react/public/theme/kibana_theme_provider.tsx#L27). +- Ensure the `` component is mounted inside the hierachy of a [`EuiThemeProvider`](https://github.com/elastic/kibana/blob/main/src/plugins/kibana_react/common/eui_styled_components.tsx). This is not the same as the provider exported by EUI. It bridges the gap between EUI and styled components and predates the css-in-js support in EUI. ## Usage diff --git a/x-pack/plugins/infra/public/components/log_stream/log_stream_embeddable.tsx b/x-pack/plugins/infra/public/components/log_stream/log_stream_embeddable.tsx index e3fc4ca1de565..19f7d7f1b4a7a 100644 --- a/x-pack/plugins/infra/public/components/log_stream/log_stream_embeddable.tsx +++ b/x-pack/plugins/infra/public/components/log_stream/log_stream_embeddable.tsx @@ -77,7 +77,7 @@ export class LogStreamEmbeddable extends Embeddable { } ReactDOM.render( - +
{renderStory()}) + .addDecorator(decorateWithGlobalStorybookThemeProviders) .add('Partitioned warnings', () => { return ( ( - -
{renderStory()}
-
- )) + .addDecorator((renderStory) =>
{renderStory()}
) + .addDecorator(decorateWithGlobalStorybookThemeProviders) .add('Reconfiguration with partitioned warnings', () => { return ( { return ( - - - - - - - - - - - + + + + + + + + + ); }, + decorateWithGlobalStorybookThemeProviders, ], argTypes: { logIndices: { diff --git a/x-pack/plugins/infra/public/test_utils/use_global_storybook_theme.tsx b/x-pack/plugins/infra/public/test_utils/use_global_storybook_theme.tsx new file mode 100644 index 0000000000000..9330f83cd968a --- /dev/null +++ b/x-pack/plugins/infra/public/test_utils/use_global_storybook_theme.tsx @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { StoryContext } from '@storybook/addons'; +import React, { useEffect, useMemo, useState } from 'react'; +import { BehaviorSubject } from 'rxjs'; +import type { CoreTheme } from '../../../../../src/core/public'; +import { EuiThemeProvider } from '../../../../../src/plugins/kibana_react/common'; +import { KibanaThemeProvider } from '../../../../../src/plugins/kibana_react/public'; + +export const useGlobalStorybookTheme = ({ globals: { euiTheme } }: StoryContext) => { + const theme = useMemo(() => euiThemeFromId(euiTheme), [euiTheme]); + const [theme$] = useState(() => new BehaviorSubject(theme)); + + useEffect(() => { + theme$.next(theme); + }, [theme$, theme]); + + return { + theme, + theme$, + }; +}; + +export const GlobalStorybookThemeProviders: React.FC<{ storyContext: StoryContext }> = ({ + children, + storyContext, +}) => { + const { theme, theme$ } = useGlobalStorybookTheme(storyContext); + return ( + + {children} + + ); +}; + +export const decorateWithGlobalStorybookThemeProviders = < + StoryFnReactReturnType extends React.ReactNode +>( + wrappedStory: () => StoryFnReactReturnType, + storyContext: StoryContext +) => ( + + {wrappedStory()} + +); + +const euiThemeFromId = (themeId: string): CoreTheme => { + switch (themeId) { + case 'v8.dark': + return { darkMode: true }; + default: + return { darkMode: false }; + } +}; diff --git a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss index 756804a0e6aa0..551734bc2fcdc 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss +++ b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss @@ -25,8 +25,3 @@ min-width: $euiButtonMinWidth; td { text-align: center } } - -/* Override to align column header to bottom of cell when no chart is available */ -.mlDataGrid .euiDataGridHeaderCell__content { - margin-top: auto; -} diff --git a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.scss b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.scss index b445c82b35ff9..f9cc09ef8c425 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.scss +++ b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.scss @@ -1,11 +1,18 @@ .mlDataGrid { + .euiDataGridRowCell--boolean { text-transform: none; } - // Override to align the sorting arrow at the bottom when histogram charts are enabled - .euiDataGridHeaderCell .euiDataGridHeaderCell__sortingArrow { - margin-top: auto; - margin-bottom: 0; + // Overrides to align the sorting arrow, actions icon and the column header when no chart is available, + // to the bottom of the cell when histogram charts are enabled. + // Note that overrides have to be used as currently it is not possible to add a custom class name + // for the EuiDataGridHeaderCell - see https://github.com/elastic/eui/issues/5106 + .euiDataGridHeaderCell { + .euiDataGridHeaderCell__sortingArrow, + .euiDataGridHeaderCell__icon, + .euiPopover { + margin-top: auto; + } } } diff --git a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx index 5240dbb1ec474..a77f43e68daef 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx @@ -114,14 +114,6 @@ export const DataGrid: FC = memo( // }; // }; - // If the charts are visible, hide the column actions icon. - const columnsWithChartsActionized = columnsWithCharts.map((d) => { - if (chartsVisible === true) { - d.actions = false; - } - return d; - }); - const popOverContent = useMemo(() => { return analysisType === ANALYSIS_CONFIG_TYPE.REGRESSION || analysisType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION || @@ -341,7 +333,7 @@ export const DataGrid: FC = memo(
{ + columns={columnsWithCharts.map((c) => { c.initialWidth = 165; return c; })} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/report_metric_options.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/report_metric_options.tsx index 0c1f41437ede7..e3e63af94118e 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/report_metric_options.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/report_metric_options.tsx @@ -169,7 +169,7 @@ const NO_DATA_AVAILABLE = i18n.translate('xpack.observability.expView.seriesEdit const NO_PERMISSIONS = i18n.translate('xpack.observability.expView.seriesEditor.noPermissions', { defaultMessage: - "Unable to create Index Pattern. You don't have the required permission, please contact your admin.", + "Unable to create Data View. You don't have the required permission, please contact your admin.", }); const REPORT_METRIC_TOOLTIP = i18n.translate( diff --git a/x-pack/plugins/reporting/common/constants.ts b/x-pack/plugins/reporting/common/constants.ts index 65d196b6e068a..1fe37f86b037f 100644 --- a/x-pack/plugins/reporting/common/constants.ts +++ b/x-pack/plugins/reporting/common/constants.ts @@ -55,17 +55,6 @@ export const UI_SETTINGS_CSV_SEPARATOR = 'csv:separator'; export const UI_SETTINGS_CSV_QUOTE_VALUES = 'csv:quoteValues'; export const UI_SETTINGS_DATEFORMAT_TZ = 'dateFormat:tz'; -export const LAYOUT_TYPES = { - CANVAS: 'canvas', - PRESERVE_LAYOUT: 'preserve_layout', - PRINT: 'print', -}; - -export const DEFAULT_VIEWPORT = { - width: 1950, - height: 1200, -}; - // Export Type Definitions export const CSV_REPORT_TYPE = 'CSV'; export const CSV_JOB_TYPE = 'csv_searchsource'; diff --git a/x-pack/plugins/reporting/common/test/fixtures.ts b/x-pack/plugins/reporting/common/test/fixtures.ts index c7489d54e9504..5cc6cf274c340 100644 --- a/x-pack/plugins/reporting/common/test/fixtures.ts +++ b/x-pack/plugins/reporting/common/test/fixtures.ts @@ -11,7 +11,6 @@ import type { ReportMock } from './types'; const buildMockReport = (baseObj: ReportMock) => ({ index: '.reporting-2020.04.12', migration_version: '7.15.0', - browser_type: 'chromium', max_attempts: 1, timeout: 300000, created_by: 'elastic', diff --git a/x-pack/plugins/reporting/common/types/base.ts b/x-pack/plugins/reporting/common/types/base.ts index a44378979ac3c..234467a16921e 100644 --- a/x-pack/plugins/reporting/common/types/base.ts +++ b/x-pack/plugins/reporting/common/types/base.ts @@ -6,7 +6,7 @@ */ import type { Ensure, SerializableRecord } from '@kbn/utility-types'; -import type { LayoutParams } from './layout'; +import type { LayoutParams } from '../../../screenshotting/common'; import { LocatorParams } from './url'; export type JobId = string; diff --git a/x-pack/plugins/reporting/common/types/export_types/png.ts b/x-pack/plugins/reporting/common/types/export_types/png.ts index 3b850b5bd8b33..5afde424127a1 100644 --- a/x-pack/plugins/reporting/common/types/export_types/png.ts +++ b/x-pack/plugins/reporting/common/types/export_types/png.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { LayoutParams } from '../layout'; +import type { LayoutParams } from '../../../../screenshotting/common'; import type { BaseParams, BasePayload } from '../base'; interface BaseParamsPNG { diff --git a/x-pack/plugins/reporting/common/types/export_types/png_v2.ts b/x-pack/plugins/reporting/common/types/export_types/png_v2.ts index c937d01ce0be1..1469437fe6199 100644 --- a/x-pack/plugins/reporting/common/types/export_types/png_v2.ts +++ b/x-pack/plugins/reporting/common/types/export_types/png_v2.ts @@ -6,7 +6,7 @@ */ import type { LocatorParams } from '../url'; -import type { LayoutParams } from '../layout'; +import type { LayoutParams } from '../../../../screenshotting/common'; import type { BaseParams, BasePayload } from '../base'; // Job params: structure of incoming user request data diff --git a/x-pack/plugins/reporting/common/types/export_types/printable_pdf.ts b/x-pack/plugins/reporting/common/types/export_types/printable_pdf.ts index a424706430f2c..57e5a90595d5c 100644 --- a/x-pack/plugins/reporting/common/types/export_types/printable_pdf.ts +++ b/x-pack/plugins/reporting/common/types/export_types/printable_pdf.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { LayoutParams } from '../layout'; +import type { LayoutParams } from '../../../../screenshotting/common'; import type { BaseParams, BasePayload } from '../base'; interface BaseParamsPDF { diff --git a/x-pack/plugins/reporting/common/types/export_types/printable_pdf_v2.ts b/x-pack/plugins/reporting/common/types/export_types/printable_pdf_v2.ts index c9a7a2ce2331a..b3fbbc1653dfb 100644 --- a/x-pack/plugins/reporting/common/types/export_types/printable_pdf_v2.ts +++ b/x-pack/plugins/reporting/common/types/export_types/printable_pdf_v2.ts @@ -6,7 +6,7 @@ */ import type { LocatorParams } from '../url'; -import type { LayoutParams } from '../layout'; +import type { LayoutParams } from '../../../../screenshotting/common'; import type { BaseParams, BasePayload } from '../base'; interface BaseParamsPDFV2 { diff --git a/x-pack/plugins/reporting/common/types/index.ts b/x-pack/plugins/reporting/common/types/index.ts index 8612400e8b390..056ef81e70a0a 100644 --- a/x-pack/plugins/reporting/common/types/index.ts +++ b/x-pack/plugins/reporting/common/types/index.ts @@ -5,11 +5,9 @@ * 2.0. */ -import type { Size, LayoutParams } from './layout'; import type { JobId, BaseParams, BaseParamsV2, BasePayload, BasePayloadV2 } from './base'; export type { JobId, BaseParams, BaseParamsV2, BasePayload, BasePayloadV2 }; -export type { Size, LayoutParams }; export type { DownloadReportFn, IlmPolicyMigrationStatus, @@ -20,20 +18,6 @@ export type { } from './url'; export * from './export_types'; -export interface PageSizeParams { - pageMarginTop: number; - pageMarginBottom: number; - pageMarginWidth: number; - tableBorderWidth: number; - headingHeight: number; - subheadingHeight: number; -} - -export interface PdfImageSize { - width: number; - height?: number; -} - export interface ReportDocumentHead { _id: string; _index: string; @@ -83,7 +67,6 @@ export interface ReportSource { */ kibana_name?: string; // for troubleshooting kibana_id?: string; // for troubleshooting - browser_type?: string; // no longer used since chromium is the only option (used to allow phantomjs) timeout?: number; // for troubleshooting: the actual comparison uses the config setting xpack.reporting.queue.timeout max_attempts?: number; // for troubleshooting: the actual comparison uses the config setting xpack.reporting.capture.maxAttempts started_at?: string; // timestamp in UTC diff --git a/x-pack/plugins/reporting/common/types/layout.ts b/x-pack/plugins/reporting/common/types/layout.ts deleted file mode 100644 index b22d6b59d0873..0000000000000 --- a/x-pack/plugins/reporting/common/types/layout.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { Ensure, SerializableRecord } from '@kbn/utility-types'; - -export type Size = Ensure< - { - width: number; - height: number; - }, - SerializableRecord ->; - -export type LayoutParams = Ensure< - { - id: string; - dimensions?: Size; - }, - SerializableRecord ->; diff --git a/x-pack/plugins/reporting/kibana.json b/x-pack/plugins/reporting/kibana.json index 4bddfae96756d..123c23e5e1c29 100644 --- a/x-pack/plugins/reporting/kibana.json +++ b/x-pack/plugins/reporting/kibana.json @@ -18,6 +18,7 @@ "uiActions", "taskManager", "embeddable", + "screenshotting", "screenshotMode", "share", "features" diff --git a/x-pack/plugins/reporting/public/lib/job.tsx b/x-pack/plugins/reporting/public/lib/job.tsx index 8e2db6a9d998e..d24695b1041c7 100644 --- a/x-pack/plugins/reporting/public/lib/job.tsx +++ b/x-pack/plugins/reporting/public/lib/job.tsx @@ -51,7 +51,6 @@ export class Job { public timeout: ReportSource['timeout']; public kibana_name: ReportSource['kibana_name']; public kibana_id: ReportSource['kibana_id']; - public browser_type: ReportSource['browser_type']; public size?: ReportOutput['size']; public content_type?: TaskRunResult['content_type']; @@ -80,7 +79,6 @@ export class Job { this.timeout = report.timeout; this.kibana_name = report.kibana_name; this.kibana_id = report.kibana_id; - this.browser_type = report.browser_type; this.browserTimezone = report.payload.browserTimezone; this.size = report.output?.size; this.content_type = report.output?.content_type; diff --git a/x-pack/plugins/reporting/public/management/components/report_info_flyout_content.tsx b/x-pack/plugins/reporting/public/management/components/report_info_flyout_content.tsx index 25199c4abaa68..00ce9069d81ce 100644 --- a/x-pack/plugins/reporting/public/management/components/report_info_flyout_content.tsx +++ b/x-pack/plugins/reporting/public/management/components/report_info_flyout_content.tsx @@ -141,12 +141,6 @@ export const ReportInfoFlyoutContent: FunctionComponent = ({ info }) => { }), description: info.layout?.id || UNKNOWN, }, - { - title: i18n.translate('xpack.reporting.listing.infoPanel.browserTypeInfo', { - defaultMessage: 'Browser type', - }), - description: info.browser_type || NA, - }, ]; const warnings = info.getWarnings(); diff --git a/x-pack/plugins/reporting/public/plugin.ts b/x-pack/plugins/reporting/public/plugin.ts index fe80ed679c8ed..ea48bb253ad9f 100644 --- a/x-pack/plugins/reporting/public/plugin.ts +++ b/x-pack/plugins/reporting/public/plugin.ts @@ -18,6 +18,7 @@ import { Plugin, PluginInitializerContext, } from 'src/core/public'; +import type { ScreenshottingSetup } from '../../screenshotting/public'; import { CONTEXT_MENU_TRIGGER } from '../../../../src/plugins/embeddable/public'; import { FeatureCatalogueCategory, @@ -73,6 +74,7 @@ export interface ReportingPublicPluginSetupDendencies { management: ManagementSetup; licensing: LicensingPluginSetup; uiActions: UiActionsSetup; + screenshotting: ScreenshottingSetup; share: SharePluginSetup; } @@ -145,6 +147,7 @@ export class ReportingPublicPlugin home, management, licensing: { license$ }, // FIXME: 'license$' is deprecated + screenshotting, share, uiActions, } = setupDeps; @@ -203,7 +206,7 @@ export class ReportingPublicPlugin id: 'reportingRedirect', mount: async (params) => { const { mountRedirectApp } = await import('./redirect'); - return mountRedirectApp({ ...params, share, apiClient }); + return mountRedirectApp({ ...params, apiClient, screenshotting, share }); }, title: 'Reporting redirect app', searchable: false, diff --git a/x-pack/plugins/reporting/public/redirect/mount_redirect_app.tsx b/x-pack/plugins/reporting/public/redirect/mount_redirect_app.tsx index eb34fc71cbf4e..fa658126efebc 100644 --- a/x-pack/plugins/reporting/public/redirect/mount_redirect_app.tsx +++ b/x-pack/plugins/reporting/public/redirect/mount_redirect_app.tsx @@ -10,6 +10,7 @@ import React from 'react'; import { EuiErrorBoundary } from '@elastic/eui'; import type { AppMountParameters } from 'kibana/public'; +import type { ScreenshottingSetup } from '../../../screenshotting/public'; import type { SharePluginSetup } from '../shared_imports'; import type { ReportingAPIClient } from '../lib/reporting_api_client'; @@ -17,13 +18,25 @@ import { RedirectApp } from './redirect_app'; interface MountParams extends AppMountParameters { apiClient: ReportingAPIClient; + screenshotting: ScreenshottingSetup; share: SharePluginSetup; } -export const mountRedirectApp = ({ element, apiClient, history, share }: MountParams) => { +export const mountRedirectApp = ({ + element, + apiClient, + history, + screenshotting, + share, +}: MountParams) => { render( - + , element ); diff --git a/x-pack/plugins/reporting/public/redirect/redirect_app.tsx b/x-pack/plugins/reporting/public/redirect/redirect_app.tsx index 4b271b17c5e85..9f0b3f51f2731 100644 --- a/x-pack/plugins/reporting/public/redirect/redirect_app.tsx +++ b/x-pack/plugins/reporting/public/redirect/redirect_app.tsx @@ -12,6 +12,7 @@ import { i18n } from '@kbn/i18n'; import { EuiCallOut, EuiCodeBlock } from '@elastic/eui'; import type { ScopedHistory } from 'src/core/public'; +import type { ScreenshottingSetup } from '../../../screenshotting/public'; import { REPORTING_REDIRECT_LOCATOR_STORE_KEY } from '../../common/constants'; import { LocatorParams } from '../../common/types'; @@ -24,6 +25,7 @@ import './redirect_app.scss'; interface Props { apiClient: ReportingAPIClient; history: ScopedHistory; + screenshotting: ScreenshottingSetup; share: SharePluginSetup; } @@ -39,7 +41,9 @@ const i18nTexts = { ), }; -export const RedirectApp: FunctionComponent = ({ share, apiClient }) => { +type ReportingContext = Record; + +export const RedirectApp: FunctionComponent = ({ apiClient, screenshotting, share }) => { const [error, setError] = useState(); useEffect(() => { @@ -53,9 +57,8 @@ export const RedirectApp: FunctionComponent = ({ share, apiClient }) => { const result = await apiClient.getInfo(jobId as string); locatorParams = result?.locatorParams?.[0]; } else { - locatorParams = (window as unknown as Record)[ - REPORTING_REDIRECT_LOCATOR_STORE_KEY - ]; + locatorParams = + screenshotting.getContext()?.[REPORTING_REDIRECT_LOCATOR_STORE_KEY]; } if (!locatorParams) { @@ -70,7 +73,7 @@ export const RedirectApp: FunctionComponent = ({ share, apiClient }) => { throw e; } })(); - }, [share, apiClient]); + }, [apiClient, screenshotting, share]); return (
diff --git a/x-pack/plugins/reporting/public/share_context_menu/index.ts b/x-pack/plugins/reporting/public/share_context_menu/index.ts index b0d6f2e6a2b52..321a5a29281af 100644 --- a/x-pack/plugins/reporting/public/share_context_menu/index.ts +++ b/x-pack/plugins/reporting/public/share_context_menu/index.ts @@ -8,8 +8,8 @@ import * as Rx from 'rxjs'; import type { IUiSettingsClient, ToastsSetup } from 'src/core/public'; import { CoreStart } from 'src/core/public'; +import type { LayoutParams } from '../../../screenshotting/common'; import type { LicensingPluginSetup } from '../../../licensing/public'; -import type { LayoutParams } from '../../common/types'; import type { ReportingAPIClient } from '../lib/reporting_api_client'; export interface ExportPanelShareOpts { diff --git a/x-pack/plugins/reporting/public/share_context_menu/screen_capture_panel_content.tsx b/x-pack/plugins/reporting/public/share_context_menu/screen_capture_panel_content.tsx index de3cc89b31fd0..f9e2908c0f733 100644 --- a/x-pack/plugins/reporting/public/share_context_menu/screen_capture_panel_content.tsx +++ b/x-pack/plugins/reporting/public/share_context_menu/screen_capture_panel_content.tsx @@ -8,7 +8,7 @@ import { EuiFormRow, EuiSwitch, EuiSwitchEvent } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import React, { Component } from 'react'; -import { LayoutParams } from '../../common/types'; +import type { LayoutParams } from '../../../screenshotting/common'; import { ReportingPanelContent, ReportingPanelProps } from './reporting_panel_content'; export interface Props extends ReportingPanelProps { @@ -103,7 +103,7 @@ export class ScreenCapturePanelContent extends Component { this.setState({ useCanvasLayout: evt.target.checked, usePrintLayout: false }); }; - private getLayout = (): Required => { + private getLayout = (): LayoutParams => { const { layout: outerLayout } = this.props.getJobParams(); let dimensions = outerLayout?.dimensions; diff --git a/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.test.ts b/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.test.ts deleted file mode 100644 index dae692fae8825..0000000000000 --- a/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.test.ts +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import puppeteer from 'puppeteer'; -import * as Rx from 'rxjs'; -import { take } from 'rxjs/operators'; -import { HeadlessChromiumDriverFactory } from '.'; -import type { ReportingCore } from '../../..'; -import { - createMockConfigSchema, - createMockLevelLogger, - createMockReportingCore, -} from '../../../test_helpers'; - -jest.mock('puppeteer'); - -const mock = (browserDriverFactory: HeadlessChromiumDriverFactory) => { - browserDriverFactory.getBrowserLogger = jest.fn(() => new Rx.Observable()); - browserDriverFactory.getProcessLogger = jest.fn(() => new Rx.Observable()); - browserDriverFactory.getPageExit = jest.fn(() => new Rx.Observable()); - return browserDriverFactory; -}; - -describe('class HeadlessChromiumDriverFactory', () => { - let reporting: ReportingCore; - const logger = createMockLevelLogger(); - const path = 'path/to/headless_shell'; - - beforeEach(async () => { - (puppeteer as jest.Mocked).launch.mockResolvedValue({ - newPage: jest.fn().mockResolvedValue({ - target: jest.fn(() => ({ - createCDPSession: jest.fn().mockResolvedValue({ - send: jest.fn(), - }), - })), - emulateTimezone: jest.fn(), - setDefaultTimeout: jest.fn(), - }), - close: jest.fn(), - process: jest.fn(), - } as unknown as puppeteer.Browser); - - reporting = await createMockReportingCore( - createMockConfigSchema({ - capture: { - browser: { chromium: { proxy: {} } }, - timeouts: { openUrl: 50000 }, - }, - }) - ); - }); - - it('createPage returns browser driver and process exit observable', async () => { - const factory = mock(new HeadlessChromiumDriverFactory(reporting, path, logger)); - const utils = await factory.createPage({}).pipe(take(1)).toPromise(); - expect(utils).toHaveProperty('driver'); - expect(utils).toHaveProperty('exit$'); - }); - - it('createPage rejects if Puppeteer launch fails', async () => { - (puppeteer as jest.Mocked).launch.mockRejectedValue( - `Puppeteer Launch mock fail.` - ); - const factory = mock(new HeadlessChromiumDriverFactory(reporting, path, logger)); - expect(() => - factory.createPage({}).pipe(take(1)).toPromise() - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Error spawning Chromium browser! Puppeteer Launch mock fail."` - ); - }); -}); diff --git a/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.ts b/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.ts deleted file mode 100644 index 2aef62f59985b..0000000000000 --- a/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.ts +++ /dev/null @@ -1,268 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; -import { getDataPath } from '@kbn/utils'; -import del from 'del'; -import apm from 'elastic-apm-node'; -import fs from 'fs'; -import path from 'path'; -import puppeteer from 'puppeteer'; -import * as Rx from 'rxjs'; -import { InnerSubscriber } from 'rxjs/internal/InnerSubscriber'; -import { ignoreElements, map, mergeMap, tap } from 'rxjs/operators'; -import { getChromiumDisconnectedError } from '../'; -import { ReportingCore } from '../../..'; -import { durationToNumber } from '../../../../common/schema_utils'; -import { CaptureConfig } from '../../../../server/types'; -import { LevelLogger } from '../../../lib'; -import { safeChildProcess } from '../../safe_child_process'; -import { HeadlessChromiumDriver } from '../driver'; -import { args } from './args'; -import { getMetrics } from './metrics'; - -type BrowserConfig = CaptureConfig['browser']['chromium']; - -export class HeadlessChromiumDriverFactory { - private binaryPath: string; - private captureConfig: CaptureConfig; - private browserConfig: BrowserConfig; - private userDataDir: string; - private getChromiumArgs: () => string[]; - private core: ReportingCore; - - constructor(core: ReportingCore, binaryPath: string, private logger: LevelLogger) { - this.core = core; - this.binaryPath = binaryPath; - const config = core.getConfig(); - this.captureConfig = config.get('capture'); - this.browserConfig = this.captureConfig.browser.chromium; - - if (this.browserConfig.disableSandbox) { - logger.warning(`Enabling the Chromium sandbox provides an additional layer of protection.`); - } - - this.userDataDir = fs.mkdtempSync(path.join(getDataPath(), 'chromium-')); - this.getChromiumArgs = () => - args({ - userDataDir: this.userDataDir, - disableSandbox: this.browserConfig.disableSandbox, - proxy: this.browserConfig.proxy, - }); - } - - type = 'chromium'; - - /* - * Return an observable to objects which will drive screenshot capture for a page - */ - createPage( - { browserTimezone }: { browserTimezone?: string }, - pLogger = this.logger - ): Rx.Observable<{ driver: HeadlessChromiumDriver; exit$: Rx.Observable }> { - // FIXME: 'create' is deprecated - return Rx.Observable.create(async (observer: InnerSubscriber) => { - const logger = pLogger.clone(['browser-driver']); - logger.info(`Creating browser page driver`); - - const chromiumArgs = this.getChromiumArgs(); - logger.debug(`Chromium launch args set to: ${chromiumArgs}`); - - let browser: puppeteer.Browser | null = null; - - try { - browser = await puppeteer.launch({ - pipe: !this.browserConfig.inspect, - userDataDir: this.userDataDir, - executablePath: this.binaryPath, - ignoreHTTPSErrors: true, - handleSIGHUP: false, - args: chromiumArgs, - env: { - TZ: browserTimezone, - }, - }); - } catch (err) { - observer.error(new Error(`Error spawning Chromium browser! ${err}`)); - return; - } - - const page = await browser.newPage(); - const devTools = await page.target().createCDPSession(); - - await devTools.send('Performance.enable', { timeDomain: 'timeTicks' }); - const startMetrics = await devTools.send('Performance.getMetrics'); - - // Log version info for debugging / maintenance - const versionInfo = await devTools.send('Browser.getVersion'); - logger.debug(`Browser version: ${JSON.stringify(versionInfo)}`); - - await page.emulateTimezone(browserTimezone); - - // Set the default timeout for all navigation methods to the openUrl timeout - // All waitFor methods have their own timeout config passed in to them - page.setDefaultTimeout(durationToNumber(this.captureConfig.timeouts.openUrl)); - - logger.debug(`Browser page driver created`); - - const childProcess = { - async kill() { - try { - if (devTools && startMetrics) { - const endMetrics = await devTools.send('Performance.getMetrics'); - const { cpu, cpuInPercentage, memory, memoryInMegabytes } = getMetrics( - startMetrics, - endMetrics - ); - - apm.currentTransaction?.setLabel('cpu', cpu, false); - apm.currentTransaction?.setLabel('memory', memory, false); - logger.debug( - `Chromium consumed CPU ${cpuInPercentage}% Memory ${memoryInMegabytes}MB` - ); - } - } catch (error) { - logger.error(error); - } - - try { - await browser?.close(); - } catch (err) { - // do not throw - logger.error(err); - } - }, - }; - const { terminate$ } = safeChildProcess(logger, childProcess); - - // this is adding unsubscribe logic to our observer - // so that if our observer unsubscribes, we terminate our child-process - observer.add(() => { - logger.debug(`The browser process observer has unsubscribed. Closing the browser...`); - childProcess.kill(); // ignore async - }); - - // make the observer subscribe to terminate$ - observer.add( - terminate$ - .pipe( - tap((signal) => { - logger.debug(`Termination signal received: ${signal}`); - }), - ignoreElements() - ) - .subscribe(observer) - ); - - // taps the browser log streams and combine them to Kibana logs - this.getBrowserLogger(page, logger).subscribe(); - this.getProcessLogger(browser, logger).subscribe(); - - // HeadlessChromiumDriver: object to "drive" a browser page - const driver = new HeadlessChromiumDriver(this.core, page, { - inspect: !!this.browserConfig.inspect, - networkPolicy: this.captureConfig.networkPolicy, - }); - - // Rx.Observable: stream to interrupt page capture - const exit$ = this.getPageExit(browser, page); - - observer.next({ driver, exit$ }); - - // unsubscribe logic makes a best-effort attempt to delete the user data directory used by chromium - observer.add(() => { - const userDataDir = this.userDataDir; - logger.debug(`deleting chromium user data directory at [${userDataDir}]`); - // the unsubscribe function isn't `async` so we're going to make our best effort at - // deleting the userDataDir and if it fails log an error. - del(userDataDir, { force: true }).catch((error) => { - logger.error(`error deleting user data directory at [${userDataDir}]!`); - logger.error(error); - }); - }); - }); - } - - getBrowserLogger(page: puppeteer.Page, logger: LevelLogger): Rx.Observable { - const consoleMessages$ = Rx.fromEvent(page, 'console').pipe( - map((line) => { - const formatLine = () => `{ text: "${line.text()?.trim()}", url: ${line.location()?.url} }`; - - if (line.type() === 'error') { - logger.error(`Error in browser console: ${formatLine()}`, ['headless-browser-console']); - } else { - logger.debug(`Message in browser console: ${formatLine()}`, [ - `headless-browser-console:${line.type()}`, - ]); - } - }) - ); - - const uncaughtExceptionPageError$ = Rx.fromEvent(page, 'pageerror').pipe( - map((err) => { - logger.warning( - i18n.translate('xpack.reporting.browsers.chromium.pageErrorDetected', { - defaultMessage: `Reporting encountered an uncaught error on the page that will be ignored: {err}`, - values: { err: err.toString() }, - }) - ); - }) - ); - - const pageRequestFailed$ = Rx.fromEvent(page, 'requestfailed').pipe( - map((req) => { - const failure = req.failure && req.failure(); - if (failure) { - logger.warning( - `Request to [${req.url()}] failed! [${failure.errorText}]. This error will be ignored.` - ); - } - }) - ); - - return Rx.merge(consoleMessages$, uncaughtExceptionPageError$, pageRequestFailed$); - } - - getProcessLogger(browser: puppeteer.Browser, logger: LevelLogger): Rx.Observable { - const childProcess = browser.process(); - // NOTE: The browser driver can not observe stdout and stderr of the child process - // Puppeteer doesn't give a handle to the original ChildProcess object - // See https://github.com/GoogleChrome/puppeteer/issues/1292#issuecomment-521470627 - - if (childProcess == null) { - throw new TypeError('childProcess is null or undefined!'); - } - - // just log closing of the process - const processClose$ = Rx.fromEvent(childProcess, 'close').pipe( - tap(() => { - logger.debug('child process closed', ['headless-browser-process']); - }) - ); - - return processClose$; // ideally, this would also merge with observers for stdout and stderr - } - - getPageExit(browser: puppeteer.Browser, page: puppeteer.Page) { - const pageError$ = Rx.fromEvent(page, 'error').pipe( - mergeMap((err) => { - return Rx.throwError( - i18n.translate('xpack.reporting.browsers.chromium.errorDetected', { - defaultMessage: 'Reporting encountered an error: {err}', - values: { err: err.toString() }, - }) - ); - }) - ); - - const browserDisconnect$ = Rx.fromEvent(browser, 'disconnected').pipe( - mergeMap(() => Rx.throwError(getChromiumDisconnectedError())) - ); - - return Rx.merge(pageError$, browserDisconnect$); - } -} diff --git a/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/start_logs.ts b/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/start_logs.ts deleted file mode 100644 index 1a739488bf6ed..0000000000000 --- a/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/start_logs.ts +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; -import { spawn } from 'child_process'; -import del from 'del'; -import { mkdtempSync } from 'fs'; -import { uniq } from 'lodash'; -import os from 'os'; -import { join } from 'path'; -import { createInterface } from 'readline'; -import { getDataPath } from '@kbn/utils'; -import { fromEvent, merge, of, timer } from 'rxjs'; -import { catchError, map, reduce, takeUntil, tap } from 'rxjs/operators'; -import { ReportingCore } from '../../../'; -import { LevelLogger } from '../../../lib'; -import { ChromiumArchivePaths } from '../paths'; -import { args } from './args'; - -const paths = new ChromiumArchivePaths(); -const browserLaunchTimeToWait = 5 * 1000; - -// Default args used by pptr -// https://github.com/puppeteer/puppeteer/blob/13ea347/src/node/Launcher.ts#L168 -const defaultArgs = [ - '--disable-background-networking', - '--enable-features=NetworkService,NetworkServiceInProcess', - '--disable-background-timer-throttling', - '--disable-backgrounding-occluded-windows', - '--disable-breakpad', - '--disable-client-side-phishing-detection', - '--disable-component-extensions-with-background-pages', - '--disable-default-apps', - '--disable-dev-shm-usage', - '--disable-extensions', - '--disable-features=TranslateUI', - '--disable-hang-monitor', - '--disable-ipc-flooding-protection', - '--disable-popup-blocking', - '--disable-prompt-on-repost', - '--disable-renderer-backgrounding', - '--disable-sync', - '--force-color-profile=srgb', - '--metrics-recording-only', - '--no-first-run', - '--enable-automation', - '--password-store=basic', - '--use-mock-keychain', - '--remote-debugging-port=0', - '--headless', -]; - -export const browserStartLogs = ( - core: ReportingCore, - logger: LevelLogger, - overrideFlags: string[] = [] -) => { - const config = core.getConfig(); - const proxy = config.get('capture', 'browser', 'chromium', 'proxy'); - const disableSandbox = config.get('capture', 'browser', 'chromium', 'disableSandbox'); - const userDataDir = mkdtempSync(join(getDataPath(), 'chromium-')); - - const platform = process.platform; - const architecture = os.arch(); - const pkg = paths.find(platform, architecture); - if (!pkg) { - throw new Error(`Unsupported platform: ${platform}-${architecture}`); - } - const binaryPath = paths.getBinaryPath(pkg); - - const kbnArgs = args({ - userDataDir, - disableSandbox, - proxy, - }); - const finalArgs = uniq([...defaultArgs, ...kbnArgs, ...overrideFlags]); - - // On non-windows platforms, `detached: true` makes child process a - // leader of a new process group, making it possible to kill child - // process tree with `.kill(-pid)` command. @see - // https://nodejs.org/api/child_process.html#child_process_options_detached - const browserProcess = spawn(binaryPath, finalArgs, { - detached: process.platform !== 'win32', - }); - - const rl = createInterface({ input: browserProcess.stderr }); - - const exit$ = fromEvent(browserProcess, 'exit').pipe( - map((code) => { - logger.error(`Browser exited abnormally, received code: ${code}`); - return i18n.translate('xpack.reporting.diagnostic.browserCrashed', { - defaultMessage: `Browser exited abnormally during startup`, - }); - }) - ); - - const error$ = fromEvent(browserProcess, 'error').pipe( - map((err) => { - logger.error(`Browser process threw an error on startup`); - logger.error(err as string | Error); - return i18n.translate('xpack.reporting.diagnostic.browserErrored', { - defaultMessage: `Browser process threw an error on startup`, - }); - }) - ); - - const browserProcessLogger = logger.clone(['chromium-stderr']); - const log$ = fromEvent(rl, 'line').pipe( - tap((message: unknown) => { - if (typeof message === 'string') { - browserProcessLogger.info(message); - } - }) - ); - - // Collect all events (exit, error and on log-lines), but let chromium keep spitting out - // logs as sometimes it's "bind" successfully for remote connections, but later emit - // a log indicative of an issue (for example, no default font found). - return merge(exit$, error$, log$).pipe( - takeUntil(timer(browserLaunchTimeToWait)), - reduce((acc, curr) => `${acc}${curr}\n`, ''), - tap(() => { - if (browserProcess && browserProcess.pid && !browserProcess.killed) { - browserProcess.kill('SIGKILL'); - logger.info(`Successfully sent 'SIGKILL' to browser process (PID: ${browserProcess.pid})`); - } - browserProcess.removeAllListeners(); - rl.removeAllListeners(); - rl.close(); - del(userDataDir, { force: true }).catch((error) => { - logger.error(`Error deleting user data directory at [${userDataDir}]!`); - logger.error(error); - }); - }), - catchError((error) => { - logger.error(error); - return of(error); - }) - ); -}; diff --git a/x-pack/plugins/reporting/server/browsers/chromium/index.ts b/x-pack/plugins/reporting/server/browsers/chromium/index.ts deleted file mode 100644 index e0d043f821ab4..0000000000000 --- a/x-pack/plugins/reporting/server/browsers/chromium/index.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; -import { BrowserDownload } from '../'; -import { ReportingCore } from '../../../server'; -import { LevelLogger } from '../../lib'; -import { HeadlessChromiumDriverFactory } from './driver_factory'; -import { ChromiumArchivePaths } from './paths'; - -export const chromium: BrowserDownload = { - paths: new ChromiumArchivePaths(), - createDriverFactory: (core: ReportingCore, binaryPath: string, logger: LevelLogger) => - new HeadlessChromiumDriverFactory(core, binaryPath, logger), -}; - -export const getChromiumDisconnectedError = () => - new Error( - i18n.translate('xpack.reporting.screencapture.browserWasClosed', { - defaultMessage: 'Browser was closed unexpectedly! Check the server logs for more info.', - }) - ); - -export const getDisallowedOutgoingUrlError = (interceptedUrl: string) => - new Error( - i18n.translate('xpack.reporting.chromiumDriver.disallowedOutgoingUrl', { - defaultMessage: `Received disallowed outgoing URL: "{interceptedUrl}". Failing the request and closing the browser.`, - values: { interceptedUrl }, - }) - ); - -export { ChromiumArchivePaths }; diff --git a/x-pack/plugins/reporting/server/browsers/download/download.test.ts b/x-pack/plugins/reporting/server/browsers/download/download.test.ts deleted file mode 100644 index 688a746826e54..0000000000000 --- a/x-pack/plugins/reporting/server/browsers/download/download.test.ts +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { createHash } from 'crypto'; -import del from 'del'; -import { readFileSync } from 'fs'; -import { resolve as resolvePath } from 'path'; -import { Readable } from 'stream'; -import { LevelLogger } from '../../lib'; -import { download } from './download'; - -const TEMP_DIR = resolvePath(__dirname, '__tmp__'); -const TEMP_FILE = resolvePath(TEMP_DIR, 'foo/bar/download'); - -class ReadableOf extends Readable { - constructor(private readonly responseBody: string) { - super(); - } - - _read() { - this.push(this.responseBody); - this.push(null); - } -} - -jest.mock('axios'); -const request: jest.Mock = jest.requireMock('axios').request; - -const mockLogger = { - error: jest.fn(), - warn: jest.fn(), - info: jest.fn(), -} as unknown as LevelLogger; - -test('downloads the url to the path', async () => { - const BODY = 'abdcefg'; - request.mockImplementationOnce(async () => { - return { - data: new ReadableOf(BODY), - }; - }); - - await download('url', TEMP_FILE, mockLogger); - expect(readFileSync(TEMP_FILE, 'utf8')).toEqual(BODY); -}); - -test('returns the md5 hex hash of the http body', async () => { - const BODY = 'foobar'; - const HASH = createHash('md5').update(BODY).digest('hex'); - request.mockImplementationOnce(async () => { - return { - data: new ReadableOf(BODY), - }; - }); - - const returned = await download('url', TEMP_FILE, mockLogger); - expect(returned).toEqual(HASH); -}); - -test('throws if request emits an error', async () => { - request.mockImplementationOnce(async () => { - throw new Error('foo'); - }); - - return expect(download('url', TEMP_FILE, mockLogger)).rejects.toThrow('foo'); -}); - -afterEach(async () => await del(TEMP_DIR)); diff --git a/x-pack/plugins/reporting/server/browsers/download/ensure_downloaded.test.ts b/x-pack/plugins/reporting/server/browsers/download/ensure_downloaded.test.ts deleted file mode 100644 index 9db128c019ac0..0000000000000 --- a/x-pack/plugins/reporting/server/browsers/download/ensure_downloaded.test.ts +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import path from 'path'; -import mockFs from 'mock-fs'; -import { existsSync, readdirSync } from 'fs'; -import { chromium } from '../chromium'; -import { download } from './download'; -import { md5 } from './checksum'; -import { ensureBrowserDownloaded } from './ensure_downloaded'; -import { LevelLogger } from '../../lib'; - -jest.mock('./checksum'); -jest.mock('./download'); - -// https://github.com/elastic/kibana/issues/115881 -describe.skip('ensureBrowserDownloaded', () => { - let logger: jest.Mocked; - - beforeEach(() => { - logger = { - debug: jest.fn(), - error: jest.fn(), - warning: jest.fn(), - } as unknown as typeof logger; - - (md5 as jest.MockedFunction).mockImplementation( - async (packagePath) => - chromium.paths.packages.find( - (packageInfo) => chromium.paths.resolvePath(packageInfo) === packagePath - )?.archiveChecksum ?? 'some-md5' - ); - - (download as jest.MockedFunction).mockImplementation( - async (_url, packagePath) => - chromium.paths.packages.find( - (packageInfo) => chromium.paths.resolvePath(packageInfo) === packagePath - )?.archiveChecksum ?? 'some-md5' - ); - - mockFs(); - }); - - afterEach(() => { - mockFs.restore(); - jest.resetAllMocks(); - }); - - it('should remove unexpected files', async () => { - const unexpectedPath1 = `${chromium.paths.archivesPath}/unexpected1`; - const unexpectedPath2 = `${chromium.paths.archivesPath}/unexpected2`; - - mockFs({ - [unexpectedPath1]: 'test', - [unexpectedPath2]: 'test', - }); - - await ensureBrowserDownloaded(logger); - - expect(existsSync(unexpectedPath1)).toBe(false); - expect(existsSync(unexpectedPath2)).toBe(false); - }); - - it('should reject when download fails', async () => { - (download as jest.MockedFunction).mockRejectedValueOnce( - new Error('some error') - ); - - await expect(ensureBrowserDownloaded(logger)).rejects.toBeInstanceOf(Error); - }); - - it('should reject when downloaded md5 hash is different', async () => { - (download as jest.MockedFunction).mockResolvedValue('random-md5'); - - await expect(ensureBrowserDownloaded(logger)).rejects.toBeInstanceOf(Error); - }); - - describe('when archives are already present', () => { - beforeEach(() => { - mockFs( - Object.fromEntries( - chromium.paths.packages.map((packageInfo) => [ - chromium.paths.resolvePath(packageInfo), - '', - ]) - ) - ); - }); - - it('should not download again', async () => { - await ensureBrowserDownloaded(logger); - - expect(download).not.toHaveBeenCalled(); - const paths = [ - readdirSync(path.resolve(chromium.paths.archivesPath + '/x64')), - readdirSync(path.resolve(chromium.paths.archivesPath + '/arm64')), - ]; - - expect(paths).toEqual([ - expect.arrayContaining([ - 'chrome-win.zip', - 'chromium-70f5d88-linux_x64.zip', - 'chromium-d163fd7-darwin_x64.zip', - ]), - expect.arrayContaining(['chromium-70f5d88-linux_arm64.zip']), - ]); - }); - - it('should download again if md5 hash different', async () => { - (md5 as jest.MockedFunction).mockResolvedValueOnce('random-md5'); - await ensureBrowserDownloaded(logger); - - expect(download).toHaveBeenCalledTimes(1); - }); - }); -}); diff --git a/x-pack/plugins/reporting/server/browsers/download/ensure_downloaded.ts b/x-pack/plugins/reporting/server/browsers/download/ensure_downloaded.ts deleted file mode 100644 index 2766b404f1dd1..0000000000000 --- a/x-pack/plugins/reporting/server/browsers/download/ensure_downloaded.ts +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { existsSync } from 'fs'; -import del from 'del'; -import { BrowserDownload, chromium } from '../'; -import { GenericLevelLogger } from '../../lib/level_logger'; -import { md5 } from './checksum'; -import { download } from './download'; - -/** - * Check for the downloaded archive of each requested browser type and - * download them if they are missing or their checksum is invalid - */ -export async function ensureBrowserDownloaded(logger: GenericLevelLogger) { - await ensureDownloaded([chromium], logger); -} - -/** - * Clears the unexpected files in the browsers archivesPath - * and ensures that all packages/archives are downloaded and - * that their checksums match the declared value - */ -async function ensureDownloaded(browsers: BrowserDownload[], logger: GenericLevelLogger) { - await Promise.all( - browsers.map(async ({ paths: pSet }) => { - const removedFiles = await del(`${pSet.archivesPath}/**/*`, { - force: true, - onlyFiles: true, - ignore: pSet.getAllArchiveFilenames(), - }); - - removedFiles.forEach((path) => { - logger.warning(`Deleting unexpected file ${path}`); - }); - - const invalidChecksums: string[] = []; - await Promise.all( - pSet.packages.map(async (p) => { - const { archiveFilename, archiveChecksum } = p; - if (archiveFilename && archiveChecksum) { - const path = pSet.resolvePath(p); - const pathExists = existsSync(path); - - let foundChecksum: string; - try { - foundChecksum = await md5(path).catch(); - } catch { - foundChecksum = 'MISSING'; - } - - if (pathExists && foundChecksum === archiveChecksum) { - logger.debug(`Browser archive for ${p.platform}/${p.architecture} found in ${path} `); - return; - } - - if (!pathExists) { - logger.warning( - `Browser archive for ${p.platform}/${p.architecture} not found in ${path}.` - ); - } - if (foundChecksum !== archiveChecksum) { - logger.warning( - `Browser archive checksum for ${p.platform}/${p.architecture} ` + - `is ${foundChecksum} but ${archiveChecksum} was expected.` - ); - } - - const url = pSet.getDownloadUrl(p); - try { - const downloadedChecksum = await download(url, path, logger); - if (downloadedChecksum !== archiveChecksum) { - logger.warning( - `Invalid checksum for ${p.platform}/${p.architecture}: ` + - `expected ${archiveChecksum} got ${downloadedChecksum}` - ); - invalidChecksums.push(`${url} => ${path}`); - } - } catch (err) { - throw new Error(`Failed to download ${url}: ${err}`); - } - } - }) - ); - - if (invalidChecksums.length) { - const err = new Error( - `Error downloading browsers, checksums incorrect for:\n - ${invalidChecksums.join( - '\n - ' - )}` - ); - logger.error(err); - throw err; - } - }) - ); -} diff --git a/x-pack/plugins/reporting/server/browsers/download/index.ts b/x-pack/plugins/reporting/server/browsers/download/index.ts deleted file mode 100644 index d54a7a1f30cc7..0000000000000 --- a/x-pack/plugins/reporting/server/browsers/download/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export { ensureBrowserDownloaded } from './ensure_downloaded'; diff --git a/x-pack/plugins/reporting/server/browsers/index.ts b/x-pack/plugins/reporting/server/browsers/index.ts deleted file mode 100644 index be5c85a6e9581..0000000000000 --- a/x-pack/plugins/reporting/server/browsers/index.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { first } from 'rxjs/operators'; -import { ReportingCore } from '../'; -import { LevelLogger } from '../lib'; -import { chromium, ChromiumArchivePaths } from './chromium'; -import { HeadlessChromiumDriverFactory } from './chromium/driver_factory'; -import { installBrowser } from './install'; - -export { chromium } from './chromium'; -export { HeadlessChromiumDriver } from './chromium/driver'; -export { HeadlessChromiumDriverFactory } from './chromium/driver_factory'; - -type CreateDriverFactory = ( - core: ReportingCore, - binaryPath: string, - logger: LevelLogger -) => HeadlessChromiumDriverFactory; - -export interface BrowserDownload { - createDriverFactory: CreateDriverFactory; - paths: ChromiumArchivePaths; -} - -export const initializeBrowserDriverFactory = async (core: ReportingCore, logger: LevelLogger) => { - const chromiumLogger = logger.clone(['chromium']); - const { binaryPath$ } = installBrowser(chromiumLogger); - const binaryPath = await binaryPath$.pipe(first()).toPromise(); - return chromium.createDriverFactory(core, binaryPath, chromiumLogger); -}; diff --git a/x-pack/plugins/reporting/server/browsers/install.ts b/x-pack/plugins/reporting/server/browsers/install.ts deleted file mode 100644 index 0441bbcfb5306..0000000000000 --- a/x-pack/plugins/reporting/server/browsers/install.ts +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import del from 'del'; -import os from 'os'; -import path from 'path'; -import * as Rx from 'rxjs'; -import { GenericLevelLogger } from '../lib/level_logger'; -import { ChromiumArchivePaths } from './chromium'; -import { ensureBrowserDownloaded } from './download'; -import { md5 } from './download/checksum'; -import { extract } from './extract'; - -/** - * "install" a browser by type into installs path by extracting the downloaded - * archive. If there is an error extracting the archive an `ExtractError` is thrown - */ -export function installBrowser( - logger: GenericLevelLogger, - chromiumPath: string = path.resolve(__dirname, '../../chromium'), - platform: string = process.platform, - architecture: string = os.arch() -): { binaryPath$: Rx.Subject } { - const binaryPath$ = new Rx.Subject(); - - const paths = new ChromiumArchivePaths(); - const pkg = paths.find(platform, architecture); - - if (!pkg) { - throw new Error(`Unsupported platform: ${platform}-${architecture}`); - } - - const backgroundInstall = async () => { - const binaryPath = paths.getBinaryPath(pkg); - const binaryChecksum = await md5(binaryPath).catch(() => ''); - - if (binaryChecksum !== pkg.binaryChecksum) { - logger.warning( - `Found browser binary checksum for ${pkg.platform}/${pkg.architecture} ` + - `is ${binaryChecksum} but ${pkg.binaryChecksum} was expected. Re-installing...` - ); - try { - await del(chromiumPath); - } catch (err) { - logger.error(err); - } - - try { - await ensureBrowserDownloaded(logger); - const archive = path.join(paths.archivesPath, pkg.architecture, pkg.archiveFilename); - logger.info(`Extracting [${archive}] to [${chromiumPath}]`); - await extract(archive, chromiumPath); - } catch (err) { - logger.error(err); - } - } - - logger.info(`Browser executable: ${binaryPath}`); - - binaryPath$.next(binaryPath); // subscribers wait for download and extract to complete - }; - - backgroundInstall(); - - return { - binaryPath$, - }; -} diff --git a/x-pack/plugins/reporting/server/config/__snapshots__/schema.test.ts.snap b/x-pack/plugins/reporting/server/config/__snapshots__/schema.test.ts.snap index a384550f18462..65f3c45fb2255 100644 --- a/x-pack/plugins/reporting/server/config/__snapshots__/schema.test.ts.snap +++ b/x-pack/plugins/reporting/server/config/__snapshots__/schema.test.ts.snap @@ -3,52 +3,8 @@ exports[`Reporting Config Schema context {"dev":false,"dist":false} produces correct config 1`] = ` Object { "capture": Object { - "browser": Object { - "autoDownload": true, - "chromium": Object { - "proxy": Object { - "enabled": false, - }, - }, - "type": "chromium", - }, "loadDelay": "PT3S", "maxAttempts": 1, - "networkPolicy": Object { - "enabled": true, - "rules": Array [ - Object { - "allow": true, - "host": undefined, - "protocol": "http:", - }, - Object { - "allow": true, - "host": undefined, - "protocol": "https:", - }, - Object { - "allow": true, - "host": undefined, - "protocol": "ws:", - }, - Object { - "allow": true, - "host": undefined, - "protocol": "wss:", - }, - Object { - "allow": true, - "host": undefined, - "protocol": "data:", - }, - Object { - "allow": false, - "host": undefined, - "protocol": undefined, - }, - ], - }, "timeouts": Object { "openUrl": "PT1M", "renderComplete": "PT30S", @@ -101,53 +57,8 @@ Object { exports[`Reporting Config Schema context {"dev":false,"dist":true} produces correct config 1`] = ` Object { "capture": Object { - "browser": Object { - "autoDownload": false, - "chromium": Object { - "inspect": false, - "proxy": Object { - "enabled": false, - }, - }, - "type": "chromium", - }, "loadDelay": "PT3S", "maxAttempts": 3, - "networkPolicy": Object { - "enabled": true, - "rules": Array [ - Object { - "allow": true, - "host": undefined, - "protocol": "http:", - }, - Object { - "allow": true, - "host": undefined, - "protocol": "https:", - }, - Object { - "allow": true, - "host": undefined, - "protocol": "ws:", - }, - Object { - "allow": true, - "host": undefined, - "protocol": "wss:", - }, - Object { - "allow": true, - "host": undefined, - "protocol": "data:", - }, - Object { - "allow": false, - "host": undefined, - "protocol": undefined, - }, - ], - }, "timeouts": Object { "openUrl": "PT1M", "renderComplete": "PT30S", diff --git a/x-pack/plugins/reporting/server/config/create_config.test.ts b/x-pack/plugins/reporting/server/config/create_config.test.ts index 3c5ecdc1dab0b..fd8180bd46a05 100644 --- a/x-pack/plugins/reporting/server/config/create_config.test.ts +++ b/x-pack/plugins/reporting/server/config/create_config.test.ts @@ -77,13 +77,6 @@ describe('Reporting server createConfig$', () => { expect(result).toMatchInlineSnapshot(` Object { - "capture": Object { - "browser": Object { - "chromium": Object { - "disableSandbox": true, - }, - }, - }, "csv": Object {}, "encryptionKey": "iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii", "index": ".reporting", @@ -106,47 +99,6 @@ describe('Reporting server createConfig$', () => { expect(mockLogger.warn).not.toHaveBeenCalled(); }); - it('uses user-provided disableSandbox: false', async () => { - mockInitContext = coreMock.createPluginInitializerContext( - createMockConfigSchema({ - encryptionKey: '888888888888888888888888888888888', - capture: { browser: { chromium: { disableSandbox: false } } }, - }) - ); - const mockConfig$ = createMockConfig(mockInitContext); - const result = await createConfig$(mockCoreSetup, mockConfig$, mockLogger).toPromise(); - - expect(result.capture.browser.chromium).toMatchObject({ disableSandbox: false }); - expect(mockLogger.warn).not.toHaveBeenCalled(); - }); - - it('uses user-provided disableSandbox: true', async () => { - mockInitContext = coreMock.createPluginInitializerContext( - createMockConfigSchema({ - encryptionKey: '888888888888888888888888888888888', - capture: { browser: { chromium: { disableSandbox: true } } }, - }) - ); - const mockConfig$ = createMockConfig(mockInitContext); - const result = await createConfig$(mockCoreSetup, mockConfig$, mockLogger).toPromise(); - - expect(result.capture.browser.chromium).toMatchObject({ disableSandbox: true }); - expect(mockLogger.warn).not.toHaveBeenCalled(); - }); - - it('provides a default for disableSandbox', async () => { - mockInitContext = coreMock.createPluginInitializerContext( - createMockConfigSchema({ - encryptionKey: '888888888888888888888888888888888', - }) - ); - const mockConfig$ = createMockConfig(mockInitContext); - const result = await createConfig$(mockCoreSetup, mockConfig$, mockLogger).toPromise(); - - expect(result.capture.browser.chromium).toMatchObject({ disableSandbox: expect.any(Boolean) }); - expect(mockLogger.warn).not.toHaveBeenCalled(); - }); - it.each(['0', '0.0', '0.0.0', '0.0.0.0', '0000:0000:0000:0000:0000:0000:0000:0000', '::'])( `apply failover logic when hostname is given as "%s"`, async (hostname) => { diff --git a/x-pack/plugins/reporting/server/config/create_config.ts b/x-pack/plugins/reporting/server/config/create_config.ts index 5de54a43582ab..2ac225ec4576a 100644 --- a/x-pack/plugins/reporting/server/config/create_config.ts +++ b/x-pack/plugins/reporting/server/config/create_config.ts @@ -7,17 +7,15 @@ import crypto from 'crypto'; import ipaddr from 'ipaddr.js'; -import { sum, upperFirst } from 'lodash'; +import { sum } from 'lodash'; import { Observable } from 'rxjs'; -import { map, mergeMap } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; import { CoreSetup } from 'src/core/server'; import { LevelLogger } from '../lib'; -import { getDefaultChromiumSandboxDisabled } from './default_chromium_sandbox_disabled'; import { ReportingConfigType } from './schema'; /* * Set up dynamic config defaults - * - xpack.capture.browser.chromium.disableSandbox * - xpack.kibanaServer * - xpack.reporting.encryptionKey */ @@ -71,41 +69,6 @@ export function createConfig$( protocol: kibanaServerProtocol, }, }; - }), - mergeMap(async (config) => { - if (config.capture.browser.chromium.disableSandbox != null) { - // disableSandbox was set by user - return { ...config }; - } - - // disableSandbox was not set by user, apply default for OS - const { os, disableSandbox } = await getDefaultChromiumSandboxDisabled(); - const osName = [os.os, os.dist, os.release].filter(Boolean).map(upperFirst).join(' '); - - logger.debug(`Running on OS: '{osName}'`); - - if (disableSandbox === true) { - logger.warn( - `Chromium sandbox provides an additional layer of protection, but is not supported for ${osName} OS.` + - ` Automatically setting 'xpack.reporting.capture.browser.chromium.disableSandbox: true'.` - ); - } else { - logger.info( - `Chromium sandbox provides an additional layer of protection, and is supported for ${osName} OS.` + - ` Automatically enabling Chromium sandbox.` - ); - } - - return { - ...config, - capture: { - ...config.capture, - browser: { - ...config.capture.browser, - chromium: { ...config.capture.browser.chromium, disableSandbox }, - }, - }, - }; }) ); } diff --git a/x-pack/plugins/reporting/server/config/default_chromium_sandbox_disabled.test.ts b/x-pack/plugins/reporting/server/config/default_chromium_sandbox_disabled.test.ts deleted file mode 100644 index 6ca75b7a1701b..0000000000000 --- a/x-pack/plugins/reporting/server/config/default_chromium_sandbox_disabled.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -jest.mock('getos', () => { - return jest.fn(); -}); - -import { getDefaultChromiumSandboxDisabled } from './default_chromium_sandbox_disabled'; -import getos from 'getos'; - -interface TestObject { - os: string; - dist?: string; - release?: string; -} - -function defaultTest(os: TestObject, expectedDefault: boolean) { - test(`${expectedDefault ? 'disabled' : 'enabled'} on ${JSON.stringify(os)}`, async () => { - (getos as jest.Mock).mockImplementation((cb) => cb(null, os)); - const actualDefault = await getDefaultChromiumSandboxDisabled(); - expect(actualDefault.disableSandbox).toBe(expectedDefault); - }); -} - -defaultTest({ os: 'win32' }, false); -defaultTest({ os: 'darwin' }, false); -defaultTest({ os: 'linux', dist: 'Centos', release: '7.0' }, true); -defaultTest({ os: 'linux', dist: 'Red Hat Linux', release: '7.0' }, true); -defaultTest({ os: 'linux', dist: 'Ubuntu Linux', release: '14.04' }, false); -defaultTest({ os: 'linux', dist: 'Ubuntu Linux', release: '16.04' }, false); -defaultTest({ os: 'linux', dist: 'SUSE Linux', release: '11' }, false); -defaultTest({ os: 'linux', dist: 'SUSE Linux', release: '12' }, false); -defaultTest({ os: 'linux', dist: 'SUSE Linux', release: '42.0' }, false); -defaultTest({ os: 'linux', dist: 'Debian', release: '8' }, true); -defaultTest({ os: 'linux', dist: 'Debian', release: '9' }, true); diff --git a/x-pack/plugins/reporting/server/config/index.ts b/x-pack/plugins/reporting/server/config/index.ts index 711e930484e01..963895d1fe583 100644 --- a/x-pack/plugins/reporting/server/config/index.ts +++ b/x-pack/plugins/reporting/server/config/index.ts @@ -19,6 +19,7 @@ export const config: PluginConfigDescriptor = { schema: ConfigSchema, deprecations: ({ unused }) => [ unused('capture.browser.chromium.maxScreenshotDimension', { level: 'warning' }), // unused since 7.8 + unused('capture.browser.type'), unused('poll.jobCompletionNotifier.intervalErrorMultiplier', { level: 'warning' }), // unused since 7.10 unused('poll.jobsRefresh.intervalErrorMultiplier', { level: 'warning' }), // unused since 7.10 unused('capture.viewport', { level: 'warning' }), // deprecated as unused since 7.16 @@ -72,7 +73,6 @@ export const config: PluginConfigDescriptor = { capture: { maxAttempts: true, timeouts: { openUrl: true, renderComplete: true, waitForElements: true }, - networkPolicy: false, // show as [redacted] zoom: true, }, csv: { maxSizeBytes: true, scroll: { size: true, duration: true } }, diff --git a/x-pack/plugins/reporting/server/config/schema.test.ts b/x-pack/plugins/reporting/server/config/schema.test.ts index c49490be87a15..3af7a4e5cfe4c 100644 --- a/x-pack/plugins/reporting/server/config/schema.test.ts +++ b/x-pack/plugins/reporting/server/config/schema.test.ts @@ -55,47 +55,12 @@ describe('Reporting Config Schema', () => { ).toBe('qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq'); expect(ConfigSchema.validate({ encryptionKey: 'weaksauce' }).encryptionKey).toBe('weaksauce'); - - // disableSandbox - expect( - ConfigSchema.validate({ capture: { browser: { chromium: { disableSandbox: true } } } }) - .capture.browser.chromium - ).toMatchObject({ disableSandbox: true, proxy: { enabled: false } }); - // kibanaServer expect( ConfigSchema.validate({ kibanaServer: { hostname: 'Frodo' } }).kibanaServer ).toMatchObject({ hostname: 'Frodo' }); }); - it('allows setting a wildcard for chrome proxy bypass', () => { - expect( - ConfigSchema.validate({ - capture: { - browser: { - chromium: { - proxy: { - enabled: true, - server: 'http://example.com:8080', - bypass: ['*.example.com', '*bar.example.com', 'bats.example.com'], - }, - }, - }, - }, - }).capture.browser.chromium.proxy - ).toMatchInlineSnapshot(` - Object { - "bypass": Array [ - "*.example.com", - "*bar.example.com", - "bats.example.com", - ], - "enabled": true, - "server": "http://example.com:8080", - } - `); - }); - it.each(['0', '0.0', '0.0.0'])( `fails to validate "kibanaServer.hostname" with an invalid hostname: "%s"`, (address) => { diff --git a/x-pack/plugins/reporting/server/config/schema.ts b/x-pack/plugins/reporting/server/config/schema.ts index 4c56fc4c6db60..c031ed4f94f9d 100644 --- a/x-pack/plugins/reporting/server/config/schema.ts +++ b/x-pack/plugins/reporting/server/config/schema.ts @@ -46,20 +46,6 @@ const QueueSchema = schema.object({ }), }); -const RulesSchema = schema.object({ - allow: schema.boolean(), - host: schema.maybe(schema.string()), - protocol: schema.maybe( - schema.string({ - validate(value) { - if (!/:$/.test(value)) { - return 'must end in colon'; - } - }, - }) - ), -}); - const CaptureSchema = schema.object({ timeouts: schema.object({ openUrl: schema.oneOf([schema.number(), schema.duration()], { @@ -72,56 +58,10 @@ const CaptureSchema = schema.object({ defaultValue: moment.duration({ seconds: 30 }), }), }), - networkPolicy: schema.object({ - enabled: schema.boolean({ defaultValue: true }), - rules: schema.arrayOf(RulesSchema, { - defaultValue: [ - { host: undefined, allow: true, protocol: 'http:' }, - { host: undefined, allow: true, protocol: 'https:' }, - { host: undefined, allow: true, protocol: 'ws:' }, - { host: undefined, allow: true, protocol: 'wss:' }, - { host: undefined, allow: true, protocol: 'data:' }, - { host: undefined, allow: false, protocol: undefined }, // Default action is to deny! - ], - }), - }), zoom: schema.number({ defaultValue: 2 }), loadDelay: schema.oneOf([schema.number(), schema.duration()], { defaultValue: moment.duration({ seconds: 3 }), }), - browser: schema.object({ - autoDownload: schema.conditional( - schema.contextRef('dist'), - true, - schema.boolean({ defaultValue: false }), - schema.boolean({ defaultValue: true }) - ), - chromium: schema.object({ - inspect: schema.conditional( - schema.contextRef('dist'), - true, - schema.boolean({ defaultValue: false }), - schema.maybe(schema.never()) - ), - disableSandbox: schema.maybe(schema.boolean()), // default value is dynamic in createConfig$ - proxy: schema.object({ - enabled: schema.boolean({ defaultValue: false }), - server: schema.conditional( - schema.siblingRef('enabled'), - true, - schema.uri({ scheme: ['http', 'https'] }), - schema.maybe(schema.never()) - ), - bypass: schema.conditional( - schema.siblingRef('enabled'), - true, - schema.arrayOf(schema.string()), - schema.maybe(schema.never()) - ), - }), - }), - type: schema.string({ defaultValue: 'chromium' }), - }), maxAttempts: schema.conditional( schema.contextRef('dist'), true, diff --git a/x-pack/plugins/reporting/server/core.ts b/x-pack/plugins/reporting/server/core.ts index 43aefb73aebb9..63900db4016b5 100644 --- a/x-pack/plugins/reporting/server/core.ts +++ b/x-pack/plugins/reporting/server/core.ts @@ -7,8 +7,8 @@ import Hapi from '@hapi/hapi'; import * as Rx from 'rxjs'; -import { filter, first, map, take } from 'rxjs/operators'; -import { ScreenshotModePluginSetup } from 'src/plugins/screenshot_mode/server'; +import { filter, first, map, switchMap, take } from 'rxjs/operators'; +import type { ScreenshottingStart, ScreenshotResult } from '../../screenshotting/server'; import { BasePath, IClusterClient, @@ -28,13 +28,14 @@ import { SecurityPluginSetup } from '../../security/server'; import { DEFAULT_SPACE_ID } from '../../spaces/common/constants'; import { SpacesPluginSetup } from '../../spaces/server'; import { TaskManagerSetupContract, TaskManagerStartContract } from '../../task_manager/server'; +import { REPORTING_REDIRECT_LOCATOR_STORE_KEY } from '../common/constants'; +import { durationToNumber } from '../common/schema_utils'; import { ReportingConfig, ReportingSetup } from './'; -import { HeadlessChromiumDriverFactory } from './browsers/chromium/driver_factory'; import { ReportingConfigType } from './config'; import { checkLicense, getExportTypesRegistry, LevelLogger } from './lib'; import { ReportingStore } from './lib/store'; import { ExecuteReportTask, MonitorReportsTask, ReportTaskParams } from './lib/tasks'; -import { ReportingPluginRouter } from './types'; +import { ReportingPluginRouter, ScreenshotOptions } from './types'; export interface ReportingInternalSetup { basePath: Pick; @@ -44,13 +45,11 @@ export interface ReportingInternalSetup { security?: SecurityPluginSetup; spaces?: SpacesPluginSetup; taskManager: TaskManagerSetupContract; - screenshotMode: ScreenshotModePluginSetup; logger: LevelLogger; status: StatusServiceSetup; } export interface ReportingInternalStart { - browserDriverFactory: HeadlessChromiumDriverFactory; store: ReportingStore; savedObjects: SavedObjectsServiceStart; uiSettings: UiSettingsServiceStart; @@ -58,6 +57,7 @@ export interface ReportingInternalStart { data: DataPluginStart; taskManager: TaskManagerStartContract; logger: LevelLogger; + screenshotting: ScreenshottingStart; } export class ReportingCore { @@ -253,18 +253,6 @@ export class ReportingCore { .toPromise(); } - private getScreenshotModeDep() { - return this.getPluginSetupDeps().screenshotMode; - } - - public getEnableScreenshotMode() { - return this.getScreenshotModeDep().setScreenshotModeEnabled; - } - - public getSetScreenshotLayout() { - return this.getScreenshotModeDep().setScreenshotLayout; - } - /* * Gives synchronous access to the setupDeps */ @@ -350,6 +338,35 @@ export class ReportingCore { return startDeps.esClient; } + public getScreenshots(options: ScreenshotOptions): Rx.Observable { + return Rx.defer(() => this.getPluginStartDeps()).pipe( + switchMap(({ screenshotting }) => { + const config = this.getConfig(); + return screenshotting.getScreenshots({ + ...options, + + timeouts: { + loadDelay: durationToNumber(config.get('capture', 'loadDelay')), + openUrl: durationToNumber(config.get('capture', 'timeouts', 'openUrl')), + waitForElements: durationToNumber(config.get('capture', 'timeouts', 'waitForElements')), + renderComplete: durationToNumber(config.get('capture', 'timeouts', 'renderComplete')), + }, + + layout: { + zoom: config.get('capture', 'zoom'), + ...options.layout, + }, + + urls: options.urls.map((url) => + typeof url === 'string' + ? url + : [url[0], { [REPORTING_REDIRECT_LOCATOR_STORE_KEY]: url[1] }] + ), + }); + }) + ); + } + public trackReport(reportId: string) { this.executing.add(reportId); } diff --git a/x-pack/plugins/reporting/server/export_types/common/generate_png.ts b/x-pack/plugins/reporting/server/export_types/common/generate_png.ts index c5e70a6c93eff..8c83e0ae73527 100644 --- a/x-pack/plugins/reporting/server/export_types/common/generate_png.ts +++ b/x-pack/plugins/reporting/server/export_types/common/generate_png.ts @@ -8,70 +8,60 @@ import apm from 'elastic-apm-node'; import * as Rx from 'rxjs'; import { finalize, map, tap } from 'rxjs/operators'; +import { LayoutTypes } from '../../../../screenshotting/common'; import { REPORTING_TRANSACTION_TYPE } from '../../../common/constants'; import { ReportingCore } from '../../'; -import { UrlOrUrlLocatorTuple } from '../../../common/types'; +import { ScreenshotOptions } from '../../types'; import { LevelLogger } from '../../lib'; -import { LayoutParams, LayoutSelectorDictionary, PreserveLayout } from '../../lib/layouts'; -import { getScreenshots$, ScreenshotResults } from '../../lib/screenshots'; -import { ConditionalHeaders } from '../common'; -export async function generatePngObservableFactory(reporting: ReportingCore) { - const config = reporting.getConfig(); - const captureConfig = config.get('capture'); - const { browserDriverFactory } = await reporting.getPluginStartDeps(); - - return function generatePngObservable( - logger: LevelLogger, - urlOrUrlLocatorTuple: UrlOrUrlLocatorTuple, - browserTimezone: string | undefined, - conditionalHeaders: ConditionalHeaders, - layoutParams: LayoutParams & { selectors?: Partial } - ): Rx.Observable<{ buffer: Buffer; warnings: string[] }> { - const apmTrans = apm.startTransaction('generate-png', REPORTING_TRANSACTION_TYPE); - const apmLayout = apmTrans?.startSpan('create-layout', 'setup'); - if (!layoutParams || !layoutParams.dimensions) { - throw new Error(`LayoutParams.Dimensions is undefined.`); - } - const layout = new PreserveLayout(layoutParams.dimensions, layoutParams.selectors); +export function generatePngObservable( + reporting: ReportingCore, + logger: LevelLogger, + options: ScreenshotOptions +): Rx.Observable<{ buffer: Buffer; warnings: string[] }> { + const apmTrans = apm.startTransaction('generate-png', REPORTING_TRANSACTION_TYPE); + const apmLayout = apmTrans?.startSpan('create-layout', 'setup'); + if (!options.layout.dimensions) { + throw new Error(`LayoutParams.Dimensions is undefined.`); + } + const layout = { + id: LayoutTypes.PRESERVE_LAYOUT, + ...options.layout, + }; - if (apmLayout) apmLayout.end(); + apmLayout?.end(); - const apmScreenshots = apmTrans?.startSpan('screenshots-pipeline', 'setup'); - let apmBuffer: typeof apm.currentSpan; - const screenshots$ = getScreenshots$(captureConfig, browserDriverFactory, { - logger, - urlsOrUrlLocatorTuples: [urlOrUrlLocatorTuple], - conditionalHeaders, - layout, - browserTimezone, - }).pipe( - tap(() => { - apmScreenshots?.end(); - apmBuffer = apmTrans?.startSpan('get-buffer', 'output') ?? null; - }), - map((results: ScreenshotResults[]) => ({ - buffer: results[0].screenshots[0].data, - warnings: results.reduce((found, current) => { - if (current.error) { - found.push(current.error.message); - } - if (current.renderErrors) { - found.push(...current.renderErrors); - } - return found; - }, [] as string[]), - })), - tap(({ buffer }) => { - logger.debug(`PNG buffer byte length: ${buffer.byteLength}`); - apmTrans?.setLabel('byte-length', buffer.byteLength, false); - }), - finalize(() => { - apmBuffer?.end(); - apmTrans?.end(); - }) - ); + const apmScreenshots = apmTrans?.startSpan('screenshots-pipeline', 'setup'); + let apmBuffer: typeof apm.currentSpan; - return screenshots$; - }; + return reporting.getScreenshots({ ...options, layout }).pipe( + tap(({ metrics$ }) => { + metrics$.subscribe(({ cpu, memory }) => { + apmTrans?.setLabel('cpu', cpu, false); + apmTrans?.setLabel('memory', memory, false); + }); + apmScreenshots?.end(); + apmBuffer = apmTrans?.startSpan('get-buffer', 'output') ?? null; + }), + map(({ results }) => ({ + buffer: results[0].screenshots[0].data, + warnings: results.reduce((found, current) => { + if (current.error) { + found.push(current.error.message); + } + if (current.renderErrors) { + found.push(...current.renderErrors); + } + return found; + }, [] as string[]), + })), + tap(({ buffer }) => { + logger.debug(`PNG buffer byte length: ${buffer.byteLength}`); + apmTrans?.setLabel('byte-length', buffer.byteLength, false); + }), + finalize(() => { + apmBuffer?.end(); + apmTrans?.end(); + }) + ); } diff --git a/x-pack/plugins/reporting/server/export_types/common/index.ts b/x-pack/plugins/reporting/server/export_types/common/index.ts index c35dcb5344e21..501de48e0450a 100644 --- a/x-pack/plugins/reporting/server/export_types/common/index.ts +++ b/x-pack/plugins/reporting/server/export_types/common/index.ts @@ -10,7 +10,7 @@ export { getConditionalHeaders } from './get_conditional_headers'; export { getFullUrls } from './get_full_urls'; export { omitBlockedHeaders } from './omit_blocked_headers'; export { validateUrls } from './validate_urls'; -export { generatePngObservableFactory } from './generate_png'; +export { generatePngObservable } from './generate_png'; export { getCustomLogo } from './get_custom_logo'; export interface TimeRangeParams { diff --git a/x-pack/plugins/reporting/server/export_types/common/pdf/get_template.ts b/x-pack/plugins/reporting/server/export_types/common/pdf/get_template.ts index 58ddeb51e7a4f..0c7fedc8f7b7e 100644 --- a/x-pack/plugins/reporting/server/export_types/common/pdf/get_template.ts +++ b/x-pack/plugins/reporting/server/export_types/common/pdf/get_template.ts @@ -13,12 +13,12 @@ import { StyleDictionary, TDocumentDefinitions, } from 'pdfmake/interfaces'; -import { LayoutInstance } from '../../../lib/layouts'; +import type { Layout } from '../../../../../screenshotting/server'; import { REPORTING_TABLE_LAYOUT } from './get_doc_options'; import { getFont } from './get_font'; export function getTemplate( - layout: LayoutInstance, + layout: Layout, logo: string | undefined, title: string, tableBorderWidth: number, diff --git a/x-pack/plugins/reporting/server/export_types/common/pdf/index.test.ts b/x-pack/plugins/reporting/server/export_types/common/pdf/index.test.ts index 74a247d4568ab..2df98c6c79357 100644 --- a/x-pack/plugins/reporting/server/export_types/common/pdf/index.test.ts +++ b/x-pack/plugins/reporting/server/export_types/common/pdf/index.test.ts @@ -5,8 +5,7 @@ * 2.0. */ -import { PreserveLayout, PrintLayout } from '../../../lib/layouts'; -import { createMockConfig, createMockConfigSchema } from '../../../test_helpers'; +import { createMockLayout } from '../../../../../screenshotting/server/layouts/mock'; import { PdfMaker } from './'; const imageBase64 = Buffer.from( @@ -16,66 +15,22 @@ const imageBase64 = Buffer.from( // FLAKY: https://github.com/elastic/kibana/issues/118484 describe.skip('PdfMaker', () => { - it('makes PDF using PrintLayout mode', async () => { - const config = createMockConfig(createMockConfigSchema()); - const layout = new PrintLayout(config.get('capture')); - const pdf = new PdfMaker(layout, undefined); + let layout: ReturnType; + let pdf: PdfMaker; - expect(pdf.setTitle('the best PDF in the world')).toBe(undefined); - expect([ - pdf.addImage(imageBase64, { title: 'first viz', description: '☃️' }), - pdf.addImage(imageBase64, { title: 'second viz', description: '❄️' }), - ]).toEqual([undefined, undefined]); - - const { _layout: testLayout, _title: testTitle } = pdf as unknown as { - _layout: object; - _title: string; - }; - expect(testLayout).toMatchObject({ - captureConfig: { browser: { chromium: { disableSandbox: true } } }, // NOTE: irrelevant data? - groupCount: 2, - id: 'print', - selectors: { - itemsCountAttribute: 'data-shared-items-count', - renderComplete: '[data-shared-item]', - screenshot: '[data-shared-item]', - timefilterDurationAttribute: 'data-shared-timefilter-duration', - }, - }); - expect(testTitle).toBe('the best PDF in the world'); - - // generate buffer - pdf.generate(); - const result = await pdf.getBuffer(); - expect(Buffer.isBuffer(result)).toBe(true); + beforeEach(() => { + layout = createMockLayout(); + pdf = new PdfMaker(layout, undefined); }); - it('makes PDF using PreserveLayout mode', async () => { - const layout = new PreserveLayout({ width: 400, height: 300 }); - const pdf = new PdfMaker(layout, undefined); + describe('getBuffer', () => { + it('should generate PDF buffer', async () => { + pdf.setTitle('the best PDF in the world'); + pdf.addImage(imageBase64, { title: 'first viz', description: '☃️' }); + pdf.addImage(imageBase64, { title: 'second viz', description: '❄️' }); + pdf.generate(); - expect(pdf.setTitle('the finest PDF in the world')).toBe(undefined); - expect(pdf.addImage(imageBase64, { title: 'cool times', description: '☃️' })).toBe(undefined); - - const { _layout: testLayout, _title: testTitle } = pdf as unknown as { - _layout: object; - _title: string; - }; - expect(testLayout).toMatchObject({ - groupCount: 1, - id: 'preserve_layout', - selectors: { - itemsCountAttribute: 'data-shared-items-count', - renderComplete: '[data-shared-item]', - screenshot: '[data-shared-items-container]', - timefilterDurationAttribute: 'data-shared-timefilter-duration', - }, + await expect(pdf.getBuffer()).resolves.toBeInstanceOf(Buffer); }); - expect(testTitle).toBe('the finest PDF in the world'); - - // generate buffer - pdf.generate(); - const result = await pdf.getBuffer(); - expect(Buffer.isBuffer(result)).toBe(true); }); }); diff --git a/x-pack/plugins/reporting/server/export_types/common/pdf/index.ts b/x-pack/plugins/reporting/server/export_types/common/pdf/index.ts index 0cd054d3e3709..d6c0ec9dd844c 100644 --- a/x-pack/plugins/reporting/server/export_types/common/pdf/index.ts +++ b/x-pack/plugins/reporting/server/export_types/common/pdf/index.ts @@ -12,7 +12,7 @@ import _ from 'lodash'; import path from 'path'; import Printer from 'pdfmake'; import { Content, ContentImage, ContentText } from 'pdfmake/interfaces'; -import { LayoutInstance } from '../../../lib/layouts'; +import type { Layout } from '../../../../../screenshotting/server'; import { getDocOptions, REPORTING_TABLE_LAYOUT } from './get_doc_options'; import { getFont } from './get_font'; import { getTemplate } from './get_template'; @@ -21,14 +21,14 @@ const assetPath = path.resolve(__dirname, '..', '..', 'common', 'assets'); const tableBorderWidth = 1; export class PdfMaker { - private _layout: LayoutInstance; + private _layout: Layout; private _logo: string | undefined; private _title: string; private _content: Content[]; private _printer: Printer; private _pdfDoc: PDFKit.PDFDocument | undefined; - constructor(layout: LayoutInstance, logo: string | undefined) { + constructor(layout: Layout, logo: string | undefined) { const fontPath = (filename: string) => path.resolve(assetPath, 'fonts', filename); const fonts = { Roboto: { diff --git a/x-pack/plugins/reporting/server/export_types/png/execute_job/index.test.ts b/x-pack/plugins/reporting/server/export_types/png/execute_job/index.test.ts index ed4709d501b43..7356da4da3a11 100644 --- a/x-pack/plugins/reporting/server/export_types/png/execute_job/index.test.ts +++ b/x-pack/plugins/reporting/server/export_types/png/execute_job/index.test.ts @@ -15,11 +15,11 @@ import { createMockConfigSchema, createMockReportingCore, } from '../../../test_helpers'; -import { generatePngObservableFactory } from '../../common'; +import { generatePngObservable } from '../../common'; import { TaskPayloadPNG } from '../types'; import { runTaskFnFactory } from './'; -jest.mock('../../common/generate_png', () => ({ generatePngObservableFactory: jest.fn() })); +jest.mock('../../common/generate_png'); let content: string; let mockReporting: ReportingCore; @@ -61,16 +61,13 @@ beforeEach(async () => { mockReporting = await createMockReportingCore(mockReportingConfig); mockReporting.setConfig(createMockConfig(mockReportingConfig)); - - (generatePngObservableFactory as jest.Mock).mockReturnValue(jest.fn()); }); -afterEach(() => (generatePngObservableFactory as jest.Mock).mockReset()); +afterEach(() => (generatePngObservable as jest.Mock).mockReset()); test(`passes browserTimezone to generatePng`, async () => { const encryptedHeaders = await encryptHeaders({}); - const generatePngObservable = (await generatePngObservableFactory(mockReporting)) as jest.Mock; - generatePngObservable.mockReturnValue(Rx.of({ buffer: Buffer.from('') })); + (generatePngObservable as jest.Mock).mockReturnValue(Rx.of({ buffer: Buffer.from('') })); const runTask = await runTaskFnFactory(mockReporting, getMockLogger()); const browserTimezone = 'UTC'; @@ -85,42 +82,24 @@ test(`passes browserTimezone to generatePng`, async () => { stream ); - expect(generatePngObservable.mock.calls).toMatchInlineSnapshot(` - Array [ - Array [ - LevelLogger { - "_logger": Object { - "get": [MockFunction], - }, - "_tags": Array [ - "PNG", - "execute", - "pngJobId", - ], - "warning": [Function], - }, - "localhost:80undefined/app/kibana#/something", - "UTC", - Object { - "conditions": Object { - "basePath": undefined, - "hostname": "localhost", - "port": 80, - "protocol": undefined, - }, - "headers": Object {}, - }, - undefined, - ], - ] - `); + expect(generatePngObservable).toHaveBeenCalledWith( + expect.anything(), + expect.anything(), + expect.objectContaining({ + urls: ['localhost:80undefined/app/kibana#/something'], + browserTimezone: 'UTC', + conditionalHeaders: expect.objectContaining({ + conditions: expect.any(Object), + headers: {}, + }), + }) + ); }); test(`returns content_type of application/png`, async () => { const runTask = await runTaskFnFactory(mockReporting, getMockLogger()); const encryptedHeaders = await encryptHeaders({}); - const generatePngObservable = await generatePngObservableFactory(mockReporting); (generatePngObservable as jest.Mock).mockReturnValue(Rx.of({ buffer: Buffer.from('foo') })); const { content_type: contentType } = await runTask( @@ -134,7 +113,6 @@ test(`returns content_type of application/png`, async () => { test(`returns content of generatePng`, async () => { const testContent = 'raw string from get_screenhots'; - const generatePngObservable = await generatePngObservableFactory(mockReporting); (generatePngObservable as jest.Mock).mockReturnValue(Rx.of({ buffer: Buffer.from(testContent) })); const runTask = await runTaskFnFactory(mockReporting, getMockLogger()); diff --git a/x-pack/plugins/reporting/server/export_types/png/execute_job/index.ts b/x-pack/plugins/reporting/server/export_types/png/execute_job/index.ts index 2446e7a7d1c51..e6cbfb45eb095 100644 --- a/x-pack/plugins/reporting/server/export_types/png/execute_job/index.ts +++ b/x-pack/plugins/reporting/server/export_types/png/execute_job/index.ts @@ -7,7 +7,7 @@ import apm from 'elastic-apm-node'; import * as Rx from 'rxjs'; -import { catchError, finalize, map, mergeMap, takeUntil, tap } from 'rxjs/operators'; +import { finalize, map, mergeMap, takeUntil, tap } from 'rxjs/operators'; import { PNG_JOB_TYPE, REPORTING_TRANSACTION_TYPE } from '../../../../common/constants'; import { TaskRunResult } from '../../../lib/tasks'; import { RunTaskFn, RunTaskFnFactory } from '../../../types'; @@ -16,7 +16,7 @@ import { getConditionalHeaders, getFullUrls, omitBlockedHeaders, - generatePngObservableFactory, + generatePngObservable, } from '../../common'; import { TaskPayloadPNG } from '../types'; @@ -25,40 +25,35 @@ export const runTaskFnFactory: RunTaskFnFactory> = const config = reporting.getConfig(); const encryptionKey = config.get('encryptionKey'); - return async function runTask(jobId, job, cancellationToken, stream) { + return function runTask(jobId, job, cancellationToken, stream) { const apmTrans = apm.startTransaction('execute-job-png', REPORTING_TRANSACTION_TYPE); const apmGetAssets = apmTrans?.startSpan('get-assets', 'setup'); let apmGeneratePng: { end: () => void } | null | undefined; - const generatePngObservable = await generatePngObservableFactory(reporting); const jobLogger = parentLogger.clone([PNG_JOB_TYPE, 'execute', jobId]); const process$: Rx.Observable = Rx.of(1).pipe( mergeMap(() => decryptJobHeaders(encryptionKey, job.headers, jobLogger)), map((decryptedHeaders) => omitBlockedHeaders(decryptedHeaders)), map((filteredHeaders) => getConditionalHeaders(config, filteredHeaders)), mergeMap((conditionalHeaders) => { - const urls = getFullUrls(config, job); - const hashUrl = urls[0]; - if (apmGetAssets) apmGetAssets.end(); + const [url] = getFullUrls(config, job); + apmGetAssets?.end(); apmGeneratePng = apmTrans?.startSpan('generate-png-pipeline', 'execute'); - return generatePngObservable( - jobLogger, - hashUrl, - job.browserTimezone, + + return generatePngObservable(reporting, jobLogger, { conditionalHeaders, - job.layout - ); + urls: [url], + browserTimezone: job.browserTimezone, + layout: job.layout, + }); }), tap(({ buffer }) => stream.write(buffer)), map(({ warnings }) => ({ content_type: 'image/png', warnings, })), - catchError((err) => { - jobLogger.error(err); - return Rx.throwError(err); - }), + tap({ error: (error) => jobLogger.error(error) }), finalize(() => apmGeneratePng?.end()) ); diff --git a/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.test.ts b/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.test.ts index ba076f98996b1..783c8f8e8f880 100644 --- a/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.test.ts +++ b/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.test.ts @@ -16,11 +16,11 @@ import { createMockConfigSchema, createMockReportingCore, } from '../../test_helpers'; -import { generatePngObservableFactory } from '../common'; +import { generatePngObservable } from '../common'; import { runTaskFnFactory } from './execute_job'; import { TaskPayloadPNGV2 } from './types'; -jest.mock('../common/generate_png', () => ({ generatePngObservableFactory: jest.fn() })); +jest.mock('../common/generate_png'); let content: string; let mockReporting: ReportingCore; @@ -62,16 +62,13 @@ beforeEach(async () => { mockReporting = await createMockReportingCore(mockReportingConfig); mockReporting.setConfig(createMockConfig(mockReportingConfig)); - - (generatePngObservableFactory as jest.Mock).mockReturnValue(jest.fn()); }); -afterEach(() => (generatePngObservableFactory as jest.Mock).mockReset()); +afterEach(() => (generatePngObservable as jest.Mock).mockReset()); test(`passes browserTimezone to generatePng`, async () => { const encryptedHeaders = await encryptHeaders({}); - const generatePngObservable = (await generatePngObservableFactory(mockReporting)) as jest.Mock; - generatePngObservable.mockReturnValue(Rx.of({ buffer: Buffer.from('') })); + (generatePngObservable as jest.Mock).mockReturnValue(Rx.of({ buffer: Buffer.from('') })); const runTask = await runTaskFnFactory(mockReporting, getMockLogger()); const browserTimezone = 'UTC'; @@ -87,49 +84,29 @@ test(`passes browserTimezone to generatePng`, async () => { stream ); - expect(generatePngObservable.mock.calls).toMatchInlineSnapshot(` - Array [ - Array [ - LevelLogger { - "_logger": Object { - "get": [MockFunction], - }, - "_tags": Array [ - "PNGV2", - "execute", - "pngJobId", - ], - "warning": [Function], - }, - Array [ - "localhost:80undefined/app/reportingRedirect?forceNow=test", - Object { - "id": "test", - "params": Object {}, - "version": "test", - }, + expect(generatePngObservable).toHaveBeenCalledWith( + expect.anything(), + expect.anything(), + expect.objectContaining({ + urls: [ + [ + 'localhost:80undefined/app/reportingRedirect?forceNow=test', + { id: 'test', params: {}, version: 'test' }, ], - "UTC", - Object { - "conditions": Object { - "basePath": undefined, - "hostname": "localhost", - "port": 80, - "protocol": undefined, - }, - "headers": Object {}, - }, - undefined, ], - ] - `); + browserTimezone: 'UTC', + conditionalHeaders: expect.objectContaining({ + conditions: expect.any(Object), + headers: {}, + }), + }) + ); }); test(`returns content_type of application/png`, async () => { const runTask = await runTaskFnFactory(mockReporting, getMockLogger()); const encryptedHeaders = await encryptHeaders({}); - const generatePngObservable = await generatePngObservableFactory(mockReporting); (generatePngObservable as jest.Mock).mockReturnValue(Rx.of({ buffer: Buffer.from('foo') })); const { content_type: contentType } = await runTask( @@ -146,7 +123,6 @@ test(`returns content_type of application/png`, async () => { test(`returns content of generatePng getBuffer base64 encoded`, async () => { const testContent = 'raw string from get_screenhots'; - const generatePngObservable = await generatePngObservableFactory(mockReporting); (generatePngObservable as jest.Mock).mockReturnValue(Rx.of({ buffer: Buffer.from(testContent) })); const runTask = await runTaskFnFactory(mockReporting, getMockLogger()); diff --git a/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.ts b/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.ts index 00652309b88c1..a8ab6c4355000 100644 --- a/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.ts +++ b/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.ts @@ -7,7 +7,7 @@ import apm from 'elastic-apm-node'; import * as Rx from 'rxjs'; -import { catchError, finalize, map, mergeMap, takeUntil, tap } from 'rxjs/operators'; +import { finalize, map, mergeMap, takeUntil, tap } from 'rxjs/operators'; import { PNG_JOB_TYPE_V2, REPORTING_TRANSACTION_TYPE } from '../../../common/constants'; import { TaskRunResult } from '../../lib/tasks'; import { RunTaskFn, RunTaskFnFactory } from '../../types'; @@ -15,7 +15,7 @@ import { decryptJobHeaders, getConditionalHeaders, omitBlockedHeaders, - generatePngObservableFactory, + generatePngObservable, } from '../common'; import { getFullRedirectAppUrl } from '../common/v2/get_full_redirect_app_url'; import { TaskPayloadPNGV2 } from './types'; @@ -25,12 +25,11 @@ export const runTaskFnFactory: RunTaskFnFactory> = const config = reporting.getConfig(); const encryptionKey = config.get('encryptionKey'); - return async function runTask(jobId, job, cancellationToken, stream) { + return function runTask(jobId, job, cancellationToken, stream) { const apmTrans = apm.startTransaction('execute-job-png-v2', REPORTING_TRANSACTION_TYPE); const apmGetAssets = apmTrans?.startSpan('get-assets', 'setup'); let apmGeneratePng: { end: () => void } | null | undefined; - const generatePngObservable = await generatePngObservableFactory(reporting); const jobLogger = parentLogger.clone([PNG_JOB_TYPE_V2, 'execute', jobId]); const process$: Rx.Observable = Rx.of(1).pipe( mergeMap(() => decryptJobHeaders(encryptionKey, job.headers, jobLogger)), @@ -41,25 +40,21 @@ export const runTaskFnFactory: RunTaskFnFactory> = const [locatorParams] = job.locatorParams; apmGetAssets?.end(); - apmGeneratePng = apmTrans?.startSpan('generate-png-pipeline', 'execute'); - return generatePngObservable( - jobLogger, - [url, locatorParams], - job.browserTimezone, + + return generatePngObservable(reporting, jobLogger, { conditionalHeaders, - job.layout - ); + browserTimezone: job.browserTimezone, + layout: job.layout, + urls: [[url, locatorParams]], + }); }), tap(({ buffer }) => stream.write(buffer)), map(({ warnings }) => ({ content_type: 'image/png', warnings, })), - catchError((err) => { - jobLogger.error(err); - return Rx.throwError(err); - }), + tap({ error: (error) => jobLogger.error(error) }), finalize(() => apmGeneratePng?.end()) ); diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.test.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.test.ts index 02f9c93929ea1..eb02097ec7924 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.test.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.test.ts @@ -5,18 +5,18 @@ * 2.0. */ -jest.mock('../lib/generate_pdf', () => ({ generatePdfObservableFactory: jest.fn() })); - import * as Rx from 'rxjs'; import { Writable } from 'stream'; import { ReportingCore } from '../../../'; import { CancellationToken } from '../../../../common'; import { cryptoFactory, LevelLogger } from '../../../lib'; import { createMockConfigSchema, createMockReportingCore } from '../../../test_helpers'; -import { generatePdfObservableFactory } from '../lib/generate_pdf'; +import { generatePdfObservable } from '../lib/generate_pdf'; import { TaskPayloadPDF } from '../types'; import { runTaskFnFactory } from './'; +jest.mock('../lib/generate_pdf'); + let content: string; let mockReporting: ReportingCore; let stream: jest.Mocked; @@ -56,16 +56,13 @@ beforeEach(async () => { }; const mockSchema = createMockConfigSchema(reportingConfig); mockReporting = await createMockReportingCore(mockSchema); - - (generatePdfObservableFactory as jest.Mock).mockReturnValue(jest.fn()); }); -afterEach(() => (generatePdfObservableFactory as jest.Mock).mockReset()); +afterEach(() => (generatePdfObservable as jest.Mock).mockReset()); test(`passes browserTimezone to generatePdf`, async () => { const encryptedHeaders = await encryptHeaders({}); - const generatePdfObservable = (await generatePdfObservableFactory(mockReporting)) as jest.Mock; - generatePdfObservable.mockReturnValue(Rx.of({ buffer: Buffer.from('') })); + (generatePdfObservable as jest.Mock).mockReturnValue(Rx.of({ buffer: Buffer.from('') })); const runTask = runTaskFnFactory(mockReporting, getMockLogger()); const browserTimezone = 'UTC'; @@ -81,8 +78,13 @@ test(`passes browserTimezone to generatePdf`, async () => { stream ); - const tzParam = generatePdfObservable.mock.calls[0][3]; - expect(tzParam).toBe('UTC'); + expect(generatePdfObservable).toHaveBeenCalledWith( + expect.anything(), + expect.anything(), + expect.anything(), + expect.objectContaining({ browserTimezone: 'UTC' }), + undefined + ); }); test(`returns content_type of application/pdf`, async () => { @@ -90,7 +92,6 @@ test(`returns content_type of application/pdf`, async () => { const runTask = runTaskFnFactory(mockReporting, logger); const encryptedHeaders = await encryptHeaders({}); - const generatePdfObservable = await generatePdfObservableFactory(mockReporting); (generatePdfObservable as jest.Mock).mockReturnValue(Rx.of({ buffer: Buffer.from('') })); const { content_type: contentType } = await runTask( @@ -104,7 +105,6 @@ test(`returns content_type of application/pdf`, async () => { test(`returns content of generatePdf getBuffer base64 encoded`, async () => { const testContent = 'test content'; - const generatePdfObservable = await generatePdfObservableFactory(mockReporting); (generatePdfObservable as jest.Mock).mockReturnValue(Rx.of({ buffer: Buffer.from(testContent) })); const runTask = runTaskFnFactory(mockReporting, getMockLogger()); diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.ts index 2358333bbe7ef..f301b3e1e6ef2 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.ts @@ -18,7 +18,7 @@ import { omitBlockedHeaders, getCustomLogo, } from '../../common'; -import { generatePdfObservableFactory } from '../lib/generate_pdf'; +import { generatePdfObservable } from '../lib/generate_pdf'; import { TaskPayloadPDF } from '../types'; export const runTaskFnFactory: RunTaskFnFactory> = @@ -32,8 +32,6 @@ export const runTaskFnFactory: RunTaskFnFactory> = const apmGetAssets = apmTrans?.startSpan('get-assets', 'setup'); let apmGeneratePdf: { end: () => void } | null | undefined; - const generatePdfObservable = await generatePdfObservableFactory(reporting); - const process$: Rx.Observable = Rx.of(1).pipe( mergeMap(() => decryptJobHeaders(encryptionKey, job.headers, jobLogger)), map((decryptedHeaders) => omitBlockedHeaders(decryptedHeaders)), @@ -49,12 +47,15 @@ export const runTaskFnFactory: RunTaskFnFactory> = apmGeneratePdf = apmTrans?.startSpan('generate-pdf-pipeline', 'execute'); return generatePdfObservable( + reporting, jobLogger, title, - urls, - browserTimezone, - conditionalHeaders, - layout, + { + urls, + browserTimezone, + conditionalHeaders, + layout, + }, logo ); }), diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/generate_pdf.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/generate_pdf.ts index dce6ea678bded..5bf087fecd10a 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/generate_pdf.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/generate_pdf.ts @@ -8,16 +8,15 @@ import { groupBy } from 'lodash'; import * as Rx from 'rxjs'; import { mergeMap } from 'rxjs/operators'; +import { ScreenshotResult } from '../../../../../screenshotting/server'; import { ReportingCore } from '../../../'; import { LevelLogger } from '../../../lib'; -import { createLayout, LayoutParams } from '../../../lib/layouts'; -import { getScreenshots$, ScreenshotResults } from '../../../lib/screenshots'; -import { ConditionalHeaders } from '../../common'; +import { ScreenshotOptions } from '../../../types'; import { PdfMaker } from '../../common/pdf'; import { getTracker } from './tracker'; -const getTimeRange = (urlScreenshots: ScreenshotResults[]) => { - const grouped = groupBy(urlScreenshots.map((u) => u.timeRange)); +const getTimeRange = (urlScreenshots: ScreenshotResult['results']) => { + const grouped = groupBy(urlScreenshots.map(({ timeRange }) => timeRange)); const values = Object.values(grouped); if (values.length === 1) { return values[0][0]; @@ -26,97 +25,80 @@ const getTimeRange = (urlScreenshots: ScreenshotResults[]) => { return null; }; -export async function generatePdfObservableFactory(reporting: ReportingCore) { - const config = reporting.getConfig(); - const captureConfig = config.get('capture'); - const { browserDriverFactory } = await reporting.getPluginStartDeps(); - - return function generatePdfObservable( - logger: LevelLogger, - title: string, - urls: string[], - browserTimezone: string | undefined, - conditionalHeaders: ConditionalHeaders, - layoutParams: LayoutParams, - logo?: string - ): Rx.Observable<{ buffer: Buffer | null; warnings: string[] }> { - const tracker = getTracker(); - tracker.startLayout(); - - const layout = createLayout(captureConfig, layoutParams); - logger.debug(`Layout: width=${layout.width} height=${layout.height}`); - tracker.endLayout(); - - tracker.startScreenshots(); - const screenshots$ = getScreenshots$(captureConfig, browserDriverFactory, { - logger, - urlsOrUrlLocatorTuples: urls, - conditionalHeaders, - layout, - browserTimezone, - }).pipe( - mergeMap(async (results: ScreenshotResults[]) => { - tracker.endScreenshots(); - - tracker.startSetup(); - const pdfOutput = new PdfMaker(layout, logo); - if (title) { - const timeRange = getTimeRange(results); - title += timeRange ? ` - ${timeRange}` : ''; - pdfOutput.setTitle(title); - } - tracker.endSetup(); - - results.forEach((r) => { - r.screenshots.forEach((screenshot) => { - logger.debug(`Adding image to PDF. Image size: ${screenshot.data.byteLength}`); // prettier-ignore - tracker.startAddImage(); - tracker.endAddImage(); - pdfOutput.addImage(screenshot.data, { - title: screenshot.title ?? undefined, - description: screenshot.description ?? undefined, - }); +export function generatePdfObservable( + reporting: ReportingCore, + logger: LevelLogger, + title: string, + options: ScreenshotOptions, + logo?: string +): Rx.Observable<{ buffer: Buffer | null; warnings: string[] }> { + const tracker = getTracker(); + tracker.startScreenshots(); + + return reporting.getScreenshots(options).pipe( + mergeMap(async ({ layout, metrics$, results }) => { + metrics$.subscribe(({ cpu, memory }) => { + tracker.setCpuUsage(cpu); + tracker.setMemoryUsage(memory); + }); + tracker.endScreenshots(); + tracker.startSetup(); + + const pdfOutput = new PdfMaker(layout, logo); + if (title) { + const timeRange = getTimeRange(results); + title += timeRange ? ` - ${timeRange}` : ''; + pdfOutput.setTitle(title); + } + tracker.endSetup(); + + results.forEach((r) => { + r.screenshots.forEach((screenshot) => { + logger.debug(`Adding image to PDF. Image size: ${screenshot.data.byteLength}`); // prettier-ignore + tracker.startAddImage(); + tracker.endAddImage(); + pdfOutput.addImage(screenshot.data, { + title: screenshot.title ?? undefined, + description: screenshot.description ?? undefined, }); }); - - let buffer: Buffer | null = null; - try { - tracker.startCompile(); - logger.debug(`Compiling PDF using "${layout.id}" layout...`); - pdfOutput.generate(); - tracker.endCompile(); - - tracker.startGetBuffer(); - logger.debug(`Generating PDF Buffer...`); - buffer = await pdfOutput.getBuffer(); - - const byteLength = buffer?.byteLength ?? 0; - logger.debug(`PDF buffer byte length: ${byteLength}`); - tracker.setByteLength(byteLength); - - tracker.endGetBuffer(); - } catch (err) { - logger.error(`Could not generate the PDF buffer!`); - logger.error(err); - } - - tracker.end(); - - return { - buffer, - warnings: results.reduce((found, current) => { - if (current.error) { - found.push(current.error.message); - } - if (current.renderErrors) { - found.push(...current.renderErrors); - } - return found; - }, [] as string[]), - }; - }) - ); - - return screenshots$; - }; + }); + + let buffer: Buffer | null = null; + try { + tracker.startCompile(); + logger.debug(`Compiling PDF using "${layout.id}" layout...`); + pdfOutput.generate(); + tracker.endCompile(); + + tracker.startGetBuffer(); + logger.debug(`Generating PDF Buffer...`); + buffer = await pdfOutput.getBuffer(); + + const byteLength = buffer?.byteLength ?? 0; + logger.debug(`PDF buffer byte length: ${byteLength}`); + tracker.setByteLength(byteLength); + + tracker.endGetBuffer(); + } catch (err) { + logger.error(`Could not generate the PDF buffer!`); + logger.error(err); + } + + tracker.end(); + + return { + buffer, + warnings: results.reduce((found, current) => { + if (current.error) { + found.push(current.error.message); + } + if (current.renderErrors) { + found.push(...current.renderErrors); + } + return found; + }, [] as string[]), + }; + }) + ); } diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/tracker.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/tracker.ts index 3d720ccade546..d1cf2b96817d2 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/tracker.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/tracker.ts @@ -10,8 +10,8 @@ import { REPORTING_TRANSACTION_TYPE } from '../../../../common/constants'; interface PdfTracker { setByteLength: (byteLength: number) => void; - startLayout: () => void; - endLayout: () => void; + setCpuUsage: (cpu: number) => void; + setMemoryUsage: (memory: number) => void; startScreenshots: () => void; endScreenshots: () => void; startSetup: () => void; @@ -35,7 +35,6 @@ interface ApmSpan { export function getTracker(): PdfTracker { const apmTrans = apm.startTransaction('generate-pdf', REPORTING_TRANSACTION_TYPE); - let apmLayout: ApmSpan | null = null; let apmScreenshots: ApmSpan | null = null; let apmSetup: ApmSpan | null = null; let apmAddImage: ApmSpan | null = null; @@ -43,12 +42,6 @@ export function getTracker(): PdfTracker { let apmGetBuffer: ApmSpan | null = null; return { - startLayout() { - apmLayout = apmTrans?.startSpan('create-layout', SPANTYPE_SETUP) || null; - }, - endLayout() { - if (apmLayout) apmLayout.end(); - }, startScreenshots() { apmScreenshots = apmTrans?.startSpan('screenshots-pipeline', SPANTYPE_SETUP) || null; }, @@ -82,6 +75,12 @@ export function getTracker(): PdfTracker { setByteLength(byteLength: number) { apmTrans?.setLabel('byte-length', byteLength, false); }, + setCpuUsage(cpu: number) { + apmTrans?.setLabel('cpu', cpu, false); + }, + setMemoryUsage(memory: number) { + apmTrans?.setLabel('memory', memory, false); + }, end() { if (apmTrans) apmTrans.end(); }, diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.test.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.test.ts index 197bd3866b8f6..9a73595ff32da 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.test.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -jest.mock('./lib/generate_pdf', () => ({ generatePdfObservableFactory: jest.fn() })); +jest.mock('./lib/generate_pdf'); import * as Rx from 'rxjs'; import { Writable } from 'stream'; @@ -15,7 +15,7 @@ import { LocatorParams } from '../../../common/types'; import { cryptoFactory, LevelLogger } from '../../lib'; import { createMockConfigSchema, createMockReportingCore } from '../../test_helpers'; import { runTaskFnFactory } from './execute_job'; -import { generatePdfObservableFactory } from './lib/generate_pdf'; +import { generatePdfObservable } from './lib/generate_pdf'; import { TaskPayloadPDFV2 } from './types'; let content: string; @@ -61,16 +61,13 @@ beforeEach(async () => { }; const mockSchema = createMockConfigSchema(reportingConfig); mockReporting = await createMockReportingCore(mockSchema); - - (generatePdfObservableFactory as jest.Mock).mockReturnValue(jest.fn()); }); -afterEach(() => (generatePdfObservableFactory as jest.Mock).mockReset()); +afterEach(() => (generatePdfObservable as jest.Mock).mockReset()); test(`passes browserTimezone to generatePdf`, async () => { const encryptedHeaders = await encryptHeaders({}); - const generatePdfObservable = (await generatePdfObservableFactory(mockReporting)) as jest.Mock; - generatePdfObservable.mockReturnValue(Rx.of(Buffer.from(''))); + (generatePdfObservable as jest.Mock).mockReturnValue(Rx.of(Buffer.from(''))); const runTask = runTaskFnFactory(mockReporting, getMockLogger()); const browserTimezone = 'UTC'; @@ -87,8 +84,15 @@ test(`passes browserTimezone to generatePdf`, async () => { stream ); - const tzParam = generatePdfObservable.mock.calls[0][4]; - expect(tzParam).toBe('UTC'); + expect(generatePdfObservable).toHaveBeenCalledWith( + expect.anything(), + expect.anything(), + expect.anything(), + expect.anything(), + expect.anything(), + expect.objectContaining({ browserTimezone: 'UTC' }), + undefined + ); }); test(`returns content_type of application/pdf`, async () => { @@ -96,7 +100,6 @@ test(`returns content_type of application/pdf`, async () => { const runTask = runTaskFnFactory(mockReporting, logger); const encryptedHeaders = await encryptHeaders({}); - const generatePdfObservable = await generatePdfObservableFactory(mockReporting); (generatePdfObservable as jest.Mock).mockReturnValue(Rx.of({ buffer: Buffer.from('') })); const { content_type: contentType } = await runTask( @@ -110,7 +113,6 @@ test(`returns content_type of application/pdf`, async () => { test(`returns content of generatePdf getBuffer base64 encoded`, async () => { const testContent = 'test content'; - const generatePdfObservable = await generatePdfObservableFactory(mockReporting); (generatePdfObservable as jest.Mock).mockReturnValue(Rx.of({ buffer: Buffer.from(testContent) })); const runTask = runTaskFnFactory(mockReporting, getMockLogger()); diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.ts index b1b6f3f79aee3..890c0c9cde731 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.ts @@ -17,7 +17,7 @@ import { omitBlockedHeaders, getCustomLogo, } from '../common'; -import { generatePdfObservableFactory } from './lib/generate_pdf'; +import { generatePdfObservable } from './lib/generate_pdf'; import { TaskPayloadPDFV2 } from './types'; export const runTaskFnFactory: RunTaskFnFactory> = @@ -31,8 +31,6 @@ export const runTaskFnFactory: RunTaskFnFactory> = const apmGetAssets = apmTrans?.startSpan('get-assets', 'setup'); let apmGeneratePdf: { end: () => void } | null | undefined; - const generatePdfObservable = await generatePdfObservableFactory(reporting); - const process$: Rx.Observable = Rx.of(1).pipe( mergeMap(() => decryptJobHeaders(encryptionKey, job.headers, jobLogger)), map((decryptedHeaders) => omitBlockedHeaders(decryptedHeaders)), @@ -46,13 +44,16 @@ export const runTaskFnFactory: RunTaskFnFactory> = apmGeneratePdf = apmTrans?.startSpan('generate-pdf-pipeline', 'execute'); return generatePdfObservable( + reporting, jobLogger, job, title, locatorParams, - browserTimezone, - conditionalHeaders, - layout, + { + browserTimezone, + conditionalHeaders, + layout, + }, logo ); }), diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/lib/generate_pdf.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/lib/generate_pdf.ts index b44e2ca4441eb..3d790beb41b39 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/lib/generate_pdf.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/lib/generate_pdf.ts @@ -5,21 +5,20 @@ * 2.0. */ -import { groupBy, zip } from 'lodash'; +import { groupBy } from 'lodash'; import * as Rx from 'rxjs'; import { mergeMap } from 'rxjs/operators'; import { ReportingCore } from '../../../'; import { LocatorParams, UrlOrUrlLocatorTuple } from '../../../../common/types'; import { LevelLogger } from '../../../lib'; -import { createLayout, LayoutParams } from '../../../lib/layouts'; -import { getScreenshots$, ScreenshotResults } from '../../../lib/screenshots'; -import { ConditionalHeaders } from '../../common'; +import { ScreenshotResult } from '../../../../../screenshotting/server'; +import { ScreenshotOptions } from '../../../types'; import { PdfMaker } from '../../common/pdf'; import { getFullRedirectAppUrl } from '../../common/v2/get_full_redirect_app_url'; import type { TaskPayloadPDFV2 } from '../types'; import { getTracker } from './tracker'; -const getTimeRange = (urlScreenshots: ScreenshotResults[]) => { +const getTimeRange = (urlScreenshots: ScreenshotResult['results']) => { const grouped = groupBy(urlScreenshots.map((u) => u.timeRange)); const values = Object.values(grouped); if (values.length === 1) { @@ -29,106 +28,92 @@ const getTimeRange = (urlScreenshots: ScreenshotResults[]) => { return null; }; -export async function generatePdfObservableFactory(reporting: ReportingCore) { - const config = reporting.getConfig(); - const captureConfig = config.get('capture'); - const { browserDriverFactory } = await reporting.getPluginStartDeps(); - - return function generatePdfObservable( - logger: LevelLogger, - job: TaskPayloadPDFV2, - title: string, - locatorParams: LocatorParams[], - browserTimezone: string | undefined, - conditionalHeaders: ConditionalHeaders, - layoutParams: LayoutParams, - logo?: string - ): Rx.Observable<{ buffer: Buffer | null; warnings: string[] }> { - const tracker = getTracker(); - tracker.startLayout(); - - const layout = createLayout(captureConfig, layoutParams); - logger.debug(`Layout: width=${layout.width} height=${layout.height}`); - tracker.endLayout(); - - tracker.startScreenshots(); - - /** - * For each locator we get the relative URL to the redirect app - */ - const urls = locatorParams.map(() => - getFullRedirectAppUrl(reporting.getConfig(), job.spaceId, job.forceNow) - ); - - const screenshots$ = getScreenshots$(captureConfig, browserDriverFactory, { - logger, - urlsOrUrlLocatorTuples: zip(urls, locatorParams) as UrlOrUrlLocatorTuple[], - conditionalHeaders, - layout, - browserTimezone, - }).pipe( - mergeMap(async (results: ScreenshotResults[]) => { - tracker.endScreenshots(); - - tracker.startSetup(); - const pdfOutput = new PdfMaker(layout, logo); - if (title) { - const timeRange = getTimeRange(results); - title += timeRange ? ` - ${timeRange}` : ''; - pdfOutput.setTitle(title); - } - tracker.endSetup(); - - results.forEach((r) => { - r.screenshots.forEach((screenshot) => { - logger.debug(`Adding image to PDF. Image base64 size: ${screenshot.data.byteLength}`); // prettier-ignore - tracker.startAddImage(); - tracker.endAddImage(); - pdfOutput.addImage(screenshot.data, { - title: screenshot.title ?? undefined, - description: screenshot.description ?? undefined, - }); +export function generatePdfObservable( + reporting: ReportingCore, + logger: LevelLogger, + job: TaskPayloadPDFV2, + title: string, + locatorParams: LocatorParams[], + options: Omit, + logo?: string +): Rx.Observable<{ buffer: Buffer | null; warnings: string[] }> { + const tracker = getTracker(); + tracker.startScreenshots(); + + /** + * For each locator we get the relative URL to the redirect app + */ + const urls = locatorParams.map((locator) => [ + getFullRedirectAppUrl(reporting.getConfig(), job.spaceId, job.forceNow), + locator, + ]) as UrlOrUrlLocatorTuple[]; + + const screenshots$ = reporting.getScreenshots({ ...options, urls }).pipe( + mergeMap(async ({ layout, metrics$, results }) => { + metrics$.subscribe(({ cpu, memory }) => { + tracker.setCpuUsage(cpu); + tracker.setMemoryUsage(memory); + }); + tracker.endScreenshots(); + tracker.startSetup(); + + const pdfOutput = new PdfMaker(layout, logo); + if (title) { + const timeRange = getTimeRange(results); + title += timeRange ? ` - ${timeRange}` : ''; + pdfOutput.setTitle(title); + } + tracker.endSetup(); + + results.forEach((r) => { + r.screenshots.forEach((screenshot) => { + logger.debug(`Adding image to PDF. Image base64 size: ${screenshot.data.byteLength}`); // prettier-ignore + tracker.startAddImage(); + tracker.endAddImage(); + pdfOutput.addImage(screenshot.data, { + title: screenshot.title ?? undefined, + description: screenshot.description ?? undefined, }); }); - - let buffer: Buffer | null = null; - try { - tracker.startCompile(); - logger.debug(`Compiling PDF using "${layout.id}" layout...`); - pdfOutput.generate(); - tracker.endCompile(); - - tracker.startGetBuffer(); - logger.debug(`Generating PDF Buffer...`); - buffer = await pdfOutput.getBuffer(); - - const byteLength = buffer?.byteLength ?? 0; - logger.debug(`PDF buffer byte length: ${byteLength}`); - tracker.setByteLength(byteLength); - - tracker.endGetBuffer(); - } catch (err) { - logger.error(`Could not generate the PDF buffer!`); - logger.error(err); - } - - tracker.end(); - - return { - buffer, - warnings: results.reduce((found, current) => { - if (current.error) { - found.push(current.error.message); - } - if (current.renderErrors) { - found.push(...current.renderErrors); - } - return found; - }, [] as string[]), - }; - }) - ); - - return screenshots$; - }; + }); + + let buffer: Buffer | null = null; + try { + tracker.startCompile(); + logger.debug(`Compiling PDF using "${layout.id}" layout...`); + pdfOutput.generate(); + tracker.endCompile(); + + tracker.startGetBuffer(); + logger.debug(`Generating PDF Buffer...`); + buffer = await pdfOutput.getBuffer(); + + const byteLength = buffer?.byteLength ?? 0; + logger.debug(`PDF buffer byte length: ${byteLength}`); + tracker.setByteLength(byteLength); + + tracker.endGetBuffer(); + } catch (err) { + logger.error(`Could not generate the PDF buffer!`); + logger.error(err); + } + + tracker.end(); + + return { + buffer, + warnings: results.reduce((found, current) => { + if (current.error) { + found.push(current.error.message); + } + if (current.renderErrors) { + found.push(...current.renderErrors); + } + return found; + }, [] as string[]), + }; + }) + ); + + return screenshots$; } diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/lib/tracker.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/lib/tracker.ts index 3d720ccade546..d1cf2b96817d2 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/lib/tracker.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/lib/tracker.ts @@ -10,8 +10,8 @@ import { REPORTING_TRANSACTION_TYPE } from '../../../../common/constants'; interface PdfTracker { setByteLength: (byteLength: number) => void; - startLayout: () => void; - endLayout: () => void; + setCpuUsage: (cpu: number) => void; + setMemoryUsage: (memory: number) => void; startScreenshots: () => void; endScreenshots: () => void; startSetup: () => void; @@ -35,7 +35,6 @@ interface ApmSpan { export function getTracker(): PdfTracker { const apmTrans = apm.startTransaction('generate-pdf', REPORTING_TRANSACTION_TYPE); - let apmLayout: ApmSpan | null = null; let apmScreenshots: ApmSpan | null = null; let apmSetup: ApmSpan | null = null; let apmAddImage: ApmSpan | null = null; @@ -43,12 +42,6 @@ export function getTracker(): PdfTracker { let apmGetBuffer: ApmSpan | null = null; return { - startLayout() { - apmLayout = apmTrans?.startSpan('create-layout', SPANTYPE_SETUP) || null; - }, - endLayout() { - if (apmLayout) apmLayout.end(); - }, startScreenshots() { apmScreenshots = apmTrans?.startSpan('screenshots-pipeline', SPANTYPE_SETUP) || null; }, @@ -82,6 +75,12 @@ export function getTracker(): PdfTracker { setByteLength(byteLength: number) { apmTrans?.setLabel('byte-length', byteLength, false); }, + setCpuUsage(cpu: number) { + apmTrans?.setLabel('cpu', cpu, false); + }, + setMemoryUsage(memory: number) { + apmTrans?.setLabel('memory', memory, false); + }, end() { if (apmTrans) apmTrans.end(); }, diff --git a/x-pack/plugins/reporting/server/lib/layouts/create_layout.ts b/x-pack/plugins/reporting/server/lib/layouts/create_layout.ts deleted file mode 100644 index f62ee6ab720c3..0000000000000 --- a/x-pack/plugins/reporting/server/lib/layouts/create_layout.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { LAYOUT_TYPES } from '../../../common/constants'; -import { CaptureConfig } from '../../types'; -import { LayoutInstance, LayoutParams, LayoutTypes } from './'; -import { CanvasLayout } from './canvas_layout'; -import { PreserveLayout } from './preserve_layout'; -import { PrintLayout } from './print_layout'; - -export function createLayout( - captureConfig: CaptureConfig, - layoutParams?: LayoutParams -): LayoutInstance { - if (layoutParams && layoutParams.dimensions && layoutParams.id === LAYOUT_TYPES.PRESERVE_LAYOUT) { - return new PreserveLayout(layoutParams.dimensions); - } - - if (layoutParams && layoutParams.dimensions && layoutParams.id === LayoutTypes.CANVAS) { - return new CanvasLayout(layoutParams.dimensions); - } - - // layoutParams is optional as PrintLayout doesn't use it - return new PrintLayout(captureConfig); -} diff --git a/x-pack/plugins/reporting/server/lib/layouts/index.ts b/x-pack/plugins/reporting/server/lib/layouts/index.ts deleted file mode 100644 index daff568ab0067..0000000000000 --- a/x-pack/plugins/reporting/server/lib/layouts/index.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { LevelLogger } from '../'; -import { Size } from '../../../common/types'; -import { HeadlessChromiumDriver } from '../../browsers'; -import type { Layout } from './layout'; - -export interface LayoutSelectorDictionary { - screenshot: string; - renderComplete: string; - renderError: string; - renderErrorAttribute: string; - itemsCountAttribute: string; - timefilterDurationAttribute: string; -} - -export type { LayoutParams, PageSizeParams, PdfImageSize, Size } from '../../../common/types'; -export { CanvasLayout } from './canvas_layout'; -export { createLayout } from './create_layout'; -export type { Layout } from './layout'; -export { PreserveLayout } from './preserve_layout'; -export { PrintLayout } from './print_layout'; - -export const LayoutTypes = { - PRESERVE_LAYOUT: 'preserve_layout', - PRINT: 'print', - CANVAS: 'canvas', // no margins or branding in the layout -}; - -export const getDefaultLayoutSelectors = (): LayoutSelectorDictionary => ({ - screenshot: '[data-shared-items-container]', - renderComplete: '[data-shared-item]', - renderError: '[data-render-error]', - renderErrorAttribute: 'data-render-error', - itemsCountAttribute: 'data-shared-items-count', - timefilterDurationAttribute: 'data-shared-timefilter-duration', -}); - -interface LayoutSelectors { - // Fields that are not part of Layout: the instances - // independently implement these fields on their own - selectors: LayoutSelectorDictionary; - positionElements?: (browser: HeadlessChromiumDriver, logger: LevelLogger) => Promise; -} - -export type LayoutInstance = Layout & LayoutSelectors & Partial; diff --git a/x-pack/plugins/reporting/server/lib/screenshots/get_number_of_items.test.ts b/x-pack/plugins/reporting/server/lib/screenshots/get_number_of_items.test.ts deleted file mode 100644 index f160fcb8b27ad..0000000000000 --- a/x-pack/plugins/reporting/server/lib/screenshots/get_number_of_items.test.ts +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { set } from 'lodash'; -import { durationToNumber } from '../../../common/schema_utils'; -import { HeadlessChromiumDriver } from '../../browsers'; -import { - createMockBrowserDriverFactory, - createMockConfig, - createMockConfigSchema, - createMockLayoutInstance, - createMockLevelLogger, - createMockReportingCore, -} from '../../test_helpers'; -import { CaptureConfig } from '../../types'; -import { LayoutInstance } from '../layouts'; -import { LevelLogger } from '../level_logger'; -import { getNumberOfItems } from './get_number_of_items'; - -describe('getNumberOfItems', () => { - let captureConfig: CaptureConfig; - let layout: LayoutInstance; - let logger: jest.Mocked; - let browser: HeadlessChromiumDriver; - let timeout: number; - - beforeEach(async () => { - const schema = createMockConfigSchema(set({}, 'capture.timeouts.waitForElements', 0)); - const config = createMockConfig(schema); - const core = await createMockReportingCore(schema); - - captureConfig = config.get('capture'); - layout = createMockLayoutInstance(captureConfig); - logger = createMockLevelLogger(); - timeout = durationToNumber(captureConfig.timeouts.waitForElements); - - await createMockBrowserDriverFactory(core, logger, { - evaluate: jest.fn( - async unknown>({ - fn, - args, - }: { - fn: T; - args: Parameters; - }) => fn(...args) - ), - getCreatePage: (driver) => { - browser = driver; - - return jest.fn(); - }, - }); - }); - - afterEach(() => { - document.body.innerHTML = ''; - }); - - it('should determine the number of items by attribute', async () => { - document.body.innerHTML = ` -
- `; - - await expect(getNumberOfItems(timeout, browser, layout, logger)).resolves.toBe(10); - }); - - it('should determine the number of items by selector ', async () => { - document.body.innerHTML = ` - - - - `; - - await expect(getNumberOfItems(timeout, browser, layout, logger)).resolves.toBe(3); - }); - - it('should fall back to the selector when the attribute is empty', async () => { - document.body.innerHTML = ` -
- - - `; - - await expect(getNumberOfItems(timeout, browser, layout, logger)).resolves.toBe(2); - }); -}); diff --git a/x-pack/plugins/reporting/server/lib/screenshots/get_render_errors.test.ts b/x-pack/plugins/reporting/server/lib/screenshots/get_render_errors.test.ts deleted file mode 100644 index d29c0936bfceb..0000000000000 --- a/x-pack/plugins/reporting/server/lib/screenshots/get_render_errors.test.ts +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { HeadlessChromiumDriver } from '../../browsers'; -import { - createMockBrowserDriverFactory, - createMockConfig, - createMockConfigSchema, - createMockLayoutInstance, - createMockLevelLogger, - createMockReportingCore, -} from '../../test_helpers'; -import { CaptureConfig } from '../../types'; -import { LayoutInstance } from '../layouts'; -import { LevelLogger } from '../level_logger'; -import { getRenderErrors } from './get_render_errors'; - -describe('getRenderErrors', () => { - let captureConfig: CaptureConfig; - let layout: LayoutInstance; - let logger: jest.Mocked; - let browser: HeadlessChromiumDriver; - - beforeEach(async () => { - const schema = createMockConfigSchema(); - const config = createMockConfig(schema); - const core = await createMockReportingCore(schema); - - captureConfig = config.get('capture'); - layout = createMockLayoutInstance(captureConfig); - logger = createMockLevelLogger(); - - await createMockBrowserDriverFactory(core, logger, { - evaluate: jest.fn( - async unknown>({ - fn, - args, - }: { - fn: T; - args: Parameters; - }) => fn(...args) - ), - getCreatePage: (driver) => { - browser = driver; - - return jest.fn(); - }, - }); - }); - - afterEach(() => { - document.body.innerHTML = ''; - }); - - it('should extract the error messages', async () => { - document.body.innerHTML = ` -
-
-
-
- `; - - await expect(getRenderErrors(browser, layout, logger)).resolves.toEqual([ - 'a test error', - 'a test error', - 'a test error', - 'a test error', - ]); - }); - - it('should extract the error messages, even when there are none', async () => { - document.body.innerHTML = ` - - `; - - await expect(getRenderErrors(browser, layout, logger)).resolves.toEqual(undefined); - }); -}); diff --git a/x-pack/plugins/reporting/server/lib/screenshots/get_time_range.test.ts b/x-pack/plugins/reporting/server/lib/screenshots/get_time_range.test.ts deleted file mode 100644 index 003d1dc254a2a..0000000000000 --- a/x-pack/plugins/reporting/server/lib/screenshots/get_time_range.test.ts +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { HeadlessChromiumDriver } from '../../browsers'; -import { - createMockBrowserDriverFactory, - createMockConfig, - createMockConfigSchema, - createMockLayoutInstance, - createMockLevelLogger, - createMockReportingCore, -} from '../../test_helpers'; -import { LayoutInstance } from '../layouts'; -import { LevelLogger } from '../level_logger'; -import { getTimeRange } from './get_time_range'; - -describe('getTimeRange', () => { - let layout: LayoutInstance; - let logger: jest.Mocked; - let browser: HeadlessChromiumDriver; - - beforeEach(async () => { - const schema = createMockConfigSchema(); - const config = createMockConfig(schema); - const captureConfig = config.get('capture'); - const core = await createMockReportingCore(schema); - - layout = createMockLayoutInstance(captureConfig); - logger = createMockLevelLogger(); - - await createMockBrowserDriverFactory(core, logger, { - evaluate: jest.fn( - async unknown>({ - fn, - args, - }: { - fn: T; - args: Parameters; - }) => fn(...args) - ), - getCreatePage: (driver) => { - browser = driver; - - return jest.fn(); - }, - }); - }); - - afterEach(() => { - document.body.innerHTML = ''; - }); - - it('should return null when there is no duration element', async () => { - await expect(getTimeRange(browser, layout, logger)).resolves.toBeNull(); - }); - - it('should return null when duration attrbute is empty', async () => { - document.body.innerHTML = ` -
- `; - - await expect(getTimeRange(browser, layout, logger)).resolves.toBeNull(); - }); - - it('should return duration', async () => { - document.body.innerHTML = ` -
- `; - - await expect(getTimeRange(browser, layout, logger)).resolves.toBe('10'); - }); -}); diff --git a/x-pack/plugins/reporting/server/lib/screenshots/index.ts b/x-pack/plugins/reporting/server/lib/screenshots/index.ts deleted file mode 100644 index 2b8a0d6207a9b..0000000000000 --- a/x-pack/plugins/reporting/server/lib/screenshots/index.ts +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { LevelLogger } from '../'; -import { UrlOrUrlLocatorTuple } from '../../../common/types'; -import { ConditionalHeaders } from '../../export_types/common'; -import { LayoutInstance } from '../layouts'; - -export { getScreenshots$ } from './observable'; - -export interface PhaseInstance { - timeoutValue: number; - configValue: string; - label: string; -} - -export interface PhaseTimeouts { - openUrl: PhaseInstance; - waitForElements: PhaseInstance; - renderComplete: PhaseInstance; - loadDelay: number; -} - -export interface ScreenshotObservableOpts { - logger: LevelLogger; - urlsOrUrlLocatorTuples: UrlOrUrlLocatorTuple[]; - conditionalHeaders: ConditionalHeaders; - layout: LayoutInstance; - browserTimezone?: string; -} - -export interface AttributesMap { - [key: string]: string | null; -} - -export interface ElementPosition { - boundingClientRect: { - // modern browsers support x/y, but older ones don't - top: number; - left: number; - width: number; - height: number; - }; - scroll: { - x: number; - y: number; - }; -} - -export interface ElementsPositionAndAttribute { - position: ElementPosition; - attributes: AttributesMap; -} - -export interface Screenshot { - data: Buffer; - title: string | null; - description: string | null; -} - -export interface PageSetupResults { - elementsPositionAndAttributes: ElementsPositionAndAttribute[] | null; - timeRange: string | null; - error?: Error; -} - -export interface ScreenshotResults { - timeRange: string | null; - screenshots: Screenshot[]; - error?: Error; - - /** - * Individual visualizations might encounter errors at runtime. If there are any they are added to this - * field. Any text captured here is intended to be shown to the user for debugging purposes, reporting - * does no further sanitization on these strings. - */ - renderErrors?: string[]; - elementsPositionAndAttributes?: ElementsPositionAndAttribute[]; // NOTE: for testing -} diff --git a/x-pack/plugins/reporting/server/lib/screenshots/observable.test.ts b/x-pack/plugins/reporting/server/lib/screenshots/observable.test.ts deleted file mode 100644 index 3071ecb54dc26..0000000000000 --- a/x-pack/plugins/reporting/server/lib/screenshots/observable.test.ts +++ /dev/null @@ -1,490 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -jest.mock('puppeteer', () => ({ - launch: () => ({ - // Fixme needs event emitters - newPage: () => ({ - emulateTimezone: jest.fn(), - setDefaultTimeout: jest.fn(), - }), - process: jest.fn(), - close: jest.fn(), - }), -})); - -import moment from 'moment'; -import * as Rx from 'rxjs'; -import { ReportingCore } from '../..'; -import { HeadlessChromiumDriver } from '../../browsers'; -import { ConditionalHeaders } from '../../export_types/common'; -import { - createMockBrowserDriverFactory, - createMockConfig, - createMockConfigSchema, - createMockLayoutInstance, - createMockLevelLogger, - createMockReportingCore, -} from '../../test_helpers'; -import * as contexts from './constants'; -import { getScreenshots$ } from './'; - -/* - * Mocks - */ -const logger = createMockLevelLogger(); - -const mockSchema = createMockConfigSchema({ - capture: { - loadDelay: moment.duration(2, 's'), - timeouts: { - openUrl: moment.duration(2, 'm'), - waitForElements: moment.duration(20, 's'), - renderComplete: moment.duration(10, 's'), - }, - }, -}); -const mockConfig = createMockConfig(mockSchema); -const captureConfig = mockConfig.get('capture'); -const mockLayout = createMockLayoutInstance(captureConfig); - -let core: ReportingCore; - -/* - * Tests - */ -describe('Screenshot Observable Pipeline', () => { - let mockBrowserDriverFactory: any; - - beforeEach(async () => { - core = await createMockReportingCore(mockSchema); - mockBrowserDriverFactory = await createMockBrowserDriverFactory(core, logger, {}); - }); - - it('pipelines a single url into screenshot and timeRange', async () => { - const result = await getScreenshots$(captureConfig, mockBrowserDriverFactory, { - logger, - urlsOrUrlLocatorTuples: ['/welcome/home/start/index.htm'], - conditionalHeaders: {} as ConditionalHeaders, - layout: mockLayout, - browserTimezone: 'UTC', - }).toPromise(); - - expect(result).toMatchInlineSnapshot(` - Array [ - Object { - "elementsPositionAndAttributes": Array [ - Object { - "attributes": Object { - "description": "Default ", - "title": "Default Mock Title", - }, - "position": Object { - "boundingClientRect": Object { - "height": 600, - "left": 0, - "top": 0, - "width": 800, - }, - "scroll": Object { - "x": 0, - "y": 0, - }, - }, - }, - ], - "error": undefined, - "screenshots": Array [ - Object { - "data": Object { - "data": Array [ - 115, - 99, - 114, - 101, - 101, - 110, - 115, - 104, - 111, - 116, - ], - "type": "Buffer", - }, - "description": "Default ", - "title": "Default Mock Title", - }, - ], - "timeRange": "Default GetTimeRange Result", - }, - ] - `); - }); - - it('pipelines multiple urls into', async () => { - // mock implementations - const mockScreenshot = jest.fn(async () => Buffer.from('some screenshots')); - const mockOpen = jest.fn(); - - // mocks - mockBrowserDriverFactory = await createMockBrowserDriverFactory(core, logger, { - screenshot: mockScreenshot, - open: mockOpen, - }); - - // test - const result = await getScreenshots$(captureConfig, mockBrowserDriverFactory, { - logger, - urlsOrUrlLocatorTuples: [ - '/welcome/home/start/index2.htm', - '/welcome/home/start/index.php3?page=./home.php', - ], - conditionalHeaders: {} as ConditionalHeaders, - layout: mockLayout, - browserTimezone: 'UTC', - }).toPromise(); - - expect(result).toMatchInlineSnapshot(` - Array [ - Object { - "elementsPositionAndAttributes": Array [ - Object { - "attributes": Object { - "description": "Default ", - "title": "Default Mock Title", - }, - "position": Object { - "boundingClientRect": Object { - "height": 600, - "left": 0, - "top": 0, - "width": 800, - }, - "scroll": Object { - "x": 0, - "y": 0, - }, - }, - }, - ], - "error": undefined, - "screenshots": Array [ - Object { - "data": Object { - "data": Array [ - 115, - 111, - 109, - 101, - 32, - 115, - 99, - 114, - 101, - 101, - 110, - 115, - 104, - 111, - 116, - 115, - ], - "type": "Buffer", - }, - "description": "Default ", - "title": "Default Mock Title", - }, - ], - "timeRange": "Default GetTimeRange Result", - }, - Object { - "elementsPositionAndAttributes": Array [ - Object { - "attributes": Object { - "description": "Default ", - "title": "Default Mock Title", - }, - "position": Object { - "boundingClientRect": Object { - "height": 600, - "left": 0, - "top": 0, - "width": 800, - }, - "scroll": Object { - "x": 0, - "y": 0, - }, - }, - }, - ], - "error": undefined, - "screenshots": Array [ - Object { - "data": Object { - "data": Array [ - 115, - 111, - 109, - 101, - 32, - 115, - 99, - 114, - 101, - 101, - 110, - 115, - 104, - 111, - 116, - 115, - ], - "type": "Buffer", - }, - "description": "Default ", - "title": "Default Mock Title", - }, - ], - "timeRange": "Default GetTimeRange Result", - }, - ] - `); - - // ensures the correct selectors are waited on for multi URL jobs - expect(mockOpen.mock.calls.length).toBe(2); - - const firstSelector = mockOpen.mock.calls[0][1].waitForSelector; - expect(firstSelector).toBe('.kbnAppWrapper'); - - const secondSelector = mockOpen.mock.calls[1][1].waitForSelector; - expect(secondSelector).toBe('[data-shared-page="2"]'); - }); - - describe('error handling', () => { - it('recovers if waitForSelector fails', async () => { - // mock implementations - const mockWaitForSelector = jest.fn().mockImplementation((selectorArg: string) => { - throw new Error('Mock error!'); - }); - - // mocks - mockBrowserDriverFactory = await createMockBrowserDriverFactory(core, logger, { - waitForSelector: mockWaitForSelector, - }); - - // test - const getScreenshot = async () => { - return await getScreenshots$(captureConfig, mockBrowserDriverFactory, { - logger, - urlsOrUrlLocatorTuples: [ - '/welcome/home/start/index2.htm', - '/welcome/home/start/index.php3?page=./home.php3', - ], - conditionalHeaders: {} as ConditionalHeaders, - layout: mockLayout, - browserTimezone: 'UTC', - }).toPromise(); - }; - - await expect(getScreenshot()).resolves.toMatchInlineSnapshot(` - Array [ - Object { - "elementsPositionAndAttributes": Array [ - Object { - "attributes": Object {}, - "position": Object { - "boundingClientRect": Object { - "height": 100, - "left": 0, - "top": 0, - "width": 100, - }, - "scroll": Object { - "x": 0, - "y": 0, - }, - }, - }, - ], - "error": [Error: The "wait for elements" phase encountered an error: Error: An error occurred when trying to read the page for visualization panel info: Error: Mock error!], - "screenshots": Array [ - Object { - "data": Object { - "data": Array [ - 115, - 99, - 114, - 101, - 101, - 110, - 115, - 104, - 111, - 116, - ], - "type": "Buffer", - }, - "description": undefined, - "title": undefined, - }, - ], - "timeRange": null, - }, - Object { - "elementsPositionAndAttributes": Array [ - Object { - "attributes": Object {}, - "position": Object { - "boundingClientRect": Object { - "height": 100, - "left": 0, - "top": 0, - "width": 100, - }, - "scroll": Object { - "x": 0, - "y": 0, - }, - }, - }, - ], - "error": [Error: The "wait for elements" phase encountered an error: Error: An error occurred when trying to read the page for visualization panel info: Error: Mock error!], - "screenshots": Array [ - Object { - "data": Object { - "data": Array [ - 115, - 99, - 114, - 101, - 101, - 110, - 115, - 104, - 111, - 116, - ], - "type": "Buffer", - }, - "description": undefined, - "title": undefined, - }, - ], - "timeRange": null, - }, - ] - `); - }); - - it('observes page exit', async () => { - // mocks - const mockGetCreatePage = (driver: HeadlessChromiumDriver) => - jest - .fn() - .mockImplementation(() => - Rx.of({ driver, exit$: Rx.throwError('Instant timeout has fired!') }) - ); - - const mockWaitForSelector = jest.fn().mockImplementation((selectorArg: string) => { - return Rx.never().toPromise(); - }); - - mockBrowserDriverFactory = await createMockBrowserDriverFactory(core, logger, { - getCreatePage: mockGetCreatePage, - waitForSelector: mockWaitForSelector, - }); - - // test - const getScreenshot = async () => { - return await getScreenshots$(captureConfig, mockBrowserDriverFactory, { - logger, - urlsOrUrlLocatorTuples: ['/welcome/home/start/index.php3?page=./home.php3'], - conditionalHeaders: {} as ConditionalHeaders, - layout: mockLayout, - browserTimezone: 'UTC', - }).toPromise(); - }; - - await expect(getScreenshot()).rejects.toMatchInlineSnapshot(`"Instant timeout has fired!"`); - }); - - it(`uses defaults for element positions and size when Kibana page is not ready`, async () => { - // mocks - const mockBrowserEvaluate = jest.fn(); - mockBrowserEvaluate.mockImplementation(() => { - const lastCallIndex = mockBrowserEvaluate.mock.calls.length - 1; - const { context: mockCall } = mockBrowserEvaluate.mock.calls[lastCallIndex][1]; - - if (mockCall === contexts.CONTEXT_ELEMENTATTRIBUTES) { - return Promise.resolve(null); - } else { - return Promise.resolve(); - } - }); - mockBrowserDriverFactory = await createMockBrowserDriverFactory(core, logger, { - evaluate: mockBrowserEvaluate, - }); - mockLayout.getViewport = () => null; - - const screenshots = await getScreenshots$(captureConfig, mockBrowserDriverFactory, { - logger, - urlsOrUrlLocatorTuples: ['/welcome/home/start/index.php3?page=./home.php3'], - conditionalHeaders: {} as ConditionalHeaders, - layout: mockLayout, - browserTimezone: 'UTC', - }).toPromise(); - - expect(screenshots).toMatchInlineSnapshot(` - Array [ - Object { - "elementsPositionAndAttributes": Array [ - Object { - "attributes": Object {}, - "position": Object { - "boundingClientRect": Object { - "height": 1200, - "left": 0, - "top": 0, - "width": 1800, - }, - "scroll": Object { - "x": 0, - "y": 0, - }, - }, - }, - ], - "error": undefined, - "screenshots": Array [ - Object { - "data": Object { - "data": Array [ - 115, - 99, - 114, - 101, - 101, - 110, - 115, - 104, - 111, - 116, - ], - "type": "Buffer", - }, - "description": undefined, - "title": undefined, - }, - ], - "timeRange": undefined, - }, - ] - `); - }); - }); -}); diff --git a/x-pack/plugins/reporting/server/lib/screenshots/observable.ts b/x-pack/plugins/reporting/server/lib/screenshots/observable.ts deleted file mode 100644 index 8ba2a125a5504..0000000000000 --- a/x-pack/plugins/reporting/server/lib/screenshots/observable.ts +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import apm from 'elastic-apm-node'; -import * as Rx from 'rxjs'; -import { catchError, concatMap, first, mergeMap, take, takeUntil, toArray } from 'rxjs/operators'; -import { durationToNumber } from '../../../common/schema_utils'; -import { REPORTING_TRANSACTION_TYPE } from '../../../common/constants'; -import { HeadlessChromiumDriverFactory } from '../../browsers'; -import { CaptureConfig } from '../../types'; -import { - ElementPosition, - ElementsPositionAndAttribute, - PageSetupResults, - ScreenshotObservableOpts, - ScreenshotResults, -} from './'; -import { ScreenshotObservableHandler } from './observable_handler'; - -export type { ElementPosition, ElementsPositionAndAttribute, ScreenshotResults }; - -const getTimeouts = (captureConfig: CaptureConfig) => ({ - openUrl: { - timeoutValue: durationToNumber(captureConfig.timeouts.openUrl), - configValue: `xpack.reporting.capture.timeouts.openUrl`, - label: 'open URL', - }, - waitForElements: { - timeoutValue: durationToNumber(captureConfig.timeouts.waitForElements), - configValue: `xpack.reporting.capture.timeouts.waitForElements`, - label: 'wait for elements', - }, - renderComplete: { - timeoutValue: durationToNumber(captureConfig.timeouts.renderComplete), - configValue: `xpack.reporting.capture.timeouts.renderComplete`, - label: 'render complete', - }, - loadDelay: durationToNumber(captureConfig.loadDelay), -}); - -export function getScreenshots$( - captureConfig: CaptureConfig, - browserDriverFactory: HeadlessChromiumDriverFactory, - opts: ScreenshotObservableOpts -): Rx.Observable { - const apmTrans = apm.startTransaction('screenshot-pipeline', REPORTING_TRANSACTION_TYPE); - const apmCreatePage = apmTrans?.startSpan('create-page', 'wait'); - const { browserTimezone, logger } = opts; - - return browserDriverFactory.createPage({ browserTimezone }, logger).pipe( - mergeMap(({ driver, exit$ }) => { - apmCreatePage?.end(); - exit$.subscribe({ error: () => apmTrans?.end() }); - - const screen = new ScreenshotObservableHandler(driver, opts, getTimeouts(captureConfig)); - - return Rx.from(opts.urlsOrUrlLocatorTuples).pipe( - concatMap((urlOrUrlLocatorTuple, index) => - screen.setupPage(index, urlOrUrlLocatorTuple, apmTrans).pipe( - catchError((err) => { - screen.checkPageIsOpen(); // this fails the job if the browser has closed - - logger.error(err); - return Rx.of({ ...defaultSetupResult, error: err }); // allow failover screenshot capture - }), - takeUntil(exit$), - screen.getScreenshots() - ) - ), - take(opts.urlsOrUrlLocatorTuples.length), - toArray() - ); - }), - first() - ); -} - -const defaultSetupResult: PageSetupResults = { - elementsPositionAndAttributes: null, - timeRange: null, -}; diff --git a/x-pack/plugins/reporting/server/lib/screenshots/observable_handler.test.ts b/x-pack/plugins/reporting/server/lib/screenshots/observable_handler.test.ts deleted file mode 100644 index cb0a513992722..0000000000000 --- a/x-pack/plugins/reporting/server/lib/screenshots/observable_handler.test.ts +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as Rx from 'rxjs'; -import { first, map } from 'rxjs/operators'; -import { HeadlessChromiumDriver } from '../../browsers'; -import { ReportingConfigType } from '../../config'; -import { ConditionalHeaders } from '../../export_types/common'; -import { - createMockBrowserDriverFactory, - createMockConfigSchema, - createMockLayoutInstance, - createMockLevelLogger, - createMockReportingCore, -} from '../../test_helpers'; -import { LayoutInstance } from '../layouts'; -import { PhaseTimeouts, ScreenshotObservableOpts } from './'; -import { ScreenshotObservableHandler } from './observable_handler'; - -const logger = createMockLevelLogger(); - -describe('ScreenshotObservableHandler', () => { - let captureConfig: ReportingConfigType['capture']; - let layout: LayoutInstance; - let conditionalHeaders: ConditionalHeaders; - let opts: ScreenshotObservableOpts; - let timeouts: PhaseTimeouts; - let driver: HeadlessChromiumDriver; - - beforeAll(async () => { - captureConfig = { - timeouts: { - openUrl: 30000, - waitForElements: 30000, - renderComplete: 30000, - }, - loadDelay: 5000, - } as unknown as typeof captureConfig; - - layout = createMockLayoutInstance(captureConfig); - - conditionalHeaders = { - headers: { testHeader: 'testHeadValue' }, - conditions: {} as unknown as ConditionalHeaders['conditions'], - }; - - opts = { - conditionalHeaders, - layout, - logger, - urlsOrUrlLocatorTuples: [], - }; - - timeouts = { - openUrl: { - timeoutValue: 60000, - configValue: `xpack.reporting.capture.timeouts.openUrl`, - label: 'open URL', - }, - waitForElements: { - timeoutValue: 30000, - configValue: `xpack.reporting.capture.timeouts.waitForElements`, - label: 'wait for elements', - }, - renderComplete: { - timeoutValue: 60000, - configValue: `xpack.reporting.capture.timeouts.renderComplete`, - label: 'render complete', - }, - loadDelay: 5000, - }; - }); - - beforeEach(async () => { - const reporting = await createMockReportingCore(createMockConfigSchema()); - const driverFactory = await createMockBrowserDriverFactory(reporting, logger); - ({ driver } = await driverFactory.createPage({}, logger).pipe(first()).toPromise()); - driver.isPageOpen = jest.fn().mockImplementation(() => true); - }); - - describe('waitUntil', () => { - it('catches TimeoutError and references the timeout config in a custom message', async () => { - const screenshots = new ScreenshotObservableHandler(driver, opts, timeouts); - const test$ = Rx.interval(1000).pipe( - screenshots.waitUntil({ - timeoutValue: 200, - configValue: 'test.config.value', - label: 'Test Config', - }) - ); - - const testPipeline = () => test$.toPromise(); - await expect(testPipeline).rejects.toMatchInlineSnapshot( - `[Error: The "Test Config" phase took longer than 0.2 seconds. You may need to increase "test.config.value"]` - ); - }); - - it('catches other Errors and explains where they were thrown', async () => { - const screenshots = new ScreenshotObservableHandler(driver, opts, timeouts); - const test$ = Rx.throwError(new Error(`Test Error to Throw`)).pipe( - screenshots.waitUntil({ - timeoutValue: 200, - configValue: 'test.config.value', - label: 'Test Config', - }) - ); - - const testPipeline = () => test$.toPromise(); - await expect(testPipeline).rejects.toMatchInlineSnapshot( - `[Error: The "Test Config" phase encountered an error: Error: Test Error to Throw]` - ); - }); - - it('is a pass-through if there is no Error', async () => { - const screenshots = new ScreenshotObservableHandler(driver, opts, timeouts); - const test$ = Rx.of('nice to see you').pipe( - screenshots.waitUntil({ - timeoutValue: 20, - configValue: 'xxxxxxxxxxxxxxxxx', - label: 'xxxxxxxxxxx', - }) - ); - - await expect(test$.toPromise()).resolves.toBe(`nice to see you`); - }); - }); - - describe('checkPageIsOpen', () => { - it('throws a decorated Error when page is not open', async () => { - driver.isPageOpen = jest.fn().mockImplementation(() => false); - const screenshots = new ScreenshotObservableHandler(driver, opts, timeouts); - const test$ = Rx.of(234455).pipe( - map((input) => { - screenshots.checkPageIsOpen(); - return input; - }) - ); - - await expect(test$.toPromise()).rejects.toMatchInlineSnapshot( - `[Error: Browser was closed unexpectedly! Check the server logs for more info.]` - ); - }); - - it('is a pass-through when the page is open', async () => { - const screenshots = new ScreenshotObservableHandler(driver, opts, timeouts); - const test$ = Rx.of(234455).pipe( - map((input) => { - screenshots.checkPageIsOpen(); - return input; - }) - ); - - await expect(test$.toPromise()).resolves.toBe(234455); - }); - }); -}); diff --git a/x-pack/plugins/reporting/server/lib/screenshots/observable_handler.ts b/x-pack/plugins/reporting/server/lib/screenshots/observable_handler.ts deleted file mode 100644 index c241a529818fa..0000000000000 --- a/x-pack/plugins/reporting/server/lib/screenshots/observable_handler.ts +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import apm from 'elastic-apm-node'; -import * as Rx from 'rxjs'; -import { catchError, mergeMap, switchMapTo, timeoutWith } from 'rxjs/operators'; -import { numberToDuration } from '../../../common/schema_utils'; -import { UrlOrUrlLocatorTuple } from '../../../common/types'; -import { HeadlessChromiumDriver } from '../../browsers'; -import { getChromiumDisconnectedError } from '../../browsers/chromium'; -import { - PageSetupResults, - PhaseInstance, - PhaseTimeouts, - ScreenshotObservableOpts, - ScreenshotResults, -} from './'; -import { getElementPositionAndAttributes } from './get_element_position_data'; -import { getNumberOfItems } from './get_number_of_items'; -import { getRenderErrors } from './get_render_errors'; -import { getScreenshots } from './get_screenshots'; -import { getTimeRange } from './get_time_range'; -import { injectCustomCss } from './inject_css'; -import { openUrl } from './open_url'; -import { waitForRenderComplete } from './wait_for_render'; -import { waitForVisualizations } from './wait_for_visualizations'; - -export class ScreenshotObservableHandler { - private conditionalHeaders: ScreenshotObservableOpts['conditionalHeaders']; - private layout: ScreenshotObservableOpts['layout']; - private logger: ScreenshotObservableOpts['logger']; - - constructor( - private readonly driver: HeadlessChromiumDriver, - opts: ScreenshotObservableOpts, - private timeouts: PhaseTimeouts - ) { - this.conditionalHeaders = opts.conditionalHeaders; - this.layout = opts.layout; - this.logger = opts.logger; - } - - /* - * Decorates a TimeoutError with context of the phase that has timed out. - */ - public waitUntil(phase: PhaseInstance) { - const { timeoutValue, label, configValue } = phase; - - return (source: Rx.Observable) => - source.pipe( - catchError((error) => { - throw new Error(`The "${label}" phase encountered an error: ${error}`); - }), - timeoutWith( - timeoutValue, - Rx.throwError( - new Error( - `The "${label}" phase took longer than ${numberToDuration( - timeoutValue - ).asSeconds()} seconds. You may need to increase "${configValue}"` - ) - ) - ) - ); - } - - private openUrl(index: number, urlOrUrlLocatorTuple: UrlOrUrlLocatorTuple) { - return Rx.defer(() => - openUrl( - this.timeouts.openUrl.timeoutValue, - this.driver, - index, - urlOrUrlLocatorTuple, - this.conditionalHeaders, - this.layout, - this.logger - ) - ).pipe(this.waitUntil(this.timeouts.openUrl)); - } - - private waitForElements() { - const driver = this.driver; - const waitTimeout = this.timeouts.waitForElements.timeoutValue; - - return Rx.defer(() => getNumberOfItems(waitTimeout, driver, this.layout, this.logger)).pipe( - mergeMap((itemsCount) => { - // set the viewport to the dimentions from the job, to allow elements to flow into the expected layout - const viewport = this.layout.getViewport(itemsCount) || getDefaultViewPort(); - - return Rx.forkJoin([ - driver.setViewport(viewport, this.logger), - waitForVisualizations(waitTimeout, driver, itemsCount, this.layout, this.logger), - ]); - }), - this.waitUntil(this.timeouts.waitForElements) - ); - } - - private completeRender(apmTrans: apm.Transaction | null) { - const driver = this.driver; - const layout = this.layout; - const logger = this.logger; - - return Rx.defer(async () => { - // Waiting till _after_ elements have rendered before injecting our CSS - // allows for them to be displayed properly in many cases - await injectCustomCss(driver, layout, logger); - - const apmPositionElements = apmTrans?.startSpan('position-elements', 'correction'); - // position panel elements for print layout - await layout.positionElements?.(driver, logger); - apmPositionElements?.end(); - - await waitForRenderComplete(this.timeouts.loadDelay, driver, layout, logger); - }).pipe( - mergeMap(() => - Rx.forkJoin({ - timeRange: getTimeRange(driver, layout, logger), - elementsPositionAndAttributes: getElementPositionAndAttributes(driver, layout, logger), - renderErrors: getRenderErrors(driver, layout, logger), - }) - ), - this.waitUntil(this.timeouts.renderComplete) - ); - } - - public setupPage( - index: number, - urlOrUrlLocatorTuple: UrlOrUrlLocatorTuple, - apmTrans: apm.Transaction | null - ) { - return this.openUrl(index, urlOrUrlLocatorTuple).pipe( - switchMapTo(this.waitForElements()), - switchMapTo(this.completeRender(apmTrans)) - ); - } - - public getScreenshots() { - return (withRenderComplete: Rx.Observable) => - withRenderComplete.pipe( - mergeMap(async (data: PageSetupResults): Promise => { - this.checkPageIsOpen(); // fail the report job if the browser has closed - - const elements = - data.elementsPositionAndAttributes ?? - getDefaultElementPosition(this.layout.getViewport(1)); - const screenshots = await getScreenshots(this.driver, elements, this.logger); - const { timeRange, error: setupError } = data; - - return { - timeRange, - screenshots, - error: setupError, - elementsPositionAndAttributes: elements, - }; - }) - ); - } - - public checkPageIsOpen() { - if (!this.driver.isPageOpen()) { - throw getChromiumDisconnectedError(); - } - } -} - -const DEFAULT_SCREENSHOT_CLIP_HEIGHT = 1200; -const DEFAULT_SCREENSHOT_CLIP_WIDTH = 1800; - -const getDefaultElementPosition = (dimensions: { height?: number; width?: number } | null) => { - const height = dimensions?.height || DEFAULT_SCREENSHOT_CLIP_HEIGHT; - const width = dimensions?.width || DEFAULT_SCREENSHOT_CLIP_WIDTH; - - return [ - { - position: { - boundingClientRect: { top: 0, left: 0, height, width }, - scroll: { x: 0, y: 0 }, - }, - attributes: {}, - }, - ]; -}; - -/* - * If Kibana is showing a non-HTML error message, the viewport might not be - * provided by the browser. - */ -const getDefaultViewPort = () => ({ - height: DEFAULT_SCREENSHOT_CLIP_HEIGHT, - width: DEFAULT_SCREENSHOT_CLIP_WIDTH, - zoom: 1, -}); diff --git a/x-pack/plugins/reporting/server/lib/store/mapping.ts b/x-pack/plugins/reporting/server/lib/store/mapping.ts index a43b4494fe913..667648d3372c5 100644 --- a/x-pack/plugins/reporting/server/lib/store/mapping.ts +++ b/x-pack/plugins/reporting/server/lib/store/mapping.ts @@ -34,7 +34,6 @@ export const mapping = { }, }, }, - browser_type: { type: 'keyword' }, migration_version: { type: 'keyword' }, // new field (7.14) to distinguish reports that were scheduled with Task Manager jobtype: { type: 'keyword' }, payload: { type: 'object', enabled: false }, diff --git a/x-pack/plugins/reporting/server/lib/store/report.test.ts b/x-pack/plugins/reporting/server/lib/store/report.test.ts index f9cd413b3e5a7..f6cbbade4df7b 100644 --- a/x-pack/plugins/reporting/server/lib/store/report.test.ts +++ b/x-pack/plugins/reporting/server/lib/store/report.test.ts @@ -13,7 +13,6 @@ describe('Class Report', () => { _index: '.reporting-test-index-12345', jobtype: 'test-report', created_by: 'created_by_test_string', - browser_type: 'browser_type_test_string', max_attempts: 50, payload: { headers: 'payload_test_field', @@ -28,7 +27,6 @@ describe('Class Report', () => { expect(report.toReportSource()).toMatchObject({ attempts: 0, - browser_type: 'browser_type_test_string', completed_at: undefined, created_by: 'created_by_test_string', jobtype: 'test-report', @@ -49,7 +47,6 @@ describe('Class Report', () => { }); expect(report.toApiJSON()).toMatchObject({ attempts: 0, - browser_type: 'browser_type_test_string', created_by: 'created_by_test_string', index: '.reporting-test-index-12345', jobtype: 'test-report', @@ -68,7 +65,6 @@ describe('Class Report', () => { _index: '.reporting-test-index-12345', jobtype: 'test-report', created_by: 'created_by_test_string', - browser_type: 'browser_type_test_string', max_attempts: 50, payload: { headers: 'payload_test_field', @@ -91,7 +87,6 @@ describe('Class Report', () => { expect(report.toReportSource()).toMatchObject({ attempts: 0, - browser_type: 'browser_type_test_string', completed_at: undefined, created_by: 'created_by_test_string', jobtype: 'test-report', @@ -113,7 +108,6 @@ describe('Class Report', () => { }); expect(report.toApiJSON()).toMatchObject({ attempts: 0, - browser_type: 'browser_type_test_string', completed_at: undefined, created_by: 'created_by_test_string', id: '12342p9o387549o2345', diff --git a/x-pack/plugins/reporting/server/lib/store/report.ts b/x-pack/plugins/reporting/server/lib/store/report.ts index 2f802334eb6ff..67f1ccdea5db8 100644 --- a/x-pack/plugins/reporting/server/lib/store/report.ts +++ b/x-pack/plugins/reporting/server/lib/store/report.ts @@ -38,7 +38,6 @@ export class Report implements Partial { public readonly payload: ReportSource['payload']; public readonly meta: ReportSource['meta']; - public readonly browser_type: ReportSource['browser_type']; public readonly status: ReportSource['status']; public readonly attempts: ReportSource['attempts']; @@ -82,7 +81,6 @@ export class Report implements Partial { this.max_attempts = opts.max_attempts; this.attempts = opts.attempts || 0; this.timeout = opts.timeout; - this.browser_type = opts.browser_type; this.process_expiration = opts.process_expiration; this.started_at = opts.started_at; @@ -125,7 +123,6 @@ export class Report implements Partial { meta: this.meta, timeout: this.timeout, max_attempts: this.max_attempts, - browser_type: this.browser_type, status: this.status, attempts: this.attempts, started_at: this.started_at, @@ -170,7 +167,6 @@ export class Report implements Partial { meta: this.meta, timeout: this.timeout, max_attempts: this.max_attempts, - browser_type: this.browser_type, status: this.status, attempts: this.attempts, started_at: this.started_at, diff --git a/x-pack/plugins/reporting/server/lib/store/store.test.ts b/x-pack/plugins/reporting/server/lib/store/store.test.ts index a28197d261ba2..c67dc3fa2d992 100644 --- a/x-pack/plugins/reporting/server/lib/store/store.test.ts +++ b/x-pack/plugins/reporting/server/lib/store/store.test.ts @@ -193,7 +193,6 @@ describe('ReportingStore', () => { status: 'pending', meta: { testMeta: 'meta' } as any, payload: { testPayload: 'payload' } as any, - browser_type: 'browser type string', attempts: 0, max_attempts: 1, timeout: 30000, @@ -214,7 +213,6 @@ describe('ReportingStore', () => { "_primary_term": 1234, "_seq_no": 5678, "attempts": 0, - "browser_type": "browser type string", "completed_at": undefined, "created_at": "some time", "created_by": "some security person", @@ -247,7 +245,6 @@ describe('ReportingStore', () => { _primary_term: 10002, jobtype: 'test-report', created_by: 'created_by_test_string', - browser_type: 'browser_type_test_string', max_attempts: 50, payload: { title: 'test report', @@ -279,7 +276,6 @@ describe('ReportingStore', () => { _primary_term: 10002, jobtype: 'test-report', created_by: 'created_by_test_string', - browser_type: 'browser_type_test_string', max_attempts: 50, payload: { title: 'test report', @@ -310,7 +306,6 @@ describe('ReportingStore', () => { _primary_term: 10002, jobtype: 'test-report', created_by: 'created_by_test_string', - browser_type: 'browser_type_test_string', max_attempts: 50, payload: { title: 'test report', @@ -341,7 +336,6 @@ describe('ReportingStore', () => { _primary_term: 10002, jobtype: 'test-report', created_by: 'created_by_test_string', - browser_type: 'browser_type_test_string', max_attempts: 50, payload: { title: 'test report', @@ -385,7 +379,6 @@ describe('ReportingStore', () => { _primary_term: 10002, jobtype: 'test-report-2', created_by: 'created_by_test_string', - browser_type: 'browser_type_test_string', status: 'processing', process_expiration: '2002', max_attempts: 3, diff --git a/x-pack/plugins/reporting/server/lib/store/store.ts b/x-pack/plugins/reporting/server/lib/store/store.ts index 43f57da8c21f7..7ddef6d66e275 100644 --- a/x-pack/plugins/reporting/server/lib/store/store.ts +++ b/x-pack/plugins/reporting/server/lib/store/store.ts @@ -24,7 +24,6 @@ import { MIGRATION_VERSION } from './report'; export type ReportProcessingFields = Required<{ kibana_id: Report['kibana_id']; kibana_name: Report['kibana_name']; - browser_type: Report['browser_type']; attempts: Report['attempts']; started_at: Report['started_at']; max_attempts: Report['max_attempts']; @@ -252,7 +251,6 @@ export class ReportingStore { _primary_term: document._primary_term, jobtype: document._source?.jobtype, attempts: document._source?.attempts, - browser_type: document._source?.browser_type, created_at: document._source?.created_at, created_by: document._source?.created_by, max_attempts: document._source?.max_attempts, diff --git a/x-pack/plugins/reporting/server/lib/tasks/execute_report.ts b/x-pack/plugins/reporting/server/lib/tasks/execute_report.ts index 5f885ad127b43..b725c31da398d 100644 --- a/x-pack/plugins/reporting/server/lib/tasks/execute_report.ts +++ b/x-pack/plugins/reporting/server/lib/tasks/execute_report.ts @@ -159,7 +159,6 @@ export class ExecuteReportTask implements ReportingTask { const doc: ReportProcessingFields = { kibana_id: this.kibanaId, kibana_name: this.kibanaName, - browser_type: this.config.capture.browser.type, attempts: report.attempts + 1, max_attempts: maxAttempts, started_at: startTime, diff --git a/x-pack/plugins/reporting/server/plugin.test.ts b/x-pack/plugins/reporting/server/plugin.test.ts index 9a2acc4a51202..4c04eb0c004e5 100644 --- a/x-pack/plugins/reporting/server/plugin.test.ts +++ b/x-pack/plugins/reporting/server/plugin.test.ts @@ -5,16 +5,6 @@ * 2.0. */ -jest.mock('./browsers/install', () => ({ - installBrowser: jest.fn().mockImplementation(() => ({ - binaryPath$: { - pipe: jest.fn().mockImplementation(() => ({ - toPromise: () => Promise.resolve(), - })), - }, - })), -})); - import { coreMock } from 'src/core/server/mocks'; import { featuresPluginMock } from '../../features/server/mocks'; import { TaskManagerSetupContract } from '../../task_manager/server'; diff --git a/x-pack/plugins/reporting/server/plugin.ts b/x-pack/plugins/reporting/server/plugin.ts index 8969a698a8ce4..0a2318daded02 100644 --- a/x-pack/plugins/reporting/server/plugin.ts +++ b/x-pack/plugins/reporting/server/plugin.ts @@ -8,7 +8,6 @@ import type { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'src/core/server'; import { PLUGIN_ID } from '../common/constants'; import { ReportingCore } from './'; -import { initializeBrowserDriverFactory } from './browsers'; import { buildConfig, registerUiSettings, ReportingConfigType } from './config'; import { registerDeprecations } from './deprecations'; import { LevelLogger, ReportingStore } from './lib'; @@ -35,7 +34,7 @@ export class ReportingPlugin public setup(core: CoreSetup, plugins: ReportingSetupDeps) { const { http } = core; - const { screenshotMode, features, licensing, security, spaces, taskManager } = plugins; + const { features, licensing, security, spaces, taskManager } = plugins; const reportingCore = new ReportingCore(this.logger, this.initContext); @@ -53,7 +52,6 @@ export class ReportingPlugin const router = http.createRouter(); const basePath = http.basePath; reportingCore.pluginSetup({ - screenshotMode, features, licensing, basePath, @@ -98,11 +96,9 @@ export class ReportingPlugin (async () => { await reportingCore.pluginSetsUp(); - const browserDriverFactory = await initializeBrowserDriverFactory(reportingCore, this.logger); const store = new ReportingStore(reportingCore, this.logger); await reportingCore.pluginStart({ - browserDriverFactory, savedObjects: core.savedObjects, uiSettings: core.uiSettings, store, @@ -110,6 +106,7 @@ export class ReportingPlugin data: plugins.data, taskManager: plugins.taskManager, logger: this.logger, + screenshotting: plugins.screenshotting, }); // Note: this must be called after ReportingCore.pluginStart diff --git a/x-pack/plugins/reporting/server/routes/diagnostic/browser.test.ts b/x-pack/plugins/reporting/server/routes/diagnostic/browser.test.ts index a27ce6a49b1a2..47dae7f96daa4 100644 --- a/x-pack/plugins/reporting/server/routes/diagnostic/browser.test.ts +++ b/x-pack/plugins/reporting/server/routes/diagnostic/browser.test.ts @@ -6,11 +6,10 @@ */ import { UnwrapPromise } from '@kbn/utility-types'; -import { spawn } from 'child_process'; -import { createInterface } from 'readline'; import { setupServer } from 'src/core/server/test_utils'; import supertest from 'supertest'; import * as Rx from 'rxjs'; +import type { ScreenshottingStart } from '../../../../screenshotting/server'; import { ReportingCore } from '../..'; import { createMockConfigSchema, @@ -21,17 +20,11 @@ import { import type { ReportingRequestHandlerContext } from '../../types'; import { registerDiagnoseBrowser } from './browser'; -jest.mock('child_process'); -jest.mock('readline'); - type SetupServerReturn = UnwrapPromise>; const devtoolMessage = 'DevTools listening on (ws://localhost:4000)'; const fontNotFoundMessage = 'Could not find the default font'; -const wait = (ms: number): Rx.Observable<0> => - Rx.from(new Promise<0>((resolve) => setTimeout(() => resolve(0), ms))); - describe('POST /diagnose/browser', () => { jest.setTimeout(6000); const reportingSymbol = Symbol('reporting'); @@ -40,12 +33,11 @@ describe('POST /diagnose/browser', () => { let server: SetupServerReturn['server']; let httpSetup: SetupServerReturn['httpSetup']; let core: ReportingCore; - const mockedSpawn: any = spawn; - const mockedCreateInterface: any = createInterface; + let screenshotting: jest.Mocked; const config = createMockConfigSchema({ queue: { timeout: 120000 }, - capture: { browser: { chromium: { proxy: { enabled: false } } } }, + capture: {}, }); beforeEach(async () => { @@ -56,9 +48,6 @@ describe('POST /diagnose/browser', () => { () => ({ usesUiCapabilities: () => false }) ); - // Make all uses of 'Rx.timer' return an observable that completes in 50ms - jest.spyOn(Rx, 'timer').mockImplementation(() => wait(50)); - core = await createMockReportingCore( config, createMockPluginSetup({ @@ -67,21 +56,7 @@ describe('POST /diagnose/browser', () => { }) ); - mockedSpawn.mockImplementation(() => ({ - removeAllListeners: jest.fn(), - kill: jest.fn(), - pid: 123, - stderr: 'stderr', - addEventListener: jest.fn(), - removeEventListener: jest.fn(), - })); - - mockedCreateInterface.mockImplementation(() => ({ - addEventListener: jest.fn(), - removeEventListener: jest.fn(), - removeAllListeners: jest.fn(), - close: jest.fn(), - })); + screenshotting = (await core.getPluginStartDeps()).screenshotting as typeof screenshotting; }); afterEach(async () => { @@ -94,12 +69,7 @@ describe('POST /diagnose/browser', () => { await server.start(); - mockedCreateInterface.mockImplementation(() => ({ - addEventListener: (_e: string, cb: any) => setTimeout(() => cb(devtoolMessage), 0), - removeEventListener: jest.fn(), - removeAllListeners: jest.fn(), - close: jest.fn(), - })); + screenshotting.diagnose.mockReturnValue(Rx.of(devtoolMessage)); return supertest(httpSetup.server.listener) .post('/api/reporting/diagnose/browser') @@ -115,20 +85,7 @@ describe('POST /diagnose/browser', () => { registerDiagnoseBrowser(core, mockLogger); await server.start(); - - mockedCreateInterface.mockImplementation(() => ({ - addEventListener: (_e: string, cb: any) => setTimeout(() => cb(logs), 0), - removeEventListener: jest.fn(), - removeAllListeners: jest.fn(), - close: jest.fn(), - })); - - mockedSpawn.mockImplementation(() => ({ - removeAllListeners: jest.fn(), - kill: jest.fn(), - addEventListener: jest.fn(), - removeEventListener: jest.fn(), - })); + screenshotting.diagnose.mockReturnValue(Rx.of(logs)); return supertest(httpSetup.server.listener) .post('/api/reporting/diagnose/browser') @@ -139,8 +96,7 @@ describe('POST /diagnose/browser', () => { "help": Array [ "The browser couldn't locate a default font. Please see https://www.elastic.co/guide/en/kibana/current/reporting-troubleshooting.html#reporting-troubleshooting-system-dependencies to fix this issue.", ], - "logs": "Could not find the default font - ", + "logs": "Could not find the default font", "success": false, } `); @@ -151,23 +107,7 @@ describe('POST /diagnose/browser', () => { registerDiagnoseBrowser(core, mockLogger); await server.start(); - - mockedCreateInterface.mockImplementation(() => ({ - addEventListener: (_e: string, cb: any) => { - setTimeout(() => cb(devtoolMessage), 0); - setTimeout(() => cb(fontNotFoundMessage), 0); - }, - removeEventListener: jest.fn(), - removeAllListeners: jest.fn(), - close: jest.fn(), - })); - - mockedSpawn.mockImplementation(() => ({ - removeAllListeners: jest.fn(), - kill: jest.fn(), - addEventListener: jest.fn(), - removeEventListener: jest.fn(), - })); + screenshotting.diagnose.mockReturnValue(Rx.of(`${devtoolMessage}\n${fontNotFoundMessage}`)); return supertest(httpSetup.server.listener) .post('/api/reporting/diagnose/browser') @@ -179,89 +119,10 @@ describe('POST /diagnose/browser', () => { "The browser couldn't locate a default font. Please see https://www.elastic.co/guide/en/kibana/current/reporting-troubleshooting.html#reporting-troubleshooting-system-dependencies to fix this issue.", ], "logs": "DevTools listening on (ws://localhost:4000) - Could not find the default font - ", + Could not find the default font", "success": false, } `); }); }); - - it('logs a message when the browser starts, but then crashes', async () => { - registerDiagnoseBrowser(core, mockLogger); - - await server.start(); - - mockedCreateInterface.mockImplementation(() => ({ - addEventListener: (_e: string, cb: any) => { - setTimeout(() => cb(fontNotFoundMessage), 0); - }, - removeEventListener: jest.fn(), - removeAllListeners: jest.fn(), - close: jest.fn(), - })); - - mockedSpawn.mockImplementation(() => ({ - removeAllListeners: jest.fn(), - kill: jest.fn(), - addEventListener: (e: string, cb: any) => { - if (e === 'exit') { - setTimeout(() => cb(), 5); - } - }, - removeEventListener: jest.fn(), - })); - - return supertest(httpSetup.server.listener) - .post('/api/reporting/diagnose/browser') - .expect(200) - .then(({ body }) => { - const helpArray = [...body.help]; - helpArray.sort(); - expect(helpArray).toMatchInlineSnapshot(` - Array [ - "The browser couldn't locate a default font. Please see https://www.elastic.co/guide/en/kibana/current/reporting-troubleshooting.html#reporting-troubleshooting-system-dependencies to fix this issue.", - ] - `); - expect(body.logs).toMatch(/Could not find the default font/); - expect(body.logs).toMatch(/Browser exited abnormally during startup/); - expect(body.success).toBe(false); - }); - }); - - it('cleans up process and subscribers', async () => { - registerDiagnoseBrowser(core, mockLogger); - - await server.start(); - const killMock = jest.fn(); - const spawnListenersMock = jest.fn(); - const createInterfaceListenersMock = jest.fn(); - const createInterfaceCloseMock = jest.fn(); - - mockedSpawn.mockImplementation(() => ({ - removeAllListeners: spawnListenersMock, - kill: killMock, - pid: 123, - stderr: 'stderr', - addEventListener: jest.fn(), - removeEventListener: jest.fn(), - })); - - mockedCreateInterface.mockImplementation(() => ({ - addEventListener: (_e: string, cb: any) => setTimeout(() => cb(devtoolMessage), 0), - removeEventListener: jest.fn(), - removeAllListeners: createInterfaceListenersMock, - close: createInterfaceCloseMock, - })); - - return supertest(httpSetup.server.listener) - .post('/api/reporting/diagnose/browser') - .expect(200) - .then(() => { - expect(killMock.mock.calls.length).toBe(1); - expect(spawnListenersMock.mock.calls.length).toBe(1); - expect(createInterfaceListenersMock.mock.calls.length).toBe(1); - expect(createInterfaceCloseMock.mock.calls.length).toBe(1); - }); - }); }); diff --git a/x-pack/plugins/reporting/server/routes/diagnostic/browser.ts b/x-pack/plugins/reporting/server/routes/diagnostic/browser.ts index 7793fc658c535..f68df294b4118 100644 --- a/x-pack/plugins/reporting/server/routes/diagnostic/browser.ts +++ b/x-pack/plugins/reporting/server/routes/diagnostic/browser.ts @@ -8,7 +8,6 @@ import { i18n } from '@kbn/i18n'; import { ReportingCore } from '../..'; import { API_DIAGNOSE_URL } from '../../../common/constants'; -import { browserStartLogs } from '../../browsers/chromium/driver_factory/start_logs'; import { LevelLogger as Logger } from '../../lib'; import { authorizedUserPreRouting } from '../lib/authorized_user_pre_routing'; import { DiagnosticResponse } from './'; @@ -52,7 +51,8 @@ export const registerDiagnoseBrowser = (reporting: ReportingCore, logger: Logger }, authorizedUserPreRouting(reporting, async (_user, _context, _req, res) => { try { - const logs = await browserStartLogs(reporting, logger).toPromise(); + const { screenshotting } = await reporting.getPluginStartDeps(); + const logs = await screenshotting.diagnose().toPromise(); const knownIssues = Object.keys(logsToHelpMap) as Array; const boundSuccessfully = logs.includes(`DevTools listening on`); diff --git a/x-pack/plugins/reporting/server/routes/diagnostic/screenshot.test.ts b/x-pack/plugins/reporting/server/routes/diagnostic/screenshot.test.ts index dd543707fe66a..4bc33d20d6fcf 100644 --- a/x-pack/plugins/reporting/server/routes/diagnostic/screenshot.test.ts +++ b/x-pack/plugins/reporting/server/routes/diagnostic/screenshot.test.ts @@ -20,7 +20,7 @@ import type { ReportingRequestHandlerContext } from '../../types'; jest.mock('../../export_types/common/generate_png'); -import { generatePngObservableFactory } from '../../export_types/common'; +import { generatePngObservable } from '../../export_types/common'; type SetupServerReturn = UnwrapPromise>; @@ -31,12 +31,12 @@ describe('POST /diagnose/screenshot', () => { let core: ReportingCore; const setScreenshotResponse = (resp: object | Error) => { - const generateMock = Promise.resolve(() => ({ + const generateMock = { pipe: () => ({ toPromise: () => (resp instanceof Error ? Promise.reject(resp) : Promise.resolve(resp)), }), - })); - (generatePngObservableFactory as jest.Mock).mockResolvedValue(generateMock); + }; + (generatePngObservable as jest.Mock).mockReturnValue(generateMock); }; const config = createMockConfigSchema({ queue: { timeout: 120000 } }); diff --git a/x-pack/plugins/reporting/server/routes/diagnostic/screenshot.ts b/x-pack/plugins/reporting/server/routes/diagnostic/screenshot.ts index f2002dd945882..2d5a254045104 100644 --- a/x-pack/plugins/reporting/server/routes/diagnostic/screenshot.ts +++ b/x-pack/plugins/reporting/server/routes/diagnostic/screenshot.ts @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; import { ReportingCore } from '../..'; import { APP_WRAPPER_CLASS } from '../../../../../../src/core/server'; import { API_DIAGNOSE_URL } from '../../../common/constants'; -import { omitBlockedHeaders, generatePngObservableFactory } from '../../export_types/common'; +import { omitBlockedHeaders, generatePngObservable } from '../../export_types/common'; import { getAbsoluteUrlFactory } from '../../export_types/common/get_absolute_url'; import { LevelLogger as Logger } from '../../lib'; import { authorizedUserPreRouting } from '../lib/authorized_user_pre_routing'; @@ -25,7 +25,6 @@ export const registerDiagnoseScreenshot = (reporting: ReportingCore, logger: Log validate: {}, }, authorizedUserPreRouting(reporting, async (_user, _context, req, res) => { - const generatePngObservable = await generatePngObservableFactory(reporting); const config = reporting.getConfig(); const decryptedHeaders = req.headers as Record; const [basePath, protocol, hostname, port] = [ @@ -40,7 +39,6 @@ export const registerDiagnoseScreenshot = (reporting: ReportingCore, logger: Log // Hack the layout to make the base/login page work const layout = { - id: 'png', dimensions: { width: 1440, height: 2024, @@ -53,7 +51,7 @@ export const registerDiagnoseScreenshot = (reporting: ReportingCore, logger: Log }, }; - const headers = { + const conditionalHeaders = { headers: omitBlockedHeaders(decryptedHeaders), conditions: { hostname, @@ -63,7 +61,12 @@ export const registerDiagnoseScreenshot = (reporting: ReportingCore, logger: Log }, }; - return generatePngObservable(logger, hashUrl, 'America/Los_Angeles', headers, layout) + return generatePngObservable(reporting, logger, { + conditionalHeaders, + layout, + browserTimezone: 'America/Los_Angeles', + urls: [hashUrl], + }) .pipe() .toPromise() .then((screenshot) => { diff --git a/x-pack/plugins/reporting/server/routes/lib/request_handler.test.ts b/x-pack/plugins/reporting/server/routes/lib/request_handler.test.ts index 5900a151f92da..6d73a3ec7ee74 100644 --- a/x-pack/plugins/reporting/server/routes/lib/request_handler.test.ts +++ b/x-pack/plugins/reporting/server/routes/lib/request_handler.test.ts @@ -103,7 +103,6 @@ describe('Handle request to generate', () => { "_primary_term": undefined, "_seq_no": undefined, "attempts": 0, - "browser_type": undefined, "completed_at": undefined, "created_by": "testymcgee", "jobtype": "printable_pdf", @@ -180,7 +179,6 @@ describe('Handle request to generate', () => { expect(snapObj).toMatchInlineSnapshot(` Object { "attempts": 0, - "browser_type": undefined, "completed_at": undefined, "created_by": "testymcgee", "index": ".reporting-foo-index-234", diff --git a/x-pack/plugins/reporting/server/test_helpers/create_mock_browserdriverfactory.ts b/x-pack/plugins/reporting/server/test_helpers/create_mock_browserdriverfactory.ts deleted file mode 100644 index d42fb73b447a5..0000000000000 --- a/x-pack/plugins/reporting/server/test_helpers/create_mock_browserdriverfactory.ts +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import moment from 'moment'; -import { Page } from 'puppeteer'; -import * as Rx from 'rxjs'; -import { ReportingCore } from '..'; -import { chromium, HeadlessChromiumDriver, HeadlessChromiumDriverFactory } from '../browsers'; -import { LevelLogger } from '../lib'; -import { ElementsPositionAndAttribute } from '../lib/screenshots'; -import * as contexts from '../lib/screenshots/constants'; -import { CaptureConfig } from '../types'; - -interface CreateMockBrowserDriverFactoryOpts { - evaluate: jest.Mock, any[]>; - waitForSelector: jest.Mock, any[]>; - waitFor: jest.Mock, any[]>; - screenshot: jest.Mock, any[]>; - open: jest.Mock, any[]>; - getCreatePage: (driver: HeadlessChromiumDriver) => jest.Mock; -} - -const mockSelectors = { - renderComplete: 'renderedSelector', - itemsCountAttribute: 'itemsSelector', - screenshot: 'screenshotSelector', - timefilterDurationAttribute: 'timefilterDurationSelector', - toastHeader: 'toastHeaderSelector', -}; - -const getMockElementsPositionAndAttributes = ( - title: string, - description: string -): ElementsPositionAndAttribute[] => [ - { - position: { - boundingClientRect: { top: 0, left: 0, width: 800, height: 600 }, - scroll: { x: 0, y: 0 }, - }, - attributes: { title, description }, - }, -]; - -const mockWaitForSelector = jest.fn(); -mockWaitForSelector.mockImplementation((selectorArg: string) => { - const { renderComplete, itemsCountAttribute, toastHeader } = mockSelectors; - if (selectorArg === `${renderComplete},[${itemsCountAttribute}]`) { - return Promise.resolve(true); - } else if (selectorArg === toastHeader) { - return Rx.never().toPromise(); - } - throw new Error(selectorArg); -}); -const mockBrowserEvaluate = jest.fn(); -mockBrowserEvaluate.mockImplementation(() => { - const lastCallIndex = mockBrowserEvaluate.mock.calls.length - 1; - const { context: mockCall } = mockBrowserEvaluate.mock.calls[lastCallIndex][1]; - - if (mockCall === contexts.CONTEXT_SKIPTELEMETRY) { - return Promise.resolve(); - } - if (mockCall === contexts.CONTEXT_GETNUMBEROFITEMS) { - return Promise.resolve(1); - } - if (mockCall === contexts.CONTEXT_INJECTCSS) { - return Promise.resolve(); - } - if (mockCall === contexts.CONTEXT_WAITFORRENDER) { - return Promise.resolve(); - } - if (mockCall === contexts.CONTEXT_GETTIMERANGE) { - return Promise.resolve('Default GetTimeRange Result'); - } - if (mockCall === contexts.CONTEXT_ELEMENTATTRIBUTES) { - return Promise.resolve(getMockElementsPositionAndAttributes('Default Mock Title', 'Default ')); - } - if (mockCall === contexts.CONTEXT_GETRENDERERRORS) { - return Promise.resolve(); - } - throw new Error(mockCall); -}); -const mockScreenshot = jest.fn(async () => Buffer.from('screenshot')); -const getCreatePage = (driver: HeadlessChromiumDriver) => - jest.fn().mockImplementation(() => Rx.of({ driver, exit$: Rx.never() })); - -const defaultOpts: CreateMockBrowserDriverFactoryOpts = { - evaluate: mockBrowserEvaluate, - waitForSelector: mockWaitForSelector, - waitFor: jest.fn(), - screenshot: mockScreenshot, - open: jest.fn(), - getCreatePage, -}; - -export const createMockBrowserDriverFactory = async ( - core: ReportingCore, - logger: LevelLogger, - opts: Partial = {} -): Promise => { - const captureConfig: CaptureConfig = { - timeouts: { - openUrl: moment.duration(60, 's'), - waitForElements: moment.duration(30, 's'), - renderComplete: moment.duration(30, 's'), - }, - browser: { - type: 'chromium', - chromium: { - inspect: false, - disableSandbox: false, - proxy: { enabled: false, server: undefined, bypass: undefined }, - }, - autoDownload: false, - }, - networkPolicy: { enabled: true, rules: [] }, - loadDelay: moment.duration(2, 's'), - zoom: 2, - maxAttempts: 1, - }; - - const binaryPath = '/usr/local/share/common/secure/super_awesome_binary'; - const mockBrowserDriverFactory = chromium.createDriverFactory(core, binaryPath, logger); - const mockPage = { setViewport: () => {} } as unknown as Page; - const mockBrowserDriver = new HeadlessChromiumDriver(core, mockPage, { - inspect: true, - networkPolicy: captureConfig.networkPolicy, - }); - - // mock the driver methods as either default mocks or passed-in - mockBrowserDriver.waitForSelector = opts.waitForSelector ? opts.waitForSelector : defaultOpts.waitForSelector; // prettier-ignore - mockBrowserDriver.waitFor = opts.waitFor ? opts.waitFor : defaultOpts.waitFor; - mockBrowserDriver.evaluate = opts.evaluate ? opts.evaluate : defaultOpts.evaluate; - mockBrowserDriver.screenshot = opts.screenshot ? opts.screenshot : defaultOpts.screenshot; - mockBrowserDriver.open = opts.open ? opts.open : defaultOpts.open; - mockBrowserDriver.isPageOpen = () => true; - - mockBrowserDriverFactory.createPage = opts.getCreatePage - ? opts.getCreatePage(mockBrowserDriver) - : getCreatePage(mockBrowserDriver); - - return mockBrowserDriverFactory; -}; diff --git a/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts b/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts index c05b2c54aeabf..0569ea1400555 100644 --- a/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts +++ b/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts @@ -7,7 +7,6 @@ jest.mock('../routes'); jest.mock('../usage'); -jest.mock('../browsers'); import _ from 'lodash'; import * as Rx from 'rxjs'; @@ -18,24 +17,15 @@ import { FieldFormatsRegistry } from 'src/plugins/field_formats/common'; import { ReportingConfig, ReportingCore } from '../'; import { featuresPluginMock } from '../../../features/server/mocks'; import { securityMock } from '../../../security/server/mocks'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { createMockScreenshottingStart } from '../../../screenshotting/server/mock'; import { taskManagerMock } from '../../../task_manager/server/mocks'; -import { - chromium, - HeadlessChromiumDriverFactory, - initializeBrowserDriverFactory, -} from '../browsers'; import { ReportingConfigType } from '../config'; import { ReportingInternalSetup, ReportingInternalStart } from '../core'; import { ReportingStore } from '../lib'; import { setFieldFormats } from '../services'; import { createMockLevelLogger } from './create_mock_levellogger'; -( - initializeBrowserDriverFactory as jest.Mock> -).mockImplementation(() => Promise.resolve({} as HeadlessChromiumDriverFactory)); - -(chromium as any).createDriverFactory.mockImplementation(() => ({})); - export const createMockPluginSetup = (setupMock?: any): ReportingInternalSetup => { return { features: featuresPluginMock.createSetup(), @@ -63,7 +53,6 @@ export const createMockPluginStart = ( : createMockReportingStore(); return { - browserDriverFactory: startMock.browserDriverFactory, esClient: elasticsearchServiceMock.createClusterClient(), savedObjects: startMock.savedObjects || { getScopedClient: jest.fn() }, uiSettings: startMock.uiSettings || { asScopedToClient: () => ({ get: jest.fn() }) }, @@ -74,6 +63,7 @@ export const createMockPluginStart = ( ensureScheduled: jest.fn(), } as any, logger: createMockLevelLogger(), + screenshotting: startMock.screenshotting || createMockScreenshottingStart(), ...startMock, }; }; @@ -102,14 +92,6 @@ export const createMockConfigSchema = ( port: 80, ...overrides.kibanaServer, }, - capture: { - browser: { - chromium: { - disableSandbox: true, - }, - }, - ...overrides.capture, - }, queue: { indexInterval: 'week', pollEnabled: true, diff --git a/x-pack/plugins/reporting/server/test_helpers/index.ts b/x-pack/plugins/reporting/server/test_helpers/index.ts index fe8c92d928af5..667c85c24a35d 100644 --- a/x-pack/plugins/reporting/server/test_helpers/index.ts +++ b/x-pack/plugins/reporting/server/test_helpers/index.ts @@ -5,8 +5,6 @@ * 2.0. */ -export { createMockBrowserDriverFactory } from './create_mock_browserdriverfactory'; -export { createMockLayoutInstance } from './create_mock_layoutinstance'; export { createMockLevelLogger } from './create_mock_levellogger'; export { createMockConfig, diff --git a/x-pack/plugins/reporting/server/types.ts b/x-pack/plugins/reporting/server/types.ts index af9a973b0bb45..3b1e819f0863c 100644 --- a/x-pack/plugins/reporting/server/types.ts +++ b/x-pack/plugins/reporting/server/types.ts @@ -11,13 +11,17 @@ import { DataPluginStart } from 'src/plugins/data/server/plugin'; import { ScreenshotModePluginSetup } from 'src/plugins/screenshot_mode/server'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { Writable } from 'stream'; +import type { + ScreenshottingStart, + ScreenshotOptions as BaseScreenshotOptions, +} from '../../screenshotting/server'; import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server'; import { LicensingPluginSetup } from '../../licensing/server'; import { AuthenticatedUser, SecurityPluginSetup } from '../../security/server'; import { SpacesPluginSetup } from '../../spaces/server'; import { TaskManagerSetupContract, TaskManagerStartContract } from '../../task_manager/server'; import { CancellationToken } from '../common'; -import { BaseParams, BasePayload, TaskRunResult } from '../common/types'; +import { BaseParams, BasePayload, TaskRunResult, UrlOrUrlLocatorTuple } from '../common/types'; import { ReportingConfigType } from './config'; import { ReportingCore } from './core'; import { LevelLogger } from './lib'; @@ -39,6 +43,7 @@ export interface ReportingSetupDeps { export interface ReportingStartDeps { data: DataPluginStart; + screenshotting: ScreenshottingStart; taskManager: TaskManagerStartContract; } @@ -109,3 +114,10 @@ export interface ReportingRequestHandlerContext { * @internal */ export type ReportingPluginRouter = IRouter; + +/** + * @internal + */ +export interface ScreenshotOptions extends Omit { + urls: UrlOrUrlLocatorTuple[]; +} diff --git a/x-pack/plugins/reporting/server/usage/__snapshots__/reporting_usage_collector.test.ts.snap b/x-pack/plugins/reporting/server/usage/__snapshots__/reporting_usage_collector.test.ts.snap index 2017ae0be59c7..78bb9ab6df51f 100644 --- a/x-pack/plugins/reporting/server/usage/__snapshots__/reporting_usage_collector.test.ts.snap +++ b/x-pack/plugins/reporting/server/usage/__snapshots__/reporting_usage_collector.test.ts.snap @@ -129,9 +129,6 @@ Object { "available": Object { "type": "boolean", }, - "browser_type": Object { - "type": "keyword", - }, "csv": Object { "app": Object { "canvas workpad": Object { @@ -1973,7 +1970,6 @@ Object { }, "_all": 9, "available": true, - "browser_type": undefined, "csv": Object { "app": Object { "canvas workpad": 0, @@ -2243,7 +2239,6 @@ Object { }, "_all": 0, "available": true, - "browser_type": undefined, "csv": Object { "app": Object { "canvas workpad": 0, @@ -2492,7 +2487,6 @@ Object { }, "_all": 4, "available": true, - "browser_type": undefined, "csv": Object { "app": Object { "canvas workpad": 0, @@ -2768,7 +2762,6 @@ Object { }, "_all": 11, "available": true, - "browser_type": undefined, "csv": Object { "app": Object { "canvas workpad": 0, diff --git a/x-pack/plugins/reporting/server/usage/get_reporting_usage.ts b/x-pack/plugins/reporting/server/usage/get_reporting_usage.ts index 73a4920b350e3..59387923e3755 100644 --- a/x-pack/plugins/reporting/server/usage/get_reporting_usage.ts +++ b/x-pack/plugins/reporting/server/usage/get_reporting_usage.ts @@ -206,10 +206,6 @@ export async function getReportingUsage( .search(params) .then(({ body: response }) => handleResponse(response)) .then((usage: Partial): ReportingUsageType => { - // Allow this to explicitly throw an exception if/when this config is deprecated, - // because we shouldn't collect browserType in that case! - const browserType = config.get('capture', 'browser', 'type'); - const exportTypesHandler = getExportTypesHandler(exportTypesRegistry); const availability = exportTypesHandler.getAvailability( featureAvailability @@ -219,7 +215,6 @@ export async function getReportingUsage( return { available: true, - browser_type: browserType, enabled: true, last7Days: getExportStats(last7Days, availability, exportTypesHandler), ...getExportStats(all, availability, exportTypesHandler), diff --git a/x-pack/plugins/reporting/server/usage/schema.ts b/x-pack/plugins/reporting/server/usage/schema.ts index 9580ddb935dfb..fc464903edaee 100644 --- a/x-pack/plugins/reporting/server/usage/schema.ts +++ b/x-pack/plugins/reporting/server/usage/schema.ts @@ -92,7 +92,6 @@ const rangeStatsSchema: MakeSchemaFrom = { export const reportingSchema: MakeSchemaFrom = { ...rangeStatsSchema, available: { type: 'boolean' }, - browser_type: { type: 'keyword' }, enabled: { type: 'boolean' }, last7Days: rangeStatsSchema, }; diff --git a/x-pack/plugins/reporting/server/usage/types.ts b/x-pack/plugins/reporting/server/usage/types.ts index 856d3ad10cb26..e6695abc8da74 100644 --- a/x-pack/plugins/reporting/server/usage/types.ts +++ b/x-pack/plugins/reporting/server/usage/types.ts @@ -129,7 +129,6 @@ export type RangeStats = JobTypes & { export type ReportingUsageType = RangeStats & { available: boolean; - browser_type: string; enabled: boolean; last7Days: RangeStats; }; diff --git a/x-pack/plugins/reporting/tsconfig.json b/x-pack/plugins/reporting/tsconfig.json index 3e58450565720..4e09708915f95 100644 --- a/x-pack/plugins/reporting/tsconfig.json +++ b/x-pack/plugins/reporting/tsconfig.json @@ -26,6 +26,7 @@ { "path": "../../../src/plugins/field_formats/tsconfig.json" }, { "path": "../features/tsconfig.json" }, { "path": "../licensing/tsconfig.json" }, + { "path": "../screenshotting/tsconfig.json" }, { "path": "../security/tsconfig.json" }, { "path": "../spaces/tsconfig.json" }, ] diff --git a/x-pack/plugins/screenshotting/README.md b/x-pack/plugins/screenshotting/README.md new file mode 100644 index 0000000000000..3439f06dff8e5 --- /dev/null +++ b/x-pack/plugins/screenshotting/README.md @@ -0,0 +1,11 @@ +# Kibana Screenshotting + +This plugin provides functionality to take screenshots of the Kibana pages. +It uses Chromium and Puppeteer underneath to run the browser in headless mode. + +## API + +The plugin exposes most of the functionality in the start contract. +The Chromium download and setup is happening during the setup stage. + +To learn more about the public API, please use automatically generated API reference or generated TypeDoc comments. diff --git a/x-pack/plugins/screenshotting/common/context.ts b/x-pack/plugins/screenshotting/common/context.ts new file mode 100644 index 0000000000000..c47f8706533b8 --- /dev/null +++ b/x-pack/plugins/screenshotting/common/context.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/** + * Screenshot context. + * This is a serializable object that can be passed from the screenshotting backend and then deserialized on the target page. + */ +export type Context = Record; + +/** + * @interal + */ +export const SCREENSHOTTING_CONTEXT_KEY = '__SCREENSHOTTING_CONTEXT_KEY__'; diff --git a/x-pack/plugins/reporting/server/browsers/chromium/driver/index.ts b/x-pack/plugins/screenshotting/common/index.ts similarity index 66% rename from x-pack/plugins/reporting/server/browsers/chromium/driver/index.ts rename to x-pack/plugins/screenshotting/common/index.ts index afd31608d5a6e..04296dd5426b5 100644 --- a/x-pack/plugins/reporting/server/browsers/chromium/driver/index.ts +++ b/x-pack/plugins/screenshotting/common/index.ts @@ -5,4 +5,6 @@ * 2.0. */ -export { HeadlessChromiumDriver } from './chromium_driver'; +export type { Context } from './context'; +export type { LayoutParams } from './layout'; +export { LayoutTypes } from './layout'; diff --git a/x-pack/plugins/screenshotting/common/layout.ts b/x-pack/plugins/screenshotting/common/layout.ts new file mode 100644 index 0000000000000..aade05eeea04e --- /dev/null +++ b/x-pack/plugins/screenshotting/common/layout.ts @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Ensure, SerializableRecord } from '@kbn/utility-types'; + +/** + * @internal + */ +export type Size = Ensure< + { + /** + * Layout width. + */ + width: number; + + /** + * Layout height. + */ + height: number; + }, + SerializableRecord +>; + +/** + * @internal + */ +export interface LayoutSelectorDictionary { + screenshot: string; + renderComplete: string; + renderError: string; + renderErrorAttribute: string; + itemsCountAttribute: string; + timefilterDurationAttribute: string; +} + +/** + * Screenshot layout parameters. + */ +export type LayoutParams = Ensure< + { + /** + * Unique layout name. + */ + id?: string; + + /** + * Layout sizing. + */ + dimensions?: Size; + + /** + * Element selectors determining the page state. + */ + selectors?: Partial; + + /** + * Page zoom. + */ + zoom?: number; + }, + SerializableRecord +>; + +/** + * Supported layout types. + */ +export const LayoutTypes = { + PRESERVE_LAYOUT: 'preserve_layout', + PRINT: 'print', + CANVAS: 'canvas', // no margins or branding in the layout +}; diff --git a/x-pack/plugins/screenshotting/jest.config.js b/x-pack/plugins/screenshotting/jest.config.js new file mode 100644 index 0000000000000..a02d667f86a19 --- /dev/null +++ b/x-pack/plugins/screenshotting/jest.config.js @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/screenshotting'], + coverageDirectory: '/target/kibana-coverage/jest/x-pack/plugins/screenshotting', + coverageReporters: ['text', 'html'], + collectCoverageFrom: ['/x-pack/plugins/screenshotting/server/**/*.{ts}'], +}; diff --git a/x-pack/plugins/screenshotting/kibana.json b/x-pack/plugins/screenshotting/kibana.json new file mode 100644 index 0000000000000..32446551627e0 --- /dev/null +++ b/x-pack/plugins/screenshotting/kibana.json @@ -0,0 +1,14 @@ +{ + "id": "screenshotting", + "version": "8.0.0", + "kibanaVersion": "kibana", + "owner": { + "name": "Kibana Reporting Services", + "githubTeam": "kibana-reporting-services" + }, + "description": "Kibana Screenshotting Plugin", + "requiredPlugins": ["screenshotMode"], + "configPath": ["xpack", "screenshotting"], + "server": true, + "ui": true +} diff --git a/x-pack/plugins/screenshotting/public/context_storage.ts b/x-pack/plugins/screenshotting/public/context_storage.ts new file mode 100644 index 0000000000000..76a2cf231cf83 --- /dev/null +++ b/x-pack/plugins/screenshotting/public/context_storage.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Context, SCREENSHOTTING_CONTEXT_KEY } from '../common/context'; + +declare global { + interface Window { + [SCREENSHOTTING_CONTEXT_KEY]?: Context; + } +} + +export class ContextStorage { + get(): T { + return (window[SCREENSHOTTING_CONTEXT_KEY] ?? {}) as T; + } +} diff --git a/x-pack/plugins/screenshotting/public/index.ts b/x-pack/plugins/screenshotting/public/index.ts new file mode 100644 index 0000000000000..659dbc81917a7 --- /dev/null +++ b/x-pack/plugins/screenshotting/public/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ScreenshottingPlugin } from './plugin'; + +/** + * Screenshotting plugin entry point. + */ +export function plugin(...args: ConstructorParameters) { + return new ScreenshottingPlugin(...args); +} + +export { LayoutTypes } from '../common'; +export type { ScreenshottingSetup, ScreenshottingStart } from './plugin'; diff --git a/x-pack/plugins/screenshotting/public/plugin.tsx b/x-pack/plugins/screenshotting/public/plugin.tsx new file mode 100755 index 0000000000000..4ba5046b8a881 --- /dev/null +++ b/x-pack/plugins/screenshotting/public/plugin.tsx @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Plugin } from 'src/core/public'; +import { ContextStorage } from './context_storage'; + +/** + * Setup public contract. + */ +export interface ScreenshottingSetup { + /** + * Gathers screenshot context that has been set on the backend. + */ + getContext: ContextStorage['get']; +} + +/** + * Start public contract. + */ +export type ScreenshottingStart = ScreenshottingSetup; + +export class ScreenshottingPlugin implements Plugin { + private contextStorage = new ContextStorage(); + + setup(): ScreenshottingSetup { + return { + getContext: () => this.contextStorage.get(), + }; + } + + start(): ScreenshottingStart { + return { + getContext: () => this.contextStorage.get(), + }; + } + + stop() {} +} diff --git a/x-pack/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts b/x-pack/plugins/screenshotting/server/browsers/chromium/driver.ts similarity index 75% rename from x-pack/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts rename to x-pack/plugins/screenshotting/server/browsers/chromium/driver.ts index 0f2572ff2b2e4..245572efe9348 100644 --- a/x-pack/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts +++ b/x-pack/plugins/screenshotting/server/browsers/chromium/driver.ts @@ -8,22 +8,56 @@ import { i18n } from '@kbn/i18n'; import { map, truncate } from 'lodash'; import open from 'opn'; -import puppeteer, { ElementHandle, EvaluateFn, SerializableOrJSHandle } from 'puppeteer'; +import puppeteer, { ElementHandle, EvaluateFn, Page, SerializableOrJSHandle } from 'puppeteer'; import { parse as parseUrl } from 'url'; -import type { LocatorParams } from '../../../../common/types'; -import { REPORTING_REDIRECT_LOCATOR_STORE_KEY } from '../../../../common/constants'; -import { getDisallowedOutgoingUrlError } from '../'; -import { ReportingCore } from '../../..'; -import { KBN_SCREENSHOT_MODE_HEADER } from '../../../../../../../src/plugins/screenshot_mode/server'; -import { ConditionalHeaders, ConditionalHeadersConditions } from '../../../export_types/common'; -import { LevelLogger } from '../../../lib'; -import { Layout, ViewZoomWidthHeight } from '../../../lib/layouts/layout'; -import { ElementPosition } from '../../../lib/screenshots'; -import { allowRequest, NetworkPolicy } from '../../network_policy'; - -export interface ChromiumDriverOptions { - inspect: boolean; - networkPolicy: NetworkPolicy; +import { Logger } from 'src/core/server'; +import type { Layout } from 'src/plugins/screenshot_mode/common'; +import { + KBN_SCREENSHOT_MODE_HEADER, + ScreenshotModePluginSetup, +} from '../../../../../../src/plugins/screenshot_mode/server'; +import { Context, SCREENSHOTTING_CONTEXT_KEY } from '../../../common/context'; +import { ConfigType } from '../../config'; +import { allowRequest } from '../network_policy'; + +export interface ConditionalHeadersConditions { + protocol: string; + hostname: string; + port: number; + basePath: string; +} + +export interface ConditionalHeaders { + headers: Record; + conditions: ConditionalHeadersConditions; +} + +export interface ElementPosition { + boundingClientRect: { + // modern browsers support x/y, but older ones don't + top: number; + left: number; + width: number; + height: number; + }; + scroll: { + x: number; + y: number; + }; +} + +export interface Viewport { + zoom: number; + width: number; + height: number; +} + +interface OpenOptions { + conditionalHeaders: ConditionalHeaders; + context?: Context; + waitForSelector: string; + timeout: number; + layout?: Layout; } interface WaitForSelectorOpts { @@ -56,28 +90,30 @@ interface InterceptedRequest { const WAIT_FOR_DELAY_MS: number = 100; -export class HeadlessChromiumDriver { - private readonly page: puppeteer.Page; - private readonly inspect: boolean; - private readonly networkPolicy: NetworkPolicy; +function getDisallowedOutgoingUrlError(interceptedUrl: string) { + return new Error( + i18n.translate('xpack.screenshotting.chromiumDriver.disallowedOutgoingUrl', { + defaultMessage: `Received disallowed outgoing URL: "{interceptedUrl}". Failing the request and closing the browser.`, + values: { interceptedUrl }, + }) + ); +} +/** + * @internal + */ +export class HeadlessChromiumDriver { private listenersAttached = false; private interceptedCount = 0; - private core: ReportingCore; constructor( - core: ReportingCore, - page: puppeteer.Page, - { inspect, networkPolicy }: ChromiumDriverOptions - ) { - this.core = core; - this.page = page; - this.inspect = inspect; - this.networkPolicy = networkPolicy; - } + private screenshotMode: ScreenshotModePluginSetup, + private config: ConfigType, + private readonly page: Page + ) {} private allowRequest(url: string) { - return !this.networkPolicy.enabled || allowRequest(url, this.networkPolicy.rules); + return !this.config.networkPolicy.enabled || allowRequest(url, this.config.networkPolicy.rules); } private truncateUrl(url: string) { @@ -90,22 +126,16 @@ export class HeadlessChromiumDriver { /* * Call Page.goto and wait to see the Kibana DOM content */ - public async open( + async open( url: string, { conditionalHeaders, + context, + layout, waitForSelector: pageLoadSelector, timeout, - locator, - layout, - }: { - conditionalHeaders: ConditionalHeaders; - waitForSelector: string; - timeout: number; - locator?: LocatorParams; - layout?: Layout; - }, - logger: LevelLogger + }: OpenOptions, + logger: Logger ): Promise { logger.info(`opening url ${url}`); @@ -116,13 +146,9 @@ export class HeadlessChromiumDriver { * Integrate with the screenshot mode plugin contract by calling this function before any other * scripts have run on the browser page. */ - await this.page.evaluateOnNewDocument(this.core.getEnableScreenshotMode()); - - if (layout) { - await this.page.evaluateOnNewDocument(this.core.getSetScreenshotLayout(), layout.id); - } + await this.page.evaluateOnNewDocument(this.screenshotMode.setScreenshotModeEnabled); - if (locator) { + if (context) { await this.page.evaluateOnNewDocument( (key: string, value: unknown) => { Object.defineProperty(window, key, { @@ -132,18 +158,20 @@ export class HeadlessChromiumDriver { value, }); }, - REPORTING_REDIRECT_LOCATOR_STORE_KEY, - locator + SCREENSHOTTING_CONTEXT_KEY, + context ); } - await this.page.setRequestInterception(true); + if (layout) { + await this.page.evaluateOnNewDocument(this.screenshotMode.setScreenshotLayout, layout); + } + await this.page.setRequestInterception(true); this.registerListeners(conditionalHeaders, logger); - await this.page.goto(url, { waitUntil: 'domcontentloaded' }); - if (this.inspect) { + if (this.config.browser.chromium.inspect) { await this.launchDebugger(); } @@ -159,14 +187,14 @@ export class HeadlessChromiumDriver { /* * Let modules poll if Chrome is still running so they can short circuit if needed */ - public isPageOpen() { + isPageOpen() { return !this.page.isClosed(); } /* * Call Page.screenshot and return a base64-encoded string of the image */ - public async screenshot(elementPosition: ElementPosition): Promise { + async screenshot(elementPosition: ElementPosition): Promise { const { boundingClientRect, scroll } = elementPosition; const screenshot = await this.page.screenshot({ clip: { @@ -188,32 +216,28 @@ export class HeadlessChromiumDriver { return undefined; } - public async evaluate( - { fn, args = [] }: EvaluateOpts, - meta: EvaluateMetaOpts, - logger: LevelLogger - ) { + evaluate({ fn, args = [] }: EvaluateOpts, meta: EvaluateMetaOpts, logger: Logger): Promise { logger.debug(`evaluate ${meta.context}`); - const result = await this.page.evaluate(fn, ...args); - return result; + + return this.page.evaluate(fn, ...args); } - public async waitForSelector( + async waitForSelector( selector: string, opts: WaitForSelectorOpts, context: EvaluateMetaOpts, - logger: LevelLogger + logger: Logger ): Promise> { const { timeout } = opts; logger.debug(`waitForSelector ${selector}`); - const resp = await this.page.waitForSelector(selector, { timeout }); // override default 30000ms + const response = await this.page.waitForSelector(selector, { timeout }); // override default 30000ms - if (!resp) { + if (!response) { throw new Error(`Failure in waitForSelector: void response! Context: ${context.context}`); } logger.debug(`waitForSelector ${selector} resolved`); - return resp; + return response; } public async waitFor({ @@ -228,9 +252,9 @@ export class HeadlessChromiumDriver { await this.page.waitForFunction(fn, { timeout, polling: WAIT_FOR_DELAY_MS }, ...args); } - public async setViewport( - { width: _width, height: _height, zoom }: ViewZoomWidthHeight, - logger: LevelLogger + async setViewport( + { width: _width, height: _height, zoom }: Viewport, + logger: Logger ): Promise { const width = Math.floor(_width); const height = Math.floor(_height); @@ -245,7 +269,7 @@ export class HeadlessChromiumDriver { }); } - private registerListeners(conditionalHeaders: ConditionalHeaders, logger: LevelLogger) { + private registerListeners(conditionalHeaders: ConditionalHeaders, logger: Logger) { if (this.listenersAttached) { return; } @@ -300,10 +324,13 @@ export class HeadlessChromiumDriver { }); } catch (err) { logger.error( - i18n.translate('xpack.reporting.chromiumDriver.failedToCompleteRequestUsingHeaders', { - defaultMessage: 'Failed to complete a request using headers: {error}', - values: { error: err }, - }) + i18n.translate( + 'xpack.screenshotting.chromiumDriver.failedToCompleteRequestUsingHeaders', + { + defaultMessage: 'Failed to complete a request using headers: {error}', + values: { error: err }, + } + ) ); } } else { @@ -313,7 +340,7 @@ export class HeadlessChromiumDriver { await client.send('Fetch.continueRequest', { requestId }); } catch (err) { logger.error( - i18n.translate('xpack.reporting.chromiumDriver.failedToCompleteRequest', { + i18n.translate('xpack.screenshotting.chromiumDriver.failedToCompleteRequest', { defaultMessage: 'Failed to complete a request: {error}', values: { error: err }, }) diff --git a/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/args.ts b/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/args.ts similarity index 81% rename from x-pack/plugins/reporting/server/browsers/chromium/driver_factory/args.ts rename to x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/args.ts index 07ae13fa31849..e5985082b3c1c 100644 --- a/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/args.ts +++ b/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/args.ts @@ -5,18 +5,23 @@ * 2.0. */ -import { CaptureConfig } from '../../../../server/types'; -import { DEFAULT_VIEWPORT } from '../../../../common/constants'; +import type { ConfigType } from '../../../config'; -type BrowserConfig = CaptureConfig['browser']['chromium']; +interface Viewport { + height: number; + width: number; +} + +type Proxy = ConfigType['browser']['chromium']['proxy']; interface LaunchArgs { userDataDir: string; - disableSandbox: BrowserConfig['disableSandbox']; - proxy: BrowserConfig['proxy']; + viewport?: Viewport; + disableSandbox?: boolean; + proxy: Proxy; } -export const args = ({ userDataDir, disableSandbox, proxy: proxyConfig }: LaunchArgs) => { +export const args = ({ userDataDir, disableSandbox, viewport, proxy: proxyConfig }: LaunchArgs) => { const flags = [ // Disable built-in Google Translate service '--disable-translate', @@ -41,14 +46,17 @@ export const args = ({ userDataDir, disableSandbox, proxy: proxyConfig }: Launch '--disable-gpu', '--headless', '--hide-scrollbars', - // NOTE: setting the window size does NOT set the viewport size: viewport and window size are different. - // The viewport may later need to be resized depending on the position of the clip area. - // These numbers come from the job parameters, so this is a close guess. - `--window-size=${Math.floor(DEFAULT_VIEWPORT.width)},${Math.floor(DEFAULT_VIEWPORT.height)}`, // allow screenshot clip region to go outside of the viewport `--mainFrameClipsContent=false`, ]; + if (viewport) { + // NOTE: setting the window size does NOT set the viewport size: viewport and window size are different. + // The viewport may later need to be resized depending on the position of the clip area. + // These numbers come from the job parameters, so this is a close guess. + flags.push(`--window-size=${Math.floor(viewport.width)},${Math.floor(viewport.height)}`); + } + if (proxyConfig.enabled) { flags.push(`--proxy-server=${proxyConfig.server}`); if (proxyConfig.bypass) { diff --git a/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/index.test.ts b/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/index.test.ts new file mode 100644 index 0000000000000..23e276541465a --- /dev/null +++ b/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/index.test.ts @@ -0,0 +1,84 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import puppeteer from 'puppeteer'; +import * as Rx from 'rxjs'; +import { take } from 'rxjs/operators'; +import type { Logger } from 'src/core/server'; +import type { ScreenshotModePluginSetup } from 'src/plugins/screenshot_mode/server'; +import { ConfigType } from '../../../config'; +import { HeadlessChromiumDriverFactory } from '.'; + +jest.mock('puppeteer'); + +describe('HeadlessChromiumDriverFactory', () => { + const path = 'path/to/headless_shell'; + const config = { + browser: { + chromium: { + proxy: {}, + }, + }, + } as ConfigType; + let logger: jest.Mocked; + let screenshotMode: jest.Mocked; + let factory: HeadlessChromiumDriverFactory; + + beforeEach(async () => { + logger = { + debug: jest.fn(), + error: jest.fn(), + info: jest.fn(), + warn: jest.fn(), + get: jest.fn(() => logger), + } as unknown as typeof logger; + screenshotMode = {} as unknown as typeof screenshotMode; + + (puppeteer as jest.Mocked).launch.mockResolvedValue({ + newPage: jest.fn().mockResolvedValue({ + target: jest.fn(() => ({ + createCDPSession: jest.fn().mockResolvedValue({ + send: jest.fn(), + }), + })), + emulateTimezone: jest.fn(), + setDefaultTimeout: jest.fn(), + }), + close: jest.fn(), + process: jest.fn(), + } as unknown as puppeteer.Browser); + + factory = new HeadlessChromiumDriverFactory(screenshotMode, config, logger, path); + jest.spyOn(factory, 'getBrowserLogger').mockReturnValue(Rx.EMPTY); + jest.spyOn(factory, 'getProcessLogger').mockReturnValue(Rx.EMPTY); + jest.spyOn(factory, 'getPageExit').mockReturnValue(Rx.EMPTY); + }); + + describe('createPage', () => { + it('returns browser driver and process exit observable', async () => { + await expect( + factory.createPage({ openUrlTimeout: 0 }).pipe(take(1)).toPromise() + ).resolves.toEqual( + expect.objectContaining({ + driver: expect.anything(), + exit$: expect.anything(), + }) + ); + }); + + it('rejects if Puppeteer launch fails', async () => { + (puppeteer as jest.Mocked).launch.mockRejectedValue( + `Puppeteer Launch mock fail.` + ); + expect(() => + factory.createPage({ openUrlTimeout: 0 }).pipe(take(1)).toPromise() + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Error spawning Chromium browser! Puppeteer Launch mock fail."` + ); + }); + }); +}); diff --git a/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/index.ts b/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/index.ts new file mode 100644 index 0000000000000..e9656013140c2 --- /dev/null +++ b/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/index.ts @@ -0,0 +1,379 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { getDataPath } from '@kbn/utils'; +import { spawn } from 'child_process'; +import del from 'del'; +import fs from 'fs'; +import { uniq } from 'lodash'; +import path from 'path'; +import puppeteer, { Browser, ConsoleMessage, HTTPRequest, Page } from 'puppeteer'; +import { createInterface } from 'readline'; +import * as Rx from 'rxjs'; +import { InnerSubscriber } from 'rxjs/internal/InnerSubscriber'; +import { catchError, ignoreElements, map, mergeMap, reduce, takeUntil, tap } from 'rxjs/operators'; +import type { Logger } from 'src/core/server'; +import type { ScreenshotModePluginSetup } from 'src/plugins/screenshot_mode/server'; +import { ConfigType } from '../../../config'; +import { getChromiumDisconnectedError } from '../'; +import { safeChildProcess } from '../../safe_child_process'; +import { HeadlessChromiumDriver } from '../driver'; +import { args } from './args'; +import { getMetrics, PerformanceMetrics } from './metrics'; + +interface CreatePageOptions { + browserTimezone?: string; + openUrlTimeout: number; +} + +interface CreatePageResult { + driver: HeadlessChromiumDriver; + exit$: Rx.Observable; + metrics$: Rx.Observable; +} + +export const DEFAULT_VIEWPORT = { + width: 1950, + height: 1200, +}; + +// Default args used by pptr +// https://github.com/puppeteer/puppeteer/blob/13ea347/src/node/Launcher.ts#L168 +const DEFAULT_ARGS = [ + '--disable-background-networking', + '--enable-features=NetworkService,NetworkServiceInProcess', + '--disable-background-timer-throttling', + '--disable-backgrounding-occluded-windows', + '--disable-breakpad', + '--disable-client-side-phishing-detection', + '--disable-component-extensions-with-background-pages', + '--disable-default-apps', + '--disable-dev-shm-usage', + '--disable-extensions', + '--disable-features=TranslateUI', + '--disable-hang-monitor', + '--disable-ipc-flooding-protection', + '--disable-popup-blocking', + '--disable-prompt-on-repost', + '--disable-renderer-backgrounding', + '--disable-sync', + '--force-color-profile=srgb', + '--metrics-recording-only', + '--no-first-run', + '--enable-automation', + '--password-store=basic', + '--use-mock-keychain', + '--remote-debugging-port=0', + '--headless', +]; + +const DIAGNOSTIC_TIME = 5 * 1000; + +export class HeadlessChromiumDriverFactory { + private userDataDir = fs.mkdtempSync(path.join(getDataPath(), 'chromium-')); + type = 'chromium'; + + constructor( + private screenshotMode: ScreenshotModePluginSetup, + private config: ConfigType, + private logger: Logger, + private binaryPath: string + ) { + if (this.config.browser.chromium.disableSandbox) { + logger.warn(`Enabling the Chromium sandbox provides an additional layer of protection.`); + } + } + + private getChromiumArgs() { + return args({ + userDataDir: this.userDataDir, + disableSandbox: this.config.browser.chromium.disableSandbox, + proxy: this.config.browser.chromium.proxy, + viewport: DEFAULT_VIEWPORT, + }); + } + + /* + * Return an observable to objects which will drive screenshot capture for a page + */ + createPage( + { browserTimezone, openUrlTimeout }: CreatePageOptions, + pLogger = this.logger + ): Rx.Observable { + // FIXME: 'create' is deprecated + return Rx.Observable.create(async (observer: InnerSubscriber) => { + const logger = pLogger.get('browser-driver'); + logger.info(`Creating browser page driver`); + + const chromiumArgs = this.getChromiumArgs(); + logger.debug(`Chromium launch args set to: ${chromiumArgs}`); + + let browser: Browser | undefined; + + try { + browser = await puppeteer.launch({ + pipe: !this.config.browser.chromium.inspect, + userDataDir: this.userDataDir, + executablePath: this.binaryPath, + ignoreHTTPSErrors: true, + handleSIGHUP: false, + args: chromiumArgs, + env: { + TZ: browserTimezone, + }, + }); + } catch (err) { + observer.error(new Error(`Error spawning Chromium browser! ${err}`)); + return; + } + + const page = await browser.newPage(); + const devTools = await page.target().createCDPSession(); + + await devTools.send('Performance.enable', { timeDomain: 'timeTicks' }); + const startMetrics = await devTools.send('Performance.getMetrics'); + const metrics$ = new Rx.Subject(); + + // Log version info for debugging / maintenance + const versionInfo = await devTools.send('Browser.getVersion'); + logger.debug(`Browser version: ${JSON.stringify(versionInfo)}`); + + await page.emulateTimezone(browserTimezone); + + // Set the default timeout for all navigation methods to the openUrl timeout + // All waitFor methods have their own timeout config passed in to them + page.setDefaultTimeout(openUrlTimeout); + + logger.debug(`Browser page driver created`); + + const childProcess = { + async kill() { + try { + if (devTools && startMetrics) { + const endMetrics = await devTools.send('Performance.getMetrics'); + const metrics = getMetrics(startMetrics, endMetrics); + const { cpuInPercentage, memoryInMegabytes } = metrics; + + metrics$.next(metrics); + logger.debug( + `Chromium consumed CPU ${cpuInPercentage}% Memory ${memoryInMegabytes}MB` + ); + } + } catch (error) { + logger.error(error); + } finally { + metrics$.complete(); + } + + try { + await browser?.close(); + } catch (err) { + // do not throw + logger.error(err); + } + }, + }; + const { terminate$ } = safeChildProcess(logger, childProcess); + + // this is adding unsubscribe logic to our observer + // so that if our observer unsubscribes, we terminate our child-process + observer.add(() => { + logger.debug(`The browser process observer has unsubscribed. Closing the browser...`); + childProcess.kill(); // ignore async + }); + + // make the observer subscribe to terminate$ + observer.add( + terminate$ + .pipe( + tap((signal) => { + logger.debug(`Termination signal received: ${signal}`); + }), + ignoreElements() + ) + .subscribe(observer) + ); + + // taps the browser log streams and combine them to Kibana logs + this.getBrowserLogger(page, logger).subscribe(); + this.getProcessLogger(browser, logger).subscribe(); + + // HeadlessChromiumDriver: object to "drive" a browser page + const driver = new HeadlessChromiumDriver(this.screenshotMode, this.config, page); + + // Rx.Observable: stream to interrupt page capture + const exit$ = this.getPageExit(browser, page); + + observer.next({ driver, exit$, metrics$: metrics$.asObservable() }); + + // unsubscribe logic makes a best-effort attempt to delete the user data directory used by chromium + observer.add(() => { + const userDataDir = this.userDataDir; + logger.debug(`deleting chromium user data directory at [${userDataDir}]`); + // the unsubscribe function isn't `async` so we're going to make our best effort at + // deleting the userDataDir and if it fails log an error. + del(userDataDir, { force: true }).catch((error) => { + logger.error(`error deleting user data directory at [${userDataDir}]!`); + logger.error(error); + }); + }); + }); + } + + getBrowserLogger(page: Page, logger: Logger): Rx.Observable { + const consoleMessages$ = Rx.fromEvent(page, 'console').pipe( + map((line) => { + const formatLine = () => `{ text: "${line.text()?.trim()}", url: ${line.location()?.url} }`; + + if (line.type() === 'error') { + logger.get('headless-browser-console').error(`Error in browser console: ${formatLine()}`); + } else { + logger + .get(`headless-browser-console:${line.type()}`) + .debug(`Message in browser console: ${formatLine()}`); + } + }) + ); + + const uncaughtExceptionPageError$ = Rx.fromEvent(page, 'pageerror').pipe( + map((err) => { + logger.warn( + i18n.translate('xpack.screenshotting.browsers.chromium.pageErrorDetected', { + defaultMessage: `Reporting encountered an uncaught error on the page that will be ignored: {err}`, + values: { err: err.toString() }, + }) + ); + }) + ); + + const pageRequestFailed$ = Rx.fromEvent(page, 'requestfailed').pipe( + map((req) => { + const failure = req.failure && req.failure(); + if (failure) { + logger.warn( + `Request to [${req.url()}] failed! [${failure.errorText}]. This error will be ignored.` + ); + } + }) + ); + + return Rx.merge(consoleMessages$, uncaughtExceptionPageError$, pageRequestFailed$); + } + + getProcessLogger(browser: Browser, logger: Logger): Rx.Observable { + const childProcess = browser.process(); + // NOTE: The browser driver can not observe stdout and stderr of the child process + // Puppeteer doesn't give a handle to the original ChildProcess object + // See https://github.com/GoogleChrome/puppeteer/issues/1292#issuecomment-521470627 + + if (childProcess == null) { + throw new TypeError('childProcess is null or undefined!'); + } + + // just log closing of the process + const processClose$ = Rx.fromEvent(childProcess, 'close').pipe( + tap(() => { + logger.get('headless-browser-process').debug('child process closed'); + }) + ); + + return processClose$; // ideally, this would also merge with observers for stdout and stderr + } + + getPageExit(browser: Browser, page: Page) { + const pageError$ = Rx.fromEvent(page, 'error').pipe( + mergeMap((err) => { + return Rx.throwError( + i18n.translate('xpack.screenshotting.browsers.chromium.errorDetected', { + defaultMessage: 'Reporting encountered an error: {err}', + values: { err: err.toString() }, + }) + ); + }) + ); + + const browserDisconnect$ = Rx.fromEvent(browser, 'disconnected').pipe( + mergeMap(() => Rx.throwError(getChromiumDisconnectedError())) + ); + + return Rx.merge(pageError$, browserDisconnect$); + } + + diagnose(overrideFlags: string[] = []): Rx.Observable { + const kbnArgs = this.getChromiumArgs(); + const finalArgs = uniq([...DEFAULT_ARGS, ...kbnArgs, ...overrideFlags]); + + // On non-windows platforms, `detached: true` makes child process a + // leader of a new process group, making it possible to kill child + // process tree with `.kill(-pid)` command. @see + // https://nodejs.org/api/child_process.html#child_process_options_detached + const browserProcess = spawn(this.binaryPath, finalArgs, { + detached: process.platform !== 'win32', + }); + + const rl = createInterface({ input: browserProcess.stderr }); + + const exit$ = Rx.fromEvent(browserProcess, 'exit').pipe( + map((code) => { + this.logger.error(`Browser exited abnormally, received code: ${code}`); + return i18n.translate('xpack.screenshotting.diagnostic.browserCrashed', { + defaultMessage: `Browser exited abnormally during startup`, + }); + }) + ); + + const error$ = Rx.fromEvent(browserProcess, 'error').pipe( + map((err) => { + this.logger.error(`Browser process threw an error on startup`); + this.logger.error(err as string | Error); + return i18n.translate('xpack.screenshotting.diagnostic.browserErrored', { + defaultMessage: `Browser process threw an error on startup`, + }); + }) + ); + + const browserProcessLogger = this.logger.get('chromium-stderr'); + const log$ = Rx.fromEvent(rl, 'line').pipe( + tap((message: unknown) => { + if (typeof message === 'string') { + browserProcessLogger.info(message); + } + }) + ); + + // Collect all events (exit, error and on log-lines), but let chromium keep spitting out + // logs as sometimes it's "bind" successfully for remote connections, but later emit + // a log indicative of an issue (for example, no default font found). + return Rx.merge(exit$, error$, log$).pipe( + takeUntil(Rx.timer(DIAGNOSTIC_TIME)), + reduce((acc, curr) => `${acc}${curr}\n`, ''), + tap(() => { + if (browserProcess && browserProcess.pid && !browserProcess.killed) { + browserProcess.kill('SIGKILL'); + this.logger.info( + `Successfully sent 'SIGKILL' to browser process (PID: ${browserProcess.pid})` + ); + } + browserProcess.removeAllListeners(); + rl.removeAllListeners(); + rl.close(); + del(this.userDataDir, { force: true }).catch((error) => { + this.logger.error(`Error deleting user data directory at [${this.userDataDir}]!`); + this.logger.error(error); + }); + }), + catchError((error) => { + this.logger.error(error); + + return Rx.of(error); + }) + ); + } +} + +export type { PerformanceMetrics }; diff --git a/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/metrics.test.ts b/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/metrics.test.ts similarity index 100% rename from x-pack/plugins/reporting/server/browsers/chromium/driver_factory/metrics.test.ts rename to x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/metrics.test.ts diff --git a/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/metrics.ts b/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/metrics.ts similarity index 81% rename from x-pack/plugins/reporting/server/browsers/chromium/driver_factory/metrics.ts rename to x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/metrics.ts index 1659f28dea9b0..6e9971324ae4b 100644 --- a/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/metrics.ts +++ b/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/metrics.ts @@ -29,10 +29,28 @@ interface NormalizedMetrics extends Required { ProcessTime: number; } -interface PerformanceMetrics { +/** + * Collected performance metrics during a screenshotting session. + */ +export interface PerformanceMetrics { + /** + * The percentage of CPU time spent by the browser divided by number or cores. + */ cpu: number; + + /** + * The percentage of CPU in percent untis. + */ cpuInPercentage: number; + + /** + * The total amount of memory used by the browser. + */ memory: number; + + /** + * The total amount of memory used by the browser in megabytes. + */ memoryInMegabytes: number; } diff --git a/x-pack/plugins/screenshotting/server/browsers/chromium/index.ts b/x-pack/plugins/screenshotting/server/browsers/chromium/index.ts new file mode 100644 index 0000000000000..c51ee0e8b8651 --- /dev/null +++ b/x-pack/plugins/screenshotting/server/browsers/chromium/index.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const getChromiumDisconnectedError = () => + new Error( + i18n.translate('xpack.screenshotting.screencapture.browserWasClosed', { + defaultMessage: 'Browser was closed unexpectedly! Check the server logs for more info.', + }) + ); + +export { ChromiumArchivePaths } from './paths'; +export type { ConditionalHeaders } from './driver'; +export { HeadlessChromiumDriver } from './driver'; +export type { PerformanceMetrics } from './driver_factory'; +export { DEFAULT_VIEWPORT, HeadlessChromiumDriverFactory } from './driver_factory'; diff --git a/x-pack/plugins/reporting/server/browsers/chromium/paths.ts b/x-pack/plugins/screenshotting/server/browsers/chromium/paths.ts similarity index 100% rename from x-pack/plugins/reporting/server/browsers/chromium/paths.ts rename to x-pack/plugins/screenshotting/server/browsers/chromium/paths.ts diff --git a/x-pack/plugins/reporting/server/browsers/download/checksum.test.ts b/x-pack/plugins/screenshotting/server/browsers/download/checksum.test.ts similarity index 100% rename from x-pack/plugins/reporting/server/browsers/download/checksum.test.ts rename to x-pack/plugins/screenshotting/server/browsers/download/checksum.test.ts diff --git a/x-pack/plugins/reporting/server/browsers/download/checksum.ts b/x-pack/plugins/screenshotting/server/browsers/download/checksum.ts similarity index 62% rename from x-pack/plugins/reporting/server/browsers/download/checksum.ts rename to x-pack/plugins/screenshotting/server/browsers/download/checksum.ts index 35feb1ff534ab..9b177e0b4c756 100644 --- a/x-pack/plugins/reporting/server/browsers/download/checksum.ts +++ b/x-pack/plugins/screenshotting/server/browsers/download/checksum.ts @@ -7,16 +7,15 @@ import { createHash } from 'crypto'; import { createReadStream } from 'fs'; -import { Readable } from 'stream'; - -function readableEnd(stream: Readable) { - return new Promise((resolve, reject) => { - stream.on('error', reject).on('end', resolve); - }); -} +import { finished } from 'stream'; +import { promisify } from 'util'; export async function md5(path: string) { const hash = createHash('md5'); - await readableEnd(createReadStream(path).on('data', (chunk) => hash.update(chunk))); + const stream = createReadStream(path); + + stream.on('data', (chunk) => hash.update(chunk)); + await promisify(finished)(stream, { writable: false }); + return hash.digest('hex'); } diff --git a/x-pack/plugins/screenshotting/server/browsers/download/fetch.test.ts b/x-pack/plugins/screenshotting/server/browsers/download/fetch.test.ts new file mode 100644 index 0000000000000..cc22f152216af --- /dev/null +++ b/x-pack/plugins/screenshotting/server/browsers/download/fetch.test.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import mockFs from 'mock-fs'; +import axios from 'axios'; +import { createHash } from 'crypto'; +import { readFileSync } from 'fs'; +import { resolve as resolvePath } from 'path'; +import { Readable } from 'stream'; +import { fetch } from './fetch'; + +const TEMP_DIR = resolvePath(__dirname, '__tmp__'); +const TEMP_FILE = resolvePath(TEMP_DIR, 'foo/bar/download'); + +describe('fetch', () => { + beforeEach(() => { + jest.spyOn(axios, 'request').mockResolvedValue({ + data: new Readable({ + read() { + this.push('foobar'); + this.push(null); + }, + }), + }); + + mockFs(); + }); + + afterEach(() => { + mockFs.restore(); + jest.resetAllMocks(); + }); + + test('downloads the url to the path', async () => { + await fetch('url', TEMP_FILE); + + expect(readFileSync(TEMP_FILE, 'utf8')).toEqual('foobar'); + }); + + test('returns the md5 hex hash of the http body', async () => { + const hash = createHash('md5').update('foobar').digest('hex'); + + await expect(fetch('url', TEMP_FILE)).resolves.toEqual(hash); + }); + + test('throws if request emits an error', async () => { + (axios.request as jest.Mock).mockImplementationOnce(async () => { + throw new Error('foo'); + }); + + await expect(fetch('url', TEMP_FILE)).rejects.toThrow('foo'); + }); +}); diff --git a/x-pack/plugins/reporting/server/browsers/download/download.ts b/x-pack/plugins/screenshotting/server/browsers/download/fetch.ts similarity index 55% rename from x-pack/plugins/reporting/server/browsers/download/download.ts rename to x-pack/plugins/screenshotting/server/browsers/download/fetch.ts index 528395fe1afb2..aa52f7a4491c4 100644 --- a/x-pack/plugins/reporting/server/browsers/download/download.ts +++ b/x-pack/plugins/screenshotting/server/browsers/download/fetch.ts @@ -9,17 +9,15 @@ import Axios from 'axios'; import { createHash } from 'crypto'; import { closeSync, mkdirSync, openSync, writeSync } from 'fs'; import { dirname } from 'path'; -import { GenericLevelLogger } from '../../lib/level_logger'; +import { finished, Readable } from 'stream'; +import { promisify } from 'util'; +import type { Logger } from 'src/core/server'; /** * Download a url and calculate it's checksum */ -export async function download( - url: string, - path: string, - logger: GenericLevelLogger -): Promise { - logger.info(`Downloading ${url} to ${path}`); +export async function fetch(url: string, path: string, logger?: Logger): Promise { + logger?.info(`Downloading ${url} to ${path}`); const hash = createHash('md5'); @@ -27,30 +25,23 @@ export async function download( const handle = openSync(path, 'w'); try { - const resp = await Axios.request({ + const response = await Axios.request({ url, method: 'GET', responseType: 'stream', }); - resp.data.on('data', (chunk: Buffer) => { + response.data.on('data', (chunk: Buffer) => { writeSync(handle, chunk); hash.update(chunk); }); - await new Promise((resolve, reject) => { - resp.data - .on('error', (err: Error) => { - logger.error(err); - reject(err); - }) - .on('end', () => { - logger.info(`Downloaded ${url}`); - resolve(); - }); - }); - } catch (err) { - throw new Error(`Unable to download ${url}: ${err}`); + await promisify(finished)(response.data, { writable: false }); + logger?.info(`Downloaded ${url}`); + } catch (error) { + logger?.error(error); + + throw new Error(`Unable to download ${url}: ${error}`); } finally { closeSync(handle); } diff --git a/x-pack/plugins/screenshotting/server/browsers/download/index.test.ts b/x-pack/plugins/screenshotting/server/browsers/download/index.test.ts new file mode 100644 index 0000000000000..f960b65859172 --- /dev/null +++ b/x-pack/plugins/screenshotting/server/browsers/download/index.test.ts @@ -0,0 +1,104 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import path from 'path'; +import mockFs from 'mock-fs'; +import { existsSync, readdirSync } from 'fs'; +import { ChromiumArchivePaths } from '../chromium'; +import { fetch } from './fetch'; +import { md5 } from './checksum'; +import { download } from '.'; + +jest.mock('./checksum'); +jest.mock('./fetch'); + +describe('ensureDownloaded', () => { + let paths: ChromiumArchivePaths; + + beforeEach(() => { + paths = new ChromiumArchivePaths(); + + (md5 as jest.MockedFunction).mockImplementation( + async (packagePath) => + paths.packages.find((packageInfo) => paths.resolvePath(packageInfo) === packagePath) + ?.archiveChecksum ?? 'some-md5' + ); + + (fetch as jest.MockedFunction).mockImplementation( + async (_url, packagePath) => + paths.packages.find((packageInfo) => paths.resolvePath(packageInfo) === packagePath) + ?.archiveChecksum ?? 'some-md5' + ); + + mockFs(); + }); + + afterEach(() => { + mockFs.restore(); + jest.resetAllMocks(); + }); + + it('should remove unexpected files', async () => { + const unexpectedPath1 = `${paths.archivesPath}/unexpected1`; + const unexpectedPath2 = `${paths.archivesPath}/unexpected2`; + + mockFs({ + [unexpectedPath1]: 'test', + [unexpectedPath2]: 'test', + }); + + await download(paths); + + expect(existsSync(unexpectedPath1)).toBe(false); + expect(existsSync(unexpectedPath2)).toBe(false); + }); + + it('should reject when download fails', async () => { + (fetch as jest.MockedFunction).mockRejectedValueOnce(new Error('some error')); + + await expect(download(paths)).rejects.toBeInstanceOf(Error); + }); + + it('should reject when downloaded md5 hash is different', async () => { + (fetch as jest.MockedFunction).mockResolvedValue('random-md5'); + + await expect(download(paths)).rejects.toBeInstanceOf(Error); + }); + + describe('when archives are already present', () => { + beforeEach(() => { + mockFs( + Object.fromEntries( + paths.packages.map((packageInfo) => [paths.resolvePath(packageInfo), '']) + ) + ); + }); + + it('should not download again', async () => { + await download(paths); + + expect(fetch).not.toHaveBeenCalled(); + expect(readdirSync(path.resolve(`${paths.archivesPath}/x64`))).toEqual( + expect.arrayContaining([ + 'chrome-win.zip', + 'chromium-70f5d88-linux_x64.zip', + 'chromium-d163fd7-darwin_x64.zip', + ]) + ); + expect(readdirSync(path.resolve(`${paths.archivesPath}/arm64`))).toEqual( + expect.arrayContaining(['chromium-70f5d88-linux_arm64.zip']) + ); + }); + + it('should download again if md5 hash different', async () => { + (md5 as jest.MockedFunction).mockResolvedValueOnce('random-md5'); + await download(paths); + + expect(fetch).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/x-pack/plugins/screenshotting/server/browsers/download/index.ts b/x-pack/plugins/screenshotting/server/browsers/download/index.ts new file mode 100644 index 0000000000000..8866fcc1caf2b --- /dev/null +++ b/x-pack/plugins/screenshotting/server/browsers/download/index.ts @@ -0,0 +1,94 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { existsSync } from 'fs'; +import del from 'del'; +import type { Logger } from 'src/core/server'; +import type { ChromiumArchivePaths } from '../chromium'; +import { md5 } from './checksum'; +import { fetch } from './fetch'; + +/** + * Clears the unexpected files in the browsers archivesPath + * and ensures that all packages/archives are downloaded and + * that their checksums match the declared value + * @param {BrowserSpec} browsers + * @return {Promise} + */ +export async function download(paths: ChromiumArchivePaths, logger?: Logger) { + const removedFiles = await del(`${paths.archivesPath}/**/*`, { + force: true, + onlyFiles: true, + ignore: paths.getAllArchiveFilenames(), + }); + + removedFiles.forEach((path) => logger?.warn(`Deleting unexpected file ${path}`)); + + const invalidChecksums: string[] = []; + await Promise.all( + paths.packages.map(async (path) => { + const { archiveFilename, archiveChecksum } = path; + if (!archiveFilename || !archiveChecksum) { + return; + } + + const resolvedPath = paths.resolvePath(path); + const pathExists = existsSync(resolvedPath); + + let foundChecksum = 'MISSING'; + try { + foundChecksum = await md5(resolvedPath); + // eslint-disable-next-line no-empty + } catch {} + + if (pathExists && foundChecksum === archiveChecksum) { + logger?.debug( + `Browser archive for ${path.platform}/${path.architecture} found in ${resolvedPath}.` + ); + return; + } + + if (!pathExists) { + logger?.warn( + `Browser archive for ${path.platform}/${path.architecture} not found in ${resolvedPath}.` + ); + } + + if (foundChecksum !== archiveChecksum) { + logger?.warn( + `Browser archive checksum for ${path.platform}/${path.architecture} ` + + `is ${foundChecksum} but ${archiveChecksum} was expected.` + ); + } + + const url = paths.getDownloadUrl(path); + try { + const downloadedChecksum = await fetch(url, resolvedPath, logger); + if (downloadedChecksum !== archiveChecksum) { + logger?.warn( + `Invalid checksum for ${path.platform}/${path.architecture}: ` + + `expected ${archiveChecksum} got ${downloadedChecksum}` + ); + invalidChecksums.push(`${url} => ${resolvedPath}`); + } + } catch (error) { + throw new Error(`Failed to download ${url}: ${error}`); + } + }) + ); + + if (invalidChecksums.length) { + const error = new Error( + `Error downloading browsers, checksums incorrect for:\n - ${invalidChecksums.join( + '\n - ' + )}` + ); + logger?.error(error); + + throw error; + } +} diff --git a/x-pack/plugins/reporting/server/browsers/extract/__fixtures__/file.md b/x-pack/plugins/screenshotting/server/browsers/extract/__fixtures__/file.md similarity index 100% rename from x-pack/plugins/reporting/server/browsers/extract/__fixtures__/file.md rename to x-pack/plugins/screenshotting/server/browsers/extract/__fixtures__/file.md diff --git a/x-pack/plugins/reporting/server/browsers/extract/__fixtures__/file.md.zip b/x-pack/plugins/screenshotting/server/browsers/extract/__fixtures__/file.md.zip similarity index 100% rename from x-pack/plugins/reporting/server/browsers/extract/__fixtures__/file.md.zip rename to x-pack/plugins/screenshotting/server/browsers/extract/__fixtures__/file.md.zip diff --git a/x-pack/plugins/reporting/server/browsers/extract/extract.test.ts b/x-pack/plugins/screenshotting/server/browsers/extract/extract.test.ts similarity index 100% rename from x-pack/plugins/reporting/server/browsers/extract/extract.test.ts rename to x-pack/plugins/screenshotting/server/browsers/extract/extract.test.ts diff --git a/x-pack/plugins/reporting/server/browsers/extract/extract.ts b/x-pack/plugins/screenshotting/server/browsers/extract/extract.ts similarity index 100% rename from x-pack/plugins/reporting/server/browsers/extract/extract.ts rename to x-pack/plugins/screenshotting/server/browsers/extract/extract.ts diff --git a/x-pack/plugins/reporting/server/browsers/extract/extract_error.ts b/x-pack/plugins/screenshotting/server/browsers/extract/extract_error.ts similarity index 99% rename from x-pack/plugins/reporting/server/browsers/extract/extract_error.ts rename to x-pack/plugins/screenshotting/server/browsers/extract/extract_error.ts index 838b8a7dbc158..04a915c2afc93 100644 --- a/x-pack/plugins/reporting/server/browsers/extract/extract_error.ts +++ b/x-pack/plugins/screenshotting/server/browsers/extract/extract_error.ts @@ -7,6 +7,7 @@ export class ExtractError extends Error { public readonly cause: string; + constructor(cause: string, message = 'Failed to extract the browser archive') { super(message); this.message = message; diff --git a/x-pack/plugins/reporting/server/browsers/extract/index.ts b/x-pack/plugins/screenshotting/server/browsers/extract/index.ts similarity index 100% rename from x-pack/plugins/reporting/server/browsers/extract/index.ts rename to x-pack/plugins/screenshotting/server/browsers/extract/index.ts diff --git a/x-pack/plugins/reporting/server/browsers/extract/unzip.test.ts b/x-pack/plugins/screenshotting/server/browsers/extract/unzip.test.ts similarity index 100% rename from x-pack/plugins/reporting/server/browsers/extract/unzip.test.ts rename to x-pack/plugins/screenshotting/server/browsers/extract/unzip.test.ts diff --git a/x-pack/plugins/reporting/server/browsers/extract/unzip.ts b/x-pack/plugins/screenshotting/server/browsers/extract/unzip.ts similarity index 100% rename from x-pack/plugins/reporting/server/browsers/extract/unzip.ts rename to x-pack/plugins/screenshotting/server/browsers/extract/unzip.ts diff --git a/x-pack/plugins/screenshotting/server/browsers/index.ts b/x-pack/plugins/screenshotting/server/browsers/index.ts new file mode 100644 index 0000000000000..ef5069ae51112 --- /dev/null +++ b/x-pack/plugins/screenshotting/server/browsers/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { download } from './download'; +export { install } from './install'; +export type { ConditionalHeaders, PerformanceMetrics } from './chromium'; +export { + getChromiumDisconnectedError, + ChromiumArchivePaths, + DEFAULT_VIEWPORT, + HeadlessChromiumDriver, + HeadlessChromiumDriverFactory, +} from './chromium'; diff --git a/x-pack/plugins/screenshotting/server/browsers/install.ts b/x-pack/plugins/screenshotting/server/browsers/install.ts new file mode 100644 index 0000000000000..acd31ec8ef2b5 --- /dev/null +++ b/x-pack/plugins/screenshotting/server/browsers/install.ts @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import del from 'del'; +import os from 'os'; +import path from 'path'; +import type { Logger } from 'src/core/server'; +import { ChromiumArchivePaths } from './chromium'; +import { download } from './download'; +import { md5 } from './download/checksum'; +import { extract } from './extract'; + +/** + * "install" a browser by type into installs path by extracting the downloaded + * archive. If there is an error extracting the archive an `ExtractError` is thrown + */ +export async function install( + paths: ChromiumArchivePaths, + logger: Logger, + chromiumPath: string = path.resolve(__dirname, '../../chromium'), + platform: string = process.platform, + architecture: string = os.arch() +): Promise { + const pkg = paths.find(platform, architecture); + + if (!pkg) { + throw new Error(`Unsupported platform: ${platform}-${architecture}`); + } + + const binaryPath = paths.getBinaryPath(pkg); + const binaryChecksum = await md5(binaryPath).catch(() => ''); + + if (binaryChecksum !== pkg.binaryChecksum) { + logger?.warn( + `Found browser binary checksum for ${pkg.platform}/${pkg.architecture} ` + + `is ${binaryChecksum} but ${pkg.binaryChecksum} was expected. Re-installing...` + ); + try { + await del(chromiumPath); + } catch (error) { + logger.error(error); + } + + try { + await download(paths, logger); + const archive = path.join(paths.archivesPath, pkg.architecture, pkg.archiveFilename); + logger.info(`Extracting [${archive}] to [${chromiumPath}]`); + await extract(archive, chromiumPath); + } catch (error) { + logger.error(error); + } + } + + logger.info(`Browser executable: ${binaryPath}`); + + return binaryPath; +} diff --git a/x-pack/plugins/screenshotting/server/browsers/mock.ts b/x-pack/plugins/screenshotting/server/browsers/mock.ts new file mode 100644 index 0000000000000..4b9142b298588 --- /dev/null +++ b/x-pack/plugins/screenshotting/server/browsers/mock.ts @@ -0,0 +1,95 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { NEVER, of } from 'rxjs'; +import type { HeadlessChromiumDriver, HeadlessChromiumDriverFactory } from './chromium'; +import { + CONTEXT_SKIPTELEMETRY, + CONTEXT_GETNUMBEROFITEMS, + CONTEXT_INJECTCSS, + CONTEXT_WAITFORRENDER, + CONTEXT_GETTIMERANGE, + CONTEXT_ELEMENTATTRIBUTES, + CONTEXT_GETRENDERERRORS, +} from '../screenshots/constants'; + +const selectors = { + renderComplete: 'renderedSelector', + itemsCountAttribute: 'itemsSelector', + screenshot: 'screenshotSelector', + timefilterDurationAttribute: 'timefilterDurationSelector', + toastHeader: 'toastHeaderSelector', +}; + +function getElementsPositionAndAttributes(title: string, description: string) { + return [ + { + position: { + boundingClientRect: { top: 0, left: 0, width: 800, height: 600 }, + scroll: { x: 0, y: 0 }, + }, + attributes: { title, description }, + }, + ]; +} + +export function createMockBrowserDriver(): jest.Mocked { + const evaluate = jest.fn(async (_, { context }) => { + switch (context) { + case CONTEXT_SKIPTELEMETRY: + case CONTEXT_INJECTCSS: + case CONTEXT_WAITFORRENDER: + case CONTEXT_GETRENDERERRORS: + return; + case CONTEXT_GETNUMBEROFITEMS: + return 1; + case CONTEXT_GETTIMERANGE: + return 'Default GetTimeRange Result'; + case CONTEXT_ELEMENTATTRIBUTES: + return getElementsPositionAndAttributes('Default Mock Title', 'Default '); + } + + throw new Error(context); + }); + + const screenshot = jest.fn(async () => Buffer.from('screenshot')); + + const waitForSelector = jest.fn(async (selectorArg: string) => { + const { renderComplete, itemsCountAttribute, toastHeader } = selectors; + + if (selectorArg === `${renderComplete},[${itemsCountAttribute}]`) { + return true; + } + + if (selectorArg === toastHeader) { + return NEVER.toPromise(); + } + + throw new Error(selectorArg); + }); + + return { + evaluate, + screenshot, + waitForSelector, + isPageOpen: jest.fn(), + open: jest.fn(), + setViewport: jest.fn(async () => {}), + waitFor: jest.fn(), + } as unknown as ReturnType; +} + +export function createMockBrowserDriverFactory( + driver?: HeadlessChromiumDriver +): jest.Mocked { + return { + createPage: jest.fn(() => + of({ driver: driver ?? createMockBrowserDriver(), exit$: NEVER, metrics$: NEVER }) + ), + diagnose: jest.fn(() => of('message')), + } as unknown as ReturnType; +} diff --git a/x-pack/plugins/reporting/server/browsers/network_policy.test.ts b/x-pack/plugins/screenshotting/server/browsers/network_policy.test.ts similarity index 99% rename from x-pack/plugins/reporting/server/browsers/network_policy.test.ts rename to x-pack/plugins/screenshotting/server/browsers/network_policy.test.ts index 4f0c60f4d14d2..84e42347100ae 100644 --- a/x-pack/plugins/reporting/server/browsers/network_policy.test.ts +++ b/x-pack/plugins/screenshotting/server/browsers/network_policy.test.ts @@ -7,7 +7,7 @@ import { allowRequest } from './network_policy'; -describe('Network Policy', () => { +describe('allowRequest', () => { it('allows requests when there are no rules', () => { expect(allowRequest('https://kibana.com/cool/route/bro', [])).toEqual(true); }); diff --git a/x-pack/plugins/reporting/server/browsers/network_policy.ts b/x-pack/plugins/screenshotting/server/browsers/network_policy.ts similarity index 94% rename from x-pack/plugins/reporting/server/browsers/network_policy.ts rename to x-pack/plugins/screenshotting/server/browsers/network_policy.ts index 721094dce6edf..4d47b01889924 100644 --- a/x-pack/plugins/reporting/server/browsers/network_policy.ts +++ b/x-pack/plugins/screenshotting/server/browsers/network_policy.ts @@ -26,7 +26,7 @@ const isHostMatch = (actualHost: string, ruleHost: string) => { return every(ruleParts, (part, idx) => part === hostParts[idx]); }; -export const allowRequest = (url: string, rules: NetworkPolicyRule[]) => { +export function allowRequest(url: string, rules: NetworkPolicyRule[]): boolean { const parsed = parse(url); if (!rules.length) { @@ -52,4 +52,4 @@ export const allowRequest = (url: string, rules: NetworkPolicyRule[]) => { }, undefined); return typeof allowed !== 'undefined' ? allowed : false; -}; +} diff --git a/x-pack/plugins/reporting/server/browsers/safe_child_process.ts b/x-pack/plugins/screenshotting/server/browsers/safe_child_process.ts similarity index 70% rename from x-pack/plugins/reporting/server/browsers/safe_child_process.ts rename to x-pack/plugins/screenshotting/server/browsers/safe_child_process.ts index 70e45bf10803f..4bc378a4c8c86 100644 --- a/x-pack/plugins/reporting/server/browsers/safe_child_process.ts +++ b/x-pack/plugins/screenshotting/server/browsers/safe_child_process.ts @@ -5,9 +5,9 @@ * 2.0. */ -import * as Rx from 'rxjs'; +import { fromEvent, merge, Observable } from 'rxjs'; import { take, share, mapTo, delay, tap } from 'rxjs/operators'; -import { LevelLogger } from '../lib'; +import type { Logger } from 'src/core/server'; interface IChild { kill: (signal: string) => Promise; @@ -16,13 +16,13 @@ interface IChild { // Our process can get sent various signals, and when these occur we wish to // kill the subprocess and then kill our process as long as the observer isn't cancelled export function safeChildProcess( - logger: LevelLogger, + logger: Logger, childProcess: IChild -): { terminate$: Rx.Observable } { - const ownTerminateSignal$ = Rx.merge( - Rx.fromEvent(process as NodeJS.EventEmitter, 'SIGTERM').pipe(mapTo('SIGTERM')), - Rx.fromEvent(process as NodeJS.EventEmitter, 'SIGINT').pipe(mapTo('SIGINT')), - Rx.fromEvent(process as NodeJS.EventEmitter, 'SIGBREAK').pipe(mapTo('SIGBREAK')) +): { terminate$: Observable } { + const ownTerminateSignal$ = merge( + fromEvent(process as NodeJS.EventEmitter, 'SIGTERM').pipe(mapTo('SIGTERM')), + fromEvent(process as NodeJS.EventEmitter, 'SIGINT').pipe(mapTo('SIGINT')), + fromEvent(process as NodeJS.EventEmitter, 'SIGBREAK').pipe(mapTo('SIGBREAK')) ).pipe(take(1), share()); const ownTerminateMapToKill$ = ownTerminateSignal$.pipe( @@ -32,7 +32,7 @@ export function safeChildProcess( mapTo('SIGKILL') ); - const kibanaForceExit$ = Rx.fromEvent(process as NodeJS.EventEmitter, 'exit').pipe( + const kibanaForceExit$ = fromEvent(process as NodeJS.EventEmitter, 'exit').pipe( take(1), tap((signal) => { logger.debug(`Kibana process forcefully exited with signal: ${signal}`); @@ -40,7 +40,7 @@ export function safeChildProcess( mapTo('SIGKILL') ); - const signalForChildProcess$ = Rx.merge(ownTerminateMapToKill$, kibanaForceExit$); + const signalForChildProcess$ = merge(ownTerminateMapToKill$, kibanaForceExit$); const logAndKillChildProcess = tap((signal: string) => { logger.debug(`Child process terminate signal was: ${signal}. Closing the browser...`); @@ -48,7 +48,7 @@ export function safeChildProcess( }); // send termination signals - const terminate$ = Rx.merge( + const terminate$ = merge( signalForChildProcess$.pipe(logAndKillChildProcess), ownTerminateSignal$.pipe( diff --git a/x-pack/plugins/screenshotting/server/config/create_config.test.ts b/x-pack/plugins/screenshotting/server/config/create_config.test.ts new file mode 100644 index 0000000000000..18ac6ceb6874d --- /dev/null +++ b/x-pack/plugins/screenshotting/server/config/create_config.test.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Logger } from 'src/core/server'; +import { createConfig } from './create_config'; +import { ConfigType } from './schema'; + +describe('createConfig$', () => { + let logger: jest.Mocked; + + beforeEach(() => { + logger = { + debug: jest.fn(), + get: jest.fn(() => logger), + info: jest.fn(), + warn: jest.fn(), + } as unknown as typeof logger; + }); + + it('should use user-provided disableSandbox', async () => { + const result = await createConfig(logger, { + browser: { chromium: { disableSandbox: false } }, + } as ConfigType); + + expect(result).toHaveProperty('browser.chromium.disableSandbox', false); + expect(logger.warn).not.toHaveBeenCalled(); + }); + + it('should provide a default for disableSandbox', async () => { + const result = await createConfig(logger, { browser: { chromium: {} } } as ConfigType); + + expect(result).toHaveProperty('browser.chromium.disableSandbox', expect.any(Boolean)); + expect((logger.warn as any).mock.calls.length).toBe(0); + }); +}); diff --git a/x-pack/plugins/screenshotting/server/config/create_config.ts b/x-pack/plugins/screenshotting/server/config/create_config.ts new file mode 100644 index 0000000000000..1819f37e1bccd --- /dev/null +++ b/x-pack/plugins/screenshotting/server/config/create_config.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { cloneDeep, set, upperFirst } from 'lodash'; +import type { Logger } from 'src/core/server'; +import { getDefaultChromiumSandboxDisabled } from './default_chromium_sandbox_disabled'; +import { ConfigType } from './schema'; + +/* + * Set up dynamic config defaults + * - xpack.capture.browser.chromium.disableSandbox + */ +export async function createConfig(parentLogger: Logger, config: ConfigType) { + const logger = parentLogger.get('config'); + + if (config.browser.chromium.disableSandbox != null) { + // disableSandbox was set by user + return config; + } + + // disableSandbox was not set by user, apply default for OS + const { os, disableSandbox } = await getDefaultChromiumSandboxDisabled(); + const osName = [os.os, os.dist, os.release].filter(Boolean).map(upperFirst).join(' '); + + logger.debug( + i18n.translate('xpack.screenshotting.serverConfig.osDetected', { + defaultMessage: `Running on OS: '{osName}'`, + values: { osName }, + }) + ); + + if (disableSandbox === true) { + logger.warn( + i18n.translate('xpack.screenshotting.serverConfig.autoSet.sandboxDisabled', { + defaultMessage: `Chromium sandbox provides an additional layer of protection, but is not supported for {osName} OS. Automatically setting '{configKey}: true'.`, + values: { + configKey: 'xpack.screenshotting.capture.browser.chromium.disableSandbox', + osName, + }, + }) + ); + } else { + logger.info( + i18n.translate('xpack.screenshotting.serverConfig.autoSet.sandboxEnabled', { + defaultMessage: `Chromium sandbox provides an additional layer of protection, and is supported for {osName} OS. Automatically enabling Chromium sandbox.`, + values: { osName }, + }) + ); + } + + return set(cloneDeep(config), 'browser.chromium.disableSandbox', disableSandbox); +} diff --git a/x-pack/plugins/screenshotting/server/config/default_chromium_sandbox_disabled.test.ts b/x-pack/plugins/screenshotting/server/config/default_chromium_sandbox_disabled.test.ts new file mode 100644 index 0000000000000..7204230ef5160 --- /dev/null +++ b/x-pack/plugins/screenshotting/server/config/default_chromium_sandbox_disabled.test.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +jest.mock('getos', () => jest.fn()); + +import { getDefaultChromiumSandboxDisabled } from './default_chromium_sandbox_disabled'; +import getos from 'getos'; + +describe('getDefaultChromiumSandboxDisabled', () => { + it.each` + os | dist | release | expected + ${'win32'} | ${'Windows'} | ${'11'} | ${false} + ${'darwin'} | ${'macOS'} | ${'11.2.3'} | ${false} + ${'linux'} | ${'Centos'} | ${'7.0'} | ${true} + ${'linux'} | ${'Red Hat Linux'} | ${'7.0'} | ${true} + ${'linux'} | ${'Ubuntu Linux'} | ${'14.04'} | ${false} + ${'linux'} | ${'Ubuntu Linux'} | ${'16.04'} | ${false} + ${'linux'} | ${'SUSE Linux'} | ${'11'} | ${false} + ${'linux'} | ${'SUSE Linux'} | ${'12'} | ${false} + ${'linux'} | ${'SUSE Linux'} | ${'42.0'} | ${false} + ${'linux'} | ${'Debian'} | ${'8'} | ${true} + ${'linux'} | ${'Debian'} | ${'9'} | ${true} + `('should return $expected for $dist $release', async ({ expected, ...os }) => { + (getos as jest.Mock).mockImplementation((cb) => cb(null, os)); + + await expect(getDefaultChromiumSandboxDisabled()).resolves.toHaveProperty( + 'disableSandbox', + expected + ); + }); +}); diff --git a/x-pack/plugins/reporting/server/config/default_chromium_sandbox_disabled.ts b/x-pack/plugins/screenshotting/server/config/default_chromium_sandbox_disabled.ts similarity index 80% rename from x-pack/plugins/reporting/server/config/default_chromium_sandbox_disabled.ts rename to x-pack/plugins/screenshotting/server/config/default_chromium_sandbox_disabled.ts index 89872cf63d1bc..4461b53bf0fcf 100644 --- a/x-pack/plugins/reporting/server/config/default_chromium_sandbox_disabled.ts +++ b/x-pack/plugins/screenshotting/server/config/default_chromium_sandbox_disabled.ts @@ -5,10 +5,10 @@ * 2.0. */ -import getosSync from 'getos'; +import getOsSync from 'getos'; import { promisify } from 'util'; -const getos = promisify(getosSync); +const getOs = promisify(getOsSync); const distroSupportsUnprivilegedUsernamespaces = (distro: string) => { // Debian 7 and 8 don't support usernamespaces by default @@ -38,11 +38,10 @@ interface OsSummary { } export async function getDefaultChromiumSandboxDisabled(): Promise { - const os = await getos(); + const os = await getOs(); - if (os.os === 'linux' && !distroSupportsUnprivilegedUsernamespaces(os.dist)) { - return { os, disableSandbox: true }; - } else { - return { os, disableSandbox: false }; - } + return { + os, + disableSandbox: os.os === 'linux' && !distroSupportsUnprivilegedUsernamespaces(os.dist), + }; } diff --git a/x-pack/plugins/screenshotting/server/config/index.ts b/x-pack/plugins/screenshotting/server/config/index.ts new file mode 100644 index 0000000000000..38f5a6e8f20fa --- /dev/null +++ b/x-pack/plugins/screenshotting/server/config/index.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { PluginConfigDescriptor } from 'src/core/server'; +import { ConfigSchema, ConfigType } from './schema'; + +/** + * Screenshotting plugin configuration schema. + */ +export const config: PluginConfigDescriptor = { + schema: ConfigSchema, + deprecations: ({ renameFromRoot }) => [ + renameFromRoot('xpack.reporting.capture.networkPolicy', 'xpack.screenshotting.networkPolicy'), + renameFromRoot( + 'xpack.reporting.capture.browser.autoDownload', + 'xpack.screenshotting.browser.autoDownload' + ), + renameFromRoot( + 'xpack.reporting.capture.browser.chromium.inspect', + 'xpack.screenshotting.browser.chromium.inspect' + ), + renameFromRoot( + 'xpack.reporting.capture.browser.chromium.disableSandbox', + 'xpack.screenshotting.browser.chromium.disableSandbox' + ), + renameFromRoot( + 'xpack.reporting.capture.browser.chromium.proxy.enabled', + 'xpack.screenshotting.browser.chromium.proxy.enabled' + ), + renameFromRoot( + 'xpack.reporting.capture.browser.chromium.proxy.server', + 'xpack.screenshotting.browser.chromium.proxy.server' + ), + renameFromRoot( + 'xpack.reporting.capture.browser.chromium.proxy.bypass', + 'xpack.screenshotting.browser.chromium.proxy.bypass' + ), + ], + exposeToUsage: { + networkPolicy: false, // show as [redacted] + }, +}; + +export { createConfig } from './create_config'; +export type { ConfigType } from './schema'; diff --git a/x-pack/plugins/screenshotting/server/config/schema.test.ts b/x-pack/plugins/screenshotting/server/config/schema.test.ts new file mode 100644 index 0000000000000..9180f0d180d5f --- /dev/null +++ b/x-pack/plugins/screenshotting/server/config/schema.test.ts @@ -0,0 +1,146 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ConfigSchema } from './schema'; + +describe('ConfigSchema', () => { + it(`should produce correct config for context {"dev": false,"dist": false}`, () => { + expect(ConfigSchema.validate({}, { dev: false, dist: false })).toMatchInlineSnapshot(` + Object { + "browser": Object { + "autoDownload": true, + "chromium": Object { + "proxy": Object { + "enabled": false, + }, + }, + }, + "networkPolicy": Object { + "enabled": true, + "rules": Array [ + Object { + "allow": true, + "host": undefined, + "protocol": "http:", + }, + Object { + "allow": true, + "host": undefined, + "protocol": "https:", + }, + Object { + "allow": true, + "host": undefined, + "protocol": "ws:", + }, + Object { + "allow": true, + "host": undefined, + "protocol": "wss:", + }, + Object { + "allow": true, + "host": undefined, + "protocol": "data:", + }, + Object { + "allow": false, + "host": undefined, + "protocol": undefined, + }, + ], + }, + } + `); + }); + + it(`should produce correct config for context {"dev": false,"dist": true}`, () => { + expect(ConfigSchema.validate({}, { dev: false, dist: true })).toMatchInlineSnapshot(` + Object { + "browser": Object { + "autoDownload": false, + "chromium": Object { + "inspect": false, + "proxy": Object { + "enabled": false, + }, + }, + }, + "networkPolicy": Object { + "enabled": true, + "rules": Array [ + Object { + "allow": true, + "host": undefined, + "protocol": "http:", + }, + Object { + "allow": true, + "host": undefined, + "protocol": "https:", + }, + Object { + "allow": true, + "host": undefined, + "protocol": "ws:", + }, + Object { + "allow": true, + "host": undefined, + "protocol": "wss:", + }, + Object { + "allow": true, + "host": undefined, + "protocol": "data:", + }, + Object { + "allow": false, + "host": undefined, + "protocol": undefined, + }, + ], + }, + } + `); + }); + + it(`should allow optional settings`, () => { + const config = ConfigSchema.validate({ browser: { chromium: { disableSandbox: true } } }); + + expect(config).toHaveProperty('browser.chromium', { + disableSandbox: true, + proxy: { enabled: false }, + }); + }); + + it('should allow setting a wildcard for chrome proxy bypass', () => { + expect( + ConfigSchema.validate({ + browser: { + chromium: { + proxy: { + enabled: true, + server: 'http://example.com:8080', + bypass: ['*.example.com', '*bar.example.com', 'bats.example.com'], + }, + }, + }, + }).browser.chromium.proxy + ).toMatchInlineSnapshot(` + Object { + "bypass": Array [ + "*.example.com", + "*bar.example.com", + "bats.example.com", + ], + "enabled": true, + "server": "http://example.com:8080", + } + `); + }); +}); diff --git a/x-pack/plugins/screenshotting/server/config/schema.ts b/x-pack/plugins/screenshotting/server/config/schema.ts new file mode 100644 index 0000000000000..bcf2fa9feead9 --- /dev/null +++ b/x-pack/plugins/screenshotting/server/config/schema.ts @@ -0,0 +1,72 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema, TypeOf } from '@kbn/config-schema'; + +const RulesSchema = schema.object({ + allow: schema.boolean(), + host: schema.maybe(schema.string()), + protocol: schema.maybe( + schema.string({ + validate(value) { + if (!/:$/.test(value)) { + return 'must end in colon'; + } + }, + }) + ), +}); + +export const ConfigSchema = schema.object({ + networkPolicy: schema.object({ + enabled: schema.boolean({ defaultValue: true }), + rules: schema.arrayOf(RulesSchema, { + defaultValue: [ + { host: undefined, allow: true, protocol: 'http:' }, + { host: undefined, allow: true, protocol: 'https:' }, + { host: undefined, allow: true, protocol: 'ws:' }, + { host: undefined, allow: true, protocol: 'wss:' }, + { host: undefined, allow: true, protocol: 'data:' }, + { host: undefined, allow: false, protocol: undefined }, // Default action is to deny! + ], + }), + }), + browser: schema.object({ + autoDownload: schema.conditional( + schema.contextRef('dist'), + true, + schema.boolean({ defaultValue: false }), + schema.boolean({ defaultValue: true }) + ), + chromium: schema.object({ + inspect: schema.conditional( + schema.contextRef('dist'), + true, + schema.boolean({ defaultValue: false }), + schema.maybe(schema.never()) + ), + disableSandbox: schema.maybe(schema.boolean()), // default value is dynamic in createConfig$ + proxy: schema.object({ + enabled: schema.boolean({ defaultValue: false }), + server: schema.conditional( + schema.siblingRef('enabled'), + true, + schema.uri({ scheme: ['http', 'https'] }), + schema.maybe(schema.never()) + ), + bypass: schema.conditional( + schema.siblingRef('enabled'), + true, + schema.arrayOf(schema.string()), + schema.maybe(schema.never()) + ), + }), + }), + }), +}); + +export type ConfigType = TypeOf; diff --git a/x-pack/plugins/screenshotting/server/index.ts b/x-pack/plugins/screenshotting/server/index.ts new file mode 100755 index 0000000000000..340a6688e79eb --- /dev/null +++ b/x-pack/plugins/screenshotting/server/index.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ScreenshottingPlugin } from './plugin'; + +/** + * Screenshotting plugin entry point. + */ +export function plugin(...args: ConstructorParameters) { + return new ScreenshottingPlugin(...args); +} + +export { config } from './config'; +export type { Layout } from './layouts'; +export type { ScreenshottingStart } from './plugin'; +export type { ScreenshotOptions, ScreenshotResult } from './screenshots'; diff --git a/x-pack/plugins/reporting/server/lib/layouts/layout.ts b/x-pack/plugins/screenshotting/server/layouts/base_layout.ts similarity index 79% rename from x-pack/plugins/reporting/server/lib/layouts/layout.ts rename to x-pack/plugins/screenshotting/server/layouts/base_layout.ts index d68e7690d79f1..846904170a0c1 100644 --- a/x-pack/plugins/reporting/server/lib/layouts/layout.ts +++ b/x-pack/plugins/screenshotting/server/layouts/base_layout.ts @@ -6,7 +6,7 @@ */ import type { CustomPageSize, PredefinedPageSize } from 'pdfmake/interfaces'; -import type { PageSizeParams, PdfImageSize, Size } from '../../../common/types'; +import type { Size } from '../../common/layout'; export interface ViewZoomWidthHeight { zoom: number; @@ -14,7 +14,21 @@ export interface ViewZoomWidthHeight { height: number; } -export abstract class Layout { +export interface PdfImageSize { + width: number; + height?: number; +} + +export interface PageSizeParams { + pageMarginTop: number; + pageMarginBottom: number; + pageMarginWidth: number; + tableBorderWidth: number; + headingHeight: number; + subheadingHeight: number; +} + +export abstract class BaseLayout { public id: string = ''; public groupCount: number = 0; diff --git a/x-pack/plugins/reporting/server/lib/layouts/canvas_layout.ts b/x-pack/plugins/screenshotting/server/layouts/canvas_layout.ts similarity index 80% rename from x-pack/plugins/reporting/server/lib/layouts/canvas_layout.ts rename to x-pack/plugins/screenshotting/server/layouts/canvas_layout.ts index ec95b0f75997d..d164f8c7e91e2 100644 --- a/x-pack/plugins/reporting/server/lib/layouts/canvas_layout.ts +++ b/x-pack/plugins/screenshotting/server/layouts/canvas_layout.ts @@ -5,15 +5,11 @@ * 2.0. */ -import { - getDefaultLayoutSelectors, - LayoutInstance, - LayoutSelectorDictionary, - LayoutTypes, - PageSizeParams, - Size, -} from './'; -import { Layout } from './layout'; +import type { LayoutSelectorDictionary, Size } from '../../common/layout'; +import { LayoutTypes } from '../../common'; +import { DEFAULT_SELECTORS } from '.'; +import type { Layout } from '.'; +import { BaseLayout } from './base_layout'; // FIXME - should use zoom from capture config const ZOOM: number = 2; @@ -24,8 +20,8 @@ const ZOOM: number = 2; * The single image that was captured should be the only structural part of the * PDF document definition */ -export class CanvasLayout extends Layout implements LayoutInstance { - public readonly selectors: LayoutSelectorDictionary = getDefaultLayoutSelectors(); +export class CanvasLayout extends BaseLayout implements Layout { + public readonly selectors: LayoutSelectorDictionary = { ...DEFAULT_SELECTORS }; public readonly groupCount = 1; public readonly height: number; public readonly width: number; @@ -78,7 +74,7 @@ export class CanvasLayout extends Layout implements LayoutInstance { }; } - public getPdfPageSize(pageSizeParams: PageSizeParams): Size { + public getPdfPageSize(): Size { return { height: this.height, width: this.width, diff --git a/x-pack/plugins/reporting/server/lib/layouts/create_layout.test.ts b/x-pack/plugins/screenshotting/server/layouts/create_layout.test.ts similarity index 80% rename from x-pack/plugins/reporting/server/lib/layouts/create_layout.test.ts rename to x-pack/plugins/screenshotting/server/layouts/create_layout.test.ts index aebd20451b834..1ea6c7440b455 100644 --- a/x-pack/plugins/reporting/server/lib/layouts/create_layout.test.ts +++ b/x-pack/plugins/screenshotting/server/layouts/create_layout.test.ts @@ -5,21 +5,16 @@ * 2.0. */ -import { ReportingConfig } from '../..'; -import { createMockConfig, createMockConfigSchema } from '../../test_helpers'; -import { createLayout, LayoutParams, PreserveLayout } from './'; +import type { LayoutParams } from '../../common/layout'; import { CanvasLayout } from './canvas_layout'; +import { PreserveLayout } from './preserve_layout'; +import { createLayout } from './create_layout'; describe('Create Layout', () => { - let config: ReportingConfig; - beforeEach(() => { - config = createMockConfig(createMockConfigSchema()); - }); - it('creates preserve layout instance', () => { const { id, height, width } = new PreserveLayout({ width: 16, height: 16 }); const preserveParams: LayoutParams = { id, dimensions: { height, width } }; - const layout = createLayout(config.get('capture'), preserveParams); + const layout = createLayout(preserveParams); expect(layout).toMatchInlineSnapshot(` PreserveLayout { "groupCount": 1, @@ -44,20 +39,14 @@ describe('Create Layout', () => { }); it('creates the print layout', () => { - const print = createLayout(config.get('capture')); + const print = createLayout({ zoom: 1 }); const printParams: LayoutParams = { id: print.id, + zoom: 1, }; - const layout = createLayout(config.get('capture'), printParams); + const layout = createLayout(printParams); expect(layout).toMatchInlineSnapshot(` PrintLayout { - "captureConfig": Object { - "browser": Object { - "chromium": Object { - "disableSandbox": true, - }, - }, - }, "groupCount": 2, "hasFooter": true, "hasHeader": true, @@ -75,6 +64,7 @@ describe('Create Layout', () => { "height": 1200, "width": 1950, }, + "zoom": 1, } `); }); @@ -82,7 +72,7 @@ describe('Create Layout', () => { it('creates the canvas layout', () => { const { id, height, width } = new CanvasLayout({ width: 18, height: 18 }); const canvasParams: LayoutParams = { id, dimensions: { height, width } }; - const layout = createLayout(config.get('capture'), canvasParams); + const layout = createLayout(canvasParams); expect(layout).toMatchInlineSnapshot(` CanvasLayout { "groupCount": 1, diff --git a/x-pack/plugins/screenshotting/server/layouts/create_layout.ts b/x-pack/plugins/screenshotting/server/layouts/create_layout.ts new file mode 100644 index 0000000000000..29a34a07e696f --- /dev/null +++ b/x-pack/plugins/screenshotting/server/layouts/create_layout.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { LayoutParams } from '../../common/layout'; +import { LayoutTypes } from '../../common'; +import type { Layout } from '.'; +import { CanvasLayout } from './canvas_layout'; +import { PreserveLayout } from './preserve_layout'; +import { PrintLayout } from './print_layout'; + +export function createLayout({ id, dimensions, selectors, ...config }: LayoutParams): Layout { + if (dimensions && id === LayoutTypes.PRESERVE_LAYOUT) { + return new PreserveLayout(dimensions, selectors); + } + + if (dimensions && id === LayoutTypes.CANVAS) { + return new CanvasLayout(dimensions); + } + + // layoutParams is optional as PrintLayout doesn't use it + return new PrintLayout(config); +} diff --git a/x-pack/plugins/screenshotting/server/layouts/index.ts b/x-pack/plugins/screenshotting/server/layouts/index.ts new file mode 100644 index 0000000000000..d21b06e6a688a --- /dev/null +++ b/x-pack/plugins/screenshotting/server/layouts/index.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Logger } from 'src/core/server'; +import type { LayoutSelectorDictionary, Size } from '../../common/layout'; +import type { HeadlessChromiumDriver } from '../browsers'; +import type { BaseLayout } from './base_layout'; + +interface LayoutSelectors { + /** + * Element selectors determining the page state. + */ + selectors: LayoutSelectorDictionary; + + /** + * A callback to position elements before taking a screenshot. + * @param browser Browser adapter instance. + * @param logger Message logger. + */ + positionElements?(browser: HeadlessChromiumDriver, logger: Logger): Promise; +} + +export type Layout = BaseLayout & LayoutSelectors & Partial; + +export const DEFAULT_SELECTORS: LayoutSelectorDictionary = { + screenshot: '[data-shared-items-container]', + renderComplete: '[data-shared-item]', + renderError: '[data-render-error]', + renderErrorAttribute: 'data-render-error', + itemsCountAttribute: 'data-shared-items-count', + timefilterDurationAttribute: 'data-shared-timefilter-duration', +}; + +export { createLayout } from './create_layout'; diff --git a/x-pack/plugins/reporting/server/test_helpers/create_mock_layoutinstance.ts b/x-pack/plugins/screenshotting/server/layouts/mock.ts similarity index 59% rename from x-pack/plugins/reporting/server/test_helpers/create_mock_layoutinstance.ts rename to x-pack/plugins/screenshotting/server/layouts/mock.ts index e9b94c3c98bec..d5395c5db6f82 100644 --- a/x-pack/plugins/reporting/server/test_helpers/create_mock_layoutinstance.ts +++ b/x-pack/plugins/screenshotting/server/layouts/mock.ts @@ -5,16 +5,17 @@ * 2.0. */ -import { LAYOUT_TYPES } from '../../common/constants'; -import { createLayout, LayoutInstance } from '../lib/layouts'; -import { CaptureConfig } from '../types'; +import { LayoutTypes } from '../../common'; +import { createLayout, Layout } from '.'; -export const createMockLayoutInstance = (captureConfig: CaptureConfig) => { - const mockLayout = createLayout(captureConfig, { - id: LAYOUT_TYPES.PRESERVE_LAYOUT, +export function createMockLayout(): Layout { + const layout = createLayout({ + id: LayoutTypes.PRESERVE_LAYOUT, dimensions: { height: 100, width: 100 }, - }) as LayoutInstance; - mockLayout.selectors = { + zoom: 1, + }) as Layout; + + layout.selectors = { renderComplete: 'renderedSelector', itemsCountAttribute: 'itemsSelector', screenshot: 'screenshotSelector', @@ -22,5 +23,6 @@ export const createMockLayoutInstance = (captureConfig: CaptureConfig) => { renderErrorAttribute: 'dataRenderErrorSelector', timefilterDurationAttribute: 'timefilterDurationSelector', }; - return mockLayout; -}; + + return layout; +} diff --git a/x-pack/plugins/reporting/server/lib/layouts/preserve_layout.css b/x-pack/plugins/screenshotting/server/layouts/preserve_layout.css similarity index 100% rename from x-pack/plugins/reporting/server/lib/layouts/preserve_layout.css rename to x-pack/plugins/screenshotting/server/layouts/preserve_layout.css diff --git a/x-pack/plugins/reporting/server/lib/layouts/preserve_layout.test.ts b/x-pack/plugins/screenshotting/server/layouts/preserve_layout.test.ts similarity index 100% rename from x-pack/plugins/reporting/server/lib/layouts/preserve_layout.test.ts rename to x-pack/plugins/screenshotting/server/layouts/preserve_layout.test.ts diff --git a/x-pack/plugins/reporting/server/lib/layouts/preserve_layout.ts b/x-pack/plugins/screenshotting/server/layouts/preserve_layout.ts similarity index 79% rename from x-pack/plugins/reporting/server/lib/layouts/preserve_layout.ts rename to x-pack/plugins/screenshotting/server/layouts/preserve_layout.ts index 7f6bc9e5d9505..f265920675f85 100644 --- a/x-pack/plugins/reporting/server/lib/layouts/preserve_layout.ts +++ b/x-pack/plugins/screenshotting/server/layouts/preserve_layout.ts @@ -5,16 +5,18 @@ * 2.0. */ import path from 'path'; -import { CustomPageSize } from 'pdfmake/interfaces'; -import { LAYOUT_TYPES } from '../../../common/constants'; -import { PageSizeParams, Size } from '../../../common/types'; -import { getDefaultLayoutSelectors, LayoutInstance, LayoutSelectorDictionary } from './'; -import { Layout } from './layout'; +import type { CustomPageSize } from 'pdfmake/interfaces'; +import type { LayoutSelectorDictionary, Size } from '../../common/layout'; +import { LayoutTypes } from '../../common'; +import { DEFAULT_SELECTORS } from '.'; +import type { Layout } from '.'; +import { BaseLayout } from './base_layout'; +import type { PageSizeParams } from './base_layout'; // We use a zoom of two to bump up the resolution of the screenshot a bit. const ZOOM: number = 2; -export class PreserveLayout extends Layout implements LayoutInstance { +export class PreserveLayout extends BaseLayout implements Layout { public readonly selectors: LayoutSelectorDictionary; public readonly groupCount = 1; public readonly height: number; @@ -23,16 +25,13 @@ export class PreserveLayout extends Layout implements LayoutInstance { private readonly scaledWidth: number; constructor(size: Size, selectors?: Partial) { - super(LAYOUT_TYPES.PRESERVE_LAYOUT); + super(LayoutTypes.PRESERVE_LAYOUT); this.height = size.height; this.width = size.width; this.scaledHeight = size.height * ZOOM; this.scaledWidth = size.width * ZOOM; - this.selectors = { - ...getDefaultLayoutSelectors(), - ...selectors, - }; + this.selectors = { ...DEFAULT_SELECTORS, ...selectors }; } public getCssOverridesPath() { diff --git a/x-pack/plugins/reporting/server/lib/layouts/print_layout.ts b/x-pack/plugins/screenshotting/server/layouts/print_layout.ts similarity index 64% rename from x-pack/plugins/reporting/server/lib/layouts/print_layout.ts rename to x-pack/plugins/screenshotting/server/layouts/print_layout.ts index 68226affb41e4..bfcbe84842c40 100644 --- a/x-pack/plugins/reporting/server/lib/layouts/print_layout.ts +++ b/x-pack/plugins/screenshotting/server/layouts/print_layout.ts @@ -6,23 +6,26 @@ */ import { PageOrientation, PredefinedPageSize } from 'pdfmake/interfaces'; -import { DEFAULT_VIEWPORT, LAYOUT_TYPES } from '../../../common/constants'; -import { CaptureConfig } from '../../types'; -import { getDefaultLayoutSelectors, LayoutInstance, LayoutSelectorDictionary } from './'; -import { Layout } from './layout'; +import type { LayoutParams, LayoutSelectorDictionary } from '../../common/layout'; +import { LayoutTypes } from '../../common'; +import type { Layout } from '.'; +import { DEFAULT_SELECTORS } from '.'; +import { DEFAULT_VIEWPORT } from '../browsers'; +import { BaseLayout } from './base_layout'; -export class PrintLayout extends Layout implements LayoutInstance { +export class PrintLayout extends BaseLayout implements Layout { public readonly selectors: LayoutSelectorDictionary = { - ...getDefaultLayoutSelectors(), + ...DEFAULT_SELECTORS, screenshot: '[data-shared-item]', // override '[data-shared-items-container]' }; public readonly groupCount = 2; - private readonly captureConfig: CaptureConfig; private readonly viewport = DEFAULT_VIEWPORT; + private zoom: number; - constructor(captureConfig: CaptureConfig) { - super(LAYOUT_TYPES.PRINT); - this.captureConfig = captureConfig; + constructor({ zoom = 1 }: Pick) { + super(LayoutTypes.PRINT); + + this.zoom = zoom; } public getCssOverridesPath() { @@ -34,16 +37,17 @@ export class PrintLayout extends Layout implements LayoutInstance { } public getBrowserZoom() { - return this.captureConfig.zoom; + return this.zoom; } public getViewport(itemsCount: number) { return { - zoom: this.captureConfig.zoom, + zoom: this.zoom, width: this.viewport.width, height: this.viewport.height * itemsCount, }; } + public getPdfImageSize() { return { width: 500, diff --git a/x-pack/plugins/screenshotting/server/mock.ts b/x-pack/plugins/screenshotting/server/mock.ts new file mode 100644 index 0000000000000..49d69521f2c19 --- /dev/null +++ b/x-pack/plugins/screenshotting/server/mock.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Logger } from 'src/core/server'; +import { createMockBrowserDriverFactory } from './browsers/mock'; +import { createMockScreenshots } from './screenshots/mock'; +import type { ScreenshottingStart } from '.'; + +export function createMockScreenshottingStart(): jest.Mocked { + const driver = createMockBrowserDriverFactory(); + const { getScreenshots } = createMockScreenshots(); + const { diagnose } = driver; + + return { + diagnose, + getScreenshots: jest.fn((options) => getScreenshots(driver, {} as Logger, options)), + }; +} diff --git a/x-pack/plugins/screenshotting/server/plugin.ts b/x-pack/plugins/screenshotting/server/plugin.ts new file mode 100755 index 0000000000000..53f855e1f544d --- /dev/null +++ b/x-pack/plugins/screenshotting/server/plugin.ts @@ -0,0 +1,89 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { from } from 'rxjs'; +import { switchMap } from 'rxjs/operators'; +import type { + CoreSetup, + CoreStart, + Logger, + Plugin, + PluginInitializerContext, +} from 'src/core/server'; +import type { ScreenshotModePluginSetup } from 'src/plugins/screenshot_mode/server'; +import { ChromiumArchivePaths, HeadlessChromiumDriverFactory, install } from './browsers'; +import { createConfig, ConfigType } from './config'; +import { getScreenshots, ScreenshotOptions } from './screenshots'; + +interface SetupDeps { + screenshotMode: ScreenshotModePluginSetup; +} + +/** + * Start public contract. + */ +export interface ScreenshottingStart { + /** + * Runs browser diagnostics. + * @returns Observable with output messages. + */ + diagnose: HeadlessChromiumDriverFactory['diagnose']; + + /** + * Takes screenshots of multiple pages. + * @param options Screenshots session options. + * @returns Observable with screenshotting results. + */ + getScreenshots(options: ScreenshotOptions): ReturnType; +} + +export class ScreenshottingPlugin implements Plugin { + private config: ConfigType; + private logger: Logger; + private screenshotMode!: ScreenshotModePluginSetup; + private browserDriverFactory!: Promise; + + constructor(context: PluginInitializerContext) { + this.logger = context.logger.get(); + this.config = context.config.get(); + } + + setup({}: CoreSetup, { screenshotMode }: SetupDeps) { + this.screenshotMode = screenshotMode; + this.browserDriverFactory = (async () => { + try { + const paths = new ChromiumArchivePaths(); + const logger = this.logger.get('chromium'); + const [config, binaryPath] = await Promise.all([ + createConfig(this.logger, this.config), + install(paths, logger), + ]); + + return new HeadlessChromiumDriverFactory(this.screenshotMode, config, logger, binaryPath); + } catch (error) { + this.logger.error('Error in screenshotting setup, it may not function properly.'); + + throw error; + } + })(); + + return {}; + } + + start({}: CoreStart): ScreenshottingStart { + return { + diagnose: () => + from(this.browserDriverFactory).pipe(switchMap((factory) => factory.diagnose())), + getScreenshots: (options) => + from(this.browserDriverFactory).pipe( + switchMap((factory) => getScreenshots(factory, this.logger.get('screenshot'), options)) + ), + }; + } + + stop() {} +} diff --git a/x-pack/plugins/reporting/server/lib/screenshots/constants.ts b/x-pack/plugins/screenshotting/server/screenshots/constants.ts similarity index 92% rename from x-pack/plugins/reporting/server/lib/screenshots/constants.ts rename to x-pack/plugins/screenshotting/server/screenshots/constants.ts index c62b910630874..b1064ec147745 100644 --- a/x-pack/plugins/reporting/server/lib/screenshots/constants.ts +++ b/x-pack/plugins/screenshotting/server/screenshots/constants.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { APP_WRAPPER_CLASS } from '../../../../../../src/core/server'; +import { APP_WRAPPER_CLASS } from '../../../../../src/core/server'; export const DEFAULT_PAGELOAD_SELECTOR = `.${APP_WRAPPER_CLASS}`; export const CONTEXT_GETNUMBEROFITEMS = 'GetNumberOfItems'; diff --git a/x-pack/plugins/reporting/server/lib/screenshots/get_element_position_data.test.ts b/x-pack/plugins/screenshotting/server/screenshots/get_element_position_data.test.ts similarity index 69% rename from x-pack/plugins/reporting/server/lib/screenshots/get_element_position_data.test.ts rename to x-pack/plugins/screenshotting/server/screenshots/get_element_position_data.test.ts index 389ae4f49f3b6..7d5791f0dfeb1 100644 --- a/x-pack/plugins/reporting/server/lib/screenshots/get_element_position_data.test.ts +++ b/x-pack/plugins/screenshotting/server/screenshots/get_element_position_data.test.ts @@ -5,48 +5,21 @@ * 2.0. */ -import { HeadlessChromiumDriver } from '../../browsers'; -import { - createMockBrowserDriverFactory, - createMockConfig, - createMockConfigSchema, - createMockLayoutInstance, - createMockLevelLogger, - createMockReportingCore, -} from '../../test_helpers'; -import { LayoutInstance } from '../layouts'; +import type { Logger } from 'src/core/server'; +import { createMockBrowserDriver } from '../browsers/mock'; +import { createMockLayout } from '../layouts/mock'; import { getElementPositionAndAttributes } from './get_element_position_data'; describe('getElementPositionAndAttributes', () => { - let layout: LayoutInstance; - let logger: ReturnType; - let browser: HeadlessChromiumDriver; + const logger = {} as jest.Mocked; + let browser: ReturnType; + let layout: ReturnType; beforeEach(async () => { - const schema = createMockConfigSchema(); - const config = createMockConfig(schema); - const captureConfig = config.get('capture'); - const core = await createMockReportingCore(schema); + browser = createMockBrowserDriver(); + layout = createMockLayout(); - layout = createMockLayoutInstance(captureConfig); - logger = createMockLevelLogger(); - - await createMockBrowserDriverFactory(core, logger, { - evaluate: jest.fn( - async unknown>({ - fn, - args, - }: { - fn: T; - args: Parameters; - }) => fn(...args) - ), - getCreatePage: (driver) => { - browser = driver; - - return jest.fn(); - }, - }); + browser.evaluate.mockImplementation(({ fn, args }) => (fn as Function)(...args)); // @see https://github.com/jsdom/jsdom/issues/653 const querySelectorAll = document.querySelectorAll.bind(document); @@ -69,7 +42,6 @@ describe('getElementPositionAndAttributes', () => { }); afterEach(() => { - jest.restoreAllMocks(); document.body.innerHTML = ''; }); @@ -87,7 +59,7 @@ describe('getElementPositionAndAttributes', () => { /> `; - await expect(getElementPositionAndAttributes(browser, layout, logger)).resolves + await expect(getElementPositionAndAttributes(browser, logger, layout)).resolves .toMatchInlineSnapshot(` Array [ Object { @@ -131,6 +103,6 @@ describe('getElementPositionAndAttributes', () => { }); it('should return null when there are no elements matching', async () => { - await expect(getElementPositionAndAttributes(browser, layout, logger)).resolves.toBeNull(); + await expect(getElementPositionAndAttributes(browser, logger, layout)).resolves.toBeNull(); }); }); diff --git a/x-pack/plugins/reporting/server/lib/screenshots/get_element_position_data.ts b/x-pack/plugins/screenshotting/server/screenshots/get_element_position_data.ts similarity index 75% rename from x-pack/plugins/reporting/server/lib/screenshots/get_element_position_data.ts rename to x-pack/plugins/screenshotting/server/screenshots/get_element_position_data.ts index 39163843c732f..f7576a012e738 100644 --- a/x-pack/plugins/reporting/server/lib/screenshots/get_element_position_data.ts +++ b/x-pack/plugins/screenshotting/server/screenshots/get_element_position_data.ts @@ -6,18 +6,41 @@ */ import { i18n } from '@kbn/i18n'; -import { LevelLogger, startTrace } from '../'; -import { HeadlessChromiumDriver } from '../../browsers'; -import { LayoutInstance } from '../layouts'; -import { AttributesMap, ElementsPositionAndAttribute } from './'; +import apm from 'elastic-apm-node'; +import type { Logger } from 'src/core/server'; +import type { HeadlessChromiumDriver } from '../browsers'; +import { Layout } from '../layouts'; import { CONTEXT_ELEMENTATTRIBUTES } from './constants'; +export interface AttributesMap { + [key: string]: string | null; +} + +export interface ElementPosition { + boundingClientRect: { + // modern browsers support x/y, but older ones don't + top: number; + left: number; + width: number; + height: number; + }; + scroll: { + x: number; + y: number; + }; +} + +export interface ElementsPositionAndAttribute { + position: ElementPosition; + attributes: AttributesMap; +} + export const getElementPositionAndAttributes = async ( browser: HeadlessChromiumDriver, - layout: LayoutInstance, - logger: LevelLogger + logger: Logger, + layout: Layout ): Promise => { - const endTrace = startTrace('get_element_position_data', 'read'); + const span = apm.startSpan('get_element_position_data', 'read'); const { screenshot: screenshotSelector } = layout.selectors; // data-shared-items-container let elementsPositionAndAttributes: ElementsPositionAndAttribute[] | null; try { @@ -60,7 +83,7 @@ export const getElementPositionAndAttributes = async ( if (!elementsPositionAndAttributes?.length) { throw new Error( - i18n.translate('xpack.reporting.screencapture.noElements', { + i18n.translate('xpack.screenshotting.screencapture.noElements', { defaultMessage: `An error occurred while reading the page for visualization panels: no panels were found.`, }) ); @@ -69,7 +92,7 @@ export const getElementPositionAndAttributes = async ( elementsPositionAndAttributes = null; } - endTrace(); + span?.end(); return elementsPositionAndAttributes; }; diff --git a/x-pack/plugins/screenshotting/server/screenshots/get_number_of_items.test.ts b/x-pack/plugins/screenshotting/server/screenshots/get_number_of_items.test.ts new file mode 100644 index 0000000000000..e5e70f617339d --- /dev/null +++ b/x-pack/plugins/screenshotting/server/screenshots/get_number_of_items.test.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Logger } from 'src/core/server'; +import { createMockBrowserDriver } from '../browsers/mock'; +import { createMockLayout } from '../layouts/mock'; +import { getNumberOfItems } from './get_number_of_items'; + +describe('getNumberOfItems', () => { + const timeout = 10; + let browser: ReturnType; + let layout: ReturnType; + let logger: jest.Mocked; + + beforeEach(async () => { + browser = createMockBrowserDriver(); + layout = createMockLayout(); + logger = { debug: jest.fn() } as unknown as jest.Mocked; + + browser.evaluate.mockImplementation(({ fn, args }) => (fn as Function)(...args)); + }); + + afterEach(() => { + document.body.innerHTML = ''; + }); + + it('should determine the number of items by attribute', async () => { + document.body.innerHTML = ` +
+ `; + + await expect(getNumberOfItems(browser, logger, timeout, layout)).resolves.toBe(10); + }); + + it('should determine the number of items by selector ', async () => { + document.body.innerHTML = ` + + + + `; + + await expect(getNumberOfItems(browser, logger, timeout, layout)).resolves.toBe(3); + }); + + it('should fall back to the selector when the attribute is empty', async () => { + document.body.innerHTML = ` +
+ + + `; + + await expect(getNumberOfItems(browser, logger, timeout, layout)).resolves.toBe(2); + }); +}); diff --git a/x-pack/plugins/reporting/server/lib/screenshots/get_number_of_items.ts b/x-pack/plugins/screenshotting/server/screenshots/get_number_of_items.ts similarity index 79% rename from x-pack/plugins/reporting/server/lib/screenshots/get_number_of_items.ts rename to x-pack/plugins/screenshotting/server/screenshots/get_number_of_items.ts index 9e5dfa180fd0f..3677fe99d932f 100644 --- a/x-pack/plugins/reporting/server/lib/screenshots/get_number_of_items.ts +++ b/x-pack/plugins/screenshotting/server/screenshots/get_number_of_items.ts @@ -6,23 +6,24 @@ */ import { i18n } from '@kbn/i18n'; -import { LevelLogger, startTrace } from '../'; -import { HeadlessChromiumDriver } from '../../browsers'; -import { LayoutInstance } from '../layouts'; +import apm from 'elastic-apm-node'; +import type { Logger } from 'src/core/server'; +import type { HeadlessChromiumDriver } from '../browsers'; +import { Layout } from '../layouts'; import { CONTEXT_GETNUMBEROFITEMS, CONTEXT_READMETADATA } from './constants'; export const getNumberOfItems = async ( - timeout: number, browser: HeadlessChromiumDriver, - layout: LayoutInstance, - logger: LevelLogger + logger: Logger, + timeout: number, + layout: Layout ): Promise => { - const endTrace = startTrace('get_number_of_items', 'read'); + const span = apm.startSpan('get_number_of_items', 'read'); const { renderComplete: renderCompleteSelector, itemsCountAttribute } = layout.selectors; let itemsCount: number; logger.debug( - i18n.translate('xpack.reporting.screencapture.logWaitingForElements', { + i18n.translate('xpack.screenshotting.screencapture.logWaitingForElements', { defaultMessage: 'waiting for elements or items count attribute; or not found to interrupt', }) ); @@ -58,17 +59,17 @@ export const getNumberOfItems = async ( { context: CONTEXT_GETNUMBEROFITEMS }, logger ); - } catch (err) { - logger.error(err); + } catch (error) { + logger.error(error); throw new Error( - i18n.translate('xpack.reporting.screencapture.readVisualizationsError', { + i18n.translate('xpack.screenshotting.screencapture.readVisualizationsError', { defaultMessage: `An error occurred when trying to read the page for visualization panel info: {error}`, - values: { error: err }, + values: { error }, }) ); } - endTrace(); + span?.end(); return itemsCount; }; diff --git a/x-pack/plugins/screenshotting/server/screenshots/get_render_errors.test.ts b/x-pack/plugins/screenshotting/server/screenshots/get_render_errors.test.ts new file mode 100644 index 0000000000000..75576d7221f5e --- /dev/null +++ b/x-pack/plugins/screenshotting/server/screenshots/get_render_errors.test.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Logger } from 'src/core/server'; +import { createMockBrowserDriver } from '../browsers/mock'; +import { createMockLayout } from '../layouts/mock'; +import { getRenderErrors } from './get_render_errors'; + +describe('getRenderErrors', () => { + let browser: ReturnType; + let layout: ReturnType; + let logger: jest.Mocked; + + beforeEach(async () => { + browser = createMockBrowserDriver(); + layout = createMockLayout(); + logger = { debug: jest.fn(), warn: jest.fn() } as unknown as jest.Mocked; + + browser.evaluate.mockImplementation(({ fn, args }) => (fn as Function)(...args)); + }); + + afterEach(() => { + document.body.innerHTML = ''; + }); + + it('should extract the error messages', async () => { + document.body.innerHTML = ` +
+
+
+
+ `; + + await expect(getRenderErrors(browser, logger, layout)).resolves.toEqual([ + 'a test error', + 'a test error', + 'a test error', + 'a test error', + ]); + }); + + it('should extract the error messages, even when there are none', async () => { + document.body.innerHTML = ` + + `; + + await expect(getRenderErrors(browser, logger, layout)).resolves.toEqual(undefined); + }); +}); diff --git a/x-pack/plugins/reporting/server/lib/screenshots/get_render_errors.ts b/x-pack/plugins/screenshotting/server/screenshots/get_render_errors.ts similarity index 78% rename from x-pack/plugins/reporting/server/lib/screenshots/get_render_errors.ts rename to x-pack/plugins/screenshotting/server/screenshots/get_render_errors.ts index ded4ed6238872..ad3da8d0ef488 100644 --- a/x-pack/plugins/reporting/server/lib/screenshots/get_render_errors.ts +++ b/x-pack/plugins/screenshotting/server/screenshots/get_render_errors.ts @@ -6,17 +6,18 @@ */ import { i18n } from '@kbn/i18n'; -import type { HeadlessChromiumDriver } from '../../browsers'; -import type { LayoutInstance } from '../layouts'; -import { LevelLogger, startTrace } from '../'; +import apm from 'elastic-apm-node'; +import type { Logger } from 'src/core/server'; +import type { HeadlessChromiumDriver } from '../browsers'; +import type { Layout } from '../layouts'; import { CONTEXT_GETRENDERERRORS } from './constants'; export const getRenderErrors = async ( browser: HeadlessChromiumDriver, - layout: LayoutInstance, - logger: LevelLogger + logger: Logger, + layout: Layout ): Promise => { - const endTrace = startTrace('get_render_errors', 'read'); + const span = apm.startSpan('get_render_errors', 'read'); logger.debug('reading render errors'); const errorsFound: undefined | string[] = await browser.evaluate( { @@ -38,11 +39,11 @@ export const getRenderErrors = async ( { context: CONTEXT_GETRENDERERRORS }, logger ); - endTrace(); + span?.end(); if (errorsFound?.length) { - logger.warning( - i18n.translate('xpack.reporting.screencapture.renderErrorsFound', { + logger.warn( + i18n.translate('xpack.screenshotting.screencapture.renderErrorsFound', { defaultMessage: 'Found {count} error messages. See report object for more information.', values: { count: errorsFound.length }, }) diff --git a/x-pack/plugins/reporting/server/lib/screenshots/get_screenshots.test.ts b/x-pack/plugins/screenshotting/server/screenshots/get_screenshots.test.ts similarity index 71% rename from x-pack/plugins/reporting/server/lib/screenshots/get_screenshots.test.ts rename to x-pack/plugins/screenshotting/server/screenshots/get_screenshots.test.ts index edd346c9b8928..2bb00413c8231 100644 --- a/x-pack/plugins/reporting/server/lib/screenshots/get_screenshots.test.ts +++ b/x-pack/plugins/screenshotting/server/screenshots/get_screenshots.test.ts @@ -5,13 +5,8 @@ * 2.0. */ -import { HeadlessChromiumDriver } from '../../browsers'; -import { - createMockBrowserDriverFactory, - createMockConfigSchema, - createMockLevelLogger, - createMockReportingCore, -} from '../../test_helpers'; +import type { Logger } from 'src/core/server'; +import { createMockBrowserDriver } from '../browsers/mock'; import { getScreenshots } from './get_screenshots'; describe('getScreenshots', () => { @@ -31,31 +26,14 @@ describe('getScreenshots', () => { }, }, ]; - - let logger: ReturnType; - let browser: jest.Mocked; + let browser: ReturnType; + let logger: jest.Mocked; beforeEach(async () => { - const core = await createMockReportingCore(createMockConfigSchema()); - - logger = createMockLevelLogger(); + browser = createMockBrowserDriver(); + logger = { info: jest.fn() } as unknown as jest.Mocked; - await createMockBrowserDriverFactory(core, logger, { - evaluate: jest.fn( - async unknown>({ - fn, - args, - }: { - fn: T; - args: Parameters; - }) => fn(...args) - ), - getCreatePage: (driver) => { - browser = driver as typeof browser; - - return jest.fn(); - }, - }); + browser.evaluate.mockImplementation(({ fn, args }) => (fn as Function)(...args)); }); afterEach(() => { @@ -63,7 +41,7 @@ describe('getScreenshots', () => { }); it('should return screenshots', async () => { - await expect(getScreenshots(browser, elementsPositionAndAttributes, logger)).resolves + await expect(getScreenshots(browser, logger, elementsPositionAndAttributes)).resolves .toMatchInlineSnapshot(` Array [ Object { @@ -109,7 +87,7 @@ describe('getScreenshots', () => { }); it('should forward elements positions', async () => { - await getScreenshots(browser, elementsPositionAndAttributes, logger); + await getScreenshots(browser, logger, elementsPositionAndAttributes); expect(browser.screenshot).toHaveBeenCalledTimes(2); expect(browser.screenshot).toHaveBeenNthCalledWith( @@ -126,7 +104,7 @@ describe('getScreenshots', () => { browser.screenshot.mockResolvedValue(Buffer.from('')); await expect( - getScreenshots(browser, elementsPositionAndAttributes, logger) + getScreenshots(browser, logger, elementsPositionAndAttributes) ).rejects.toBeInstanceOf(Error); }); }); diff --git a/x-pack/plugins/reporting/server/lib/screenshots/get_screenshots.ts b/x-pack/plugins/screenshotting/server/screenshots/get_screenshots.ts similarity index 59% rename from x-pack/plugins/reporting/server/lib/screenshots/get_screenshots.ts rename to x-pack/plugins/screenshotting/server/screenshots/get_screenshots.ts index 9b5f234b78363..8e03bb8a77cc9 100644 --- a/x-pack/plugins/reporting/server/lib/screenshots/get_screenshots.ts +++ b/x-pack/plugins/screenshotting/server/screenshots/get_screenshots.ts @@ -6,17 +6,35 @@ */ import { i18n } from '@kbn/i18n'; -import { LevelLogger, startTrace } from '../'; -import { HeadlessChromiumDriver } from '../../browsers'; -import { ElementsPositionAndAttribute, Screenshot } from './'; +import apm from 'elastic-apm-node'; +import type { Logger } from 'src/core/server'; +import type { HeadlessChromiumDriver } from '../browsers'; +import type { ElementsPositionAndAttribute } from './get_element_position_data'; + +export interface Screenshot { + /** + * Screenshot PNG image data. + */ + data: Buffer; + + /** + * Screenshot title. + */ + title: string | null; + + /** + * Screenshot description. + */ + description: string | null; +} export const getScreenshots = async ( browser: HeadlessChromiumDriver, - elementsPositionAndAttributes: ElementsPositionAndAttribute[], - logger: LevelLogger + logger: Logger, + elementsPositionAndAttributes: ElementsPositionAndAttribute[] ): Promise => { logger.info( - i18n.translate('xpack.reporting.screencapture.takingScreenshots', { + i18n.translate('xpack.screenshotting.screencapture.takingScreenshots', { defaultMessage: `taking screenshots`, }) ); @@ -24,7 +42,7 @@ export const getScreenshots = async ( const screenshots: Screenshot[] = []; for (let i = 0; i < elementsPositionAndAttributes.length; i++) { - const endTrace = startTrace('get_screenshots', 'read'); + const span = apm.startSpan('get_screenshots', 'read'); const item = elementsPositionAndAttributes[i]; const data = await browser.screenshot(item.position); @@ -39,11 +57,11 @@ export const getScreenshots = async ( description: item.attributes.description, }); - endTrace(); + span?.end(); } logger.info( - i18n.translate('xpack.reporting.screencapture.screenshotsTaken', { + i18n.translate('xpack.screenshotting.screencapture.screenshotsTaken', { defaultMessage: `screenshots taken: {numScreenhots}`, values: { numScreenhots: screenshots.length, diff --git a/x-pack/plugins/screenshotting/server/screenshots/get_time_range.test.ts b/x-pack/plugins/screenshotting/server/screenshots/get_time_range.test.ts new file mode 100644 index 0000000000000..d277690a08282 --- /dev/null +++ b/x-pack/plugins/screenshotting/server/screenshots/get_time_range.test.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Logger } from 'src/core/server'; +import { createMockBrowserDriver } from '../browsers/mock'; +import { createMockLayout } from '../layouts/mock'; +import { getTimeRange } from './get_time_range'; + +describe('getTimeRange', () => { + let browser: ReturnType; + let layout: ReturnType; + let logger: jest.Mocked; + + beforeEach(async () => { + browser = createMockBrowserDriver(); + layout = createMockLayout(); + logger = { debug: jest.fn(), info: jest.fn() } as unknown as jest.Mocked; + + browser.evaluate.mockImplementation(({ fn, args }) => (fn as Function)(...args)); + }); + + afterEach(() => { + document.body.innerHTML = ''; + }); + + it('should return null when there is no duration element', async () => { + await expect(getTimeRange(browser, logger, layout)).resolves.toBeNull(); + }); + + it('should return null when duration attrbute is empty', async () => { + document.body.innerHTML = ` +
+ `; + + await expect(getTimeRange(browser, logger, layout)).resolves.toBeNull(); + }); + + it('should return duration', async () => { + document.body.innerHTML = ` +
+ `; + + await expect(getTimeRange(browser, logger, layout)).resolves.toBe('10'); + }); +}); diff --git a/x-pack/plugins/reporting/server/lib/screenshots/get_time_range.ts b/x-pack/plugins/screenshotting/server/screenshots/get_time_range.ts similarity index 79% rename from x-pack/plugins/reporting/server/lib/screenshots/get_time_range.ts rename to x-pack/plugins/screenshotting/server/screenshots/get_time_range.ts index 111c68de62bdf..6734a35932b59 100644 --- a/x-pack/plugins/reporting/server/lib/screenshots/get_time_range.ts +++ b/x-pack/plugins/screenshotting/server/screenshots/get_time_range.ts @@ -5,17 +5,18 @@ * 2.0. */ -import { LevelLogger, startTrace } from '../'; -import { LayoutInstance } from '../layouts'; -import { HeadlessChromiumDriver } from '../../browsers'; +import apm from 'elastic-apm-node'; +import type { Logger } from 'src/core/server'; +import type { HeadlessChromiumDriver } from '../browsers'; +import { Layout } from '../layouts'; import { CONTEXT_GETTIMERANGE } from './constants'; export const getTimeRange = async ( browser: HeadlessChromiumDriver, - layout: LayoutInstance, - logger: LevelLogger + logger: Logger, + layout: Layout ): Promise => { - const endTrace = startTrace('get_time_range', 'read'); + const span = apm.startSpan('get_time_range', 'read'); logger.debug('getting timeRange'); const timeRange = await browser.evaluate( @@ -46,7 +47,7 @@ export const getTimeRange = async ( logger.debug('no timeRange'); } - endTrace(); + span?.end(); return timeRange; }; diff --git a/x-pack/plugins/screenshotting/server/screenshots/index.test.ts b/x-pack/plugins/screenshotting/server/screenshots/index.test.ts new file mode 100644 index 0000000000000..1fa7eb66192c8 --- /dev/null +++ b/x-pack/plugins/screenshotting/server/screenshots/index.test.ts @@ -0,0 +1,411 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { of, throwError, NEVER } from 'rxjs'; +import type { Logger } from 'src/core/server'; +import { createMockBrowserDriver, createMockBrowserDriverFactory } from '../browsers/mock'; +import type { HeadlessChromiumDriverFactory } from '../browsers'; +import * as Layouts from '../layouts/create_layout'; +import { createMockLayout } from '../layouts/mock'; +import { CONTEXT_ELEMENTATTRIBUTES } from './constants'; +import { getScreenshots, ScreenshotOptions } from '.'; + +/* + * Tests + */ +describe('Screenshot Observable Pipeline', () => { + let driver: ReturnType; + let driverFactory: jest.Mocked; + let layout: ReturnType; + let logger: jest.Mocked; + let options: ScreenshotOptions; + + beforeEach(async () => { + driver = createMockBrowserDriver(); + driverFactory = createMockBrowserDriverFactory(driver); + layout = createMockLayout(); + logger = { + debug: jest.fn(), + error: jest.fn(), + info: jest.fn(), + } as unknown as jest.Mocked; + options = { + browserTimezone: 'UTC', + conditionalHeaders: {}, + layout: {}, + timeouts: { + loadDelay: 2000, + openUrl: 120000, + waitForElements: 20000, + renderComplete: 10000, + }, + urls: ['/welcome/home/start/index.htm'], + } as unknown as typeof options; + + jest.spyOn(Layouts, 'createLayout').mockReturnValue(layout); + + driver.isPageOpen.mockReturnValue(true); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('pipelines a single url into screenshot and timeRange', async () => { + const result = await getScreenshots(driverFactory, logger, options).toPromise(); + + expect(result).toHaveProperty('results'); + expect(result.results).toMatchInlineSnapshot(` + Array [ + Object { + "elementsPositionAndAttributes": Array [ + Object { + "attributes": Object { + "description": "Default ", + "title": "Default Mock Title", + }, + "position": Object { + "boundingClientRect": Object { + "height": 600, + "left": 0, + "top": 0, + "width": 800, + }, + "scroll": Object { + "x": 0, + "y": 0, + }, + }, + }, + ], + "error": undefined, + "screenshots": Array [ + Object { + "data": Object { + "data": Array [ + 115, + 99, + 114, + 101, + 101, + 110, + 115, + 104, + 111, + 116, + ], + "type": "Buffer", + }, + "description": "Default ", + "title": "Default Mock Title", + }, + ], + "timeRange": "Default GetTimeRange Result", + }, + ] + `); + }); + + it('pipelines multiple urls into', async () => { + driver.screenshot.mockResolvedValue(Buffer.from('some screenshots')); + const result = await getScreenshots(driverFactory, logger, { + ...options, + urls: ['/welcome/home/start/index2.htm', '/welcome/home/start/index.php3?page=./home.php'], + }).toPromise(); + + expect(result).toHaveProperty('results'); + expect(result.results).toMatchInlineSnapshot(` + Array [ + Object { + "elementsPositionAndAttributes": Array [ + Object { + "attributes": Object { + "description": "Default ", + "title": "Default Mock Title", + }, + "position": Object { + "boundingClientRect": Object { + "height": 600, + "left": 0, + "top": 0, + "width": 800, + }, + "scroll": Object { + "x": 0, + "y": 0, + }, + }, + }, + ], + "error": undefined, + "screenshots": Array [ + Object { + "data": Object { + "data": Array [ + 115, + 111, + 109, + 101, + 32, + 115, + 99, + 114, + 101, + 101, + 110, + 115, + 104, + 111, + 116, + 115, + ], + "type": "Buffer", + }, + "description": "Default ", + "title": "Default Mock Title", + }, + ], + "timeRange": "Default GetTimeRange Result", + }, + Object { + "elementsPositionAndAttributes": Array [ + Object { + "attributes": Object { + "description": "Default ", + "title": "Default Mock Title", + }, + "position": Object { + "boundingClientRect": Object { + "height": 600, + "left": 0, + "top": 0, + "width": 800, + }, + "scroll": Object { + "x": 0, + "y": 0, + }, + }, + }, + ], + "error": undefined, + "screenshots": Array [ + Object { + "data": Object { + "data": Array [ + 115, + 111, + 109, + 101, + 32, + 115, + 99, + 114, + 101, + 101, + 110, + 115, + 104, + 111, + 116, + 115, + ], + "type": "Buffer", + }, + "description": "Default ", + "title": "Default Mock Title", + }, + ], + "timeRange": "Default GetTimeRange Result", + }, + ] + `); + + expect(driver.open).toHaveBeenCalledTimes(2); + expect(driver.open).nthCalledWith( + 1, + expect.anything(), + expect.objectContaining({ waitForSelector: '.kbnAppWrapper' }), + expect.anything() + ); + expect(driver.open).nthCalledWith( + 2, + expect.anything(), + expect.objectContaining({ waitForSelector: '[data-shared-page="2"]' }), + expect.anything() + ); + }); + + describe('error handling', () => { + it('recovers if waitForSelector fails', async () => { + driver.waitForSelector.mockImplementation((selectorArg: string) => { + throw new Error('Mock error!'); + }); + const result = await getScreenshots(driverFactory, logger, { + ...options, + urls: ['/welcome/home/start/index2.htm', '/welcome/home/start/index.php3?page=./home.php3'], + }).toPromise(); + + expect(result).toHaveProperty('results'); + expect(result.results).toMatchInlineSnapshot(` + Array [ + Object { + "elementsPositionAndAttributes": Array [ + Object { + "attributes": Object {}, + "position": Object { + "boundingClientRect": Object { + "height": 100, + "left": 0, + "top": 0, + "width": 100, + }, + "scroll": Object { + "x": 0, + "y": 0, + }, + }, + }, + ], + "error": [Error: The "wait for elements" phase encountered an error: Error: An error occurred when trying to read the page for visualization panel info: Error: Mock error!], + "screenshots": Array [ + Object { + "data": Object { + "data": Array [ + 115, + 99, + 114, + 101, + 101, + 110, + 115, + 104, + 111, + 116, + ], + "type": "Buffer", + }, + "description": undefined, + "title": undefined, + }, + ], + "timeRange": null, + }, + Object { + "elementsPositionAndAttributes": Array [ + Object { + "attributes": Object {}, + "position": Object { + "boundingClientRect": Object { + "height": 100, + "left": 0, + "top": 0, + "width": 100, + }, + "scroll": Object { + "x": 0, + "y": 0, + }, + }, + }, + ], + "error": [Error: The "wait for elements" phase encountered an error: Error: An error occurred when trying to read the page for visualization panel info: Error: Mock error!], + "screenshots": Array [ + Object { + "data": Object { + "data": Array [ + 115, + 99, + 114, + 101, + 101, + 110, + 115, + 104, + 111, + 116, + ], + "type": "Buffer", + }, + "description": undefined, + "title": undefined, + }, + ], + "timeRange": null, + }, + ] + `); + }); + + it('observes page exit', async () => { + driverFactory.createPage.mockReturnValue( + of({ driver, exit$: throwError('Instant timeout has fired!'), metrics$: NEVER }) + ); + + await expect( + getScreenshots(driverFactory, logger, options).toPromise() + ).rejects.toMatchInlineSnapshot(`"Instant timeout has fired!"`); + }); + + it(`uses defaults for element positions and size when Kibana page is not ready`, async () => { + driver.evaluate.mockImplementation(async (_, { context }) => + context === CONTEXT_ELEMENTATTRIBUTES ? null : undefined + ); + + layout.getViewport = () => null; + const result = await getScreenshots(driverFactory, logger, options).toPromise(); + + expect(result).toHaveProperty('results'); + expect(result.results).toMatchInlineSnapshot(` + Array [ + Object { + "elementsPositionAndAttributes": Array [ + Object { + "attributes": Object {}, + "position": Object { + "boundingClientRect": Object { + "height": 1200, + "left": 0, + "top": 0, + "width": 1800, + }, + "scroll": Object { + "x": 0, + "y": 0, + }, + }, + }, + ], + "error": undefined, + "screenshots": Array [ + Object { + "data": Object { + "data": Array [ + 115, + 99, + 114, + 101, + 101, + 110, + 115, + 104, + 111, + 116, + ], + "type": "Buffer", + }, + "description": undefined, + "title": undefined, + }, + ], + "timeRange": undefined, + }, + ] + `); + }); + }); +}); diff --git a/x-pack/plugins/screenshotting/server/screenshots/index.ts b/x-pack/plugins/screenshotting/server/screenshots/index.ts new file mode 100644 index 0000000000000..e264538d8be39 --- /dev/null +++ b/x-pack/plugins/screenshotting/server/screenshots/index.ts @@ -0,0 +1,102 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import apm from 'elastic-apm-node'; +import { from, of, Observable } from 'rxjs'; +import { + catchError, + concatMap, + first, + map, + mergeMap, + take, + takeUntil, + toArray, +} from 'rxjs/operators'; +import type { Logger } from 'src/core/server'; +import { LayoutParams } from '../../common'; +import type { HeadlessChromiumDriverFactory, PerformanceMetrics } from '../browsers'; +import { createLayout } from '../layouts'; +import type { Layout } from '../layouts'; +import { ScreenshotObservableHandler } from './observable'; +import type { ScreenshotObservableOptions, ScreenshotObservableResult } from './observable'; + +export interface ScreenshotOptions extends ScreenshotObservableOptions { + layout: LayoutParams; +} + +export interface ScreenshotResult { + /** + * Used layout instance constructed from the given options. + */ + layout: Layout; + + /** + * Collected performance metrics during the screenshotting session. + */ + metrics$: Observable; + + /** + * Screenshotting results. + */ + results: ScreenshotObservableResult[]; +} + +const DEFAULT_SETUP_RESULT = { + elementsPositionAndAttributes: null, + timeRange: null, +}; + +export function getScreenshots( + browserDriverFactory: HeadlessChromiumDriverFactory, + logger: Logger, + options: ScreenshotOptions +): Observable { + const apmTrans = apm.startTransaction('screenshot-pipeline', 'screenshotting'); + const apmCreateLayout = apmTrans?.startSpan('create-layout', 'setup'); + const layout = createLayout(options.layout); + logger.debug(`Layout: width=${layout.width} height=${layout.height}`); + apmCreateLayout?.end(); + + const apmCreatePage = apmTrans?.startSpan('create-page', 'wait'); + const { + browserTimezone, + timeouts: { openUrl: openUrlTimeout }, + } = options; + + return browserDriverFactory.createPage({ browserTimezone, openUrlTimeout }, logger).pipe( + mergeMap(({ driver, exit$, metrics$ }) => { + apmCreatePage?.end(); + metrics$.subscribe(({ cpu, memory }) => { + apmTrans?.setLabel('cpu', cpu, false); + apmTrans?.setLabel('memory', memory, false); + }); + exit$.subscribe({ error: () => apmTrans?.end() }); + + const screen = new ScreenshotObservableHandler(driver, logger, layout, options); + + return from(options.urls).pipe( + concatMap((url, index) => + screen.setupPage(index, url, apmTrans).pipe( + catchError((error) => { + screen.checkPageIsOpen(); // this fails the job if the browser has closed + + logger.error(error); + return of({ ...DEFAULT_SETUP_RESULT, error }); // allow failover screenshot capture + }), + takeUntil(exit$), + screen.getScreenshots() + ) + ), + take(options.urls.length), + toArray(), + map((results) => ({ layout, metrics$, results })) + ); + }), + first() + ); +} diff --git a/x-pack/plugins/reporting/server/lib/screenshots/inject_css.ts b/x-pack/plugins/screenshotting/server/screenshots/inject_css.ts similarity index 78% rename from x-pack/plugins/reporting/server/lib/screenshots/inject_css.ts rename to x-pack/plugins/screenshotting/server/screenshots/inject_css.ts index 607441e719c32..d4e38600db7de 100644 --- a/x-pack/plugins/reporting/server/lib/screenshots/inject_css.ts +++ b/x-pack/plugins/screenshotting/server/screenshots/inject_css.ts @@ -8,8 +8,9 @@ import { i18n } from '@kbn/i18n'; import fs from 'fs'; import { promisify } from 'util'; -import { LevelLogger, startTrace } from '../'; -import { HeadlessChromiumDriver } from '../../browsers'; +import apm from 'elastic-apm-node'; +import type { Logger } from 'src/core/server'; +import type { HeadlessChromiumDriver } from '../browsers'; import { Layout } from '../layouts'; import { CONTEXT_INJECTCSS } from './constants'; @@ -17,12 +18,12 @@ const fsp = { readFile: promisify(fs.readFile) }; export const injectCustomCss = async ( browser: HeadlessChromiumDriver, - layout: Layout, - logger: LevelLogger + logger: Logger, + layout: Layout ): Promise => { - const endTrace = startTrace('inject_css', 'correction'); + const span = apm.startSpan('inject_css', 'correction'); logger.debug( - i18n.translate('xpack.reporting.screencapture.injectingCss', { + i18n.translate('xpack.screenshotting.screencapture.injectingCss', { defaultMessage: 'injecting custom css', }) ); @@ -49,12 +50,12 @@ export const injectCustomCss = async ( } catch (err) { logger.error(err); throw new Error( - i18n.translate('xpack.reporting.screencapture.injectCss', { + i18n.translate('xpack.screenshotting.screencapture.injectCss', { defaultMessage: `An error occurred when trying to update Kibana CSS for reporting. {error}`, values: { error: err }, }) ); } - endTrace(); + span?.end(); }; diff --git a/x-pack/plugins/screenshotting/server/screenshots/mock.ts b/x-pack/plugins/screenshotting/server/screenshots/mock.ts new file mode 100644 index 0000000000000..edef9c9044c9a --- /dev/null +++ b/x-pack/plugins/screenshotting/server/screenshots/mock.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { of, NEVER } from 'rxjs'; +import { createMockLayout } from '../layouts/mock'; +import type { getScreenshots, ScreenshotResult } from '.'; + +export function createMockScreenshots(): jest.Mocked<{ getScreenshots: typeof getScreenshots }> { + return { + getScreenshots: jest.fn((driverFactory, logger, options) => + of({ + layout: createMockLayout(), + metrics$: NEVER, + results: options.urls.map(() => ({ + timeRange: null, + screenshots: [ + { + data: Buffer.from('screenshot'), + description: null, + title: null, + }, + ], + })), + } as ScreenshotResult) + ), + }; +} diff --git a/x-pack/plugins/screenshotting/server/screenshots/observable.test.ts b/x-pack/plugins/screenshotting/server/screenshots/observable.test.ts new file mode 100644 index 0000000000000..5d5fbbde4e048 --- /dev/null +++ b/x-pack/plugins/screenshotting/server/screenshots/observable.test.ts @@ -0,0 +1,102 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { interval, throwError, of } from 'rxjs'; +import { map } from 'rxjs/operators'; +import type { Logger } from 'src/core/server'; +import type { ConditionalHeaders } from '../browsers'; +import { createMockBrowserDriver } from '../browsers/mock'; +import { createMockLayout } from '../layouts/mock'; +import { ScreenshotObservableHandler, ScreenshotObservableOptions } from './observable'; + +describe('ScreenshotObservableHandler', () => { + let browser: ReturnType; + let layout: ReturnType; + let logger: jest.Mocked; + let options: ScreenshotObservableOptions; + + beforeEach(async () => { + browser = createMockBrowserDriver(); + layout = createMockLayout(); + logger = { error: jest.fn() } as unknown as jest.Mocked; + options = { + conditionalHeaders: { + headers: { testHeader: 'testHeadValue' }, + conditions: {} as unknown as ConditionalHeaders['conditions'], + }, + timeouts: { + loadDelay: 5000, + openUrl: 30000, + waitForElements: 30000, + renderComplete: 30000, + }, + urls: [], + }; + + browser.isPageOpen.mockReturnValue(true); + }); + + describe('waitUntil', () => { + it('catches TimeoutError and references the timeout config in a custom message', async () => { + const screenshots = new ScreenshotObservableHandler(browser, logger, layout, options); + const test$ = interval(1000).pipe(screenshots.waitUntil(200, 'Test Config')); + + const testPipeline = () => test$.toPromise(); + await expect(testPipeline).rejects.toMatchInlineSnapshot( + `[Error: The "Test Config" phase took longer than 0.2 seconds.]` + ); + }); + + it('catches other Errors and explains where they were thrown', async () => { + const screenshots = new ScreenshotObservableHandler(browser, logger, layout, options); + const test$ = throwError(new Error(`Test Error to Throw`)).pipe( + screenshots.waitUntil(200, 'Test Config') + ); + + const testPipeline = () => test$.toPromise(); + await expect(testPipeline).rejects.toMatchInlineSnapshot( + `[Error: The "Test Config" phase encountered an error: Error: Test Error to Throw]` + ); + }); + + it('is a pass-through if there is no Error', async () => { + const screenshots = new ScreenshotObservableHandler(browser, logger, layout, options); + const test$ = of('nice to see you').pipe(screenshots.waitUntil(20, 'xxxxxxxxxxx')); + + await expect(test$.toPromise()).resolves.toBe(`nice to see you`); + }); + }); + + describe('checkPageIsOpen', () => { + it('throws a decorated Error when page is not open', async () => { + browser.isPageOpen.mockReturnValue(false); + const screenshots = new ScreenshotObservableHandler(browser, logger, layout, options); + const test$ = of(234455).pipe( + map((input) => { + screenshots.checkPageIsOpen(); + return input; + }) + ); + + await expect(test$.toPromise()).rejects.toMatchInlineSnapshot( + `[Error: Browser was closed unexpectedly! Check the server logs for more info.]` + ); + }); + + it('is a pass-through when the page is open', async () => { + const screenshots = new ScreenshotObservableHandler(browser, logger, layout, options); + const test$ = of(234455).pipe( + map((input) => { + screenshots.checkPageIsOpen(); + return input; + }) + ); + + await expect(test$.toPromise()).resolves.toBe(234455); + }); + }); +}); diff --git a/x-pack/plugins/screenshotting/server/screenshots/observable.ts b/x-pack/plugins/screenshotting/server/screenshots/observable.ts new file mode 100644 index 0000000000000..b77180a9399b1 --- /dev/null +++ b/x-pack/plugins/screenshotting/server/screenshots/observable.ts @@ -0,0 +1,258 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Transaction } from 'elastic-apm-node'; +import { defer, forkJoin, throwError, Observable } from 'rxjs'; +import { catchError, mergeMap, switchMapTo, timeoutWith } from 'rxjs/operators'; +import type { Logger } from 'src/core/server'; +import type { Layout as ScreenshotModeLayout } from 'src/plugins/screenshot_mode/common'; +import type { ConditionalHeaders, HeadlessChromiumDriver } from '../browsers'; +import { getChromiumDisconnectedError } from '../browsers'; +import type { Layout } from '../layouts'; +import type { ElementsPositionAndAttribute } from './get_element_position_data'; +import { getElementPositionAndAttributes } from './get_element_position_data'; +import { getNumberOfItems } from './get_number_of_items'; +import { getRenderErrors } from './get_render_errors'; +import { getScreenshots } from './get_screenshots'; +import type { Screenshot } from './get_screenshots'; +import { getTimeRange } from './get_time_range'; +import { injectCustomCss } from './inject_css'; +import { openUrl } from './open_url'; +import type { UrlOrUrlWithContext } from './open_url'; +import { waitForRenderComplete } from './wait_for_render'; +import { waitForVisualizations } from './wait_for_visualizations'; + +export interface PhaseTimeouts { + /** + * Open URL phase timeout. + */ + openUrl: number; + + /** + * Timeout of the page readiness phase. + */ + waitForElements: number; + + /** + * Timeout of the page render phase. + */ + renderComplete: number; + + /** + * An additional delay to wait until the visualizations are ready. + */ + loadDelay: number; +} + +export interface ScreenshotObservableOptions { + /** + * The browser timezone that will be emulated in the browser instance. + * This option should be used to keep timezone on server and client in sync. + */ + browserTimezone?: string; + + /** + * Custom headers to be sent with each request. + */ + conditionalHeaders: ConditionalHeaders; + + /** + * Timeouts for each phase of the screenshot. + */ + timeouts: PhaseTimeouts; + + /** + * The list or URL to take screenshots of. + * Every item can either be a string or a tuple containing a URL and a context. + */ + urls: UrlOrUrlWithContext[]; +} + +export interface ScreenshotObservableResult { + /** + * Used time range filter. + */ + timeRange: string | null; + + /** + * Taken screenshots. + */ + screenshots: Screenshot[]; + + /** + * Error that occurred during the screenshotting. + */ + error?: Error; + + /** + * Individual visualizations might encounter errors at runtime. If there are any they are added to this + * field. Any text captured here is intended to be shown to the user for debugging purposes, reporting + * does no further sanitization on these strings. + */ + renderErrors?: string[]; + + /** + * @internal + */ + elementsPositionAndAttributes?: ElementsPositionAndAttribute[]; // NOTE: for testing +} + +interface PageSetupResults { + elementsPositionAndAttributes: ElementsPositionAndAttribute[] | null; + timeRange: string | null; + error?: Error; +} + +const DEFAULT_SCREENSHOT_CLIP_HEIGHT = 1200; +const DEFAULT_SCREENSHOT_CLIP_WIDTH = 1800; + +const getDefaultElementPosition = (dimensions: { height?: number; width?: number } | null) => { + const height = dimensions?.height || DEFAULT_SCREENSHOT_CLIP_HEIGHT; + const width = dimensions?.width || DEFAULT_SCREENSHOT_CLIP_WIDTH; + + return [ + { + position: { + boundingClientRect: { top: 0, left: 0, height, width }, + scroll: { x: 0, y: 0 }, + }, + attributes: {}, + }, + ]; +}; + +/* + * If Kibana is showing a non-HTML error message, the viewport might not be + * provided by the browser. + */ +const getDefaultViewPort = () => ({ + height: DEFAULT_SCREENSHOT_CLIP_HEIGHT, + width: DEFAULT_SCREENSHOT_CLIP_WIDTH, + zoom: 1, +}); + +export class ScreenshotObservableHandler { + constructor( + private readonly driver: HeadlessChromiumDriver, + private readonly logger: Logger, + private readonly layout: Layout, + private options: ScreenshotObservableOptions + ) {} + + /* + * Decorates a TimeoutError with context of the phase that has timed out. + */ + public waitUntil(timeoutValue: number, label: string) { + return (source: Observable) => + source.pipe( + catchError((error) => { + throw new Error(`The "${label}" phase encountered an error: ${error}`); + }), + timeoutWith( + timeoutValue, + throwError( + new Error(`The "${label}" phase took longer than ${timeoutValue / 1000} seconds.`) + ) + ) + ); + } + + private openUrl(index: number, url: UrlOrUrlWithContext) { + return defer(() => + openUrl( + this.driver, + this.logger, + this.options.timeouts.openUrl, + index, + url, + this.options.conditionalHeaders, + this.layout.id as ScreenshotModeLayout + ) + ).pipe(this.waitUntil(this.options.timeouts.openUrl, 'open URL')); + } + + private waitForElements() { + const driver = this.driver; + const waitTimeout = this.options.timeouts.waitForElements; + + return defer(() => getNumberOfItems(driver, this.logger, waitTimeout, this.layout)).pipe( + mergeMap((itemsCount) => { + // set the viewport to the dimentions from the job, to allow elements to flow into the expected layout + const viewport = this.layout.getViewport(itemsCount) || getDefaultViewPort(); + + return forkJoin([ + driver.setViewport(viewport, this.logger), + waitForVisualizations(driver, this.logger, waitTimeout, itemsCount, this.layout), + ]); + }), + this.waitUntil(waitTimeout, 'wait for elements') + ); + } + + private completeRender(apmTrans: Transaction | null) { + const driver = this.driver; + const layout = this.layout; + const logger = this.logger; + + return defer(async () => { + // Waiting till _after_ elements have rendered before injecting our CSS + // allows for them to be displayed properly in many cases + await injectCustomCss(driver, logger, layout); + + const apmPositionElements = apmTrans?.startSpan('position-elements', 'correction'); + // position panel elements for print layout + await layout.positionElements?.(driver, logger); + apmPositionElements?.end(); + + await waitForRenderComplete(driver, logger, this.options.timeouts.loadDelay, layout); + }).pipe( + mergeMap(() => + forkJoin({ + timeRange: getTimeRange(driver, logger, layout), + elementsPositionAndAttributes: getElementPositionAndAttributes(driver, logger, layout), + renderErrors: getRenderErrors(driver, logger, layout), + }) + ), + this.waitUntil(this.options.timeouts.renderComplete, 'render complete') + ); + } + + public setupPage(index: number, url: UrlOrUrlWithContext, apmTrans: Transaction | null) { + return this.openUrl(index, url).pipe( + switchMapTo(this.waitForElements()), + switchMapTo(this.completeRender(apmTrans)) + ); + } + + public getScreenshots() { + return (withRenderComplete: Observable) => + withRenderComplete.pipe( + mergeMap(async (data: PageSetupResults): Promise => { + this.checkPageIsOpen(); // fail the report job if the browser has closed + + const elements = + data.elementsPositionAndAttributes ?? + getDefaultElementPosition(this.layout.getViewport(1)); + const screenshots = await getScreenshots(this.driver, this.logger, elements); + const { timeRange, error: setupError } = data; + + return { + timeRange, + screenshots, + error: setupError, + elementsPositionAndAttributes: elements, + }; + }) + ); + } + + public checkPageIsOpen() { + if (!this.driver.isPageOpen()) { + throw getChromiumDisconnectedError(); + } + } +} diff --git a/x-pack/plugins/reporting/server/lib/screenshots/open_url.ts b/x-pack/plugins/screenshotting/server/screenshots/open_url.ts similarity index 54% rename from x-pack/plugins/reporting/server/lib/screenshots/open_url.ts rename to x-pack/plugins/screenshotting/server/screenshots/open_url.ts index b26037aa917b8..08639122a4c26 100644 --- a/x-pack/plugins/reporting/server/lib/screenshots/open_url.ts +++ b/x-pack/plugins/screenshotting/server/screenshots/open_url.ts @@ -6,52 +6,57 @@ */ import { i18n } from '@kbn/i18n'; -import { LevelLogger, startTrace } from '../'; -import { LocatorParams, UrlOrUrlLocatorTuple } from '../../../common/types'; -import { HeadlessChromiumDriver } from '../../browsers'; -import { ConditionalHeaders } from '../../export_types/common'; -import { Layout } from '../layouts'; +import apm from 'elastic-apm-node'; +import type { Logger } from 'src/core/server'; +import type { Layout } from 'src/plugins/screenshot_mode/common'; +import { Context } from '../../common'; +import type { HeadlessChromiumDriver } from '../browsers'; +import type { ConditionalHeaders } from '../browsers'; import { DEFAULT_PAGELOAD_SELECTOR } from './constants'; +type Url = string; +type UrlWithContext = [url: Url, context: Context]; +export type UrlOrUrlWithContext = Url | UrlWithContext; + export const openUrl = async ( - timeout: number, browser: HeadlessChromiumDriver, + logger: Logger, + timeout: number, index: number, - urlOrUrlLocatorTuple: UrlOrUrlLocatorTuple, + urlOrUrlWithContext: UrlOrUrlWithContext, conditionalHeaders: ConditionalHeaders, - layout: undefined | Layout, - logger: LevelLogger + layout?: Layout ): Promise => { // If we're moving to another page in the app, we'll want to wait for the app to tell us // it's loaded the next page. const page = index + 1; const waitForSelector = page > 1 ? `[data-shared-page="${page}"]` : DEFAULT_PAGELOAD_SELECTOR; + const span = apm.startSpan('open_url', 'wait'); - const endTrace = startTrace('open_url', 'wait'); let url: string; - let locator: undefined | LocatorParams; + let context: Context | undefined; - if (typeof urlOrUrlLocatorTuple === 'string') { - url = urlOrUrlLocatorTuple; + if (typeof urlOrUrlWithContext === 'string') { + url = urlOrUrlWithContext; } else { - [url, locator] = urlOrUrlLocatorTuple; + [url, context] = urlOrUrlWithContext; } try { await browser.open( url, - { conditionalHeaders, waitForSelector, timeout, locator, layout }, + { conditionalHeaders, context, layout, waitForSelector, timeout }, logger ); } catch (err) { logger.error(err); throw new Error( - i18n.translate('xpack.reporting.screencapture.couldntLoadKibana', { + i18n.translate('xpack.screenshotting.screencapture.couldntLoadKibana', { defaultMessage: `An error occurred when trying to open the Kibana URL: {error}`, values: { error: err }, }) ); } - endTrace(); + span?.end(); }; diff --git a/x-pack/plugins/reporting/server/lib/screenshots/wait_for_render.ts b/x-pack/plugins/screenshotting/server/screenshots/wait_for_render.ts similarity index 84% rename from x-pack/plugins/reporting/server/lib/screenshots/wait_for_render.ts rename to x-pack/plugins/screenshotting/server/screenshots/wait_for_render.ts index 1ac4b58b61507..bdc75572e685e 100644 --- a/x-pack/plugins/reporting/server/lib/screenshots/wait_for_render.ts +++ b/x-pack/plugins/screenshotting/server/screenshots/wait_for_render.ts @@ -6,21 +6,22 @@ */ import { i18n } from '@kbn/i18n'; -import { LevelLogger, startTrace } from '../'; -import { HeadlessChromiumDriver } from '../../browsers'; -import { LayoutInstance } from '../layouts'; +import apm from 'elastic-apm-node'; +import type { Logger } from 'src/core/server'; +import type { HeadlessChromiumDriver } from '../browsers'; +import { Layout } from '../layouts'; import { CONTEXT_WAITFORRENDER } from './constants'; export const waitForRenderComplete = async ( - loadDelay: number, browser: HeadlessChromiumDriver, - layout: LayoutInstance, - logger: LevelLogger + logger: Logger, + loadDelay: number, + layout: Layout ) => { - const endTrace = startTrace('wait_for_render', 'wait'); + const span = apm.startSpan('wait_for_render', 'wait'); logger.debug( - i18n.translate('xpack.reporting.screencapture.waitingForRenderComplete', { + i18n.translate('xpack.screenshotting.screencapture.waitingForRenderComplete', { defaultMessage: 'waiting for rendering to complete', }) ); @@ -74,11 +75,11 @@ export const waitForRenderComplete = async ( ) .then(() => { logger.debug( - i18n.translate('xpack.reporting.screencapture.renderIsComplete', { + i18n.translate('xpack.screenshotting.screencapture.renderIsComplete', { defaultMessage: 'rendering is complete', }) ); - endTrace(); + span?.end(); }); }; diff --git a/x-pack/plugins/reporting/server/lib/screenshots/wait_for_visualizations.ts b/x-pack/plugins/screenshotting/server/screenshots/wait_for_visualizations.ts similarity index 80% rename from x-pack/plugins/reporting/server/lib/screenshots/wait_for_visualizations.ts rename to x-pack/plugins/screenshotting/server/screenshots/wait_for_visualizations.ts index 10a53b238d892..3102f444c2340 100644 --- a/x-pack/plugins/reporting/server/lib/screenshots/wait_for_visualizations.ts +++ b/x-pack/plugins/screenshotting/server/screenshots/wait_for_visualizations.ts @@ -6,9 +6,10 @@ */ import { i18n } from '@kbn/i18n'; -import { LevelLogger, startTrace } from '../'; -import { HeadlessChromiumDriver } from '../../browsers'; -import { LayoutInstance } from '../layouts'; +import apm from 'elastic-apm-node'; +import type { Logger } from 'src/core/server'; +import type { HeadlessChromiumDriver } from '../browsers'; +import { Layout } from '../layouts'; import { CONTEXT_WAITFORELEMENTSTOBEINDOM } from './constants'; interface CompletedItemsCountParameters { @@ -36,17 +37,17 @@ const getCompletedItemsCount = ({ * 3. Wait for the render complete event to be fired once for each item */ export const waitForVisualizations = async ( - timeout: number, browser: HeadlessChromiumDriver, + logger: Logger, + timeout: number, toEqual: number, - layout: LayoutInstance, - logger: LevelLogger + layout: Layout ): Promise => { - const endTrace = startTrace('wait_for_visualizations', 'wait'); + const span = apm.startSpan('wait_for_visualizations', 'wait'); const { renderComplete: renderCompleteSelector } = layout.selectors; logger.debug( - i18n.translate('xpack.reporting.screencapture.waitingForRenderedElements', { + i18n.translate('xpack.screenshotting.screencapture.waitingForRenderedElements', { defaultMessage: `waiting for {itemsCount} rendered elements to be in the DOM`, values: { itemsCount: toEqual }, }) @@ -63,12 +64,12 @@ export const waitForVisualizations = async ( } catch (err) { logger.error(err); throw new Error( - i18n.translate('xpack.reporting.screencapture.couldntFinishRendering', { + i18n.translate('xpack.screenshotting.screencapture.couldntFinishRendering', { defaultMessage: `An error occurred when trying to wait for {count} visualizations to finish rendering. {error}`, values: { count: toEqual, error: err }, }) ); } - endTrace(); + span?.end(); }; diff --git a/x-pack/plugins/screenshotting/server/utils.ts b/x-pack/plugins/screenshotting/server/utils.ts new file mode 100644 index 0000000000000..eb6b18ef85906 --- /dev/null +++ b/x-pack/plugins/screenshotting/server/utils.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ChromiumArchivePaths, download as baseDownload, install as baseInstall } from './browsers'; + +const paths = new ChromiumArchivePaths(); + +export const download = baseDownload.bind(undefined, paths); +export const install = baseInstall.bind(undefined, paths); diff --git a/x-pack/plugins/screenshotting/tsconfig.json b/x-pack/plugins/screenshotting/tsconfig.json new file mode 100644 index 0000000000000..a1e81c4fb38d9 --- /dev/null +++ b/x-pack/plugins/screenshotting/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true + }, + "include": [ + "common/**/*", + "public/**/*", + "server/**/*", + "../../../typings/**/*" + ], + "references": [ + { "path": "../../../src/core/tsconfig.json" }, + { "path": "../../../src/plugins/screenshot_mode/tsconfig.json" }, + ] +} diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index 25c6b07fd03af..14e3c8cc95fe6 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -7,7 +7,6 @@ import type { TransformConfigSchema } from './transforms/types'; import { ENABLE_CASE_CONNECTOR } from '../../cases/common'; -import { METADATA_TRANSFORMS_PATTERN } from './endpoint/constants'; /** * as const @@ -252,8 +251,6 @@ export const DETECTION_ENGINE_PREPACKAGED_URL = export const DETECTION_ENGINE_PRIVILEGES_URL = `${DETECTION_ENGINE_URL}/privileges` as const; export const DETECTION_ENGINE_INDEX_URL = `${DETECTION_ENGINE_URL}/index` as const; export const DETECTION_ENGINE_TAGS_URL = `${DETECTION_ENGINE_URL}/tags` as const; -export const DETECTION_ENGINE_RULES_STATUS_URL = - `${DETECTION_ENGINE_RULES_URL}/_find_statuses` as const; export const DETECTION_ENGINE_PREPACKAGED_RULES_STATUS_URL = `${DETECTION_ENGINE_RULES_URL}/prepackaged/_status` as const; export const DETECTION_ENGINE_RULES_BULK_ACTION = @@ -362,8 +359,6 @@ export const showAllOthersBucket: string[] = [ */ export const ELASTIC_NAME = 'estc' as const; -export const METADATA_TRANSFORM_STATS_URL = `/api/transform/transforms/${METADATA_TRANSFORMS_PATTERN}/_stats`; - export const RISKY_HOSTS_INDEX_PREFIX = 'ml_host_risk_score_latest_' as const; export const TRANSFORM_STATES = { diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts index 3933d7e39275e..23c45c03b62a0 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts @@ -344,6 +344,15 @@ export type LastFailureAt = t.TypeOf; export const last_failure_message = t.string; export type LastFailureMessage = t.TypeOf; +export const last_gap = t.string; +export type LastGap = t.TypeOf; + +export const bulk_create_time_durations = t.array(t.string); +export type BulkCreateTimeDurations = t.TypeOf; + +export const search_after_time_durations = t.array(t.string); +export type SearchAfterTimeDurations = t.TypeOf; + export const status_date = IsoDateString; export type StatusDate = t.TypeOf; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.ts index a9b9db09212dd..c5f4e5631e5c8 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.ts @@ -71,6 +71,9 @@ import { last_failure_at, last_failure_message, namespace, + last_gap, + bulk_create_time_durations, + search_after_time_durations, } from '../common/schemas'; export const createSchema = < @@ -422,6 +425,9 @@ const responseOptionalFields = { last_success_message, last_failure_at, last_failure_message, + last_gap, + bulk_create_time_durations, + search_after_time_durations, }; export const fullResponseSchema = t.intersection([ diff --git a/x-pack/plugins/security_solution/common/endpoint/constants.ts b/x-pack/plugins/security_solution/common/endpoint/constants.ts index a7fe91345dd14..bcd3b9524bf60 100644 --- a/x-pack/plugins/security_solution/common/endpoint/constants.ts +++ b/x-pack/plugins/security_solution/common/endpoint/constants.ts @@ -44,6 +44,7 @@ export const LIMITED_CONCURRENCY_ENDPOINT_COUNT = 100; export const BASE_ENDPOINT_ROUTE = '/api/endpoint'; export const HOST_METADATA_LIST_ROUTE = `${BASE_ENDPOINT_ROUTE}/metadata`; export const HOST_METADATA_GET_ROUTE = `${BASE_ENDPOINT_ROUTE}/metadata/{id}`; +export const METADATA_TRANSFORMS_STATUS_ROUTE = `${BASE_ENDPOINT_ROUTE}/metadata/transforms`; export const TRUSTED_APPS_GET_API = `${BASE_ENDPOINT_ROUTE}/trusted_apps/{id}`; export const TRUSTED_APPS_LIST_API = `${BASE_ENDPOINT_ROUTE}/trusted_apps`; @@ -68,3 +69,6 @@ export const failedFleetActionErrorCode = '424'; export const ENDPOINT_DEFAULT_PAGE = 0; export const ENDPOINT_DEFAULT_PAGE_SIZE = 10; + +export const FORBIDDEN_MESSAGE = + 'You do not have permission to perform this action or license level does not allow for this action'; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/index.ts index d48172bebee4c..2acbce2c88653 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/index.ts @@ -9,6 +9,7 @@ export * from './authentications'; export * from './common'; export * from './hosts'; export * from './unique_ips'; +export * from './risky_hosts'; import { HostsKpiAuthenticationsStrategyResponse } from './authentications'; import { HostsKpiHostsStrategyResponse } from './hosts'; @@ -20,6 +21,7 @@ export enum HostsKpiQueries { kpiHosts = 'hostsKpiHosts', kpiHostsEntities = 'hostsKpiHostsEntities', kpiUniqueIps = 'hostsKpiUniqueIps', + kpiRiskyHosts = 'hostsKpiRiskyHosts', kpiUniqueIpsEntities = 'hostsKpiUniqueIpsEntities', } diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/risky_hosts/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/risky_hosts/index.ts new file mode 100644 index 0000000000000..5216052b1a6b1 --- /dev/null +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/kpi/risky_hosts/index.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { IEsSearchResponse } from '../../../../../../../../../src/plugins/data/common'; +import type { Inspect, Maybe } from '../../../../common'; +import type { RequestBasicOptions } from '../../..'; + +export type HostsKpiRiskyHostsRequestOptions = RequestBasicOptions; + +export interface HostsKpiRiskyHostsStrategyResponse extends IEsSearchResponse { + inspect?: Maybe; + riskyHosts: { + [key in HostRiskSeverity]: number; + }; +} + +export enum HostRiskSeverity { + unknown = 'Unknown', + low = 'Low', + moderate = 'Moderate', + high = 'High', + critical = 'Critical', +} diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/risk_score/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/risk_score/index.ts index f931694a4e229..23cda0b68f038 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/risk_score/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/risk_score/index.ts @@ -27,6 +27,14 @@ export interface HostsRiskScore { host: { name: string; }; - risk_score: number; risk: string; + risk_stats: { + rule_risks: RuleRisk[]; + risk_score: number; + }; +} + +export interface RuleRisk { + rule_name: string; + rule_risk: string; } diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts index 00cbdb941c11b..3362a004423d3 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts @@ -84,6 +84,10 @@ import { UserRulesRequestOptions, UserRulesStrategyResponse, } from './ueba'; +import { + HostsKpiRiskyHostsRequestOptions, + HostsKpiRiskyHostsStrategyResponse, +} from './hosts/kpi/risky_hosts'; export * from './hosts'; export * from './matrix_histogram'; @@ -146,6 +150,8 @@ export type StrategyResponseType = T extends HostsQ ? HostsKpiAuthenticationsStrategyResponse : T extends HostsKpiQueries.kpiHosts ? HostsKpiHostsStrategyResponse + : T extends HostsKpiQueries.kpiRiskyHosts + ? HostsKpiRiskyHostsStrategyResponse : T extends HostsKpiQueries.kpiUniqueIps ? HostsKpiUniqueIpsStrategyResponse : T extends NetworkQueries.details @@ -200,6 +206,8 @@ export type StrategyRequestType = T extends HostsQu ? HostsKpiHostsRequestOptions : T extends HostsKpiQueries.kpiUniqueIps ? HostsKpiUniqueIpsRequestOptions + : T extends HostsKpiQueries.kpiRiskyHosts + ? HostsKpiRiskyHostsRequestOptions : T extends NetworkQueries.details ? NetworkDetailsRequestOptions : T extends NetworkQueries.dns diff --git a/x-pack/plugins/security_solution/cypress/integration/hosts/risky_hosts_kpi.spec.ts b/x-pack/plugins/security_solution/cypress/integration/hosts/risky_hosts_kpi.spec.ts new file mode 100644 index 0000000000000..4f282e1e69d5c --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/integration/hosts/risky_hosts_kpi.spec.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { loginAndWaitForPage } from '../../tasks/login'; + +import { HOSTS_URL } from '../../urls/navigation'; +import { cleanKibana } from '../../tasks/common'; + +describe('RiskyHosts KPI', () => { + before(() => { + cleanKibana(); + }); + + it('it renders', () => { + loginAndWaitForPage(HOSTS_URL); + + cy.get('[data-test-subj="riskyHostsTotal"]').should('have.text', '0 Risky Hosts'); + cy.get('[data-test-subj="riskyHostsCriticalQuantity"]').should('have.text', '0 hosts'); + cy.get('[data-test-subj="riskyHostsHighQuantity"]').should('have.text', '0 hosts'); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/screens/inspect.ts b/x-pack/plugins/security_solution/cypress/screens/inspect.ts index ee9a3ad8dbbc6..f2b332b1772b2 100644 --- a/x-pack/plugins/security_solution/cypress/screens/inspect.ts +++ b/x-pack/plugins/security_solution/cypress/screens/inspect.ts @@ -20,10 +20,6 @@ export const INSPECT_HOSTS_BUTTONS_IN_SECURITY: InspectButtonMetadata[] = [ id: '[data-test-subj="stat-hosts"]', title: 'Hosts Stat', }, - { - id: '[data-test-subj="stat-authentication"]', - title: 'User Authentications Stat', - }, { id: '[data-test-subj="stat-uniqueIps"]', title: 'Unique IPs Stat', diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/host_risk_summary.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/host_risk_summary.test.tsx index 21b86fc1740b7..9d60fbc496d8d 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/host_risk_summary.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/host_risk_summary.test.tsx @@ -23,8 +23,11 @@ describe('HostRiskSummary', () => { host: { name: 'test-host-name', }, - risk_score: 9999, risk: riskKeyword, + risk_stats: { + risk_score: 9999, + rule_risks: [], + }, }, ], }; @@ -63,8 +66,11 @@ describe('HostRiskSummary', () => { host: { name: 'test-host-name', }, - risk_score: 9999, risk: 'test-risk', + risk_stats: { + risk_score: 9999, + rule_risks: [], + }, }, ], }; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/host_risk_summary.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/host_risk_summary.tsx index dd7d10014022f..8b15ed4b250b8 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/host_risk_summary.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/host_risk_summary.tsx @@ -10,7 +10,7 @@ import { EuiLoadingSpinner, EuiPanel, EuiSpacer, EuiLink, EuiText } from '@elast import { FormattedMessage } from '@kbn/i18n-react'; import * as i18n from './translations'; import { RISKY_HOSTS_DOC_LINK } from '../../../../overview/components/overview_risky_host_links/risky_hosts_disabled_module'; -import { HostRisk } from '../../../../overview/containers/overview_risky_host_links/use_hosts_risk_score'; +import type { HostRisk } from '../../../containers/hosts_risk/use_hosts_risk_score'; import { EnrichedDataRow, ThreatSummaryPanelHeader } from './threat_summary_view'; const HostRiskSummaryComponent: React.FC<{ diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_summary_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_summary_view.tsx index c4d7902e151b4..5382fb5a9bcc1 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_summary_view.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_summary_view.tsx @@ -28,7 +28,7 @@ import { BrowserFields, TimelineEventsDetailsItem, } from '../../../../../common/search_strategy'; -import { HostRisk } from '../../../../overview/containers/overview_risky_host_links/use_hosts_risk_score'; +import { HostRisk } from '../../../containers/hosts_risk/use_hosts_risk_score'; import { HostRiskSummary } from './host_risk_summary'; import { EnrichmentSummary } from './enrichment_summary'; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx index a8305a635f157..0fe48d5a998ea 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx @@ -39,7 +39,7 @@ import { EnrichmentRangePicker } from './cti_details/enrichment_range_picker'; import { Reason } from './reason'; import { InvestigationGuideView } from './investigation_guide_view'; -import { HostRisk } from '../../../overview/containers/overview_risky_host_links/use_hosts_risk_score'; +import { HostRisk } from '../../containers/hosts_risk/use_hosts_risk_score'; type EventViewTab = EuiTabbedContentTab; diff --git a/x-pack/plugins/security_solution/public/overview/containers/overview_risky_host_links/use_hosts_risk_score.ts b/x-pack/plugins/security_solution/public/common/containers/hosts_risk/use_hosts_risk_score.ts similarity index 88% rename from x-pack/plugins/security_solution/public/overview/containers/overview_risky_host_links/use_hosts_risk_score.ts rename to x-pack/plugins/security_solution/public/common/containers/hosts_risk/use_hosts_risk_score.ts index eb363f4f77067..41fcd29191da2 100644 --- a/x-pack/plugins/security_solution/public/overview/containers/overview_risky_host_links/use_hosts_risk_score.ts +++ b/x-pack/plugins/security_solution/public/common/containers/hosts_risk/use_hosts_risk_score.ts @@ -9,13 +9,13 @@ import { i18n } from '@kbn/i18n'; import { useCallback, useEffect, useState } from 'react'; import { useDispatch } from 'react-redux'; -import { useAppToasts } from '../../../common/hooks/use_app_toasts'; -import { useKibana } from '../../../common/lib/kibana'; -import { inputsActions } from '../../../common/store/actions'; -import { isIndexNotFoundError } from '../../../common/utils/exceptions'; +import { useAppToasts } from '../../hooks/use_app_toasts'; +import { useKibana } from '../../lib/kibana'; +import { inputsActions } from '../../store/actions'; +import { isIndexNotFoundError } from '../../utils/exceptions'; import { HostsRiskScore } from '../../../../common/search_strategy'; import { useHostsRiskScoreComplete } from './use_hosts_risk_score_complete'; -import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; +import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features'; import { getHostRiskIndex } from '../../../helpers'; export const QUERY_ID = 'host_risk_score'; @@ -24,11 +24,11 @@ const noop = () => {}; const isRecord = (item: unknown): item is Record => typeof item === 'object' && !!item; -const isHostsRiskScoreHit = (item: unknown): item is HostsRiskScore => +const isHostsRiskScoreHit = (item: Partial): item is HostsRiskScore => isRecord(item) && isRecord(item.host) && typeof item.host.name === 'string' && - typeof item.risk_score === 'number' && + typeof item.risk_stats?.risk_score === 'number' && typeof item.risk === 'string'; export interface HostRisk { diff --git a/x-pack/plugins/security_solution/public/overview/containers/overview_risky_host_links/use_hosts_risk_score_complete.ts b/x-pack/plugins/security_solution/public/common/containers/hosts_risk/use_hosts_risk_score_complete.ts similarity index 87% rename from x-pack/plugins/security_solution/public/overview/containers/overview_risky_host_links/use_hosts_risk_score_complete.ts rename to x-pack/plugins/security_solution/public/common/containers/hosts_risk/use_hosts_risk_score_complete.ts index 959fb94c5bbd7..934cb88ee0d86 100644 --- a/x-pack/plugins/security_solution/public/overview/containers/overview_risky_host_links/use_hosts_risk_score_complete.ts +++ b/x-pack/plugins/security_solution/public/common/containers/hosts_risk/use_hosts_risk_score_complete.ts @@ -4,14 +4,15 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { Observable } from 'rxjs'; +import type { Observable } from 'rxjs'; import { filter } from 'rxjs/operators'; import { useObservable, withOptionalSignal } from '@kbn/securitysolution-hook-utils'; -import type { DataPublicPluginStart } from '../../../../../../../src/plugins/data/public'; - -import { isCompleteResponse, isErrorResponse } from '../../../../../../../src/plugins/data/common'; - +import { + DataPublicPluginStart, + isCompleteResponse, + isErrorResponse, +} from '../../../../../../../src/plugins/data/public'; import { HostsQueries, HostsRiskScoreRequestOptions, diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_error_toast.test.ts b/x-pack/plugins/security_solution/public/common/hooks/use_error_toast.test.ts new file mode 100644 index 0000000000000..993326d906a18 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/hooks/use_error_toast.test.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { renderHook } from '@testing-library/react-hooks'; +import { useErrorToast } from './use_error_toast'; + +jest.mock('./use_app_toasts'); + +import { useAppToasts } from './use_app_toasts'; + +describe('useErrorToast', () => { + let addErrorMock: jest.Mock; + + beforeEach(() => { + addErrorMock = jest.fn(); + (useAppToasts as jest.Mock).mockImplementation(() => ({ + addError: addErrorMock, + })); + }); + + it('calls useAppToasts error when an error param is provided', () => { + const title = 'testErrorTitle'; + const error = new Error(); + renderHook(() => useErrorToast(title, error)); + + expect(addErrorMock).toHaveBeenCalledWith(error, { title }); + }); + + it("doesn't call useAppToasts error when an error param is undefined", () => { + const title = 'testErrorTitle'; + const error = undefined; + renderHook(() => useErrorToast(title, error)); + + expect(addErrorMock).not.toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_error_toast.ts b/x-pack/plugins/security_solution/public/common/hooks/use_error_toast.ts new file mode 100644 index 0000000000000..f459827f6cc9a --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/hooks/use_error_toast.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useEffect } from 'react'; +import { useAppToasts } from './use_app_toasts'; + +/** + * Display App error toast when error is defined. + */ +export const useErrorToast = (title: string, error: unknown) => { + const { addError } = useAppToasts(); + + useEffect(() => { + if (error) { + addError(error, { title }); + } + }, [error, title, addError]); +}; diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_inspect_query.test.tsx b/x-pack/plugins/security_solution/public/common/hooks/use_inspect_query.test.tsx new file mode 100644 index 0000000000000..1bf2de3242ac7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/hooks/use_inspect_query.test.tsx @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { renderHook } from '@testing-library/react-hooks'; +import { useInspectQuery } from './use_inspect_query'; + +import { useGlobalTime } from '../containers/use_global_time'; + +jest.mock('../containers/use_global_time'); + +const QUERY_ID = 'tes_query_id'; + +const RESPONSE = { + inspect: { dsl: [], response: [] }, + isPartial: false, + isRunning: false, + total: 0, + loaded: 0, + rawResponse: { + took: 0, + timed_out: false, + _shards: { + total: 0, + successful: 0, + failed: 0, + skipped: 0, + }, + results: { + hits: { + total: 0, + }, + }, + hits: { + total: 0, + max_score: 0, + hits: [], + }, + }, + totalCount: 0, + enrichments: [], +}; + +describe('useInspectQuery', () => { + let deleteQuery: jest.Mock; + let setQuery: jest.Mock; + + beforeEach(() => { + deleteQuery = jest.fn(); + setQuery = jest.fn(); + (useGlobalTime as jest.Mock).mockImplementation(() => ({ + deleteQuery, + setQuery, + isInitializing: false, + })); + }); + + it('it calls setQuery', () => { + renderHook(() => useInspectQuery(QUERY_ID, false, RESPONSE)); + + expect(setQuery).toHaveBeenCalledTimes(1); + expect(setQuery.mock.calls[0][0].id).toBe(QUERY_ID); + }); + + it("doesn't call setQuery when response is undefined", () => { + renderHook(() => useInspectQuery(QUERY_ID, false, undefined)); + + expect(setQuery).not.toHaveBeenCalled(); + }); + + it("doesn't call setQuery when loading", () => { + renderHook(() => useInspectQuery(QUERY_ID, true)); + + expect(setQuery).not.toHaveBeenCalled(); + }); + + it('calls deleteQuery when unmouting', () => { + const result = renderHook(() => useInspectQuery(QUERY_ID, false, RESPONSE)); + result.unmount(); + + expect(deleteQuery).toHaveBeenCalledWith({ id: QUERY_ID }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_inspect_query.ts b/x-pack/plugins/security_solution/public/common/hooks/use_inspect_query.ts new file mode 100644 index 0000000000000..4c0cb1c4fcdca --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/hooks/use_inspect_query.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { noop } from 'lodash'; +import { useEffect } from 'react'; +import type { FactoryQueryTypes, StrategyResponseType } from '../../../common/search_strategy'; +import { getInspectResponse } from '../../helpers'; +import { useGlobalTime } from '../containers/use_global_time'; +import type { Refetch, RefetchKql } from '../store/inputs/model'; + +/** + * Add and remove query response from global input store. + */ +export const useInspectQuery = ( + id: string, + loading: boolean, + response?: StrategyResponseType, + refetch: Refetch | RefetchKql = noop +) => { + const { deleteQuery, setQuery, isInitializing } = useGlobalTime(); + + useEffect(() => { + if (!loading && !isInitializing && response?.inspect) { + setQuery({ + id, + inspect: getInspectResponse(response, { + dsl: [], + response: [], + }), + loading, + refetch, + }); + } + + return () => { + if (deleteQuery) { + deleteQuery({ id }); + } + }; + }, [deleteQuery, setQuery, loading, response, isInitializing, id, refetch]); +}; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/severity_badge/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/severity_badge/index.tsx index 1e1a5f1c35bc4..835ab73282f1a 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/severity_badge/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/severity_badge/index.tsx @@ -7,22 +7,25 @@ import { upperFirst } from 'lodash/fp'; import React from 'react'; - import { euiLightVars } from '@kbn/ui-shared-deps-src/theme'; +import type { Severity } from '@kbn/securitysolution-io-ts-alerting-types'; import { HealthTruncateText } from '../../../../common/components/health_truncate_text'; + +const { euiColorVis0, euiColorVis5, euiColorVis7, euiColorVis9 } = euiLightVars; +const severityToColorMap: Record = { + low: euiColorVis0, + medium: euiColorVis5, + high: euiColorVis7, + critical: euiColorVis9, +}; + interface Props { - value: string; + value: Severity; } const SeverityBadgeComponent: React.FC = ({ value }) => { const displayValue = upperFirst(value); - const color = 'low' - ? euiLightVars.euiColorVis0 - : value === 'medium' - ? euiLightVars.euiColorVis5 - : value === 'high' - ? euiLightVars.euiColorVis7 - : euiLightVars.euiColorVis9; + const color = severityToColorMap[value] ?? 'subdued'; return ( diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.ts index 5f9ad7fdd2bf5..17207c2651dd6 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.ts @@ -14,7 +14,6 @@ import { HttpStart } from '../../../../../../../../src/core/public'; import { DETECTION_ENGINE_RULES_URL, DETECTION_ENGINE_PREPACKAGED_URL, - DETECTION_ENGINE_RULES_STATUS_URL, DETECTION_ENGINE_PREPACKAGED_RULES_STATUS_URL, DETECTION_ENGINE_TAGS_URL, DETECTION_ENGINE_RULES_BULK_ACTION, @@ -246,6 +245,9 @@ export const duplicateRules = async ({ rules }: DuplicateRulesProps): Promise => { - const res = await KibanaServices.get().http.fetch( - DETECTION_ENGINE_RULES_STATUS_URL, - { - method: 'POST', - body: JSON.stringify({ ids }), - signal, - } - ); - return res; -}; - /** * Fetch all unique Tags used by Rules * diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts index fea28eba61d70..53adc93e39580 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts @@ -19,6 +19,7 @@ import { threats, type, severity_mapping, + severity, } from '@kbn/securitysolution-io-ts-alerting-types'; import { SortOrder, @@ -101,7 +102,7 @@ export const RuleSchema = t.intersection([ risk_score: t.number, risk_score_mapping, rule_id: t.string, - severity: t.string, + severity, severity_mapping, tags: t.array(t.string), type, @@ -125,6 +126,9 @@ export const RuleSchema = t.intersection([ last_failure_message: t.string, last_success_message: t.string, last_success_at: t.string, + last_gap: t.string, + bulk_create_time_durations: t.array(t.string), + search_after_time_durations: t.array(t.string), meta: MetaRule, machine_learning_job_id: t.array(t.string), output_index: t.string, diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_status.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_status.test.tsx index a5809ea776322..73b05f94153e6 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_status.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_status.test.tsx @@ -6,70 +6,14 @@ */ import { renderHook, act, cleanup } from '@testing-library/react-hooks'; -import { - useRuleStatus, - ReturnRuleStatus, - useRulesStatuses, - ReturnRulesStatuses, -} from './use_rule_status'; +import { useRuleStatus, ReturnRuleStatus } from './use_rule_status'; import * as api from './api'; -import { Rule } from './types'; import { useAppToastsMock } from '../../../../common/hooks/use_app_toasts.mock'; import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; jest.mock('./api'); jest.mock('../../../../common/hooks/use_app_toasts'); -const testRule: Rule = { - actions: [ - { - group: 'fake group', - id: 'fake id', - action_type_id: 'fake action_type_id', - params: { - someKey: 'someVal', - }, - }, - ], - author: [], - created_at: 'mm/dd/yyyyTHH:MM:sssz', - created_by: 'mockUser', - description: 'some desc', - enabled: true, - false_positives: [], - filters: [], - from: 'now-360s', - id: '12345678987654321', - immutable: false, - index: [ - 'apm-*-transaction*', - 'traces-apm*', - 'auditbeat-*', - 'endgame-*', - 'filebeat-*', - 'packetbeat-*', - 'winlogbeat-*', - ], - interval: '5m', - language: 'kuery', - name: 'Test rule', - max_signals: 100, - query: "user.email: 'root@elastic.co'", - references: [], - risk_score: 75, - risk_score_mapping: [], - rule_id: 'bbd3106e-b4b5-4d7c-a1a2-47531d6a2baf', - severity: 'high', - severity_mapping: [], - tags: ['APM'], - threat: [], - throttle: null, - to: 'now', - type: 'query', - updated_at: 'mm/dd/yyyyTHH:MM:sssz', - updated_by: 'mockUser', -}; - describe('useRuleStatus', () => { (useAppToasts as jest.Mock).mockReturnValue(useAppToastsMock.create()); @@ -138,50 +82,4 @@ describe('useRuleStatus', () => { }); }); }); - - describe('useRulesStatuses', () => { - test('init rules statuses', async () => { - const payload = [testRule]; - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useRulesStatuses(payload) - ); - await waitForNextUpdate(); - expect(result.current).toEqual({ loading: false, rulesStatuses: [] }); - }); - }); - - test('fetch rules statuses', async () => { - const payload = [testRule]; - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useRulesStatuses(payload) - ); - await waitForNextUpdate(); - await waitForNextUpdate(); - expect(result.current).toEqual({ - loading: false, - rulesStatuses: [ - { - current_status: { - alert_id: 'alertId', - bulk_create_time_durations: ['2235.01'], - gap: null, - last_failure_at: null, - last_failure_message: null, - last_look_back_date: '2020-03-19T00:32:07.996Z', // NOTE: This is no longer used on the UI, but left here in case users are using it within the API - last_success_at: 'mm/dd/yyyyTHH:MM:sssz', - last_success_message: 'it is a success', - search_after_time_durations: ['616.97'], - status: 'succeeded', - status_date: 'mm/dd/yyyyTHH:MM:sssz', - }, - failures: [], - id: '12345678987654321', - }, - ], - }); - }); - }); - }); }); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_status.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_status.tsx index d0c75e08ae01b..815ed261c490f 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_status.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_status.tsx @@ -9,17 +9,12 @@ import { useEffect, useRef, useState } from 'react'; import { isNotFoundError } from '@kbn/securitysolution-t-grid'; import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; -import { EnhancedRuleStatus } from '../../../pages/detection_engine/rules/all/columns'; -import { getRuleStatusById, getRulesStatusByIds } from './api'; +import { getRuleStatusById } from './api'; import * as i18n from './translations'; -import { RuleStatus, Rules } from './types'; +import { RuleStatus } from './types'; type Func = (ruleId: string) => void; export type ReturnRuleStatus = [boolean, RuleStatus | null, Func | null]; -export interface ReturnRulesStatuses { - loading: boolean; - rulesStatuses: EnhancedRuleStatus[]; -} /** * Hook for using to get a Rule from the Detection Engine API @@ -70,58 +65,3 @@ export const useRuleStatus = (id: string | undefined | null): ReturnRuleStatus = return [loading, ruleStatus, fetchRuleStatus.current]; }; - -/** - * Hook for using to get all the statuses for all given rule ids - * - * @param ids desired Rule ID's (not rule_id) - * - */ -export const useRulesStatuses = (rules: Rules): ReturnRulesStatuses => { - const [rulesStatuses, setRuleStatuses] = useState([]); - const [loading, setLoading] = useState(false); - const { addError } = useAppToasts(); - - useEffect(() => { - let isSubscribed = true; - const abortCtrl = new AbortController(); - - const fetchData = async (ids: string[]) => { - try { - setLoading(true); - const ruleStatusesResponse = await getRulesStatusByIds({ - ids, - signal: abortCtrl.signal, - }); - - if (isSubscribed) { - setRuleStatuses( - rules.map((rule) => ({ - id: rule.id, - ...ruleStatusesResponse[rule.id], - })) - ); - } - } catch (error) { - if (isSubscribed) { - setRuleStatuses([]); - addError(error, { title: i18n.RULE_AND_TIMELINE_FETCH_FAILURE }); - } - } - if (isSubscribed) { - setLoading(false); - } - }; - - if (rules.length > 0) { - fetchData(rules.map((r) => r.id)); - } - - return () => { - isSubscribed = false; - abortCtrl.abort(); - }; - }, [rules, addError]); - - return { loading, rulesStatuses }; -}; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx index d586f0e4856d4..5ca008eb0daa0 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx @@ -18,7 +18,7 @@ import { sum } from 'lodash'; import React, { Dispatch } from 'react'; import { isMlRule } from '../../../../../../common/machine_learning/helpers'; -import { Rule, RuleStatus } from '../../../../containers/detection_engine/rules'; +import { Rule } from '../../../../containers/detection_engine/rules'; import { getEmptyTagValue } from '../../../../../common/components/empty_value'; import { FormattedRelativePreferenceDate } from '../../../../../common/components/formatted_date'; import { getRuleDetailsUrl } from '../../../../../common/components/link_to/redirect_to_detection_engine'; @@ -54,10 +54,7 @@ type HasReadActionsPrivileges = [x: string]: boolean; }>; -export type TableItem = Rule & Partial; -export type TableColumn = EuiBasicTableColumn | EuiTableActionsColumnType; - -const extractRuleFromRow = ({ current_status: _, failures, ...rule }: TableItem): Rule => rule; +export type TableColumn = EuiBasicTableColumn | EuiTableActionsColumnType; export const getActions = ( dispatch: React.Dispatch, @@ -78,9 +75,8 @@ export const getActions = ( i18n.EDIT_RULE_SETTINGS ), icon: 'controlsHorizontal', - onClick: (rowItem: TableItem) => editRuleAction(rowItem.id, navigateToApp), - enabled: (rowItem: TableItem) => - canEditRuleWithActions(extractRuleFromRow(rowItem), actionsPrivileges), + onClick: (rowItem: Rule) => editRuleAction(rowItem.id, navigateToApp), + enabled: (rowItem: Rule) => canEditRuleWithActions(rowItem, actionsPrivileges), }, { 'data-test-subj': 'duplicateRuleAction', @@ -93,10 +89,10 @@ export const getActions = ( ) : ( i18n.DUPLICATE_RULE ), - enabled: (rowItem: TableItem) => canEditRuleWithActions(rowItem, actionsPrivileges), - onClick: async (rowItem: TableItem) => { + enabled: (rowItem: Rule) => canEditRuleWithActions(rowItem, actionsPrivileges), + onClick: async (rowItem: Rule) => { const createdRules = await duplicateRulesAction( - [extractRuleFromRow(rowItem)], + [rowItem], [rowItem.id], dispatch, dispatchToaster @@ -127,10 +123,6 @@ export const getActions = ( }, ]; -export type EnhancedRuleStatus = RuleStatus & { - id: string; -}; - interface GetColumnsProps { dispatch: React.Dispatch; formatUrl: FormatUrl; @@ -154,7 +146,7 @@ const getColumnEnabled = ({ }: GetColumnsProps): TableColumn => ({ field: 'enabled', name: i18n.COLUMN_ACTIVATE, - render: (_, rule: TableItem) => ( + render: (_, rule: Rule) => ( ({ field: 'name', name: i18n.COLUMN_RULE, - render: (value: Rule['name'], item: TableItem) => ( + render: (value: Rule['name'], item: Rule) => ( , + } as EuiTableActionsColumnType, ] : []; @@ -348,14 +340,14 @@ export const getMonitoringColumns = (columnsProps: GetColumnsProps): TableColumn { ...getColumnRuleName(columnsProps), width: '28%' }, getColumnTags(), { - field: 'current_status.bulk_create_time_durations', + field: 'bulk_create_time_durations', name: ( ), - render: (value: RuleStatus['current_status']['bulk_create_time_durations'] | undefined) => ( + render: (value: Rule['bulk_create_time_durations'] | undefined) => ( {value?.length ? sum(value.map(Number)).toFixed() : getEmptyTagValue()} @@ -364,14 +356,14 @@ export const getMonitoringColumns = (columnsProps: GetColumnsProps): TableColumn truncateText: true, }, { - field: 'current_status.search_after_time_durations', + field: 'search_after_time_durations', name: ( ), - render: (value: RuleStatus['current_status']['search_after_time_durations'] | undefined) => ( + render: (value: Rule['search_after_time_durations'] | undefined) => ( {value?.length ? sum(value.map(Number)).toFixed() : getEmptyTagValue()} @@ -380,7 +372,7 @@ export const getMonitoringColumns = (columnsProps: GetColumnsProps): TableColumn truncateText: true, }, { - field: 'current_status.gap', + field: 'last_gap', name: ( ), - render: (value: RuleStatus['current_status']['gap'] | undefined) => ( + render: (value: Rule['last_gap'] | undefined) => ( {value ?? getEmptyTagValue()} @@ -418,16 +410,14 @@ export const getMonitoringColumns = (columnsProps: GetColumnsProps): TableColumn { field: 'current_status.status', name: i18n.COLUMN_LAST_RESPONSE, - render: (value: RuleStatus['current_status']['status'] | undefined) => ( - - ), + render: (value: Rule['status'] | undefined) => , width: '12%', truncateText: true, }, { field: 'current_status.status_date', name: i18n.COLUMN_LAST_COMPLETE_RUN, - render: (value: RuleStatus['current_status']['status_date'] | undefined) => { + render: (value: Rule['status_date'] | undefined) => { return value == null ? ( getEmptyTagValue() ) : ( diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.test.tsx index 487d0862cf467..c63aa17902740 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.test.tsx @@ -22,11 +22,7 @@ import { DEFAULT_RULES_TABLE_REFRESH_SETTING, } from '../../../../../../common/constants'; -import { - useRulesTable, - useRulesStatuses, - RulesTableState, -} from '../../../../containers/detection_engine/rules'; +import { useRulesTable, RulesTableState } from '../../../../containers/detection_engine/rules'; import { AllRules } from './index'; @@ -147,29 +143,6 @@ describe('AllRules', () => { }; }); - (useRulesStatuses as jest.Mock).mockReturnValue({ - loading: false, - rulesStatuses: [ - { - current_status: { - alert_id: 'alertId', - bulk_create_time_durations: ['2235.01'], - gap: null, - last_failure_at: null, - last_failure_message: null, - last_look_back_date: new Date().toISOString(), // NOTE: This is no longer used on the UI, but left here in case users are using it within the API - last_success_at: new Date().toISOString(), - last_success_message: 'it is a success', - search_after_time_durations: ['616.97'], - status: 'succeeded', - status_date: new Date().toISOString(), - }, - failures: [], - id: '12345678987654321', - }, - ], - }); - useKibanaMock().services.application.capabilities = { navLinks: {}, management: {}, diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx index 54c2500d03b03..5668a4d489c53 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx @@ -21,10 +21,10 @@ import { History } from 'history'; import { useRulesTable, - useRulesStatuses, CreatePreBuiltRules, FilterOptions, RulesSortingFields, + Rule, } from '../../../../containers/detection_engine/rules'; import { FormatUrl } from '../../../../../common/components/link_to'; @@ -37,7 +37,7 @@ import { getPrePackagedRuleStatus } from '../helpers'; import * as i18n from '../translations'; import { EuiBasicTableOnChange } from '../types'; import { getBatchItems } from './batch_actions'; -import { getRulesColumns, getMonitoringColumns, TableItem } from './columns'; +import { getRulesColumns, getMonitoringColumns } from './columns'; import { showRulesTable } from './helpers'; import { RulesTableFilters } from './rules_table_filters/rules_table_filters'; import { useMlCapabilities } from '../../../../../common/components/ml/hooks/use_ml_capabilities'; @@ -144,7 +144,6 @@ export const RulesTables = React.memo( reFetchRules, } = rulesTable; - const { loading: isLoadingRulesStatuses, rulesStatuses } = useRulesStatuses(rules); const [, dispatchToaster] = useStateToaster(); const mlCapabilities = useMlCapabilities(); const { navigateToApp } = useKibana().services.application; @@ -305,10 +304,10 @@ export const RulesTables = React.memo( }, [reFetchRules, setRefreshRulesData]); useEffect(() => { - if (initLoading && !loading && !isLoadingRules && !isLoadingRulesStatuses) { + if (initLoading && !loading && !isLoadingRules) { setInitLoading(false); } - }, [initLoading, loading, isLoadingRules, isLoadingRulesStatuses]); + }, [initLoading, loading, isLoadingRules]); const handleCreatePrePackagedRules = useCallback(async () => { if (createPrePackagedRules != null) { @@ -329,8 +328,8 @@ export const RulesTables = React.memo( const euiBasicTableSelectionProps = useMemo( () => ({ - selectable: (item: TableItem) => !loadingRuleIds.includes(item.id), - onSelectionChange: (selected: TableItem[]) => { + selectable: (item: Rule) => !loadingRuleIds.includes(item.id), + onSelectionChange: (selected: Rule[]) => { /** * EuiBasicTable doesn't provide declarative API to control selected rows. * This limitation requires us to synchronize selection state manually using setSelection(). @@ -444,17 +443,6 @@ export const RulesTables = React.memo( [initLoading, prePackagedRuleStatus, rulesCustomInstalled] ); - const items = useMemo(() => { - const rulesStatusesMap = new Map(rulesStatuses.map((item) => [item.id, item])); - - return rules.map((rule) => { - return { - ...rule, - ...rulesStatusesMap.get(rule.id), - }; - }); - }, [rulesStatuses, rules]); - const tableProps = selectedTab === AllRulesTabs.rules ? { @@ -484,7 +472,7 @@ export const RulesTables = React.memo( growLeftSplit={false} title={i18n.ALL_RULES} subtitle={timelines.getLastUpdated({ - showUpdating: loading || isLoadingRules || isLoadingRulesStatuses, + showUpdating: loading || isLoadingRules, updatedAt: lastUpdated, })} > @@ -553,7 +541,7 @@ export const RulesTables = React.memo( /> ( ); if (loading) { - return ( - - - - - - ); + return ; } return ( @@ -80,3 +74,11 @@ export const HostsKpiBaseComponent = React.memo( HostsKpiBaseComponent.displayName = 'HostsKpiBaseComponent'; export const HostsKpiBaseComponentManage = manageQuery(HostsKpiBaseComponent); + +export const HostsKpiBaseComponentLoader: React.FC = () => ( + + + + + +); diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.tsx index 26d908cba4d0d..1f854b1328aad 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.tsx @@ -6,51 +6,96 @@ */ import React from 'react'; -import { EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; +import { EuiFlexItem, EuiFlexGroup, EuiSpacer, EuiLink } from '@elastic/eui'; import { HostsKpiAuthentications } from './authentications'; import { HostsKpiHosts } from './hosts'; import { HostsKpiUniqueIps } from './unique_ips'; import { HostsKpiProps } from './types'; +import { RiskyHosts } from './risky_hosts'; +import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; +import { useRiskyHosts } from '../../containers/kpi_hosts/risky_hosts'; +import { CallOutSwitcher } from '../../../common/components/callouts'; +import { RISKY_HOSTS_DOC_LINK } from '../../../overview/components/overview_risky_host_links/risky_hosts_disabled_module'; +import * as i18n from './translations'; export const HostsKpiComponent = React.memo( - ({ filterQuery, from, indexNames, to, setQuery, skip, narrowDateRange }) => ( - - - - - - - - - - - - ) + ({ filterQuery, from, indexNames, to, setQuery, skip, narrowDateRange }) => { + const riskyHostsExperimentEnabled = useIsExperimentalFeatureEnabled('riskyHostsEnabled'); + const { + error, + response, + loading, + isModuleDisabled: isRiskHostsModuleDisabled, + } = useRiskyHosts({ + filterQuery, + from, + to, + skip: skip || !riskyHostsExperimentEnabled, + }); + + return ( + <> + {isRiskHostsModuleDisabled && ( + <> + + {i18n.LEARN_MORE}{' '} + + {i18n.HOST_RISK_DATA} + + + + ), + }} + /> + + + )} + + + + + + {riskyHostsExperimentEnabled && ( + + + + )} + + + + + + ); + } ); HostsKpiComponent.displayName = 'HostsKpiComponent'; diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/risky_hosts/index.test.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/risky_hosts/index.test.tsx new file mode 100644 index 0000000000000..f0e3dcfb69c6e --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/risky_hosts/index.test.tsx @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { render } from '@testing-library/react'; + +import { RiskyHosts } from './'; +import { TestProviders } from '../../../../common/mock'; +import { HostsKpiRiskyHostsStrategyResponse } from '../../../../../common/search_strategy'; + +jest.mock('../../../containers/kpi_hosts/risky_hosts'); + +describe('RiskyHosts', () => { + const defaultProps = { + error: undefined, + loading: false, + }; + + test('it renders', () => { + const { queryByText } = render( + + + + ); + + expect(queryByText('Risky Hosts')).toBeInTheDocument(); + }); + + test('it displays loader while API is loading', () => { + const { getByTestId } = render( + + + + ); + + expect(getByTestId('hostsKpiLoader')).toBeInTheDocument(); + }); + + test('it displays 0 risky hosts when initializing', () => { + const { getByTestId } = render( + + + + ); + + expect(getByTestId('riskyHostsTotal').textContent).toEqual('0 Risky Hosts'); + expect(getByTestId('riskyHostsCriticalQuantity').textContent).toEqual('0 hosts'); + expect(getByTestId('riskyHostsHighQuantity').textContent).toEqual('0 hosts'); + }); + + test('it displays risky hosts quantity returned by the API', () => { + const data: HostsKpiRiskyHostsStrategyResponse = { + rawResponse: {} as HostsKpiRiskyHostsStrategyResponse['rawResponse'], + riskyHosts: { + Critical: 1, + High: 1, + Unknown: 0, + Low: 0, + Moderate: 0, + }, + }; + const { getByTestId } = render( + + + + ); + + expect(getByTestId('riskyHostsTotal').textContent).toEqual('2 Risky Hosts'); + expect(getByTestId('riskyHostsCriticalQuantity').textContent).toEqual('1 host'); + expect(getByTestId('riskyHostsHighQuantity').textContent).toEqual('1 host'); + }); +}); diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/risky_hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/risky_hosts/index.tsx new file mode 100644 index 0000000000000..1030ea4c5e65b --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/risky_hosts/index.tsx @@ -0,0 +1,154 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + EuiFlexGroup, + EuiFlexItem, + EuiHealth, + EuiHorizontalRule, + EuiIcon, + EuiPanel, + EuiTitle, + EuiText, + transparentize, +} from '@elastic/eui'; +import React from 'react'; +import styled, { css } from 'styled-components'; +import { euiLightVars } from '@kbn/ui-shared-deps-src/theme'; +import { InspectButtonContainer, InspectButton } from '../../../../common/components/inspect'; + +import { HostsKpiBaseComponentLoader } from '../common'; +import * as i18n from './translations'; + +import { + HostRiskSeverity, + HostsKpiRiskyHostsStrategyResponse, +} from '../../../../../common/search_strategy/security_solution/hosts/kpi/risky_hosts'; + +import { useInspectQuery } from '../../../../common/hooks/use_inspect_query'; +import { useErrorToast } from '../../../../common/hooks/use_error_toast'; + +const QUERY_ID = 'hostsKpiRiskyHostsQuery'; + +const HOST_RISK_SEVERITY_COLOUR = { + Unknown: euiLightVars.euiColorMediumShade, + Low: euiLightVars.euiColorVis0, + Moderate: euiLightVars.euiColorWarning, + High: euiLightVars.euiColorVis9_behindText, + Critical: euiLightVars.euiColorDanger, +}; + +const HostRiskBadge = styled.div<{ $severity: HostRiskSeverity }>` + ${({ theme, $severity }) => css` + width: fit-content; + padding-right: ${theme.eui.paddingSizes.s}; + padding-left: ${theme.eui.paddingSizes.xs}; + + ${($severity === 'Critical' || $severity === 'High') && + css` + background-color: ${transparentize(theme.eui.euiColorDanger, 0.2)}; + border-radius: 999px; // pill shaped + `}; + `} +`; + +const HostRisk: React.FC<{ severity: HostRiskSeverity }> = ({ severity }) => ( + + {severity} + +); + +const HostCount = styled(EuiText)` + font-weight: bold; +`; +HostCount.displayName = 'HostCount'; + +const StatusTitle = styled(EuiTitle)` + text-transform: lowercase; +`; + +const RiskScoreContainer = styled(EuiFlexItem)` + min-width: 80px; +`; + +const RiskyHostsComponent: React.FC<{ + error: unknown; + loading: boolean; + data?: HostsKpiRiskyHostsStrategyResponse; +}> = ({ error, loading, data }) => { + useInspectQuery(QUERY_ID, loading, data); + useErrorToast(i18n.ERROR_TITLE, error); + + if (loading) { + return ; + } + + const criticalRiskCount = data?.riskyHosts.Critical ?? 0; + const hightlRiskCount = data?.riskyHosts.High ?? 0; + + const totalCount = criticalRiskCount + hightlRiskCount; + + return ( + + + + + +
{i18n.RISKY_HOSTS_TITLE}
+
+
+ + {data?.inspect && } + +
+ + + + + + + + +

{i18n.RISKY_HOSTS_DESCRIPTION(totalCount, totalCount.toLocaleString())}

+
+
+
+
+
+ + + + + + + + + + {i18n.HOSTS_COUNT(criticalRiskCount)} + + + + + + + + + + + + {i18n.HOSTS_COUNT(hightlRiskCount)} + + + + + +
+
+ ); +}; + +export const RiskyHosts = React.memo(RiskyHostsComponent); diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/risky_hosts/translations.ts b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/risky_hosts/translations.ts new file mode 100644 index 0000000000000..f97dc80fd9679 --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/risky_hosts/translations.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const HOSTS_COUNT = (quantity: number) => + i18n.translate('xpack.securitySolution.kpiHosts.riskyHosts.hostsCount', { + defaultMessage: '{quantity} {quantity, plural, =1 {host} other {hosts}}', + values: { + quantity, + }, + }); + +export const RISKY_HOSTS_DESCRIPTION = (quantity: number, formattedQuantity: string) => + i18n.translate('xpack.securitySolution.kpiHosts.riskyHosts.description', { + defaultMessage: '{formattedQuantity} Risky {quantity, plural, =1 {Host} other {Hosts}}', + values: { + formattedQuantity, + quantity, + }, + }); + +export const RISKY_HOSTS_TITLE = i18n.translate( + 'xpack.securitySolution.kpiHosts.riskyHosts.title', + { + defaultMessage: 'Risky Hosts', + } +); + +export const INSPECT_RISKY_HOSTS = i18n.translate( + 'xpack.securitySolution.kpiHosts.riskyHosts.inspectTitle', + { + defaultMessage: 'KPI Risky Hosts', + } +); + +export const ERROR_TITLE = i18n.translate( + 'xpack.securitySolution.kpiHosts.riskyHosts.errorMessage', + { + defaultMessage: 'Error Fetching Risky Hosts API', + } +); diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/translations.ts b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/translations.ts new file mode 100644 index 0000000000000..cc706ed6e68e8 --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/translations.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const LEARN_MORE = i18n.translate('xpack.securitySolution.kpiHost.learnMore', { + defaultMessage: 'Learn more about', +}); + +export const HOST_RISK_DATA = i18n.translate('xpack.securitySolution.kpiHost.hostRiskData', { + defaultMessage: 'host risk data', +}); + +export const ENABLE_HOST_RISK_TEXT = i18n.translate( + 'xpack.securitySolution.kpiHost.enableHostRiskText', + { + defaultMessage: 'Enable host risk module to see more data', + } +); diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/risky_hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/risky_hosts/index.tsx new file mode 100644 index 0000000000000..cd9f01e2fd67c --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/risky_hosts/index.tsx @@ -0,0 +1,98 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Observable } from 'rxjs'; +import { filter } from 'rxjs/operators'; +import { useEffect, useState } from 'react'; +import { useObservable, withOptionalSignal } from '@kbn/securitysolution-hook-utils'; +import { createFilter } from '../../../../common/containers/helpers'; + +import { HostsKpiQueries, RequestBasicOptions } from '../../../../../common/search_strategy'; + +import { + isCompleteResponse, + isErrorResponse, +} from '../../../../../../../../src/plugins/data/common'; +import type { DataPublicPluginStart } from '../../../../../../../../src/plugins/data/public'; +import type { HostsKpiRiskyHostsStrategyResponse } from '../../../../../common/search_strategy/security_solution/hosts/kpi/risky_hosts'; +import { useKibana } from '../../../../common/lib/kibana'; +import { isIndexNotFoundError } from '../../../../common/utils/exceptions'; +import { getHostRiskIndex } from '../../../../helpers'; + +export type RiskyHostsScoreRequestOptions = RequestBasicOptions; + +type GetHostsRiskScoreProps = RiskyHostsScoreRequestOptions & { + data: DataPublicPluginStart; + signal: AbortSignal; +}; + +export const getRiskyHosts = ({ + data, + defaultIndex, + timerange, + signal, + filterQuery, +}: GetHostsRiskScoreProps): Observable => + data.search.search( + { + defaultIndex, + factoryQueryType: HostsKpiQueries.kpiRiskyHosts, + filterQuery: createFilter(filterQuery), + timerange, + }, + { + strategy: 'securitySolutionSearchStrategy', + abortSignal: signal, + } + ); + +export const getRiskyHostsComplete = ( + props: GetHostsRiskScoreProps +): Observable => { + return getRiskyHosts(props).pipe( + filter((response) => { + return isErrorResponse(response) || isCompleteResponse(response); + }) + ); +}; + +const getRiskyHostsWithOptionalSignal = withOptionalSignal(getRiskyHostsComplete); + +const useRiskyHostsComplete = () => useObservable(getRiskyHostsWithOptionalSignal); + +interface UseRiskyHostProps { + filterQuery?: string; + from: string; + to: string; + skip: boolean; +} + +export const useRiskyHosts = ({ filterQuery, from, to, skip }: UseRiskyHostProps) => { + const { error, result: response, start, loading } = useRiskyHostsComplete(); + const { data, spaces } = useKibana().services; + const isModuleDisabled = error && isIndexNotFoundError(error); + const [spaceId, setSpaceId] = useState(); + + useEffect(() => { + if (spaces) { + spaces.getActiveSpace().then((space) => setSpaceId(space.id)); + } + }, [spaces]); + + useEffect(() => { + if (!skip && spaceId) { + start({ + data, + timerange: { to, from, interval: '' }, + filterQuery, + defaultIndex: [getHostRiskIndex(spaceId)], + }); + } + }, [data, spaceId, start, filterQuery, to, from, skip]); + + return { error, response, loading, isModuleDisabled }; +}; diff --git a/x-pack/plugins/security_solution/public/management/components/effected_policy_select/utils.ts b/x-pack/plugins/security_solution/public/management/components/effected_policy_select/utils.ts index f273ecaa96c56..ba4f3ae38fffa 100644 --- a/x-pack/plugins/security_solution/public/management/components/effected_policy_select/utils.ts +++ b/x-pack/plugins/security_solution/public/management/components/effected_policy_select/utils.ts @@ -47,7 +47,7 @@ export function getEffectedPolicySelectionByTags( tags: string[], policies: PolicyData[] ): EffectedPolicySelection { - if (tags.length === 0 || tags.find((tag) => tag === GLOBAL_POLICY_TAG)) { + if (tags.find((tag) => tag === GLOBAL_POLICY_TAG)) { return { isGlobal: true, selected: [], diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/mocks.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/mocks.ts index 3aacd1db2f3dd..12685896d9535 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/mocks.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/mocks.ts @@ -24,12 +24,13 @@ import { ENDPOINT_ACTION_LOG_ROUTE, HOST_METADATA_GET_ROUTE, HOST_METADATA_LIST_ROUTE, + METADATA_TRANSFORMS_STATUS_ROUTE, } from '../../../../common/endpoint/constants'; import { pendingActionsHttpMock, PendingActionsHttpMockInterface, } from '../../../common/lib/endpoint_pending_actions/mocks'; -import { METADATA_TRANSFORM_STATS_URL, TRANSFORM_STATES } from '../../../../common/constants'; +import { TRANSFORM_STATES } from '../../../../common/constants'; import { TransformStatsResponse } from './types'; import { fleetGetAgentPolicyListHttpMock, @@ -162,7 +163,7 @@ export const failedTransformStateMock = { export const transformsHttpMocks = httpHandlerMockFactory([ { id: 'metadataTransformStats', - path: METADATA_TRANSFORM_STATS_URL, + path: METADATA_TRANSFORMS_STATUS_ROUTE, method: 'get', handler: () => failedTransformStateMock, }, diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts index 2759a35841524..9d839be623920 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts @@ -10,7 +10,6 @@ import { CoreStart, HttpStart } from 'kibana/public'; import { Dispatch } from 'redux'; import semverGte from 'semver/functions/gte'; import { AGENT_POLICY_SAVED_OBJECT_TYPE } from '../../../../../../fleet/common'; -import { METADATA_TRANSFORM_STATS_URL } from '../../../../../common/constants'; import { BASE_POLICY_RESPONSE_ROUTE, ENDPOINT_ACTION_LOG_ROUTE, @@ -18,6 +17,7 @@ import { HOST_METADATA_LIST_ROUTE, metadataCurrentIndexPattern, METADATA_UNITED_INDEX, + METADATA_TRANSFORMS_STATUS_ROUTE, } from '../../../../../common/endpoint/constants'; import { ActivityLog, @@ -783,7 +783,7 @@ export async function handleLoadMetadataTransformStats(http: HttpStart, store: E try { const transformStatsResponse: TransformStatsResponse = await http.get( - METADATA_TRANSFORM_STATS_URL + METADATA_TRANSFORMS_STATUS_ROUTE ); dispatch({ diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/mock_endpoint_result_list.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/mock_endpoint_result_list.ts index 61eb5ad3c541d..1e8b55ef977e8 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/mock_endpoint_result_list.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/mock_endpoint_result_list.ts @@ -32,8 +32,8 @@ import { pendingActionsResponseMock } from '../../../../common/lib/endpoint_pend import { ACTION_STATUS_ROUTE, HOST_METADATA_LIST_ROUTE, + METADATA_TRANSFORMS_STATUS_ROUTE, } from '../../../../../common/endpoint/constants'; -import { METADATA_TRANSFORM_STATS_URL } from '../../../../../common/constants'; import { TransformStats, TransformStatsResponse } from '../types'; const generator = new EndpointDocGenerator('seed'); @@ -162,7 +162,7 @@ const endpointListApiPathHandlerMocks = ({ return pendingActionsResponseMock(); }, - [METADATA_TRANSFORM_STATS_URL]: (): TransformStatsResponse => ({ + [METADATA_TRANSFORMS_STATUS_ROUTE]: (): TransformStatsResponse => ({ count: transforms.length, transforms, }), diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/form.test.tsx b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/form.test.tsx index 10180d378457a..81e34e538468d 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/form.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/form.test.tsx @@ -233,5 +233,21 @@ describe('When on the host isolation exceptions entry form', () => { 'true' ); }); + + it('should show the policies selector when no policy is selected', () => { + existingException.tags = []; + + renderResult = render(existingException); + + expect(renderResult.queryByTestId('effectedPolicies-select-policiesSelectable')).toBeTruthy(); + }); + + it('should show the policies selector when no policy is selected and there are previous tags', () => { + existingException.tags = ['non-a-policy-tag']; + + renderResult = render(existingException); + + expect(renderResult.queryByTestId('effectedPolicies-select-policiesSelectable')).toBeTruthy(); + }); }); }); diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_risky_host_links/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_risky_host_links/index.test.tsx index 2476b4d07c3c7..95ebe9be77019 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_risky_host_links/index.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_risky_host_links/index.test.tsx @@ -22,11 +22,11 @@ import { } from '../../../common/mock'; import { useRiskyHostsDashboardButtonHref } from '../../containers/overview_risky_host_links/use_risky_hosts_dashboard_button_href'; import { useRiskyHostsDashboardLinks } from '../../containers/overview_risky_host_links/use_risky_hosts_dashboard_links'; -import { useHostsRiskScore } from '../../containers/overview_risky_host_links/use_hosts_risk_score'; +import { useHostsRiskScore } from '../../../common/containers/hosts_risk/use_hosts_risk_score'; jest.mock('../../../common/lib/kibana'); -jest.mock('../../containers/overview_risky_host_links/use_hosts_risk_score'); +jest.mock('../../../common/containers/hosts_risk/use_hosts_risk_score'); const useHostsRiskScoreMock = useHostsRiskScore as jest.Mock; jest.mock('../../containers/overview_risky_host_links/use_risky_hosts_dashboard_button_href'); diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_risky_host_links/index.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_risky_host_links/index.tsx index 57bcff45a6348..64829aab7776d 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_risky_host_links/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_risky_host_links/index.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { RiskyHostsEnabledModule } from './risky_hosts_enabled_module'; import { RiskyHostsDisabledModule } from './risky_hosts_disabled_module'; -import { useHostsRiskScore } from '../../containers/overview_risky_host_links/use_hosts_risk_score'; +import { useHostsRiskScore } from '../../../common/containers/hosts_risk/use_hosts_risk_score'; export interface RiskyHostLinksProps { timerange: { to: string; from: string }; } diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_risky_host_links/risky_hosts_enabled_module.test.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_risky_host_links/risky_hosts_enabled_module.test.tsx index 912945549be8c..364b608c6086d 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_risky_host_links/risky_hosts_enabled_module.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_risky_host_links/risky_hosts_enabled_module.test.tsx @@ -60,7 +60,10 @@ describe('RiskyHostsEnabledModule', () => { host: { name: 'a', }, - risk_score: 1, + risk_stats: { + risk_score: 1, + rule_risks: [], + }, risk: '', }, ], diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_risky_host_links/risky_hosts_enabled_module.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_risky_host_links/risky_hosts_enabled_module.tsx index 412c4a69ec2f5..875b7c206d793 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_risky_host_links/risky_hosts_enabled_module.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_risky_host_links/risky_hosts_enabled_module.tsx @@ -10,13 +10,13 @@ import { RiskyHostsPanelView } from './risky_hosts_panel_view'; import { LinkPanelListItem } from '../link_panel'; import { useRiskyHostsDashboardButtonHref } from '../../containers/overview_risky_host_links/use_risky_hosts_dashboard_button_href'; import { useRiskyHostsDashboardLinks } from '../../containers/overview_risky_host_links/use_risky_hosts_dashboard_links'; -import { HostRisk } from '../../containers/overview_risky_host_links/use_hosts_risk_score'; +import { HostRisk } from '../../../common/containers/hosts_risk/use_hosts_risk_score'; import { HostsRiskScore } from '../../../../common/search_strategy'; const getListItemsFromHits = (items: HostsRiskScore[]): LinkPanelListItem[] => { - return items.map(({ host, risk_score: count, risk: copy }) => ({ + return items.map(({ host, risk_stats: riskStats, risk: copy }) => ({ title: host.name, - count, + count: riskStats.risk_score, copy, path: '', })); diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_risky_host_links/risky_hosts_panel_view.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_risky_host_links/risky_hosts_panel_view.tsx index 87a5710ab0372..8a42cedc3be46 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_risky_host_links/risky_hosts_panel_view.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_risky_host_links/risky_hosts_panel_view.tsx @@ -14,7 +14,7 @@ import { LinkPanelViewProps } from '../link_panel/types'; import { Link } from '../link_panel/link'; import * as i18n from './translations'; import { VIEW_DASHBOARD } from '../overview_cti_links/translations'; -import { QUERY_ID as RiskyHostsQueryId } from '../../containers/overview_risky_host_links/use_hosts_risk_score'; +import { QUERY_ID as RiskyHostsQueryId } from '../../../common/containers/hosts_risk/use_hosts_risk_score'; import { NavigateToHost } from './navigate_to_host'; const columns: Array> = [ 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 33fd1918dad59..e8b6e9d7c9e56 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 @@ -28,9 +28,9 @@ import { } from '../components/overview_cti_links/mock'; import { useCtiDashboardLinks } from '../containers/overview_cti_links'; import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features'; -import { useHostsRiskScore } from '../containers/overview_risky_host_links/use_hosts_risk_score'; import { initialUserPrivilegesState } from '../../common/components/user_privileges/user_privileges_context'; import { EndpointPrivileges } from '../../../common/endpoint/types'; +import { useHostsRiskScore } from '../../common/containers/hosts_risk/use_hosts_risk_score'; jest.mock('../../common/lib/kibana'); jest.mock('../../common/containers/source'); @@ -84,7 +84,7 @@ jest.mock('../containers/overview_cti_links/use_is_threat_intel_module_enabled') const useIsThreatIntelModuleEnabledMock = useIsThreatIntelModuleEnabled as jest.Mock; useIsThreatIntelModuleEnabledMock.mockReturnValue(true); -jest.mock('../containers/overview_risky_host_links/use_hosts_risk_score'); +jest.mock('../../common/containers/hosts_risk/use_hosts_risk_score'); const useHostsRiskScoreMock = useHostsRiskScore as jest.Mock; useHostsRiskScoreMock.mockReturnValue({ loading: false, diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/expandable_event.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/expandable_event.tsx index 6a7f0602c3675..145edafe38318 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/expandable_event.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/expandable_event.tsx @@ -22,7 +22,7 @@ import { BrowserFields } from '../../../../common/containers/source'; import { EventDetails } from '../../../../common/components/event_details/event_details'; import { TimelineEventsDetailsItem } from '../../../../../common/search_strategy/timeline'; import * as i18n from './translations'; -import { HostRisk } from '../../../../overview/containers/overview_risky_host_links/use_hosts_risk_score'; +import { HostRisk } from '../../../../common/containers/hosts_risk/use_hosts_risk_score'; export type HandleOnEventClosed = () => void; interface Props { diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx index 224662f0fd6ab..1d68356fc0bb7 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx @@ -35,7 +35,7 @@ import { TimelineNonEcsData } from '../../../../../common/search_strategy'; import { Ecs } from '../../../../../common/ecs'; import { EventDetailsFooter } from './footer'; import { EntityType } from '../../../../../../timelines/common'; -import { useHostsRiskScore } from '../../../../overview/containers/overview_risky_host_links/use_hosts_risk_score'; +import { useHostsRiskScore } from '../../../../common/containers/hosts_risk/use_hosts_risk_score'; const StyledEuiFlyoutBody = styled(EuiFlyoutBody)` .euiFlyoutBody__overflow { diff --git a/x-pack/plugins/security_solution/server/endpoint/mocks.ts b/x-pack/plugins/security_solution/server/endpoint/mocks.ts index dce08e2522beb..95a1f92ea94cd 100644 --- a/x-pack/plugins/security_solution/server/endpoint/mocks.ts +++ b/x-pack/plugins/security_solution/server/endpoint/mocks.ts @@ -40,6 +40,8 @@ import { createCasesClientMock } from '../../../cases/server/client/mocks'; import { requestContextFactoryMock } from '../request_context_factory.mock'; import { EndpointMetadataService } from './services/metadata'; import { createFleetAuthzMock } from '../../../fleet/common'; +import { createMockClients } from '../lib/detection_engine/routes/__mocks__/request_context'; +import type { EndpointAuthz } from '../../common/endpoint/types/authz'; /** * Creates a mocked EndpointAppContext. @@ -181,9 +183,13 @@ export const createMockMetadataRequestContext = (): jest.Mocked, - savedObjectsClient: jest.Mocked + savedObjectsClient: jest.Mocked, + overrides: { endpointAuthz?: Partial } = {} ) { - const context = requestContextMock.create() as jest.Mocked; + const context = requestContextMock.create( + createMockClients(), + overrides + ) as jest.Mocked; context.core.elasticsearch.client = dataClient; context.core.savedObjects.client = savedObjectsClient; return context; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.ts index 51f88730eb6fd..9c8decf6b90c2 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.ts @@ -18,6 +18,7 @@ import { ISOLATE_HOST_ROUTE, UNISOLATE_HOST_ROUTE, failedFleetActionErrorCode, + FORBIDDEN_MESSAGE, } from '../../../../common/endpoint/constants'; import { AGENT_ACTIONS_INDEX } from '../../../../../fleet/common'; import { @@ -105,8 +106,7 @@ export const isolationRequestHandler = function ( if ((!canIsolateHost && isolate) || (!canUnIsolateHost && !isolate)) { return res.forbidden({ body: { - message: - 'You do not have permission to perform this action or license level does not allow for this action', + message: FORBIDDEN_MESSAGE, }, }); } diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts index 06e63c6b7ec59..b79b6985f68ea 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts @@ -41,6 +41,8 @@ import { GetMetadataListRequestQuery } from '../../../../common/endpoint/schema/ import { ENDPOINT_DEFAULT_PAGE, ENDPOINT_DEFAULT_PAGE_SIZE, + FORBIDDEN_MESSAGE, + METADATA_TRANSFORMS_PATTERN, } from '../../../../common/endpoint/constants'; import { EndpointFleetServicesInterface } from '../../services/endpoint_fleet_services'; @@ -185,6 +187,34 @@ export const getMetadataRequestHandler = function ( }; }; +export function getMetadataTransformStatsHandler( + logger: Logger +): RequestHandler { + return async (context, _, response) => { + const { canAccessEndpointManagement } = context.securitySolution.endpointAuthz; + if (!canAccessEndpointManagement) { + return response.forbidden({ + body: { + message: FORBIDDEN_MESSAGE, + }, + }); + } + + const esClient = context.core.elasticsearch.client.asInternalUser; + try { + const transformStats = await esClient.transform.getTransformStats({ + transform_id: METADATA_TRANSFORMS_PATTERN, + allow_no_match: true, + }); + return response.ok({ + body: transformStats.body, + }); + } catch (error) { + return errorHandler(logger, response, error); + } + }; +} + export async function mapToHostResultList( // eslint-disable-next-line @typescript-eslint/no-explicit-any queryParams: Record, diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/index.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/index.ts index 6cd1ae275d592..d5a428638702e 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/index.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/index.ts @@ -9,11 +9,17 @@ import { schema } from '@kbn/config-schema'; import { HostStatus } from '../../../../common/endpoint/types'; import { EndpointAppContext } from '../../types'; -import { getLogger, getMetadataRequestHandler, getMetadataListRequestHandler } from './handlers'; +import { + getLogger, + getMetadataRequestHandler, + getMetadataListRequestHandler, + getMetadataTransformStatsHandler, +} from './handlers'; import type { SecuritySolutionPluginRouter } from '../../../types'; import { HOST_METADATA_GET_ROUTE, HOST_METADATA_LIST_ROUTE, + METADATA_TRANSFORMS_STATUS_ROUTE, } from '../../../../common/endpoint/constants'; import { GetMetadataListRequestSchema } from '../../../../common/endpoint/schema/metadata'; @@ -60,4 +66,13 @@ export function registerEndpointRoutes( }, getMetadataRequestHandler(endpointAppContext, logger) ); + + router.get( + { + path: METADATA_TRANSFORMS_STATUS_ROUTE, + validate: false, + options: { authRequired: true, tags: ['access:securitySolution'] }, + }, + getMetadataTransformStatsHandler(logger) + ); } diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts index e324f66ad38f6..1050273a5ff75 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts @@ -44,8 +44,10 @@ import { HOST_METADATA_LIST_ROUTE, metadataCurrentIndexPattern, metadataTransformPrefix, + METADATA_TRANSFORMS_STATUS_ROUTE, METADATA_UNITED_INDEX, } from '../../../../common/endpoint/constants'; +import { TRANSFORM_STATES } from '../../../../common/constants'; import type { SecuritySolutionPluginRouter } from '../../../types'; import { AgentNotFoundError, PackagePolicyServiceInterface } from '../../../../../fleet/server'; import { @@ -56,6 +58,8 @@ import { import { EndpointHostNotFoundError } from '../../services/metadata'; import { FleetAgentGenerator } from '../../../../common/endpoint/data_generators/fleet_agent_generator'; import { createMockAgentClient } from '../../../../../fleet/server/mocks'; +import { TransformGetTransformStatsResponse } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { getEndpointAuthzInitialStateMock } from '../../../../common/endpoint/service/authz'; class IndexNotFoundException extends Error { meta: { body: { error: { type: string } } }; @@ -114,54 +118,52 @@ describe('test endpoint routes', () => { perPage: 1000, }); }); + + endpointAppContextService = new EndpointAppContextService(); + mockPackageService = createMockPackageService(); + mockPackageService.getInstallation.mockReturnValue( + Promise.resolve({ + installed_kibana: [], + package_assets: [], + es_index_patterns: {}, + name: '', + version: '', + install_status: 'installed', + install_version: '', + install_started_at: '', + install_source: 'registry', + installed_es: [ + { + id: 'logs-endpoint.events.security', + type: ElasticsearchAssetType.indexTemplate, + }, + { + id: `${metadataTransformPrefix}-0.16.0-dev.0`, + type: ElasticsearchAssetType.transform, + }, + ], + keep_policies_up_to_date: false, + }) + ); + endpointAppContextService.setup(createMockEndpointAppContextServiceSetupContract()); + endpointAppContextService.start({ ...startContract, packageService: mockPackageService }); + mockAgentService = startContract.agentService!; + mockAgentClient = createMockAgentClient(); + mockAgentService.asScoped = () => mockAgentClient; + mockAgentPolicyService = startContract.agentPolicyService!; + + registerEndpointRoutes(routerMock, { + logFactory: loggingSystemMock.create(), + service: endpointAppContextService, + config: () => Promise.resolve(createMockConfig()), + experimentalFeatures: parseExperimentalConfigValue(createMockConfig().enableExperimental), + }); }); + afterEach(() => endpointAppContextService.stop()); + describe('GET list endpoints route', () => { describe('with .metrics-endpoint.metadata_united_default index', () => { - beforeEach(() => { - endpointAppContextService = new EndpointAppContextService(); - mockPackageService = createMockPackageService(); - mockPackageService.getInstallation.mockReturnValue( - Promise.resolve({ - installed_kibana: [], - package_assets: [], - es_index_patterns: {}, - name: '', - version: '', - install_status: 'installed', - install_version: '', - install_started_at: '', - install_source: 'registry', - installed_es: [ - { - id: 'logs-endpoint.events.security', - type: ElasticsearchAssetType.indexTemplate, - }, - { - id: `${metadataTransformPrefix}-0.16.0-dev.0`, - type: ElasticsearchAssetType.transform, - }, - ], - keep_policies_up_to_date: false, - }) - ); - endpointAppContextService.setup(createMockEndpointAppContextServiceSetupContract()); - endpointAppContextService.start({ ...startContract, packageService: mockPackageService }); - mockAgentService = startContract.agentService!; - mockAgentClient = createMockAgentClient(); - mockAgentService.asScoped = () => mockAgentClient; - mockAgentPolicyService = startContract.agentPolicyService!; - - registerEndpointRoutes(routerMock, { - logFactory: loggingSystemMock.create(), - service: endpointAppContextService, - config: () => Promise.resolve(createMockConfig()), - experimentalFeatures: parseExperimentalConfigValue(createMockConfig().enableExperimental), - }); - }); - - afterEach(() => endpointAppContextService.stop()); - it('should fallback to legacy index if index not found', async () => { const mockRequest = httpServerMock.createKibanaRequest({ query: { @@ -380,49 +382,6 @@ describe('test endpoint routes', () => { }); describe('with metrics-endpoint.metadata_current_default index', () => { - beforeEach(() => { - endpointAppContextService = new EndpointAppContextService(); - mockPackageService = createMockPackageService(); - mockPackageService.getInstallation.mockReturnValue( - Promise.resolve({ - installed_kibana: [], - package_assets: [], - es_index_patterns: {}, - name: '', - version: '', - install_status: 'installed', - install_version: '', - install_started_at: '', - install_source: 'registry', - installed_es: [ - { - id: 'logs-endpoint.events.security', - type: ElasticsearchAssetType.indexTemplate, - }, - { - id: `${metadataTransformPrefix}-0.16.0-dev.0`, - type: ElasticsearchAssetType.transform, - }, - ], - keep_policies_up_to_date: false, - }) - ); - endpointAppContextService.setup(createMockEndpointAppContextServiceSetupContract()); - endpointAppContextService.start({ ...startContract, packageService: mockPackageService }); - mockAgentService = startContract.agentService!; - mockAgentClient = createMockAgentClient(); - mockAgentService.asScoped = () => mockAgentClient; - - registerEndpointRoutes(routerMock, { - logFactory: loggingSystemMock.create(), - service: endpointAppContextService, - config: () => Promise.resolve(createMockConfig()), - experimentalFeatures: parseExperimentalConfigValue(createMockConfig().enableExperimental), - }); - }); - - afterEach(() => endpointAppContextService.stop()); - it('test find the latest of all endpoints', async () => { const mockRequest = httpServerMock.createKibanaRequest({ query: { @@ -611,49 +570,6 @@ describe('test endpoint routes', () => { }); describe('GET endpoint details route', () => { - beforeEach(() => { - endpointAppContextService = new EndpointAppContextService(); - mockPackageService = createMockPackageService(); - mockPackageService.getInstallation.mockReturnValue( - Promise.resolve({ - installed_kibana: [], - package_assets: [], - es_index_patterns: {}, - name: '', - version: '', - install_status: 'installed', - install_version: '', - install_started_at: '', - install_source: 'registry', - installed_es: [ - { - id: 'logs-endpoint.events.security', - type: ElasticsearchAssetType.indexTemplate, - }, - { - id: `${metadataTransformPrefix}-0.16.0-dev.0`, - type: ElasticsearchAssetType.transform, - }, - ], - keep_policies_up_to_date: false, - }) - ); - endpointAppContextService.setup(createMockEndpointAppContextServiceSetupContract()); - endpointAppContextService.start({ ...startContract, packageService: mockPackageService }); - mockAgentService = startContract.agentService!; - mockAgentClient = createMockAgentClient(); - mockAgentService.asScoped = () => mockAgentClient; - - registerEndpointRoutes(routerMock, { - logFactory: loggingSystemMock.create(), - service: endpointAppContextService, - config: () => Promise.resolve(createMockConfig()), - experimentalFeatures: parseExperimentalConfigValue(createMockConfig().enableExperimental), - }); - }); - - afterEach(() => endpointAppContextService.stop()); - it('should return 404 on no results', async () => { const mockRequest = httpServerMock.createKibanaRequest({ params: { id: 'BADID' } }); @@ -821,4 +737,60 @@ describe('test endpoint routes', () => { expect(mockResponse.badRequest).toBeCalled(); }); }); + + describe('GET metadata transform stats route', () => { + it('should get forbidden if no fleet access', async () => { + const mockRequest = httpServerMock.createKibanaRequest(); + + [routeConfig, routeHandler] = routerMock.get.mock.calls.find(([{ path }]) => + path.startsWith(METADATA_TRANSFORMS_STATUS_ROUTE) + )!; + + const contextOverrides = { + endpointAuthz: getEndpointAuthzInitialStateMock({ canAccessEndpointManagement: false }), + }; + await routeHandler( + createRouteHandlerContext(mockScopedClient, mockSavedObjectClient, contextOverrides), + mockRequest, + mockResponse + ); + + expect(mockResponse.forbidden).toBeCalled(); + }); + + it('should correctly return metadata transform stats', async () => { + const mockRequest = httpServerMock.createKibanaRequest(); + const expectedResponse = { + count: 1, + transforms: [ + { + id: 'someid', + state: TRANSFORM_STATES.STARTED, + }, + ], + }; + const esClientMock = mockScopedClient.asInternalUser; + (esClientMock.transform.getTransformStats as jest.Mock).mockImplementationOnce(() => + Promise.resolve({ body: expectedResponse }) + ); + [routeConfig, routeHandler] = routerMock.get.mock.calls.find(([{ path }]) => + path.startsWith(METADATA_TRANSFORMS_STATUS_ROUTE) + )!; + await routeHandler( + createRouteHandlerContext(mockScopedClient, mockSavedObjectClient), + mockRequest, + mockResponse + ); + + expect(esClientMock.transform.getTransformStats).toHaveBeenCalledTimes(1); + expect(routeConfig.options).toEqual({ + authRequired: true, + tags: ['access:securitySolution'], + }); + expect(mockResponse.ok).toBeCalled(); + const response = mockResponse.ok.mock.calls[0][0]?.body as TransformGetTransformStatsResponse; + expect(response.count).toEqual(expectedResponse.count); + expect(response.transforms).toEqual(expectedResponse.transforms); + }); + }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts index 8abe054daeaf5..778efa05f692e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts @@ -31,8 +31,9 @@ import type { SecuritySolutionRequestHandlerContext, } from '../../../../types'; import { getEndpointAuthzInitialStateMock } from '../../../../../common/endpoint/service/authz'; +import { EndpointAuthz } from '../../../../../common/endpoint/types/authz'; -const createMockClients = () => { +export const createMockClients = () => { const core = coreMock.createRequestHandlerContext(); const license = licensingMock.createLicenseMock(); @@ -67,11 +68,12 @@ type SecuritySolutionRequestHandlerContextMock = }; const createRequestContextMock = ( - clients: MockClients = createMockClients() + clients: MockClients = createMockClients(), + overrides: { endpointAuthz?: Partial } = {} ): SecuritySolutionRequestHandlerContextMock => { return { core: clients.core, - securitySolution: createSecuritySolutionRequestContextMock(clients), + securitySolution: createSecuritySolutionRequestContextMock(clients, overrides), actions: { getActionsClient: jest.fn(() => clients.actionsClient), } as unknown as jest.Mocked, @@ -87,14 +89,15 @@ const createRequestContextMock = ( }; const createSecuritySolutionRequestContextMock = ( - clients: MockClients + clients: MockClients, + overrides: { endpointAuthz?: Partial } = {} ): jest.Mocked => { const core = clients.core; const kibanaRequest = requestMock.create(); return { core, - endpointAuthz: getEndpointAuthzInitialStateMock(), + endpointAuthz: getEndpointAuthzInitialStateMock(overrides.endpointAuthz), getConfig: jest.fn(() => clients.config), getFrameworkRequest: jest.fn(() => { return { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts index 3c1a49c640863..547bdb9105c21 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -226,13 +226,6 @@ export const getFindResultWithMultiHits = ({ }; }; -export const ruleStatusRequest = () => - requestMock.create({ - method: 'post', - path: `${DETECTION_ENGINE_RULES_URL}/_find_statuses`, - body: { ids: ['04128c15-0d1b-4716-a4c5-46997ac7f3bd'] }, - }); - export const internalRuleStatusRequest = () => requestMock.create({ method: 'post', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_status_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_status_route.test.ts deleted file mode 100644 index 2286c010a0a5a..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_status_route.test.ts +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; -import { - ruleStatusRequest, - getAlertMock, - getFindBulkResultStatus, -} from '../__mocks__/request_responses'; -import { serverMock, requestContextMock, requestMock } from '../__mocks__'; -import { findRulesStatusesRoute } from './find_rules_status_route'; -import { RuleStatusResponse } from '../../rules/types'; -import { AlertExecutionStatusErrorReasons } from '../../../../../../alerting/common'; -import { getQueryRuleParams } from '../../schemas/rule_schemas.mock'; - -describe.each([ - ['Legacy', false], - ['RAC', true], -])('find_statuses - %s', (_, isRuleRegistryEnabled) => { - let server: ReturnType; - let { clients, context } = requestContextMock.createTools(); - - beforeEach(async () => { - server = serverMock.create(); - ({ clients, context } = requestContextMock.createTools()); - clients.ruleExecutionLogClient.getCurrentStatusBulk.mockResolvedValue( - getFindBulkResultStatus() - ); // successful status search - clients.rulesClient.get.mockResolvedValue( - getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()) - ); - findRulesStatusesRoute(server.router); - }); - - describe('status codes with actionClient and alertClient', () => { - test('returns 200 when finding a single rule status with a valid rulesClient', async () => { - const response = await server.inject(ruleStatusRequest(), context); - expect(response.status).toEqual(200); - }); - - test('returns 404 if alertClient is not available on the route', async () => { - context.alerting.getRulesClient = jest.fn(); - const response = await server.inject(ruleStatusRequest(), context); - expect(response.status).toEqual(404); - expect(response.body).toEqual({ message: 'Not Found', status_code: 404 }); - }); - - test('catch error when status search throws error', async () => { - clients.ruleExecutionLogClient.getCurrentStatusBulk.mockImplementation(async () => { - throw new Error('Test error'); - }); - const response = await server.inject(ruleStatusRequest(), context); - expect(response.status).toEqual(500); - expect(response.body).toEqual({ - message: 'Test error', - status_code: 500, - }); - }); - - test('returns success if rule status client writes an error status', async () => { - // 0. task manager tried to run the rule but couldn't, so the alerting framework - // wrote an error to the executionStatus. - const failingExecutionRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); - failingExecutionRule.executionStatus = { - status: 'error', - lastExecutionDate: failingExecutionRule.executionStatus.lastExecutionDate, - error: { - reason: AlertExecutionStatusErrorReasons.Read, - message: 'oops', - }, - }; - - // 1. getFailingRules api found a rule where the executionStatus was 'error' - clients.rulesClient.get.mockResolvedValue({ - ...failingExecutionRule, - }); - - const response = await server.inject(ruleStatusRequest(), context); - const body: RuleStatusResponse = response.body; - expect(response.status).toEqual(200); - expect(body[ruleStatusRequest().body.ids[0]].current_status?.status).toEqual('failed'); - expect(body[ruleStatusRequest().body.ids[0]].current_status?.last_failure_message).toEqual( - 'Reason: read Message: oops' - ); - }); - }); - - describe('request validation', () => { - test('disallows singular id query param', async () => { - const request = requestMock.create({ - method: 'post', - path: `${DETECTION_ENGINE_RULES_URL}/_find_statuses`, - body: { id: ['someId'] }, - }); - const result = server.validate(request); - - expect(result.badRequest).toHaveBeenCalledWith('Invalid value "undefined" supplied to "ids"'); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_status_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_status_route.ts deleted file mode 100644 index af4f8ddbb9ec8..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_status_route.ts +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { transformError } from '@kbn/securitysolution-es-utils'; -import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; -import type { SecuritySolutionPluginRouter } from '../../../../types'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; -import { buildSiemResponse, mergeStatuses, getFailingRules } from '../utils'; -import { - findRulesStatusesSchema, - FindRulesStatusesSchemaDecoded, -} from '../../../../../common/detection_engine/schemas/request/find_rule_statuses_schema'; -import { mergeAlertWithSidecarStatus } from '../../schemas/rule_converters'; - -/** - * Returns the current execution status and metrics for N rules. - * Accepts an array of rule ids. - * - * NOTE: This endpoint is used on the Rule Management page and will be reworked. - * See the plan in https://github.com/elastic/kibana/pull/115574 - * - * @param router - * @returns RuleStatusResponse containing data for N requested rules. - * RuleStatusResponse[ruleId].failures is always an empty array, because - * we don't need failure history of every rule when we render tables with rules. - */ -export const findRulesStatusesRoute = (router: SecuritySolutionPluginRouter) => { - router.post( - { - path: `${DETECTION_ENGINE_RULES_URL}/_find_statuses`, - validate: { - body: buildRouteValidation( - findRulesStatusesSchema - ), - }, - options: { - tags: ['access:securitySolution'], - }, - }, - async (context, request, response) => { - const { body } = request; - const siemResponse = buildSiemResponse(response); - const rulesClient = context.alerting?.getRulesClient(); - - if (!rulesClient) { - return siemResponse.error({ statusCode: 404 }); - } - - const ids = body.ids; - try { - const ruleStatusClient = context.securitySolution.getExecutionLogClient(); - const [currentStatusesByRuleId, failingRules] = await Promise.all([ - ruleStatusClient.getCurrentStatusBulk({ - ruleIds: ids, - spaceId: context.securitySolution.getSpaceId(), - }), - getFailingRules(ids, rulesClient), - ]); - - const statuses = ids.reduce((acc, id) => { - const currentStatus = currentStatusesByRuleId[id]; - const failingRule = failingRules[id]; - - if (currentStatus == null) { - return acc; - } - - const finalCurrentStatus = - failingRule != null - ? mergeAlertWithSidecarStatus(failingRule, currentStatus) - : currentStatus; - - return mergeStatuses(id, [finalCurrentStatus], acc); - }, {}); - - return response.ok({ body: statuses }); - } catch (err) { - const error = transformError(err); - return siemResponse.error({ - body: error.message, - statusCode: error.statusCode, - }); - } - } - ); -}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.ts index c10aa0bd42ecd..4cc73f48202ab 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.ts @@ -318,6 +318,9 @@ export const internalRuleToAPIResponse = ( last_success_at: mergedStatus?.lastSuccessAt ?? undefined, last_failure_message: mergedStatus?.lastFailureMessage ?? undefined, last_success_message: mergedStatus?.lastSuccessMessage ?? undefined, + last_gap: mergedStatus?.gap ?? undefined, + bulk_create_time_durations: mergedStatus?.bulkCreateTimeDurations ?? undefined, + search_after_time_durations: mergedStatus?.searchAfterTimeDurations ?? undefined, }; }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/find_rules_statuses_by_ids.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/find_rules_statuses_by_ids.sh deleted file mode 100755 index 943530aa3e956..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/find_rules_statuses_by_ids.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/sh - -# -# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -# or more contributor license agreements. Licensed under the Elastic License -# 2.0; you may not use this file except in compliance with the Elastic License -# 2.0. -# - -set -e -./check_env_variables.sh - - -# Example: ./find_rules_statuses_by_ids.sh [\"12345\",\"6789abc\"] -curl -g -k \ - -s \ - -H 'Content-Type: application/json' \ - -H 'kbn-xsrf: 123' \ - -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ - -X POST "${KIBANA_URL}${SPACE_URL}/api/detection_engine/rules/_find_statuses" \ - -d "{\"ids\": $1}" \ - | jq . diff --git a/x-pack/plugins/security_solution/server/routes/index.ts b/x-pack/plugins/security_solution/server/routes/index.ts index dd66a4333ad15..57af7153ff0a5 100644 --- a/x-pack/plugins/security_solution/server/routes/index.ts +++ b/x-pack/plugins/security_solution/server/routes/index.ts @@ -36,7 +36,6 @@ import { deleteRulesBulkRoute } from '../lib/detection_engine/routes/rules/delet import { performBulkActionRoute } from '../lib/detection_engine/routes/rules/perform_bulk_action_route'; import { importRulesRoute } from '../lib/detection_engine/routes/rules/import_rules_route'; import { exportRulesRoute } from '../lib/detection_engine/routes/rules/export_rules_route'; -import { findRulesStatusesRoute } from '../lib/detection_engine/routes/rules/find_rules_status_route'; import { findRuleStatusInternalRoute } from '../lib/detection_engine/routes/rules/find_rule_status_internal_route'; import { getPrepackagedRulesStatusRoute } from '../lib/detection_engine/routes/rules/get_prepackaged_rules_status_route'; import { @@ -95,8 +94,6 @@ export const initRoutes = ( // Once we no longer have the legacy notifications system/"side car actions" this should be removed. legacyCreateLegacyNotificationRoute(router, logger); - // TODO: pass isRuleRegistryEnabled to all relevant routes - addPrepackedRulesRoute(router); getPrepackagedRulesStatusRoute(router, config, security, isRuleRegistryEnabled); createRulesBulkRoute(router, ml, isRuleRegistryEnabled); @@ -125,7 +122,6 @@ export const initRoutes = ( persistNoteRoute(router, config, security); persistPinnedEventRoute(router, config, security); - findRulesStatusesRoute(router); findRuleStatusInternalRoute(router); // Detection Engine Signals routes that have the REST endpoints of /api/detection_engine/signals diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.test.ts index 9aef01d953c82..36add5af0ed25 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.test.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.test.ts @@ -18,6 +18,7 @@ import { authentications, authenticationsEntities } from './authentications'; import { hostsKpiAuthentications, hostsKpiAuthenticationsEntities } from './kpi/authentications'; import { hostsKpiHosts, hostsKpiHostsEntities } from './kpi/hosts'; import { hostsKpiUniqueIps, hostsKpiUniqueIpsEntities } from './kpi/unique_ips'; +import { hostsKpiRiskyHosts } from './kpi/risky_hosts'; jest.mock('./all'); jest.mock('./details'); @@ -45,6 +46,7 @@ describe('hostsFactory', () => { [HostsKpiQueries.kpiAuthenticationsEntities]: hostsKpiAuthenticationsEntities, [HostsKpiQueries.kpiHosts]: hostsKpiHosts, [HostsKpiQueries.kpiHostsEntities]: hostsKpiHostsEntities, + [HostsKpiQueries.kpiRiskyHosts]: hostsKpiRiskyHosts, [HostsKpiQueries.kpiUniqueIpsEntities]: hostsKpiUniqueIpsEntities, [HostsKpiQueries.kpiUniqueIps]: hostsKpiUniqueIps, }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts index 5b501099a21ed..f182280667e13 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts @@ -22,6 +22,7 @@ import { hostsKpiAuthentications, hostsKpiAuthenticationsEntities } from './kpi/ import { hostsKpiHosts, hostsKpiHostsEntities } from './kpi/hosts'; import { hostsKpiUniqueIps, hostsKpiUniqueIpsEntities } from './kpi/unique_ips'; import { riskScore } from './risk_score'; +import { hostsKpiRiskyHosts } from './kpi/risky_hosts'; export const hostsFactory: Record< HostsQueries | HostsKpiQueries, @@ -40,6 +41,7 @@ export const hostsFactory: Record< [HostsKpiQueries.kpiAuthenticationsEntities]: hostsKpiAuthenticationsEntities, [HostsKpiQueries.kpiHosts]: hostsKpiHosts, [HostsKpiQueries.kpiHostsEntities]: hostsKpiHostsEntities, + [HostsKpiQueries.kpiRiskyHosts]: hostsKpiRiskyHosts, [HostsKpiQueries.kpiUniqueIps]: hostsKpiUniqueIps, [HostsKpiQueries.kpiUniqueIpsEntities]: hostsKpiUniqueIpsEntities, }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/risky_hosts/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/risky_hosts/__mocks__/index.ts new file mode 100644 index 0000000000000..c0522d61e3804 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/risky_hosts/__mocks__/index.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + HostsKpiQueries, + HostsKpiRiskyHostsRequestOptions, +} from '../../../../../../../../common/search_strategy'; + +export const mockOptions: HostsKpiRiskyHostsRequestOptions = { + defaultIndex: [ + 'apm-*-transaction*', + 'traces-apm*', + 'auditbeat-*', + 'endgame-*', + 'filebeat-*', + 'logs-*', + 'packetbeat-*', + 'winlogbeat-*', + ], + factoryQueryType: HostsKpiQueries.kpiRiskyHosts, + filterQuery: + '{"bool":{"must":[],"filter":[{"match_all":{}},{"bool":{"filter":[{"bool":{"should":[{"exists":{"field":"host.name"}}],"minimum_should_match":1}}]}}],"should":[],"must_not":[]}}', + timerange: { interval: '12h', from: '2020-09-07T09:47:28.606Z', to: '2020-09-08T09:47:28.606Z' }, +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/risky_hosts/index.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/risky_hosts/index.test.ts new file mode 100644 index 0000000000000..cbfe63d86ea73 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/risky_hosts/index.test.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { hostsKpiRiskyHosts } from '.'; +import * as buildQuery from './query.hosts_kpi_risky_hosts.dsl'; +import { mockOptions } from './__mocks__'; + +describe('buildHostsKpiRiskyHostsQuery search strategy', () => { + const buildHostsKpiRiskyHostsQuery = jest.spyOn(buildQuery, 'buildHostsKpiRiskyHostsQuery'); + + describe('buildDsl', () => { + test('should build dsl query', () => { + hostsKpiRiskyHosts.buildDsl(mockOptions); + expect(buildHostsKpiRiskyHostsQuery).toHaveBeenCalledWith(mockOptions); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/risky_hosts/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/risky_hosts/index.ts new file mode 100644 index 0000000000000..0e0f48f37efcf --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/risky_hosts/index.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getOr } from 'lodash/fp'; + +import type { IEsSearchResponse } from '../../../../../../../../../../src/plugins/data/common'; +import type { HostsKpiQueries } from '../../../../../../../common/search_strategy'; + +import type { + HostsKpiRiskyHostsRequestOptions, + HostsKpiRiskyHostsStrategyResponse, + HostRiskSeverity, +} from '../../../../../../../common/search_strategy/security_solution/hosts/kpi/risky_hosts'; +import { inspectStringifyObject } from '../../../../../../utils/build_query'; +import type { SecuritySolutionFactory } from '../../../types'; +import { buildHostsKpiRiskyHostsQuery } from './query.hosts_kpi_risky_hosts.dsl'; + +interface AggBucket { + key: HostRiskSeverity; + doc_count: number; +} + +export const hostsKpiRiskyHosts: SecuritySolutionFactory = { + buildDsl: (options: HostsKpiRiskyHostsRequestOptions) => buildHostsKpiRiskyHostsQuery(options), + parse: async ( + options: HostsKpiRiskyHostsRequestOptions, + response: IEsSearchResponse + ): Promise => { + const inspect = { + dsl: [inspectStringifyObject(buildHostsKpiRiskyHostsQuery(options))], + }; + + const riskBuckets = getOr([], 'aggregations.risk.buckets', response.rawResponse); + + const riskyHosts: Record = riskBuckets.reduce( + (cummulative: Record, bucket: AggBucket) => ({ + ...cummulative, + [bucket.key]: bucket.doc_count, + }), + {} + ); + + return { + ...response, + riskyHosts, + inspect, + }; + }, +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/risky_hosts/query.hosts_kpi_risky_hosts.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/risky_hosts/query.hosts_kpi_risky_hosts.dsl.ts new file mode 100644 index 0000000000000..201d73c4ebb18 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/risky_hosts/query.hosts_kpi_risky_hosts.dsl.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { HostsKpiRiskyHostsRequestOptions } from '../../../../../../../common/search_strategy/security_solution/hosts/kpi/risky_hosts'; +import { createQueryFilterClauses } from '../../../../../../utils/build_query'; + +export const buildHostsKpiRiskyHostsQuery = ({ + filterQuery, + timerange: { from, to }, + defaultIndex, +}: HostsKpiRiskyHostsRequestOptions) => { + const filter = [ + ...createQueryFilterClauses(filterQuery), + { + range: { + '@timestamp': { + gte: from, + lte: to, + format: 'strict_date_optional_time', + }, + }, + }, + ]; + + const dslQuery = { + index: defaultIndex, + allow_no_indices: false, + ignore_unavailable: true, + track_total_hits: false, + body: { + aggs: { + risk: { + terms: { field: 'risk.keyword' }, + }, + }, + query: { + bool: { + filter, + }, + }, + size: 0, + }, + }; + + return dslQuery; +}; diff --git a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json index 55efcd4d15a33..7e05d4df7644a 100644 --- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json +++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @@ -5962,9 +5962,6 @@ "available": { "type": "boolean" }, - "browser_type": { - "type": "keyword" - }, "enabled": { "type": "boolean" }, diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 4694b8c63d7e0..02b3ea61732af 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -19972,11 +19972,6 @@ "xpack.remoteClusters.updateRemoteCluster.unknownRemoteClusterErrorMessage": "ES からレスポンスが返らず、クラスターを編集できません。", "xpack.reporting.apiClient.unknownError": "レポートジョブ{job}が失敗しました。不明なエラーです。", "xpack.reporting.breadcrumb": "レポート", - "xpack.reporting.browsers.chromium.errorDetected": "レポートでエラーが発生しました:{err}", - "xpack.reporting.browsers.chromium.pageErrorDetected": "レポートのページで処理されていないエラーが発生し、無視されるます:{err}", - "xpack.reporting.chromiumDriver.disallowedOutgoingUrl": "許可されていない送信URLを受信しました:「{interceptedUrl}」。要求が失敗しています。ブラウザーを終了しています。", - "xpack.reporting.chromiumDriver.failedToCompleteRequest": "リクエストを完了できませんでした:{error}", - "xpack.reporting.chromiumDriver.failedToCompleteRequestUsingHeaders": "ヘッダーを使用してリクエストを完了できませんでした:{error}", "xpack.reporting.dashboard.csvDownloadStartedMessage": "間もなく CSV がダウンロードされます。", "xpack.reporting.dashboard.csvDownloadStartedTitle": "CSV のダウンロードが開始しました", "xpack.reporting.dashboard.downloadCsvPanelTitle": "CSV をダウンロード", @@ -20007,8 +20002,6 @@ "xpack.reporting.deprecations.reportingRoleUsers.manualStepTwo": "存在する場合は、kibana.ymlで「xpack.reporting.roles.allow」を削除します。", "xpack.reporting.deprecations.reportingRoleUsersMessage": "既存のユーザーには廃止予定の設定によって付与されたレポート権限があります。", "xpack.reporting.deprecations.reportingRoleUsersTitle": "\"{reportingUserRoleName}\"ロールは廃止予定です。ユーザーロールを確認してください", - "xpack.reporting.diagnostic.browserCrashed": "ブラウザーは起動中に異常終了しました", - "xpack.reporting.diagnostic.browserErrored": "ブラウザープロセスは起動中にエラーが発生しました", "xpack.reporting.diagnostic.browserMissingDependency": "システム依存関係が不足しているため、ブラウザーを正常に起動できませんでした。{url}を参照してください", "xpack.reporting.diagnostic.browserMissingFonts": "ブラウザーはデフォルトフォントを検索できませんでした。この問題を修正するには、{url}を参照してください。", "xpack.reporting.diagnostic.noUsableSandbox": "Chromiumサンドボックスを使用できません。これは「xpack.reporting.capture.browser.chromium.disableSandbox」で無効にすることができます。この作業はご自身の責任で行ってください。{url}を参照してください", @@ -20059,7 +20052,6 @@ "xpack.reporting.listing.ilmPolicyCallout.migrationNeededDescription": "レポートが一貫して管理されることを保証するために、すべてのレポートインデックスは{ilmPolicyName}ポリシーを使用します。", "xpack.reporting.listing.ilmPolicyCallout.migrationNeededTitle": "レポートの新しいライフサイクルポリシーを適用", "xpack.reporting.listing.infoPanel.attemptsInfo": "試行", - "xpack.reporting.listing.infoPanel.browserTypeInfo": "ブラウザータイプ", "xpack.reporting.listing.infoPanel.completedAtInfo": "完了日時", "xpack.reporting.listing.infoPanel.contentTypeInfo": "コンテンツタイプ", "xpack.reporting.listing.infoPanel.createdAtInfo": "作成日時:", @@ -20135,20 +20127,6 @@ "xpack.reporting.redirectApp.redirectConsoleErrorPrefixLabel": "リダイレクトページエラー:", "xpack.reporting.registerFeature.reportingDescription": "Discover、可視化、ダッシュボードから生成されたレポートを管理します。", "xpack.reporting.registerFeature.reportingTitle": "レポート", - "xpack.reporting.screencapture.browserWasClosed": "ブラウザーは予期せず終了しました。詳細については、サーバーログを確認してください。", - "xpack.reporting.screencapture.couldntFinishRendering": "{count} 件のビジュアライゼーションのレンダリングが完了するのを待つ間にエラーが発生しました。{error}", - "xpack.reporting.screencapture.couldntLoadKibana": "Kibana URLを開こうとするときにエラーが発生しました:{error}", - "xpack.reporting.screencapture.injectCss": "Kibana CSS をレポート用に更新しようとしたときにエラーが発生しました。{error}", - "xpack.reporting.screencapture.injectingCss": "カスタム css の投入中", - "xpack.reporting.screencapture.logWaitingForElements": "要素または項目のカウント属性を待ち、または見つからないため中断", - "xpack.reporting.screencapture.noElements": "ビジュアライゼーションパネルのページを読み取る間にエラーが発生しました:パネルが見つかりませんでした。", - "xpack.reporting.screencapture.readVisualizationsError": "ビジュアライゼーションパネル情報のページを読み取ろうとしたときにエラーが発生しました:{error}", - "xpack.reporting.screencapture.renderErrorsFound": "{count}件のエラーメッセージが見つかりました。詳細については、レポートオブジェクトを参照してください。", - "xpack.reporting.screencapture.renderIsComplete": "レンダリングが完了しました", - "xpack.reporting.screencapture.screenshotsTaken": "撮影したスクリーンショット:{numScreenhots}", - "xpack.reporting.screencapture.takingScreenshots": "スクリーンショットの撮影中", - "xpack.reporting.screencapture.waitingForRenderComplete": "レンダリングの完了を待っています", - "xpack.reporting.screencapture.waitingForRenderedElements": "レンダリングされた {itemsCount} 個の要素が DOM に入るのを待っています", "xpack.reporting.screenCapturePanelContent.canvasLayoutHelpText": "枠線とフッターロゴを削除", "xpack.reporting.screenCapturePanelContent.canvasLayoutLabel": "全ページレイアウト", "xpack.reporting.screenCapturePanelContent.optimizeForPrintingHelpText": "複数のページを使用します。ページごとに最大2のビジュアライゼーションが表示されます", @@ -20447,6 +20425,30 @@ "xpack.savedObjectsTagging.validation.description.errorTooLong": "タグ説明は {length} 文字以下で入力してください", "xpack.savedObjectsTagging.validation.name.errorTooLong": "タグ名は {length} 文字以下で入力してください", "xpack.savedObjectsTagging.validation.name.errorTooShort": "タグ名は {length} 文字以上で入力してください", + "xpack.screenshotting.browsers.chromium.errorDetected": "レポートでエラーが発生しました:{err}", + "xpack.screenshotting.browsers.chromium.pageErrorDetected": "レポートのページで処理されていないエラーが発生し、無視されるます:{err}", + "xpack.screenshotting.chromiumDriver.disallowedOutgoingUrl": "許可されていない送信URLを受信しました:「{interceptedUrl}」。要求が失敗しています。ブラウザーを終了しています。", + "xpack.screenshotting.chromiumDriver.failedToCompleteRequest": "リクエストを完了できませんでした:{error}", + "xpack.screenshotting.chromiumDriver.failedToCompleteRequestUsingHeaders": "ヘッダーを使用してリクエストを完了できませんでした:{error}", + "xpack.screenshotting.diagnostic.browserCrashed": "ブラウザーは起動中に異常終了しました", + "xpack.screenshotting.diagnostic.browserErrored": "ブラウザープロセスは起動中にエラーが発生しました", + "xpack.screenshotting.screencapture.browserWasClosed": "ブラウザーは予期せず終了しました。詳細については、サーバーログを確認してください。", + "xpack.screenshotting.screencapture.couldntFinishRendering": "{count} 件のビジュアライゼーションのレンダリングが完了するのを待つ間にエラーが発生しました。{error}", + "xpack.screenshotting.screencapture.couldntLoadKibana": "Kibana URLを開こうとするときにエラーが発生しました:{error}", + "xpack.screenshotting.screencapture.injectCss": "Kibana CSS をレポート用に更新しようとしたときにエラーが発生しました。{error}", + "xpack.screenshotting.screencapture.injectingCss": "カスタム css の投入中", + "xpack.screenshotting.screencapture.logWaitingForElements": "要素または項目のカウント属性を待ち、または見つからないため中断", + "xpack.screenshotting.screencapture.noElements": "ビジュアライゼーションパネルのページを読み取る間にエラーが発生しました:パネルが見つかりませんでした。", + "xpack.screenshotting.screencapture.readVisualizationsError": "ビジュアライゼーションパネル情報のページを読み取ろうとしたときにエラーが発生しました:{error}", + "xpack.screenshotting.screencapture.renderErrorsFound": "{count}件のエラーメッセージが見つかりました。詳細については、レポートオブジェクトを参照してください。", + "xpack.screenshotting.screencapture.renderIsComplete": "レンダリングが完了しました", + "xpack.screenshotting.screencapture.screenshotsTaken": "撮影したスクリーンショット:{numScreenhots}", + "xpack.screenshotting.screencapture.takingScreenshots": "スクリーンショットの撮影中", + "xpack.screenshotting.screencapture.waitingForRenderComplete": "レンダリングの完了を待っています", + "xpack.screenshotting.screencapture.waitingForRenderedElements": "レンダリングされた {itemsCount} 個の要素が DOM に入るのを待っています", + "xpack.screenshotting.serverConfig.autoSet.sandboxDisabled": "Chromiumサンドボックスは保護が強化されていますが、{osName} OSではサポートされていません。自動的に'{configKey}: true'を設定しています。", + "xpack.screenshotting.serverConfig.autoSet.sandboxEnabled": "Chromiumサンドボックスは保護が強化され、{osName} OSでサポートされています。自動的にChromiumサンドボックスを有効にしています。", + "xpack.screenshotting.serverConfig.osDetected": "OSは'{osName}'で実行しています", "xpack.searchProfiler.advanceTimeDescription": "イテレーターを次のドキュメントに進めるためにかかった時間。", "xpack.searchProfiler.aggregationProfileTabTitle": "集約プロフィール", "xpack.searchProfiler.basicLicenseTitle": "基本", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index ba523a4236b7d..4296209d9fce1 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -20264,11 +20264,6 @@ "xpack.remoteClusters.updateRemoteCluster.unknownRemoteClusterErrorMessage": "无法编辑集群,ES 未返回任何响应。", "xpack.reporting.apiClient.unknownError": "报告作业 {job} 失败。错误未知。", "xpack.reporting.breadcrumb": "Reporting", - "xpack.reporting.browsers.chromium.errorDetected": "报告时遇到错误:{err}", - "xpack.reporting.browsers.chromium.pageErrorDetected": "Reporting 在将忽略的页面上遇到未捕获的错误:{err}", - "xpack.reporting.chromiumDriver.disallowedOutgoingUrl": "收到禁止的传出 URL:“{interceptedUrl}”。请求失败,关闭浏览器。", - "xpack.reporting.chromiumDriver.failedToCompleteRequest": "无法完成请求:{error}", - "xpack.reporting.chromiumDriver.failedToCompleteRequestUsingHeaders": "无法完成使用 headers 的请求:{error}", "xpack.reporting.dashboard.csvDownloadStartedMessage": "您的 CSV 将很快下载。", "xpack.reporting.dashboard.csvDownloadStartedTitle": "CSV 下载已开始", "xpack.reporting.dashboard.downloadCsvPanelTitle": "下载 CSV", @@ -20299,8 +20294,6 @@ "xpack.reporting.deprecations.reportingRoleUsers.manualStepTwo": "移除 kibana.yml 中的“xpack.reporting.roles.allow”(如果存在)。", "xpack.reporting.deprecations.reportingRoleUsersMessage": "现有用户具有由过时设置授予的 Reporting 权限。", "xpack.reporting.deprecations.reportingRoleUsersTitle": "“{reportingUserRoleName}”角色已过时:检查用户角色", - "xpack.reporting.diagnostic.browserCrashed": "启动期间浏览器已异常退出", - "xpack.reporting.diagnostic.browserErrored": "启动时浏览器进程引发了错误", "xpack.reporting.diagnostic.browserMissingDependency": "由于缺少系统依赖项,浏览器无法正常启动。请参见 {url}", "xpack.reporting.diagnostic.browserMissingFonts": "浏览器找不到默认字体。请参见 {url} 以解决此问题。", "xpack.reporting.diagnostic.noUsableSandbox": "无法使用 Chromium 沙盒。您自行承担使用“xpack.reporting.capture.browser.chromium.disableSandbox”禁用此项的风险。请参见 {url}", @@ -20351,7 +20344,6 @@ "xpack.reporting.listing.ilmPolicyCallout.migrationNeededDescription": "为了确保得到一致的管理,所有报告索引应使用 {ilmPolicyName} 策略。", "xpack.reporting.listing.ilmPolicyCallout.migrationNeededTitle": "为报告应用新的生命周期策略", "xpack.reporting.listing.infoPanel.attemptsInfo": "尝试次数", - "xpack.reporting.listing.infoPanel.browserTypeInfo": "浏览器类型", "xpack.reporting.listing.infoPanel.completedAtInfo": "完成时间", "xpack.reporting.listing.infoPanel.contentTypeInfo": "内容类型", "xpack.reporting.listing.infoPanel.createdAtInfo": "创建于", @@ -20428,20 +20420,6 @@ "xpack.reporting.redirectApp.redirectConsoleErrorPrefixLabel": "重定向页面错误:", "xpack.reporting.registerFeature.reportingDescription": "管理您从 Discover、Visualize 和 Dashboard 生成的报告。", "xpack.reporting.registerFeature.reportingTitle": "Reporting", - "xpack.reporting.screencapture.browserWasClosed": "浏览器已意外关闭!有关更多信息,请查看服务器日志。", - "xpack.reporting.screencapture.couldntFinishRendering": "尝试等候 {count} 个可视化完成渲染时发生错误。{error}", - "xpack.reporting.screencapture.couldntLoadKibana": "尝试打开 Kibana URL 时发生错误:{error}", - "xpack.reporting.screencapture.injectCss": "尝试为 Reporting 更新 Kibana CSS 时发生错误。{error}", - "xpack.reporting.screencapture.injectingCss": "正在注入定制 css", - "xpack.reporting.screencapture.logWaitingForElements": "等候元素或项目计数属性;或未发现要中断", - "xpack.reporting.screencapture.noElements": "读取页面以获取可视化面板时发生了错误:未找到任何面板。", - "xpack.reporting.screencapture.readVisualizationsError": "尝试读取页面以获取可视化面板信息时发生错误:{error}", - "xpack.reporting.screencapture.renderErrorsFound": "找到 {count} 条错误消息。请参阅报告对象了解更多信息。", - "xpack.reporting.screencapture.renderIsComplete": "渲染已完成", - "xpack.reporting.screencapture.screenshotsTaken": "已捕获的屏幕截图:{numScreenhots}", - "xpack.reporting.screencapture.takingScreenshots": "正在捕获屏幕截图", - "xpack.reporting.screencapture.waitingForRenderComplete": "正在等候渲染完成", - "xpack.reporting.screencapture.waitingForRenderedElements": "正在等候 {itemsCount} 个已渲染元素进入 DOM", "xpack.reporting.screenCapturePanelContent.canvasLayoutHelpText": "删除边框和页脚徽标", "xpack.reporting.screenCapturePanelContent.canvasLayoutLabel": "全页面布局", "xpack.reporting.screenCapturePanelContent.optimizeForPrintingHelpText": "使用多页,每页最多显示 2 个可视化", @@ -20752,6 +20730,30 @@ "xpack.savedObjectsTagging.validation.description.errorTooLong": "标签描述不能超过 {length} 个字符", "xpack.savedObjectsTagging.validation.name.errorTooLong": "标签名称不能超过 {length} 个字符", "xpack.savedObjectsTagging.validation.name.errorTooShort": "标签名称必须至少有 {length} 个字符", + "xpack.screenshotting.browsers.chromium.errorDetected": "报告时遇到错误:{err}", + "xpack.screenshotting.browsers.chromium.pageErrorDetected": "Reporting 在将忽略的页面上遇到未捕获的错误:{err}", + "xpack.screenshotting.chromiumDriver.disallowedOutgoingUrl": "收到禁止的传出 URL:“{interceptedUrl}”。请求失败,关闭浏览器。", + "xpack.screenshotting.chromiumDriver.failedToCompleteRequest": "无法完成请求:{error}", + "xpack.screenshotting.chromiumDriver.failedToCompleteRequestUsingHeaders": "无法完成使用 headers 的请求:{error}", + "xpack.screenshotting.diagnostic.browserCrashed": "启动期间浏览器已异常退出", + "xpack.screenshotting.diagnostic.browserErrored": "启动时浏览器进程引发了错误", + "xpack.screenshotting.screencapture.browserWasClosed": "浏览器已意外关闭!有关更多信息,请查看服务器日志。", + "xpack.screenshotting.screencapture.couldntFinishRendering": "尝试等候 {count} 个可视化完成渲染时发生错误。{error}", + "xpack.screenshotting.screencapture.couldntLoadKibana": "尝试打开 Kibana URL 时发生错误:{error}", + "xpack.screenshotting.screencapture.injectCss": "尝试为 Reporting 更新 Kibana CSS 时发生错误。{error}", + "xpack.screenshotting.screencapture.injectingCss": "正在注入定制 css", + "xpack.screenshotting.screencapture.logWaitingForElements": "等候元素或项目计数属性;或未发现要中断", + "xpack.screenshotting.screencapture.noElements": "读取页面以获取可视化面板时发生了错误:未找到任何面板。", + "xpack.screenshotting.screencapture.readVisualizationsError": "尝试读取页面以获取可视化面板信息时发生错误:{error}", + "xpack.screenshotting.screencapture.renderErrorsFound": "找到 {count} 条错误消息。请参阅报告对象了解更多信息。", + "xpack.screenshotting.screencapture.renderIsComplete": "渲染已完成", + "xpack.screenshotting.screencapture.screenshotsTaken": "已捕获的屏幕截图:{numScreenhots}", + "xpack.screenshotting.screencapture.takingScreenshots": "正在捕获屏幕截图", + "xpack.screenshotting.screencapture.waitingForRenderComplete": "正在等候渲染完成", + "xpack.screenshotting.screencapture.waitingForRenderedElements": "正在等候 {itemsCount} 个已渲染元素进入 DOM", + "xpack.screenshotting.serverConfig.autoSet.sandboxDisabled": "Chromium 沙盒提供附加保护层,但不受 {osName} OS 支持。自动设置“{configKey}: true”。", + "xpack.screenshotting.serverConfig.autoSet.sandboxEnabled": "Chromium 沙盒提供附加保护层,受 {osName} OS 支持。自动启用 Chromium 沙盒。", + "xpack.screenshotting.serverConfig.osDetected": "正在以下 OS 上运行:“{osName}”", "xpack.searchProfiler.advanceTimeDescription": "将迭代器推进至下一文档所用时间。", "xpack.searchProfiler.aggregationProfileTabTitle": "聚合配置文件", "xpack.searchProfiler.basicLicenseTitle": "基本级", diff --git a/x-pack/plugins/uptime/common/runtime_types/monitor_management/config_key.ts b/x-pack/plugins/uptime/common/runtime_types/monitor_management/config_key.ts index 13a7c1e1f2f2d..b7d66ba204e67 100644 --- a/x-pack/plugins/uptime/common/runtime_types/monitor_management/config_key.ts +++ b/x-pack/plugins/uptime/common/runtime_types/monitor_management/config_key.ts @@ -11,6 +11,7 @@ import { tEnum } from '../../utils/t_enum'; // values must match keys in the integration package export enum ConfigKey { APM_SERVICE_NAME = 'service.name', + ENABLED = 'enabled', HOSTS = 'hosts', IGNORE_HTTPS_ERRORS = 'ignore_https_errors', JOURNEY_FILTERS_MATCH = 'filter_journeys.match', diff --git a/x-pack/plugins/uptime/common/runtime_types/monitor_management/monitor_types.ts b/x-pack/plugins/uptime/common/runtime_types/monitor_management/monitor_types.ts index f54035b7f69ff..fd2ef24bacabd 100644 --- a/x-pack/plugins/uptime/common/runtime_types/monitor_management/monitor_types.ts +++ b/x-pack/plugins/uptime/common/runtime_types/monitor_management/monitor_types.ts @@ -48,6 +48,7 @@ export type ZipUrlTLSFields = t.TypeOf; // CommonFields export const CommonFieldsCodec = t.interface({ [ConfigKey.MONITOR_TYPE]: DataStreamCodec, + [ConfigKey.ENABLED]: t.boolean, [ConfigKey.SCHEDULE]: Schedule, [ConfigKey.APM_SERVICE_NAME]: t.string, [ConfigKey.TIMEOUT]: t.string, diff --git a/x-pack/plugins/uptime/public/components/fleet_package/browser/simple_fields.tsx b/x-pack/plugins/uptime/public/components/fleet_package/browser/simple_fields.tsx index b0f3b29599242..621e269521ad9 100644 --- a/x-pack/plugins/uptime/public/components/fleet_package/browser/simple_fields.tsx +++ b/x-pack/plugins/uptime/public/components/fleet_package/browser/simple_fields.tsx @@ -13,7 +13,7 @@ import { ConfigKey } from '../types'; import { useBrowserSimpleFieldsContext } from '../contexts'; import { ScheduleField } from '../schedule_field'; import { SourceField } from './source_field'; -import { CommonFields } from '../common/common_fields'; +import { SimpleFieldsWrapper } from '../common/simple_fields_wrapper'; interface Props { validate: Validation; @@ -58,7 +58,7 @@ export const BrowserSimpleFields = memo(({ validate }) => { ); return ( - <> + (({ validate }) => { )} /> - - + ); }); diff --git a/x-pack/plugins/uptime/public/components/fleet_package/browser/source_field.test.tsx b/x-pack/plugins/uptime/public/components/fleet_package/browser/source_field.test.tsx index 6cf37aa2238f3..402bd175a09ea 100644 --- a/x-pack/plugins/uptime/public/components/fleet_package/browser/source_field.test.tsx +++ b/x-pack/plugins/uptime/public/components/fleet_package/browser/source_field.test.tsx @@ -17,6 +17,12 @@ jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({ htmlIdGenerator: () => () => `id-${Math.random()}`, })); +// ensures that fields appropriately match to their label +jest.mock('@elastic/eui/lib/services/accessibility', () => ({ + ...jest.requireActual('@elastic/eui/lib/services/accessibility'), + useGeneratedHtmlId: () => `id-${Math.random()}`, +})); + jest.mock('../../../../../../../src/plugins/kibana_react/public', () => { const original = jest.requireActual('../../../../../../../src/plugins/kibana_react/public'); return { diff --git a/x-pack/plugins/uptime/public/components/fleet_package/common/default_values.ts b/x-pack/plugins/uptime/public/components/fleet_package/common/default_values.ts index 50c540266724a..c997d42a22c00 100644 --- a/x-pack/plugins/uptime/public/components/fleet_package/common/default_values.ts +++ b/x-pack/plugins/uptime/public/components/fleet_package/common/default_values.ts @@ -9,6 +9,7 @@ import { CommonFields, ConfigKey, ScheduleUnit, DataStream } from '../types'; export const defaultValues: CommonFields = { [ConfigKey.MONITOR_TYPE]: DataStream.HTTP, + [ConfigKey.ENABLED]: true, [ConfigKey.SCHEDULE]: { number: '3', unit: ScheduleUnit.MINUTES, diff --git a/x-pack/plugins/uptime/public/components/fleet_package/common/enabled.tsx b/x-pack/plugins/uptime/public/components/fleet_package/common/enabled.tsx new file mode 100644 index 0000000000000..09a8e9aec3719 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/fleet_package/common/enabled.tsx @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { EuiFormRow, EuiSwitch } from '@elastic/eui'; +import { ConfigKey, CommonFields } from '../types'; + +interface Props { + fields: CommonFields; + onChange: ({ value, configKey }: { value: boolean; configKey: ConfigKey }) => void; +} + +export function Enabled({ fields, onChange }: Props) { + return ( + <> + + } + > + + } + data-test-subj="syntheticsEnabled" + checked={fields[ConfigKey.ENABLED]} + onChange={(event) => + onChange({ + value: event.target.checked, + configKey: ConfigKey.ENABLED, + }) + } + /> + + + ); +} diff --git a/x-pack/plugins/uptime/public/components/fleet_package/common/formatters.ts b/x-pack/plugins/uptime/public/components/fleet_package/common/formatters.ts index 077e0fb0becda..4934882430cb4 100644 --- a/x-pack/plugins/uptime/public/components/fleet_package/common/formatters.ts +++ b/x-pack/plugins/uptime/public/components/fleet_package/common/formatters.ts @@ -14,6 +14,7 @@ export type CommonFormatMap = Record JSON.stringify( `@every ${fields[ConfigKey.SCHEDULE]?.number}${fields[ConfigKey.SCHEDULE]?.unit}` diff --git a/x-pack/plugins/uptime/public/components/fleet_package/common/normalizers.ts b/x-pack/plugins/uptime/public/components/fleet_package/common/normalizers.ts index 5710a99a65660..bcc521abf468a 100644 --- a/x-pack/plugins/uptime/public/components/fleet_package/common/normalizers.ts +++ b/x-pack/plugins/uptime/public/components/fleet_package/common/normalizers.ts @@ -57,6 +57,7 @@ export const getCommonCronToSecondsNormalizer = (key: ConfigKey) => { export const commonNormalizers: CommonNormalizerMap = { [ConfigKey.NAME]: (fields) => fields?.[ConfigKey.NAME]?.value ?? '', + [ConfigKey.ENABLED]: getCommonNormalizer(ConfigKey.ENABLED), [ConfigKey.MONITOR_TYPE]: getCommonNormalizer(ConfigKey.MONITOR_TYPE), [ConfigKey.SCHEDULE]: (fields) => { const value = fields?.[ConfigKey.SCHEDULE]?.value; diff --git a/x-pack/plugins/uptime/public/components/fleet_package/common/simple_fields_wrapper.tsx b/x-pack/plugins/uptime/public/components/fleet_package/common/simple_fields_wrapper.tsx new file mode 100644 index 0000000000000..989763ab92275 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/fleet_package/common/simple_fields_wrapper.tsx @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { ConfigKey, Validation, CommonFields as CommonFieldsType } from '../types'; +import { CommonFields } from '../common/common_fields'; +import { Enabled } from '../common/enabled'; + +interface Props { + validate: Validation; + onInputChange: ({ value, configKey }: { value: unknown; configKey: ConfigKey }) => void; + children: React.ReactNode; + fields: CommonFieldsType; +} + +export const SimpleFieldsWrapper = ({ validate, onInputChange, children, fields }: Props) => { + return ( + <> + + {children} + + + ); +}; diff --git a/x-pack/plugins/uptime/public/components/fleet_package/custom_fields.test.tsx b/x-pack/plugins/uptime/public/components/fleet_package/custom_fields.test.tsx index 2aed5db789f44..6597953279e81 100644 --- a/x-pack/plugins/uptime/public/components/fleet_package/custom_fields.test.tsx +++ b/x-pack/plugins/uptime/public/components/fleet_package/custom_fields.test.tsx @@ -28,6 +28,12 @@ jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({ htmlIdGenerator: () => () => `id-${Math.random()}`, })); +// ensures that fields appropriately match to their label +jest.mock('@elastic/eui/lib/services/accessibility', () => ({ + ...jest.requireActual('@elastic/eui/lib/services/accessibility'), + useGeneratedHtmlId: () => `id-${Math.random()}`, +})); + jest.mock('../../../../../../src/plugins/kibana_react/public', () => { const original = jest.requireActual('../../../../../../src/plugins/kibana_react/public'); return { @@ -323,4 +329,19 @@ describe('', () => { expect(queryByText('Browser (Beta)')).not.toBeInTheDocument(); }); }); + + it('allows monitors to be disabled', async () => { + const { queryByLabelText } = render( + + ); + + const enabled = queryByLabelText('Enabled') as HTMLInputElement; + expect(enabled).toBeChecked(); + + fireEvent.click(enabled); + + await waitFor(() => { + expect(enabled).not.toBeChecked(); + }); + }); }); diff --git a/x-pack/plugins/uptime/public/components/fleet_package/custom_fields.tsx b/x-pack/plugins/uptime/public/components/fleet_package/custom_fields.tsx index 50f8d93ca8f4a..603c6b0e72560 100644 --- a/x-pack/plugins/uptime/public/components/fleet_package/custom_fields.tsx +++ b/x-pack/plugins/uptime/public/components/fleet_package/custom_fields.tsx @@ -176,9 +176,10 @@ export const CustomFields = memo(({ validate, dataStreams = [], children defaultMessage="Configure TLS options, including verification mode, certificate authorities, and client certificates." /> } + id="uptimeFleetIsTLSEnabled" > (({ validate }) => { }; return ( - <> + (({ validate }) => { } /> - - + ); }); diff --git a/x-pack/plugins/uptime/public/components/fleet_package/icmp/simple_fields.tsx b/x-pack/plugins/uptime/public/components/fleet_package/icmp/simple_fields.tsx index 20bd9b422ea0b..2fa1d471008f9 100644 --- a/x-pack/plugins/uptime/public/components/fleet_package/icmp/simple_fields.tsx +++ b/x-pack/plugins/uptime/public/components/fleet_package/icmp/simple_fields.tsx @@ -12,7 +12,7 @@ import { ConfigKey, Validation } from '../types'; import { useICMPSimpleFieldsContext } from '../contexts'; import { OptionalLabel } from '../optional_label'; import { ScheduleField } from '../schedule_field'; -import { CommonFields } from '../common/common_fields'; +import { SimpleFieldsWrapper } from '../common/simple_fields_wrapper'; interface Props { validate: Validation; @@ -25,7 +25,7 @@ export const ICMPSimpleFields = memo(({ validate }) => { }; return ( - <> + (({ validate }) => { step={'any'} /> - - + ); }); diff --git a/x-pack/plugins/uptime/public/components/fleet_package/synthetics_policy_create_extension_wrapper.test.tsx b/x-pack/plugins/uptime/public/components/fleet_package/synthetics_policy_create_extension_wrapper.test.tsx index 313fc460c5c5e..cbb3c0d3e477d 100644 --- a/x-pack/plugins/uptime/public/components/fleet_package/synthetics_policy_create_extension_wrapper.test.tsx +++ b/x-pack/plugins/uptime/public/components/fleet_package/synthetics_policy_create_extension_wrapper.test.tsx @@ -21,6 +21,12 @@ jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({ htmlIdGenerator: () => () => `id-${Math.random()}`, })); +// ensures that fields appropriately match to their label +jest.mock('@elastic/eui/lib/services/accessibility', () => ({ + ...jest.requireActual('@elastic/eui/lib/services/accessibility'), + useGeneratedHtmlId: () => `id-${Math.random()}`, +})); + jest.mock('../../../../../../src/plugins/kibana_react/public', () => { const original = jest.requireActual('../../../../../../src/plugins/kibana_react/public'); return { diff --git a/x-pack/plugins/uptime/public/components/fleet_package/synthetics_policy_edit_extension_wrapper.test.tsx b/x-pack/plugins/uptime/public/components/fleet_package/synthetics_policy_edit_extension_wrapper.test.tsx index 5ad02ef2b9a50..d22f72b762e49 100644 --- a/x-pack/plugins/uptime/public/components/fleet_package/synthetics_policy_edit_extension_wrapper.test.tsx +++ b/x-pack/plugins/uptime/public/components/fleet_package/synthetics_policy_edit_extension_wrapper.test.tsx @@ -21,6 +21,12 @@ jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({ htmlIdGenerator: () => () => `id-${Math.random()}`, })); +// ensures that fields appropriately match to their label +jest.mock('@elastic/eui/lib/services/accessibility', () => ({ + ...jest.requireActual('@elastic/eui/lib/services/accessibility'), + useGeneratedHtmlId: () => `id-${Math.random()}`, +})); + jest.mock('../../../../../../src/plugins/kibana_react/public', () => { const original = jest.requireActual('../../../../../../src/plugins/kibana_react/public'); return { @@ -1174,6 +1180,7 @@ describe('', () => { }} /> ); + const verificationMode = queryByLabelText('Verification mode'); const enableTLSConfig = getByLabelText('Enable TLS configuration') as HTMLInputElement; expect(enableTLSConfig.getAttribute('aria-checked')).toEqual('false'); diff --git a/x-pack/plugins/uptime/public/components/fleet_package/tcp/simple_fields.tsx b/x-pack/plugins/uptime/public/components/fleet_package/tcp/simple_fields.tsx index cb6ebf6430eb8..4055b92dcf70f 100644 --- a/x-pack/plugins/uptime/public/components/fleet_package/tcp/simple_fields.tsx +++ b/x-pack/plugins/uptime/public/components/fleet_package/tcp/simple_fields.tsx @@ -11,7 +11,7 @@ import { EuiFormRow, EuiFieldText } from '@elastic/eui'; import { ConfigKey, Validation } from '../types'; import { useTCPSimpleFieldsContext } from '../contexts'; import { ScheduleField } from '../schedule_field'; -import { CommonFields } from '../common/common_fields'; +import { SimpleFieldsWrapper } from '../common/simple_fields_wrapper'; interface Props { validate: Validation; @@ -24,7 +24,7 @@ export const TCPSimpleFields = memo(({ validate }) => { }; return ( - <> + (({ validate }) => { unit={fields[ConfigKey.SCHEDULE].unit} /> - - + ); }); diff --git a/x-pack/plugins/uptime/public/components/monitor_management/formatters/common.ts b/x-pack/plugins/uptime/public/components/monitor_management/formatters/common.ts index da50dfd4b0860..c8defe16b488e 100644 --- a/x-pack/plugins/uptime/public/components/monitor_management/formatters/common.ts +++ b/x-pack/plugins/uptime/public/components/monitor_management/formatters/common.ts @@ -15,6 +15,7 @@ export type CommonFormatMap = Record `@every ${fields[ConfigKey.SCHEDULE]?.number}${fields[ConfigKey.SCHEDULE]?.unit}`, diff --git a/x-pack/tasks/download_chromium.ts b/x-pack/tasks/download_chromium.ts index 6e1efc60f3185..51394bfb00349 100644 --- a/x-pack/tasks/download_chromium.ts +++ b/x-pack/tasks/download_chromium.ts @@ -5,14 +5,13 @@ * 2.0. */ -import { LevelLogger } from '../plugins/reporting/server/lib'; -import { ensureBrowserDownloaded } from '../plugins/reporting/server/browsers/download'; +import { download } from '../plugins/screenshotting/server/utils'; export const downloadChromium = async () => { // eslint-disable-next-line no-console const consoleLogger = (tag: string) => (message: unknown) => console.log(tag, message); - const innerLogger = { - get: () => innerLogger, + const logger = { + get: () => logger, debug: consoleLogger('debug'), info: consoleLogger('info'), warn: consoleLogger('warn'), @@ -22,6 +21,5 @@ export const downloadChromium = async () => { log: consoleLogger('log'), }; - const levelLogger = new LevelLogger(innerLogger); - await ensureBrowserDownloaded(levelLogger); + await download(logger); }; diff --git a/x-pack/test/cases_api_integration/common/fixtures/plugins/cases_client_user/server/plugin.ts b/x-pack/test/cases_api_integration/common/fixtures/plugins/cases_client_user/server/plugin.ts index d26da34579dff..8bb361fba4440 100644 --- a/x-pack/test/cases_api_integration/common/fixtures/plugins/cases_client_user/server/plugin.ts +++ b/x-pack/test/cases_api_integration/common/fixtures/plugins/cases_client_user/server/plugin.ts @@ -12,7 +12,7 @@ import { PluginSetupContract as FeaturesPluginSetup } from '../../../../../../.. import { SpacesPluginStart } from '../../../../../../../plugins/spaces/server'; import { SecurityPluginStart } from '../../../../../../../plugins/security/server'; import { PluginStartContract as CasesPluginStart } from '../../../../../../../plugins/cases/server'; -import { CasesPatchRequest } from '../../../../../../../plugins/cases/common'; +import { CasesPatchRequest } from '../../../../../../../plugins/cases/common/api'; export interface FixtureSetupDeps { features: FeaturesPluginSetup; diff --git a/x-pack/test/cases_api_integration/common/lib/validation.ts b/x-pack/test/cases_api_integration/common/lib/validation.ts index 8b1c8ca124149..3dcb15c14645d 100644 --- a/x-pack/test/cases_api_integration/common/lib/validation.ts +++ b/x-pack/test/cases_api_integration/common/lib/validation.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; -import { CaseResponse, CasesByAlertId } from '../../../../plugins/cases/common'; +import { CaseResponse, CasesByAlertId } from '../../../../plugins/cases/common/api'; /** * Ensure that the result of the alerts API request matches with the cases created for the test. diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/alerts/get_cases.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/alerts/get_cases.ts index 136e52d08f46a..f1c16e9b6dc56 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/alerts/get_cases.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/alerts/get_cases.ts @@ -17,7 +17,7 @@ import { deleteAllCaseItems, } from '../../../../common/lib/utils'; import { validateCasesFromAlertIDResponse } from '../../../../common/lib/validation'; -import { CaseResponse } from '../../../../../../plugins/cases/common'; +import { CaseResponse } from '../../../../../../plugins/cases/common/api'; import { globalRead, noKibanaPrivileges, diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/import_export.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/import_export.ts index dd1c2e810f150..1377bbdabf2e0 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/import_export.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/import_export.ts @@ -19,19 +19,21 @@ import { } from '../../../../common/lib/utils'; import { getPostCaseRequest, postCommentUserReq } from '../../../../common/lib/mock'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; +import { + CASES_URL, + CASE_SAVED_OBJECT, + CASE_USER_ACTION_SAVED_OBJECT, + CASE_COMMENT_SAVED_OBJECT, +} from '../../../../../../plugins/cases/common/constants'; import { AttributesTypeUser, CommentsResponse, - CASES_URL, CaseType, - CASE_SAVED_OBJECT, CaseAttributes, - CASE_USER_ACTION_SAVED_OBJECT, CaseUserActionAttributes, - CASE_COMMENT_SAVED_OBJECT, CasePostRequest, CaseUserActionResponse, -} from '../../../../../../plugins/cases/common'; +} from '../../../../../../plugins/cases/common/api'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/migrations.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/migrations.ts index a489b403354b7..4d42609f57d49 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/migrations.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/migrations.ts @@ -13,7 +13,7 @@ import { } from '../../../../../../plugins/cases/common/constants'; import { getCase, getCaseSavedObjectsFromES, resolveCase } from '../../../../common/lib/utils'; import { superUser } from '../../../../common/lib/authentication/users'; -import { AttributesTypeUser } from '../../../../../../plugins/cases/common'; +import { AttributesTypeUser } from '../../../../../../plugins/cases/common/api'; // eslint-disable-next-line import/no-default-export export default function createGetTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/client/update_alert_status.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/client/update_alert_status.ts index d2949c9728989..006deaad27f56 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/client/update_alert_status.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/client/update_alert_status.ts @@ -15,7 +15,11 @@ import { deleteAllCaseItems, getSignalsWithES, } from '../../../../common/lib/utils'; -import { CasesResponse, CaseStatuses, CommentType } from '../../../../../../plugins/cases/common'; +import { + CasesResponse, + CaseStatuses, + CommentType, +} from '../../../../../../plugins/cases/common/api'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/user_actions/migrations.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/user_actions/migrations.ts index f9e66880c5230..2dc4f740a6819 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/user_actions/migrations.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/user_actions/migrations.ts @@ -15,7 +15,7 @@ import { getCaseUserActions } from '../../../../common/lib/utils'; import { CaseUserActionResponse, CaseUserActionsResponse, -} from '../../../../../../plugins/cases/common'; +} from '../../../../../../plugins/cases/common/api'; // eslint-disable-next-line import/no-default-export export default function createGetTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/find_statuses.ts b/x-pack/test/detection_engine_api_integration/basic/tests/find_statuses.ts deleted file mode 100644 index 82a793619483e..0000000000000 --- a/x-pack/test/detection_engine_api_integration/basic/tests/find_statuses.ts +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import expect from '@kbn/expect'; - -import { DETECTION_ENGINE_RULES_URL } from '../../../../plugins/security_solution/common/constants'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { - createSignalsIndex, - deleteAllAlerts, - deleteSignalsIndex, - deleteAllRulesStatuses, - getSimpleRule, - createRule, - waitForRuleSuccessOrStatus, -} from '../../utils'; - -// eslint-disable-next-line import/no-default-export -export default ({ getService }: FtrProviderContext): void => { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - const es = getService('es'); - const log = getService('log'); - - describe('find_statuses', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); - }); - - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); - }); - - beforeEach(async () => { - await createSignalsIndex(supertest, log); - }); - - afterEach(async () => { - await deleteSignalsIndex(supertest, log); - await deleteAllAlerts(supertest, log); - await deleteAllRulesStatuses(es, log); - }); - - it('should return an empty find statuses body correctly if no statuses are loaded', async () => { - const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_find_statuses`) - .set('kbn-xsrf', 'true') - .send({ ids: [] }) - .expect(200); - - expect(body).to.eql({}); - }); - - it('should return a single rule status when a single rule is loaded from a find status with defaults added', async () => { - const resBody = await createRule(supertest, log, getSimpleRule('rule-1', true)); - - await waitForRuleSuccessOrStatus(supertest, log, resBody.id); - - // query the single rule from _find - const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_find_statuses`) - .set('kbn-xsrf', 'true') - .send({ ids: [resBody.id] }) - .expect(200); - - // expected result for status should be 'going to run' or 'succeeded - expect(['succeeded', 'going to run']).to.contain(body[resBody.id].current_status.status); - }); - }); -}; diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/index.ts b/x-pack/test/detection_engine_api_integration/basic/tests/index.ts index 5fa4540bbe854..1a5ea8de935b4 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/index.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/index.ts @@ -19,7 +19,6 @@ export default ({ loadTestFile }: FtrProviderContext): void => { loadTestFile(require.resolve('./delete_rules_bulk')); loadTestFile(require.resolve('./export_rules')); loadTestFile(require.resolve('./find_rules')); - loadTestFile(require.resolve('./find_statuses')); loadTestFile(require.resolve('./get_prepackaged_rules_status')); loadTestFile(require.resolve('./import_rules')); loadTestFile(require.resolve('./read_rules')); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_actions.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_actions.ts index cf29875839060..5506febb781f6 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_actions.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_actions.ts @@ -8,7 +8,6 @@ import expect from '@kbn/expect'; import { CreateRulesSchema } from '../../../../plugins/security_solution/common/detection_engine/schemas/request'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../plugins/security_solution/common/constants'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createSignalsIndex, @@ -76,14 +75,6 @@ export default ({ getService }: FtrProviderContext) => { getRuleWithWebHookAction(hookAction.id, true) ); await waitForRuleSuccessOrStatus(supertest, log, rule.id); - - // expected result for status should be 'succeeded' - const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_find_statuses`) - .set('kbn-xsrf', 'true') - .send({ ids: [rule.id] }) - .expect(200); - expect(body[rule.id].current_status.status).to.eql('succeeded'); }); it('should be able to create a new webhook action and attach it to a rule with a meta field and run it correctly', async () => { @@ -102,14 +93,6 @@ export default ({ getService }: FtrProviderContext) => { const rule = await createRule(supertest, log, ruleWithAction); await waitForRuleSuccessOrStatus(supertest, log, rule.id); - - // expected result for status should be 'succeeded' - const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_find_statuses`) - .set('kbn-xsrf', 'true') - .send({ ids: [rule.id] }) - .expect(200); - expect(body[rule.id].current_status.status).to.eql('succeeded'); }); }); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/check_privileges.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/check_privileges.ts index 4e7cccd85d828..6d2610dfce186 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/check_privileges.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/check_privileges.ts @@ -66,11 +66,11 @@ export default ({ getService }: FtrProviderContext) => { }); await waitForRuleSuccessOrStatus(supertest, log, id, 'partial failure'); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_find_statuses`) + .get(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') - .send({ ids: [id] }) + .query({ id }) .expect(200); - expect(body[id]?.current_status?.last_success_message).to.eql( + expect(body?.last_success_message).to.eql( `This rule may not have the required read privileges to the following indices/index patterns: ["${index[0]}"]` ); @@ -92,11 +92,11 @@ export default ({ getService }: FtrProviderContext) => { }); await waitForRuleSuccessOrStatus(supertest, log, id, 'partial failure'); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_find_statuses`) + .get(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') - .send({ ids: [id] }) + .query({ id }) .expect(200); - expect(body[id]?.current_status?.last_success_message).to.eql( + expect(body?.last_success_message).to.eql( `This rule may not have the required read privileges to the following indices/index patterns: ["${index[0]}"]` ); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules.ts index d7bba5fa5dbe5..4a572f94b959d 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules.ts @@ -8,10 +8,7 @@ import expect from '@kbn/expect'; import { CreateRulesSchema } from '../../../../plugins/security_solution/common/detection_engine/schemas/request'; -import { - DETECTION_ENGINE_RULES_URL, - DETECTION_ENGINE_RULES_STATUS_URL, -} from '../../../../plugins/security_solution/common/constants'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../plugins/security_solution/common/constants'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createSignalsIndex, @@ -33,7 +30,6 @@ import { } from '../../utils'; import { ROLES } from '../../../../plugins/security_solution/common/test'; import { createUserAndRole, deleteUserAndRole } from '../../../common/services/security_solution'; -import { RuleStatusResponse } from '../../../../plugins/security_solution/server/lib/detection_engine/rules/types'; function sleep(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)); @@ -105,14 +101,6 @@ export default ({ getService }: FtrProviderContext) => { .expect(200); await waitForRuleSuccessOrStatus(supertest, log, body.id); - - const { body: statusBody } = await supertest - .post(DETECTION_ENGINE_RULES_STATUS_URL) - .set('kbn-xsrf', 'true') - .send({ ids: [body.id] }) - .expect(200); - - expect(statusBody[body.id].current_status.status).to.eql('succeeded'); }); // TODO: does the below test work? @@ -126,14 +114,14 @@ export default ({ getService }: FtrProviderContext) => { await waitForRuleSuccessOrStatus(supertest, log, body.id, 'partial failure'); - const { body: statusBody } = await supertest - .post(DETECTION_ENGINE_RULES_STATUS_URL) + const { body: rule } = await supertest + .get(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') - .send({ ids: [body.id] }) + .query({ id: body.id }) .expect(200); - expect(statusBody[body.id].current_status.status).to.eql('partial failure'); - expect(statusBody[body.id].current_status.last_success_message).to.eql( + expect(rule.status).to.eql('partial failure'); + expect(rule.last_success_message).to.eql( 'This rule is attempting to query data from Elasticsearch indices listed in the "Index pattern" section of the rule definition, however no index matching: ["does-not-exist-*"] was found. This warning will continue to appear until a matching index is created or this rule is de-activated.' ); }); @@ -147,14 +135,6 @@ export default ({ getService }: FtrProviderContext) => { .expect(200); await waitForRuleSuccessOrStatus(supertest, log, body.id, 'succeeded'); - - const { body: statusBody } = await supertest - .post(DETECTION_ENGINE_RULES_STATUS_URL) - .set('kbn-xsrf', 'true') - .send({ ids: [body.id] }) - .expect(200); - - expect(statusBody[body.id].current_status.status).to.eql('succeeded'); }); it('should create a single rule without an input index', async () => { @@ -321,18 +301,14 @@ export default ({ getService }: FtrProviderContext) => { await waitForRuleSuccessOrStatus(supertest, log, bodyId, 'partial failure'); await sleep(5000); - const { body: statusBody } = await supertest - .post(DETECTION_ENGINE_RULES_STATUS_URL) + const { body: rule } = await supertest + .get(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') - .send({ ids: [bodyId] }) + .query({ id: bodyId }) .expect(200); - expect((statusBody as RuleStatusResponse)[bodyId].current_status?.status).to.eql( - 'partial failure' - ); - expect( - (statusBody as RuleStatusResponse)[bodyId].current_status?.last_success_message - ).to.eql( + expect(rule?.status).to.eql('partial failure'); + expect(rule?.last_success_message).to.eql( 'The following indices are missing the timestamp override field "event.ingested": ["myfakeindex-1"]' ); }); @@ -353,13 +329,13 @@ export default ({ getService }: FtrProviderContext) => { await sleep(5000); await waitForSignalsToBePresent(supertest, log, 2, [bodyId]); - const { body: statusBody } = await supertest - .post(DETECTION_ENGINE_RULES_STATUS_URL) + const { body: rule } = await supertest + .get(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') - .send({ ids: [bodyId] }) + .query({ id: bodyId }) .expect(200); - expect(statusBody[bodyId].current_status.status).to.eql('partial failure'); + expect(rule.status).to.eql('partial failure'); }); }); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules_bulk.ts index e12f9b7fe7825..06bc7139d5ac2 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules_bulk.ts @@ -7,10 +7,7 @@ import expect from '@kbn/expect'; -import { - DETECTION_ENGINE_RULES_URL, - DETECTION_ENGINE_RULES_STATUS_URL, -} from '../../../../plugins/security_solution/common/constants'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../plugins/security_solution/common/constants'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createSignalsIndex, @@ -90,14 +87,6 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200); await waitForRuleSuccessOrStatus(supertest, log, body[0].id); - - const { body: statusBody } = await supertest - .post(DETECTION_ENGINE_RULES_STATUS_URL) - .set('kbn-xsrf', 'true') - .send({ ids: [body[0].id] }) - .expect(200); - - expect(statusBody[body[0].id].current_status.status).to.eql('succeeded'); }); it('should create a single rule without a rule_id', async () => { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_threat_matching.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_threat_matching.ts index 3d09599278809..fb3c9342536f2 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_threat_matching.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_threat_matching.ts @@ -22,10 +22,7 @@ import { import { flattenWithPrefix } from '@kbn/securitysolution-rules'; import { CreateRulesSchema } from '../../../../plugins/security_solution/common/detection_engine/schemas/request'; -import { - DETECTION_ENGINE_RULES_STATUS_URL, - DETECTION_ENGINE_RULES_URL, -} from '../../../../plugins/security_solution/common/constants'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../plugins/security_solution/common/constants'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createRule, @@ -110,15 +107,15 @@ export default ({ getService }: FtrProviderContext) => { await waitForRuleSuccessOrStatus(supertest, log, ruleResponse.id, 'succeeded'); - const { body: statusBody } = await supertest - .post(DETECTION_ENGINE_RULES_STATUS_URL) + const { body: rule } = await supertest + .get(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') - .send({ ids: [ruleResponse.id] }) + .query({ id: ruleResponse.id }) .expect(200); const bodyToCompare = removeServerGeneratedProperties(ruleResponse); expect(bodyToCompare).to.eql(getThreatMatchingSchemaPartialMock(true)); - expect(statusBody[ruleResponse.id].current_status.status).to.eql('succeeded'); + expect(rule.status).to.eql('succeeded'); }); }); @@ -495,11 +492,11 @@ export default ({ getService }: FtrProviderContext) => { await waitForRuleSuccessOrStatus(supertest, log, id, 'failed'); const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_find_statuses`) + .post(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') - .send({ ids: [id] }) + .query({ id }) .expect(200); - expect(body[id].current_status.last_failure_message).to.contain( + expect(body.last_failure_message).to.contain( 'execution has exceeded its allotted interval' ); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/find_statuses.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/find_statuses.ts deleted file mode 100644 index 0b430d6ae2d92..0000000000000 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/find_statuses.ts +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import expect from '@kbn/expect'; - -import { DETECTION_ENGINE_RULES_URL } from '../../../../plugins/security_solution/common/constants'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { - createSignalsIndex, - deleteAllAlerts, - deleteSignalsIndex, - deleteAllRulesStatuses, - getSimpleRule, - waitForRuleSuccessOrStatus, - createRule, -} from '../../utils'; - -// eslint-disable-next-line import/no-default-export -export default ({ getService }: FtrProviderContext): void => { - const supertest = getService('supertest'); - const es = getService('es'); - const esArchiver = getService('esArchiver'); - const log = getService('log'); - - describe('find_statuses', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); - }); - - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); - }); - - beforeEach(async () => { - await createSignalsIndex(supertest, log); - }); - - afterEach(async () => { - await deleteSignalsIndex(supertest, log); - await deleteAllAlerts(supertest, log); - await deleteAllRulesStatuses(es, log); - }); - - it('should return an empty find statuses body correctly if no statuses are loaded', async () => { - const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_find_statuses`) - .set('kbn-xsrf', 'true') - .send({ ids: [] }) - .expect(200); - - expect(body).to.eql({}); - }); - - /* - This test is to ensure no future regressions introduced by the following scenario - a call to updateApiKey was invalidating the api key used by the - rule while the rule was executing, or even before it executed, - on the first rule run. - this pr https://github.com/elastic/kibana/pull/68184 - fixed this by finding the true source of a bug that required the manual - api key update, and removed the call to that function. - - When the api key is updated before / while the rule is executing, the alert - executor no longer has access to a service to update the rule status - saved object in Elasticsearch. Because of this, we cannot set the rule into - a 'failure' state, so the user ends up seeing 'going to run' as that is the - last status set for the rule before it erupts in an error that cannot be - recorded inside of the executor. - - This adds an e2e test for the backend to catch that in case - this pops up again elsewhere. - */ - it('should return a single rule status when a single rule is loaded from a find status with defaults added', async () => { - const resBody = await createRule(supertest, log, getSimpleRule('rule-1', true)); - - await waitForRuleSuccessOrStatus(supertest, log, resBody.id); - - // query the single rule from _find - const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_find_statuses`) - .set('kbn-xsrf', 'true') - .send({ ids: [resBody.id] }) - .expect(200); - - // expected result for status should be 'going to run' or 'succeeded - expect(body[resBody.id].current_status.status).to.eql('succeeded'); - }); - }); -}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts index 073c5a9971f4b..eaf30f18c1ca3 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts @@ -1189,12 +1189,10 @@ export default ({ getService }: FtrProviderContext) => { expect(signals.hits.hits.length).to.eql(1); const statusResponse = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_find_statuses`) + .get(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') - .send({ ids: [ruleResponse.id] }); - const initialStatusDate = new Date( - statusResponse.body[ruleResponse.id].current_status.status_date - ); + .query({ id: ruleResponse.id }); + const initialStatusDate = new Date(statusResponse.body.status_date); const initialSignal = signals.hits.hits[0]; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/index.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/index.ts index db616baa0678a..05f5b7973e9ea 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/index.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/index.ts @@ -29,7 +29,6 @@ export default ({ loadTestFile }: FtrProviderContext): void => { loadTestFile(require.resolve('./delete_rules_bulk')); loadTestFile(require.resolve('./export_rules')); loadTestFile(require.resolve('./find_rules')); - loadTestFile(require.resolve('./find_statuses')); loadTestFile(require.resolve('./generating_signals')); loadTestFile(require.resolve('./get_prepackaged_rules_status')); loadTestFile(require.resolve('./import_rules')); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_actions.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_actions.ts index 43b982ff537d7..a46df0711c45d 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_actions.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_actions.ts @@ -8,7 +8,6 @@ import expect from '@kbn/expect'; import { CreateRulesSchema } from '../../../../plugins/security_solution/common/detection_engine/schemas/request'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../plugins/security_solution/common/constants'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createSignalsIndex, @@ -91,14 +90,6 @@ export default ({ getService }: FtrProviderContext) => { const ruleToUpdate = getRuleWithWebHookAction(hookAction.id, true, rule); const updatedRule = await updateRule(supertest, log, ruleToUpdate); await waitForRuleSuccessOrStatus(supertest, log, updatedRule.id); - - // expected result for status should be 'succeeded' - const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_find_statuses`) - .set('kbn-xsrf', 'true') - .send({ ids: [updatedRule.id] }) - .expect(200); - expect(body[updatedRule.id].current_status.status).to.eql('succeeded'); }); it('should be able to create a new webhook action and attach it to a rule with a meta field and run it correctly', async () => { @@ -111,14 +102,6 @@ export default ({ getService }: FtrProviderContext) => { }; const updatedRule = await updateRule(supertest, log, ruleToUpdate); await waitForRuleSuccessOrStatus(supertest, log, updatedRule.id); - - // expected result for status should be 'succeeded' - const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_find_statuses`) - .set('kbn-xsrf', 'true') - .send({ ids: [updatedRule.id] }) - .expect(200); - expect(body[updatedRule.id].current_status.status).to.eql('succeeded'); }); it('should be able to create a new webhook action and attach it to an immutable rule', async () => { diff --git a/x-pack/test/detection_engine_api_integration/utils.ts b/x-pack/test/detection_engine_api_integration/utils.ts index bb4f564edaf1e..b36a94e76ca60 100644 --- a/x-pack/test/detection_engine_api_integration/utils.ts +++ b/x-pack/test/detection_engine_api_integration/utils.ts @@ -69,6 +69,9 @@ export const removeServerGeneratedProperties = ( last_failure_message, last_success_at, last_success_message, + last_gap, + search_after_time_durations, + bulk_create_time_durations, status, status_date, /* eslint-enable @typescript-eslint/naming-convention */ @@ -1315,9 +1318,9 @@ export const waitForRuleSuccessOrStatus = async ( async () => { try { const response = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_find_statuses`) + .get(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') - .send({ ids: [id] }); + .query({ id }); if (response.status !== 200) { log.error( `Did not get an expected 200 "ok" when waiting for a rule success or status (waitForRuleSuccessOrStatus). CI issues could happen. Suspect this line if you are seeing CI issues. body: ${JSON.stringify( @@ -1325,9 +1328,9 @@ export const waitForRuleSuccessOrStatus = async ( )}, status: ${JSON.stringify(response.status)}` ); } - const currentStatus = response.body[id]?.current_status; + const rule = response.body; - if (currentStatus?.status !== status) { + if (rule?.status !== status) { log.debug( `Did not get an expected status of ${status} while waiting for a rule success or status for rule id ${id} (waitForRuleSuccessOrStatus). Will continue retrying until status is found. body: ${JSON.stringify( response.body @@ -1335,9 +1338,9 @@ export const waitForRuleSuccessOrStatus = async ( ); } return ( - currentStatus != null && - currentStatus.status === status && - (afterDate ? new Date(currentStatus.status_date) > afterDate : true) + rule != null && + rule.status === status && + (afterDate ? new Date(rule.status_date) > afterDate : true) ); } catch (e) { if ((e as Error).message.includes('got 503 "Service Unavailable"')) { diff --git a/x-pack/test/functional/apps/dashboard/index.ts b/x-pack/test/functional/apps/dashboard/index.ts index 59211ecf37f2d..be817faeaa6c4 100644 --- a/x-pack/test/functional/apps/dashboard/index.ts +++ b/x-pack/test/functional/apps/dashboard/index.ts @@ -22,6 +22,7 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./dashboard_maps_by_value')); loadTestFile(require.resolve('./migration_smoke_tests/lens_migration_smoke_test')); + loadTestFile(require.resolve('./migration_smoke_tests/controls_migration_smoke_test')); loadTestFile(require.resolve('./migration_smoke_tests/visualize_migration_smoke_test')); loadTestFile(require.resolve('./migration_smoke_tests/tsvb_migration_smoke_test')); }); diff --git a/x-pack/test/functional/apps/dashboard/migration_smoke_tests/controls_migration_smoke_test.ts b/x-pack/test/functional/apps/dashboard/migration_smoke_tests/controls_migration_smoke_test.ts new file mode 100644 index 0000000000000..72de77c2e2c2b --- /dev/null +++ b/x-pack/test/functional/apps/dashboard/migration_smoke_tests/controls_migration_smoke_test.ts @@ -0,0 +1,96 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* + * This test imports a dashboard saved with controls from 8.0.0, because that is the earliest version + * with the dashboard controls integration in place. + */ + +import expect from '@kbn/expect'; +import path from 'path'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const retry = getService('retry'); + const pieChart = getService('pieChart'); + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + const testSubjects = getService('testSubjects'); + + const { common, settings, savedObjects, dashboard, dashboardControls } = getPageObjects([ + 'common', + 'settings', + 'dashboard', + 'savedObjects', + 'dashboardControls', + ]); + + describe('Export import saved objects between versions', () => { + before(async () => { + await esArchiver.loadIfNeeded( + 'x-pack/test/functional/es_archives/getting_started/shakespeare' + ); + await kibanaServer.uiSettings.replace({}); + await settings.navigateTo(); + await settings.clickKibanaSavedObjects(); + await savedObjects.importFile( + path.join(__dirname, 'exports', 'controls_dashboard_migration_test_8_0_0.ndjson') + ); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/getting_started/shakespeare'); + await esArchiver.load('x-pack/test/functional/es_archives/empty_kibana'); + }); + + it('should be able to import dashboard with controls from 8.0.0', async () => { + // this will catch cases where there is an error in the migrations. + await savedObjects.checkImportSucceeded(); + await savedObjects.clickImportDone(); + }); + + it('should render all panels on the dashboard', async () => { + await dashboardControls.enableControlsLab(); + await common.navigateToApp('dashboard'); + await dashboard.loadSavedDashboard('[8.0.0] Controls Dashboard'); + + // dashboard should load properly + await dashboard.expectOnDashboard('[8.0.0] Controls Dashboard'); + await dashboard.waitForRenderComplete(); + + // There should be 0 error embeddables on the dashboard + const errorEmbeddables = await testSubjects.findAll('embeddableStackError'); + expect(errorEmbeddables.length).to.be(0); + }); + + it('loads all controls from the saved dashboard', async () => { + expect(await dashboardControls.getControlsCount()).to.be(2); + expect(await dashboardControls.getAllControlTitles()).to.eql(['Speaker Name', 'Play Name']); + + const ids = await dashboardControls.getAllControlIds(); + for (const id of ids) { + await dashboardControls.optionsListOpenPopover(id); + await retry.try(async () => { + // Value counts should be 10, because there are more than 10 speakers and plays in the data set + expect(await dashboardControls.optionsListPopoverGetAvailableOptionsCount()).to.be(10); + }); + await dashboardControls.optionsListEnsurePopoverIsClosed(id); + } + }); + + it('applies default selected options list options to control', async () => { + const controlIds = await dashboardControls.getAllControlIds(); + const selectionString = await dashboardControls.optionsListGetSelectionsString(controlIds[0]); + expect(selectionString).to.be('HAMLET, ROMEO, JULIET, BRUTUS'); + }); + + it('applies default selected options list options to dashboard', async () => { + // because 4 selections are made on the control, the pie chart should only show 4 slices. + expect(await pieChart.getPieSliceCount()).to.be(4); + }); + }); +} diff --git a/x-pack/test/functional/apps/dashboard/migration_smoke_tests/exports/controls_dashboard_migration_test_8_0_0.ndjson b/x-pack/test/functional/apps/dashboard/migration_smoke_tests/exports/controls_dashboard_migration_test_8_0_0.ndjson new file mode 100644 index 0000000000000..8996a93476621 --- /dev/null +++ b/x-pack/test/functional/apps/dashboard/migration_smoke_tests/exports/controls_dashboard_migration_test_8_0_0.ndjson @@ -0,0 +1,3 @@ +{"attributes":{"fieldAttrs":"{}","fields":"[]","runtimeFieldMap":"{}","title":"shakespeare","typeMeta":"{}"},"coreMigrationVersion":"8.1.0","id":"f60ebe00-4e0f-11ec-afa1-59364130c0f2","migrationVersion":{"index-pattern":"8.0.0"},"references":[],"type":"index-pattern","updated_at":"2021-11-25T17:46:44.515Z","version":"WzEwLDJd"} +{"attributes":{"controlGroupInput":{"controlStyle":"oneLine","panelsJSON":"{\"ec64cb73-f891-4f48-bfa5-e3ca0df9920e\":{\"order\":1,\"width\":\"auto\",\"type\":\"optionsListControl\",\"explicitInput\":{\"title\":\"Speaker Name\",\"fieldName\":\"speaker\",\"id\":\"ec64cb73-f891-4f48-bfa5-e3ca0df9920e\",\"enhancements\":{},\"selectedOptions\":[\"HAMLET\",\"ROMEO\",\"JULIET\",\"BRUTUS\"]}},\"3b343fbe-c0a0-4bc5-825e-7c4fff42ad8f\":{\"order\":2,\"width\":\"auto\",\"type\":\"optionsListControl\",\"explicitInput\":{\"title\":\"Play Name\",\"fieldName\":\"play_name\",\"id\":\"3b343fbe-c0a0-4bc5-825e-7c4fff42ad8f\",\"enhancements\":{},\"selectedOptions\":[]}}}"},"description":"","hits":0,"kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}"},"optionsJSON":"{\"useMargins\":true,\"syncColors\":false,\"hidePanelTitles\":false}","panelsJSON":"[{\"version\":\"8.1.0\",\"type\":\"lens\",\"gridData\":{\"x\":0,\"y\":0,\"w\":23,\"h\":15,\"i\":\"bee1d6e0-389a-4992-a802-d51a658e3faa\"},\"panelIndex\":\"bee1d6e0-389a-4992-a802-d51a658e3faa\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"f60ebe00-4e0f-11ec-afa1-59364130c0f2\",\"name\":\"indexpattern-datasource-current-indexpattern\",\"type\":\"index-pattern\"},{\"id\":\"f60ebe00-4e0f-11ec-afa1-59364130c0f2\",\"name\":\"indexpattern-datasource-layer-623f6f95-c70a-4e42-9505-b2b428d3c86b\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"legend\":{\"isVisible\":true,\"position\":\"right\"},\"valueLabels\":\"hide\",\"fittingFunction\":\"None\",\"yLeftExtent\":{\"mode\":\"full\"},\"yRightExtent\":{\"mode\":\"full\"},\"axisTitlesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"tickLabelsVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"labelsOrientation\":{\"x\":0,\"yLeft\":0,\"yRight\":0},\"gridlinesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"623f6f95-c70a-4e42-9505-b2b428d3c86b\",\"accessors\":[\"9f9547b8-c132-4634-80f6-ed70ba4ec689\"],\"position\":\"top\",\"seriesType\":\"bar_stacked\",\"showGridlines\":false,\"layerType\":\"data\",\"xAccessor\":\"44c77394-0cbc-43ec-b97a-7c446631c77e\"}]},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"indexpattern\":{\"layers\":{\"623f6f95-c70a-4e42-9505-b2b428d3c86b\":{\"columns\":{\"44c77394-0cbc-43ec-b97a-7c446631c77e\":{\"label\":\"Top values of play_name\",\"dataType\":\"string\",\"operationType\":\"terms\",\"scale\":\"ordinal\",\"sourceField\":\"play_name\",\"isBucketed\":true,\"params\":{\"size\":5,\"orderBy\":{\"type\":\"column\",\"columnId\":\"9f9547b8-c132-4634-80f6-ed70ba4ec689\"},\"orderDirection\":\"desc\",\"otherBucket\":false,\"missingBucket\":false}},\"9f9547b8-c132-4634-80f6-ed70ba4ec689\":{\"label\":\"Count of records\",\"dataType\":\"number\",\"operationType\":\"count\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"Records\"}},\"columnOrder\":[\"44c77394-0cbc-43ec-b97a-7c446631c77e\",\"9f9547b8-c132-4634-80f6-ed70ba4ec689\"],\"incompleteColumns\":{}}}}}}},\"enhancements\":{}}},{\"version\":\"8.1.0\",\"type\":\"visualization\",\"gridData\":{\"x\":23,\"y\":0,\"w\":24,\"h\":15,\"i\":\"63eee8ef-0220-4089-9be9-13a9839cae17\"},\"panelIndex\":\"63eee8ef-0220-4089-9be9-13a9839cae17\",\"embeddableConfig\":{\"savedVis\":{\"id\":\"\",\"title\":\"\",\"description\":\"\",\"type\":\"pie\",\"params\":{\"type\":\"pie\",\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"nestedLegend\":false,\"truncateLegend\":true,\"maxLegendLines\":1,\"distinctColors\":false,\"isDonut\":true,\"palette\":{\"type\":\"palette\",\"name\":\"default\"},\"labels\":{\"show\":true,\"last_level\":true,\"values\":true,\"valuesFormat\":\"percent\",\"percentDecimals\":2,\"truncate\":100,\"position\":\"default\"}},\"uiState\":{},\"data\":{\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"params\":{},\"schema\":\"metric\"},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"speaker\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":5,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\"},\"schema\":\"segment\"}],\"searchSource\":{\"index\":\"f60ebe00-4e0f-11ec-afa1-59364130c0f2\",\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}}},\"enhancements\":{}}}]","timeRestore":false,"title":"[8.0.0] Controls Dashboard","version":1},"coreMigrationVersion":"8.1.0","id":"5bcfc8b0-4e10-11ec-afa1-59364130c0f2","migrationVersion":{"dashboard":"8.0.0"},"references":[{"id":"f60ebe00-4e0f-11ec-afa1-59364130c0f2","name":"bee1d6e0-389a-4992-a802-d51a658e3faa:indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"f60ebe00-4e0f-11ec-afa1-59364130c0f2","name":"bee1d6e0-389a-4992-a802-d51a658e3faa:indexpattern-datasource-layer-623f6f95-c70a-4e42-9505-b2b428d3c86b","type":"index-pattern"},{"id":"f60ebe00-4e0f-11ec-afa1-59364130c0f2","name":"63eee8ef-0220-4089-9be9-13a9839cae17:kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"},{"id":"f60ebe00-4e0f-11ec-afa1-59364130c0f2","name":"controlGroup_ec64cb73-f891-4f48-bfa5-e3ca0df9920e:optionsListDataView","type":"index-pattern"},{"id":"f60ebe00-4e0f-11ec-afa1-59364130c0f2","name":"controlGroup_3b343fbe-c0a0-4bc5-825e-7c4fff42ad8f:optionsListDataView","type":"index-pattern"}],"type":"dashboard","updated_at":"2021-11-25T17:48:02.981Z","version":"WzM5LDJd"} +{"excludedObjects":[],"excludedObjectsCount":0,"exportedCount":2,"missingRefCount":0,"missingReferences":[]} \ No newline at end of file diff --git a/x-pack/test/functional/apps/ml/data_frame_analytics/outlier_detection_creation.ts b/x-pack/test/functional/apps/ml/data_frame_analytics/outlier_detection_creation.ts index 384d6fa43cac8..7ce3fcdc51e1b 100644 --- a/x-pack/test/functional/apps/ml/data_frame_analytics/outlier_detection_creation.ts +++ b/x-pack/test/functional/apps/ml/data_frame_analytics/outlier_detection_creation.ts @@ -63,13 +63,13 @@ export default function ({ getService }: FtrProviderContext) { // markers { color: '#52B398', percentage: 15 }, // grey boilerplate - { color: '#6A717D', percentage: 33 }, + { color: '#6A717D', percentage: 13 }, ], scatterplotMatrixColorStatsResults: [ // red markers { color: '#D98071', percentage: 1 }, // tick/grid/axis, grey markers - { color: '#6A717D', percentage: 33 }, + { color: '#6A717D', percentage: 12 }, { color: '#D3DAE6', percentage: 8 }, { color: '#98A1B3', percentage: 12 }, // anti-aliasing diff --git a/x-pack/test/functional/apps/ml/data_frame_analytics/regression_creation.ts b/x-pack/test/functional/apps/ml/data_frame_analytics/regression_creation.ts index 66d0ba0ec6e36..d2a9554f3c6d6 100644 --- a/x-pack/test/functional/apps/ml/data_frame_analytics/regression_creation.ts +++ b/x-pack/test/functional/apps/ml/data_frame_analytics/regression_creation.ts @@ -53,8 +53,8 @@ export default function ({ getService }: FtrProviderContext) { { color: '#61AFA3', percentage: 2 }, { color: '#D1E5E0', percentage: 2 }, // tick/grid/axis - { color: '#6A717D', percentage: 10 }, - { color: '#F5F7FA', percentage: 10 }, + { color: '#6A717D', percentage: 5 }, + { color: '#F5F7FA', percentage: 5 }, { color: '#D3DAE6', percentage: 3 }, ], runtimeFieldsEditorContent: ['{', ' "uppercase_stab": {', ' "type": "keyword",'], diff --git a/x-pack/test/functional/apps/transform/creation_index_pattern.ts b/x-pack/test/functional/apps/transform/creation_index_pattern.ts index 49211deaa2c42..735efec4a3c66 100644 --- a/x-pack/test/functional/apps/transform/creation_index_pattern.ts +++ b/x-pack/test/functional/apps/transform/creation_index_pattern.ts @@ -181,7 +181,7 @@ export default function ({ getService }: FtrProviderContext) { legend: 'top 20 of 46 categories', colorStats: [ { color: '#000000', percentage: 60 }, - { color: '#54B399', percentage: 35 }, + { color: '#54B399', percentage: 37 }, ], }, { @@ -190,7 +190,7 @@ export default function ({ getService }: FtrProviderContext) { legend: 'top 20 of 3321 categories', colorStats: [ { color: '#000000', percentage: 25 }, - { color: '#54B399', percentage: 67 }, + { color: '#54B399', percentage: 75 }, ], }, { @@ -207,7 +207,7 @@ export default function ({ getService }: FtrProviderContext) { id: 'customer_id', legend: 'top 20 of 46 categories', colorStats: [ - { color: '#54B399', percentage: 35 }, + { color: '#54B399', percentage: 37 }, { color: '#000000', percentage: 60 }, ], }, @@ -216,8 +216,8 @@ export default function ({ getService }: FtrProviderContext) { id: 'customer_last_name', legend: 'top 20 of 183 categories', colorStats: [ - { color: '#000000', percentage: 25 }, - { color: '#54B399', percentage: 70 }, + { color: '#000000', percentage: 23 }, + { color: '#54B399', percentage: 77 }, ], }, { diff --git a/x-pack/test/functional/apps/transform/creation_runtime_mappings.ts b/x-pack/test/functional/apps/transform/creation_runtime_mappings.ts index 30baeb9022833..bbcfe91b54a7d 100644 --- a/x-pack/test/functional/apps/transform/creation_runtime_mappings.ts +++ b/x-pack/test/functional/apps/transform/creation_runtime_mappings.ts @@ -69,7 +69,7 @@ export default function ({ getService }: FtrProviderContext) { legend: '19 categories', colorStats: [ { color: '#000000', percentage: 49 }, - { color: '#54B399', percentage: 41 }, + { color: '#54B399', percentage: 50 }, ], }, { @@ -87,7 +87,7 @@ export default function ({ getService }: FtrProviderContext) { legend: '19 categories', colorStats: [ { color: '#000000', percentage: 49 }, - { color: '#54B399', percentage: 41 }, + { color: '#54B399', percentage: 50 }, ], }, { diff --git a/x-pack/test/functional/apps/uptime/synthetics_integration.ts b/x-pack/test/functional/apps/uptime/synthetics_integration.ts index 49f5895bd98f8..ae7edf3524d7d 100644 --- a/x-pack/test/functional/apps/uptime/synthetics_integration.ts +++ b/x-pack/test/functional/apps/uptime/synthetics_integration.ts @@ -53,6 +53,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { id: `${getSyntheticsPolicy(agentFullPolicy)?.streams?.[0]?.id}`, name, type: monitorType, + enabled: true, processors: [ { add_observer_metadata: { diff --git a/x-pack/test/functional/services/canvas_element.ts b/x-pack/test/functional/services/canvas_element.ts index 6167c8fab45f8..61bfe23950ee2 100644 --- a/x-pack/test/functional/services/canvas_element.ts +++ b/x-pack/test/functional/services/canvas_element.ts @@ -7,6 +7,8 @@ import { rgb, nest } from 'd3'; +import { FtrProviderContext } from '../ftr_provider_context'; + interface ColorStat { color: string; percentage: number; @@ -16,7 +18,9 @@ interface ColorStat { export type CanvasElementColorStats = ColorStat[]; -import { FtrProviderContext } from '../ftr_provider_context'; +function getRoundedChannel(value: number, tolerance: number): number { + return Math.round(value / tolerance) * tolerance; +} export async function CanvasElementProvider({ getService }: FtrProviderContext) { const { driver } = await getService('__webdriver__').init(); @@ -87,9 +91,9 @@ export async function CanvasElementProvider({ getService }: FtrProviderContext) const colors: string[] = []; for (let i = 0; i < imageData.length; i += 4) { // uses d3's `rgb` method create a color object, `toString()` returns the hex value - const r = imageData[i]; - const g = imageData[i + 1]; - const b = imageData[i + 2]; + const r = getRoundedChannel(imageData[i], channelTolerance); + const g = getRoundedChannel(imageData[i + 1], channelTolerance); + const b = getRoundedChannel(imageData[i + 2], channelTolerance); const color = rgb(r, g, b).toString().toUpperCase(); if (exclude === undefined || !exclude.includes(color)) colors.push(color); } @@ -196,20 +200,13 @@ export async function CanvasElementProvider({ getService }: FtrProviderContext) const actualRGB = rgb(actualColor); const expectedRGB = rgb(expectedColor); - const lowerR = expectedRGB.r - toleranceRange / 2; - const upperR = expectedRGB.r + toleranceRange / 2; - const lowerG = expectedRGB.g - toleranceRange / 2; - const upperG = expectedRGB.g + toleranceRange / 2; - const lowerB = expectedRGB.b - toleranceRange / 2; - const upperB = expectedRGB.b + toleranceRange / 2; - return ( - lowerR <= actualRGB.r && - upperR >= actualRGB.r && - lowerG <= actualRGB.g && - upperG >= actualRGB.g && - lowerB <= actualRGB.b && - upperB >= actualRGB.b + getRoundedChannel(expectedRGB.r, toleranceRange) === + getRoundedChannel(actualRGB.r, toleranceRange) && + getRoundedChannel(expectedRGB.g, toleranceRange) === + getRoundedChannel(actualRGB.g, toleranceRange) && + getRoundedChannel(expectedRGB.b, toleranceRange) === + getRoundedChannel(actualRGB.b, toleranceRange) ); } diff --git a/x-pack/test/reporting_api_integration/reporting_and_security.config.ts b/x-pack/test/reporting_api_integration/reporting_and_security.config.ts index b5f75ed7d501c..3574ee5b8b2b1 100644 --- a/x-pack/test/reporting_api_integration/reporting_and_security.config.ts +++ b/x-pack/test/reporting_api_integration/reporting_and_security.config.ts @@ -33,7 +33,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...apiConfig.get('kbnTestServer'), serverArgs: [ ...apiConfig.get('kbnTestServer.serverArgs'), - `--xpack.reporting.capture.networkPolicy.rules=${JSON.stringify(testPolicyRules)}`, + `--xpack.screenshotting.networkPolicy.rules=${JSON.stringify(testPolicyRules)}`, `--xpack.reporting.capture.maxAttempts=1`, `--xpack.reporting.csv.maxSizeBytes=6000`, '--xpack.reporting.roles.enabled=false', // Reporting access control is implemented by sub-feature application privileges diff --git a/x-pack/test/security_solution_cypress/es_archives/risky_hosts/data.json b/x-pack/test/security_solution_cypress/es_archives/risky_hosts/data.json index e42a13ab8d8a8..e6187d1f7e0a6 100644 --- a/x-pack/test/security_solution_cypress/es_archives/risky_hosts/data.json +++ b/x-pack/test/security_solution_cypress/es_archives/risky_hosts/data.json @@ -5,17 +5,18 @@ "index":"ml_host_risk_score_latest_default", "source":{ "@timestamp":"2021-03-10T14:51:05.766Z", - "risk_score":21, + "risk_stats": { + "risk_score": 21, + "rule_risks": [ + { + "rule_name": "Unusual Linux Username", + "rule_risk": 42 + } + ] + }, "host":{ "name":"ip-10-10-10-121" }, - "rules":{ - "Unusual Linux Username":{ - "average_risk":21, - "rule_count":2, - "rule_risk":42 - } - }, "ingest_timestamp":"2021-03-09T18:02:08.319296053Z", "risk":"Low" } diff --git a/x-pack/test/security_solution_cypress/es_archives/risky_hosts/mappings.json b/x-pack/test/security_solution_cypress/es_archives/risky_hosts/mappings.json index f71c9cf8ed4c2..2738d85d8b3af 100644 --- a/x-pack/test/security_solution_cypress/es_archives/risky_hosts/mappings.json +++ b/x-pack/test/security_solution_cypress/es_archives/risky_hosts/mappings.json @@ -32,8 +32,12 @@ } } }, - "risk_score": { - "type": "long" + "risk_stats": { + "properties": { + "risk_score": { + "type": "long" + } + } } } }, diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/data_stream_helper.ts b/x-pack/test/security_solution_endpoint_api_int/apis/data_stream_helper.ts index dc4b4113c6b11..63e9430679f80 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/data_stream_helper.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/data_stream_helper.ts @@ -119,10 +119,12 @@ export async function startTransform( const transformsResponse = await client.transform.getTransform({ transform_id: `${transformId}*`, }); - return transformsResponse.transforms.map((transform) => { - const t = transform as unknown as { id: string }; - return client.transform.startTransform({ transform_id: t.id }); - }); + return Promise.all( + transformsResponse.transforms.map((transform) => { + const t = transform as unknown as { id: string }; + return client.transform.startTransform({ transform_id: t.id }); + }) + ); } export function bulkIndex( diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts b/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts index 93f3756fc111c..b0aaf71ef3257 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts @@ -7,6 +7,7 @@ import uuid from 'uuid'; import expect from '@kbn/expect'; +import { TransformGetTransformStatsTransformStats } from '@elastic/elasticsearch/lib/api/types'; import { FtrProviderContext } from '../ftr_provider_context'; import { deleteAllDocsFromMetadataCurrentIndex, @@ -24,10 +25,13 @@ import { HOST_METADATA_LIST_ROUTE, METADATA_UNITED_INDEX, METADATA_UNITED_TRANSFORM, + METADATA_TRANSFORMS_STATUS_ROUTE, + metadataTransformPrefix, } from '../../../plugins/security_solution/common/endpoint/constants'; import { AGENTS_INDEX } from '../../../plugins/fleet/common'; import { generateAgentDocs, generateMetadataDocs } from './metadata.fixtures'; import { indexFleetEndpointPolicy } from '../../../plugins/security_solution/common/endpoint/data_loaders/index_fleet_endpoint_policy'; +import { TRANSFORM_STATES } from '../../../plugins/security_solution/common/constants'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); @@ -71,7 +75,6 @@ export default function ({ getService }: FtrProviderContext) { await deleteAllDocsFromFleetAgents(getService); await deleteAllDocsFromMetadataDatastream(getService); await deleteAllDocsFromMetadataCurrentIndex(getService); - await stopTransform(getService, `${METADATA_UNITED_TRANSFORM}*`); await deleteAllDocsFromIndex(getService, METADATA_UNITED_INDEX); }); @@ -504,5 +507,62 @@ export default function ({ getService }: FtrProviderContext) { }); }); }); + + describe('get metadata transforms', () => { + it('should respond forbidden if no fleet access', async () => { + await getService('supertestWithoutAuth') + .get(METADATA_TRANSFORMS_STATUS_ROUTE) + .set('kbn-xsrf', 'xxx') + .expect(401); + }); + + it('correctly returns stopped transform stats', async () => { + await stopTransform(getService, `${metadataTransformPrefix}*`); + await stopTransform(getService, `${METADATA_UNITED_TRANSFORM}*`); + + const { body } = await supertest + .get(METADATA_TRANSFORMS_STATUS_ROUTE) + .set('kbn-xsrf', 'xxx') + .expect(200); + + expect(body.count).to.eql(2); + + const transforms: TransformGetTransformStatsTransformStats[] = body.transforms.sort( + ( + a: TransformGetTransformStatsTransformStats, + b: TransformGetTransformStatsTransformStats + ) => a.id > b.id + ); + + expect(transforms[0].id).to.contain(metadataTransformPrefix); + expect(transforms[0].state).to.eql(TRANSFORM_STATES.STOPPED); + expect(transforms[1].id).to.contain(METADATA_UNITED_TRANSFORM); + expect(transforms[1].state).to.eql(TRANSFORM_STATES.STOPPED); + + await startTransform(getService, metadataTransformPrefix); + await startTransform(getService, METADATA_UNITED_TRANSFORM); + }); + + it('correctly returns started transform stats', async () => { + const { body } = await supertest + .get(METADATA_TRANSFORMS_STATUS_ROUTE) + .set('kbn-xsrf', 'xxx') + .expect(200); + + expect(body.count).to.eql(2); + + const transforms: TransformGetTransformStatsTransformStats[] = body.transforms.sort( + ( + a: TransformGetTransformStatsTransformStats, + b: TransformGetTransformStatsTransformStats + ) => a.id > b.id + ); + + expect(transforms[0].id).to.contain(metadataTransformPrefix); + expect(transforms[0].state).to.eql(TRANSFORM_STATES.STARTED); + expect(transforms[1].id).to.contain(METADATA_UNITED_TRANSFORM); + expect(transforms[1].state).to.eql(TRANSFORM_STATES.STARTED); + }); + }); }); } diff --git a/yarn.lock b/yarn.lock index 86eb774c84d4e..e838d0ec297a3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -137,7 +137,7 @@ jsesc "^2.5.1" source-map "^0.5.0" -"@babel/helper-annotate-as-pure@^7.0.0", "@babel/helper-annotate-as-pure@^7.15.4", "@babel/helper-annotate-as-pure@^7.16.0": +"@babel/helper-annotate-as-pure@^7.0.0", "@babel/helper-annotate-as-pure@^7.16.0": version "7.16.0" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.0.tgz#9a1f0ebcda53d9a2d00108c4ceace6a5d5f1f08d" integrity sha512-ItmYF9vR4zA8cByDocY05o0LGUkp1zhbTQOH1NFyl5xXEqlTJQCEJjieriw+aFpxo16swMxUnUiKS7a/r4vtHg== @@ -247,7 +247,7 @@ dependencies: "@babel/types" "^7.16.0" -"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.15.4", "@babel/helper-module-imports@^7.16.0", "@babel/helper-module-imports@^7.7.0": +"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.16.0", "@babel/helper-module-imports@^7.7.0": version "7.16.0" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.0.tgz#90538e60b672ecf1b448f5f4f5433d37e79a3ec3" integrity sha512-kkH7sWzKPq0xt3H1n+ghb4xEMP8k0U7XV3kkB+ZGy69kDk2ySFW1qPi06sjKzFY3t1j6XbJSqr4mF9L7CYVyhg== @@ -5850,6 +5850,10 @@ version "0.0.0" uid "" +"@types/kbn__docs-utils@link:bazel-bin/packages/kbn-docs-utils/npm_module_types": + version "0.0.0" + uid "" + "@types/kbn__i18n-react@link:bazel-bin/packages/kbn-i18n-react/npm_module_types": version "0.0.0" uid "" @@ -8334,13 +8338,13 @@ babel-plugin-require-context-hook@^1.0.0: babel-plugin-syntax-jsx "^6.18.0" lodash "^4.17.11" -babel-plugin-styled-components@^1.13.3: - version "1.13.3" - resolved "https://registry.yarnpkg.com/babel-plugin-styled-components/-/babel-plugin-styled-components-1.13.3.tgz#1f1cb3927d4afa1e324695c78f690900e3d075bc" - integrity sha512-meGStRGv+VuKA/q0/jXxrPNWEm4LPfYIqxooDTdmh8kFsP/Ph7jJG5rUPwUPX3QHUvggwdbgdGpo88P/rRYsVw== +babel-plugin-styled-components@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.2.tgz#0fac11402dc9db73698b55847ab1dc73f5197c54" + integrity sha512-7eG5NE8rChnNTDxa6LQfynwgHTVOYYaHJbUYSlOhk8QBXIQiMBKq4gyfHBBKPrxUcVBXVJL61ihduCpCQbuNbw== dependencies: - "@babel/helper-annotate-as-pure" "^7.15.4" - "@babel/helper-module-imports" "^7.15.4" + "@babel/helper-annotate-as-pure" "^7.16.0" + "@babel/helper-module-imports" "^7.16.0" babel-plugin-syntax-jsx "^6.18.0" lodash "^4.17.11" @@ -8723,16 +8727,17 @@ broadcast-channel@^3.4.1: rimraf "3.0.2" unload "2.2.0" -broadcast-channel@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-4.5.0.tgz#d4717c493e219908fcb7f2f9078fe0baf95b77c1" - integrity sha512-jp+VPlQ1HyR0CM3uIYUrdpXupBvhTMFRkjR6mEmt5W4HaGDPFEzrO2Jqvi2PZ6zCC4zwLeco7CC5EUJPrVH8Tw== +broadcast-channel@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-4.7.0.tgz#4f5c31982f627eae4ffe463623ba36a9e7da1992" + integrity sha512-1C7wDPqeiKkwpScqFP044MsPAtxxDNKZzOnJmkHaTuOlUdaMLo11op56NrCOMiRh8dzktstcNsiHELGeTMKnNQ== dependencies: "@babel/runtime" "^7.16.0" detect-node "^2.1.0" microseconds "0.2.0" nano-time "1.0.0" oblivious-set "1.0.0" + p-queue "6.6.2" rimraf "3.0.2" unload "2.3.1" @@ -13333,7 +13338,7 @@ eventemitter2@^6.4.3: resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.3.tgz#35c563619b13f3681e7eb05cbdaf50f56ba58820" integrity sha512-t0A2msp6BzOf+QAcI6z9XMktLj52OjGQg+8SJH6v5+3uxNpWYRR3wQmfA+6xtMU9kOC59qk9licus5dYcrYkMQ== -eventemitter3@^4.0.0: +eventemitter3@^4.0.0, eventemitter3@^4.0.4: version "4.0.7" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== @@ -21120,6 +21125,14 @@ p-map@^4.0.0: dependencies: aggregate-error "^3.0.0" +p-queue@6.6.2: + version "6.6.2" + resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426" + integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ== + dependencies: + eventemitter3 "^4.0.4" + p-timeout "^3.2.0" + p-retry@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-3.0.1.tgz#316b4c8893e2c8dc1cfa891f406c4b422bebf328" @@ -21142,6 +21155,13 @@ p-timeout@^2.0.1: dependencies: p-finally "^1.0.0" +p-timeout@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" + integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== + dependencies: + p-finally "^1.0.0" + p-try@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3"