diff --git a/docs/canvas/canvas-elements.asciidoc b/docs/canvas/canvas-elements.asciidoc index 85f0ee1150ba0..163579d5763b2 100644 --- a/docs/canvas/canvas-elements.asciidoc +++ b/docs/canvas/canvas-elements.asciidoc @@ -23,7 +23,7 @@ By default, most of the elements you create use demo data until you change the d * *{es} SQL* — Access your data in {es} using SQL syntax. For information about SQL syntax, refer to {ref}/sql-spec.html[SQL language]. -* *{es} raw data* — Access your raw data in {es} without the use of aggregations. Use {es} raw data when you have low volume datasets, or to plot exact, non-aggregated values. +* *{es} documents* — Access your data in {es} without using aggregations. To use, select an index and fields, and optionally enter a query using the <>. Use the *{es} documents* data source when you have low volume datasets, to view raw documents, or to plot exact, non-aggregated values on a chart. * *Timelion* — Access your time series data using <> queries. To use Timelion queries, you can enter a query using the <>. diff --git a/examples/alerting_example/tsconfig.json b/examples/alerting_example/tsconfig.json index 078522b36cb12..fbcec9de439bd 100644 --- a/examples/alerting_example/tsconfig.json +++ b/examples/alerting_example/tsconfig.json @@ -1,9 +1,7 @@ { "extends": "../../tsconfig.json", "compilerOptions": { - "outDir": "./target", - "skipLibCheck": true, - "resolveJsonModule": true + "outDir": "./target" }, "include": [ "index.ts", diff --git a/packages/elastic-datemath/README.md b/packages/elastic-datemath/README.md new file mode 100644 index 0000000000000..a8dcd9f6721cb --- /dev/null +++ b/packages/elastic-datemath/README.md @@ -0,0 +1,5 @@ +# datemath + +Datemath string parser used in Kibana. This is published to NPM for use in a limited number of locations outside of Kibana, but is not regularly updated and may get seriously out of date. + +If you file an issue in elastic/kibana we can probably update it for you if needed, though you probably shouldn't depend on this package for anything important. diff --git a/packages/elastic-datemath/readme.md b/packages/elastic-datemath/readme.md deleted file mode 100644 index f7de9627e6d69..0000000000000 --- a/packages/elastic-datemath/readme.md +++ /dev/null @@ -1,3 +0,0 @@ -# datemath - -Datemath string parser used in Kibana diff --git a/src/dev/ci_setup/setup.sh b/src/dev/ci_setup/setup.sh index 104a818f72a20..dc91d1cf23a37 100755 --- a/src/dev/ci_setup/setup.sh +++ b/src/dev/ci_setup/setup.sh @@ -16,6 +16,14 @@ echo " -- TEST_ES_SNAPSHOT_VERSION='$TEST_ES_SNAPSHOT_VERSION'" echo " -- installing node.js dependencies" yarn kbn bootstrap --prefer-offline +### +### Download es snapshots +### +echo " -- downloading es snapshot" +node scripts/es snapshot --download-only; +node scripts/es snapshot --license=oss --download-only; + + ### ### verify no git modifications ### diff --git a/test/scripts/jenkins_build_kibana.sh b/test/scripts/jenkins_build_kibana.sh index a7c05b6e5802d..1f6e09fad19e9 100755 --- a/test/scripts/jenkins_build_kibana.sh +++ b/test/scripts/jenkins_build_kibana.sh @@ -11,9 +11,6 @@ node scripts/build_kibana_platform_plugins \ # doesn't persist, also set in kibanaPipeline.groovy export KBN_NP_PLUGINS_BUILT=true -echo " -> downloading es snapshot" -node scripts/es snapshot --license=oss --download-only; - echo " -> Ensuring all functional tests are in a ciGroup" yarn run grunt functionalTests:ensureAllTestsInCiGroup; diff --git a/test/scripts/jenkins_xpack_build_kibana.sh b/test/scripts/jenkins_xpack_build_kibana.sh index 2bf9d2d9c158b..777d98080e407 100755 --- a/test/scripts/jenkins_xpack_build_kibana.sh +++ b/test/scripts/jenkins_xpack_build_kibana.sh @@ -12,9 +12,6 @@ node scripts/build_kibana_platform_plugins \ # doesn't persist, also set in kibanaPipeline.groovy export KBN_NP_PLUGINS_BUILT=true -echo " -> downloading es snapshot" -node scripts/es snapshot --download-only; - echo " -> Ensuring all functional tests are in a ciGroup" cd "$XPACK_DIR" node scripts/functional_tests --assert-none-excluded \ diff --git a/typings/index.d.ts b/typings/index.d.ts index 1c58a92a046df..6d97aca4024c3 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -23,6 +23,27 @@ declare module '*.html' { export default template; } +declare module '*.png' { + const content: string; + // eslint-disable-next-line import/no-default-export + export default content; +} + +declare module '*.svg' { + const content: string; + // eslint-disable-next-line import/no-default-export + export default content; +} + +// allow JSON files to be imported directly without lint errors +// see: https://github.com/palantir/tslint/issues/1264#issuecomment-228433367 +// and: https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#arbitrary-expressions-are-forbidden-in-export-assignments-in-ambient-contexts +declare module '*.json' { + const json: any; + // eslint-disable-next-line import/no-default-export + export default json; +} + type MethodKeysOf = { [K in keyof T]: T[K] extends (...args: any[]) => any ? K : never; }[keyof T]; @@ -37,3 +58,7 @@ type DeeplyMockedKeys = { : DeeplyMockedKeys; } & T; + +type Writable = { + -readonly [K in keyof T]: T[K]; +}; diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index 2a28e349ace99..ae8d61769b14c 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -16,7 +16,7 @@ "xpack.features": "plugins/features", "xpack.fileUpload": "plugins/file_upload", "xpack.graph": ["legacy/plugins/graph", "plugins/graph"], - "xpack.grokDebugger": "legacy/plugins/grokdebugger", + "xpack.grokDebugger": "plugins/grokdebugger", "xpack.idxMgmt": "plugins/index_management", "xpack.indexLifecycleMgmt": "legacy/plugins/index_lifecycle_management", "xpack.infra": "plugins/infra", diff --git a/x-pack/index.js b/x-pack/index.js index fb14b3dc10a4d..6fab13d726fa6 100644 --- a/x-pack/index.js +++ b/x-pack/index.js @@ -10,7 +10,6 @@ import { monitoring } from './legacy/plugins/monitoring'; import { reporting } from './legacy/plugins/reporting'; import { security } from './legacy/plugins/security'; import { tilemap } from './legacy/plugins/tilemap'; -import { grokdebugger } from './legacy/plugins/grokdebugger'; import { dashboardMode } from './legacy/plugins/dashboard_mode'; import { logstash } from './legacy/plugins/logstash'; import { beats } from './legacy/plugins/beats_management'; @@ -44,7 +43,6 @@ module.exports = function(kibana) { spaces(kibana), security(kibana), tilemap(kibana), - grokdebugger(kibana), dashboardMode(kibana), logstash(kibana), beats(kibana), diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/index.tsx index 77ae67b71e1b6..c3d426a6275a7 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/index.tsx @@ -28,8 +28,10 @@ export function ServiceDetails({ tab }: Props) { const canSaveAlerts = !!plugin.core.application.capabilities.apm[ 'alerting:save' ]; + const isAlertingPluginEnabled = 'alerting' in plugin.plugins; - const isAlertingAvailable = canReadAlerts || canSaveAlerts; + const isAlertingAvailable = + isAlertingPluginEnabled && (canReadAlerts || canSaveAlerts); return (
diff --git a/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx b/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx index a291678e9a20c..80a45ba66c4fa 100644 --- a/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx +++ b/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx @@ -4,48 +4,49 @@ * you may not use this file except in compliance with the Elastic License. */ +import { ApmRoute } from '@elastic/apm-rum-react'; +import { i18n } from '@kbn/i18n'; import React from 'react'; import ReactDOM from 'react-dom'; import { Route, Router, Switch } from 'react-router-dom'; -import { ApmRoute } from '@elastic/apm-rum-react'; import styled from 'styled-components'; -import { i18n } from '@kbn/i18n'; -import { AlertType } from '../../../../../plugins/apm/common/alert_types'; import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '../../../../../../src/core/public'; -import { featureCatalogueEntry } from './featureCatalogueEntry'; import { DataPublicPluginSetup } from '../../../../../../src/plugins/data/public'; import { HomePublicPluginSetup } from '../../../../../../src/plugins/home/public'; +import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; +import { PluginSetupContract as AlertingPluginPublicSetup } from '../../../../../plugins/alerting/public'; +import { AlertType } from '../../../../../plugins/apm/common/alert_types'; import { LicensingPluginSetup } from '../../../../../plugins/licensing/public'; +import { + AlertsContextProvider, + TriggersAndActionsUIPublicPluginSetup +} from '../../../../../plugins/triggers_actions_ui/public'; +import { APMIndicesPermission } from '../components/app/APMIndicesPermission'; import { routes } from '../components/app/Main/route_config'; import { ScrollToTopOnPathChange } from '../components/app/Main/ScrollToTopOnPathChange'; import { UpdateBreadcrumbs } from '../components/app/Main/UpdateBreadcrumbs'; +import { ErrorRateAlertTrigger } from '../components/shared/ErrorRateAlertTrigger'; +import { TransactionDurationAlertTrigger } from '../components/shared/TransactionDurationAlertTrigger'; import { ApmPluginContext } from '../context/ApmPluginContext'; import { LicenseProvider } from '../context/LicenseContext'; import { LoadingIndicatorProvider } from '../context/LoadingIndicatorContext'; import { LocationProvider } from '../context/LocationContext'; import { MatchedRouteProvider } from '../context/MatchedRouteContext'; import { UrlParamsProvider } from '../context/UrlParamsContext'; +import { createCallApmApi } from '../services/rest/createCallApmApi'; import { createStaticIndexPattern } from '../services/rest/index_pattern'; import { px, unit, units } from '../style/variables'; import { history } from '../utils/history'; +import { featureCatalogueEntry } from './featureCatalogueEntry'; import { getConfigFromInjectedMetadata } from './getConfigFromInjectedMetadata'; import { setHelpExtension } from './setHelpExtension'; import { toggleAppLinkInNav } from './toggleAppLinkInNav'; import { setReadonlyBadge } from './updateBadge'; -import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; -import { APMIndicesPermission } from '../components/app/APMIndicesPermission'; -import { - TriggersAndActionsUIPublicPluginSetup, - AlertsContextProvider -} from '../../../../../plugins/triggers_actions_ui/public'; -import { ErrorRateAlertTrigger } from '../components/shared/ErrorRateAlertTrigger'; -import { TransactionDurationAlertTrigger } from '../components/shared/TransactionDurationAlertTrigger'; -import { createCallApmApi } from '../services/rest/createCallApmApi'; export const REACT_APP_ROOT_ID = 'react-apm-root'; @@ -75,6 +76,7 @@ export type ApmPluginSetup = void; export type ApmPluginStart = void; export interface ApmPluginSetupDeps { + alerting?: AlertingPluginPublicSetup; data: DataPublicPluginSetup; home: HomePublicPluginSetup; licensing: LicensingPluginSetup; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/metric/index.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/metric/index.ts index def16f2a4b23a..c08c090f11f91 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/metric/index.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/metric/index.ts @@ -6,7 +6,7 @@ import { openSans } from '../../../common/lib/fonts'; import header from './header.png'; -import { AdvancedSettings } from '../../../public/lib/kibana_advanced_settings'; +import { getAdvancedSettings } from '../../../public/lib/kibana_advanced_settings'; import { ElementFactory } from '../../../types'; export const metric: ElementFactory = () => ({ @@ -23,6 +23,6 @@ export const metric: ElementFactory = () => ({ | metric "Countries" metricFont={font size=48 family="${openSans.value}" color="#000000" align="center" lHeight=48} labelFont={font size=14 family="${openSans.value}" color="#000000" align="center"} - metricFormat="${AdvancedSettings.get('format:number:defaultPattern')}" + metricFormat="${getAdvancedSettings().get('format:number:defaultPattern')}" | render`, }); diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/index.tsx b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/index.tsx index e2e9358bf99c6..55a453720e2f0 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/index.tsx +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/index.tsx @@ -5,11 +5,11 @@ */ import React from 'react'; -import { AdvancedSettings } from '../../../../public/lib/kibana_advanced_settings'; +import { getAdvancedSettings } from '../../../../public/lib/kibana_advanced_settings'; import { TimeFilter as Component, Props } from './time_filter'; export const TimeFilter = (props: Props) => { - const customQuickRanges = (AdvancedSettings.get('timepicker:quickRanges') || []).map( + const customQuickRanges = (getAdvancedSettings().get('timepicker:quickRanges') || []).map( ({ from, to, display }: { from: string; to: string; display: string }) => ({ start: from, end: to, @@ -17,7 +17,7 @@ export const TimeFilter = (props: Props) => { }) ); - const customDateFormat = AdvancedSettings.get('dateFormat'); + const customDateFormat = getAdvancedSettings().get('dateFormat'); return ( diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/date_format/index.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/date_format/index.ts index cc49943832d07..d19bfa64bae76 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/date_format/index.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/date_format/index.ts @@ -7,7 +7,7 @@ import { compose, withProps } from 'recompose'; import moment from 'moment'; import { DateFormatArgInput as Component, Props as ComponentProps } from './date_format'; -import { AdvancedSettings } from '../../../../public/lib/kibana_advanced_settings'; +import { getAdvancedSettings } from '../../../../public/lib/kibana_advanced_settings'; // @ts-ignore untyped local lib import { templateFromReactComponent } from '../../../../public/lib/template_from_react_component'; import { ArgumentFactory } from '../../../../types/arguments'; @@ -15,19 +15,19 @@ import { ArgumentStrings } from '../../../../i18n'; const { DateFormat: strings } = ArgumentStrings; -const formatMap = { - DEFAULT: AdvancedSettings.get('dateFormat'), - NANOS: AdvancedSettings.get('dateNanosFormat'), +const getFormatMap = () => ({ + DEFAULT: getAdvancedSettings().get('dateFormat'), + NANOS: getAdvancedSettings().get('dateNanosFormat'), ISO8601: '', LOCAL_LONG: 'LLLL', LOCAL_SHORT: 'LLL', LOCAL_DATE: 'l', LOCAL_TIME_WITH_SECONDS: 'LTS', -}; +}); const now = moment(); -const dateFormats = Object.values(formatMap).map(format => ({ +const dateFormats = Object.values(getFormatMap()).map(format => ({ value: format, text: moment.utc(now).format(format), })); diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/index.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/index.ts index 7654774901ff0..ce6c90c89a5a0 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/index.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/index.ts @@ -6,7 +6,7 @@ import { compose, withProps } from 'recompose'; import { NumberFormatArgInput as Component, Props as ComponentProps } from './number_format'; -import { AdvancedSettings } from '../../../../public/lib/kibana_advanced_settings'; +import { getAdvancedSettings } from '../../../../public/lib/kibana_advanced_settings'; // @ts-ignore untyped local lib import { templateFromReactComponent } from '../../../../public/lib/template_from_react_component'; import { ArgumentFactory } from '../../../../types/arguments'; @@ -14,25 +14,28 @@ import { ArgumentStrings } from '../../../../i18n'; const { NumberFormat: strings } = ArgumentStrings; -const formatMap = { - NUMBER: AdvancedSettings.get('format:number:defaultPattern'), - PERCENT: AdvancedSettings.get('format:percent:defaultPattern'), - CURRENCY: AdvancedSettings.get('format:currency:defaultPattern'), +const getFormatMap = () => ({ + NUMBER: getAdvancedSettings().get('format:number:defaultPattern'), + PERCENT: getAdvancedSettings().get('format:percent:defaultPattern'), + CURRENCY: getAdvancedSettings().get('format:currency:defaultPattern'), DURATION: '00:00:00', - BYTES: AdvancedSettings.get('format:bytes:defaultPattern'), -}; + BYTES: getAdvancedSettings().get('format:bytes:defaultPattern'), +}); -const numberFormats = [ - { value: formatMap.NUMBER, text: strings.getFormatNumber() }, - { value: formatMap.PERCENT, text: strings.getFormatPercent() }, - { value: formatMap.CURRENCY, text: strings.getFormatCurrency() }, - { value: formatMap.DURATION, text: strings.getFormatDuration() }, - { value: formatMap.BYTES, text: strings.getFormatBytes() }, -]; +const getNumberFormats = () => { + const formatMap = getFormatMap(); + return [ + { value: formatMap.NUMBER, text: strings.getFormatNumber() }, + { value: formatMap.PERCENT, text: strings.getFormatPercent() }, + { value: formatMap.CURRENCY, text: strings.getFormatCurrency() }, + { value: formatMap.DURATION, text: strings.getFormatDuration() }, + { value: formatMap.BYTES, text: strings.getFormatBytes() }, + ]; +}; -export const NumberFormatArgInput = compose(withProps({ numberFormats }))( - Component -); +export const NumberFormatArgInput = compose( + withProps({ numberFormats: getNumberFormats() }) +)(Component); export const numberFormat: ArgumentFactory = () => ({ name: 'numberFormat', diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/views/metric.js b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/views/metric.js index 33cdb5541e172..e69f8f1de5952 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/views/metric.js +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/views/metric.js @@ -5,7 +5,7 @@ */ import { openSans } from '../../../common/lib/fonts'; -import { AdvancedSettings } from '../../../public/lib/kibana_advanced_settings'; +import { getAdvancedSettings } from '../../../public/lib/kibana_advanced_settings'; import { ViewStrings } from '../../../i18n'; const { Metric: strings } = ViewStrings; @@ -21,7 +21,7 @@ export const metric = () => ({ displayName: strings.getMetricFormatDisplayName(), help: strings.getMetricFormatHelp(), argType: 'numberFormat', - default: `"${AdvancedSettings.get('format:number:defaultPattern')}"`, + default: `"${getAdvancedSettings().get('format:number:defaultPattern')}"`, }, { name: '_', diff --git a/x-pack/legacy/plugins/canvas/i18n/elements/element_strings.test.ts b/x-pack/legacy/plugins/canvas/i18n/elements/element_strings.test.ts index a946fa87a58b3..3d835bdf31bf8 100644 --- a/x-pack/legacy/plugins/canvas/i18n/elements/element_strings.test.ts +++ b/x-pack/legacy/plugins/canvas/i18n/elements/element_strings.test.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - +jest.mock('ui/new_platform'); import { getElementStrings } from './element_strings'; import { elementSpecs } from '../../canvas_plugin_src/elements'; diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_loader/workpad_loader.js b/x-pack/legacy/plugins/canvas/public/components/workpad_loader/workpad_loader.js index f8d6fb0bd76ce..9b30b3e1ec7ca 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_loader/workpad_loader.js +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_loader/workpad_loader.js @@ -25,7 +25,7 @@ import { ConfirmModal } from '../confirm_modal'; import { Link } from '../link'; import { Paginate } from '../paginate'; import { ComponentStrings } from '../../../i18n'; -import { AdvancedSettings } from '../../lib/kibana_advanced_settings'; +import { getAdvancedSettings } from '../../lib/kibana_advanced_settings'; import { WorkpadDropzone } from './workpad_dropzone'; import { WorkpadCreate } from './workpad_create'; import { WorkpadSearch } from './workpad_search'; @@ -33,7 +33,7 @@ import { uploadWorkpad } from './upload_workpad'; const { WorkpadLoader: strings } = ComponentStrings; -const formatDate = date => date && moment(date).format(AdvancedSettings.get('dateFormat')); +const formatDate = date => date && moment(date).format(getAdvancedSettings().get('dateFormat')); const getDisplayName = (name, workpad, loadedWorkpad) => { const workpadName = name.length ? name : {workpad.id}; diff --git a/x-pack/legacy/plugins/canvas/public/lib/kibana_advanced_settings.ts b/x-pack/legacy/plugins/canvas/public/lib/kibana_advanced_settings.ts index 33f3d801c22d6..f57f3188a8184 100644 --- a/x-pack/legacy/plugins/canvas/public/lib/kibana_advanced_settings.ts +++ b/x-pack/legacy/plugins/canvas/public/lib/kibana_advanced_settings.ts @@ -4,6 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import chrome from 'ui/chrome'; +import { getCoreStart } from '../legacy'; -export const AdvancedSettings = chrome.getUiSettingsClient(); +export const getAdvancedSettings = () => getCoreStart().uiSettings; diff --git a/x-pack/legacy/plugins/grokdebugger/index.js b/x-pack/legacy/plugins/grokdebugger/index.js deleted file mode 100644 index 7803aed739b99..0000000000000 --- a/x-pack/legacy/plugins/grokdebugger/index.js +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { resolve } from 'path'; -import { PLUGIN } from './common/constants'; -import { registerGrokdebuggerRoutes } from './server/routes/api/grokdebugger'; -import { registerLicenseChecker } from './server/lib/register_license_checker'; - -export const grokdebugger = kibana => - new kibana.Plugin({ - id: PLUGIN.ID, - publicDir: resolve(__dirname, 'public'), - require: ['kibana', 'elasticsearch', 'xpack_main'], - configPrefix: 'xpack.grokdebugger', - config(Joi) { - return Joi.object({ - enabled: Joi.boolean().default(true), - }).default(); - }, - uiExports: { - devTools: ['plugins/grokdebugger/register'], - home: ['plugins/grokdebugger/register_feature'], - }, - init: server => { - registerLicenseChecker(server); - registerGrokdebuggerRoutes(server); - }, - }); diff --git a/x-pack/legacy/plugins/grokdebugger/public/register.js b/x-pack/legacy/plugins/grokdebugger/public/register.js deleted file mode 100644 index 74679d65e52d2..0000000000000 --- a/x-pack/legacy/plugins/grokdebugger/public/register.js +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { i18n } from '@kbn/i18n'; -import { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; -import { npSetup, npStart } from 'ui/new_platform'; - -npSetup.plugins.devTools.register({ - order: 6, - title: i18n.translate('xpack.grokDebugger.displayName', { - defaultMessage: 'Grok Debugger', - }), - id: 'grokdebugger', - enableRouting: false, - disabled: !xpackInfo.get('features.grokdebugger.enableLink', false), - tooltipContent: xpackInfo.get('features.grokdebugger.message'), - async mount(context, { element }) { - const licenseCheck = { - showPage: xpackInfo.get('features.grokdebugger.enableLink'), - message: xpackInfo.get('features.grokdebugger.message'), - }; - if (!licenseCheck.showPage) { - npStart.core.notifications.toasts.addDanger(licenseCheck.message); - window.location.hash = '/dev_tools'; - return () => {}; - } - const { renderApp } = await import('./render_app'); - return renderApp(element, npStart); - }, -}); diff --git a/x-pack/legacy/plugins/grokdebugger/public/register_feature.ts b/x-pack/legacy/plugins/grokdebugger/public/register_feature.ts deleted file mode 100644 index 97d2e53ce7836..0000000000000 --- a/x-pack/legacy/plugins/grokdebugger/public/register_feature.ts +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { i18n } from '@kbn/i18n'; -import { npSetup } from 'ui/new_platform'; -import { FeatureCatalogueCategory } from '../../../../../src/plugins/home/public'; - -const { - plugins: { home }, -} = npSetup; - -home.featureCatalogue.register({ - id: 'grokdebugger', - title: i18n.translate('xpack.grokDebugger.registryProviderTitle', { - defaultMessage: '{grokLogParsingTool} Debugger', - values: { - grokLogParsingTool: 'Grok', - }, - }), - description: i18n.translate('xpack.grokDebugger.registryProviderDescription', { - defaultMessage: - 'Simulate and debug {grokLogParsingTool} patterns for data transformation on ingestion.', - values: { - grokLogParsingTool: 'grok', - }, - }), - icon: 'grokApp', - path: '/app/kibana#/dev_tools/grokdebugger', - showOnHomePage: false, - category: FeatureCatalogueCategory.ADMIN, -}); diff --git a/x-pack/legacy/plugins/grokdebugger/server/lib/call_with_request_factory/call_with_request_factory.js b/x-pack/legacy/plugins/grokdebugger/server/lib/call_with_request_factory/call_with_request_factory.js deleted file mode 100644 index 7359a831994f9..0000000000000 --- a/x-pack/legacy/plugins/grokdebugger/server/lib/call_with_request_factory/call_with_request_factory.js +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { once } from 'lodash'; - -const callWithRequest = once(server => { - const cluster = server.plugins.elasticsearch.getCluster('data'); - return cluster.callWithRequest; -}); - -export const callWithRequestFactory = (server, request) => { - return (...args) => { - return callWithRequest(server)(request, ...args); - }; -}; diff --git a/x-pack/legacy/plugins/grokdebugger/server/lib/call_with_request_factory/index.js b/x-pack/legacy/plugins/grokdebugger/server/lib/call_with_request_factory/index.js deleted file mode 100644 index 787814d87dff9..0000000000000 --- a/x-pack/legacy/plugins/grokdebugger/server/lib/call_with_request_factory/index.js +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { callWithRequestFactory } from './call_with_request_factory'; diff --git a/x-pack/legacy/plugins/grokdebugger/server/lib/check_license/__tests__/check_license.js b/x-pack/legacy/plugins/grokdebugger/server/lib/check_license/__tests__/check_license.js deleted file mode 100644 index 7e32d68a67ece..0000000000000 --- a/x-pack/legacy/plugins/grokdebugger/server/lib/check_license/__tests__/check_license.js +++ /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; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { set } from 'lodash'; -import { checkLicense } from '../check_license'; - -describe('check_license', function() { - let mockLicenseInfo; - beforeEach(() => (mockLicenseInfo = {})); - - describe('license information is undefined', () => { - beforeEach(() => (mockLicenseInfo = undefined)); - - it('should set enableLink to false', () => { - expect(checkLicense(mockLicenseInfo).enableLink).to.be(false); - }); - - it('should set enableAPIRoute to false', () => { - expect(checkLicense(mockLicenseInfo).enableAPIRoute).to.be(false); - }); - - it('should set a message', () => { - expect(checkLicense(mockLicenseInfo).message).to.not.be(undefined); - }); - }); - - describe('license information is not available', () => { - beforeEach(() => (mockLicenseInfo.isAvailable = () => false)); - - it('should set enableLink to false', () => { - expect(checkLicense(mockLicenseInfo).enableLink).to.be(false); - }); - - it('should set enableAPIRoute to false', () => { - expect(checkLicense(mockLicenseInfo).enableAPIRoute).to.be(false); - }); - - it('should set a message', () => { - expect(checkLicense(mockLicenseInfo).message).to.not.be(undefined); - }); - }); - - describe('license information is available', () => { - beforeEach( - () => - (mockLicenseInfo = { - isAvailable: () => true, - license: { - getType: () => 'foobar', - }, - }) - ); - - describe('& license is active', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => true)); - - it('should set enableLink to true', () => { - expect(checkLicense(mockLicenseInfo).enableLink).to.be(true); - }); - - it('should set enableAPIRoute to true', () => { - expect(checkLicense(mockLicenseInfo).enableAPIRoute).to.be(true); - }); - - it('should NOT set a message', () => { - expect(checkLicense(mockLicenseInfo).message).to.be(undefined); - }); - }); - - describe('& license is expired', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => false)); - - it('should set enableLink to false', () => { - expect(checkLicense(mockLicenseInfo).enableLink).to.be(false); - }); - - it('should set enableAPIRoute to false', () => { - expect(checkLicense(mockLicenseInfo).enableAPIRoute).to.be(false); - }); - - it('should set a message', () => { - expect(checkLicense(mockLicenseInfo).message).to.not.be(undefined); - }); - }); - }); -}); diff --git a/x-pack/legacy/plugins/grokdebugger/server/lib/check_license/check_license.js b/x-pack/legacy/plugins/grokdebugger/server/lib/check_license/check_license.js deleted file mode 100644 index c7a8d2bbca059..0000000000000 --- a/x-pack/legacy/plugins/grokdebugger/server/lib/check_license/check_license.js +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { i18n } from '@kbn/i18n'; - -export function checkLicense(xpackLicenseInfo) { - // If, for some reason, we cannot get the license information - // from Elasticsearch, assume worst case and disable the Watcher UI - if (!xpackLicenseInfo || !xpackLicenseInfo.isAvailable()) { - return { - enableLink: false, - enableAPIRoute: false, - message: i18n.translate('xpack.grokDebugger.unavailableLicenseInformationMessage', { - defaultMessage: - 'You cannot use the {grokLogParsingTool} Debugger because license information is not available at this time.', - values: { - grokLogParsingTool: 'Grok', - }, - }), - }; - } - - const isLicenseActive = xpackLicenseInfo.license.isActive(); - const licenseType = xpackLicenseInfo.license.getType(); - - // License is not valid - if (!isLicenseActive) { - return { - enableLink: false, - enableAPIRoute: false, - message: i18n.translate('xpack.grokDebugger.licenseHasExpiredMessage', { - defaultMessage: - 'You cannot use the {grokLogParsingTool} Debugger because your {licenseType} license has expired.', - values: { - licenseType, - grokLogParsingTool: 'Grok', - }, - }), - }; - } - - // License is valid and active - return { - enableLink: true, - enableAPIRoute: true, - }; -} diff --git a/x-pack/legacy/plugins/grokdebugger/server/lib/check_license/index.js b/x-pack/legacy/plugins/grokdebugger/server/lib/check_license/index.js deleted file mode 100644 index f2c070fd44b6e..0000000000000 --- a/x-pack/legacy/plugins/grokdebugger/server/lib/check_license/index.js +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { checkLicense } from './check_license'; diff --git a/x-pack/legacy/plugins/grokdebugger/server/lib/error_wrappers/wrap_es_error.js b/x-pack/legacy/plugins/grokdebugger/server/lib/error_wrappers/wrap_es_error.js deleted file mode 100644 index dfcd4e3b1e17c..0000000000000 --- a/x-pack/legacy/plugins/grokdebugger/server/lib/error_wrappers/wrap_es_error.js +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import Boom from 'boom'; - -/** - * Wraps ES errors into a Boom error response and returns it - * This also handles the permissions issue gracefully - * - * @param err Object ES error - * @return Object Boom error response - */ -export function wrapEsError(err) { - return Boom.boomify(err, { statusCode: err.statusCode }); -} diff --git a/x-pack/legacy/plugins/grokdebugger/server/lib/license_pre_routing_factory/__tests__/license_pre_routing_factory.js b/x-pack/legacy/plugins/grokdebugger/server/lib/license_pre_routing_factory/__tests__/license_pre_routing_factory.js deleted file mode 100644 index 135317f050774..0000000000000 --- a/x-pack/legacy/plugins/grokdebugger/server/lib/license_pre_routing_factory/__tests__/license_pre_routing_factory.js +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import Boom from 'boom'; -import { licensePreRoutingFactory } from '../license_pre_routing_factory'; - -describe('license_pre_routing_factory', () => { - describe('#grokDebuggerFeaturePreRoutingFactory', () => { - let mockServer; - let mockLicenseCheckResults; - - beforeEach(() => { - mockServer = { - plugins: { - xpack_main: { - info: { - feature: () => ({ - getLicenseCheckResults: () => mockLicenseCheckResults, - }), - }, - }, - }, - }; - }); - - describe('isAvailable is false', () => { - beforeEach(() => { - mockLicenseCheckResults = { - isAvailable: false, - }; - }); - - it('replies with 403', async () => { - const licensePreRouting = licensePreRoutingFactory(mockServer); - const stubRequest = {}; - expect(() => licensePreRouting(stubRequest)).to.throwException(response => { - expect(response).to.be.an(Error); - expect(response.isBoom).to.be(true); - expect(response.output.statusCode).to.be(403); - }); - }); - }); - - describe('isAvailable is true', () => { - beforeEach(() => { - mockLicenseCheckResults = { - isAvailable: true, - }; - }); - - it('replies with forbidden', async () => { - const licensePreRouting = licensePreRoutingFactory(mockServer); - const stubRequest = {}; - expect(() => licensePreRouting(stubRequest)).to.throwException(response => { - expect(response).to.eql(Boom.forbidden()); - }); - }); - }); - }); -}); diff --git a/x-pack/legacy/plugins/grokdebugger/server/lib/license_pre_routing_factory/index.js b/x-pack/legacy/plugins/grokdebugger/server/lib/license_pre_routing_factory/index.js deleted file mode 100644 index 0743e443955f4..0000000000000 --- a/x-pack/legacy/plugins/grokdebugger/server/lib/license_pre_routing_factory/index.js +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { licensePreRoutingFactory } from './license_pre_routing_factory'; diff --git a/x-pack/legacy/plugins/grokdebugger/server/lib/license_pre_routing_factory/license_pre_routing_factory.js b/x-pack/legacy/plugins/grokdebugger/server/lib/license_pre_routing_factory/license_pre_routing_factory.js deleted file mode 100644 index 88c844de3e2ca..0000000000000 --- a/x-pack/legacy/plugins/grokdebugger/server/lib/license_pre_routing_factory/license_pre_routing_factory.js +++ /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; - * you may not use this file except in compliance with the Elastic License. - */ - -import Boom from 'boom'; -import { PLUGIN } from '../../../common/constants'; - -export const licensePreRoutingFactory = server => { - const xpackMainPlugin = server.plugins.xpack_main; - - // License checking and enable/disable logic - function licensePreRouting() { - const licenseCheckResults = xpackMainPlugin.info.feature(PLUGIN.ID).getLicenseCheckResults(); - if (!licenseCheckResults.enableAPIRoute) { - throw Boom.forbidden(licenseCheckResults.message); - } - - return null; - } - - return licensePreRouting; -}; diff --git a/x-pack/legacy/plugins/grokdebugger/server/lib/register_license_checker/index.js b/x-pack/legacy/plugins/grokdebugger/server/lib/register_license_checker/index.js deleted file mode 100644 index 7b0f97c38d129..0000000000000 --- a/x-pack/legacy/plugins/grokdebugger/server/lib/register_license_checker/index.js +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { registerLicenseChecker } from './register_license_checker'; diff --git a/x-pack/legacy/plugins/grokdebugger/server/lib/register_license_checker/register_license_checker.js b/x-pack/legacy/plugins/grokdebugger/server/lib/register_license_checker/register_license_checker.js deleted file mode 100644 index ee9a9a3ebdece..0000000000000 --- a/x-pack/legacy/plugins/grokdebugger/server/lib/register_license_checker/register_license_checker.js +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { mirrorPluginStatus } from '../../../../../server/lib/mirror_plugin_status'; -import { checkLicense } from '../check_license'; -import { PLUGIN } from '../../../common/constants'; - -export function registerLicenseChecker(server) { - const xpackMainPlugin = server.plugins.xpack_main; - const grokdebuggerPlugin = server.plugins[PLUGIN.ID]; - - mirrorPluginStatus(xpackMainPlugin, grokdebuggerPlugin); - xpackMainPlugin.status.once('green', () => { - // Register a function that is called whenever the xpack info changes, - // to re-compute the license check results for this plugin - xpackMainPlugin.info.feature(PLUGIN.ID).registerLicenseCheckResultsGenerator(checkLicense); - }); -} diff --git a/x-pack/legacy/plugins/grokdebugger/server/routes/api/grokdebugger/register_grok_simulate_route.js b/x-pack/legacy/plugins/grokdebugger/server/routes/api/grokdebugger/register_grok_simulate_route.js deleted file mode 100644 index 14ea1e7acb668..0000000000000 --- a/x-pack/legacy/plugins/grokdebugger/server/routes/api/grokdebugger/register_grok_simulate_route.js +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { wrapEsError } from '../../../lib/error_wrappers'; -import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; -import { GrokdebuggerRequest } from '../../../models/grokdebugger_request'; -import { GrokdebuggerResponse } from '../../../models/grokdebugger_response'; -import { licensePreRoutingFactory } from '../../../lib/license_pre_routing_factory'; - -function simulateGrok(callWithRequest, ingestJson) { - return callWithRequest('ingest.simulate', { - body: ingestJson, - }); -} - -export function registerGrokSimulateRoute(server) { - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/grokdebugger/simulate', - method: 'POST', - handler: request => { - const callWithRequest = callWithRequestFactory(server, request); - const grokdebuggerRequest = GrokdebuggerRequest.fromDownstreamJSON(request.payload); - return simulateGrok(callWithRequest, grokdebuggerRequest.upstreamJSON) - .then(simulateResponseFromES => { - const grokdebuggerResponse = GrokdebuggerResponse.fromUpstreamJSON( - simulateResponseFromES - ); - return { grokdebuggerResponse }; - }) - .catch(e => wrapEsError(e)); - }, - config: { - pre: [licensePreRouting], - }, - }); -} diff --git a/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts b/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts index 60799e3e918b8..4b80e129c04da 100644 --- a/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts +++ b/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts @@ -161,6 +161,12 @@ export class HeadlessChromiumDriver { const interceptedUrl = interceptedResponse.url(); const allowed = !interceptedUrl.startsWith('file://'); + if (!interceptedResponse.ok()) { + logger.warn( + `Chromium received a non-OK response (${interceptedResponse.status()}) for request ${interceptedUrl}` + ); + } + if (!allowed || !this.allowRequest(interceptedUrl)) { logger.error(`Got disallowed URL "${interceptedUrl}", closing browser.`); this.page.browser().close(); diff --git a/x-pack/legacy/plugins/siem/cypress.json b/x-pack/legacy/plugins/siem/cypress.json index cc41b72714ed9..a0333a1068146 100644 --- a/x-pack/legacy/plugins/siem/cypress.json +++ b/x-pack/legacy/plugins/siem/cypress.json @@ -1,6 +1,6 @@ { "baseUrl": "http://localhost:5601", - "defaultCommandTimeout": 60000, + "defaultCommandTimeout": 120000, "screenshotsFolder": "../../../../target/kibana-siem/cypress/screenshots", "trashAssetsBeforeRuns": false, "video": false, diff --git a/x-pack/legacy/plugins/siem/cypress/integration/timeline_data_providers.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/timeline_data_providers.spec.ts index aca988e195161..4889d40ae7d39 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/timeline_data_providers.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/timeline_data_providers.spec.ts @@ -49,7 +49,7 @@ describe('timeline data providers', () => { .first() .invoke('text') .should(hostname => { - expect(dataProviderText).to.eq(hostname); + expect(dataProviderText).to.eq(`host.name: "${hostname}"`); }); }); }); diff --git a/x-pack/legacy/plugins/siem/cypress/integration/url_state.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/url_state.spec.ts index 25e50194f543d..cd60745b19040 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/url_state.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/url_state.spec.ts @@ -249,6 +249,7 @@ describe('url state', () => { const timelineName = 'SIEM'; addNameToTimeline(timelineName); addDescriptionToTimeline('This is the best timeline of the world'); + cy.wait(5000); cy.url({ timeout: 30000 }).should('match', /\w*-\w*-\w*-\w*-\w*/); cy.url().then(url => { diff --git a/x-pack/legacy/plugins/siem/cypress/tasks/common.ts b/x-pack/legacy/plugins/siem/cypress/tasks/common.ts index 03a1fe4496030..b0c64214459f0 100644 --- a/x-pack/legacy/plugins/siem/cypress/tasks/common.ts +++ b/x-pack/legacy/plugins/siem/cypress/tasks/common.ts @@ -23,14 +23,14 @@ export const drag = (subject: JQuery) => { clientY: subjectLocation.top, force: true, }) - .wait(100) + .wait(1000) .trigger('mousemove', { button: primaryButton, clientX: subjectLocation.left + dndSloppyClickDetectionThreshold, clientY: subjectLocation.top, force: true, }) - .wait(100); + .wait(1000); }; /** Drags the subject being dragged on the specified drop target, but does not drop it */ @@ -44,7 +44,7 @@ export const dragWithoutDrop = (dropTarget: JQuery) => { export const drop = (dropTarget: JQuery) => { cy.wrap(dropTarget) .trigger('mousemove', { button: primaryButton, force: true }) - .wait(100) + .wait(1000) .trigger('mouseup', { force: true }) - .wait(100); + .wait(1000); }; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/alerts/toggle_alert_flyout_button.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/alerts/toggle_alert_flyout_button.tsx index 8093dd30604e4..04dfe4b3e3509 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/alerts/toggle_alert_flyout_button.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/alerts/toggle_alert_flyout_button.tsx @@ -49,7 +49,10 @@ export const ToggleAlertFlyoutButtonComponent = ({ setAlertFlyoutVisible }: Prop data-test-subj="xpack.uptime.toggleAlertFlyout" key="create-alert" icon="bell" - onClick={() => setAlertFlyoutVisible(true)} + onClick={() => { + setAlertFlyoutVisible(true); + setIsOpen(false); + }} > body header

} + viewType="list" > - +

body header @@ -82,28 +91,37 @@ exports[`PageView component should display body header custom element 1`] = ` `; exports[`PageView component should display body header wrapped in EuiTitle 1`] = ` -.c0 { +.c0.endpoint--isListView { padding: 0; } -.c0 .endpoint-header { +.c0.endpoint--isListView .endpoint-header { padding: 24px; } -.c0 .endpoint-page-content { +.c0.endpoint--isListView .endpoint-page-content { border-left: none; border-right: none; } +.c0.endpoint--isDetailsView .endpoint-page-content { + padding: 0; + border: none; + background: none; +} + - +

- -

- body header -

-
+ + +

+ body header +

+
+
@@ -163,29 +184,38 @@ exports[`PageView component should display body header wrapped in EuiTitle 1`] = `; exports[`PageView component should display header left and right 1`] = ` -.c0 { +.c0.endpoint--isListView { padding: 0; } -.c0 .endpoint-header { +.c0.endpoint--isListView .endpoint-header { padding: 24px; } -.c0 .endpoint-page-content { +.c0.endpoint--isListView .endpoint-page-content { border-left: none; border-right: none; } +.c0.endpoint--isDetailsView .endpoint-page-content { + padding: 0; + border: none; + background: none; +} + - +
- -

+ - page title -

-
+

+ page title +

+ +
- +.c0.endpoint--isDetailsView .endpoint-page-content { + padding: 0; + border: none; + background: none; +} + + +
- +
- -

+ - page title -

-
+

+ page title +

+ +
@@ -401,28 +456,37 @@ exports[`PageView component should display only header left 1`] = ` `; exports[`PageView component should display only header right but include an empty left side 1`] = ` -.c0 { +.c0.endpoint--isListView { padding: 0; } -.c0 .endpoint-header { +.c0.endpoint--isListView .endpoint-header { padding: 24px; } -.c0 .endpoint-page-content { +.c0.endpoint--isListView .endpoint-page-content { border-left: none; border-right: none; } +.c0.endpoint--isDetailsView .endpoint-page-content { + padding: 0; + border: none; + background: none; +} + - +
title here

} + viewType="list" > - +
{ mount(ui, { wrappingComponent: EuiThemeProvider }); it('should display only body if not header props used', () => { - expect(render(body content)).toMatchSnapshot(); + expect(render(body content)).toMatchSnapshot(); }); it('should display header left and right', () => { expect( render( - + body content ) ).toMatchSnapshot(); }); it('should display only header left', () => { - expect(render(body content)).toMatchSnapshot(); + expect( + render( + + body content + + ) + ).toMatchSnapshot(); }); it('should display only header right but include an empty left side', () => { expect( - render(body content) + render( + + body content + + ) ).toMatchSnapshot(); }); it(`should use custom element for header left and not wrap in EuiTitle`, () => { expect( - render(title here

}>body content
) + render( + title here

}> + body content +
+ ) ).toMatchSnapshot(); }); it('should display body header wrapped in EuiTitle', () => { - expect(render(body content)).toMatchSnapshot(); + expect( + render( + + body content + + ) + ).toMatchSnapshot(); }); it('should display body header custom element', () => { expect( - render(body header

}>body content
) + render( + body header

}> + body content +
+ ) ).toMatchSnapshot(); }); it('should pass through EuiPage props', () => { expect( render( props.theme.eui.euiSizeL}; + .endpoint-header { + padding: ${props => props.theme.eui.euiSizeL}; + } + .endpoint-page-content { + border-left: none; + border-right: none; + } } - .endpoint-page-content { - border-left: none; - border-right: none; + &.endpoint--isDetailsView { + .endpoint-page-content { + padding: 0; + border: none; + background: none; + } } `; const isStringOrNumber = /(string|number)/; +/** + * The `PageView` component used to render `headerLeft` when it is set as a `string` + * Can be used when wanting to customize the `headerLeft` value but still use the standard + * title component + */ +export const PageViewHeaderTitle = memo<{ children: ReactNode }>(({ children }) => { + return ( + +

{children}

+
+ ); +}); + +/** + * The `PageView` component used to render `bodyHeader` when it is set as a `string` + * Can be used when wanting to customize the `bodyHeader` value but still use the standard + * title component + */ +export const PageViewBodyHeaderTitle = memo<{ children: ReactNode }>( + ({ children, ...otherProps }) => { + return ( + +

{children}

+
+ ); + } +); + /** * Page View layout for use in Endpoint */ export const PageView = memo< EuiPageProps & { + /** + * The type of view + */ + viewType: 'list' | 'details'; /** * content to be placed on the left side of the header. If a `string` is used, then it will * be wrapped with `

`, else it will just be used as is. @@ -52,17 +93,18 @@ export const PageView = memo< bodyHeader?: ReactNode; children?: ReactNode; } ->(({ children, headerLeft, headerRight, bodyHeader, ...otherProps }) => { +>(({ viewType, children, headerLeft, headerRight, bodyHeader, ...otherProps }) => { return ( - + {(headerLeft || headerRight) && ( {isStringOrNumber.test(typeof headerLeft) ? ( - -

{headerLeft}

-
+ {headerLeft} ) : ( headerLeft )} @@ -77,11 +119,9 @@ export const PageView = memo< {bodyHeader && ( - + {isStringOrNumber.test(typeof bodyHeader) ? ( - -

{bodyHeader}

-
+ {bodyHeader} ) : ( bodyHeader )} diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/models/policy.ts b/x-pack/plugins/endpoint/public/applications/endpoint/models/policy.ts new file mode 100644 index 0000000000000..e1ac9defc858e --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/models/policy.ts @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { PolicyConfig } from '../types'; + +/** + * Generate a new Policy model. + * NOTE: in the near future, this will likely be removed and an API call to EPM will be used to retrieve + * the latest from the Endpoint package + */ +export const generatePolicy = (): PolicyConfig => { + return { + windows: { + events: { + process: true, + network: true, + }, + malware: { + mode: 'prevent', + }, + logging: { + stdout: 'debug', + file: 'info', + }, + advanced: { + elasticsearch: { + indices: { + control: 'control-index', + event: 'event-index', + logging: 'logging-index', + }, + kernel: { + connect: true, + process: true, + }, + }, + }, + }, + mac: { + events: { + process: true, + }, + malware: { + mode: 'detect', + }, + logging: { + stdout: 'debug', + file: 'info', + }, + advanced: { + elasticsearch: { + indices: { + control: 'control-index', + event: 'event-index', + logging: 'logging-index', + }, + kernel: { + connect: true, + process: true, + }, + }, + }, + }, + linux: { + events: { + process: true, + }, + logging: { + stdout: 'debug', + file: 'info', + }, + advanced: { + elasticsearch: { + indices: { + control: 'control-index', + event: 'event-index', + logging: 'logging-index', + }, + kernel: { + connect: true, + process: true, + }, + }, + }, + }, + }; +}; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/models/policy_details_config.ts b/x-pack/plugins/endpoint/public/applications/endpoint/models/policy_details_config.ts index 1900516cb539b..1145d1d19242a 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/models/policy_details_config.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/models/policy_details_config.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PolicyConfig } from '../types'; +import { UIPolicyConfig } from '../types'; /** * A typed Object.entries() function where the keys and values are typed based on the given object @@ -14,10 +14,10 @@ const entries = (o: T): Array<[keyof T, T[keyof T]]> => type DeepPartial = { [K in keyof T]?: DeepPartial }; /** - * Returns a deep copy of PolicyDetailsConfig + * Returns a deep copy of `UIPolicyConfig` object */ -export function clone(policyDetailsConfig: PolicyConfig): PolicyConfig { - const clonedConfig: DeepPartial = {}; +export function clone(policyDetailsConfig: UIPolicyConfig): UIPolicyConfig { + const clonedConfig: DeepPartial = {}; for (const [key, val] of entries(policyDetailsConfig)) { if (typeof val === 'object') { const valClone: Partial = {}; @@ -41,5 +41,5 @@ export function clone(policyDetailsConfig: PolicyConfig): PolicyConfig { /** * clonedConfig is typed as DeepPartial so we can construct the copy from an empty object */ - return clonedConfig as PolicyConfig; + return clonedConfig as UIPolicyConfig; } diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/services/ingest.ts b/x-pack/plugins/endpoint/public/applications/endpoint/services/ingest.ts index fbb92f8bbe915..583ebc55d896b 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/services/ingest.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/services/ingest.ts @@ -5,11 +5,17 @@ */ import { HttpFetchOptions, HttpStart } from 'kibana/public'; -import { GetDatasourcesRequest } from '../../../../../ingest_manager/common/types/rest_spec'; -import { PolicyData } from '../types'; +import { + CreateDatasourceResponse, + GetAgentStatusResponse, + GetDatasourcesRequest, +} from '../../../../../ingest_manager/common/types/rest_spec'; +import { NewPolicyData, PolicyData } from '../types'; const INGEST_API_ROOT = `/api/ingest_manager`; const INGEST_API_DATASOURCES = `${INGEST_API_ROOT}/datasources`; +const INGEST_API_FLEET = `${INGEST_API_ROOT}/fleet`; +const INGEST_API_FLEET_AGENT_STATUS = `${INGEST_API_FLEET}/agent-status`; // FIXME: Import from ingest after - https://github.com/elastic/kibana/issues/60677 export interface GetDatasourcesResponse { @@ -26,6 +32,11 @@ export interface GetDatasourceResponse { success: boolean; } +// FIXME: Import from Ingest after - https://github.com/elastic/kibana/issues/60677 +export type UpdateDatasourceResponse = CreateDatasourceResponse & { + item: PolicyData; +}; + /** * Retrieves a list of endpoint specific datasources (those created with a `package.name` of * `endpoint`) from Ingest @@ -60,3 +71,44 @@ export const sendGetDatasource = ( ) => { return http.get(`${INGEST_API_DATASOURCES}/${datasourceId}`, options); }; + +/** + * Updates a datasources + * + * @param http + * @param datasourceId + * @param datasource + * @param options + */ +export const sendPutDatasource = ( + http: HttpStart, + datasourceId: string, + datasource: NewPolicyData, + options: Exclude = {} +): Promise => { + return http.put(`${INGEST_API_DATASOURCES}/${datasourceId}`, { + ...options, + body: JSON.stringify(datasource), + }); +}; + +/** + * Get a status summary for all Agents that are currently assigned to a given agent configuration + * + * @param http + * @param configId + * @param options + */ +export const sendGetFleetAgentStatusForConfig = ( + http: HttpStart, + /** the Agent (fleet) configuration id */ + configId: string, + options: Exclude = {} +): Promise => { + return http.get(INGEST_API_FLEET_AGENT_STATUS, { + ...options, + query: { + configId, + }, + }); +}; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/action.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/action.ts index e7e523a9287b8..9905145048a8a 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/action.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/action.ts @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PolicyData, PolicyConfig } from '../../types'; +import { PolicyData, PolicyDetailsState, ServerApiError, UIPolicyConfig } from '../../types'; +import { GetAgentStatusResponse } from '../../../../../../ingest_manager/common/types/rest_spec'; interface ServerReturnedPolicyDetailsData { type: 'serverReturnedPolicyDetailsData'; @@ -13,14 +14,50 @@ interface ServerReturnedPolicyDetailsData { }; } +interface ServerFailedToReturnPolicyDetailsData { + type: 'serverFailedToReturnPolicyDetailsData'; + payload: ServerApiError; +} + /** * When users change a policy via forms, this action is dispatched with a payload that modifies the configuration of a cloned policy config. */ interface UserChangedPolicyConfig { type: 'userChangedPolicyConfig'; payload: { - policyConfig: PolicyConfig; + policyConfig: UIPolicyConfig; + }; +} + +interface ServerReturnedPolicyDetailsAgentSummaryData { + type: 'serverReturnedPolicyDetailsAgentSummaryData'; + payload: { + agentStatusSummary: GetAgentStatusResponse['results']; + }; +} + +interface ServerReturnedPolicyDetailsUpdateFailure { + type: 'serverReturnedPolicyDetailsUpdateFailure'; + payload: PolicyDetailsState['updateStatus']; +} + +interface ServerReturnedUpdatedPolicyDetailsData { + type: 'serverReturnedUpdatedPolicyDetailsData'; + payload: { + policyItem: PolicyData; + updateStatus: PolicyDetailsState['updateStatus']; }; } -export type PolicyDetailsAction = ServerReturnedPolicyDetailsData | UserChangedPolicyConfig; +interface UserClickedPolicyDetailsSaveButton { + type: 'userClickedPolicyDetailsSaveButton'; +} + +export type PolicyDetailsAction = + | ServerReturnedPolicyDetailsData + | UserClickedPolicyDetailsSaveButton + | ServerReturnedPolicyDetailsAgentSummaryData + | ServerReturnedPolicyDetailsUpdateFailure + | ServerReturnedUpdatedPolicyDetailsData + | ServerFailedToReturnPolicyDetailsData + | UserChangedPolicyConfig; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/index.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/index.test.ts index b20df84fdf575..cf14092953227 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/index.test.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/index.test.ts @@ -9,6 +9,7 @@ import { createStore, Dispatch, Store } from 'redux'; import { policyDetailsReducer, PolicyDetailsAction } from './index'; import { policyConfig, windowsEventing } from './selectors'; import { clone } from '../../models/policy_details_config'; +import { generatePolicy } from '../../models/policy'; describe('policy details: ', () => { let store: Store; @@ -30,7 +31,18 @@ describe('policy details: ', () => { config_id: '', enabled: true, output_id: '', - inputs: [], + inputs: [ + { + type: 'endpoint', + enabled: true, + streams: [], + config: { + policy: { + value: generatePolicy(), + }, + }, + }, + ], namespace: '', package: { name: '', @@ -39,32 +51,6 @@ describe('policy details: ', () => { }, revision: 1, }, - policyConfig: { - windows: { - malware: { - mode: 'detect', - }, - eventing: { - process: false, - network: false, - }, - }, - mac: { - malware: { - mode: '', - }, - eventing: { - process: false, - network: false, - }, - }, - linux: { - eventing: { - process: false, - network: false, - }, - }, - }, }, }); }); @@ -77,7 +63,7 @@ describe('policy details: ', () => { } const newPayload1 = clone(config); - newPayload1.windows.eventing.process = true; + newPayload1.windows.events.process = true; dispatch({ type: 'userChangedPolicyConfig', diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/middleware.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/middleware.ts index 1942538aa9df9..18248e272aada 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/middleware.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/middleware.ts @@ -4,9 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import { MiddlewareFactory, PolicyDetailsState } from '../../types'; -import { policyIdFromParams, isOnPolicyDetailsPage } from './selectors'; -import { sendGetDatasource } from '../../services/ingest'; +import { MiddlewareFactory, PolicyData, PolicyDetailsState } from '../../types'; +import { policyIdFromParams, isOnPolicyDetailsPage, policyDetails } from './selectors'; +import { + sendGetDatasource, + sendGetFleetAgentStatusForConfig, + sendPutDatasource, + UpdateDatasourceResponse, +} from '../../services/ingest'; +import { generatePolicy } from '../../models/policy'; export const policyDetailsMiddlewareFactory: MiddlewareFactory = coreStart => { const http = coreStart.http; @@ -17,25 +23,78 @@ export const policyDetailsMiddlewareFactory: MiddlewareFactory { return { policyItem: undefined, - policyConfig: undefined, isLoading: false, + agentStatusSummary: { + error: 0, + events: 0, + offline: 0, + online: 0, + total: 0, + }, }; }; @@ -20,7 +27,10 @@ export const policyDetailsReducer: Reducer = ( state = initialPolicyDetailsState(), action ) => { - if (action.type === 'serverReturnedPolicyDetailsData') { + if ( + action.type === 'serverReturnedPolicyDetailsData' || + action.type === 'serverReturnedUpdatedPolicyDetailsData' + ) { return { ...state, ...action.payload, @@ -28,19 +38,67 @@ export const policyDetailsReducer: Reducer = ( }; } - if (action.type === 'userChangedUrl') { + if (action.type === 'serverFailedToReturnPolicyDetailsData') { return { ...state, - location: action.payload, + isLoading: false, + apiError: action.payload, }; } - if (action.type === 'userChangedPolicyConfig') { + if (action.type === 'serverReturnedPolicyDetailsAgentSummaryData') { + return { + ...state, + ...action.payload, + }; + } + + if (action.type === 'serverReturnedPolicyDetailsUpdateFailure') { + return { + ...state, + isLoading: false, + updateStatus: action.payload, + }; + } + + if (action.type === 'userClickedPolicyDetailsSaveButton') { return { ...state, - policyConfig: action.payload.policyConfig, + isLoading: true, + updateApiError: undefined, }; } + if (action.type === 'userChangedUrl') { + const newState = { + ...state, + location: action.payload, + }; + + if (isOnPolicyDetailsPage(newState)) { + return newState; + } + return { + ...initialPolicyDetailsState(), + location: action.payload, + }; + } + + if (action.type === 'userChangedPolicyConfig') { + const newState = { ...state, policyItem: { ...(state.policyItem as PolicyData) } }; + const newPolicy = (newState.policyItem.inputs[0].config.policy.value = { + ...fullPolicy(state), + }); + + Object.entries(action.payload.policyConfig).forEach(([section, newSettings]) => { + newPolicy[section as keyof UIPolicyConfig] = { + ...newPolicy[section as keyof UIPolicyConfig], + ...newSettings, + }; + }); + + return newState; + } + return state; }; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/selectors.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/selectors.ts index 6a5d4077b3c32..0d505931c9ec5 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/selectors.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/selectors.ts @@ -5,8 +5,8 @@ */ import { createSelector } from 'reselect'; -import { PolicyDetailsState } from '../../types'; -import { Immutable } from '../../../../../common/types'; +import { PolicyConfig, PolicyDetailsState, UIPolicyConfig } from '../../types'; +import { generatePolicy } from '../../models/policy'; /** Returns the policy details */ export const policyDetails = (state: PolicyDetailsState) => state.policyItem; @@ -32,20 +32,64 @@ export const policyIdFromParams: (state: PolicyDetailsState) => string = createS } ); +/** + * Returns the full Endpoint Policy, which will include private settings not shown on the UI. + * Note: this will return a default full policy if the `policyItem` is `undefined` + */ +export const fullPolicy: (s: PolicyDetailsState) => PolicyConfig = createSelector( + policyDetails, + policyData => { + return policyData?.inputs[0]?.config?.policy?.value ?? generatePolicy(); + } +); + +const fullWindowsPolicySettings: ( + s: PolicyDetailsState +) => PolicyConfig['windows'] = createSelector(fullPolicy, policy => policy?.windows); + +const fullMacPolicySettings: (s: PolicyDetailsState) => PolicyConfig['mac'] = createSelector( + fullPolicy, + policy => policy?.mac +); + +const fullLinuxPolicySettings: (s: PolicyDetailsState) => PolicyConfig['linux'] = createSelector( + fullPolicy, + policy => policy?.linux +); + /** Returns the policy configuration */ -export const policyConfig = (state: Immutable) => state.policyConfig; +export const policyConfig: (s: PolicyDetailsState) => UIPolicyConfig = createSelector( + fullWindowsPolicySettings, + fullMacPolicySettings, + fullLinuxPolicySettings, + (windows, mac, linux) => { + return { + windows: { + events: windows.events, + malware: windows.malware, + }, + mac: { + events: mac.events, + malware: mac.malware, + }, + linux: { + events: linux.events, + }, + }; + } +); /** Returns an object of all the windows eventing configuration */ export const windowsEventing = (state: PolicyDetailsState) => { const config = policyConfig(state); - return config && config.windows.eventing; + return config && config.windows.events; }; /** Returns the total number of possible windows eventing configurations */ export const totalWindowsEventing = (state: PolicyDetailsState): number => { const config = policyConfig(state); if (config) { - return Object.keys(config.windows.eventing).length; + return Object.keys(config.windows.events).length; } return 0; }; @@ -54,9 +98,21 @@ export const totalWindowsEventing = (state: PolicyDetailsState): number => { export const selectedWindowsEventing = (state: PolicyDetailsState): number => { const config = policyConfig(state); if (config) { - return Object.values(config.windows.eventing).reduce((count, event) => { + return Object.values(config.windows.events).reduce((count, event) => { return event === true ? count + 1 : count; }, 0); } return 0; }; + +/** is there an api call in flight */ +export const isLoading = (state: PolicyDetailsState) => state.isLoading; + +/** API error when fetching Policy data */ +export const apiError = (state: PolicyDetailsState) => state.apiError; + +/** Policy Agent Summary Stats */ +export const agentStatusSummary = (state: PolicyDetailsState) => state.agentStatusSummary; + +/** Status for an update to the policy */ +export const updateStatus = (state: PolicyDetailsState) => state.updateStatus; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/types.ts b/x-pack/plugins/endpoint/public/applications/endpoint/types.ts index 7947a35068234..4215edb4d6810 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/types.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/types.ts @@ -17,7 +17,8 @@ import { import { EndpointPluginStartDependencies } from '../../plugin'; import { AppAction } from './store/action'; import { CoreStart } from '../../../../../../src/core/public'; -import { Datasource } from '../../../../ingest_manager/common/types/models'; +import { Datasource, NewDatasource } from '../../../../ingest_manager/common/types/models'; +import { GetAgentStatusResponse } from '../../../../ingest_manager/common/types/rest_spec'; export { AppAction }; export type MiddlewareFactory = ( @@ -53,9 +54,27 @@ export interface ServerApiError { } /** - * An Endpoint Policy. + * New policy data. Used when updating the policy record via ingest APIs */ -export type PolicyData = Datasource; +export type NewPolicyData = NewDatasource & { + inputs: [ + { + type: 'endpoint'; + enabled: boolean; + streams: []; + config: { + policy: { + value: PolicyConfig; + }; + }; + } + ]; +}; + +/** + * Endpoint Policy data, which extends Ingest's `Datasource` type + */ +export type PolicyData = Datasource & NewPolicyData; /** * Policy list store state @@ -81,57 +100,100 @@ export interface PolicyListState { export interface PolicyDetailsState { /** A single policy item */ policyItem?: PolicyData; - /** data is being retrieved from server */ - policyConfig?: PolicyConfig; + /** API error if loading data failed */ + apiError?: ServerApiError; isLoading: boolean; /** current location of the application */ location?: Immutable; + /** A summary of stats for the agents associated with a given Fleet Agent Configuration */ + agentStatusSummary: GetAgentStatusResponse['results']; + /** Status of an update to the policy */ + updateStatus?: { + success: boolean; + error?: ServerApiError; + }; } /** - * Policy Details configuration + * Endpoint Policy configuration */ export interface PolicyConfig { - windows: WindowsPolicyConfig; - mac: MacPolicyConfig; - linux: LinuxPolicyConfig; + windows: { + events: { + process: boolean; + network: boolean; + }; + /** malware mode can be detect, prevent or prevent and notify user */ + malware: { + mode: string; + }; + logging: { + stdout: string; + file: string; + }; + advanced: PolicyConfigAdvancedOptions; + }; + mac: { + events: { + process: boolean; + }; + malware: { + mode: string; + }; + logging: { + stdout: string; + file: string; + }; + advanced: PolicyConfigAdvancedOptions; + }; + linux: { + events: { + process: boolean; + }; + logging: { + stdout: string; + file: string; + }; + advanced: PolicyConfigAdvancedOptions; + }; } -/** - * Windows-specific policy configuration - */ -interface WindowsPolicyConfig { - /** malware mode can be detect, prevent or prevent and notify user */ - malware: { - mode: string; - }; - eventing: { - process: boolean; - network: boolean; +interface PolicyConfigAdvancedOptions { + elasticsearch: { + indices: { + control: string; + event: string; + logging: string; + }; + kernel: { + connect: boolean; + process: boolean; + }; }; } /** - * Mac-specific policy configuration + * Windows-specific policy configuration that is supported via the UI */ -interface MacPolicyConfig { - /** malware mode can be detect, prevent or prevent and notify user */ - malware: { - mode: string; - }; - eventing: { - process: boolean; - network: boolean; - }; -} +type WindowsPolicyConfig = Pick; + /** - * Linux-specific policy configuration + * Mac-specific policy configuration that is supported via the UI */ -interface LinuxPolicyConfig { - eventing: { - process: boolean; - network: boolean; - }; +type MacPolicyConfig = Pick; + +/** + * Linux-specific policy configuration that is supported via the UI + */ +type LinuxPolicyConfig = Pick; + +/** + * The set of Policy configuration settings that are show/edited via the UI + */ +export interface UIPolicyConfig { + windows: WindowsPolicyConfig; + mac: MacPolicyConfig; + linux: LinuxPolicyConfig; } /** OS used in Policy */ diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/agents_summary.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/agents_summary.tsx new file mode 100644 index 0000000000000..d0751cf9fb886 --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/agents_summary.tsx @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { memo, useMemo } from 'react'; +import { + EuiDescriptionList, + EuiFlexGroup, + EuiFlexItem, + EuiHealth, + EuiI18nNumber, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +export interface AgentsSummaryProps { + total: number; + online: number; + offline: number; + error: number; +} + +/** + * Display a summary of stats (counts) associated with a group of agents (ex. those associated with a Policy) + */ +export const AgentsSummary = memo(props => { + const stats = useMemo< + Array<{ key: keyof AgentsSummaryProps; title: string; health: string }> + >(() => { + return [ + { + key: 'total', + title: i18n.translate('xpack.endpoint.policyDetails.agentsSummary.totalTitle', { + defaultMessage: 'Hosts', + }), + health: '', + }, + { + key: 'online', + title: i18n.translate('xpack.endpoint.policyDetails.agentsSummary.onlineTitle', { + defaultMessage: 'Online', + }), + health: 'success', + }, + { + key: 'offline', + title: i18n.translate('xpack.endpoint.policyDetails.agentsSummary.offlineTitle', { + defaultMessage: 'Offline', + }), + health: 'warning', + }, + { + key: 'error', + title: i18n.translate('xpack.endpoint.policyDetails.agentsSummary.errorTitle', { + defaultMessage: 'Error', + }), + health: 'danger', + }, + ]; + }, []); + + return ( + + {stats.map(({ key, title, health }) => { + return ( + + + {health && } + + + ), + }, + ]} + /> + + ); + })} + + ); +}); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_details.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_details.tsx index a64b3293ec6cd..f2c79155f3c23 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_details.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_details.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { EuiFlexGroup, EuiFlexItem, @@ -12,32 +12,154 @@ import { EuiButtonEmpty, EuiText, EuiSpacer, + EuiOverlayMask, + EuiConfirmModal, + EuiCallOut, + EuiLoadingSpinner, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; +import { useDispatch } from 'react-redux'; +import { useHistory } from 'react-router-dom'; import { usePolicyDetailsSelector } from './policy_hooks'; -import { policyDetails } from '../../store/policy_details/selectors'; +import { + policyDetails, + agentStatusSummary, + updateStatus, + isLoading, + apiError, +} from '../../store/policy_details/selectors'; import { WindowsEventing } from './policy_forms/eventing/windows'; -import { PageView } from '../../components/page_view'; +import { PageView, PageViewHeaderTitle } from '../../components/page_view'; +import { AppAction } from '../../types'; +import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; +import { AgentsSummary } from './agents_summary'; +import { VerticalDivider } from './vertical_divider'; export const PolicyDetails = React.memo(() => { + const dispatch = useDispatch<(action: AppAction) => void>(); + const { notifications, services } = useKibana(); + const history = useHistory(); + + // Store values const policyItem = usePolicyDetailsSelector(policyDetails); + const policyAgentStatusSummary = usePolicyDetailsSelector(agentStatusSummary); + const policyUpdateStatus = usePolicyDetailsSelector(updateStatus); + const isPolicyLoading = usePolicyDetailsSelector(isLoading); + const policyApiError = usePolicyDetailsSelector(apiError); + + // Local state + const [showConfirm, setShowConfirm] = useState(false); + const policyName = policyItem?.name ?? ''; - const headerLeftContent = - policyItem?.name ?? - i18n.translate('xpack.endpoint.policyDetails.notFound', { - defaultMessage: 'Policy Not Found', + // Handle showing udpate statuses + useEffect(() => { + if (policyUpdateStatus) { + if (policyUpdateStatus.success) { + notifications.toasts.success({ + toastLifeTimeMs: 10000, + title: i18n.translate('xpack.endpoint.policy.details.updateSuccessTitle', { + defaultMessage: 'Success!', + }), + body: ( + + ), + }); + } else { + notifications.toasts.danger({ + toastLifeTimeMs: 10000, + title: i18n.translate('xpack.endpoint.policy.details.updateErrorTitle', { + defaultMessage: 'Failed!', + }), + body: <>{policyUpdateStatus.error!.message}, + }); + } + } + }, [notifications.toasts, policyItem, policyName, policyUpdateStatus]); + + const handleBackToListOnClick = useCallback( + ev => { + ev.preventDefault(); + history.push(`/policy`); + }, + [history] + ); + + const handleSaveOnClick = useCallback(() => { + setShowConfirm(true); + }, []); + + const handleSaveConfirmation = useCallback(() => { + dispatch({ + type: 'userClickedPolicyDetailsSaveButton', }); + setShowConfirm(false); + }, [dispatch]); + + const handleSaveCancel = useCallback(() => { + setShowConfirm(false); + }, []); + + // Before proceeding - check if we have a policy data. + // If not, and we are still loading, show spinner. + // Else, if we have an error, then show error on the page. + if (!policyItem) { + return ( + + {isPolicyLoading ? ( + + ) : policyApiError ? ( + + {policyApiError?.message} + + ) : null} + + ); + } + + const headerLeftContent = ( +
+ {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} + + + + {policyItem.name} +
+ ); const headerRightContent = ( - + + + + + + + - + @@ -45,18 +167,85 @@ export const PolicyDetails = React.memo(() => { ); return ( - - -

- -

-
- - -
+ <> + {showConfirm && ( + + )} + + +

+ +

+
+ + +
+ + ); +}); + +const ConfirmUpdate = React.memo<{ + hostCount: number; + onConfirm: () => void; + onCancel: () => void; +}>(({ hostCount, onCancel, onConfirm }) => { + return ( + + + {hostCount > 0 && ( + <> + + + + + + )} +

+ +

+
+
); }); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/eventing/checkbox.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/eventing/checkbox.tsx index add137ea57a5e..8b7fb89ed1646 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/eventing/checkbox.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/eventing/checkbox.tsx @@ -27,7 +27,11 @@ export const EventingCheckbox: React.FC<{ (event: React.ChangeEvent) => { if (policyDetailsConfig) { const newPayload = clone(policyDetailsConfig); - newPayload[os].eventing[protectionField] = event.target.checked; + if (os === OS.linux || os === OS.mac) { + newPayload[os].events.process = event.target.checked; + } else { + newPayload[os].events[protectionField] = event.target.checked; + } dispatch({ type: 'userChangedPolicyConfig', diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_list.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_list.tsx index 7af302de8576e..5ee1539ce9788 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_list.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_list.tsx @@ -151,6 +151,7 @@ export const PolicyList = React.memo(() => { return ( ` + width: 0; + height: 100%; + border-left: ${props => { + return props.theme.eui.euiBorderThin; + }}; + margin-left: ${props => props.theme.eui.paddingSizes[props?.spacing ?? 'none'] || 0}; + margin-right: ${props => props.theme.eui.paddingSizes[props?.spacing ?? 'none'] || 0}; +`; diff --git a/x-pack/legacy/plugins/grokdebugger/README.md b/x-pack/plugins/grokdebugger/README.md similarity index 100% rename from x-pack/legacy/plugins/grokdebugger/README.md rename to x-pack/plugins/grokdebugger/README.md diff --git a/x-pack/legacy/plugins/grokdebugger/common/constants/editor.js b/x-pack/plugins/grokdebugger/common/constants/editor.js similarity index 100% rename from x-pack/legacy/plugins/grokdebugger/common/constants/editor.js rename to x-pack/plugins/grokdebugger/common/constants/editor.js diff --git a/x-pack/legacy/plugins/grokdebugger/common/constants/index.js b/x-pack/plugins/grokdebugger/common/constants/index.js similarity index 100% rename from x-pack/legacy/plugins/grokdebugger/common/constants/index.js rename to x-pack/plugins/grokdebugger/common/constants/index.js diff --git a/x-pack/legacy/plugins/grokdebugger/common/constants/plugin.js b/x-pack/plugins/grokdebugger/common/constants/plugin.js similarity index 100% rename from x-pack/legacy/plugins/grokdebugger/common/constants/plugin.js rename to x-pack/plugins/grokdebugger/common/constants/plugin.js diff --git a/x-pack/legacy/plugins/grokdebugger/common/constants/routes.js b/x-pack/plugins/grokdebugger/common/constants/routes.js similarity index 100% rename from x-pack/legacy/plugins/grokdebugger/common/constants/routes.js rename to x-pack/plugins/grokdebugger/common/constants/routes.js diff --git a/x-pack/plugins/grokdebugger/kibana.json b/x-pack/plugins/grokdebugger/kibana.json new file mode 100644 index 0000000000000..4d37f9ccdb0de --- /dev/null +++ b/x-pack/plugins/grokdebugger/kibana.json @@ -0,0 +1,13 @@ +{ + "id": "grokdebugger", + "version": "8.0.0", + "kibanaVersion": "kibana", + "requiredPlugins": [ + "licensing", + "home", + "devTools" + ], + "server": true, + "ui": true, + "configPath": ["xpack", "grokdebugger"] +} diff --git a/x-pack/legacy/plugins/grokdebugger/public/components/custom_patterns_input/custom_patterns_input.js b/x-pack/plugins/grokdebugger/public/components/custom_patterns_input/custom_patterns_input.js similarity index 100% rename from x-pack/legacy/plugins/grokdebugger/public/components/custom_patterns_input/custom_patterns_input.js rename to x-pack/plugins/grokdebugger/public/components/custom_patterns_input/custom_patterns_input.js diff --git a/x-pack/legacy/plugins/grokdebugger/public/components/custom_patterns_input/index.js b/x-pack/plugins/grokdebugger/public/components/custom_patterns_input/index.js similarity index 100% rename from x-pack/legacy/plugins/grokdebugger/public/components/custom_patterns_input/index.js rename to x-pack/plugins/grokdebugger/public/components/custom_patterns_input/index.js diff --git a/x-pack/legacy/plugins/grokdebugger/public/components/event_input/event_input.js b/x-pack/plugins/grokdebugger/public/components/event_input/event_input.js similarity index 100% rename from x-pack/legacy/plugins/grokdebugger/public/components/event_input/event_input.js rename to x-pack/plugins/grokdebugger/public/components/event_input/event_input.js diff --git a/x-pack/legacy/plugins/grokdebugger/public/components/event_input/index.js b/x-pack/plugins/grokdebugger/public/components/event_input/index.js similarity index 100% rename from x-pack/legacy/plugins/grokdebugger/public/components/event_input/index.js rename to x-pack/plugins/grokdebugger/public/components/event_input/index.js diff --git a/x-pack/legacy/plugins/grokdebugger/public/components/event_output/event_output.js b/x-pack/plugins/grokdebugger/public/components/event_output/event_output.js similarity index 100% rename from x-pack/legacy/plugins/grokdebugger/public/components/event_output/event_output.js rename to x-pack/plugins/grokdebugger/public/components/event_output/event_output.js diff --git a/x-pack/legacy/plugins/grokdebugger/public/components/event_output/index.js b/x-pack/plugins/grokdebugger/public/components/event_output/index.js similarity index 100% rename from x-pack/legacy/plugins/grokdebugger/public/components/event_output/index.js rename to x-pack/plugins/grokdebugger/public/components/event_output/index.js diff --git a/x-pack/legacy/plugins/grokdebugger/public/components/grok_debugger/brace_imports.ts b/x-pack/plugins/grokdebugger/public/components/grok_debugger/brace_imports.ts similarity index 100% rename from x-pack/legacy/plugins/grokdebugger/public/components/grok_debugger/brace_imports.ts rename to x-pack/plugins/grokdebugger/public/components/grok_debugger/brace_imports.ts diff --git a/x-pack/legacy/plugins/grokdebugger/public/components/grok_debugger/grok_debugger.js b/x-pack/plugins/grokdebugger/public/components/grok_debugger/grok_debugger.js similarity index 90% rename from x-pack/legacy/plugins/grokdebugger/public/components/grok_debugger/grok_debugger.js rename to x-pack/plugins/grokdebugger/public/components/grok_debugger/grok_debugger.js index dfdd524b3d3d4..c27f3314e60ae 100644 --- a/x-pack/legacy/plugins/grokdebugger/public/components/grok_debugger/grok_debugger.js +++ b/x-pack/plugins/grokdebugger/public/components/grok_debugger/grok_debugger.js @@ -22,10 +22,10 @@ import { PatternInput } from '../pattern_input'; import { CustomPatternsInput } from '../custom_patterns_input'; import { EventOutput } from '../event_output'; import { GrokdebuggerRequest } from '../../models/grokdebugger_request'; -import { toastNotifications } from 'ui/notify'; +import { withKibana } from '../../../../../../src/plugins/kibana_react/public'; import { FormattedMessage } from '@kbn/i18n/react'; -export class GrokDebugger extends React.Component { +export class GrokDebuggerComponent extends React.Component { constructor(props) { super(props); this.state = { @@ -73,6 +73,7 @@ export class GrokDebugger extends React.Component { }; simulateGrok = async () => { + const notifications = this.props.kibana.services.notifications; try { const simulateResponse = await this.props.grokdebuggerService.simulate( this.grokdebuggerRequest @@ -82,10 +83,14 @@ export class GrokDebugger extends React.Component { }); if (!isEmpty(simulateResponse.error)) { - toastNotifications.addDanger(simulateResponse.error); + notifications.toasts.addDanger({ + body: simulateResponse.error, + }); } } catch (e) { - toastNotifications.addDanger(e); + notifications.toasts.addDanger({ + body: e, + }); } }; @@ -138,3 +143,5 @@ export class GrokDebugger extends React.Component { ); } } + +export const GrokDebugger = withKibana(GrokDebuggerComponent); diff --git a/x-pack/legacy/plugins/grokdebugger/public/components/grok_debugger/index.js b/x-pack/plugins/grokdebugger/public/components/grok_debugger/index.js similarity index 100% rename from x-pack/legacy/plugins/grokdebugger/public/components/grok_debugger/index.js rename to x-pack/plugins/grokdebugger/public/components/grok_debugger/index.js diff --git a/x-pack/plugins/grokdebugger/public/components/inactive_license.js b/x-pack/plugins/grokdebugger/public/components/inactive_license.js new file mode 100644 index 0000000000000..ff0306b789190 --- /dev/null +++ b/x-pack/plugins/grokdebugger/public/components/inactive_license.js @@ -0,0 +1,91 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { + EuiCallOut, + EuiText, + EuiLink, + EuiCode, + EuiPage, + EuiPageBody, + EuiPageContent, + EuiPageContentBody, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; + +export const InactiveLicenseSlate = () => { + const registerLicenseLinkLabel = i18n.translate('xpack.grokDebugger.registerLicenseLinkLabel', { + defaultMessage: 'register a license', + }); + + const trialLicense = i18n.translate('xpack.grokDebugger.trialLicenseTitle', { + defaultMessage: 'Trial', + }); + + const basicLicense = i18n.translate('xpack.grokDebugger.basicLicenseTitle', { + defaultMessage: 'Basic', + }); + + const goldLicense = i18n.translate('xpack.grokDebugger.goldLicenseTitle', { + defaultMessage: 'Gold', + }); + + const platinumLicense = i18n.translate('xpack.grokDebugger.platinumLicenseTitle', { + defaultMessage: 'Platinum', + }); + + return ( + + + + + + +

+ + {trialLicense}, {basicLicense},{' '} + {goldLicense} + + ), + platinumLicenseType: {platinumLicense}, + }} + /> +

+

+ + {registerLicenseLinkLabel} + + ), + }} + /> +

+
+
+
+
+
+
+ ); +}; diff --git a/x-pack/legacy/plugins/grokdebugger/public/components/pattern_input/index.js b/x-pack/plugins/grokdebugger/public/components/pattern_input/index.js similarity index 100% rename from x-pack/legacy/plugins/grokdebugger/public/components/pattern_input/index.js rename to x-pack/plugins/grokdebugger/public/components/pattern_input/index.js diff --git a/x-pack/legacy/plugins/grokdebugger/public/components/pattern_input/pattern_input.js b/x-pack/plugins/grokdebugger/public/components/pattern_input/pattern_input.js similarity index 100% rename from x-pack/legacy/plugins/grokdebugger/public/components/pattern_input/pattern_input.js rename to x-pack/plugins/grokdebugger/public/components/pattern_input/pattern_input.js diff --git a/x-pack/legacy/plugins/canvas/types/webpack.d.ts b/x-pack/plugins/grokdebugger/public/index.js similarity index 67% rename from x-pack/legacy/plugins/canvas/types/webpack.d.ts rename to x-pack/plugins/grokdebugger/public/index.js index 8158071e76f08..960c9d8d58e4a 100644 --- a/x-pack/legacy/plugins/canvas/types/webpack.d.ts +++ b/x-pack/plugins/grokdebugger/public/index.js @@ -4,9 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -declare module '*.png'; +import { Plugin } from './plugin'; -declare module '*.svg' { - const content: string; - export = content; +export function plugin(initializerContext) { + return new Plugin(initializerContext); } diff --git a/x-pack/legacy/plugins/grokdebugger/public/lib/ace/grok_highlight_rules.js b/x-pack/plugins/grokdebugger/public/lib/ace/grok_highlight_rules.js similarity index 97% rename from x-pack/legacy/plugins/grokdebugger/public/lib/ace/grok_highlight_rules.js rename to x-pack/plugins/grokdebugger/public/lib/ace/grok_highlight_rules.js index 3da1afdc0fc00..1851d53ff0ee8 100644 --- a/x-pack/legacy/plugins/grokdebugger/public/lib/ace/grok_highlight_rules.js +++ b/x-pack/plugins/grokdebugger/public/lib/ace/grok_highlight_rules.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import ace from 'ace'; +import ace from 'brace'; const { TextHighlightRules } = ace.acequire('ace/mode/text_highlight_rules'); diff --git a/x-pack/legacy/plugins/grokdebugger/public/lib/ace/grok_mode.js b/x-pack/plugins/grokdebugger/public/lib/ace/grok_mode.js similarity index 95% rename from x-pack/legacy/plugins/grokdebugger/public/lib/ace/grok_mode.js rename to x-pack/plugins/grokdebugger/public/lib/ace/grok_mode.js index 84b2e482673c6..cd82a5b9917e6 100644 --- a/x-pack/legacy/plugins/grokdebugger/public/lib/ace/grok_mode.js +++ b/x-pack/plugins/grokdebugger/public/lib/ace/grok_mode.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import ace from 'ace'; +import ace from 'brace'; import { GrokHighlightRules } from './grok_highlight_rules'; const TextMode = ace.acequire('ace/mode/text').Mode; diff --git a/x-pack/legacy/plugins/grokdebugger/public/lib/ace/index.js b/x-pack/plugins/grokdebugger/public/lib/ace/index.js similarity index 100% rename from x-pack/legacy/plugins/grokdebugger/public/lib/ace/index.js rename to x-pack/plugins/grokdebugger/public/lib/ace/index.js diff --git a/x-pack/legacy/plugins/grokdebugger/public/models/grokdebugger_request/grokdebugger_request.js b/x-pack/plugins/grokdebugger/public/models/grokdebugger_request/grokdebugger_request.js similarity index 100% rename from x-pack/legacy/plugins/grokdebugger/public/models/grokdebugger_request/grokdebugger_request.js rename to x-pack/plugins/grokdebugger/public/models/grokdebugger_request/grokdebugger_request.js diff --git a/x-pack/legacy/plugins/grokdebugger/public/models/grokdebugger_request/index.js b/x-pack/plugins/grokdebugger/public/models/grokdebugger_request/index.js similarity index 100% rename from x-pack/legacy/plugins/grokdebugger/public/models/grokdebugger_request/index.js rename to x-pack/plugins/grokdebugger/public/models/grokdebugger_request/index.js diff --git a/x-pack/legacy/plugins/grokdebugger/public/models/grokdebugger_response/grokdebugger_response.js b/x-pack/plugins/grokdebugger/public/models/grokdebugger_response/grokdebugger_response.js similarity index 100% rename from x-pack/legacy/plugins/grokdebugger/public/models/grokdebugger_response/grokdebugger_response.js rename to x-pack/plugins/grokdebugger/public/models/grokdebugger_response/grokdebugger_response.js diff --git a/x-pack/legacy/plugins/grokdebugger/public/models/grokdebugger_response/index.js b/x-pack/plugins/grokdebugger/public/models/grokdebugger_response/index.js similarity index 100% rename from x-pack/legacy/plugins/grokdebugger/public/models/grokdebugger_response/index.js rename to x-pack/plugins/grokdebugger/public/models/grokdebugger_response/index.js diff --git a/x-pack/plugins/grokdebugger/public/plugin.js b/x-pack/plugins/grokdebugger/public/plugin.js new file mode 100644 index 0000000000000..9de4f9c3f8bc0 --- /dev/null +++ b/x-pack/plugins/grokdebugger/public/plugin.js @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { first } from 'rxjs/operators'; +import { registerFeature } from './register_feature'; +import { PLUGIN } from '../common/constants'; + +export class Plugin { + setup(coreSetup, plugins) { + registerFeature(plugins.home); + + plugins.devTools.register({ + order: 6, + title: i18n.translate('xpack.grokDebugger.displayName', { + defaultMessage: 'Grok Debugger', + }), + id: PLUGIN.ID, + enableRouting: false, + async mount(context, { element }) { + const [coreStart] = await coreSetup.getStartServices(); + const license = await plugins.licensing.license$.pipe(first()).toPromise(); + const { renderApp } = await import('./render_app'); + return renderApp(license, element, coreStart); + }, + }); + } + + start() {} + + stop() {} +} diff --git a/x-pack/plugins/grokdebugger/public/register_feature.ts b/x-pack/plugins/grokdebugger/public/register_feature.ts new file mode 100644 index 0000000000000..f05d4c973a5fb --- /dev/null +++ b/x-pack/plugins/grokdebugger/public/register_feature.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { + FeatureCatalogueCategory, + HomePublicPluginSetup, +} from '../../../../src/plugins/home/public'; + +export const registerFeature = (homePlugin: HomePublicPluginSetup) => { + homePlugin.featureCatalogue.register({ + id: 'grokdebugger', + title: i18n.translate('xpack.grokDebugger.registryProviderTitle', { + defaultMessage: 'Grok Debugger', + }), + description: i18n.translate('xpack.grokDebugger.registryProviderDescription', { + defaultMessage: 'Simulate and debug grok patterns for data transformation on ingestion.', + }), + icon: 'grokApp', + path: '/app/kibana#/dev_tools/grokdebugger', + showOnHomePage: false, + category: FeatureCatalogueCategory.ADMIN, + }); +}; diff --git a/x-pack/legacy/plugins/grokdebugger/public/render_app.js b/x-pack/plugins/grokdebugger/public/render_app.js similarity index 51% rename from x-pack/legacy/plugins/grokdebugger/public/render_app.js rename to x-pack/plugins/grokdebugger/public/render_app.js index 2c4894bb70ee1..82783c7bd9795 100644 --- a/x-pack/legacy/plugins/grokdebugger/public/render_app.js +++ b/x-pack/plugins/grokdebugger/public/render_app.js @@ -10,13 +10,23 @@ import { render, unmountComponentAtNode } from 'react-dom'; import { GrokDebugger } from './components/grok_debugger'; import { GrokdebuggerService } from './services/grokdebugger/grokdebugger_service'; import { I18nProvider } from '@kbn/i18n/react'; +import { KibanaContextProvider } from '../../../../src/plugins/kibana_react/public'; +import { InactiveLicenseSlate } from './components/inactive_license'; -export function renderApp(element, npStart) { - render( +export function renderApp(license, element, coreStart) { + const content = license.isActive ? ( + + + + + + ) : ( - - , - element + + ); + + render(content, element); + return () => unmountComponentAtNode(element); } diff --git a/x-pack/legacy/plugins/grokdebugger/public/services/grokdebugger/grokdebugger_service.js b/x-pack/plugins/grokdebugger/public/services/grokdebugger/grokdebugger_service.js similarity index 78% rename from x-pack/legacy/plugins/grokdebugger/public/services/grokdebugger/grokdebugger_service.js rename to x-pack/plugins/grokdebugger/public/services/grokdebugger/grokdebugger_service.js index c6d28adcefd78..e26c9c5091e14 100644 --- a/x-pack/legacy/plugins/grokdebugger/public/services/grokdebugger/grokdebugger_service.js +++ b/x-pack/plugins/grokdebugger/public/services/grokdebugger/grokdebugger_service.js @@ -5,7 +5,7 @@ */ import { ROUTES } from '../../../common/constants'; -import { GrokdebuggerResponse } from 'plugins/grokdebugger/models/grokdebugger_response'; +import { GrokdebuggerResponse } from '../../models/grokdebugger_response'; export class GrokdebuggerService { constructor(http) { @@ -18,7 +18,7 @@ export class GrokdebuggerService { body: JSON.stringify(grokdebuggerRequest.upstreamJSON), }) .then(response => { - return GrokdebuggerResponse.fromUpstreamJSON(response.grokdebuggerResponse); + return GrokdebuggerResponse.fromUpstreamJSON(response); }) .catch(e => { throw e.body.message; diff --git a/x-pack/legacy/plugins/grokdebugger/server/lib/error_wrappers/index.js b/x-pack/plugins/grokdebugger/server/index.js similarity index 66% rename from x-pack/legacy/plugins/grokdebugger/server/lib/error_wrappers/index.js rename to x-pack/plugins/grokdebugger/server/index.js index 3756b0c74fb10..960c9d8d58e4a 100644 --- a/x-pack/legacy/plugins/grokdebugger/server/lib/error_wrappers/index.js +++ b/x-pack/plugins/grokdebugger/server/index.js @@ -4,4 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -export { wrapEsError } from './wrap_es_error'; +import { Plugin } from './plugin'; + +export function plugin(initializerContext) { + return new Plugin(initializerContext); +} diff --git a/x-pack/plugins/grokdebugger/server/lib/kibana_framework.ts b/x-pack/plugins/grokdebugger/server/lib/kibana_framework.ts new file mode 100644 index 0000000000000..749f5e9ebf8f0 --- /dev/null +++ b/x-pack/plugins/grokdebugger/server/lib/kibana_framework.ts @@ -0,0 +1,105 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable @typescript-eslint/array-type */ + +import { i18n } from '@kbn/i18n'; + +import { + CoreSetup, + IRouter, + RequestHandlerContext, + RouteMethod, + RouteConfig, + RequestHandler, +} from 'src/core/server'; + +import { ILicense } from '../../../licensing/server'; + +type GrokDebuggerRouteConfig = { + method: RouteMethod; +} & RouteConfig; + +export class KibanaFramework { + public router: IRouter; + public license?: ILicense; + + constructor(core: CoreSetup) { + this.router = core.http.createRouter(); + } + + public setLicense(license: ILicense) { + this.license = license; + } + + private hasActiveLicense() { + if (!this.license) { + throw new Error( + "Please set license information in the plugin's setup method before trying to check the status" + ); + } + return this.license.isActive; + } + + public registerRoute( + config: GrokDebuggerRouteConfig, + handler: RequestHandler + ) { + // Automatically wrap all route registrations with license checking + const wrappedHandler: RequestHandler = async ( + requestContext, + request, + response + ) => { + if (this.hasActiveLicense()) { + return await handler(requestContext, request, response); + } else { + return response.forbidden({ + body: i18n.translate('xpack.grokDebugger.serverInactiveLicenseError', { + defaultMessage: 'The Grok Debugger tool requires an active license.', + }), + }); + } + }; + + const routeConfig = { + path: config.path, + validate: config.validate, + }; + + switch (config.method) { + case 'get': + this.router.get(routeConfig, wrappedHandler); + break; + case 'post': + this.router.post(routeConfig, wrappedHandler); + break; + case 'delete': + this.router.delete(routeConfig, wrappedHandler); + break; + case 'put': + this.router.put(routeConfig, wrappedHandler); + break; + } + } + + callWithRequest( + requestContext: RequestHandlerContext, + endpoint: 'ingest.simulate', + options?: { + body: any; + } + ): Promise; + + public async callWithRequest( + requestContext: RequestHandlerContext, + endpoint: string, + options?: any + ) { + const { elasticsearch } = requestContext.core; + return elasticsearch.dataClient.callAsCurrentUser(endpoint, options); + } +} diff --git a/x-pack/legacy/plugins/grokdebugger/server/models/grokdebugger_request/__tests__/grokdebugger_request.js b/x-pack/plugins/grokdebugger/server/models/grokdebugger_request/__tests__/grokdebugger_request.js similarity index 100% rename from x-pack/legacy/plugins/grokdebugger/server/models/grokdebugger_request/__tests__/grokdebugger_request.js rename to x-pack/plugins/grokdebugger/server/models/grokdebugger_request/__tests__/grokdebugger_request.js diff --git a/x-pack/legacy/plugins/grokdebugger/server/models/grokdebugger_request/grokdebugger_request.js b/x-pack/plugins/grokdebugger/server/models/grokdebugger_request/grokdebugger_request.js similarity index 100% rename from x-pack/legacy/plugins/grokdebugger/server/models/grokdebugger_request/grokdebugger_request.js rename to x-pack/plugins/grokdebugger/server/models/grokdebugger_request/grokdebugger_request.js diff --git a/x-pack/legacy/plugins/grokdebugger/server/models/grokdebugger_request/index.js b/x-pack/plugins/grokdebugger/server/models/grokdebugger_request/index.js similarity index 100% rename from x-pack/legacy/plugins/grokdebugger/server/models/grokdebugger_request/index.js rename to x-pack/plugins/grokdebugger/server/models/grokdebugger_request/index.js diff --git a/x-pack/legacy/plugins/grokdebugger/server/models/grokdebugger_response/__tests__/grokdebugger_response.js b/x-pack/plugins/grokdebugger/server/models/grokdebugger_response/__tests__/grokdebugger_response.js similarity index 100% rename from x-pack/legacy/plugins/grokdebugger/server/models/grokdebugger_response/__tests__/grokdebugger_response.js rename to x-pack/plugins/grokdebugger/server/models/grokdebugger_response/__tests__/grokdebugger_response.js diff --git a/x-pack/legacy/plugins/grokdebugger/server/models/grokdebugger_response/grokdebugger_response.js b/x-pack/plugins/grokdebugger/server/models/grokdebugger_response/grokdebugger_response.js similarity index 100% rename from x-pack/legacy/plugins/grokdebugger/server/models/grokdebugger_response/grokdebugger_response.js rename to x-pack/plugins/grokdebugger/server/models/grokdebugger_response/grokdebugger_response.js diff --git a/x-pack/legacy/plugins/grokdebugger/server/models/grokdebugger_response/index.js b/x-pack/plugins/grokdebugger/server/models/grokdebugger_response/index.js similarity index 100% rename from x-pack/legacy/plugins/grokdebugger/server/models/grokdebugger_response/index.js rename to x-pack/plugins/grokdebugger/server/models/grokdebugger_response/index.js diff --git a/x-pack/plugins/grokdebugger/server/plugin.js b/x-pack/plugins/grokdebugger/server/plugin.js new file mode 100644 index 0000000000000..06ddd92aefac9 --- /dev/null +++ b/x-pack/plugins/grokdebugger/server/plugin.js @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; +import { KibanaFramework } from './lib/kibana_framework'; +import { registerGrokdebuggerRoutes } from './routes/api/grokdebugger'; + +export const config = { + schema: schema.object({ + enabled: schema.boolean({ defaultValue: true }), + }), +}; + +export class Plugin { + setup(coreSetup, plugins) { + const framework = new KibanaFramework(coreSetup); + + plugins.licensing.license$.subscribe(license => { + framework.setLicense(license); + }); + + registerGrokdebuggerRoutes(framework); + } + + start() {} + stop() {} +} diff --git a/x-pack/legacy/plugins/grokdebugger/server/routes/api/grokdebugger/index.js b/x-pack/plugins/grokdebugger/server/routes/api/grokdebugger/index.js similarity index 100% rename from x-pack/legacy/plugins/grokdebugger/server/routes/api/grokdebugger/index.js rename to x-pack/plugins/grokdebugger/server/routes/api/grokdebugger/index.js diff --git a/x-pack/plugins/grokdebugger/server/routes/api/grokdebugger/register_grok_simulate_route.js b/x-pack/plugins/grokdebugger/server/routes/api/grokdebugger/register_grok_simulate_route.js new file mode 100644 index 0000000000000..f953bc64c3b4f --- /dev/null +++ b/x-pack/plugins/grokdebugger/server/routes/api/grokdebugger/register_grok_simulate_route.js @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; +import { GrokdebuggerRequest } from '../../../models/grokdebugger_request'; +import { GrokdebuggerResponse } from '../../../models/grokdebugger_response'; + +const requestBodySchema = schema.object({ + pattern: schema.string(), + rawEvent: schema.string(), + // We don't know these key / values up front as they depend on user input + customPatterns: schema.object({}, { unknowns: 'allow' }), +}); + +export function registerGrokSimulateRoute(framework) { + framework.registerRoute( + { + method: 'post', + path: '/api/grokdebugger/simulate', + validate: { + body: requestBodySchema, + }, + }, + async (requestContext, request, response) => { + try { + const grokdebuggerRequest = GrokdebuggerRequest.fromDownstreamJSON(request.body); + const simulateResponseFromES = await framework.callWithRequest( + requestContext, + 'ingest.simulate', + { body: grokdebuggerRequest.upstreamJSON } + ); + const grokdebuggerResponse = GrokdebuggerResponse.fromUpstreamJSON(simulateResponseFromES); + return response.ok({ + body: grokdebuggerResponse, + }); + } catch (error) { + return response.internalError({ + body: error.message, + }); + } + } + ); +} diff --git a/x-pack/legacy/plugins/grokdebugger/server/routes/api/grokdebugger/register_grokdebugger_routes.js b/x-pack/plugins/grokdebugger/server/routes/api/grokdebugger/register_grokdebugger_routes.js similarity index 77% rename from x-pack/legacy/plugins/grokdebugger/server/routes/api/grokdebugger/register_grokdebugger_routes.js rename to x-pack/plugins/grokdebugger/server/routes/api/grokdebugger/register_grokdebugger_routes.js index 6d27ce2ca0545..d7ecd1c360d81 100644 --- a/x-pack/legacy/plugins/grokdebugger/server/routes/api/grokdebugger/register_grokdebugger_routes.js +++ b/x-pack/plugins/grokdebugger/server/routes/api/grokdebugger/register_grokdebugger_routes.js @@ -6,6 +6,6 @@ import { registerGrokSimulateRoute } from './register_grok_simulate_route'; -export function registerGrokdebuggerRoutes(server) { - registerGrokSimulateRoute(server); +export function registerGrokdebuggerRoutes(framework) { + registerGrokSimulateRoute(framework); } diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 447a3b706bf96..aea6e2ab00094 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -5776,14 +5776,10 @@ "xpack.grokDebugger.customPatternsButtonLabel": "カスタムパターン", "xpack.grokDebugger.displayName": "Grok デバッガー", "xpack.grokDebugger.grokPatternLabel": "Grok パターン", - "xpack.grokDebugger.licenseHasExpiredMessage": "{licenseType} ライセンスが期限切れのため {grokLogParsingTool} デバッガーを使用できません", "xpack.grokDebugger.patternsErrorMessage": "提供された {grokLogParsingTool} パターンがインプットのデータと一致していません", - "xpack.grokDebugger.registryProviderDescription": "{grokLogParsingTool} パターンを、投入時にデータ変換用にシミュレートしデバッグします。", - "xpack.grokDebugger.registryProviderTitle": "{grokLogParsingTool} デバッガー", "xpack.grokDebugger.sampleDataLabel": "サンプルデータ", "xpack.grokDebugger.simulateButtonLabel": "シミュレート", "xpack.grokDebugger.structuredDataLabel": "構造化データ", - "xpack.grokDebugger.unavailableLicenseInformationMessage": "現在ライセンス情報が利用できないため {grokLogParsingTool} デバッガーを使用できません。", "xpack.idxMgmt.appTitle": "インデックス管理", "xpack.idxMgmt.badgeAriaLabel": "{label}。これをフィルタリングするよう選択。", "xpack.idxMgmt.breadcrumb.cloneTemplateLabel": "テンプレートのクローンを作成", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 5ca4f609a6dcd..dfc5ef065732e 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -5776,14 +5776,10 @@ "xpack.grokDebugger.customPatternsButtonLabel": "自定义模式", "xpack.grokDebugger.displayName": "Grok Debugger", "xpack.grokDebugger.grokPatternLabel": "Grok 模式", - "xpack.grokDebugger.licenseHasExpiredMessage": "您不能使用 {grokLogParsingTool} Debugger,因为您的 {licenseType} 许可证已过期。", "xpack.grokDebugger.patternsErrorMessage": "提供的 {grokLogParsingTool} 模式不匹配输入中的数据", - "xpack.grokDebugger.registryProviderDescription": "采集时模拟和调试用于数据转换的 {grokLogParsingTool} 模式。", - "xpack.grokDebugger.registryProviderTitle": "{grokLogParsingTool} Debugger", "xpack.grokDebugger.sampleDataLabel": "样例数据", "xpack.grokDebugger.simulateButtonLabel": "模拟", "xpack.grokDebugger.structuredDataLabel": "结构化数据", - "xpack.grokDebugger.unavailableLicenseInformationMessage": "您不能使用 {grokLogParsingTool} Debugger,因为许可证信息当前不可用。", "xpack.idxMgmt.appTitle": "索引管理", "xpack.idxMgmt.badgeAriaLabel": "{label}。选择以基于此选项进行筛选。", "xpack.idxMgmt.breadcrumb.cloneTemplateLabel": "克隆模板", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty.svg b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty.svg new file mode 100644 index 0000000000000..b11dcb3570c26 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty.svg @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty.test.tsx index bb06f7961b20b..7da97b9fe3436 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty.test.tsx @@ -30,7 +30,7 @@ beforeAll(() => { describe('actionTypeRegistry.get() works', () => { test('action type static data is as expected', () => { expect(actionTypeModel.id).toEqual(ACTION_TYPE_ID); - expect(actionTypeModel.iconClass).toEqual('apps'); + expect(actionTypeModel.iconClass).toEqual('test-file-stub'); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty.tsx index 947d098c46483..da324050f3242 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty.tsx @@ -25,11 +25,12 @@ import { ActionParamsProps, } from '../../../types'; import { PagerDutyActionParams, PagerDutyActionConnector } from './types'; +import pagerDutySvg from './pagerduty.svg'; export function getActionType(): ActionTypeModel { return { id: '.pagerduty', - iconClass: 'apps', + iconClass: pagerDutySvg, selectMessage: i18n.translate( 'xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.selectMessageText', { diff --git a/x-pack/typings/index.d.ts b/x-pack/typings/index.d.ts index 7696ddbfd73cb..1bf1370ad467f 100644 --- a/x-pack/typings/index.d.ts +++ b/x-pack/typings/index.d.ts @@ -10,6 +10,18 @@ declare module '*.html' { export default template; } +declare module '*.png' { + const content: string; + // eslint-disable-next-line import/no-default-export + export default content; +} + +declare module '*.svg' { + const content: string; + // eslint-disable-next-line import/no-default-export + export default content; +} + declare module 'lodash/internal/toPath' { function toPath(value: string | string[]): string[]; export = toPath;