diff --git a/.eslintignore b/.eslintignore index c4fb806b6d394..86a01b68ecab1 100644 --- a/.eslintignore +++ b/.eslintignore @@ -9,6 +9,7 @@ bower_components /built_assets /html_docs /src/plugins/data/common/es_query/kuery/ast/_generated_/** +/src/legacy/core_plugins/vis_type_timelion/public/_generated_/** src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data /src/legacy/ui/public/angular-bootstrap /src/legacy/ui/public/flot-charts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ed5721e8756e8..547776c4cfe66 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -15,6 +15,7 @@ /src/legacy/core_plugins/kibana/public/home/ @elastic/kibana-app /src/legacy/core_plugins/kibana/public/dev_tools/ @elastic/kibana-app /src/legacy/core_plugins/metrics/ @elastic/kibana-app +/src/legacy/core_plugins/vis_type_vislib/ @elastic/kibana-app /src/plugins/home/ @elastic/kibana-app /src/plugins/kibana_legacy/ @elastic/kibana-app /src/plugins/timelion/ @elastic/kibana-app @@ -128,6 +129,7 @@ # Kibana Alerting Services /x-pack/legacy/plugins/alerting/ @elastic/kibana-alerting-services /x-pack/legacy/plugins/actions/ @elastic/kibana-alerting-services +/x-pack/plugins/event_log/ @elastic/kibana-alerting-services /x-pack/plugins/task_manager/ @elastic/kibana-alerting-services /x-pack/test/alerting_api_integration/ @elastic/kibana-alerting-services /x-pack/test/plugin_api_integration/plugins/task_manager/ @elastic/kibana-alerting-services @@ -149,6 +151,6 @@ /x-pack/legacy/plugins/license_management/ @elastic/es-ui /x-pack/legacy/plugins/remote_clusters/ @elastic/es-ui /x-pack/legacy/plugins/rollup/ @elastic/es-ui -/x-pack/legacy/plugins/searchprofiler/ @elastic/es-ui +/x-pack/plugins/searchprofiler/ @elastic/es-ui /x-pack/legacy/plugins/snapshot_restore/ @elastic/es-ui /x-pack/legacy/plugins/watcher/ @elastic/es-ui diff --git a/.i18nrc.json b/.i18nrc.json index 73acf92cda149..907310b32e35c 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -21,7 +21,6 @@ "interpreter": "src/legacy/core_plugins/interpreter", "kbn": "src/legacy/core_plugins/kibana", "kbnDocViews": "src/legacy/core_plugins/kbn_doc_views", - "kbnVislibVisTypes": "src/legacy/core_plugins/vis_type_vislib", "management": ["src/legacy/core_plugins/management", "src/plugins/management"], "kibana_react": "src/legacy/core_plugins/kibana_react", "kibana-react": "src/plugins/kibana_react", @@ -41,6 +40,7 @@ "visTypeTagCloud": "src/legacy/core_plugins/vis_type_tagcloud", "visTypeTimeseries": "src/legacy/core_plugins/vis_type_timeseries", "visTypeVega": "src/legacy/core_plugins/vis_type_vega", + "visTypeVislib": "src/legacy/core_plugins/vis_type_vislib", "visualizations": [ "src/plugins/visualizations", "src/legacy/core_plugins/visualizations" @@ -50,4 +50,4 @@ "src/legacy/ui/ui_render/ui_render_mixin.js" ], "translations": [] -} \ No newline at end of file +} diff --git a/docs/developer/visualize/development-create-visualization.asciidoc b/docs/developer/visualize/development-create-visualization.asciidoc index faaa9b36a7a00..e38b76471ab25 100644 --- a/docs/developer/visualize/development-create-visualization.asciidoc +++ b/docs/developer/visualize/development-create-visualization.asciidoc @@ -62,7 +62,7 @@ The list of common parameters: - *visualization*: A constructor function for a Visualization. - *requestHandler*: one of the available request handlers or a for a custom request handler - *responseHandler*: one of the available response handlers or a for a custom response handler -- *editor*: one of the available editors or Editor class for custom one +- *editor*: Editor class for custom one - *editorConfig*: object holding editor parameters - *options.showTimePicker*: show or hide time filter (defaults to true) - *options.showQueryBar*: show or hide query bar (defaults to true) @@ -218,7 +218,6 @@ These tabs should be React components. title: 'My New Vis', icon: 'my_icon', description: 'Cool new chart', - editor: 'default', editorConfig: { optionsTemplate: MyReactComponent // or if multiple tabs are required: optionTabs: [ diff --git a/docs/development/core/public/kibana-plugin-public.appbase.category.md b/docs/development/core/public/kibana-plugin-public.appbase.category.md new file mode 100644 index 0000000000000..215ebbbd0e186 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.appbase.category.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppBase](./kibana-plugin-public.appbase.md) > [category](./kibana-plugin-public.appbase.category.md) + +## AppBase.category property + +The category definition of the product See [AppCategory](./kibana-plugin-public.appcategory.md) See DEFAULT\_APP\_CATEGORIES for more reference + +Signature: + +```typescript +category?: AppCategory; +``` diff --git a/docs/development/core/public/kibana-plugin-public.appbase.md b/docs/development/core/public/kibana-plugin-public.appbase.md index eb6d91cb92488..6f547450b6a12 100644 --- a/docs/development/core/public/kibana-plugin-public.appbase.md +++ b/docs/development/core/public/kibana-plugin-public.appbase.md @@ -16,6 +16,7 @@ export interface AppBase | Property | Type | Description | | --- | --- | --- | | [capabilities](./kibana-plugin-public.appbase.capabilities.md) | Partial<Capabilities> | Custom capabilities defined by the app. | +| [category](./kibana-plugin-public.appbase.category.md) | AppCategory | The category definition of the product See [AppCategory](./kibana-plugin-public.appcategory.md) See DEFAULT\_APP\_CATEGORIES for more reference | | [chromeless](./kibana-plugin-public.appbase.chromeless.md) | boolean | Hide the UI chrome when the application is mounted. Defaults to false. Takes precedence over chrome service visibility settings. | | [euiIconType](./kibana-plugin-public.appbase.euiicontype.md) | string | A EUI iconType that will be used for the app's icon. This icon takes precendence over the icon property. | | [icon](./kibana-plugin-public.appbase.icon.md) | string | A URL to an image file used as an icon. Used as a fallback if euiIconType is not provided. | diff --git a/docs/development/core/public/kibana-plugin-public.appcategory.arialabel.md b/docs/development/core/public/kibana-plugin-public.appcategory.arialabel.md new file mode 100644 index 0000000000000..0245b548ae74f --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.appcategory.arialabel.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppCategory](./kibana-plugin-public.appcategory.md) > [ariaLabel](./kibana-plugin-public.appcategory.arialabel.md) + +## AppCategory.ariaLabel property + +If the visual label isn't appropriate for screen readers, can override it here + +Signature: + +```typescript +ariaLabel?: string; +``` diff --git a/docs/development/core/public/kibana-plugin-public.appcategory.euiicontype.md b/docs/development/core/public/kibana-plugin-public.appcategory.euiicontype.md new file mode 100644 index 0000000000000..90133735a0082 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.appcategory.euiicontype.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppCategory](./kibana-plugin-public.appcategory.md) > [euiIconType](./kibana-plugin-public.appcategory.euiicontype.md) + +## AppCategory.euiIconType property + +Define an icon to be used for the category If the category is only 1 item, and no icon is defined, will default to the product icon Defaults to initials if no icon is defined + +Signature: + +```typescript +euiIconType?: string; +``` diff --git a/docs/development/core/public/kibana-plugin-public.appcategory.label.md b/docs/development/core/public/kibana-plugin-public.appcategory.label.md new file mode 100644 index 0000000000000..171b1627f9ef8 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.appcategory.label.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppCategory](./kibana-plugin-public.appcategory.md) > [label](./kibana-plugin-public.appcategory.label.md) + +## AppCategory.label property + +Label used for cateogry name. Also used as aria-label if one isn't set. + +Signature: + +```typescript +label: string; +``` diff --git a/docs/development/core/public/kibana-plugin-public.appcategory.md b/docs/development/core/public/kibana-plugin-public.appcategory.md new file mode 100644 index 0000000000000..f1085e7325272 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.appcategory.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppCategory](./kibana-plugin-public.appcategory.md) + +## AppCategory interface + +A category definition for nav links to know where to sort them in the left hand nav + +Signature: + +```typescript +export interface AppCategory +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [ariaLabel](./kibana-plugin-public.appcategory.arialabel.md) | string | If the visual label isn't appropriate for screen readers, can override it here | +| [euiIconType](./kibana-plugin-public.appcategory.euiicontype.md) | string | Define an icon to be used for the category If the category is only 1 item, and no icon is defined, will default to the product icon Defaults to initials if no icon is defined | +| [label](./kibana-plugin-public.appcategory.label.md) | string | Label used for cateogry name. Also used as aria-label if one isn't set. | +| [order](./kibana-plugin-public.appcategory.order.md) | number | The order that categories will be sorted in Prefer large steps between categories to allow for further editing (Default categories are in steps of 1000) | + diff --git a/docs/development/core/public/kibana-plugin-public.appcategory.order.md b/docs/development/core/public/kibana-plugin-public.appcategory.order.md new file mode 100644 index 0000000000000..ef17ac04b78d6 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.appcategory.order.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppCategory](./kibana-plugin-public.appcategory.md) > [order](./kibana-plugin-public.appcategory.order.md) + +## AppCategory.order property + +The order that categories will be sorted in Prefer large steps between categories to allow for further editing (Default categories are in steps of 1000) + +Signature: + +```typescript +order?: number; +``` diff --git a/docs/development/core/public/kibana-plugin-public.chromenavlink.category.md b/docs/development/core/public/kibana-plugin-public.chromenavlink.category.md new file mode 100644 index 0000000000000..19d5a43a29307 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.chromenavlink.category.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ChromeNavLink](./kibana-plugin-public.chromenavlink.md) > [category](./kibana-plugin-public.chromenavlink.category.md) + +## ChromeNavLink.category property + +The category the app lives in + +Signature: + +```typescript +readonly category?: AppCategory; +``` diff --git a/docs/development/core/public/kibana-plugin-public.chromenavlink.md b/docs/development/core/public/kibana-plugin-public.chromenavlink.md index 4cb9080222ac5..2afd6ce2d58c4 100644 --- a/docs/development/core/public/kibana-plugin-public.chromenavlink.md +++ b/docs/development/core/public/kibana-plugin-public.chromenavlink.md @@ -17,6 +17,7 @@ export interface ChromeNavLink | --- | --- | --- | | [active](./kibana-plugin-public.chromenavlink.active.md) | boolean | Indicates whether or not this app is currently on the screen. | | [baseUrl](./kibana-plugin-public.chromenavlink.baseurl.md) | string | The base route used to open the root of an application. | +| [category](./kibana-plugin-public.chromenavlink.category.md) | AppCategory | The category the app lives in | | [disabled](./kibana-plugin-public.chromenavlink.disabled.md) | boolean | Disables a link from being clickable. | | [euiIconType](./kibana-plugin-public.chromenavlink.euiicontype.md) | string | A EUI iconType that will be used for the app's icon. This icon takes precendence over the icon property. | | [hidden](./kibana-plugin-public.chromenavlink.hidden.md) | boolean | Hides a link from the navigation. | diff --git a/docs/development/core/public/kibana-plugin-public.chromenavlinks.update.md b/docs/development/core/public/kibana-plugin-public.chromenavlinks.update.md index d1cd2d3b04950..155d149f334a1 100644 --- a/docs/development/core/public/kibana-plugin-public.chromenavlinks.update.md +++ b/docs/development/core/public/kibana-plugin-public.chromenavlinks.update.md @@ -1,30 +1,30 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ChromeNavLinks](./kibana-plugin-public.chromenavlinks.md) > [update](./kibana-plugin-public.chromenavlinks.update.md) - -## ChromeNavLinks.update() method - -> Warning: This API is now obsolete. -> -> Uses the [AppBase.updater$](./kibana-plugin-public.appbase.updater_.md) property when registering your application with [ApplicationSetup.register()](./kibana-plugin-public.applicationsetup.register.md) instead. -> - -Update the navlink for the given id with the updated attributes. Returns the updated navlink or `undefined` if it does not exist. - -Signature: - -```typescript -update(id: string, values: ChromeNavLinkUpdateableFields): ChromeNavLink | undefined; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| id | string | | -| values | ChromeNavLinkUpdateableFields | | - -Returns: - -`ChromeNavLink | undefined` - + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ChromeNavLinks](./kibana-plugin-public.chromenavlinks.md) > [update](./kibana-plugin-public.chromenavlinks.update.md) + +## ChromeNavLinks.update() method + +> Warning: This API is now obsolete. +> +> Uses the [AppBase.updater$](./kibana-plugin-public.appbase.updater_.md) property when registering your application with [ApplicationSetup.register()](./kibana-plugin-public.applicationsetup.register.md) instead. +> + +Update the navlink for the given id with the updated attributes. Returns the updated navlink or `undefined` if it does not exist. + +Signature: + +```typescript +update(id: string, values: ChromeNavLinkUpdateableFields): ChromeNavLink | undefined; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| id | string | | +| values | ChromeNavLinkUpdateableFields | | + +Returns: + +`ChromeNavLink | undefined` + diff --git a/docs/development/core/public/kibana-plugin-public.corestart.fatalerrors.md b/docs/development/core/public/kibana-plugin-public.corestart.fatalerrors.md new file mode 100644 index 0000000000000..540b17b5a6f0b --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.corestart.fatalerrors.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [CoreStart](./kibana-plugin-public.corestart.md) > [fatalErrors](./kibana-plugin-public.corestart.fatalerrors.md) + +## CoreStart.fatalErrors property + +[FatalErrorsStart](./kibana-plugin-public.fatalerrorsstart.md) + +Signature: + +```typescript +fatalErrors: FatalErrorsStart; +``` diff --git a/docs/development/core/public/kibana-plugin-public.corestart.md b/docs/development/core/public/kibana-plugin-public.corestart.md index e561ee313f100..83af82d590c36 100644 --- a/docs/development/core/public/kibana-plugin-public.corestart.md +++ b/docs/development/core/public/kibana-plugin-public.corestart.md @@ -19,6 +19,7 @@ export interface CoreStart | [application](./kibana-plugin-public.corestart.application.md) | ApplicationStart | [ApplicationStart](./kibana-plugin-public.applicationstart.md) | | [chrome](./kibana-plugin-public.corestart.chrome.md) | ChromeStart | [ChromeStart](./kibana-plugin-public.chromestart.md) | | [docLinks](./kibana-plugin-public.corestart.doclinks.md) | DocLinksStart | [DocLinksStart](./kibana-plugin-public.doclinksstart.md) | +| [fatalErrors](./kibana-plugin-public.corestart.fatalerrors.md) | FatalErrorsStart | [FatalErrorsStart](./kibana-plugin-public.fatalerrorsstart.md) | | [http](./kibana-plugin-public.corestart.http.md) | HttpStart | [HttpStart](./kibana-plugin-public.httpstart.md) | | [i18n](./kibana-plugin-public.corestart.i18n.md) | I18nStart | [I18nStart](./kibana-plugin-public.i18nstart.md) | | [injectedMetadata](./kibana-plugin-public.corestart.injectedmetadata.md) | {
getInjectedVar: (name: string, defaultValue?: any) => unknown;
} | exposed temporarily until https://github.com/elastic/kibana/issues/41990 done use \*only\* to retrieve config values. There is no way to set injected values in the new platform. Use the legacy platform API instead. | diff --git a/docs/development/core/public/kibana-plugin-public.fatalerrorsstart.md b/docs/development/core/public/kibana-plugin-public.fatalerrorsstart.md new file mode 100644 index 0000000000000..a8ece7dcb7e02 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.fatalerrorsstart.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [FatalErrorsStart](./kibana-plugin-public.fatalerrorsstart.md) + +## FatalErrorsStart type + +FatalErrors stop the Kibana Public Core and displays a fatal error screen with details about the Kibana build and the error. + +Signature: + +```typescript +export declare type FatalErrorsStart = FatalErrorsSetup; +``` diff --git a/docs/development/core/public/kibana-plugin-public.legacynavlink.category.md b/docs/development/core/public/kibana-plugin-public.legacynavlink.category.md new file mode 100644 index 0000000000000..7026e9b519cc0 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.legacynavlink.category.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [LegacyNavLink](./kibana-plugin-public.legacynavlink.md) > [category](./kibana-plugin-public.legacynavlink.category.md) + +## LegacyNavLink.category property + +Signature: + +```typescript +category?: AppCategory; +``` diff --git a/docs/development/core/public/kibana-plugin-public.legacynavlink.md b/docs/development/core/public/kibana-plugin-public.legacynavlink.md index fc0c445f517b3..e112110dd10f8 100644 --- a/docs/development/core/public/kibana-plugin-public.legacynavlink.md +++ b/docs/development/core/public/kibana-plugin-public.legacynavlink.md @@ -15,6 +15,7 @@ export interface LegacyNavLink | Property | Type | Description | | --- | --- | --- | +| [category](./kibana-plugin-public.legacynavlink.category.md) | AppCategory | | | [euiIconType](./kibana-plugin-public.legacynavlink.euiicontype.md) | string | | | [icon](./kibana-plugin-public.legacynavlink.icon.md) | string | | | [id](./kibana-plugin-public.legacynavlink.id.md) | string | | diff --git a/docs/development/core/public/kibana-plugin-public.md b/docs/development/core/public/kibana-plugin-public.md index 27ca9f2d9fd57..52aca7501e64d 100644 --- a/docs/development/core/public/kibana-plugin-public.md +++ b/docs/development/core/public/kibana-plugin-public.md @@ -32,6 +32,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | --- | --- | | [App](./kibana-plugin-public.app.md) | Extension of [common app properties](./kibana-plugin-public.appbase.md) with the mount function. | | [AppBase](./kibana-plugin-public.appbase.md) | | +| [AppCategory](./kibana-plugin-public.appcategory.md) | A category definition for nav links to know where to sort them in the left hand nav | | [AppLeaveConfirmAction](./kibana-plugin-public.appleaveconfirmaction.md) | Action to return from a [AppLeaveHandler](./kibana-plugin-public.appleavehandler.md) to show a confirmation message when trying to leave an application.See | | [AppLeaveDefaultAction](./kibana-plugin-public.appleavedefaultaction.md) | Action to return from a [AppLeaveHandler](./kibana-plugin-public.appleavehandler.md) to execute the default behaviour when leaving the application.See | | [ApplicationSetup](./kibana-plugin-public.applicationsetup.md) | | @@ -129,6 +130,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [ChromeHelpExtensionMenuGitHubLink](./kibana-plugin-public.chromehelpextensionmenugithublink.md) | | | [ChromeHelpExtensionMenuLink](./kibana-plugin-public.chromehelpextensionmenulink.md) | | | [ChromeNavLinkUpdateableFields](./kibana-plugin-public.chromenavlinkupdateablefields.md) | | +| [FatalErrorsStart](./kibana-plugin-public.fatalerrorsstart.md) | FatalErrors stop the Kibana Public Core and displays a fatal error screen with details about the Kibana build and the error. | | [HandlerContextType](./kibana-plugin-public.handlercontexttype.md) | Extracts the type of the first argument of a [HandlerFunction](./kibana-plugin-public.handlerfunction.md) to represent the type of the context. | | [HandlerFunction](./kibana-plugin-public.handlerfunction.md) | A function that accepts a context object and an optional number of additional arguments. Used for the generic types in [IContextContainer](./kibana-plugin-public.icontextcontainer.md) | | [HandlerParameters](./kibana-plugin-public.handlerparameters.md) | Extracts the types of the additional arguments of a [HandlerFunction](./kibana-plugin-public.handlerfunction.md), excluding the [HandlerContextType](./kibana-plugin-public.handlercontexttype.md). | diff --git a/docs/development/core/server/kibana-plugin-server.coresetup.getstartservices.md b/docs/development/core/server/kibana-plugin-server.coresetup.getstartservices.md index b05d28899f9d2..589529cf2a7f7 100644 --- a/docs/development/core/server/kibana-plugin-server.coresetup.getstartservices.md +++ b/docs/development/core/server/kibana-plugin-server.coresetup.getstartservices.md @@ -1,17 +1,17 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CoreSetup](./kibana-plugin-server.coresetup.md) > [getStartServices](./kibana-plugin-server.coresetup.getstartservices.md) - -## CoreSetup.getStartServices() method - -Allows plugins to get access to APIs available in start inside async handlers. Promise will not resolve until Core and plugin dependencies have completed `start`. This should only be used inside handlers registered during `setup` that will only be executed after `start` lifecycle. - -Signature: - -```typescript -getStartServices(): Promise<[CoreStart, TPluginsStart]>; -``` -Returns: - -`Promise<[CoreStart, TPluginsStart]>` - + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CoreSetup](./kibana-plugin-server.coresetup.md) > [getStartServices](./kibana-plugin-server.coresetup.getstartservices.md) + +## CoreSetup.getStartServices() method + +Allows plugins to get access to APIs available in start inside async handlers. Promise will not resolve until Core and plugin dependencies have completed `start`. This should only be used inside handlers registered during `setup` that will only be executed after `start` lifecycle. + +Signature: + +```typescript +getStartServices(): Promise<[CoreStart, TPluginsStart]>; +``` +Returns: + +`Promise<[CoreStart, TPluginsStart]>` + diff --git a/docs/development/core/server/kibana-plugin-server.coresetup.md b/docs/development/core/server/kibana-plugin-server.coresetup.md index c36d649837e8a..325f7216122b5 100644 --- a/docs/development/core/server/kibana-plugin-server.coresetup.md +++ b/docs/development/core/server/kibana-plugin-server.coresetup.md @@ -1,32 +1,32 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CoreSetup](./kibana-plugin-server.coresetup.md) - -## CoreSetup interface - -Context passed to the plugins `setup` method. - -Signature: - -```typescript -export interface CoreSetup -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [capabilities](./kibana-plugin-server.coresetup.capabilities.md) | CapabilitiesSetup | [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) | -| [context](./kibana-plugin-server.coresetup.context.md) | ContextSetup | [ContextSetup](./kibana-plugin-server.contextsetup.md) | -| [elasticsearch](./kibana-plugin-server.coresetup.elasticsearch.md) | ElasticsearchServiceSetup | [ElasticsearchServiceSetup](./kibana-plugin-server.elasticsearchservicesetup.md) | -| [http](./kibana-plugin-server.coresetup.http.md) | HttpServiceSetup | [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) | -| [savedObjects](./kibana-plugin-server.coresetup.savedobjects.md) | SavedObjectsServiceSetup | [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) | -| [uiSettings](./kibana-plugin-server.coresetup.uisettings.md) | UiSettingsServiceSetup | [UiSettingsServiceSetup](./kibana-plugin-server.uisettingsservicesetup.md) | -| [uuid](./kibana-plugin-server.coresetup.uuid.md) | UuidServiceSetup | [UuidServiceSetup](./kibana-plugin-server.uuidservicesetup.md) | - -## Methods - -| Method | Description | -| --- | --- | -| [getStartServices()](./kibana-plugin-server.coresetup.getstartservices.md) | Allows plugins to get access to APIs available in start inside async handlers. Promise will not resolve until Core and plugin dependencies have completed start. This should only be used inside handlers registered during setup that will only be executed after start lifecycle. | - + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CoreSetup](./kibana-plugin-server.coresetup.md) + +## CoreSetup interface + +Context passed to the plugins `setup` method. + +Signature: + +```typescript +export interface CoreSetup +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [capabilities](./kibana-plugin-server.coresetup.capabilities.md) | CapabilitiesSetup | [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) | +| [context](./kibana-plugin-server.coresetup.context.md) | ContextSetup | [ContextSetup](./kibana-plugin-server.contextsetup.md) | +| [elasticsearch](./kibana-plugin-server.coresetup.elasticsearch.md) | ElasticsearchServiceSetup | [ElasticsearchServiceSetup](./kibana-plugin-server.elasticsearchservicesetup.md) | +| [http](./kibana-plugin-server.coresetup.http.md) | HttpServiceSetup | [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) | +| [savedObjects](./kibana-plugin-server.coresetup.savedobjects.md) | SavedObjectsServiceSetup | [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) | +| [uiSettings](./kibana-plugin-server.coresetup.uisettings.md) | UiSettingsServiceSetup | [UiSettingsServiceSetup](./kibana-plugin-server.uisettingsservicesetup.md) | +| [uuid](./kibana-plugin-server.coresetup.uuid.md) | UuidServiceSetup | [UuidServiceSetup](./kibana-plugin-server.uuidservicesetup.md) | + +## Methods + +| Method | Description | +| --- | --- | +| [getStartServices()](./kibana-plugin-server.coresetup.getstartservices.md) | Allows plugins to get access to APIs available in start inside async handlers. Promise will not resolve until Core and plugin dependencies have completed start. This should only be used inside handlers registered during setup that will only be executed after start lifecycle. | + diff --git a/docs/development/core/server/kibana-plugin-server.cspconfig.md b/docs/development/core/server/kibana-plugin-server.cspconfig.md index 7e491cb0df912..6f12d64676ce9 100644 --- a/docs/development/core/server/kibana-plugin-server.cspconfig.md +++ b/docs/development/core/server/kibana-plugin-server.cspconfig.md @@ -20,9 +20,9 @@ The constructor for this class is marked as internal. Third-party code should no | Property | Modifiers | Type | Description | | --- | --- | --- | --- | -| [DEFAULT](./kibana-plugin-server.cspconfig.default.md) | static | CspConfig | | | [header](./kibana-plugin-server.cspconfig.header.md) | | string | | | [rules](./kibana-plugin-server.cspconfig.rules.md) | | string[] | | +| [rulesChangedFromDefault](./kibana-plugin-server.cspconfig.ruleschangedfromdefault.md) | | boolean | | | [strict](./kibana-plugin-server.cspconfig.strict.md) | | boolean | | | [warnLegacyBrowsers](./kibana-plugin-server.cspconfig.warnlegacybrowsers.md) | | boolean | | diff --git a/docs/development/core/server/kibana-plugin-server.cspconfig.default.md b/docs/development/core/server/kibana-plugin-server.cspconfig.ruleschangedfromdefault.md similarity index 56% rename from docs/development/core/server/kibana-plugin-server.cspconfig.default.md rename to docs/development/core/server/kibana-plugin-server.cspconfig.ruleschangedfromdefault.md index 56e6cf35cdd13..3320785935b21 100644 --- a/docs/development/core/server/kibana-plugin-server.cspconfig.default.md +++ b/docs/development/core/server/kibana-plugin-server.cspconfig.ruleschangedfromdefault.md @@ -1,11 +1,11 @@ -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CspConfig](./kibana-plugin-server.cspconfig.md) > [DEFAULT](./kibana-plugin-server.cspconfig.default.md) +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CspConfig](./kibana-plugin-server.cspconfig.md) > [rulesChangedFromDefault](./kibana-plugin-server.cspconfig.ruleschangedfromdefault.md) -## CspConfig.DEFAULT property +## CspConfig.rulesChangedFromDefault property Signature: ```typescript -static readonly DEFAULT: CspConfig; +readonly rulesChangedFromDefault: boolean; ``` diff --git a/docs/development/core/server/kibana-plugin-server.icspconfig.md b/docs/development/core/server/kibana-plugin-server.icspconfig.md index fb8188386a376..00107d561873f 100644 --- a/docs/development/core/server/kibana-plugin-server.icspconfig.md +++ b/docs/development/core/server/kibana-plugin-server.icspconfig.md @@ -18,6 +18,7 @@ export interface ICspConfig | --- | --- | --- | | [header](./kibana-plugin-server.icspconfig.header.md) | string | The CSP rules in a formatted directives string for use in a Content-Security-Policy header. | | [rules](./kibana-plugin-server.icspconfig.rules.md) | string[] | The CSP rules used for Kibana. | +| [rulesChangedFromDefault](./kibana-plugin-server.icspconfig.ruleschangedfromdefault.md) | boolean | Flag indicating that the configuraion changes the csp rules from the defaults | | [strict](./kibana-plugin-server.icspconfig.strict.md) | boolean | Specify whether browsers that do not support CSP should be able to use Kibana. Use true to block and false to allow. | | [warnLegacyBrowsers](./kibana-plugin-server.icspconfig.warnlegacybrowsers.md) | boolean | Specify whether users with legacy browsers should be warned about their lack of Kibana security compliance. | diff --git a/docs/development/core/server/kibana-plugin-server.icspconfig.ruleschangedfromdefault.md b/docs/development/core/server/kibana-plugin-server.icspconfig.ruleschangedfromdefault.md new file mode 100644 index 0000000000000..427c07d2d1e4d --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.icspconfig.ruleschangedfromdefault.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ICspConfig](./kibana-plugin-server.icspconfig.md) > [rulesChangedFromDefault](./kibana-plugin-server.icspconfig.ruleschangedfromdefault.md) + +## ICspConfig.rulesChangedFromDefault property + +Flag indicating that the configuraion changes the csp rules from the defaults + +Signature: + +```typescript +readonly rulesChangedFromDefault: boolean; +``` diff --git a/docs/development/core/server/kibana-plugin-server.kibanarequest.events.md b/docs/development/core/server/kibana-plugin-server.kibanarequest.events.md new file mode 100644 index 0000000000000..5a002fc28f5db --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.kibanarequest.events.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [KibanaRequest](./kibana-plugin-server.kibanarequest.md) > [events](./kibana-plugin-server.kibanarequest.events.md) + +## KibanaRequest.events property + +Request events [KibanaRequestEvents](./kibana-plugin-server.kibanarequestevents.md) + +Signature: + +```typescript +readonly events: KibanaRequestEvents; +``` diff --git a/docs/development/core/server/kibana-plugin-server.kibanarequest.md b/docs/development/core/server/kibana-plugin-server.kibanarequest.md index bc805fdc0b86f..6603de24494d5 100644 --- a/docs/development/core/server/kibana-plugin-server.kibanarequest.md +++ b/docs/development/core/server/kibana-plugin-server.kibanarequest.md @@ -23,10 +23,11 @@ export declare class KibanaRequestBody | | +| [events](./kibana-plugin-server.kibanarequest.events.md) | | KibanaRequestEvents | Request events [KibanaRequestEvents](./kibana-plugin-server.kibanarequestevents.md) | | [headers](./kibana-plugin-server.kibanarequest.headers.md) | | Headers | Readonly copy of incoming request headers. | | [params](./kibana-plugin-server.kibanarequest.params.md) | | Params | | | [query](./kibana-plugin-server.kibanarequest.query.md) | | Query | | | [route](./kibana-plugin-server.kibanarequest.route.md) | | RecursiveReadonly<KibanaRequestRoute<Method>> | matched route details | -| [socket](./kibana-plugin-server.kibanarequest.socket.md) | | IKibanaSocket | | +| [socket](./kibana-plugin-server.kibanarequest.socket.md) | | IKibanaSocket | [IKibanaSocket](./kibana-plugin-server.ikibanasocket.md) | | [url](./kibana-plugin-server.kibanarequest.url.md) | | Url | a WHATWG URL standard object. | diff --git a/docs/development/core/server/kibana-plugin-server.kibanarequest.socket.md b/docs/development/core/server/kibana-plugin-server.kibanarequest.socket.md index 3880428273ac9..c55f4656c993c 100644 --- a/docs/development/core/server/kibana-plugin-server.kibanarequest.socket.md +++ b/docs/development/core/server/kibana-plugin-server.kibanarequest.socket.md @@ -4,6 +4,8 @@ ## KibanaRequest.socket property +[IKibanaSocket](./kibana-plugin-server.ikibanasocket.md) + Signature: ```typescript diff --git a/docs/development/core/server/kibana-plugin-server.kibanarequestevents.aborted_.md b/docs/development/core/server/kibana-plugin-server.kibanarequestevents.aborted_.md new file mode 100644 index 0000000000000..d292d5d60bf5f --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.kibanarequestevents.aborted_.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [KibanaRequestEvents](./kibana-plugin-server.kibanarequestevents.md) > [aborted$](./kibana-plugin-server.kibanarequestevents.aborted_.md) + +## KibanaRequestEvents.aborted$ property + +Observable that emits once if and when the request has been aborted. + +Signature: + +```typescript +aborted$: Observable; +``` diff --git a/docs/development/core/server/kibana-plugin-server.kibanarequestevents.md b/docs/development/core/server/kibana-plugin-server.kibanarequestevents.md new file mode 100644 index 0000000000000..9137c4673a60c --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.kibanarequestevents.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [KibanaRequestEvents](./kibana-plugin-server.kibanarequestevents.md) + +## KibanaRequestEvents interface + +Request events. + +Signature: + +```typescript +export interface KibanaRequestEvents +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [aborted$](./kibana-plugin-server.kibanarequestevents.aborted_.md) | Observable<void> | Observable that emits once if and when the request has been aborted. | + diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md index 00ab83123319a..15f5329d494c2 100644 --- a/docs/development/core/server/kibana-plugin-server.md +++ b/docs/development/core/server/kibana-plugin-server.md @@ -1,223 +1,226 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) - -## kibana-plugin-server package - -The Kibana Core APIs for server-side plugins. - -A plugin requires a `kibana.json` file at it's root directory that follows [the manfiest schema](./kibana-plugin-server.pluginmanifest.md) to define static plugin information required to load the plugin. - -A plugin's `server/index` file must contain a named import, `plugin`, that implements [PluginInitializer](./kibana-plugin-server.plugininitializer.md) which returns an object that implements [Plugin](./kibana-plugin-server.plugin.md). - -The plugin integrates with the core system via lifecycle events: `setup`, `start`, and `stop`. In each lifecycle method, the plugin will receive the corresponding core services available (either [CoreSetup](./kibana-plugin-server.coresetup.md) or [CoreStart](./kibana-plugin-server.corestart.md)) and any interfaces returned by dependency plugins' lifecycle method. Anything returned by the plugin's lifecycle method will be exposed to downstream dependencies when their corresponding lifecycle methods are invoked. - -## Classes - -| Class | Description | -| --- | --- | -| [BasePath](./kibana-plugin-server.basepath.md) | Access or manipulate the Kibana base path | -| [ClusterClient](./kibana-plugin-server.clusterclient.md) | Represents an Elasticsearch cluster API client created by the platform. It allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via asScoped(...)).See [ClusterClient](./kibana-plugin-server.clusterclient.md). | -| [CspConfig](./kibana-plugin-server.cspconfig.md) | CSP configuration for use in Kibana. | -| [ElasticsearchErrorHelpers](./kibana-plugin-server.elasticsearcherrorhelpers.md) | Helpers for working with errors returned from the Elasticsearch service.Since the internal data of errors are subject to change, consumers of the Elasticsearch service should always use these helpers to classify errors instead of checking error internals such as body.error.header[WWW-Authenticate] | -| [KibanaRequest](./kibana-plugin-server.kibanarequest.md) | Kibana specific abstraction for an incoming request. | -| [RouteValidationError](./kibana-plugin-server.routevalidationerror.md) | Error to return when the validation is not successful. | -| [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) | | -| [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md) | | -| [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) | | -| [ScopedClusterClient](./kibana-plugin-server.scopedclusterclient.md) | Serves the same purpose as "normal" ClusterClient but exposes additional callAsCurrentUser method that doesn't use credentials of the Kibana internal user (as callAsInternalUser does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API.See [ScopedClusterClient](./kibana-plugin-server.scopedclusterclient.md). | - -## Enumerations - -| Enumeration | Description | -| --- | --- | -| [AuthResultType](./kibana-plugin-server.authresulttype.md) | | -| [AuthStatus](./kibana-plugin-server.authstatus.md) | Status indicating an outcome of the authentication. | - -## Interfaces - -| Interface | Description | -| --- | --- | -| [APICaller](./kibana-plugin-server.apicaller.md) | | -| [AssistanceAPIResponse](./kibana-plugin-server.assistanceapiresponse.md) | | -| [AssistantAPIClientParams](./kibana-plugin-server.assistantapiclientparams.md) | | -| [Authenticated](./kibana-plugin-server.authenticated.md) | | -| [AuthResultParams](./kibana-plugin-server.authresultparams.md) | Result of an incoming request authentication. | -| [AuthToolkit](./kibana-plugin-server.authtoolkit.md) | A tool set defining an outcome of Auth interceptor for incoming request. | -| [CallAPIOptions](./kibana-plugin-server.callapioptions.md) | The set of options that defines how API call should be made and result be processed. | -| [Capabilities](./kibana-plugin-server.capabilities.md) | The read-only set of capabilities available for the current UI session. Capabilities are simple key-value pairs of (string, boolean), where the string denotes the capability ID, and the boolean is a flag indicating if the capability is enabled or disabled. | -| [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) | APIs to manage the [Capabilities](./kibana-plugin-server.capabilities.md) that will be used by the application.Plugins relying on capabilities to toggle some of their features should register them during the setup phase using the registerProvider method.Plugins having the responsibility to restrict capabilities depending on a given context should register their capabilities switcher using the registerSwitcher method.Refers to the methods documentation for complete description and examples. | -| [CapabilitiesStart](./kibana-plugin-server.capabilitiesstart.md) | APIs to access the application [Capabilities](./kibana-plugin-server.capabilities.md). | -| [ConfigDeprecationFactory](./kibana-plugin-server.configdeprecationfactory.md) | Provides helpers to generates the most commonly used [ConfigDeprecation](./kibana-plugin-server.configdeprecation.md) when invoking a [ConfigDeprecationProvider](./kibana-plugin-server.configdeprecationprovider.md).See methods documentation for more detailed examples. | -| [ContextSetup](./kibana-plugin-server.contextsetup.md) | An object that handles registration of context providers and configuring handlers with context. | -| [CoreSetup](./kibana-plugin-server.coresetup.md) | Context passed to the plugins setup method. | -| [CoreStart](./kibana-plugin-server.corestart.md) | Context passed to the plugins start method. | -| [CustomHttpResponseOptions](./kibana-plugin-server.customhttpresponseoptions.md) | HTTP response parameters for a response with adjustable status code. | -| [DeprecationAPIClientParams](./kibana-plugin-server.deprecationapiclientparams.md) | | -| [DeprecationAPIResponse](./kibana-plugin-server.deprecationapiresponse.md) | | -| [DeprecationInfo](./kibana-plugin-server.deprecationinfo.md) | | -| [DeprecationSettings](./kibana-plugin-server.deprecationsettings.md) | UiSettings deprecation field options. | -| [DiscoveredPlugin](./kibana-plugin-server.discoveredplugin.md) | Small container object used to expose information about discovered plugins that may or may not have been started. | -| [ElasticsearchError](./kibana-plugin-server.elasticsearcherror.md) | | -| [ElasticsearchServiceSetup](./kibana-plugin-server.elasticsearchservicesetup.md) | | -| [EnvironmentMode](./kibana-plugin-server.environmentmode.md) | | -| [ErrorHttpResponseOptions](./kibana-plugin-server.errorhttpresponseoptions.md) | HTTP response parameters | -| [FakeRequest](./kibana-plugin-server.fakerequest.md) | Fake request object created manually by Kibana plugins. | -| [HttpResponseOptions](./kibana-plugin-server.httpresponseoptions.md) | HTTP response parameters | -| [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) | Kibana HTTP Service provides own abstraction for work with HTTP stack. Plugins don't have direct access to hapi server and its primitives anymore. Moreover, plugins shouldn't rely on the fact that HTTP Service uses one or another library under the hood. This gives the platform flexibility to upgrade or changing our internal HTTP stack without breaking plugins. If the HTTP Service lacks functionality you need, we are happy to discuss and support your needs. | -| [HttpServiceStart](./kibana-plugin-server.httpservicestart.md) | | -| [IContextContainer](./kibana-plugin-server.icontextcontainer.md) | An object that handles registration of context providers and configuring handlers with context. | -| [ICspConfig](./kibana-plugin-server.icspconfig.md) | CSP configuration for use in Kibana. | -| [IKibanaResponse](./kibana-plugin-server.ikibanaresponse.md) | A response data object, expected to returned as a result of [RequestHandler](./kibana-plugin-server.requesthandler.md) execution | -| [IKibanaSocket](./kibana-plugin-server.ikibanasocket.md) | A tiny abstraction for TCP socket. | -| [ImageValidation](./kibana-plugin-server.imagevalidation.md) | | -| [IndexSettingsDeprecationInfo](./kibana-plugin-server.indexsettingsdeprecationinfo.md) | | -| [IRenderOptions](./kibana-plugin-server.irenderoptions.md) | | -| [IRouter](./kibana-plugin-server.irouter.md) | Registers route handlers for specified resource path and method. See [RouteConfig](./kibana-plugin-server.routeconfig.md) and [RequestHandler](./kibana-plugin-server.requesthandler.md) for more information about arguments to route registrations. | -| [IScopedRenderingClient](./kibana-plugin-server.iscopedrenderingclient.md) | | -| [IUiSettingsClient](./kibana-plugin-server.iuisettingsclient.md) | Server-side client that provides access to the advanced settings stored in elasticsearch. The settings provide control over the behavior of the Kibana application. For example, a user can specify how to display numeric or date fields. Users can adjust the settings via Management UI. | -| [KibanaRequestRoute](./kibana-plugin-server.kibanarequestroute.md) | Request specific route information exposed to a handler. | -| [LegacyRequest](./kibana-plugin-server.legacyrequest.md) | | -| [LegacyServiceSetupDeps](./kibana-plugin-server.legacyservicesetupdeps.md) | | -| [LegacyServiceStartDeps](./kibana-plugin-server.legacyservicestartdeps.md) | | -| [Logger](./kibana-plugin-server.logger.md) | Logger exposes all the necessary methods to log any type of information and this is the interface used by the logging consumers including plugins. | -| [LoggerFactory](./kibana-plugin-server.loggerfactory.md) | The single purpose of LoggerFactory interface is to define a way to retrieve a context-based logger instance. | -| [LogMeta](./kibana-plugin-server.logmeta.md) | Contextual metadata | -| [OnPostAuthToolkit](./kibana-plugin-server.onpostauthtoolkit.md) | A tool set defining an outcome of OnPostAuth interceptor for incoming request. | -| [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md) | A tool set defining an outcome of OnPreAuth interceptor for incoming request. | -| [OnPreResponseExtensions](./kibana-plugin-server.onpreresponseextensions.md) | Additional data to extend a response. | -| [OnPreResponseInfo](./kibana-plugin-server.onpreresponseinfo.md) | Response status code. | -| [OnPreResponseToolkit](./kibana-plugin-server.onpreresponsetoolkit.md) | A tool set defining an outcome of OnPreAuth interceptor for incoming request. | -| [PackageInfo](./kibana-plugin-server.packageinfo.md) | | -| [Plugin](./kibana-plugin-server.plugin.md) | The interface that should be returned by a PluginInitializer. | -| [PluginConfigDescriptor](./kibana-plugin-server.pluginconfigdescriptor.md) | Describes a plugin configuration properties. | -| [PluginInitializerContext](./kibana-plugin-server.plugininitializercontext.md) | Context that's available to plugins during initialization stage. | -| [PluginManifest](./kibana-plugin-server.pluginmanifest.md) | Describes the set of required and optional properties plugin can define in its mandatory JSON manifest file. | -| [PluginsServiceSetup](./kibana-plugin-server.pluginsservicesetup.md) | | -| [PluginsServiceStart](./kibana-plugin-server.pluginsservicestart.md) | | -| [RequestHandlerContext](./kibana-plugin-server.requesthandlercontext.md) | Plugin specific context passed to a route handler.Provides the following clients: - [rendering](./kibana-plugin-server.iscopedrenderingclient.md) - Rendering client which uses the data of the incoming request - [savedObjects.client](./kibana-plugin-server.savedobjectsclient.md) - Saved Objects client which uses the credentials of the incoming request - [elasticsearch.dataClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch data client which uses the credentials of the incoming request - [elasticsearch.adminClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch admin client which uses the credentials of the incoming request - [uiSettings.client](./kibana-plugin-server.iuisettingsclient.md) - uiSettings client which uses the credentials of the incoming request | -| [RouteConfig](./kibana-plugin-server.routeconfig.md) | Route specific configuration. | -| [RouteConfigOptions](./kibana-plugin-server.routeconfigoptions.md) | Additional route options. | -| [RouteConfigOptionsBody](./kibana-plugin-server.routeconfigoptionsbody.md) | Additional body options for a route | -| [RouteValidationResultFactory](./kibana-plugin-server.routevalidationresultfactory.md) | Validation result factory to be used in the custom validation function to return the valid data or validation errorsSee [RouteValidationFunction](./kibana-plugin-server.routevalidationfunction.md). | -| [RouteValidatorConfig](./kibana-plugin-server.routevalidatorconfig.md) | The configuration object to the RouteValidator class. Set params, query and/or body to specify the validation logic to follow for that property. | -| [RouteValidatorOptions](./kibana-plugin-server.routevalidatoroptions.md) | Additional options for the RouteValidator class to modify its default behaviour. | -| [SavedObject](./kibana-plugin-server.savedobject.md) | | -| [SavedObjectAttributes](./kibana-plugin-server.savedobjectattributes.md) | The data for a Saved Object is stored as an object in the attributes property. | -| [SavedObjectReference](./kibana-plugin-server.savedobjectreference.md) | A reference to another saved object. | -| [SavedObjectsBaseOptions](./kibana-plugin-server.savedobjectsbaseoptions.md) | | -| [SavedObjectsBulkCreateObject](./kibana-plugin-server.savedobjectsbulkcreateobject.md) | | -| [SavedObjectsBulkGetObject](./kibana-plugin-server.savedobjectsbulkgetobject.md) | | -| [SavedObjectsBulkResponse](./kibana-plugin-server.savedobjectsbulkresponse.md) | | -| [SavedObjectsBulkUpdateObject](./kibana-plugin-server.savedobjectsbulkupdateobject.md) | | -| [SavedObjectsBulkUpdateOptions](./kibana-plugin-server.savedobjectsbulkupdateoptions.md) | | -| [SavedObjectsBulkUpdateResponse](./kibana-plugin-server.savedobjectsbulkupdateresponse.md) | | -| [SavedObjectsClientProviderOptions](./kibana-plugin-server.savedobjectsclientprovideroptions.md) | Options to control the creation of the Saved Objects Client. | -| [SavedObjectsClientWrapperOptions](./kibana-plugin-server.savedobjectsclientwrapperoptions.md) | Options passed to each SavedObjectsClientWrapperFactory to aid in creating the wrapper instance. | -| [SavedObjectsCreateOptions](./kibana-plugin-server.savedobjectscreateoptions.md) | | -| [SavedObjectsDeleteByNamespaceOptions](./kibana-plugin-server.savedobjectsdeletebynamespaceoptions.md) | | -| [SavedObjectsDeleteOptions](./kibana-plugin-server.savedobjectsdeleteoptions.md) | | -| [SavedObjectsExportOptions](./kibana-plugin-server.savedobjectsexportoptions.md) | Options controlling the export operation. | -| [SavedObjectsExportResultDetails](./kibana-plugin-server.savedobjectsexportresultdetails.md) | Structure of the export result details entry | -| [SavedObjectsFindOptions](./kibana-plugin-server.savedobjectsfindoptions.md) | | -| [SavedObjectsFindResponse](./kibana-plugin-server.savedobjectsfindresponse.md) | Return type of the Saved Objects find() method.\*Note\*: this type is different between the Public and Server Saved Objects clients. | -| [SavedObjectsImportConflictError](./kibana-plugin-server.savedobjectsimportconflicterror.md) | Represents a failure to import due to a conflict. | -| [SavedObjectsImportError](./kibana-plugin-server.savedobjectsimporterror.md) | Represents a failure to import. | -| [SavedObjectsImportMissingReferencesError](./kibana-plugin-server.savedobjectsimportmissingreferenceserror.md) | Represents a failure to import due to missing references. | -| [SavedObjectsImportOptions](./kibana-plugin-server.savedobjectsimportoptions.md) | Options to control the import operation. | -| [SavedObjectsImportResponse](./kibana-plugin-server.savedobjectsimportresponse.md) | The response describing the result of an import. | -| [SavedObjectsImportRetry](./kibana-plugin-server.savedobjectsimportretry.md) | Describes a retry operation for importing a saved object. | -| [SavedObjectsImportUnknownError](./kibana-plugin-server.savedobjectsimportunknownerror.md) | Represents a failure to import due to an unknown reason. | -| [SavedObjectsImportUnsupportedTypeError](./kibana-plugin-server.savedobjectsimportunsupportedtypeerror.md) | Represents a failure to import due to having an unsupported saved object type. | -| [SavedObjectsIncrementCounterOptions](./kibana-plugin-server.savedobjectsincrementcounteroptions.md) | | -| [SavedObjectsMigrationLogger](./kibana-plugin-server.savedobjectsmigrationlogger.md) | | -| [SavedObjectsMigrationVersion](./kibana-plugin-server.savedobjectsmigrationversion.md) | Information about the migrations that have been applied to this SavedObject. When Kibana starts up, KibanaMigrator detects outdated documents and migrates them based on this value. For each migration that has been applied, the plugin's name is used as a key and the latest migration version as the value. | -| [SavedObjectsRawDoc](./kibana-plugin-server.savedobjectsrawdoc.md) | A raw document as represented directly in the saved object index. | -| [SavedObjectsResolveImportErrorsOptions](./kibana-plugin-server.savedobjectsresolveimporterrorsoptions.md) | Options to control the "resolve import" operation. | -| [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing and querying state. The SavedObjectsServiceSetup API exposes methods for creating and registering Saved Object client wrappers. | -| [SavedObjectsServiceStart](./kibana-plugin-server.savedobjectsservicestart.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing and querying state. The SavedObjectsServiceStart API provides a scoped Saved Objects client for interacting with Saved Objects. | -| [SavedObjectsUpdateOptions](./kibana-plugin-server.savedobjectsupdateoptions.md) | | -| [SavedObjectsUpdateResponse](./kibana-plugin-server.savedobjectsupdateresponse.md) | | -| [SessionCookieValidationResult](./kibana-plugin-server.sessioncookievalidationresult.md) | Return type from a function to validate cookie contents. | -| [SessionStorage](./kibana-plugin-server.sessionstorage.md) | Provides an interface to store and retrieve data across requests. | -| [SessionStorageCookieOptions](./kibana-plugin-server.sessionstoragecookieoptions.md) | Configuration used to create HTTP session storage based on top of cookie mechanism. | -| [SessionStorageFactory](./kibana-plugin-server.sessionstoragefactory.md) | SessionStorage factory to bind one to an incoming request | -| [StringValidation](./kibana-plugin-server.stringvalidation.md) | | -| [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) | UiSettings parameters defined by the plugins. | -| [UiSettingsServiceSetup](./kibana-plugin-server.uisettingsservicesetup.md) | | -| [UiSettingsServiceStart](./kibana-plugin-server.uisettingsservicestart.md) | | -| [UserProvidedValues](./kibana-plugin-server.userprovidedvalues.md) | Describes the values explicitly set by user. | -| [UuidServiceSetup](./kibana-plugin-server.uuidservicesetup.md) | APIs to access the application's instance uuid. | - -## Variables - -| Variable | Description | -| --- | --- | -| [kibanaResponseFactory](./kibana-plugin-server.kibanaresponsefactory.md) | Set of helpers used to create KibanaResponse to form HTTP response on an incoming request. Should be returned as a result of [RequestHandler](./kibana-plugin-server.requesthandler.md) execution. | -| [validBodyOutput](./kibana-plugin-server.validbodyoutput.md) | The set of valid body.output | - -## Type Aliases - -| Type Alias | Description | -| --- | --- | -| [AuthenticationHandler](./kibana-plugin-server.authenticationhandler.md) | See [AuthToolkit](./kibana-plugin-server.authtoolkit.md). | -| [AuthHeaders](./kibana-plugin-server.authheaders.md) | Auth Headers map | -| [AuthResult](./kibana-plugin-server.authresult.md) | | -| [CapabilitiesProvider](./kibana-plugin-server.capabilitiesprovider.md) | See [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) | -| [CapabilitiesSwitcher](./kibana-plugin-server.capabilitiesswitcher.md) | See [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) | -| [ConfigDeprecation](./kibana-plugin-server.configdeprecation.md) | Configuration deprecation returned from [ConfigDeprecationProvider](./kibana-plugin-server.configdeprecationprovider.md) that handles a single deprecation from the configuration. | -| [ConfigDeprecationLogger](./kibana-plugin-server.configdeprecationlogger.md) | Logger interface used when invoking a [ConfigDeprecation](./kibana-plugin-server.configdeprecation.md) | -| [ConfigDeprecationProvider](./kibana-plugin-server.configdeprecationprovider.md) | A provider that should returns a list of [ConfigDeprecation](./kibana-plugin-server.configdeprecation.md).See [ConfigDeprecationFactory](./kibana-plugin-server.configdeprecationfactory.md) for more usage examples. | -| [ConfigPath](./kibana-plugin-server.configpath.md) | | -| [ElasticsearchClientConfig](./kibana-plugin-server.elasticsearchclientconfig.md) | | -| [GetAuthHeaders](./kibana-plugin-server.getauthheaders.md) | Get headers to authenticate a user against Elasticsearch. | -| [GetAuthState](./kibana-plugin-server.getauthstate.md) | Get authentication state for a request. Returned by auth interceptor. | -| [HandlerContextType](./kibana-plugin-server.handlercontexttype.md) | Extracts the type of the first argument of a [HandlerFunction](./kibana-plugin-server.handlerfunction.md) to represent the type of the context. | -| [HandlerFunction](./kibana-plugin-server.handlerfunction.md) | A function that accepts a context object and an optional number of additional arguments. Used for the generic types in [IContextContainer](./kibana-plugin-server.icontextcontainer.md) | -| [HandlerParameters](./kibana-plugin-server.handlerparameters.md) | Extracts the types of the additional arguments of a [HandlerFunction](./kibana-plugin-server.handlerfunction.md), excluding the [HandlerContextType](./kibana-plugin-server.handlercontexttype.md). | -| [Headers](./kibana-plugin-server.headers.md) | Http request headers to read. | -| [HttpResponsePayload](./kibana-plugin-server.httpresponsepayload.md) | Data send to the client as a response payload. | -| [IBasePath](./kibana-plugin-server.ibasepath.md) | Access or manipulate the Kibana base path[BasePath](./kibana-plugin-server.basepath.md) | -| [IClusterClient](./kibana-plugin-server.iclusterclient.md) | Represents an Elasticsearch cluster API client created by the platform. It allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via asScoped(...)).See [ClusterClient](./kibana-plugin-server.clusterclient.md). | -| [IContextProvider](./kibana-plugin-server.icontextprovider.md) | A function that returns a context value for a specific key of given context type. | -| [ICustomClusterClient](./kibana-plugin-server.icustomclusterclient.md) | Represents an Elasticsearch cluster API client created by a plugin. It allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via asScoped(...)).See [ClusterClient](./kibana-plugin-server.clusterclient.md). | -| [IsAuthenticated](./kibana-plugin-server.isauthenticated.md) | Return authentication status for a request. | -| [ISavedObjectsRepository](./kibana-plugin-server.isavedobjectsrepository.md) | See [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) | -| [IScopedClusterClient](./kibana-plugin-server.iscopedclusterclient.md) | Serves the same purpose as "normal" ClusterClient but exposes additional callAsCurrentUser method that doesn't use credentials of the Kibana internal user (as callAsInternalUser does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API.See [ScopedClusterClient](./kibana-plugin-server.scopedclusterclient.md). | -| [KibanaRequestRouteOptions](./kibana-plugin-server.kibanarequestrouteoptions.md) | Route options: If 'GET' or 'OPTIONS' method, body options won't be returned. | -| [KibanaResponseFactory](./kibana-plugin-server.kibanaresponsefactory.md) | Creates an object containing request response payload, HTTP headers, error details, and other data transmitted to the client. | -| [KnownHeaders](./kibana-plugin-server.knownheaders.md) | Set of well-known HTTP headers. | -| [LifecycleResponseFactory](./kibana-plugin-server.lifecycleresponsefactory.md) | Creates an object containing redirection or error response with error details, HTTP headers, and other data transmitted to the client. | -| [MIGRATION\_ASSISTANCE\_INDEX\_ACTION](./kibana-plugin-server.migration_assistance_index_action.md) | | -| [MIGRATION\_DEPRECATION\_LEVEL](./kibana-plugin-server.migration_deprecation_level.md) | | -| [MutatingOperationRefreshSetting](./kibana-plugin-server.mutatingoperationrefreshsetting.md) | Elasticsearch Refresh setting for mutating operation | -| [OnPostAuthHandler](./kibana-plugin-server.onpostauthhandler.md) | See [OnPostAuthToolkit](./kibana-plugin-server.onpostauthtoolkit.md). | -| [OnPreAuthHandler](./kibana-plugin-server.onpreauthhandler.md) | See [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md). | -| [OnPreResponseHandler](./kibana-plugin-server.onpreresponsehandler.md) | See [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md). | -| [PluginConfigSchema](./kibana-plugin-server.pluginconfigschema.md) | Dedicated type for plugin configuration schema. | -| [PluginInitializer](./kibana-plugin-server.plugininitializer.md) | The plugin export at the root of a plugin's server directory should conform to this interface. | -| [PluginName](./kibana-plugin-server.pluginname.md) | Dedicated type for plugin name/id that is supposed to make Map/Set/Arrays that use it as a key or value more obvious. | -| [PluginOpaqueId](./kibana-plugin-server.pluginopaqueid.md) | | -| [RecursiveReadonly](./kibana-plugin-server.recursivereadonly.md) | | -| [RedirectResponseOptions](./kibana-plugin-server.redirectresponseoptions.md) | HTTP response parameters for redirection response | -| [RequestHandler](./kibana-plugin-server.requesthandler.md) | A function executed when route path matched requested resource path. Request handler is expected to return a result of one of [KibanaResponseFactory](./kibana-plugin-server.kibanaresponsefactory.md) functions. | -| [RequestHandlerContextContainer](./kibana-plugin-server.requesthandlercontextcontainer.md) | An object that handles registration of http request context providers. | -| [RequestHandlerContextProvider](./kibana-plugin-server.requesthandlercontextprovider.md) | Context provider for request handler. Extends request context object with provided functionality or data. | -| [ResponseError](./kibana-plugin-server.responseerror.md) | Error message and optional data send to the client in case of error. | -| [ResponseErrorAttributes](./kibana-plugin-server.responseerrorattributes.md) | Additional data to provide error details. | -| [ResponseHeaders](./kibana-plugin-server.responseheaders.md) | Http response headers to set. | -| [RouteContentType](./kibana-plugin-server.routecontenttype.md) | The set of supported parseable Content-Types | -| [RouteMethod](./kibana-plugin-server.routemethod.md) | The set of common HTTP methods supported by Kibana routing. | -| [RouteRegistrar](./kibana-plugin-server.routeregistrar.md) | Route handler common definition | -| [RouteValidationFunction](./kibana-plugin-server.routevalidationfunction.md) | The custom validation function if @kbn/config-schema is not a valid solution for your specific plugin requirements. | -| [RouteValidationSpec](./kibana-plugin-server.routevalidationspec.md) | Allowed property validation options: either @kbn/config-schema validations or custom validation functionsSee [RouteValidationFunction](./kibana-plugin-server.routevalidationfunction.md) for custom validation. | -| [RouteValidatorFullConfig](./kibana-plugin-server.routevalidatorfullconfig.md) | Route validations config and options merged into one object | -| [SavedObjectAttribute](./kibana-plugin-server.savedobjectattribute.md) | Type definition for a Saved Object attribute value | -| [SavedObjectAttributeSingle](./kibana-plugin-server.savedobjectattributesingle.md) | Don't use this type, it's simply a helper type for [SavedObjectAttribute](./kibana-plugin-server.savedobjectattribute.md) | -| [SavedObjectsClientContract](./kibana-plugin-server.savedobjectsclientcontract.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing plugin state.\#\# SavedObjectsClient errorsSince the SavedObjectsClient has its hands in everything we are a little paranoid about the way we present errors back to to application code. Ideally, all errors will be either:1. Caused by bad implementation (ie. undefined is not a function) and as such unpredictable 2. An error that has been classified and decorated appropriately by the decorators in [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md)Type 1 errors are inevitable, but since all expected/handle-able errors should be Type 2 the isXYZError() helpers exposed at SavedObjectsErrorHelpers should be used to understand and manage error responses from the SavedObjectsClient.Type 2 errors are decorated versions of the source error, so if the elasticsearch client threw an error it will be decorated based on its type. That means that rather than looking for error.body.error.type or doing substring checks on error.body.error.reason, just use the helpers to understand the meaning of the error:\`\`\`js if (SavedObjectsErrorHelpers.isNotFoundError(error)) { // handle 404 }if (SavedObjectsErrorHelpers.isNotAuthorizedError(error)) { // 401 handling should be automatic, but in case you wanted to know }// always rethrow the error unless you handle it throw error; \`\`\`\#\#\# 404s from missing indexFrom the perspective of application code and APIs the SavedObjectsClient is a black box that persists objects. One of the internal details that users have no control over is that we use an elasticsearch index for persistance and that index might be missing.At the time of writing we are in the process of transitioning away from the operating assumption that the SavedObjects index is always available. Part of this transition is handling errors resulting from an index missing. These used to trigger a 500 error in most cases, and in others cause 404s with different error messages.From my (Spencer) perspective, a 404 from the SavedObjectsApi is a 404; The object the request/call was targeting could not be found. This is why \#14141 takes special care to ensure that 404 errors are generic and don't distinguish between index missing or document missing.\#\#\# 503s from missing indexUnlike all other methods, create requests are supposed to succeed even when the Kibana index does not exist because it will be automatically created by elasticsearch. When that is not the case it is because Elasticsearch's action.auto_create_index setting prevents it from being created automatically so we throw a special 503 with the intention of informing the user that their Elasticsearch settings need to be updated.See [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) See [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md) | -| [SavedObjectsClientFactory](./kibana-plugin-server.savedobjectsclientfactory.md) | Describes the factory used to create instances of the Saved Objects Client. | -| [SavedObjectsClientWrapperFactory](./kibana-plugin-server.savedobjectsclientwrapperfactory.md) | Describes the factory used to create instances of Saved Objects Client Wrappers. | -| [ScopeableRequest](./kibana-plugin-server.scopeablerequest.md) | A user credentials container. It accommodates the necessary auth credentials to impersonate the current user.See [KibanaRequest](./kibana-plugin-server.kibanarequest.md). | -| [SharedGlobalConfig](./kibana-plugin-server.sharedglobalconfig.md) | | -| [UiSettingsType](./kibana-plugin-server.uisettingstype.md) | UI element type to represent the settings. | - + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) + +## kibana-plugin-server package + +The Kibana Core APIs for server-side plugins. + +A plugin requires a `kibana.json` file at it's root directory that follows [the manfiest schema](./kibana-plugin-server.pluginmanifest.md) to define static plugin information required to load the plugin. + +A plugin's `server/index` file must contain a named import, `plugin`, that implements [PluginInitializer](./kibana-plugin-server.plugininitializer.md) which returns an object that implements [Plugin](./kibana-plugin-server.plugin.md). + +The plugin integrates with the core system via lifecycle events: `setup`, `start`, and `stop`. In each lifecycle method, the plugin will receive the corresponding core services available (either [CoreSetup](./kibana-plugin-server.coresetup.md) or [CoreStart](./kibana-plugin-server.corestart.md)) and any interfaces returned by dependency plugins' lifecycle method. Anything returned by the plugin's lifecycle method will be exposed to downstream dependencies when their corresponding lifecycle methods are invoked. + +## Classes + +| Class | Description | +| --- | --- | +| [BasePath](./kibana-plugin-server.basepath.md) | Access or manipulate the Kibana base path | +| [ClusterClient](./kibana-plugin-server.clusterclient.md) | Represents an Elasticsearch cluster API client created by the platform. It allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via asScoped(...)).See [ClusterClient](./kibana-plugin-server.clusterclient.md). | +| [CspConfig](./kibana-plugin-server.cspconfig.md) | CSP configuration for use in Kibana. | +| [ElasticsearchErrorHelpers](./kibana-plugin-server.elasticsearcherrorhelpers.md) | Helpers for working with errors returned from the Elasticsearch service.Since the internal data of errors are subject to change, consumers of the Elasticsearch service should always use these helpers to classify errors instead of checking error internals such as body.error.header[WWW-Authenticate] | +| [KibanaRequest](./kibana-plugin-server.kibanarequest.md) | Kibana specific abstraction for an incoming request. | +| [RouteValidationError](./kibana-plugin-server.routevalidationerror.md) | Error to return when the validation is not successful. | +| [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) | | +| [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md) | | +| [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) | | +| [ScopedClusterClient](./kibana-plugin-server.scopedclusterclient.md) | Serves the same purpose as "normal" ClusterClient but exposes additional callAsCurrentUser method that doesn't use credentials of the Kibana internal user (as callAsInternalUser does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API.See [ScopedClusterClient](./kibana-plugin-server.scopedclusterclient.md). | + +## Enumerations + +| Enumeration | Description | +| --- | --- | +| [AuthResultType](./kibana-plugin-server.authresulttype.md) | | +| [AuthStatus](./kibana-plugin-server.authstatus.md) | Status indicating an outcome of the authentication. | + +## Interfaces + +| Interface | Description | +| --- | --- | +| [APICaller](./kibana-plugin-server.apicaller.md) | | +| [AssistanceAPIResponse](./kibana-plugin-server.assistanceapiresponse.md) | | +| [AssistantAPIClientParams](./kibana-plugin-server.assistantapiclientparams.md) | | +| [Authenticated](./kibana-plugin-server.authenticated.md) | | +| [AuthResultParams](./kibana-plugin-server.authresultparams.md) | Result of an incoming request authentication. | +| [AuthToolkit](./kibana-plugin-server.authtoolkit.md) | A tool set defining an outcome of Auth interceptor for incoming request. | +| [CallAPIOptions](./kibana-plugin-server.callapioptions.md) | The set of options that defines how API call should be made and result be processed. | +| [Capabilities](./kibana-plugin-server.capabilities.md) | The read-only set of capabilities available for the current UI session. Capabilities are simple key-value pairs of (string, boolean), where the string denotes the capability ID, and the boolean is a flag indicating if the capability is enabled or disabled. | +| [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) | APIs to manage the [Capabilities](./kibana-plugin-server.capabilities.md) that will be used by the application.Plugins relying on capabilities to toggle some of their features should register them during the setup phase using the registerProvider method.Plugins having the responsibility to restrict capabilities depending on a given context should register their capabilities switcher using the registerSwitcher method.Refers to the methods documentation for complete description and examples. | +| [CapabilitiesStart](./kibana-plugin-server.capabilitiesstart.md) | APIs to access the application [Capabilities](./kibana-plugin-server.capabilities.md). | +| [ConfigDeprecationFactory](./kibana-plugin-server.configdeprecationfactory.md) | Provides helpers to generates the most commonly used [ConfigDeprecation](./kibana-plugin-server.configdeprecation.md) when invoking a [ConfigDeprecationProvider](./kibana-plugin-server.configdeprecationprovider.md).See methods documentation for more detailed examples. | +| [ContextSetup](./kibana-plugin-server.contextsetup.md) | An object that handles registration of context providers and configuring handlers with context. | +| [CoreSetup](./kibana-plugin-server.coresetup.md) | Context passed to the plugins setup method. | +| [CoreStart](./kibana-plugin-server.corestart.md) | Context passed to the plugins start method. | +| [CustomHttpResponseOptions](./kibana-plugin-server.customhttpresponseoptions.md) | HTTP response parameters for a response with adjustable status code. | +| [DeprecationAPIClientParams](./kibana-plugin-server.deprecationapiclientparams.md) | | +| [DeprecationAPIResponse](./kibana-plugin-server.deprecationapiresponse.md) | | +| [DeprecationInfo](./kibana-plugin-server.deprecationinfo.md) | | +| [DeprecationSettings](./kibana-plugin-server.deprecationsettings.md) | UiSettings deprecation field options. | +| [DiscoveredPlugin](./kibana-plugin-server.discoveredplugin.md) | Small container object used to expose information about discovered plugins that may or may not have been started. | +| [ElasticsearchError](./kibana-plugin-server.elasticsearcherror.md) | | +| [ElasticsearchServiceSetup](./kibana-plugin-server.elasticsearchservicesetup.md) | | +| [EnvironmentMode](./kibana-plugin-server.environmentmode.md) | | +| [ErrorHttpResponseOptions](./kibana-plugin-server.errorhttpresponseoptions.md) | HTTP response parameters | +| [FakeRequest](./kibana-plugin-server.fakerequest.md) | Fake request object created manually by Kibana plugins. | +| [HttpResponseOptions](./kibana-plugin-server.httpresponseoptions.md) | HTTP response parameters | +| [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) | Kibana HTTP Service provides own abstraction for work with HTTP stack. Plugins don't have direct access to hapi server and its primitives anymore. Moreover, plugins shouldn't rely on the fact that HTTP Service uses one or another library under the hood. This gives the platform flexibility to upgrade or changing our internal HTTP stack without breaking plugins. If the HTTP Service lacks functionality you need, we are happy to discuss and support your needs. | +| [HttpServiceStart](./kibana-plugin-server.httpservicestart.md) | | +| [IContextContainer](./kibana-plugin-server.icontextcontainer.md) | An object that handles registration of context providers and configuring handlers with context. | +| [ICspConfig](./kibana-plugin-server.icspconfig.md) | CSP configuration for use in Kibana. | +| [IKibanaResponse](./kibana-plugin-server.ikibanaresponse.md) | A response data object, expected to returned as a result of [RequestHandler](./kibana-plugin-server.requesthandler.md) execution | +| [IKibanaSocket](./kibana-plugin-server.ikibanasocket.md) | A tiny abstraction for TCP socket. | +| [ImageValidation](./kibana-plugin-server.imagevalidation.md) | | +| [IndexSettingsDeprecationInfo](./kibana-plugin-server.indexsettingsdeprecationinfo.md) | | +| [IRenderOptions](./kibana-plugin-server.irenderoptions.md) | | +| [IRouter](./kibana-plugin-server.irouter.md) | Registers route handlers for specified resource path and method. See [RouteConfig](./kibana-plugin-server.routeconfig.md) and [RequestHandler](./kibana-plugin-server.requesthandler.md) for more information about arguments to route registrations. | +| [IScopedRenderingClient](./kibana-plugin-server.iscopedrenderingclient.md) | | +| [IUiSettingsClient](./kibana-plugin-server.iuisettingsclient.md) | Server-side client that provides access to the advanced settings stored in elasticsearch. The settings provide control over the behavior of the Kibana application. For example, a user can specify how to display numeric or date fields. Users can adjust the settings via Management UI. | +| [KibanaRequestEvents](./kibana-plugin-server.kibanarequestevents.md) | Request events. | +| [KibanaRequestRoute](./kibana-plugin-server.kibanarequestroute.md) | Request specific route information exposed to a handler. | +| [LegacyRequest](./kibana-plugin-server.legacyrequest.md) | | +| [LegacyServiceSetupDeps](./kibana-plugin-server.legacyservicesetupdeps.md) | | +| [LegacyServiceStartDeps](./kibana-plugin-server.legacyservicestartdeps.md) | | +| [Logger](./kibana-plugin-server.logger.md) | Logger exposes all the necessary methods to log any type of information and this is the interface used by the logging consumers including plugins. | +| [LoggerFactory](./kibana-plugin-server.loggerfactory.md) | The single purpose of LoggerFactory interface is to define a way to retrieve a context-based logger instance. | +| [LogMeta](./kibana-plugin-server.logmeta.md) | Contextual metadata | +| [OnPostAuthToolkit](./kibana-plugin-server.onpostauthtoolkit.md) | A tool set defining an outcome of OnPostAuth interceptor for incoming request. | +| [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md) | A tool set defining an outcome of OnPreAuth interceptor for incoming request. | +| [OnPreResponseExtensions](./kibana-plugin-server.onpreresponseextensions.md) | Additional data to extend a response. | +| [OnPreResponseInfo](./kibana-plugin-server.onpreresponseinfo.md) | Response status code. | +| [OnPreResponseToolkit](./kibana-plugin-server.onpreresponsetoolkit.md) | A tool set defining an outcome of OnPreAuth interceptor for incoming request. | +| [PackageInfo](./kibana-plugin-server.packageinfo.md) | | +| [Plugin](./kibana-plugin-server.plugin.md) | The interface that should be returned by a PluginInitializer. | +| [PluginConfigDescriptor](./kibana-plugin-server.pluginconfigdescriptor.md) | Describes a plugin configuration properties. | +| [PluginInitializerContext](./kibana-plugin-server.plugininitializercontext.md) | Context that's available to plugins during initialization stage. | +| [PluginManifest](./kibana-plugin-server.pluginmanifest.md) | Describes the set of required and optional properties plugin can define in its mandatory JSON manifest file. | +| [PluginsServiceSetup](./kibana-plugin-server.pluginsservicesetup.md) | | +| [PluginsServiceStart](./kibana-plugin-server.pluginsservicestart.md) | | +| [RequestHandlerContext](./kibana-plugin-server.requesthandlercontext.md) | Plugin specific context passed to a route handler.Provides the following clients: - [rendering](./kibana-plugin-server.iscopedrenderingclient.md) - Rendering client which uses the data of the incoming request - [savedObjects.client](./kibana-plugin-server.savedobjectsclient.md) - Saved Objects client which uses the credentials of the incoming request - [elasticsearch.dataClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch data client which uses the credentials of the incoming request - [elasticsearch.adminClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch admin client which uses the credentials of the incoming request - [uiSettings.client](./kibana-plugin-server.iuisettingsclient.md) - uiSettings client which uses the credentials of the incoming request | +| [RouteConfig](./kibana-plugin-server.routeconfig.md) | Route specific configuration. | +| [RouteConfigOptions](./kibana-plugin-server.routeconfigoptions.md) | Additional route options. | +| [RouteConfigOptionsBody](./kibana-plugin-server.routeconfigoptionsbody.md) | Additional body options for a route | +| [RouteValidationResultFactory](./kibana-plugin-server.routevalidationresultfactory.md) | Validation result factory to be used in the custom validation function to return the valid data or validation errorsSee [RouteValidationFunction](./kibana-plugin-server.routevalidationfunction.md). | +| [RouteValidatorConfig](./kibana-plugin-server.routevalidatorconfig.md) | The configuration object to the RouteValidator class. Set params, query and/or body to specify the validation logic to follow for that property. | +| [RouteValidatorOptions](./kibana-plugin-server.routevalidatoroptions.md) | Additional options for the RouteValidator class to modify its default behaviour. | +| [SavedObject](./kibana-plugin-server.savedobject.md) | | +| [SavedObjectAttributes](./kibana-plugin-server.savedobjectattributes.md) | The data for a Saved Object is stored as an object in the attributes property. | +| [SavedObjectReference](./kibana-plugin-server.savedobjectreference.md) | A reference to another saved object. | +| [SavedObjectsBaseOptions](./kibana-plugin-server.savedobjectsbaseoptions.md) | | +| [SavedObjectsBulkCreateObject](./kibana-plugin-server.savedobjectsbulkcreateobject.md) | | +| [SavedObjectsBulkGetObject](./kibana-plugin-server.savedobjectsbulkgetobject.md) | | +| [SavedObjectsBulkResponse](./kibana-plugin-server.savedobjectsbulkresponse.md) | | +| [SavedObjectsBulkUpdateObject](./kibana-plugin-server.savedobjectsbulkupdateobject.md) | | +| [SavedObjectsBulkUpdateOptions](./kibana-plugin-server.savedobjectsbulkupdateoptions.md) | | +| [SavedObjectsBulkUpdateResponse](./kibana-plugin-server.savedobjectsbulkupdateresponse.md) | | +| [SavedObjectsClientProviderOptions](./kibana-plugin-server.savedobjectsclientprovideroptions.md) | Options to control the creation of the Saved Objects Client. | +| [SavedObjectsClientWrapperOptions](./kibana-plugin-server.savedobjectsclientwrapperoptions.md) | Options passed to each SavedObjectsClientWrapperFactory to aid in creating the wrapper instance. | +| [SavedObjectsCreateOptions](./kibana-plugin-server.savedobjectscreateoptions.md) | | +| [SavedObjectsDeleteByNamespaceOptions](./kibana-plugin-server.savedobjectsdeletebynamespaceoptions.md) | | +| [SavedObjectsDeleteOptions](./kibana-plugin-server.savedobjectsdeleteoptions.md) | | +| [SavedObjectsExportOptions](./kibana-plugin-server.savedobjectsexportoptions.md) | Options controlling the export operation. | +| [SavedObjectsExportResultDetails](./kibana-plugin-server.savedobjectsexportresultdetails.md) | Structure of the export result details entry | +| [SavedObjectsFindOptions](./kibana-plugin-server.savedobjectsfindoptions.md) | | +| [SavedObjectsFindResponse](./kibana-plugin-server.savedobjectsfindresponse.md) | Return type of the Saved Objects find() method.\*Note\*: this type is different between the Public and Server Saved Objects clients. | +| [SavedObjectsImportConflictError](./kibana-plugin-server.savedobjectsimportconflicterror.md) | Represents a failure to import due to a conflict. | +| [SavedObjectsImportError](./kibana-plugin-server.savedobjectsimporterror.md) | Represents a failure to import. | +| [SavedObjectsImportMissingReferencesError](./kibana-plugin-server.savedobjectsimportmissingreferenceserror.md) | Represents a failure to import due to missing references. | +| [SavedObjectsImportOptions](./kibana-plugin-server.savedobjectsimportoptions.md) | Options to control the import operation. | +| [SavedObjectsImportResponse](./kibana-plugin-server.savedobjectsimportresponse.md) | The response describing the result of an import. | +| [SavedObjectsImportRetry](./kibana-plugin-server.savedobjectsimportretry.md) | Describes a retry operation for importing a saved object. | +| [SavedObjectsImportUnknownError](./kibana-plugin-server.savedobjectsimportunknownerror.md) | Represents a failure to import due to an unknown reason. | +| [SavedObjectsImportUnsupportedTypeError](./kibana-plugin-server.savedobjectsimportunsupportedtypeerror.md) | Represents a failure to import due to having an unsupported saved object type. | +| [SavedObjectsIncrementCounterOptions](./kibana-plugin-server.savedobjectsincrementcounteroptions.md) | | +| [SavedObjectsMigrationLogger](./kibana-plugin-server.savedobjectsmigrationlogger.md) | | +| [SavedObjectsMigrationVersion](./kibana-plugin-server.savedobjectsmigrationversion.md) | Information about the migrations that have been applied to this SavedObject. When Kibana starts up, KibanaMigrator detects outdated documents and migrates them based on this value. For each migration that has been applied, the plugin's name is used as a key and the latest migration version as the value. | +| [SavedObjectsRawDoc](./kibana-plugin-server.savedobjectsrawdoc.md) | A raw document as represented directly in the saved object index. | +| [SavedObjectsRepositoryFactory](./kibana-plugin-server.savedobjectsrepositoryfactory.md) | Factory provided when invoking a [client factory provider](./kibana-plugin-server.savedobjectsclientfactoryprovider.md) See [SavedObjectsServiceSetup.setClientFactoryProvider](./kibana-plugin-server.savedobjectsservicesetup.setclientfactoryprovider.md) | +| [SavedObjectsResolveImportErrorsOptions](./kibana-plugin-server.savedobjectsresolveimporterrorsoptions.md) | Options to control the "resolve import" operation. | +| [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) | Saved Objects is Kibana's data persistence mechanism allowing plugins to use Elasticsearch for storing and querying state. The SavedObjectsServiceSetup API exposes methods for creating and registering Saved Object client wrappers. | +| [SavedObjectsServiceStart](./kibana-plugin-server.savedobjectsservicestart.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing and querying state. The SavedObjectsServiceStart API provides a scoped Saved Objects client for interacting with Saved Objects. | +| [SavedObjectsUpdateOptions](./kibana-plugin-server.savedobjectsupdateoptions.md) | | +| [SavedObjectsUpdateResponse](./kibana-plugin-server.savedobjectsupdateresponse.md) | | +| [SessionCookieValidationResult](./kibana-plugin-server.sessioncookievalidationresult.md) | Return type from a function to validate cookie contents. | +| [SessionStorage](./kibana-plugin-server.sessionstorage.md) | Provides an interface to store and retrieve data across requests. | +| [SessionStorageCookieOptions](./kibana-plugin-server.sessionstoragecookieoptions.md) | Configuration used to create HTTP session storage based on top of cookie mechanism. | +| [SessionStorageFactory](./kibana-plugin-server.sessionstoragefactory.md) | SessionStorage factory to bind one to an incoming request | +| [StringValidation](./kibana-plugin-server.stringvalidation.md) | | +| [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) | UiSettings parameters defined by the plugins. | +| [UiSettingsServiceSetup](./kibana-plugin-server.uisettingsservicesetup.md) | | +| [UiSettingsServiceStart](./kibana-plugin-server.uisettingsservicestart.md) | | +| [UserProvidedValues](./kibana-plugin-server.userprovidedvalues.md) | Describes the values explicitly set by user. | +| [UuidServiceSetup](./kibana-plugin-server.uuidservicesetup.md) | APIs to access the application's instance uuid. | + +## Variables + +| Variable | Description | +| --- | --- | +| [kibanaResponseFactory](./kibana-plugin-server.kibanaresponsefactory.md) | Set of helpers used to create KibanaResponse to form HTTP response on an incoming request. Should be returned as a result of [RequestHandler](./kibana-plugin-server.requesthandler.md) execution. | +| [validBodyOutput](./kibana-plugin-server.validbodyoutput.md) | The set of valid body.output | + +## Type Aliases + +| Type Alias | Description | +| --- | --- | +| [AuthenticationHandler](./kibana-plugin-server.authenticationhandler.md) | See [AuthToolkit](./kibana-plugin-server.authtoolkit.md). | +| [AuthHeaders](./kibana-plugin-server.authheaders.md) | Auth Headers map | +| [AuthResult](./kibana-plugin-server.authresult.md) | | +| [CapabilitiesProvider](./kibana-plugin-server.capabilitiesprovider.md) | See [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) | +| [CapabilitiesSwitcher](./kibana-plugin-server.capabilitiesswitcher.md) | See [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) | +| [ConfigDeprecation](./kibana-plugin-server.configdeprecation.md) | Configuration deprecation returned from [ConfigDeprecationProvider](./kibana-plugin-server.configdeprecationprovider.md) that handles a single deprecation from the configuration. | +| [ConfigDeprecationLogger](./kibana-plugin-server.configdeprecationlogger.md) | Logger interface used when invoking a [ConfigDeprecation](./kibana-plugin-server.configdeprecation.md) | +| [ConfigDeprecationProvider](./kibana-plugin-server.configdeprecationprovider.md) | A provider that should returns a list of [ConfigDeprecation](./kibana-plugin-server.configdeprecation.md).See [ConfigDeprecationFactory](./kibana-plugin-server.configdeprecationfactory.md) for more usage examples. | +| [ConfigPath](./kibana-plugin-server.configpath.md) | | +| [ElasticsearchClientConfig](./kibana-plugin-server.elasticsearchclientconfig.md) | | +| [GetAuthHeaders](./kibana-plugin-server.getauthheaders.md) | Get headers to authenticate a user against Elasticsearch. | +| [GetAuthState](./kibana-plugin-server.getauthstate.md) | Get authentication state for a request. Returned by auth interceptor. | +| [HandlerContextType](./kibana-plugin-server.handlercontexttype.md) | Extracts the type of the first argument of a [HandlerFunction](./kibana-plugin-server.handlerfunction.md) to represent the type of the context. | +| [HandlerFunction](./kibana-plugin-server.handlerfunction.md) | A function that accepts a context object and an optional number of additional arguments. Used for the generic types in [IContextContainer](./kibana-plugin-server.icontextcontainer.md) | +| [HandlerParameters](./kibana-plugin-server.handlerparameters.md) | Extracts the types of the additional arguments of a [HandlerFunction](./kibana-plugin-server.handlerfunction.md), excluding the [HandlerContextType](./kibana-plugin-server.handlercontexttype.md). | +| [Headers](./kibana-plugin-server.headers.md) | Http request headers to read. | +| [HttpResponsePayload](./kibana-plugin-server.httpresponsepayload.md) | Data send to the client as a response payload. | +| [IBasePath](./kibana-plugin-server.ibasepath.md) | Access or manipulate the Kibana base path[BasePath](./kibana-plugin-server.basepath.md) | +| [IClusterClient](./kibana-plugin-server.iclusterclient.md) | Represents an Elasticsearch cluster API client created by the platform. It allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via asScoped(...)).See [ClusterClient](./kibana-plugin-server.clusterclient.md). | +| [IContextProvider](./kibana-plugin-server.icontextprovider.md) | A function that returns a context value for a specific key of given context type. | +| [ICustomClusterClient](./kibana-plugin-server.icustomclusterclient.md) | Represents an Elasticsearch cluster API client created by a plugin. It allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via asScoped(...)).See [ClusterClient](./kibana-plugin-server.clusterclient.md). | +| [IsAuthenticated](./kibana-plugin-server.isauthenticated.md) | Return authentication status for a request. | +| [ISavedObjectsRepository](./kibana-plugin-server.isavedobjectsrepository.md) | See [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) | +| [IScopedClusterClient](./kibana-plugin-server.iscopedclusterclient.md) | Serves the same purpose as "normal" ClusterClient but exposes additional callAsCurrentUser method that doesn't use credentials of the Kibana internal user (as callAsInternalUser does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API.See [ScopedClusterClient](./kibana-plugin-server.scopedclusterclient.md). | +| [KibanaRequestRouteOptions](./kibana-plugin-server.kibanarequestrouteoptions.md) | Route options: If 'GET' or 'OPTIONS' method, body options won't be returned. | +| [KibanaResponseFactory](./kibana-plugin-server.kibanaresponsefactory.md) | Creates an object containing request response payload, HTTP headers, error details, and other data transmitted to the client. | +| [KnownHeaders](./kibana-plugin-server.knownheaders.md) | Set of well-known HTTP headers. | +| [LifecycleResponseFactory](./kibana-plugin-server.lifecycleresponsefactory.md) | Creates an object containing redirection or error response with error details, HTTP headers, and other data transmitted to the client. | +| [MIGRATION\_ASSISTANCE\_INDEX\_ACTION](./kibana-plugin-server.migration_assistance_index_action.md) | | +| [MIGRATION\_DEPRECATION\_LEVEL](./kibana-plugin-server.migration_deprecation_level.md) | | +| [MutatingOperationRefreshSetting](./kibana-plugin-server.mutatingoperationrefreshsetting.md) | Elasticsearch Refresh setting for mutating operation | +| [OnPostAuthHandler](./kibana-plugin-server.onpostauthhandler.md) | See [OnPostAuthToolkit](./kibana-plugin-server.onpostauthtoolkit.md). | +| [OnPreAuthHandler](./kibana-plugin-server.onpreauthhandler.md) | See [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md). | +| [OnPreResponseHandler](./kibana-plugin-server.onpreresponsehandler.md) | See [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md). | +| [PluginConfigSchema](./kibana-plugin-server.pluginconfigschema.md) | Dedicated type for plugin configuration schema. | +| [PluginInitializer](./kibana-plugin-server.plugininitializer.md) | The plugin export at the root of a plugin's server directory should conform to this interface. | +| [PluginName](./kibana-plugin-server.pluginname.md) | Dedicated type for plugin name/id that is supposed to make Map/Set/Arrays that use it as a key or value more obvious. | +| [PluginOpaqueId](./kibana-plugin-server.pluginopaqueid.md) | | +| [RecursiveReadonly](./kibana-plugin-server.recursivereadonly.md) | | +| [RedirectResponseOptions](./kibana-plugin-server.redirectresponseoptions.md) | HTTP response parameters for redirection response | +| [RequestHandler](./kibana-plugin-server.requesthandler.md) | A function executed when route path matched requested resource path. Request handler is expected to return a result of one of [KibanaResponseFactory](./kibana-plugin-server.kibanaresponsefactory.md) functions. | +| [RequestHandlerContextContainer](./kibana-plugin-server.requesthandlercontextcontainer.md) | An object that handles registration of http request context providers. | +| [RequestHandlerContextProvider](./kibana-plugin-server.requesthandlercontextprovider.md) | Context provider for request handler. Extends request context object with provided functionality or data. | +| [ResponseError](./kibana-plugin-server.responseerror.md) | Error message and optional data send to the client in case of error. | +| [ResponseErrorAttributes](./kibana-plugin-server.responseerrorattributes.md) | Additional data to provide error details. | +| [ResponseHeaders](./kibana-plugin-server.responseheaders.md) | Http response headers to set. | +| [RouteContentType](./kibana-plugin-server.routecontenttype.md) | The set of supported parseable Content-Types | +| [RouteMethod](./kibana-plugin-server.routemethod.md) | The set of common HTTP methods supported by Kibana routing. | +| [RouteRegistrar](./kibana-plugin-server.routeregistrar.md) | Route handler common definition | +| [RouteValidationFunction](./kibana-plugin-server.routevalidationfunction.md) | The custom validation function if @kbn/config-schema is not a valid solution for your specific plugin requirements. | +| [RouteValidationSpec](./kibana-plugin-server.routevalidationspec.md) | Allowed property validation options: either @kbn/config-schema validations or custom validation functionsSee [RouteValidationFunction](./kibana-plugin-server.routevalidationfunction.md) for custom validation. | +| [RouteValidatorFullConfig](./kibana-plugin-server.routevalidatorfullconfig.md) | Route validations config and options merged into one object | +| [SavedObjectAttribute](./kibana-plugin-server.savedobjectattribute.md) | Type definition for a Saved Object attribute value | +| [SavedObjectAttributeSingle](./kibana-plugin-server.savedobjectattributesingle.md) | Don't use this type, it's simply a helper type for [SavedObjectAttribute](./kibana-plugin-server.savedobjectattribute.md) | +| [SavedObjectsClientContract](./kibana-plugin-server.savedobjectsclientcontract.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing plugin state.\#\# SavedObjectsClient errorsSince the SavedObjectsClient has its hands in everything we are a little paranoid about the way we present errors back to to application code. Ideally, all errors will be either:1. Caused by bad implementation (ie. undefined is not a function) and as such unpredictable 2. An error that has been classified and decorated appropriately by the decorators in [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md)Type 1 errors are inevitable, but since all expected/handle-able errors should be Type 2 the isXYZError() helpers exposed at SavedObjectsErrorHelpers should be used to understand and manage error responses from the SavedObjectsClient.Type 2 errors are decorated versions of the source error, so if the elasticsearch client threw an error it will be decorated based on its type. That means that rather than looking for error.body.error.type or doing substring checks on error.body.error.reason, just use the helpers to understand the meaning of the error:\`\`\`js if (SavedObjectsErrorHelpers.isNotFoundError(error)) { // handle 404 }if (SavedObjectsErrorHelpers.isNotAuthorizedError(error)) { // 401 handling should be automatic, but in case you wanted to know }// always rethrow the error unless you handle it throw error; \`\`\`\#\#\# 404s from missing indexFrom the perspective of application code and APIs the SavedObjectsClient is a black box that persists objects. One of the internal details that users have no control over is that we use an elasticsearch index for persistance and that index might be missing.At the time of writing we are in the process of transitioning away from the operating assumption that the SavedObjects index is always available. Part of this transition is handling errors resulting from an index missing. These used to trigger a 500 error in most cases, and in others cause 404s with different error messages.From my (Spencer) perspective, a 404 from the SavedObjectsApi is a 404; The object the request/call was targeting could not be found. This is why \#14141 takes special care to ensure that 404 errors are generic and don't distinguish between index missing or document missing.\#\#\# 503s from missing indexUnlike all other methods, create requests are supposed to succeed even when the Kibana index does not exist because it will be automatically created by elasticsearch. When that is not the case it is because Elasticsearch's action.auto_create_index setting prevents it from being created automatically so we throw a special 503 with the intention of informing the user that their Elasticsearch settings need to be updated.See [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) See [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md) | +| [SavedObjectsClientFactory](./kibana-plugin-server.savedobjectsclientfactory.md) | Describes the factory used to create instances of the Saved Objects Client. | +| [SavedObjectsClientFactoryProvider](./kibana-plugin-server.savedobjectsclientfactoryprovider.md) | Provider to invoke to retrieve a [SavedObjectsClientFactory](./kibana-plugin-server.savedobjectsclientfactory.md). | +| [SavedObjectsClientWrapperFactory](./kibana-plugin-server.savedobjectsclientwrapperfactory.md) | Describes the factory used to create instances of Saved Objects Client Wrappers. | +| [ScopeableRequest](./kibana-plugin-server.scopeablerequest.md) | A user credentials container. It accommodates the necessary auth credentials to impersonate the current user.See [KibanaRequest](./kibana-plugin-server.kibanarequest.md). | +| [SharedGlobalConfig](./kibana-plugin-server.sharedglobalconfig.md) | | +| [UiSettingsType](./kibana-plugin-server.uisettingstype.md) | UI element type to represent the settings. | + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsclientfactory.md b/docs/development/core/server/kibana-plugin-server.savedobjectsclientfactory.md index 9e30759720680..01c6c6a108b7b 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsclientfactory.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsclientfactory.md @@ -1,15 +1,15 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsClientFactory](./kibana-plugin-server.savedobjectsclientfactory.md) - -## SavedObjectsClientFactory type - -Describes the factory used to create instances of the Saved Objects Client. - -Signature: - -```typescript -export declare type SavedObjectsClientFactory = ({ request, }: { - request: Request; -}) => SavedObjectsClientContract; -``` + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsClientFactory](./kibana-plugin-server.savedobjectsclientfactory.md) + +## SavedObjectsClientFactory type + +Describes the factory used to create instances of the Saved Objects Client. + +Signature: + +```typescript +export declare type SavedObjectsClientFactory = ({ request, }: { + request: KibanaRequest; +}) => SavedObjectsClientContract; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsclientfactoryprovider.md b/docs/development/core/server/kibana-plugin-server.savedobjectsclientfactoryprovider.md new file mode 100644 index 0000000000000..59617b6be443c --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsclientfactoryprovider.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsClientFactoryProvider](./kibana-plugin-server.savedobjectsclientfactoryprovider.md) + +## SavedObjectsClientFactoryProvider type + +Provider to invoke to retrieve a [SavedObjectsClientFactory](./kibana-plugin-server.savedobjectsclientfactory.md). + +Signature: + +```typescript +export declare type SavedObjectsClientFactoryProvider = (repositoryFactory: SavedObjectsRepositoryFactory) => SavedObjectsClientFactory; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsclientwrapperfactory.md b/docs/development/core/server/kibana-plugin-server.savedobjectsclientwrapperfactory.md index 3ef2fac727b01..f429c92209900 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsclientwrapperfactory.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsclientwrapperfactory.md @@ -1,13 +1,13 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsClientWrapperFactory](./kibana-plugin-server.savedobjectsclientwrapperfactory.md) - -## SavedObjectsClientWrapperFactory type - -Describes the factory used to create instances of Saved Objects Client Wrappers. - -Signature: - -```typescript -export declare type SavedObjectsClientWrapperFactory = (options: SavedObjectsClientWrapperOptions) => SavedObjectsClientContract; -``` + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsClientWrapperFactory](./kibana-plugin-server.savedobjectsclientwrapperfactory.md) + +## SavedObjectsClientWrapperFactory type + +Describes the factory used to create instances of Saved Objects Client Wrappers. + +Signature: + +```typescript +export declare type SavedObjectsClientWrapperFactory = (options: SavedObjectsClientWrapperOptions) => SavedObjectsClientContract; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsclientwrapperoptions.md b/docs/development/core/server/kibana-plugin-server.savedobjectsclientwrapperoptions.md index 65e7cfa64c2a6..dfff863898a2b 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsclientwrapperoptions.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsclientwrapperoptions.md @@ -1,21 +1,21 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsClientWrapperOptions](./kibana-plugin-server.savedobjectsclientwrapperoptions.md) - -## SavedObjectsClientWrapperOptions interface - -Options passed to each SavedObjectsClientWrapperFactory to aid in creating the wrapper instance. - -Signature: - -```typescript -export interface SavedObjectsClientWrapperOptions -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [client](./kibana-plugin-server.savedobjectsclientwrapperoptions.client.md) | SavedObjectsClientContract | | -| [request](./kibana-plugin-server.savedobjectsclientwrapperoptions.request.md) | Request | | - + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsClientWrapperOptions](./kibana-plugin-server.savedobjectsclientwrapperoptions.md) + +## SavedObjectsClientWrapperOptions interface + +Options passed to each SavedObjectsClientWrapperFactory to aid in creating the wrapper instance. + +Signature: + +```typescript +export interface SavedObjectsClientWrapperOptions +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [client](./kibana-plugin-server.savedobjectsclientwrapperoptions.client.md) | SavedObjectsClientContract | | +| [request](./kibana-plugin-server.savedobjectsclientwrapperoptions.request.md) | KibanaRequest | | + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsclientwrapperoptions.request.md b/docs/development/core/server/kibana-plugin-server.savedobjectsclientwrapperoptions.request.md index 0ff75028612d0..89c7e0ed207ff 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsclientwrapperoptions.request.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsclientwrapperoptions.request.md @@ -1,11 +1,11 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsClientWrapperOptions](./kibana-plugin-server.savedobjectsclientwrapperoptions.md) > [request](./kibana-plugin-server.savedobjectsclientwrapperoptions.request.md) - -## SavedObjectsClientWrapperOptions.request property - -Signature: - -```typescript -request: Request; -``` + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsClientWrapperOptions](./kibana-plugin-server.savedobjectsclientwrapperoptions.md) > [request](./kibana-plugin-server.savedobjectsclientwrapperoptions.request.md) + +## SavedObjectsClientWrapperOptions.request property + +Signature: + +```typescript +request: KibanaRequest; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepositoryfactory.createinternalrepository.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepositoryfactory.createinternalrepository.md new file mode 100644 index 0000000000000..b808d38793fff --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepositoryfactory.createinternalrepository.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepositoryFactory](./kibana-plugin-server.savedobjectsrepositoryfactory.md) > [createInternalRepository](./kibana-plugin-server.savedobjectsrepositoryfactory.createinternalrepository.md) + +## SavedObjectsRepositoryFactory.createInternalRepository property + +Creates a [Saved Objects repository](./kibana-plugin-server.isavedobjectsrepository.md) that uses the internal Kibana user for authenticating with Elasticsearch. + +Signature: + +```typescript +createInternalRepository: (extraTypes?: string[]) => ISavedObjectsRepository; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepositoryfactory.createscopedrepository.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepositoryfactory.createscopedrepository.md new file mode 100644 index 0000000000000..20322d809dce7 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepositoryfactory.createscopedrepository.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepositoryFactory](./kibana-plugin-server.savedobjectsrepositoryfactory.md) > [createScopedRepository](./kibana-plugin-server.savedobjectsrepositoryfactory.createscopedrepository.md) + +## SavedObjectsRepositoryFactory.createScopedRepository property + +Creates a [Saved Objects repository](./kibana-plugin-server.isavedobjectsrepository.md) that uses the credentials from the passed in request to authenticate with Elasticsearch. + +Signature: + +```typescript +createScopedRepository: (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepositoryfactory.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepositoryfactory.md new file mode 100644 index 0000000000000..fc6c4a516284a --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepositoryfactory.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepositoryFactory](./kibana-plugin-server.savedobjectsrepositoryfactory.md) + +## SavedObjectsRepositoryFactory interface + +Factory provided when invoking a [client factory provider](./kibana-plugin-server.savedobjectsclientfactoryprovider.md) See [SavedObjectsServiceSetup.setClientFactoryProvider](./kibana-plugin-server.savedobjectsservicesetup.setclientfactoryprovider.md) + +Signature: + +```typescript +export interface SavedObjectsRepositoryFactory +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [createInternalRepository](./kibana-plugin-server.savedobjectsrepositoryfactory.createinternalrepository.md) | (extraTypes?: string[]) => ISavedObjectsRepository | Creates a [Saved Objects repository](./kibana-plugin-server.isavedobjectsrepository.md) that uses the internal Kibana user for authenticating with Elasticsearch. | +| [createScopedRepository](./kibana-plugin-server.savedobjectsrepositoryfactory.createscopedrepository.md) | (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository | Creates a [Saved Objects repository](./kibana-plugin-server.isavedobjectsrepository.md) that uses the credentials from the passed in request to authenticate with Elasticsearch. | + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.addclientwrapper.md b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.addclientwrapper.md index e787d737ada17..becff5bd2821e 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.addclientwrapper.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.addclientwrapper.md @@ -1,13 +1,13 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) > [addClientWrapper](./kibana-plugin-server.savedobjectsservicesetup.addclientwrapper.md) - -## SavedObjectsServiceSetup.addClientWrapper property - -Add a client wrapper with the given priority. - -Signature: - -```typescript -addClientWrapper: (priority: number, id: string, factory: SavedObjectsClientWrapperFactory) => void; -``` + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) > [addClientWrapper](./kibana-plugin-server.savedobjectsservicesetup.addclientwrapper.md) + +## SavedObjectsServiceSetup.addClientWrapper property + +Add a [client wrapper factory](./kibana-plugin-server.savedobjectsclientwrapperfactory.md) with the given priority. + +Signature: + +```typescript +addClientWrapper: (priority: number, id: string, factory: SavedObjectsClientWrapperFactory) => void; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createinternalrepository.md b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createinternalrepository.md deleted file mode 100644 index 492aa1a2453a1..0000000000000 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createinternalrepository.md +++ /dev/null @@ -1,18 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) > [createInternalRepository](./kibana-plugin-server.savedobjectsservicesetup.createinternalrepository.md) - -## SavedObjectsServiceSetup.createInternalRepository property - -Creates a [Saved Objects repository](./kibana-plugin-server.isavedobjectsrepository.md) that uses the internal Kibana user for authenticating with Elasticsearch. - -Signature: - -```typescript -createInternalRepository: (extraTypes?: string[]) => ISavedObjectsRepository; -``` - -## Remarks - -The repository should only be used for creating and registering a client factory or client wrapper. Using the repository directly for interacting with Saved Objects is an anti-pattern. Use the Saved Objects client from the [SavedObjectsServiceStart\#getScopedClient](./kibana-plugin-server.savedobjectsservicestart.md) method or the [route handler context](./kibana-plugin-server.requesthandlercontext.md) instead. - diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createscopedrepository.md b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createscopedrepository.md deleted file mode 100644 index fc5aa40c21a20..0000000000000 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createscopedrepository.md +++ /dev/null @@ -1,18 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) > [createScopedRepository](./kibana-plugin-server.savedobjectsservicesetup.createscopedrepository.md) - -## SavedObjectsServiceSetup.createScopedRepository property - -Creates a [Saved Objects repository](./kibana-plugin-server.isavedobjectsrepository.md) that uses the credentials from the passed in request to authenticate with Elasticsearch. - -Signature: - -```typescript -createScopedRepository: (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository; -``` - -## Remarks - -The repository should only be used for creating and registering a client factory or client wrapper. Using the repository directly for interacting with Saved Objects is an anti-pattern. Use the Saved Objects client from the [SavedObjectsServiceStart\#getScopedClient](./kibana-plugin-server.savedobjectsservicestart.md) method or the [route handler context](./kibana-plugin-server.requesthandlercontext.md) instead. - diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.md b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.md index 95bd817a43da6..64fb1f4a5f638 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.md @@ -1,35 +1,33 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) - -## SavedObjectsServiceSetup interface - -Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing and querying state. The SavedObjectsServiceSetup API exposes methods for creating and registering Saved Object client wrappers. - -Signature: - -```typescript -export interface SavedObjectsServiceSetup -``` - -## Remarks - -Note: The Saved Object setup API's should only be used for creating and registering client wrappers. Constructing a Saved Objects client or repository for use within your own plugin won't have any of the registered wrappers applied and is considered an anti-pattern. Use the Saved Objects client from the [SavedObjectsServiceStart\#getScopedClient](./kibana-plugin-server.savedobjectsservicestart.md) method or the [route handler context](./kibana-plugin-server.requesthandlercontext.md) instead. - -When plugins access the Saved Objects client, a new client is created using the factory provided to `setClientFactory` and wrapped by all wrappers registered through `addClientWrapper`. To create a factory or wrapper, plugins will have to construct a Saved Objects client. First create a repository by calling `scopedRepository` or `internalRepository` and then use this repository as the argument to the [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) constructor. - -## Example - -import {SavedObjectsClient, CoreSetup} from 'src/core/server'; - -export class Plugin() { setup: (core: CoreSetup) => { core.savedObjects.setClientFactory(({request: KibanaRequest}) => { return new SavedObjectsClient(core.savedObjects.scopedRepository(request)); }) } } - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [addClientWrapper](./kibana-plugin-server.savedobjectsservicesetup.addclientwrapper.md) | (priority: number, id: string, factory: SavedObjectsClientWrapperFactory<KibanaRequest>) => void | Add a client wrapper with the given priority. | -| [createInternalRepository](./kibana-plugin-server.savedobjectsservicesetup.createinternalrepository.md) | (extraTypes?: string[]) => ISavedObjectsRepository | Creates a [Saved Objects repository](./kibana-plugin-server.isavedobjectsrepository.md) that uses the internal Kibana user for authenticating with Elasticsearch. | -| [createScopedRepository](./kibana-plugin-server.savedobjectsservicesetup.createscopedrepository.md) | (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository | Creates a [Saved Objects repository](./kibana-plugin-server.isavedobjectsrepository.md) that uses the credentials from the passed in request to authenticate with Elasticsearch. | -| [setClientFactory](./kibana-plugin-server.savedobjectsservicesetup.setclientfactory.md) | (customClientFactory: SavedObjectsClientFactory<KibanaRequest>) => void | Set a default factory for creating Saved Objects clients. Only one client factory can be set, subsequent calls to this method will fail. | - + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) + +## SavedObjectsServiceSetup interface + +Saved Objects is Kibana's data persistence mechanism allowing plugins to use Elasticsearch for storing and querying state. The SavedObjectsServiceSetup API exposes methods for creating and registering Saved Object client wrappers. + +Signature: + +```typescript +export interface SavedObjectsServiceSetup +``` + +## Remarks + +Note: The Saved Object setup API's should only be used for creating and registering client wrappers. Constructing a Saved Objects client or repository for use within your own plugin won't have any of the registered wrappers applied and is considered an anti-pattern. Use the Saved Objects client from the [SavedObjectsServiceStart\#getScopedClient](./kibana-plugin-server.savedobjectsservicestart.md) method or the [route handler context](./kibana-plugin-server.requesthandlercontext.md) instead. + +When plugins access the Saved Objects client, a new client is created using the factory provided to `setClientFactory` and wrapped by all wrappers registered through `addClientWrapper`. To create a factory or wrapper, plugins will have to construct a Saved Objects client. First create a repository by calling `scopedRepository` or `internalRepository` and then use this repository as the argument to the [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) constructor. + +## Example + +import { SavedObjectsClient, CoreSetup } from 'src/core/server'; + +export class Plugin() { setup: (core: CoreSetup) => { core.savedObjects.setClientFactory(({ request: KibanaRequest }) => { return new SavedObjectsClient(core.savedObjects.scopedRepository(request)); }) } } + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [addClientWrapper](./kibana-plugin-server.savedobjectsservicesetup.addclientwrapper.md) | (priority: number, id: string, factory: SavedObjectsClientWrapperFactory) => void | Add a [client wrapper factory](./kibana-plugin-server.savedobjectsclientwrapperfactory.md) with the given priority. | +| [setClientFactoryProvider](./kibana-plugin-server.savedobjectsservicesetup.setclientfactoryprovider.md) | (clientFactoryProvider: SavedObjectsClientFactoryProvider) => void | Set the default [factory provider](./kibana-plugin-server.savedobjectsclientfactoryprovider.md) for creating Saved Objects clients. Only one provider can be set, subsequent calls to this method will fail. | + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.setclientfactory.md b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.setclientfactory.md deleted file mode 100644 index 544e0b9d5fa73..0000000000000 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.setclientfactory.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) > [setClientFactory](./kibana-plugin-server.savedobjectsservicesetup.setclientfactory.md) - -## SavedObjectsServiceSetup.setClientFactory property - -Set a default factory for creating Saved Objects clients. Only one client factory can be set, subsequent calls to this method will fail. - -Signature: - -```typescript -setClientFactory: (customClientFactory: SavedObjectsClientFactory) => void; -``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.setclientfactoryprovider.md b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.setclientfactoryprovider.md new file mode 100644 index 0000000000000..ed11255048f19 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.setclientfactoryprovider.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) > [setClientFactoryProvider](./kibana-plugin-server.savedobjectsservicesetup.setclientfactoryprovider.md) + +## SavedObjectsServiceSetup.setClientFactoryProvider property + +Set the default [factory provider](./kibana-plugin-server.savedobjectsclientfactoryprovider.md) for creating Saved Objects clients. Only one provider can be set, subsequent calls to this method will fail. + +Signature: + +```typescript +setClientFactoryProvider: (clientFactoryProvider: SavedObjectsClientFactoryProvider) => void; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.createinternalrepository.md b/docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.createinternalrepository.md new file mode 100644 index 0000000000000..d639a8bc66b7e --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.createinternalrepository.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsServiceStart](./kibana-plugin-server.savedobjectsservicestart.md) > [createInternalRepository](./kibana-plugin-server.savedobjectsservicestart.createinternalrepository.md) + +## SavedObjectsServiceStart.createInternalRepository property + +Creates a [Saved Objects repository](./kibana-plugin-server.isavedobjectsrepository.md) that uses the internal Kibana user for authenticating with Elasticsearch. + +Signature: + +```typescript +createInternalRepository: (extraTypes?: string[]) => ISavedObjectsRepository; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.createscopedrepository.md b/docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.createscopedrepository.md new file mode 100644 index 0000000000000..7683a9e46c51d --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.createscopedrepository.md @@ -0,0 +1,18 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsServiceStart](./kibana-plugin-server.savedobjectsservicestart.md) > [createScopedRepository](./kibana-plugin-server.savedobjectsservicestart.createscopedrepository.md) + +## SavedObjectsServiceStart.createScopedRepository property + +Creates a [Saved Objects repository](./kibana-plugin-server.isavedobjectsrepository.md) that uses the credentials from the passed in request to authenticate with Elasticsearch. + +Signature: + +```typescript +createScopedRepository: (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository; +``` + +## Remarks + +Prefer using `getScopedClient`. This should only be used when using methods not exposed on [SavedObjectsClientContract](./kibana-plugin-server.savedobjectsclientcontract.md) + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.md b/docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.md index 5a869b3b6c1cb..cf2b4f57a7461 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.md @@ -1,20 +1,22 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsServiceStart](./kibana-plugin-server.savedobjectsservicestart.md) - -## SavedObjectsServiceStart interface - -Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing and querying state. The SavedObjectsServiceStart API provides a scoped Saved Objects client for interacting with Saved Objects. - -Signature: - -```typescript -export interface SavedObjectsServiceStart -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [getScopedClient](./kibana-plugin-server.savedobjectsservicestart.getscopedclient.md) | (req: KibanaRequest, options?: SavedObjectsClientProviderOptions) => SavedObjectsClientContract | Creates a [Saved Objects client](./kibana-plugin-server.savedobjectsclientcontract.md) that uses the credentials from the passed in request to authenticate with Elasticsearch. If other plugins have registered Saved Objects client wrappers, these will be applied to extend the functionality of the client.A client that is already scoped to the incoming request is also exposed from the route handler context see [RequestHandlerContext](./kibana-plugin-server.requesthandlercontext.md). | - + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsServiceStart](./kibana-plugin-server.savedobjectsservicestart.md) + +## SavedObjectsServiceStart interface + +Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing and querying state. The SavedObjectsServiceStart API provides a scoped Saved Objects client for interacting with Saved Objects. + +Signature: + +```typescript +export interface SavedObjectsServiceStart +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [createInternalRepository](./kibana-plugin-server.savedobjectsservicestart.createinternalrepository.md) | (extraTypes?: string[]) => ISavedObjectsRepository | Creates a [Saved Objects repository](./kibana-plugin-server.isavedobjectsrepository.md) that uses the internal Kibana user for authenticating with Elasticsearch. | +| [createScopedRepository](./kibana-plugin-server.savedobjectsservicestart.createscopedrepository.md) | (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository | Creates a [Saved Objects repository](./kibana-plugin-server.isavedobjectsrepository.md) that uses the credentials from the passed in request to authenticate with Elasticsearch. | +| [getScopedClient](./kibana-plugin-server.savedobjectsservicestart.getscopedclient.md) | (req: KibanaRequest, options?: SavedObjectsClientProviderOptions) => SavedObjectsClientContract | Creates a [Saved Objects client](./kibana-plugin-server.savedobjectsclientcontract.md) that uses the credentials from the passed in request to authenticate with Elasticsearch. If other plugins have registered Saved Objects client wrappers, these will be applied to extend the functionality of the client.A client that is already scoped to the incoming request is also exposed from the route handler context see [RequestHandlerContext](./kibana-plugin-server.requesthandlercontext.md). | + diff --git a/docs/images/management-index-templates-mappings.png b/docs/images/management-index-templates-mappings.png new file mode 100755 index 0000000000000..62321fc0e4666 Binary files /dev/null and b/docs/images/management-index-templates-mappings.png differ diff --git a/docs/management/managing-indices.asciidoc b/docs/management/managing-indices.asciidoc index 4c7f6c2aee6e6..7a3480c860b16 100644 --- a/docs/management/managing-indices.asciidoc +++ b/docs/management/managing-indices.asciidoc @@ -5,43 +5,43 @@ *Index Management* enables you to view index settings, mappings, and statistics and perform index-level operations. These include refreshing, flushing, clearing the cache, force merging segments, -freezing indices, and more. Practicing good index management helps ensure +freezing indices, and more. Practicing good index management helps ensure that your data is stored in the most cost-effective way possible. -*Index Management* also helps you create index templates. A template reduces -the amount of bookkeeping when working with indices. Instead of manually -setting up your indices, you can create them automatically from a template, +*Index Management* also helps you create index templates. A template reduces +the amount of bookkeeping when working with indices. Instead of manually +setting up your indices, you can create them automatically from a template, ensuring that your settings, mappings, and aliases are consistently defined. -To manage your indices, go to *Management > {es} > Index Management*. +To manage your indices, go to *Management > {es} > Index Management*. [role="screenshot"] image::images/management_index_labels.png[Index Management UI] If security is enabled, -you must have the `monitor` cluster privilege and the `view_index_metadata` -and `manage` index privileges to view the data. +you must have the `monitor` cluster privilege and the `view_index_metadata` +and `manage` index privileges to view the data. For index templates, you must have the `manage_index_templates` cluster privilege. See {ref}/security-privileges.html[Security privileges] for more information. -Before using this feature, you should be familiar with index management +Before using this feature, you should be familiar with index management operations. Refer to the {ref}/indices.html[index management APIs] and the {ref}/indices-templates.html[index template APIs]. [float] === View and edit indices -When you open *Index Management*, you’re presented an overview of your configured indices. -Badges indicate if an index is {ref}/frozen-indices.html[frozen], -a {ref}/ccr-put-follow.html[follower index], -or a {ref}/rollup-get-rollup-index-caps.html[rollup index]. +When you open *Index Management*, you’re presented an overview of your configured indices. +Badges indicate if an index is {ref}/frozen-indices.html[frozen], +a {ref}/ccr-put-follow.html[follower index], +or a {ref}/rollup-get-rollup-index-caps.html[rollup index]. -Clicking a badge narrows the list to only indices of that type. +Clicking a badge narrows the list to only indices of that type. You can also filter your indices using the search bar. -You can drill down into each index to investigate the index -{ref}/index-modules.html#index-modules-settings[settings], {ref}/mapping.html[mapping], and statistics. +You can drill down into each index to investigate the index +{ref}/index-modules.html#index-modules-settings[settings], {ref}/mapping.html[mapping], and statistics. From this view, you can also edit the index settings. [role="screenshot"] @@ -50,35 +50,35 @@ image::images/management_index_details.png[Index Management UI] [float] === Perform index-level operations -Use the *Manage* menu to perform index-level operations. This menu -is available in the index details view, or when you select the checkbox of one or more -indices on the overview page. The menu includes the following actions: +Use the *Manage* menu to perform index-level operations. This menu +is available in the index details view, or when you select the checkbox of one or more +indices on the overview page. The menu includes the following actions: -* *Close index*. Blocks the index from read/write operations. -A closed index exists in the cluster, but doesn't consume resources -other than disk space. If you reopen a closed index, it goes through the -normal recovery process. +* *Close index*. Blocks the index from read/write operations. +A closed index exists in the cluster, but doesn't consume resources +other than disk space. If you reopen a closed index, it goes through the +normal recovery process. -* *Force merge index*. Reduces the number of segments in your shard by +* *Force merge index*. Reduces the number of segments in your shard by merging smaller files and clearing deleted ones. Only force merge a read-only index. -* *Refresh index*. Writes the operations in the indexing buffer to the -filesystem cache. This action is automatically performed once per second. Forcing a manual -refresh is useful during testing, but should not be routinely done in +* *Refresh index*. Writes the operations in the indexing buffer to the +filesystem cache. This action is automatically performed once per second. Forcing a manual +refresh is useful during testing, but should not be routinely done in production because it has a performance impact. -* *Clear index cache*. Clears all caches associated with the index. +* *Clear index cache*. Clears all caches associated with the index. -* *Flush index*. Frees memory by syncing the filesystem cache to disk and +* *Flush index*. Frees memory by syncing the filesystem cache to disk and clearing the cache. Once the sync is complete, the internal transaction log is reset. -* *Freeze index*. Makes the index read-only and reduces its memory footprint -by moving shards to disk. Frozen indices remain +* *Freeze index*. Makes the index read-only and reduces its memory footprint +by moving shards to disk. Frozen indices remain searchable, but queries take longer. * *Delete index*. Permanently removes the index and all of its documents. -* *Add lifecycle policy*. Specifies a policy for managing the lifecycle of the +* *Add lifecycle policy*. Specifies a policy for managing the lifecycle of the index. [float] @@ -86,20 +86,20 @@ index. === Manage index templates An index template defines {ref}/index-modules.html#index-modules-settings[settings], -{ref}/mapping.html[mappings], and {ref}/indices-add-alias.html[aliases] -that you can automatically apply when creating a new index. {es} applies a +{ref}/mapping.html[mappings], and {ref}/indices-add-alias.html[aliases] +that you can automatically apply when creating a new index. {es} applies a template to a new index based on an index pattern that matches the index name. -The *Index Templates* view lists your templates and enables you to examine, edit, clone, and -delete them. Changes you make to an index template +The *Index Templates* view lists your templates and enables you to examine, edit, clone, and +delete them. Changes you make to an index template do not affect existing indices. [role="screenshot"] image::images/management-index-templates.png[Index templates] -If you don't have any templates, you can create one using the *Create template* wizard. -Index templates are applied during index creation, -so you must create the +If you don't have any templates, you can create one using the *Create template* wizard. +Index templates are applied during index creation, +so you must create the template before you create the indices. [float] @@ -107,47 +107,38 @@ template before you create the indices. In this example, you’ll create an index template for randomly generated log files. -Open the *Create template* wizard, and enter `logs_template` in the *Name* +Open the *Create template* wizard, and enter `logs_template` in the *Name* field. Set *Index pattern* to `logstash*` so the template matches any index -with that index pattern. The merge order and version are both optional, +with that index pattern. The merge order and version are both optional, and you'll leave them blank in this example. - + [role="screenshot"] image::images/management_index_create_wizard.png[Create wizard] -The second step in the *Create template* wizard allows you to define index settings. -These settings are optional, and this example skips this step. +The second step in the *Create template* wizard allows you to define index settings. +These settings are optional, and this example skips this step. -The logs data set requires a -mapping to label the latitude and longitude pairs as geographic locations -by applying the geo_point type. In the third step of the wizard, define -this mapping as follows: +The logs data set requires a +mapping to label the latitude and longitude pairs as geographic locations +by applying the geo_point type. In the third step of the wizard, define this mapping +under the *Mapped fields* tab as follows: -[source,js] ----------------------------------- -{ - "properties": { - "geo": { - "properties": { - "coordinates": { - "type": "geo_point" - } - } - } - } -} ----------------------------------- +[role="screenshot"] +image::images/management-index-templates-mappings.png[Mapped fields page] + +You can create additional mapping configurations in the *Dynamic templates* and +*Advanced options* tabs. No additional mappings are required for this example. In the fourth step, define an alias named `logstash`. - + [source,js] ---------------------------------- { "logstash": {} } ---------------------------------- - -A summary of the template is in step 5. If everything looks right, click *Create template*. + +A summary of the template is in step 5. If everything looks right, click *Create template*. At this point, you’re ready to use the {es} index API to load the logs data. In the {kib} *Console*, index two documents: @@ -186,5 +177,5 @@ POST /logstash-2019.05.20/_doc } ---------------------------------- -The mappings and alias are configured automatically based on the template. To verify, you +The mappings and alias are configured automatically based on the template. To verify, you can view one of the newly created indices using the {ref}/indices-get-index.html#indices-get-index[index API]. diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index 535ad16978217..6fdb81c780a7d 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -229,23 +229,25 @@ Kibana, the server needs to be CORS-enabled so Kibana can download the file. The following example shows a valid regionmap configuration. + -- - map.regionmap: + map includeElasticMapsService: false - layers: - - name: "Departments of France" - url: "http://my.cors.enabled.server.org/france_departements.geojson" - attribution: "INRAP" - fields: - - name: "department" + regionmap: + layers: + - name: "Departments of France" + url: "http://my.cors.enabled.server.org/france_departements.geojson" + attribution: "INRAP" + fields: + - name: "department" description: "Full department name" - - name: "INSEE" + - name: "INSEE" description: "INSEE numeric identifier" -- -[[regionmap-ES-map]]`map.regionmap.includeElasticMapsService:`:: Turns on or off +[[regionmap-ES-map]]`map.includeElasticMapsService:`:: Turns on or off whether layers from the Elastic Maps Service should be included in the vector layer option list. Supported on Elastic Cloud Enterprise. By turning this off, -only the layers that are configured here will be included. The default is `true`. +only the layers that are configured here will be included. The default is `true`. +This also affects whether tile-service from the Elastic Maps Service will be available. [[regionmap-attribution]]`map.regionmap.layers[].attribution:`:: Optional. References the originating source of the geojson file. Supported on {ece}. diff --git a/docs/user/security/securing-communications/index.asciidoc b/docs/user/security/securing-communications/index.asciidoc index b370c35905bce..3e022315a4d84 100644 --- a/docs/user/security/securing-communications/index.asciidoc +++ b/docs/user/security/securing-communications/index.asciidoc @@ -4,8 +4,9 @@ Encrypting communications ++++ -{kib} supports Transport Layer Security (TLS/SSL) encryption for all forms of data-in-transit. Browsers send traffic to {kib} and {kib} -sends traffic to {es}. These communications are configured separately. +{kib} supports Transport Layer Security (TLS/SSL) encryption for all forms of +data-in-transit. Browsers send traffic to {kib} and {kib} sends traffic to {es}. +These communications are configured separately. [[configuring-tls-browser-kib]] ==== Encrypting traffic between the browser and {kib} @@ -78,7 +79,8 @@ NOTE: To perform this step, you must {ref}/configuring-security.html[enable the {es} {security-features}] or you must have a proxy that provides an HTTPS endpoint for {es}. -. Specify the HTTPS URL in the `elasticsearch.hosts` setting in the {kib} configuration file, `kibana.yml`: +. Specify the HTTPS URL in the `elasticsearch.hosts` setting in the {kib} +configuration file, `kibana.yml`: + -- [source,yaml] @@ -86,7 +88,9 @@ must have a proxy that provides an HTTPS endpoint for {es}. elasticsearch.hosts: ["https://.com:9200"] -------------------------------------------------------------------------------- -Using the HTTPS protocol results in a default `elasticsearch.ssl.verificationMode` option of `full`, which utilizes hostname verification. +Using the HTTPS protocol results in a default +`elasticsearch.ssl.verificationMode` option of `full`, which utilizes hostname +verification. For more information, see <>. -- @@ -95,8 +99,10 @@ For more information, see <>. + -- -If you are using your own CA to sign certificates for {es}, then you need to specify the CA certificate chain in {kib} to properly establish -trust in TLS connections. If your CA certificate chain is contained in a PKCS #12 trust store, specify it like so: +If you are using your own CA to sign certificates for {es}, then you need to +specify the CA certificate chain in {kib} to properly establish trust in TLS +connections. If your CA certificate chain is contained in a PKCS #12 trust store, +specify it like so: [source,yaml] -------------------------------------------------------------------------------- @@ -104,15 +110,21 @@ elasticsearch.ssl.truststore.path: "/path/to/your/truststore.p12" elasticsearch.ssl.truststore.password: "optional decryption password" -------------------------------------------------------------------------------- -Otherwise, if your CA certificate chain is in PEM format, specify each certificate like so: +Otherwise, if your CA certificate chain is in PEM format, specify each +certificate like so: [source,yaml] -------------------------------------------------------------------------------- elasticsearch.ssl.certificateAuthorities: ["/path/to/your/cacert1.pem", "/path/to/your/cacert2.pem"] -------------------------------------------------------------------------------- +TIP: You can use the {ref}/certutil.html[`elasticsearch-certutil http` command] +to generate a PEM format x.509 certificate for the {es} CA. It also provides +detailed configuration details in readme files. + -- -. (Optional) If the Elastic {monitor-features} are enabled, configure {kib} to connect to the {es} monitoring cluster via HTTPS. The steps -are the same as above, but each setting is prefixed by `xpack.monitoring.`. For example, `xpack.monitoring.elasticsearch.hosts`, +. (Optional) If the Elastic {monitor-features} are enabled, configure {kib} to +connect to the {es} monitoring cluster via HTTPS. The steps are the same as +above, but each setting is prefixed by `xpack.monitoring.`. For example, `xpack.monitoring.elasticsearch.hosts`, `xpack.monitoring.elasticsearch.ssl.truststore.path`, etc. diff --git a/docs/user/visualize.asciidoc b/docs/user/visualize.asciidoc index e69d62daf7435..cfd2bac4989c1 100644 --- a/docs/user/visualize.asciidoc +++ b/docs/user/visualize.asciidoc @@ -3,9 +3,9 @@ [partintro] -- -_Visualize_ enables you to create visualizations of the data from your Elasticsearch indices, which you can then add to dashboards for analysis. +_Visualize_ enables you to create visualizations of the data from your {es} indices, which you can then add to dashboards for analysis. -{kib} visualizations are based on Elasticsearch queries. By using a series of {es} {ref}/search-aggregations.html[aggregations] to extract and process your data, you can create charts that show you the trends, spikes, and dips you need to know about. +{kib} visualizations are based on {es} queries. By using a series of {es} {ref}/search-aggregations.html[aggregations] to extract and process your data, you can create charts that show you the trends, spikes, and dips you need to know about. [float] [[create-a-visualization]] @@ -15,42 +15,59 @@ _Visualize_ enables you to create visualizations of the data from your Elasticse . Click *Create new visualization*. . Choose the visualization type: + -* *Basic charts* -<>:: Quickly build several types of basic visualizations by simply dragging and dropping the data fields you want to display. +* Basic charts +[horizontal] +<>:: +Quickly build several types of basic visualizations by simply dragging and dropping the data fields you want to display. + * *<>* [horizontal] Line, area, and bar charts:: Compare different series in X/Y charts. + Pie chart:: Display each source contribution to a total. + Data table:: Flattens aggregations into table format. + Metric:: Display a single number. + Goal and gauge:: Display a number with progress indicators. -Heat maps:: Display shaded cells within a matrix. + Tag cloud:: Display words in a cloud, where the size of the word corresponds to its importance. + * *Time series optimized* [horizontal] <>:: Visualize time series data using pipeline aggregations. + <>:: Compute and combine data from multiple time series data sets. + * *Maps* [horizontal] -<>:: The most powerful way of visualizing map data in {kib}. -<>:: Displays points on a map using a geohash aggregation. -<>:: Merge any structured map data onto a shape. -* *<>* +<>:: The most powerful way of visualizing map data in {kib}. + +<>:: Displays points on a map using a geohash aggregation. + +<>:: Merge any structured map data onto a shape. + +<>:: Display shaded cells within a matrix. + +* *<>* [horizontal] <>:: Provides the ability to add interactive inputs to a Dashboard. + <>:: Display free-form information or instructions. + * *For developers* [horizontal] <>:: Complete control over query and display. . Specify a search query to retrieve the data for your visualization: ** To enter new search criteria, select the <> for the indices that -contain the data you want to visualize. This opens the visualization builder +contain the data you want to visualize. The visualization builder opens with a wildcard query that matches all of the documents in the selected indices. ** To build a visualization from a saved search, click the name of the saved -search you want to use. This opens the visualization builder and loads the +search you want to use. The visualization builder opens and loads the selected query. + NOTE: When you build a visualization from a saved search, any subsequent @@ -58,9 +75,12 @@ modifications to the saved search are automatically reflected in the visualization. To disable automatic updates, you can disconnect a visualization from the saved search. + -- include::{kib-repo-dir}/visualize/visualize_rollup_data.asciidoc[] +include::{kib-repo-dir}/visualize/aggregations.asciidoc[] + include::{kib-repo-dir}/visualize/lens.asciidoc[] include::{kib-repo-dir}/visualize/most-frequent.asciidoc[] @@ -70,6 +90,7 @@ include::{kib-repo-dir}/visualize/timelion.asciidoc[] include::{kib-repo-dir}/visualize/tilemap.asciidoc[] include::{kib-repo-dir}/visualize/regionmap.asciidoc[] +include::{kib-repo-dir}/visualize/heatmap.asciidoc[] include::{kib-repo-dir}/visualize/for-dashboard.asciidoc[] diff --git a/docs/visualize/aggregations.asciidoc b/docs/visualize/aggregations.asciidoc index 36ddb0063dfc3..95aa586e6ba18 100644 --- a/docs/visualize/aggregations.asciidoc +++ b/docs/visualize/aggregations.asciidoc @@ -1,136 +1,112 @@ [[supported-aggregations]] -=== Supported aggregations +== Supported aggregations -The most frequently used visualizations support the following aggregations. +Use the supported aggregations to build your visualizations. [float] [[visualize-metric-aggregations]] -==== Metric aggregations +=== Metric aggregations -The *Count* metric lets you visualize the number of documents in a bucket. -If there are no bucket aggregations defined, this is the total number of documents that match the query. -It is the default selection. - -All other metric aggregations require a field selection, which will read from the indexed values. Alternatively, -you can override field values with a script using the <>. The -other metric aggregations are: +Metric aggregations extract field from documents to generate data values. {ref}/search-aggregations-metrics-avg-aggregation.html[Average]:: The mean value. -{ref}/search-aggregations-metrics-max-aggregation.html[Maximum]:: The highest value. -{ref}/search-aggregations-metrics-percentile-aggregation.html[Median]:: The value that is in the 50% percentile. -{ref}/search-aggregations-metrics-min-aggregation.html[Minimum]:: The lowest value. -{ref}/search-aggregations-metrics-sum-aggregation.html[Sum]:: The total value. -Unique Count:: The {ref}/search-aggregations-metrics-cardinality-aggregation.html[Cardinality] of the field within the bucket. -Supports any data type. +{ref}/search-aggregations-metrics-valuecount-aggregation.html[Count]:: The total number of documents that match the query, which allows you to visualize the number of documents in a bucket. Count is the default value. -Standard Deviation:: Requires a numeric field. Uses the {ref}/search-aggregations-metrics-extendedstats-aggregation.html[_extended stats_] aggregation. +{ref}/search-aggregations-metrics-max-aggregation.html[Max]:: The highest value. + +{ref}/search-aggregations-metrics-percentile-aggregation.html[Median]:: The value that is in the 50% percentile. + +{ref}/search-aggregations-metrics-min-aggregation.html[Min]:: The lowest value. -{ref}/search-aggregations-metrics-top-hits-aggregation.html[Top Hit]:: Returns a sample of individual documents. When the Top Hit aggregation is matched to more than one document, you must choose a technique for combining the values. Techniques include average, minimum, maximum, and sum. +{ref}/search-aggregations-metrics-percentile-rank-aggregation.html[Percentile ranks]:: Returns the percentile rankings for the values in the specified numeric field. Select a numeric field from the drop-down, then specify one or more percentile rank values in the *Values* fields. {ref}/search-aggregations-metrics-percentile-aggregation.html[Percentiles]:: Divides the -values in a numeric field into specified percentile bands. Select a field from the drop-down, then specify one or more ranges in the *Percentiles* fields. Click the *X* to remove a percentile field. Click *+ Add* to add a percentile field. +values in a numeric field into specified percentile bands. Select a field from the drop-down, then specify one or more ranges in the *Percentiles* fields. -{ref}/search-aggregations-metrics-percentile-rank-aggregation.html[Percentile Rank]:: Returns the percentile rankings for the values in the specified numeric field. Select a numeric field from the drop-down, then specify one or more percentile rank values in the *Values* fields. Click the *X* to remove a values field. Click *+Add* to add a values field. +Standard Deviation:: Requires a numeric field. Uses the {ref}/search-aggregations-metrics-extendedstats-aggregation.html[_extended stats_] aggregation. -[float] -[[visualize-sibling-pipeline-aggregations]] -==== Sibling pipeline aggregations +{ref}/search-aggregations-metrics-sum-aggregation.html[Sum]:: The total value. -For each of the sibling pipeline aggregations you have to define a bucket and metric to calculate. This -has the effect of condensing many buckets into one number. +{ref}/search-aggregations-metrics-top-hits-aggregation.html[Top hit]:: Returns a sample of individual documents. When the Top Hit aggregation is matched to more than one document, you must choose a technique for combining the values. Techniques include average, minimum, maximum, and sum. -{ref}/search-aggregations-pipeline-avg-bucket-aggregation.html[Average Bucket]:: Calculates the mean, or average, value of a specified metric in a sibling aggregation. +Unique Count:: The {ref}/search-aggregations-metrics-cardinality-aggregation.html[Cardinality] of the field within the bucket. -{ref}/search-aggregations-pipeline-avg-bucket-aggregation.html[Sum Bucket]:: Calculates the sum of the values of a specified metric in a sibling aggregation. +Alternatively, you can override the field values with a script using JSON input. For example: -{ref}/search-aggregations-pipeline-avg-bucket-aggregation.html[Min Bucket]:: Calculates the minimum value of a specified metric in a sibling aggregation. +[source,shell] +{ "script" : "doc['grade'].value * 1.2" } -{ref}/search-aggregations-pipeline-avg-bucket-aggregation.html[Max Bucket]:: Calculates the maximum value of a specified metric in a sibling aggregation. +The example implements a {es} {ref}/search-aggregations.html[Script Value Source], which replaces +the value in the metric. The options available depend on the aggregation you choose. [float] -[[visualize-bucket-aggregations]] -==== Bucket aggregations - -{ref}/search-aggregations-bucket-datehistogram-aggregation.html[Date Histogram]:: Splits a date field into buckets by interval. If the date field is the primary time field for the index pattern, it will pick an automatic interval for you. You can also choose a minimum time interval, or specify a custom interval frame by selecting *Custom* as the interval and -specifying a number and a time unit in the text field. Custom interval time units are *s* for seconds, *m* for minutes, -*h* for hours, *d* for days, *w* for weeks, and *y* for years. Different units support different levels of precision, -down to one millisecond. Intervals are labeled at the start of the interval, using the date-key returned by Elasticsearch.For example, the tooltip for a monthly interval will show the first day of the month. +[[visualize-parent-pipeline-aggregations]] +=== Parent pipeline aggregations -{ref}/search-aggregations-bucket-histogram-aggregation.html[Histogram]:: Builds from a numeric field. Specify an integer interval for this field. Select the *Show empty buckets* checkbox to include empty intervals in the histogram. +Parent pipeline aggregations assume the bucket aggregations are ordered and are especially useful for time series data. For each parent pipeline aggregation, you must define a bucket aggregation and metric aggregation. -{ref}/search-aggregations-bucket-range-aggregation.html[Range]:: Specify ranges of values for a numeric field. Click *Add Range* to add a set of range endpoints. Click the red *(x)* symbol to remove a range. +You can also nest these aggregations. For example, if you want to produce a third derivative. -{ref}/search-aggregations-bucket-daterange-aggregation.html[Date Range]:: Reports values that are within a range of dates that you specify. You can specify the ranges for the dates using {ref}/common-options.html#date-math[_date math_] expressions. Click *Add Range* to add a set of range endpoints. -Click the red *(x)* symbol to remove a range. +{ref}/search-aggregations-pipeline-bucket-script-aggregation.html[Bucket script]:: Executes a script that performs computations for each bucket that specifies metrics in the parent multi-bucket aggregation. -{ref}/search-aggregations-bucket-iprange-aggregation.html[IPv4 Range]:: Specify ranges of IPv4 addresses. Click *Add Range* to add a set of range endpoints. Click the red *(x)* symbol to remove a range. +{ref}/search-aggregations-pipeline-cumulative-sum-aggregation.html[Cumulative sum]:: Calculates the cumulative sum of a specified metric in a parent histogram. -*Filters*:: Each filter creates a bucket of documents. You can specify a filter as a -<> or <> query string. Click *Add Filter* to -add another filter. Click the image:images/labelbutton.png[Label button icon] *label* button to open the label field, where -you can type in a name to display on the visualization. +{ref}/search-aggregations-pipeline-derivative-aggregation.html[Derivative]:: Calculates the derivative of specific metrics. -{ref}/search-aggregations-bucket-terms-aggregation.html[Terms]:: Specify the top or bottom _n_ elements of a given field to display, ordered by count or a custom metric. +{ref}/search-aggregations-pipeline-movavg-aggregation.html[Moving avg]:: Slides a window across the data and emits the average value of the window. -{ref}/search-aggregations-bucket-significantterms-aggregation.html[Significant Terms]:: Returns interesting or unusual occurrences of terms in a set. +{ref}/search-aggregations-pipeline-serialdiff-aggregation.html[Serial diff]:: Values in a time series are subtracted from itself at different time lags or periods. -Both Terms and Significant Terms support {es} {ref}/search-aggregations-bucket-terms-aggregation.html#_filtering_values_4[exclude and include patterns] which -are available by clicking *Advanced* after selecting a field. +Custom {kib} plugins can <>, which includes support for adding more aggregations. -Kibana only supports filtering string fields with regular expression patterns, it does not support matching with arrays or filtering numeric fields. -Patterns are case sensitive. +[float] +[[visualize-sibling-pipeline-aggregations]] +=== Sibling pipeline aggregations -Example: +Sibling pipeline aggregations condense many buckets into one. For each sibling pipeline aggregation, you must define a bucket aggregations and metric aggregation. -* You want to exclude the metricbeat process from your visualization of top processes: `metricbeat.*` -* You only want to show processes collecting beats: `.*beat` -* You want to exclude two specific values, the string `"empty"` and `"none"`: `empty|none` +{ref}/search-aggregations-pipeline-avg-bucket-aggregation.html[Average bucket]:: Calculates the mean, or average, value of a specified metric in a sibling aggregation. -*Geo aggregations* +{ref}/search-aggregations-pipeline-avg-bucket-aggregation.html[Max Bucket]:: Calculates the maximum value of a specified metric in a sibling aggregation. -These are only supported by the tile map and table visualizations: +{ref}/search-aggregations-pipeline-avg-bucket-aggregation.html[Min Bucket]:: Calculates the minimum value of a specified metric in a sibling aggregation. -{ref}/search-aggregations-bucket-geohashgrid-aggregation.html[Geohash]:: Displays points based on a geohash. +{ref}/search-aggregations-pipeline-avg-bucket-aggregation.html[Sum Bucket]:: Calculates the sum of the values of a specified metric in a sibling aggregation. -{ref}/search-aggregations-bucket-geotilegrid-aggregation.html[Geotile]:: Groups points based on web map tiling. +[float] +[[visualize-bucket-aggregations]] +=== Bucket aggregations +Bucket aggregations sort documents into buckets, depending on the contents of the document. -[float] -[[visualize-parent-pipeline-aggregations]] -==== Parent pipeline aggregations +{ref}/search-aggregations-bucket-datehistogram-aggregation.html[Date histogram]:: Splits a date field into buckets by interval. If the date field is the primary time field for the index pattern, it chooses an automatic interval for you. Intervals are labeled at the start of the interval, using the date-key returned by {es}. For example, the tooltip for a monthly interval displays the first day of the month. -For each of the parent pipeline aggregations you have to define a bucket and metric to calculate. These -metrics expect the buckets to be ordered, and are especially useful for time series data. -You can also nest these aggregations. For example, if you want to produce a third derivative. +{ref}/search-aggregations-bucket-daterange-aggregation.html[Date range]:: Reports values that are within a range of dates that you specify. You can specify the ranges for the dates using {ref}/common-options.html#date-math[_date math_] expressions. -These visualizations support parent pipeline aggregations: +{ref}/search-aggregations-bucket-filter-aggregation.html[Filter]:: Each filter creates a bucket of documents. You can specify a filter as a +<> or <> query string. -* Line, Area and Bar charts -* Data table +{ref}/search-aggregations-bucket-geohashgrid-aggregation.html[Geohash]:: Displays points based on a geohash. Supported by the tile map and data table visualizations. -{ref}/search-aggregations-pipeline-derivative-aggregation.html[Derivative]:: Calculates the derivative of specific metrics. +{ref}/search-aggregations-bucket-geotilegrid-aggregation.html[Geotile]:: Groups points based on web map tiling. Supported by the tile map and data table visualizations. -{ref}/search-aggregations-pipeline-cumulative-sum-aggregation.html[Cumulative Sum]:: Calculates the cumulative sum of a specified metric in a parent histogram. +{ref}/search-aggregations-bucket-histogram-aggregation.html[Histogram]:: Builds from a numeric field. -{ref}/search-aggregations-pipeline-movavg-aggregation.html[Moving Average]:: Slides a window across the data and emits the average value of the window. +{ref}/search-aggregations-bucket-iprange-aggregation.html[IPv4 range]:: Specify ranges of IPv4 addresses. -{ref}/search-aggregations-pipeline-serialdiff-aggregation.html[Serial Diff]:: Values in a time series are subtracted from itself at different time lags or periods. +{ref}/search-aggregations-bucket-range-aggregation.html[Range]:: Specify ranges of values for a numeric field. -Custom {kib} plugins can <>, which includes support for adding more aggregations. +{ref}/search-aggregations-bucket-significantterms-aggregation.html[Significant terms]:: Returns interesting or unusual occurrences of terms in a set. Supports {es} {ref}/search-aggregations-bucket-terms-aggregation.html#_filtering_values_4[exclude and include patterns]. -[float] -[[visualize-advanced-aggregation-options]] -==== Advanced aggregation options +{ref}/search-aggregations-bucket-terms-aggregation.html[Terms]:: Specify the top or bottom _n_ elements of a given field to display, ordered by count or a custom metric. Supports {es} {ref}/search-aggregations-bucket-terms-aggregation.html#_filtering_values_4[exclude and include patterns]. -*JSON Input*:: A text field where you can add specific JSON-formatted properties to merge with the aggregation -definition, as in the following example: +{kib} filters string fields with only regular expression patterns, and does not filter numeric fields or match with arrays. -[source,shell] -{ "script" : "doc['grade'].value * 1.2" } +For example: -This example implements a {es} {ref}/search-aggregations.html[Script Value Source] which replaces -the value in the metric. The availability of these options varies depending on the aggregation -you choose. +* You want to exclude the metricbeat process from your visualization of top processes: `metricbeat.*` +* You only want to show processes collecting beats: `.*beat` +* You want to exclude two specific values, the string `"empty"` and `"none"`: `empty|none` -When multiple bucket aggregations are defined, you can use the drag target on each aggregation to change the priority. For more information about working with aggregation order, see https://www.elastic.co/blog/kibana-aggregation-execution-order-and-you[Kibana, Aggregation Execution Order, and You]. +Patterns are case sensitive. diff --git a/docs/visualize/heatmap.asciidoc b/docs/visualize/heatmap.asciidoc new file mode 100644 index 0000000000000..18c4018213390 --- /dev/null +++ b/docs/visualize/heatmap.asciidoc @@ -0,0 +1,40 @@ +[[heatmap]] +== Heat map + +Heat maps are graphical representations of data where the individual values are represented as colors. + +NOTE: Heat map has been replaced with <>, which offers more functionality and is easier to use. + +[float] +[[heatmap-aggregation]] +=== Supported aggregations + +Heat maps support the following aggregations: + +* <> + +* <> + +* <> + +* <> + +[float] +[[navigate-heatmap]] +=== Change the color ranges + +When only one color displays on the heat map, you might need to change the color ranges. + +To specify the number of color ranges: + +. Click *Options*. + +. Enter the *Number of colors* to display. + +To specify custom ranges: + +. Click *Options*. + +. Select *Use custom ranges*. + +. Enter the ranges to display. diff --git a/docs/visualize/lens.asciidoc b/docs/visualize/lens.asciidoc index e61895a29891b..e3f61565453b5 100644 --- a/docs/visualize/lens.asciidoc +++ b/docs/visualize/lens.asciidoc @@ -4,7 +4,7 @@ beta[] -*Lens* provides you with a simple and fast way to create visualizations from your Elasticsearch data. With Lens, you can: +*Lens* provides you with a simple and fast way to create visualizations from your {es} data. With Lens, you can: * Quickly build visualizations by dragging and dropping data fields. @@ -14,6 +14,20 @@ beta[] * Save your visualization for use in a dashboard. +[float] +[[lens-aggregation]] +=== Supported aggregations + +Lens supports the following aggregations: + +* <> + +* <> + +* <> + +* <> + [float] [[drag-drop]] === Drag and drop diff --git a/docs/visualize/metric.asciidoc b/docs/visualize/metric.asciidoc index 9cbc4a0f7a550..ddcf5fe3b73bd 100644 --- a/docs/visualize/metric.asciidoc +++ b/docs/visualize/metric.asciidoc @@ -2,3 +2,15 @@ === Metric Click the *Options* tab to display the font size slider. + +[float] +[[metric-aggregation]] +==== Supported aggregations + +Metric support the following aggregations: + +* <> + +* <> + +* <> diff --git a/docs/visualize/most-frequent.asciidoc b/docs/visualize/most-frequent.asciidoc index e9085d18185ec..2cb8aa7cb3c1f 100644 --- a/docs/visualize/most-frequent.asciidoc +++ b/docs/visualize/most-frequent.asciidoc @@ -1,30 +1,41 @@ [[most-frequent]] == Most frequently used visualizations -The most frequently used visualizations allow you to plot aggregated data from a <> or <>. They all support a single level of -Elasticsearch {es} {ref}/search-aggregations-metrics.html[metric] aggregations, and one or more -levels of {es} {ref}/search-aggregations-bucket.html[bucket] aggregations. +The most frequently used visualizations allow you to plot aggregated data from a <> or <>. The most frequently used visualizations include: * Line, area, and bar charts -* Pie charts -* Data tables -* Metric, goals, and gauges -* Heat maps -* Tag clouds +* Pie chart +* Data table +* Metric, goal, and gauge +* Tag cloud + +[float] +[[frequently-used-viz-aggregation]] +=== Supported aggregations + +The most frequently used visualizations support the following aggregations: + +* <> + +* <> + +* <> + +* <> [float] === Configure your visualization -You configure visualizations using the default editor, which is broken into metrics and buckets, and includes a default count -metric. Each visualization supports different configurations for what the metrics and buckets -represent. For example, a bar chart allows you to add an X-axis: +You configure visualizations using the default editor. Each visualization supports different configurations of the metrics and buckets. + +For example, a bar chart allows you to add an x-axis: [role="screenshot"] image::images/add-bucket.png["",height=478] -A common configuration for the X-axis is to use a {es} {ref}/search-aggregations-bucket-datehistogram-aggregation.html[date histogram] aggregation: +A common configuration for the x-axis is to use a {es} {ref}/search-aggregations-bucket-datehistogram-aggregation.html[date histogram] aggregation: [role="screenshot"] image::images/visualize-date-histogram.png[] @@ -58,5 +69,3 @@ Each visualization also has its own customization options. Most visualizations a [role="screenshot"] image::images/color-picker.png[An array of color dots that users can select,height=267] - -include::aggregations.asciidoc[] diff --git a/docs/visualize/regionmap.asciidoc b/docs/visualize/regionmap.asciidoc index c39282963ef7b..accabd16e5fcd 100644 --- a/docs/visualize/regionmap.asciidoc +++ b/docs/visualize/regionmap.asciidoc @@ -18,17 +18,18 @@ To create a region map, you configure an inner join that joins the result of an and a reference vector file based on a shared key. [float] -==== Data +[[region-map-aggregation]] +=== Supported aggregations -[float] -===== Metrics +Region maps support the following aggregations: -Select any of the supported _Metric_ or _Sibling Pipeline Aggregations_. +* <> -[float] -===== Buckets +* <> + +* <> -Configure a _Terms_ aggregation. The term is the _key_ that is used to join the results to the vector data on the map. +Use the _key_ term to join the results to the vector data on the map. [float] ==== Options diff --git a/docs/visualize/tilemap.asciidoc b/docs/visualize/tilemap.asciidoc index 2b499d098a956..08cf666345e34 100644 --- a/docs/visualize/tilemap.asciidoc +++ b/docs/visualize/tilemap.asciidoc @@ -17,36 +17,11 @@ in `kibana.yml`. [[coordinate-map-aggregation]] === Supported aggregations -Coordinate maps support the metric and bucket aggregations. +Coordinate maps support the following aggregations: -[float] -===== Metric aggregations - -The following metric aggregations are supported: - -{ref}/search-aggregations-metrics-valuecount-aggregation.html[Count]:: Returns a raw count of -the elements in the index pattern. The default metrics aggregation for a coordinate map is *Count*. - -{ref}/search-aggregations-metrics-avg-aggregation.html[Average]:: Returns the average of a numeric -field. - -{ref}/search-aggregations-metrics-sum-aggregation.html[Sum]:: Returns the total sum of a numeric -field. - -{ref}/search-aggregations-metrics-min-aggregation.html[Min]:: Returns the minimum value of a -numeric field. - -{ref}/search-aggregations-metrics-max-aggregation.html[Max]:: Returns the maximum value of a -numeric field. - -{ref}/search-aggregations-metrics-cardinality-aggregation.html[Unique Count]:: Returns -the number of unique values in a field. - -[float] -[[coordinate-bucket-aggregation]] -===== Bucket aggregation +* <> -Coordinate maps support the {ref}/search-aggregations-bucket-geohashgrid-aggregation.html[_geohash_] bucket aggregation. +* <> When you deselect *Change precision on map zoom*, the *Precision* slider appears. The *Precision* slider determines the granularity of the results displayed on the map. For details on the area specified by each precision level, refer to {ref}/search-aggregations-bucket-geohashgrid-aggregation.html#_cell_dimensions_at_the_equator[geohash grid]. diff --git a/docs/visualize/tsvb.asciidoc b/docs/visualize/tsvb.asciidoc index ff4160d1ac9d2..69d6985acd1e4 100644 --- a/docs/visualize/tsvb.asciidoc +++ b/docs/visualize/tsvb.asciidoc @@ -1,8 +1,8 @@ [[TSVB]] == TSVB -TSVB is a time series data visualizer that allows you to use the full power of the -Elasticsearch aggregation framework. With TSVB, you can combine an infinite +TSVB is a time series data visualizer that allows you to use the full power of the +Elasticsearch aggregation framework. With TSVB, you can combine an infinite number of aggregations to display complex data. NOTE: In Elasticsearch version 7.3.0 and later, the time series data visualizer is now referred to as TSVB instead of Time Series Visual Builder. @@ -43,6 +43,18 @@ Table:: Display data from multiple time series by defining the field group to sh [role="screenshot"] image:images/tsvb-table.png["Table visualization"] +[float] +[[tsvb-aggregation]] +=== Supported aggregations + +TSVB supports the following aggregations: + +* <> + +* <> + +* <> + [float] [[create-tsvb-visualization]] === Create TSVB visualizations @@ -60,7 +72,7 @@ To create a single metric, add multiple data series with multiple aggregations. . Specify the data series labels and colors. .. Select *Data*. -+ ++ If you are using the *Table* visualization, select *Columns*. .. In the *Label* field, enter a name for the data series, which is used on legends and titles. @@ -79,7 +91,7 @@ For series that are grouped by a term, you can specify a mustache variable of `{ .. To add another metric, click *+*. + -When you add more than one metric, the last metric value is displayed, which is indicated by the eye icon. +When you add more than one metric, the last metric value is displayed, which is indicated by the eye icon. . To specify the format and display options, select *Options*. @@ -95,7 +107,7 @@ Change the data that you want to display and choose the style options for the pa . Select *Panel options*. -. Under *Data*, specify how much of the data that you want to display in the visualization. +. Under *Data*, specify how much of the data that you want to display in the visualization. . Under *Style*, specify how you want the visualization to look. @@ -113,7 +125,7 @@ If you are using the Time Series visualization, add annotation data sources. [[tsvb-enter-markdown]] ==== Enter Markdown text -Edit the source for the Markdown visualization. +Edit the source for the Markdown visualization. . Select *Markdown*. diff --git a/package.json b/package.json index 430ab9e1ba77d..9707d3863d295 100644 --- a/package.json +++ b/package.json @@ -92,7 +92,8 @@ "**/react": "^16.12.0", "**/react-test-renderer": "^16.12.0", "**/deepmerge": "^4.2.2", - "**/serialize-javascript": "^2.1.1" + "**/serialize-javascript": "^2.1.1", + "**/fast-deep-equal": "^3.1.1" }, "workspaces": { "packages": [ @@ -347,6 +348,7 @@ "@types/moment-timezone": "^0.5.12", "@types/mustache": "^0.8.31", "@types/node": "^10.12.27", + "@types/numeral": "^0.0.26", "@types/opn": "^5.1.0", "@types/pegjs": "^0.10.1", "@types/pngjs": "^3.3.2", diff --git a/packages/kbn-dev-utils/src/kbn_client/kbn_client_status.ts b/packages/kbn-dev-utils/src/kbn_client/kbn_client_status.ts index 22baf4a330416..3775aecb2db09 100644 --- a/packages/kbn-dev-utils/src/kbn_client/kbn_client_status.ts +++ b/packages/kbn-dev-utils/src/kbn_client/kbn_client_status.ts @@ -32,6 +32,7 @@ interface Status { interface ApiResponseStatus { name: string; uuid: string; + running_from_source?: true; version: { number: string; build_hash: string; @@ -58,6 +59,11 @@ export class KbnClientStatus { }); } + public async isDistributable() { + const status = await this.get(); + return !status.running_from_source; + } + /** * Get the overall/merged state */ diff --git a/packages/kbn-i18n/src/loader.ts b/packages/kbn-i18n/src/loader.ts index 2d68079735c03..21f540f588f46 100644 --- a/packages/kbn-i18n/src/loader.ts +++ b/packages/kbn-i18n/src/loader.ts @@ -17,15 +17,13 @@ * under the License. */ -import { readFile } from 'fs'; +import * as fs from 'fs'; import * as path from 'path'; import { promisify } from 'util'; import { unique } from './core/helper'; import { Translation } from './translation'; -const asyncReadFile = promisify(readFile); - const TRANSLATION_FILE_EXTENSION = '.json'; /** @@ -69,7 +67,8 @@ function getLocaleFromFileName(fullFileName: string) { * @returns */ async function loadFile(pathToFile: string): Promise { - return JSON.parse(await asyncReadFile(pathToFile, 'utf8')); + // doing this at the moment because fs is mocked in a lot of places where this would otherwise fail + return JSON.parse(await promisify(fs.readFile)(pathToFile, 'utf8')); } /** diff --git a/packages/kbn-plugin-helpers/lib/plugin_config.js b/packages/kbn-plugin-helpers/lib/plugin_config.js index 07bbbfedc15da..60baa7fc97660 100644 --- a/packages/kbn-plugin-helpers/lib/plugin_config.js +++ b/packages/kbn-plugin-helpers/lib/plugin_config.js @@ -32,7 +32,7 @@ module.exports = function(root) { 'yarn.lock', 'tsconfig.json', 'package.json', - 'index.js', + 'index.{js,ts}', '{lib,public,server,webpackShims,translations}/**/*', ]; diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index 8bded9d403c21..364b91a30841c 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -43639,6 +43639,10 @@ class KbnClientStatus { path: 'api/status', }); } + async isDistributable() { + const status = await this.get(); + return !status.running_from_source; + } /** * Get the overall/merged state */ diff --git a/renovate.json5 b/renovate.json5 index 5af62d0acef85..6764ed38ba4cf 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -657,6 +657,14 @@ '@types/nodemailer', ], }, + { + groupSlug: 'numeral', + groupName: 'numeral related packages', + packageNames: [ + 'numeral', + '@types/numeral', + ], + }, { groupSlug: 'object-hash', groupName: 'object-hash related packages', diff --git a/src/core/MIGRATION.md b/src/core/MIGRATION.md index f51afd35586bd..087888922ac9b 100644 --- a/src/core/MIGRATION.md +++ b/src/core/MIGRATION.md @@ -1193,10 +1193,11 @@ In server code, `core` can be accessed from either `server.newPlatform` or `kbnS | ----------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | | `server.config()` | [`initializerContext.config.create()`](/docs/development/core/server/kibana-plugin-server.plugininitializercontext.config.md) | Must also define schema. See _[how to configure plugin](#configure-plugin)_ | | `server.route` | [`core.http.createRouter`](/docs/development/core/server/kibana-plugin-server.httpservicesetup.createrouter.md) | [Examples](./MIGRATION_EXAMPLES.md#route-registration) | +| `server.renderApp()` / `server.renderAppWithDefaultConfig()` | [`context.rendering.render()`](/docs/development/core/server/kibana-plugin-server.iscopedrenderingclient.render.md) | [Examples](./MIGRATION_EXAMPLES.md#render-html-content) | | `request.getBasePath()` | [`core.http.basePath.get`](/docs/development/core/server/kibana-plugin-server.httpservicesetup.basepath.md) | | | `server.plugins.elasticsearch.getCluster('data')` | [`context.elasticsearch.dataClient`](/docs/development/core/server/kibana-plugin-server.iscopedclusterclient.md) | | | `server.plugins.elasticsearch.getCluster('admin')` | [`context.elasticsearch.adminClient`](/docs/development/core/server/kibana-plugin-server.iscopedclusterclient.md) | | -| `server.plugins.elasticsearch.createCluster(...)` | [`core.elasticsearch.createClient`](/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.createclient.md) | | +| `server.plugins.elasticsearch.createCluster(...)` | [`core.elasticsearch.createClient`](/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.createclient.md) | | | `server.savedObjects.setScopedSavedObjectsClientFactory` | [`core.savedObjects.setClientFactory`](/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.setclientfactory.md) | | | `server.savedObjects.addScopedSavedObjectsClientWrapperFactory` | [`core.savedObjects.addClientWrapper`](/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.addclientwrapper.md) | | | `server.savedObjects.getSavedObjectsRepository` | [`core.savedObjects.createInternalRepository`](/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createinternalrepository.md) [`core.savedObjects.createScopedRepository`](/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createscopedrepository.md) | | diff --git a/src/core/MIGRATION_EXAMPLES.md b/src/core/MIGRATION_EXAMPLES.md index d7964a53358ef..568980f50117d 100644 --- a/src/core/MIGRATION_EXAMPLES.md +++ b/src/core/MIGRATION_EXAMPLES.md @@ -16,7 +16,9 @@ APIs to their New Platform equivalents. - [Accessing Services](#accessing-services) - [Chrome](#chrome) - [Updating an application navlink](#updating-application-navlink) - + - [Chromeless Applications](#chromeless-applications) + - [Render HTML Content](#render-html-content) + ## Configuration ### Declaring config schema @@ -386,7 +388,7 @@ class Plugin { }), } }, - router.wrapErrors((context, req, res) => { + router.handleLegacyErrors((context, req, res) => { throw Boom.notFound('not there'); // will be converted into proper New Platform error }) ) @@ -518,4 +520,83 @@ export class MyPlugin implements Plugin { }, }); } -``` \ No newline at end of file +``` + +## Chromeless Applications + +In Kibana, a "chromeless" application is one where the primary Kibana UI components +such as header or navigation can be hidden. In the legacy platform these were referred to +as "hidden" applications, and were set via the `hidden` property in a Kibana plugin. +Chromeless applications are also not displayed in the left navbar. + +To mark an application as chromeless, specify `chromeless: false` when registering your application +to hide the chrome UI when the application is mounted: + +```ts +application.register({ + id: 'chromeless', + chromeless: true, + async mount(context, params) { + /* ... */ + }, +}); +``` + +If you wish to render your application at a route that does not follow the `/app/${appId}` pattern, +this can be done via the `appRoute` property. Doing this currently requires you to register a server +route where you can return a bootstrapped HTML page for your application bundle. Instructions on +registering this server route is covered in the next section: [Render HTML Content](#render-html-content). + +```ts +application.register({ + id: 'chromeless', + appRoute: '/chromeless', + chromeless: true, + async mount(context, params) { + /* ... */ + }, +}); +``` + +## Render HTML Content + +You can return a blank HTML page bootstrapped with the core application bundle from an HTTP route handler +via the `rendering` context. You may wish to do this if you are rendering a chromeless application with a +custom application route or have other custom rendering needs. + +```ts +router.get( + { path: '/chromeless', validate: false }, + (context, request, response) => { + const { http, rendering } = context.core; + + return response.ok({ + body: await rendering.render(), // generates an HTML document + headers: { + 'content-security-policy': http.csp.header, + }, + }); + } +); +``` + +You can also specify to exclude user data from the bundle metadata. User data +comprises all UI Settings that are *user provided*, then injected into the page. +You may wish to exclude fetching this data if not authorized or to slim the page +size. + +```ts +router.get( + { path: '/', validate: false }, + (context, request, response) => { + const { http, rendering } = context.core; + + return response.ok({ + body: await rendering.render({ includeUserSettings: false }), + headers: { + 'content-security-policy': http.csp.header, + }, + }); + } +); +``` diff --git a/src/core/public/application/types.ts b/src/core/public/application/types.ts index 0d955482d2226..63e542b0127ed 100644 --- a/src/core/public/application/types.ts +++ b/src/core/public/application/types.ts @@ -31,6 +31,7 @@ import { PluginOpaqueId } from '../plugins'; import { IUiSettingsClient } from '../ui_settings'; import { RecursiveReadonly } from '../../utils'; import { SavedObjectsStart } from '../saved_objects'; +import { AppCategory } from '../../types'; /** @public */ export interface AppBase { @@ -44,6 +45,13 @@ export interface AppBase { */ title: string; + /** + * The category definition of the product + * See {@link AppCategory} + * See DEFAULT_APP_CATEGORIES for more reference + */ + category?: AppCategory; + /** * The initial status of the application. * Defaulting to `accessible` diff --git a/src/core/public/chrome/chrome_service.test.ts b/src/core/public/chrome/chrome_service.test.ts index abd04722a49f2..9018b21973634 100644 --- a/src/core/public/chrome/chrome_service.test.ts +++ b/src/core/public/chrome/chrome_service.test.ts @@ -29,6 +29,7 @@ import { notificationServiceMock } from '../notifications/notifications_service. import { docLinksServiceMock } from '../doc_links/doc_links_service.mock'; import { ChromeService } from './chrome_service'; import { App } from '../application'; +import { uiSettingsServiceMock } from '../ui_settings/ui_settings_service.mock'; class FakeApp implements App { public title = `${this.id} App`; @@ -51,6 +52,7 @@ function defaultStartDeps(availableApps?: App[]) { http: httpServiceMock.createStartContract(), injectedMetadata: injectedMetadataServiceMock.createStartContract(), notifications: notificationServiceMock.createStartContract(), + uiSettings: uiSettingsServiceMock.createStartContract(), }; if (availableApps) { diff --git a/src/core/public/chrome/chrome_service.tsx b/src/core/public/chrome/chrome_service.tsx index 09ea1afe35766..6ab9fe158742a 100644 --- a/src/core/public/chrome/chrome_service.tsx +++ b/src/core/public/chrome/chrome_service.tsx @@ -38,7 +38,7 @@ import { LoadingIndicator, HeaderWrapper as Header } from './ui'; import { DocLinksStart } from '../doc_links'; import { ChromeHelpExtensionMenuLink } from './ui/header/header_help_menu'; import { KIBANA_ASK_ELASTIC_LINK } from './constants'; - +import { IUiSettingsClient } from '../ui_settings'; export { ChromeNavControls, ChromeRecentlyAccessed, ChromeDocTitle }; const IS_COLLAPSED_KEY = 'core.chrome.isCollapsed'; @@ -85,6 +85,7 @@ interface StartDeps { http: HttpStart; injectedMetadata: InjectedMetadataStart; notifications: NotificationsStart; + uiSettings: IUiSettingsClient; } /** @internal */ @@ -139,6 +140,7 @@ export class ChromeService { http, injectedMetadata, notifications, + uiSettings, }: StartDeps): Promise { this.initVisibility(application); @@ -173,7 +175,6 @@ export class ChromeService { getHeaderComponent: () => ( -
), diff --git a/src/core/public/chrome/nav_links/nav_link.ts b/src/core/public/chrome/nav_links/nav_link.ts index 3b16c030ddcc9..4d3a1e9ecd199 100644 --- a/src/core/public/chrome/nav_links/nav_link.ts +++ b/src/core/public/chrome/nav_links/nav_link.ts @@ -18,6 +18,7 @@ */ import { pick } from '../../../utils'; +import { AppCategory } from '../../'; /** * @public @@ -33,6 +34,11 @@ export interface ChromeNavLink { */ readonly title: string; + /** + * The category the app lives in + */ + readonly category?: AppCategory; + /** * The base route used to open the root of an application. */ diff --git a/src/core/public/chrome/ui/header/__snapshots__/nav_drawer.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/nav_drawer.test.tsx.snap new file mode 100644 index 0000000000000..0ebc44ba67862 --- /dev/null +++ b/src/core/public/chrome/ui/header/__snapshots__/nav_drawer.test.tsx.snap @@ -0,0 +1,5224 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`NavDrawer Advanced setting set to grouped renders grouped items 1`] = ` + + + + + + + +`; + +exports[`NavDrawer Advanced setting set to grouped renders individual items if there are less than 7 1`] = ` + + + + + + + +`; + +exports[`NavDrawer Advanced setting set to grouped renders individual items if there is only 1 category 1`] = ` + + + + + + + +`; + +exports[`NavDrawer Advanced setting set to individual renders individual items 1`] = ` + + + + + + + +`; diff --git a/src/core/public/chrome/ui/header/header.tsx b/src/core/public/chrome/ui/header/header.tsx index d05a6bb53405c..c3cefd180b16f 100644 --- a/src/core/public/chrome/ui/header/header.tsx +++ b/src/core/public/chrome/ui/header/header.tsx @@ -17,141 +17,40 @@ * under the License. */ -import Url from 'url'; - -import React, { Component, createRef } from 'react'; -import * as Rx from 'rxjs'; - import { - // TODO: add type annotations EuiHeader, - EuiHeaderLogo, EuiHeaderSection, EuiHeaderSectionItem, EuiHeaderSectionItemButton, - EuiHorizontalRule, EuiIcon, - EuiImage, // @ts-ignore EuiNavDrawer, // @ts-ignore - EuiNavDrawerGroup, - // @ts-ignore EuiShowFor, } from '@elastic/eui'; - import { i18n } from '@kbn/i18n'; -import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; - -import { HeaderBadge } from './header_badge'; -import { HeaderBreadcrumbs } from './header_breadcrumbs'; -import { HeaderHelpMenu } from './header_help_menu'; -import { HeaderNavControls } from './header_nav_controls'; - +import React, { Component, createRef } from 'react'; +import * as Rx from 'rxjs'; import { ChromeBadge, ChromeBreadcrumb, + ChromeNavControl, ChromeNavLink, ChromeRecentlyAccessedHistoryItem, - ChromeNavControl, } from '../..'; +import { InternalApplicationStart } from '../../../application/types'; import { HttpStart } from '../../../http'; import { ChromeHelpExtension } from '../../chrome_service'; -import { InternalApplicationStart } from '../../../application/types'; - -// Providing a buffer between the limit and the cut off index -// protects from truncating just the last couple (6) characters -const TRUNCATE_LIMIT: number = 64; -const TRUNCATE_AT: number = 58; - -/** - * - * @param {string} url - a relative or root relative url. If a relative path is given then the - * absolute url returned will depend on the current page where this function is called from. For example - * if you are on page "http://www.mysite.com/shopping/kids" and you pass this function "adults", you would get - * back "http://www.mysite.com/shopping/adults". If you passed this function a root relative path, or one that - * starts with a "/", for example "/account/cart", you would get back "http://www.mysite.com/account/cart". - * @return {string} the relative url transformed into an absolute url - */ -function relativeToAbsolute(url: string) { - // convert all link urls to absolute urls - const a = document.createElement('a'); - a.setAttribute('href', url); - return a.href; -} - -function extendRecentlyAccessedHistoryItem( - navLinks: ChromeNavLink[], - recentlyAccessed: ChromeRecentlyAccessedHistoryItem, - basePath: HttpStart['basePath'] -) { - const href = relativeToAbsolute(basePath.prepend(recentlyAccessed.link)); - const navLink = navLinks.find(nl => href.startsWith(nl.subUrlBase || nl.baseUrl)); - - let titleAndAriaLabel = recentlyAccessed.label; - if (navLink) { - const objectTypeForAriaAppendix = navLink.title; - titleAndAriaLabel = i18n.translate('core.ui.recentLinks.linkItem.screenReaderLabel', { - defaultMessage: '{recentlyAccessedItemLinklabel}, type: {pageType}', - values: { - recentlyAccessedItemLinklabel: recentlyAccessed.label, - pageType: objectTypeForAriaAppendix, - }, - }); - } - - return { - ...recentlyAccessed, - href, - euiIconType: navLink ? navLink.euiIconType : undefined, - title: titleAndAriaLabel, - }; -} - -function extendNavLink(navLink: ChromeNavLink) { - if (navLink.legacy) { - return { - ...navLink, - href: navLink.url && !navLink.active ? navLink.url : navLink.baseUrl, - }; - } - - return { - ...navLink, - href: navLink.baseUrl, - }; -} - -function isModifiedEvent(event: MouseEvent) { - return !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey); -} - -function findClosestAnchor(element: HTMLElement): HTMLAnchorElement | void { - let current = element; - while (current) { - if (current.tagName === 'A') { - return current as HTMLAnchorElement; - } - - if (!current.parentElement || current.parentElement === document.body) { - return undefined; - } - - current = current.parentElement; - } -} - -function truncateRecentItemLabel(label: string): string { - if (label.length > TRUNCATE_LIMIT) { - label = `${label.substring(0, TRUNCATE_AT)}…`; - } - - return label; -} - -export type HeaderProps = Pick>; +import { HeaderBadge } from './header_badge'; +import { NavSetting, OnIsLockedUpdate } from './'; +import { HeaderBreadcrumbs } from './header_breadcrumbs'; +import { HeaderHelpMenu } from './header_help_menu'; +import { HeaderNavControls } from './header_nav_controls'; +import { euiNavLink } from './nav_link'; +import { HeaderLogo } from './header_logo'; +import { NavDrawer } from './nav_drawer'; -interface Props { +export interface HeaderProps { kibanaVersion: string; application: InternalApplicationStart; appTitle$: Rx.Observable; @@ -168,28 +67,29 @@ interface Props { legacyMode: boolean; navControlsLeft$: Rx.Observable; navControlsRight$: Rx.Observable; - intl: InjectedIntl; basePath: HttpStart['basePath']; isLocked?: boolean; - onIsLockedUpdate?: (isLocked: boolean) => void; + navSetting$: Rx.Observable; + onIsLockedUpdate?: OnIsLockedUpdate; } interface State { appTitle: string; - currentAppId?: string; isVisible: boolean; - navLinks: ReadonlyArray>; - recentlyAccessed: ReadonlyArray>; + navLinks: ChromeNavLink[]; + recentlyAccessed: ChromeRecentlyAccessedHistoryItem[]; forceNavigation: boolean; navControlsLeft: readonly ChromeNavControl[]; navControlsRight: readonly ChromeNavControl[]; + navSetting: NavSetting; + currentAppId: string | undefined; } -class HeaderUI extends Component { +export class Header extends Component { private subscription?: Rx.Subscription; private navDrawerRef = createRef(); - constructor(props: Props) { + constructor(props: HeaderProps) { super(props); this.state = { @@ -200,6 +100,8 @@ class HeaderUI extends Component { forceNavigation: false, navControlsLeft: [], navControlsRight: [], + navSetting: 'grouped', + currentAppId: '', }; } @@ -214,7 +116,8 @@ class HeaderUI extends Component { Rx.combineLatest( this.props.navControlsLeft$, this.props.navControlsRight$, - this.props.application.currentAppId$ + this.props.application.currentAppId$, + this.props.navSetting$ ) ).subscribe({ next: ([ @@ -223,18 +126,17 @@ class HeaderUI extends Component { forceNavigation, navLinks, recentlyAccessed, - [navControlsLeft, navControlsRight, currentAppId], + [navControlsLeft, navControlsRight, currentAppId, navSetting], ]) => { this.setState({ appTitle, isVisible, forceNavigation, - navLinks: navLinks.map(extendNavLink), - recentlyAccessed: recentlyAccessed.map(ra => - extendRecentlyAccessedHistoryItem(navLinks, ra, this.props.basePath) - ), + navLinks: navLinks.filter(navLink => !navLink.hidden), + recentlyAccessed, navControlsLeft, navControlsRight, + navSetting, currentAppId, }); }, @@ -247,26 +149,12 @@ class HeaderUI extends Component { } } - public renderLogo() { - const { homeHref, intl } = this.props; - return ( - - ); - } - public renderMenuTrigger() { return ( this.navDrawerRef.current.toggleOpen()} > @@ -275,98 +163,29 @@ class HeaderUI extends Component { } public render() { + const { appTitle, isVisible, navControlsLeft, navControlsRight } = this.state; const { - application, badge$, - basePath, breadcrumbs$, helpExtension$, helpSupportUrl$, - intl, - isLocked, kibanaDocLink, kibanaVersion, - onIsLockedUpdate, - legacyMode, } = this.props; - const { - appTitle, - currentAppId, - isVisible, - navControlsLeft, - navControlsRight, - navLinks, - recentlyAccessed, - } = this.state; + const navLinks = this.state.navLinks.map(link => + euiNavLink( + link, + this.props.legacyMode, + this.state.currentAppId, + this.props.basePath, + this.props.application.navigateToApp + ) + ); if (!isVisible) { return null; } - const navLinksArray = navLinks - .filter(navLink => !navLink.hidden) - .map(navLink => ({ - key: navLink.id, - label: navLink.tooltip ?? navLink.title, - - // Use href and onClick to support "open in new tab" and SPA navigation in the same link - href: navLink.href, - onClick: (event: MouseEvent) => { - if ( - !legacyMode && // ignore when in legacy mode - !navLink.legacy && // ignore links to legacy apps - !event.defaultPrevented && // onClick prevented default - event.button === 0 && // ignore everything but left clicks - !isModifiedEvent(event) // ignore clicks with modifier keys - ) { - event.preventDefault(); - application.navigateToApp(navLink.id); - } - }, - - // Legacy apps use `active` property, NP apps should match the current app - isActive: navLink.active || currentAppId === navLink.id, - isDisabled: navLink.disabled, - - iconType: navLink.euiIconType, - icon: - !navLink.euiIconType && navLink.icon ? ( - - ) : ( - undefined - ), - 'data-test-subj': 'navDrawerAppsMenuLink', - })); - - const recentLinksArray = [ - { - label: intl.formatMessage({ - id: 'core.ui.chrome.sideGlobalNav.viewRecentItemsLabel', - defaultMessage: 'Recently viewed', - }), - iconType: 'clock', - isDisabled: recentlyAccessed.length > 0 ? false : true, - flyoutMenu: { - title: intl.formatMessage({ - id: 'core.ui.chrome.sideGlobalNav.viewRecentItemsFlyoutTitle', - defaultMessage: 'Recent items', - }), - listItems: recentlyAccessed.map(item => ({ - label: truncateRecentItemLabel(item.label), - title: item.title, - 'aria-label': item.title, - href: item.href, - iconType: item.euiIconType, - })), - }, - }, - ]; - return (
@@ -375,7 +194,13 @@ class HeaderUI extends Component { {this.renderMenuTrigger()} - {this.renderLogo()} + + + @@ -399,75 +224,17 @@ class HeaderUI extends Component { - - - - - - + />
); } - - private onNavClick = (event: React.MouseEvent) => { - const anchor = findClosestAnchor((event as any).nativeEvent.target); - if (!anchor) { - return; - } - - const navLink = this.state.navLinks.find(item => item.href === anchor.href); - if (navLink && navLink.disabled) { - event.preventDefault(); - return; - } - - if ( - !this.state.forceNavigation || - event.isDefaultPrevented() || - event.altKey || - event.metaKey || - event.ctrlKey - ) { - return; - } - - const toParsed = Url.parse(anchor.href); - const fromParsed = Url.parse(document.location.href); - const sameProto = toParsed.protocol === fromParsed.protocol; - const sameHost = toParsed.host === fromParsed.host; - const samePath = toParsed.path === fromParsed.path; - - if (sameProto && sameHost && samePath) { - if (toParsed.hash) { - document.location.reload(); - } - - // event.preventDefault() keeps the browser from seeing the new url as an update - // and even setting window.location does not mimic that behavior, so instead - // we use stopPropagation() to prevent angular from seeing the click and - // starting a digest cycle/attempting to handle it in the router. - event.stopPropagation(); - } - }; } - -export const Header = injectI18n(HeaderUI); diff --git a/src/core/public/chrome/ui/header/header_logo.tsx b/src/core/public/chrome/ui/header/header_logo.tsx new file mode 100644 index 0000000000000..793b8646dabf7 --- /dev/null +++ b/src/core/public/chrome/ui/header/header_logo.tsx @@ -0,0 +1,104 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import Url from 'url'; +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiHeaderLogo } from '@elastic/eui'; +import { NavLink } from './nav_link'; + +function findClosestAnchor(element: HTMLElement): HTMLAnchorElement | void { + let current = element; + while (current) { + if (current.tagName === 'A') { + return current as HTMLAnchorElement; + } + + if (!current.parentElement || current.parentElement === document.body) { + return undefined; + } + + current = current.parentElement; + } +} + +function onClick( + event: React.MouseEvent, + forceNavigation: boolean, + navLinks: NavLink[] +) { + const anchor = findClosestAnchor((event as any).nativeEvent.target); + if (!anchor) { + return; + } + + const navLink = navLinks.find(item => item.href === anchor.href); + if (navLink && navLink.isDisabled) { + event.preventDefault(); + return; + } + + if ( + !forceNavigation || + event.isDefaultPrevented() || + event.altKey || + event.metaKey || + event.ctrlKey + ) { + return; + } + + const toParsed = Url.parse(anchor.href); + const fromParsed = Url.parse(document.location.href); + const sameProto = toParsed.protocol === fromParsed.protocol; + const sameHost = toParsed.host === fromParsed.host; + const samePath = toParsed.path === fromParsed.path; + + if (sameProto && sameHost && samePath) { + if (toParsed.hash) { + document.location.reload(); + } + + // event.preventDefault() keeps the browser from seeing the new url as an update + // and even setting window.location does not mimic that behavior, so instead + // we use stopPropagation() to prevent angular from seeing the click and + // starting a digest cycle/attempting to handle it in the router. + event.stopPropagation(); + } +} + +interface Props { + href: string; + navLinks: NavLink[]; + forceNavigation: boolean; +} + +export function HeaderLogo({ href, forceNavigation, navLinks }: Props) { + return ( + onClick(e, forceNavigation, navLinks)} + href={href} + aria-label={i18n.translate('core.ui.chrome.headerGlobalNav.goHomePageIconAriaLabel', { + defaultMessage: 'Go to home page', + })} + /> + ); +} diff --git a/src/core/public/chrome/ui/header/index.ts b/src/core/public/chrome/ui/header/index.ts index 6d59fc6d9433b..b396c94b3f2a3 100644 --- a/src/core/public/chrome/ui/header/index.ts +++ b/src/core/public/chrome/ui/header/index.ts @@ -26,3 +26,5 @@ export { ChromeHelpExtensionMenuDocumentationLink, ChromeHelpExtensionMenuGitHubLink, } from './header_help_menu'; +export type NavSetting = 'grouped' | 'individual'; +export type OnIsLockedUpdate = (isLocked: boolean) => void; diff --git a/src/core/public/chrome/ui/header/nav_drawer.test.tsx b/src/core/public/chrome/ui/header/nav_drawer.test.tsx new file mode 100644 index 0000000000000..7272935b93a52 --- /dev/null +++ b/src/core/public/chrome/ui/header/nav_drawer.test.tsx @@ -0,0 +1,103 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { cloneDeep } from 'lodash'; +import { mount } from 'enzyme'; +import React from 'react'; +import { NavSetting } from './'; +import { ChromeNavLink } from '../../../'; +import { AppCategory } from 'src/core/types'; +import { DEFAULT_APP_CATEGORIES } from '../../../../utils'; +import { NavDrawer } from './nav_drawer'; +import { euiNavLink } from './nav_link'; + +const { analyze, management, observability, security } = DEFAULT_APP_CATEGORIES; +const mockIBasePath = { + get: () => '/app', + prepend: () => '/app', + remove: () => '/app', +}; + +const getMockProps = (chromeNavLinks: ChromeNavLink[], navSetting: NavSetting = 'grouped') => ({ + navSetting, + navLinks: chromeNavLinks.map(link => + euiNavLink(link, true, undefined, mockIBasePath, () => Promise.resolve()) + ), + chromeNavLinks, + recentlyAccessedItems: [], + basePath: mockIBasePath, +}); + +const makeLink = (id: string, order: number, category?: AppCategory) => ({ + id, + category, + order, + title: id, + baseUrl: `http://localhost:5601/app/${id}`, + legacy: true, +}); + +const getMockChromeNavLink = () => + cloneDeep([ + makeLink('discover', 100, analyze), + makeLink('siem', 500, security), + makeLink('metrics', 600, observability), + makeLink('monitoring', 800, management), + makeLink('visualize', 200, analyze), + makeLink('dashboard', 300, analyze), + makeLink('canvas', 400, { label: 'customCategory' }), + makeLink('logs', 700, observability), + ]); + +describe('NavDrawer', () => { + describe('Advanced setting set to individual', () => { + it('renders individual items', () => { + const component = mount( + + ); + expect(component).toMatchSnapshot(); + }); + }); + describe('Advanced setting set to grouped', () => { + it('renders individual items if there are less than 7', () => { + const links = getMockChromeNavLink().slice(0, 5); + const component = mount(); + expect(component).toMatchSnapshot(); + }); + it('renders individual items if there is only 1 category', () => { + // management doesn't count as a category + const navLinks = [ + makeLink('discover', 100, analyze), + makeLink('siem', 500, analyze), + makeLink('metrics', 600, analyze), + makeLink('monitoring', 800, analyze), + makeLink('visualize', 200, analyze), + makeLink('dashboard', 300, management), + makeLink('canvas', 400, management), + makeLink('logs', 700, management), + ]; + const component = mount(); + expect(component).toMatchSnapshot(); + }); + it('renders grouped items', () => { + const component = mount(); + expect(component).toMatchSnapshot(); + }); + }); +}); diff --git a/src/core/public/chrome/ui/header/nav_drawer.tsx b/src/core/public/chrome/ui/header/nav_drawer.tsx new file mode 100644 index 0000000000000..dbb68d5dd3901 --- /dev/null +++ b/src/core/public/chrome/ui/header/nav_drawer.tsx @@ -0,0 +1,170 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { groupBy, sortBy } from 'lodash'; +import { i18n } from '@kbn/i18n'; +// @ts-ignore +import { EuiNavDrawer, EuiHorizontalRule, EuiNavDrawerGroup } from '@elastic/eui'; +import { NavSetting, OnIsLockedUpdate } from './'; +import { ChromeNavLink, ChromeRecentlyAccessedHistoryItem } from '../../..'; +import { AppCategory } from '../../../../types'; +import { HttpStart } from '../../../http'; +import { NavLink } from './nav_link'; +import { RecentLinks } from './recent_links'; + +function getAllCategories(allCategorizedLinks: Record) { + const allCategories = {} as Record; + + for (const [key, value] of Object.entries(allCategorizedLinks)) { + allCategories[key] = value[0].category; + } + + return allCategories; +} + +function getOrderedCategories( + mainCategories: Record, + categoryDictionary: ReturnType +) { + return sortBy( + Object.keys(mainCategories), + categoryName => categoryDictionary[categoryName]?.order + ); +} + +export interface Props { + navSetting: NavSetting; + isLocked?: boolean; + onIsLockedUpdate?: OnIsLockedUpdate; + navLinks: NavLink[]; + chromeNavLinks: ChromeNavLink[]; + recentlyAccessedItems: ChromeRecentlyAccessedHistoryItem[]; + basePath: HttpStart['basePath']; +} + +function navDrawerRenderer( + { + navSetting, + isLocked, + onIsLockedUpdate, + navLinks, + chromeNavLinks, + recentlyAccessedItems, + basePath, + }: Props, + ref: React.Ref +) { + const disableGroupedNavSetting = navSetting === 'individual'; + const groupedNavLinks = groupBy(navLinks, link => link?.category?.label); + const { undefined: unknowns, ...allCategorizedLinks } = groupedNavLinks; + const { Management: management, ...mainCategories } = allCategorizedLinks; + const categoryDictionary = getAllCategories(allCategorizedLinks); + const orderedCategories = getOrderedCategories(mainCategories, categoryDictionary); + const showUngroupedNav = + disableGroupedNavSetting || navLinks.length < 7 || Object.keys(mainCategories).length === 1; + + return ( + + {RecentLinks({ + recentlyAccessedItems, + navLinks: chromeNavLinks, + basePath, + })} + + {showUngroupedNav ? ( + + ) : ( + <> + { + const category = categoryDictionary[categoryName]!; + const links = mainCategories[categoryName]; + + if (links.length === 1) { + return { + ...links[0], + label: category.label, + iconType: category.euiIconType || links[0].iconType, + }; + } + + return { + 'data-test-subj': 'navDrawerCategory', + iconType: category.euiIconType, + label: category.label, + flyoutMenu: { + title: category.label, + listItems: sortBy(links, 'order').map(link => { + link['data-test-subj'] = 'navDrawerFlyoutLink'; + return link; + }), + }, + }; + }), + ...sortBy(unknowns, 'order'), + ]} + /> + + { + link['data-test-subj'] = 'navDrawerFlyoutLink'; + return link; + }), + }, + }, + ]} + /> + + )} + + ); +} + +export const NavDrawer = React.forwardRef(navDrawerRenderer); diff --git a/src/core/public/chrome/ui/header/nav_link.tsx b/src/core/public/chrome/ui/header/nav_link.tsx new file mode 100644 index 0000000000000..52b59c53b658c --- /dev/null +++ b/src/core/public/chrome/ui/header/nav_link.tsx @@ -0,0 +1,87 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { EuiImage } from '@elastic/eui'; +import { ChromeNavLink, CoreStart } from '../../../'; +import { HttpStart } from '../../../http'; + +function isModifiedEvent(event: MouseEvent) { + return !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey); +} + +function LinkIcon({ url }: { url: string }) { + return ; +} + +export type NavLink = ReturnType; + +export function euiNavLink( + navLink: ChromeNavLink, + legacyMode: boolean, + currentAppId: string | undefined, + basePath: HttpStart['basePath'], + navigateToApp: CoreStart['application']['navigateToApp'] +) { + const { + legacy, + url, + active, + baseUrl, + id, + title, + disabled, + euiIconType, + icon, + category, + order, + tooltip, + } = navLink; + let href = navLink.baseUrl; + + if (legacy) { + href = url && !active ? url : baseUrl; + } + + return { + category, + key: id, + label: tooltip ?? title, + href, // Use href and onClick to support "open in new tab" and SPA navigation in the same link + onClick(event: MouseEvent) { + if ( + !legacyMode && // ignore when in legacy mode + !legacy && // ignore links to legacy apps + !event.defaultPrevented && // onClick prevented default + event.button === 0 && // ignore everything but left clicks + !isModifiedEvent(event) // ignore clicks with modifier keys + ) { + event.preventDefault(); + navigateToApp(navLink.id); + } + }, + // Legacy apps use `active` property, NP apps should match the current app + isActive: active || currentAppId === id, + isDisabled: disabled, + iconType: euiIconType, + icon: !euiIconType && icon ? : undefined, + order, + 'data-test-subj': 'navDrawerAppsMenuLink', + }; +} diff --git a/src/core/public/chrome/ui/header/recent_links.tsx b/src/core/public/chrome/ui/header/recent_links.tsx new file mode 100644 index 0000000000000..a947ab1c45056 --- /dev/null +++ b/src/core/public/chrome/ui/header/recent_links.tsx @@ -0,0 +1,113 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +// @ts-ignore +import { EuiNavDrawerGroup } from '@elastic/eui'; +import { ChromeNavLink, ChromeRecentlyAccessedHistoryItem } from '../../..'; +import { HttpStart } from '../../../http'; + +// Providing a buffer between the limit and the cut off index +// protects from truncating just the last couple (6) characters +const TRUNCATE_LIMIT: number = 64; +const TRUNCATE_AT: number = 58; + +export function truncateRecentItemLabel(label: string): string { + if (label.length > TRUNCATE_LIMIT) { + label = `${label.substring(0, TRUNCATE_AT)}…`; + } + + return label; +} + +/** + * @param {string} url - a relative or root relative url. If a relative path is given then the + * absolute url returned will depend on the current page where this function is called from. For example + * if you are on page "http://www.mysite.com/shopping/kids" and you pass this function "adults", you would get + * back "http://www.mysite.com/shopping/adults". If you passed this function a root relative path, or one that + * starts with a "/", for example "/account/cart", you would get back "http://www.mysite.com/account/cart". + * @return {string} the relative url transformed into an absolute url + */ +function relativeToAbsolute(url: string) { + const a = document.createElement('a'); + a.setAttribute('href', url); + return a.href; +} + +function prepareForEUI( + recentlyAccessed: ChromeRecentlyAccessedHistoryItem[], + navLinks: ChromeNavLink[], + basePath: HttpStart['basePath'] +) { + return recentlyAccessed.map(({ link, label }) => { + const href = relativeToAbsolute(basePath.prepend(link)); + const navLink = navLinks.find(nl => href.startsWith(nl.baseUrl ?? nl.subUrlBase)); + let titleAndAriaLabel = label; + + if (navLink) { + titleAndAriaLabel = i18n.translate('core.ui.recentLinks.linkItem.screenReaderLabel', { + defaultMessage: '{recentlyAccessedItemLinklabel}, type: {pageType}', + values: { + recentlyAccessedItemLinklabel: label, + pageType: navLink.title, + }, + }); + } + + return { + href, + label: truncateRecentItemLabel(label), + title: titleAndAriaLabel, + 'aria-label': titleAndAriaLabel, + iconType: navLink?.euiIconType, + }; + }); +} + +interface Props { + recentlyAccessedItems: ChromeRecentlyAccessedHistoryItem[]; + navLinks: ChromeNavLink[]; + basePath: HttpStart['basePath']; +} + +export function RecentLinks({ recentlyAccessedItems, navLinks, basePath }: Props) { + return ( + + ); +} diff --git a/src/core/public/core_system.test.ts b/src/core/public/core_system.test.ts index 1ee41fe64418e..94fa74f4bd861 100644 --- a/src/core/public/core_system.test.ts +++ b/src/core/public/core_system.test.ts @@ -429,15 +429,14 @@ describe('Notifications targetDomElement', () => { rootDomElement, }); - let targetDomElementParentInStart: HTMLElement | null; + let targetDomElementInStart: HTMLElement | null; MockNotificationsService.start.mockImplementation(({ targetDomElement }): any => { - expect(targetDomElement.parentElement).not.toBeNull(); - targetDomElementParentInStart = targetDomElement.parentElement; + targetDomElementInStart = targetDomElement; }); // Starting the core system should pass the targetDomElement as a child of the rootDomElement await core.setup(); await core.start(); - expect(targetDomElementParentInStart!).toBe(rootDomElement); + expect(targetDomElementInStart!.parentElement).toBe(rootDomElement); }); }); diff --git a/src/core/public/core_system.ts b/src/core/public/core_system.ts index 5b31c740518e4..5fb12ec154952 100644 --- a/src/core/public/core_system.ts +++ b/src/core/public/core_system.ts @@ -214,6 +214,7 @@ export class CoreSystem { const http = await this.http.start({ injectedMetadata, fatalErrors: this.fatalErrorsSetup! }); const savedObjects = await this.savedObjects.start({ http }); const i18n = await this.i18n.start(); + const fatalErrors = await this.fatalErrors.start(); await this.integrations.start({ uiSettings }); const coreUiTargetDomElement = document.createElement('div'); @@ -221,13 +222,6 @@ export class CoreSystem { const notificationsTargetDomElement = document.createElement('div'); const overlayTargetDomElement = document.createElement('div'); - // ensure the rootDomElement is empty - this.rootDomElement.textContent = ''; - this.rootDomElement.classList.add('coreSystemRootDomElement'); - this.rootDomElement.appendChild(coreUiTargetDomElement); - this.rootDomElement.appendChild(notificationsTargetDomElement); - this.rootDomElement.appendChild(overlayTargetDomElement); - const overlays = this.overlay.start({ i18n, targetDomElement: overlayTargetDomElement, @@ -245,6 +239,7 @@ export class CoreSystem { http, injectedMetadata, notifications, + uiSettings, }); application.registerMountContext(this.coreContext.coreId, 'core', () => ({ @@ -271,9 +266,18 @@ export class CoreSystem { notifications, overlays, uiSettings, + fatalErrors, }; const plugins = await this.plugins.start(core); + + // ensure the rootDomElement is empty + this.rootDomElement.textContent = ''; + this.rootDomElement.classList.add('coreSystemRootDomElement'); + this.rootDomElement.appendChild(coreUiTargetDomElement); + this.rootDomElement.appendChild(notificationsTargetDomElement); + this.rootDomElement.appendChild(overlayTargetDomElement); + const rendering = this.rendering.start({ application, chrome, diff --git a/src/core/public/fatal_errors/fatal_errors_service.mock.ts b/src/core/public/fatal_errors/fatal_errors_service.mock.ts index dd7702a7ee7dd..6d9876f787fa9 100644 --- a/src/core/public/fatal_errors/fatal_errors_service.mock.ts +++ b/src/core/public/fatal_errors/fatal_errors_service.mock.ts @@ -26,18 +26,22 @@ const createSetupContractMock = () => { return setupContract; }; +const createStartContractMock = createSetupContractMock; type FatalErrorsServiceContract = PublicMethodsOf; const createMock = () => { const mocked: jest.Mocked = { setup: jest.fn(), + start: jest.fn(), }; mocked.setup.mockReturnValue(createSetupContractMock()); + mocked.start.mockReturnValue(createStartContractMock()); return mocked; }; export const fatalErrorsServiceMock = { create: createMock, createSetupContract: createSetupContractMock, + createStartContract: createStartContractMock, }; diff --git a/src/core/public/fatal_errors/fatal_errors_service.tsx b/src/core/public/fatal_errors/fatal_errors_service.tsx index 5c6a7bb322ae1..309f07859ef26 100644 --- a/src/core/public/fatal_errors/fatal_errors_service.tsx +++ b/src/core/public/fatal_errors/fatal_errors_service.tsx @@ -54,9 +54,18 @@ export interface FatalErrorsSetup { get$: () => Rx.Observable; } +/** + * FatalErrors stop the Kibana Public Core and displays a fatal error screen + * with details about the Kibana build and the error. + * + * @public + */ +export type FatalErrorsStart = FatalErrorsSetup; + /** @interal */ export class FatalErrorsService { private readonly errorInfo$ = new Rx.ReplaySubject(); + private fatalErrors?: FatalErrorsSetup; /** * @@ -82,7 +91,7 @@ export class FatalErrorsService { }, }); - const fatalErrorsSetup: FatalErrorsSetup = { + this.fatalErrors = { add: (error, source?) => { const errorInfo = getErrorInfo(error, source); @@ -101,9 +110,17 @@ export class FatalErrorsService { }, }; - this.setupGlobalErrorHandlers(fatalErrorsSetup); + this.setupGlobalErrorHandlers(this.fatalErrors!); - return fatalErrorsSetup; + return this.fatalErrors!; + } + + public start() { + const { fatalErrors } = this; + if (!fatalErrors) { + throw new Error('FatalErrorsService#setup() must be invoked before start.'); + } + return fatalErrors; } private renderError(injectedMetadata: InjectedMetadataSetup, i18n: I18nStart) { diff --git a/src/core/public/fatal_errors/index.ts b/src/core/public/fatal_errors/index.ts index e37a36152cf91..c8ea1c0bccd22 100644 --- a/src/core/public/fatal_errors/index.ts +++ b/src/core/public/fatal_errors/index.ts @@ -17,5 +17,5 @@ * under the License. */ -export { FatalErrorsSetup, FatalErrorsService } from './fatal_errors_service'; +export { FatalErrorsSetup, FatalErrorsStart, FatalErrorsService } from './fatal_errors_service'; export { FatalErrorInfo } from './get_error_info'; diff --git a/src/core/public/http/fetch.ts b/src/core/public/http/fetch.ts index b86f1f5c08029..b7ceaed6e56a7 100644 --- a/src/core/public/http/fetch.ts +++ b/src/core/public/http/fetch.ts @@ -133,7 +133,11 @@ export class Fetch { try { response = await window.fetch(request); } catch (err) { - throw new HttpFetchError(err.message, request); + if (err.name === 'AbortError') { + throw err; + } else { + throw new HttpFetchError(err.message, request); + } } const contentType = response.headers.get('Content-Type') || ''; diff --git a/src/core/public/index.ts b/src/core/public/index.ts index 5b17eccc37f8b..bf8cab9a3c778 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -55,7 +55,7 @@ import { ChromeRecentlyAccessed, ChromeRecentlyAccessedHistoryItem, } from './chrome'; -import { FatalErrorsSetup, FatalErrorInfo } from './fatal_errors'; +import { FatalErrorsSetup, FatalErrorsStart, FatalErrorInfo } from './fatal_errors'; import { HttpSetup, HttpStart } from './http'; import { I18nStart } from './i18n'; import { InjectedMetadataSetup, InjectedMetadataStart, LegacyNavLink } from './injected_metadata'; @@ -77,7 +77,8 @@ import { } from './context'; export { CoreContext, CoreSystem } from './core_system'; -export { RecursiveReadonly } from '../utils'; +export { RecursiveReadonly, DEFAULT_APP_CATEGORIES } from '../utils'; +export { AppCategory } from '../types'; export { ApplicationSetup, @@ -232,6 +233,8 @@ export interface CoreStart { overlays: OverlayStart; /** {@link IUiSettingsClient} */ uiSettings: IUiSettingsClient; + /** {@link FatalErrorsStart} */ + fatalErrors: FatalErrorsStart; /** * exposed temporarily until https://github.com/elastic/kibana/issues/41990 done * use *only* to retrieve config values. There is no way to set injected values @@ -302,6 +305,7 @@ export { DocLinksStart, FatalErrorInfo, FatalErrorsSetup, + FatalErrorsStart, HttpSetup, HttpStart, I18nStart, diff --git a/src/core/public/injected_metadata/injected_metadata_service.ts b/src/core/public/injected_metadata/injected_metadata_service.ts index 0bde1b68e1876..1075a7741ee32 100644 --- a/src/core/public/injected_metadata/injected_metadata_service.ts +++ b/src/core/public/injected_metadata/injected_metadata_service.ts @@ -26,10 +26,12 @@ import { UserProvidedValues, } from '../../server/types'; import { deepFreeze } from '../../utils/'; +import { AppCategory } from '../'; /** @public */ export interface LegacyNavLink { id: string; + category?: AppCategory; title: string; order: number; url: string; @@ -52,6 +54,7 @@ export interface InjectedMetadataParams { buildNumber: number; branch: string; basePath: string; + category?: AppCategory; csp: { warnLegacyBrowsers: boolean; }; @@ -75,6 +78,7 @@ export interface InjectedMetadataParams { basePath: string; serverName: string; devMode: boolean; + category?: AppCategory; uiSettings: { defaults: Record; user?: Record; diff --git a/src/core/public/legacy/legacy_service.test.ts b/src/core/public/legacy/legacy_service.test.ts index 9dd24f9e4a7a3..d08c8b52e39c9 100644 --- a/src/core/public/legacy/legacy_service.test.ts +++ b/src/core/public/legacy/legacy_service.test.ts @@ -98,6 +98,7 @@ const notificationsStart = notificationServiceMock.createStartContract(); const overlayStart = overlayServiceMock.createStartContract(); const uiSettingsStart = uiSettingsServiceMock.createStartContract(); const savedObjectsStart = savedObjectsMock.createStartContract(); +const fatalErrorsStart = fatalErrorsServiceMock.createStartContract(); const mockStorage = { getItem: jest.fn() } as any; const defaultStartDeps = { @@ -112,6 +113,7 @@ const defaultStartDeps = { overlays: overlayStart, uiSettings: uiSettingsStart, savedObjects: savedObjectsStart, + fatalErrors: fatalErrorsStart, }, lastSubUrlStorage: mockStorage, targetDomElement: document.createElement('div'), diff --git a/src/core/public/legacy/legacy_service.ts b/src/core/public/legacy/legacy_service.ts index f906aff1759e2..cc3210771eecc 100644 --- a/src/core/public/legacy/legacy_service.ts +++ b/src/core/public/legacy/legacy_service.ts @@ -74,6 +74,7 @@ export class LegacyPlatformService { appUrl: navLink.url, subUrlBase: navLink.subUrlBase, linkToLastSubUrl: navLink.linkToLastSubUrl, + category: navLink.category, }) ); diff --git a/src/core/public/mocks.ts b/src/core/public/mocks.ts index 43c8aa6f1d6b9..ce90d49065ad4 100644 --- a/src/core/public/mocks.ts +++ b/src/core/public/mocks.ts @@ -74,6 +74,7 @@ function createCoreStartMock({ basePath = '' } = {}) { injectedMetadata: { getInjectedVar: injectedMetadataServiceMock.createStartContract().getInjectedVar, }, + fatalErrors: fatalErrorsServiceMock.createStartContract(), }; return mock; diff --git a/src/core/public/plugins/plugin_context.ts b/src/core/public/plugins/plugin_context.ts index f146c2452868b..48100cba4f26e 100644 --- a/src/core/public/plugins/plugin_context.ts +++ b/src/core/public/plugins/plugin_context.ts @@ -151,5 +151,6 @@ export function createPluginStartContext< injectedMetadata: { getInjectedVar: deps.injectedMetadata.getInjectedVar, }, + fatalErrors: deps.fatalErrors, }; } diff --git a/src/core/public/plugins/plugins_service.test.ts b/src/core/public/plugins/plugins_service.test.ts index cafc7e5887e38..dbbcda8d60e12 100644 --- a/src/core/public/plugins/plugins_service.test.ts +++ b/src/core/public/plugins/plugins_service.test.ts @@ -111,6 +111,7 @@ describe('PluginsService', () => { overlays: overlayServiceMock.createStartContract(), uiSettings: uiSettingsServiceMock.createStartContract(), savedObjects: savedObjectsMock.createStartContract(), + fatalErrors: fatalErrorsServiceMock.createStartContract(), }; mockStartContext = { ...mockStartDeps, diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index abd39e864bd30..0da6e0d422f2d 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -26,6 +26,7 @@ export interface App extends AppBase { // @public (undocumented) export interface AppBase { capabilities?: Partial; + category?: AppCategory; chromeless?: boolean; euiIconType?: string; icon?: string; @@ -40,6 +41,14 @@ export interface AppBase { updater$?: Observable; } +// @public +export interface AppCategory { + ariaLabel?: string; + euiIconType?: string; + label: string; + order?: number; +} + // @public export type AppLeaveAction = AppLeaveDefaultAction | AppLeaveConfirmAction; @@ -251,6 +260,7 @@ export interface ChromeNavLink { // @deprecated readonly active?: boolean; readonly baseUrl: string; + readonly category?: AppCategory; // @deprecated readonly disabled?: boolean; readonly euiIconType?: string; @@ -377,6 +387,8 @@ export interface CoreStart { // (undocumented) docLinks: DocLinksStart; // (undocumented) + fatalErrors: FatalErrorsStart; + // (undocumented) http: HttpStart; // (undocumented) i18n: I18nStart; @@ -408,6 +420,26 @@ export class CoreSystem { stop(): void; } +// @internal (undocumented) +export const DEFAULT_APP_CATEGORIES: Readonly<{ + analyze: { + label: string; + order: number; + }; + observability: { + label: string; + order: number; + }; + security: { + label: string; + order: number; + }; + management: { + label: string; + euiIconType: string; + }; +}>; + // @public (undocumented) export interface DocLinksStart { // (undocumented) @@ -532,6 +564,9 @@ export interface FatalErrorsSetup { get$: () => Rx.Observable; } +// @public +export type FatalErrorsStart = FatalErrorsSetup; + // @public export type HandlerContextType> = T extends HandlerFunction ? U : never; @@ -741,6 +776,8 @@ export interface LegacyCoreStart extends CoreStart { // @public (undocumented) export interface LegacyNavLink { + // (undocumented) + category?: AppCategory; // (undocumented) euiIconType?: string; // (undocumented) diff --git a/src/core/public/saved_objects/saved_objects_client.test.ts b/src/core/public/saved_objects/saved_objects_client.test.ts index e633e00965c6a..0c34a16c68e99 100644 --- a/src/core/public/saved_objects/saved_objects_client.test.ts +++ b/src/core/public/saved_objects/saved_objects_client.test.ts @@ -448,23 +448,4 @@ describe('SavedObjectsClient', () => { `); }); }); - - it('maintains backwards compatibility by transforming http.fetch errors to be compatible with kfetch errors', () => { - const err = { - response: { ok: false, redirected: false, status: 409, statusText: 'Conflict' }, - body: 'response body', - }; - http.fetch.mockRejectedValue(err); - return expect(savedObjectsClient.get(doc.type, doc.id)).rejects.toMatchInlineSnapshot(` - Object { - "body": "response body", - "res": Object { - "ok": false, - "redirected": false, - "status": 409, - "statusText": "Conflict", - }, - } - `); - }); }); diff --git a/src/core/public/saved_objects/saved_objects_client.ts b/src/core/public/saved_objects/saved_objects_client.ts index dab98ee66cdb1..ccb23793a8534 100644 --- a/src/core/public/saved_objects/saved_objects_client.ts +++ b/src/core/public/saved_objects/saved_objects_client.ts @@ -465,11 +465,7 @@ export class SavedObjectsClient { * uses `{response: {status: number}}`. */ private savedObjectsFetch(path: string, { method, query, body }: HttpFetchOptions) { - return this.http.fetch(path, { method, query, body }).catch(err => { - const kfetchError = Object.assign(err, { res: err.response }); - delete kfetchError.response; - return Promise.reject(kfetchError); - }); + return this.http.fetch(path, { method, query, body }); } } diff --git a/src/legacy/core_plugins/kibana/server/routes/api/home/register_tutorials.js b/src/core/server/config/env.mock.ts similarity index 79% rename from src/legacy/core_plugins/kibana/server/routes/api/home/register_tutorials.js rename to src/core/server/config/env.mock.ts index 315553af53892..f9b4e0732c1cb 100644 --- a/src/legacy/core_plugins/kibana/server/routes/api/home/register_tutorials.js +++ b/src/core/server/config/env.mock.ts @@ -17,12 +17,13 @@ * under the License. */ -export function registerTutorials(server) { - server.route({ - path: '/api/kibana/home/tutorials_LP', - method: ['GET'], - handler: function(req) { - return server.getTutorials(req); +import { Env } from './env'; + +export function createMockEnv(options: { dist?: boolean } = {}): Env { + return { + // required by CspConfig + packageInfo: { + dist: options.dist ?? true, }, - }); + } as any; } diff --git a/src/core/server/csp/csp_config.test.ts b/src/core/server/csp/csp_config.test.ts index 45fa8445791b0..15cddc6fccd34 100644 --- a/src/core/server/csp/csp_config.test.ts +++ b/src/core/server/csp/csp_config.test.ts @@ -18,6 +18,7 @@ */ import { CspConfig } from '.'; +import { createMockEnv } from '../config/env.mock'; // CSP rules aren't strictly additive, so any change can potentially expand or // restrict the policy in a way we consider a breaking change. For that reason, @@ -33,23 +34,10 @@ import { CspConfig } from '.'; // the nature of a change in defaults during a PR review. describe('CspConfig', () => { - test('DEFAULT', () => { - expect(CspConfig.DEFAULT).toMatchInlineSnapshot(` - CspConfig { - "header": "script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'", - "rules": Array [ - "script-src 'unsafe-eval' 'self'", - "worker-src blob: 'self'", - "style-src 'unsafe-inline' 'self'", - ], - "strict": true, - "warnLegacyBrowsers": true, - } - `); - }); - test('defaults from config', () => { - expect(new CspConfig()).toMatchInlineSnapshot(` + const cspConfig = new CspConfig(createMockEnv()); + + expect(cspConfig).toMatchInlineSnapshot(` CspConfig { "header": "script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'", "rules": Array [ @@ -57,6 +45,7 @@ describe('CspConfig', () => { "worker-src blob: 'self'", "style-src 'unsafe-inline' 'self'", ], + "rulesChangedFromDefault": false, "strict": true, "warnLegacyBrowsers": true, } @@ -64,7 +53,9 @@ describe('CspConfig', () => { }); test('creates from partial config', () => { - expect(new CspConfig({ strict: false, warnLegacyBrowsers: false })).toMatchInlineSnapshot(` + const cspConfig = new CspConfig(createMockEnv(), { strict: false, warnLegacyBrowsers: false }); + + expect(cspConfig).toMatchInlineSnapshot(` CspConfig { "header": "script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'", "rules": Array [ @@ -72,6 +63,7 @@ describe('CspConfig', () => { "worker-src blob: 'self'", "style-src 'unsafe-inline' 'self'", ], + "rulesChangedFromDefault": false, "strict": false, "warnLegacyBrowsers": false, } @@ -79,7 +71,7 @@ describe('CspConfig', () => { }); test('computes header from rules', () => { - const cspConfig = new CspConfig({ rules: ['alpha', 'beta', 'gamma'] }); + const cspConfig = new CspConfig(createMockEnv(), { rules: ['alpha', 'beta', 'gamma'] }); expect(cspConfig).toMatchInlineSnapshot(` CspConfig { @@ -89,6 +81,25 @@ describe('CspConfig', () => { "beta", "gamma", ], + "rulesChangedFromDefault": true, + "strict": true, + "warnLegacyBrowsers": true, + } + `); + }); + + test(`includes blob: style-src if env indicates we're running from source`, () => { + const cspConfig = new CspConfig(createMockEnv({ dist: false })); + + expect(cspConfig).toMatchInlineSnapshot(` + CspConfig { + "header": "script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src blob: 'unsafe-inline' 'self'", + "rules": Array [ + "script-src 'unsafe-eval' 'self'", + "worker-src blob: 'self'", + "style-src blob: 'unsafe-inline' 'self'", + ], + "rulesChangedFromDefault": false, "strict": true, "warnLegacyBrowsers": true, } diff --git a/src/core/server/csp/csp_config.ts b/src/core/server/csp/csp_config.ts index bb57702a4a241..592a7209f4364 100644 --- a/src/core/server/csp/csp_config.ts +++ b/src/core/server/csp/csp_config.ts @@ -18,6 +18,7 @@ */ import { config } from './config'; +import { Env } from '../config'; const DEFAULT_CONFIG = Object.freeze(config.schema.validate({})); @@ -48,6 +49,12 @@ export interface ICspConfig { * in a `Content-Security-Policy` header. */ readonly header: string; + + /** + * Flag indicating that the configuraion changes the csp + * rules from the defaults + */ + readonly rulesChangedFromDefault: boolean; } /** @@ -55,23 +62,37 @@ export interface ICspConfig { * @public */ export class CspConfig implements ICspConfig { - static readonly DEFAULT = new CspConfig(); - public readonly rules: string[]; public readonly strict: boolean; public readonly warnLegacyBrowsers: boolean; public readonly header: string; + public readonly rulesChangedFromDefault: boolean; /** * Returns the default CSP configuration when passed with no config * @internal */ - constructor(rawCspConfig: Partial> = {}) { + constructor(env: Env, rawCspConfig?: Partial>) { const source = { ...DEFAULT_CONFIG, ...rawCspConfig }; - this.rules = source.rules; + this.rules = source.rules.map(rule => { + // if we receive an env, and it indicates that this isn't a distributable, add `blob:` to the style csp rules + if (env && !env.packageInfo.dist && rule.startsWith('style-src ')) { + return rule.replace(/^style-src /, 'style-src blob: '); + } + + return rule; + }); this.strict = source.strict; this.warnLegacyBrowsers = source.warnLegacyBrowsers; - this.header = source.rules.join('; '); + this.header = this.rules.join('; '); + + // only check to see if the csp values are customized when `rawCspConfig` was received. + if (!rawCspConfig) { + this.rulesChangedFromDefault = false; + } else { + const defaultCsp = new CspConfig(env); + this.rulesChangedFromDefault = defaultCsp.header !== this.header; + } } } diff --git a/src/core/server/elasticsearch/retry_call_cluster.ts b/src/core/server/elasticsearch/retry_call_cluster.ts index 89d7b88b1675a..bd72ecf726461 100644 --- a/src/core/server/elasticsearch/retry_call_cluster.ts +++ b/src/core/server/elasticsearch/retry_call_cluster.ts @@ -22,6 +22,7 @@ import { defer, throwError, iif, timer } from 'rxjs'; import * as legacyElasticsearch from 'elasticsearch'; import { CallAPIOptions } from '.'; +import { APICaller } from './api_types'; import { Logger } from '../logging'; const esErrors = legacyElasticsearch.errors; @@ -34,15 +35,11 @@ const esErrors = legacyElasticsearch.errors; * different error is received. * * @param apiCaller + * @param log + * @param delay */ - -// TODO: Replace with APICaller from './scoped_cluster_client' once #46668 is merged export function migrationsRetryCallCluster( - apiCaller: ( - endpoint: string, - clientParams: Record, - options?: CallAPIOptions - ) => Promise, + apiCaller: APICaller, log: Logger, delay: number = 2500 ) { diff --git a/src/core/server/http/http_config.ts b/src/core/server/http/http_config.ts index 73f44f3c5ab5c..b249dbb5b6a04 100644 --- a/src/core/server/http/http_config.ts +++ b/src/core/server/http/http_config.ts @@ -22,6 +22,7 @@ import { hostname } from 'os'; import { CspConfigType, CspConfig, ICspConfig } from '../csp'; import { SslConfig, sslSchema } from './ssl_config'; +import { Env } from '../config'; const validBasePathRegex = /(^$|^\/.*[^\/]$)/; const uuidRegexp = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/i; @@ -148,7 +149,7 @@ export class HttpConfig { /** * @internal */ - constructor(rawHttpConfig: HttpConfigType, rawCspConfig: CspConfigType) { + constructor(rawHttpConfig: HttpConfigType, rawCspConfig: CspConfigType, env: Env) { this.autoListen = rawHttpConfig.autoListen; this.host = rawHttpConfig.host; this.port = rawHttpConfig.port; @@ -162,7 +163,7 @@ export class HttpConfig { this.rewriteBasePath = rawHttpConfig.rewriteBasePath; this.ssl = new SslConfig(rawHttpConfig.ssl || {}); this.compression = rawHttpConfig.compression; - this.csp = new CspConfig(rawCspConfig); + this.csp = new CspConfig(env, rawCspConfig); this.xsrf = rawHttpConfig.xsrf; } } diff --git a/src/core/server/http/http_service.mock.ts b/src/core/server/http/http_service.mock.ts index 6db1ca80ab437..7fc2f5d990824 100644 --- a/src/core/server/http/http_service.mock.ts +++ b/src/core/server/http/http_service.mock.ts @@ -21,6 +21,7 @@ import { Server } from 'hapi'; import { CspConfig } from '../csp'; import { mockRouter } from './router/router.mock'; import { configMock } from '../config/config.mock'; +import { createMockEnv } from '../config/env.mock'; import { InternalHttpServiceSetup } from './types'; import { HttpService } from './http_service'; import { OnPreAuthToolkit } from './lifecycle/on_pre_auth'; @@ -61,7 +62,7 @@ const createSetupContractMock = () => { registerOnPreResponse: jest.fn(), createRouter: jest.fn().mockImplementation(() => mockRouter.create({})), basePath: createBasePathMock(), - csp: CspConfig.DEFAULT, + csp: new CspConfig(createMockEnv()), auth: { get: jest.fn(), isAuthenticated: jest.fn(), diff --git a/src/core/server/http/http_service.ts b/src/core/server/http/http_service.ts index ae9d53f9fd3db..8a86667a1a6bc 100644 --- a/src/core/server/http/http_service.ts +++ b/src/core/server/http/http_service.ts @@ -70,7 +70,7 @@ export class HttpService implements CoreService(httpConfig.path), configService.atPath(cspConfig.path), - ]).pipe(map(([http, csp]) => new HttpConfig(http, csp))); + ]).pipe(map(([http, csp]) => new HttpConfig(http, csp, env))); this.httpServer = new HttpServer(logger, 'Kibana'); this.httpsRedirectServer = new HttpsRedirectServer(logger.get('http', 'redirect', 'server')); } diff --git a/src/core/server/http/http_tools.test.ts b/src/core/server/http/http_tools.test.ts index c1322a5aa94db..d552339f1ea1c 100644 --- a/src/core/server/http/http_tools.test.ts +++ b/src/core/server/http/http_tools.test.ts @@ -29,6 +29,7 @@ import { defaultValidationErrorHandler, HapiValidationError, getServerOptions } import { HttpServer } from './http_server'; import { HttpConfig, config } from './http_config'; import { Router } from './router'; +import { createMockEnv } from '../config/env.mock'; import { loggingServiceMock } from '../logging/logging_service.mock'; import { ByteSizeValue } from '@kbn/config-schema'; @@ -120,7 +121,8 @@ describe('getServerOptions', () => { certificate: 'some-certificate-path', }, }), - {} as any + {} as any, + createMockEnv() ); expect(getServerOptions(httpConfig).tls).toMatchInlineSnapshot(` @@ -149,7 +151,8 @@ describe('getServerOptions', () => { clientAuthentication: 'required', }, }), - {} as any + {} as any, + createMockEnv() ); expect(getServerOptions(httpConfig).tls).toMatchInlineSnapshot(` diff --git a/src/core/server/http/index.ts b/src/core/server/http/index.ts index 55ba813484268..d31afe1670e41 100644 --- a/src/core/server/http/index.ts +++ b/src/core/server/http/index.ts @@ -29,6 +29,7 @@ export { HttpResponsePayload, ErrorHttpResponseOptions, KibanaRequest, + KibanaRequestEvents, KibanaRequestRoute, KibanaRequestRouteOptions, IKibanaResponse, diff --git a/src/core/server/http/integration_tests/request.test.ts b/src/core/server/http/integration_tests/request.test.ts new file mode 100644 index 0000000000000..bc1bbc881315a --- /dev/null +++ b/src/core/server/http/integration_tests/request.test.ts @@ -0,0 +1,127 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import supertest from 'supertest'; + +import { HttpService } from '../http_service'; + +import { contextServiceMock } from '../../context/context_service.mock'; +import { loggingServiceMock } from '../../logging/logging_service.mock'; +import { createHttpServer } from '../test_utils'; + +let server: HttpService; + +let logger: ReturnType; +const contextSetup = contextServiceMock.createSetupContract(); + +const setupDeps = { + context: contextSetup, +}; + +beforeEach(() => { + logger = loggingServiceMock.create(); + + server = createHttpServer({ logger }); +}); + +afterEach(async () => { + await server.stop(); +}); + +const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); +describe('KibanaRequest', () => { + describe('events', () => { + describe('aborted$', () => { + it('emits once and completes when request aborted', async done => { + expect.assertions(1); + const { server: innerServer, createRouter } = await server.setup(setupDeps); + const router = createRouter('/'); + + const nextSpy = jest.fn(); + router.get({ path: '/', validate: false }, async (context, request, res) => { + request.events.aborted$.subscribe({ + next: nextSpy, + complete: () => { + expect(nextSpy).toHaveBeenCalledTimes(1); + done(); + }, + }); + + // prevents the server to respond + await delay(30000); + return res.ok({ body: 'ok' }); + }); + + await server.start(); + + const incomingRequest = supertest(innerServer.listener) + .get('/') + // end required to send request + .end(); + + setTimeout(() => incomingRequest.abort(), 50); + }); + + it('completes & does not emit when request handled', async () => { + const { server: innerServer, createRouter } = await server.setup(setupDeps); + const router = createRouter('/'); + + const nextSpy = jest.fn(); + const completeSpy = jest.fn(); + router.get({ path: '/', validate: false }, async (context, request, res) => { + request.events.aborted$.subscribe({ + next: nextSpy, + complete: completeSpy, + }); + + return res.ok({ body: 'ok' }); + }); + + await server.start(); + + await supertest(innerServer.listener).get('/'); + + expect(nextSpy).toHaveBeenCalledTimes(0); + expect(completeSpy).toHaveBeenCalledTimes(1); + }); + + it('completes & does not emit when request rejected', async () => { + const { server: innerServer, createRouter } = await server.setup(setupDeps); + const router = createRouter('/'); + + const nextSpy = jest.fn(); + const completeSpy = jest.fn(); + router.get({ path: '/', validate: false }, async (context, request, res) => { + request.events.aborted$.subscribe({ + next: nextSpy, + complete: completeSpy, + }); + + return res.badRequest(); + }); + + await server.start(); + + await supertest(innerServer.listener).get('/'); + + expect(nextSpy).toHaveBeenCalledTimes(0); + expect(completeSpy).toHaveBeenCalledTimes(1); + }); + }); + }); +}); diff --git a/src/core/server/http/router/index.ts b/src/core/server/http/router/index.ts index 084d30d694474..32663d1513f36 100644 --- a/src/core/server/http/router/index.ts +++ b/src/core/server/http/router/index.ts @@ -21,6 +21,7 @@ export { Headers, filterHeaders, ResponseHeaders, KnownHeaders } from './headers export { Router, RequestHandler, IRouter, RouteRegistrar } from './router'; export { KibanaRequest, + KibanaRequestEvents, KibanaRequestRoute, KibanaRequestRouteOptions, isRealRequest, diff --git a/src/core/server/http/router/request.ts b/src/core/server/http/router/request.ts index 47b001700b015..22fb2d2643d1c 100644 --- a/src/core/server/http/router/request.ts +++ b/src/core/server/http/router/request.ts @@ -19,6 +19,8 @@ import { Url } from 'url'; import { Request } from 'hapi'; +import { Observable, fromEvent, merge } from 'rxjs'; +import { shareReplay, first, takeUntil } from 'rxjs/operators'; import { deepFreeze, RecursiveReadonly } from '../../../utils'; import { Headers } from './headers'; @@ -46,6 +48,17 @@ export interface KibanaRequestRoute { options: KibanaRequestRouteOptions; } +/** + * Request events. + * @public + * */ +export interface KibanaRequestEvents { + /** + * Observable that emits once if and when the request has been aborted. + */ + aborted$: Observable; +} + /** * @deprecated * `hapi` request object, supported during migration process only for backward compatibility. @@ -115,7 +128,10 @@ export class KibanaRequest< */ public readonly headers: Headers; + /** {@link IKibanaSocket} */ public readonly socket: IKibanaSocket; + /** Request events {@link KibanaRequestEvents} */ + public readonly events: KibanaRequestEvents; /** @internal */ protected readonly [requestSymbol]: Request; @@ -138,12 +154,22 @@ export class KibanaRequest< enumerable: false, }); - this.route = deepFreeze(this.getRouteInfo()); + this.route = deepFreeze(this.getRouteInfo(request)); this.socket = new KibanaSocket(request.raw.req.socket); + this.events = this.getEvents(request); + } + + private getEvents(request: Request): KibanaRequestEvents { + const finish$ = merge( + fromEvent(request.raw.req, 'end'), // all data consumed + fromEvent(request.raw.req, 'close') // connection was closed + ).pipe(shareReplay(1), first()); + return { + aborted$: fromEvent(request.raw.req, 'aborted').pipe(first(), takeUntil(finish$)), + } as const; } - private getRouteInfo(): KibanaRequestRoute { - const request = this[requestSymbol]; + private getRouteInfo(request: Request): KibanaRequestRoute { const method = request.method as Method; const { parse, maxBytes, allow, output } = request.route.settings.payload || {}; diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 3f67b9a656bb7..cdc2ec91134ed 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -109,6 +109,7 @@ export { IKibanaSocket, IsAuthenticated, KibanaRequest, + KibanaRequestEvents, KibanaRequestRoute, KibanaRequestRouteOptions, IKibanaResponse, @@ -179,6 +180,7 @@ export { SavedObjectsClientWrapperFactory, SavedObjectsClientWrapperOptions, SavedObjectsClientFactory, + SavedObjectsClientFactoryProvider, SavedObjectsCreateOptions, SavedObjectsErrorHelpers, SavedObjectsExportOptions, @@ -194,6 +196,7 @@ export { SavedObjectsImportUnsupportedTypeError, SavedObjectsMigrationLogger, SavedObjectsRawDoc, + SavedObjectsRepositoryFactory, SavedObjectsResolveImportErrorsOptions, SavedObjectsSchema, SavedObjectsSerializer, diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index 07cc933033054..ca1204f0ac05c 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -86,7 +86,7 @@ export class LegacyService implements CoreService { public legacyInternals?: ILegacyInternals; constructor(private readonly coreContext: CoreContext) { - const { logger, configService } = coreContext; + const { logger, configService, env } = coreContext; this.log = logger.get('legacy-service'); this.devConfig$ = configService @@ -95,7 +95,7 @@ export class LegacyService implements CoreService { this.httpConfig$ = combineLatest( configService.atPath(httpConfig.path), configService.atPath(cspConfig.path) - ).pipe(map(([http, csp]) => new HttpConfig(http, csp))); + ).pipe(map(([http, csp]) => new HttpConfig(http, csp, env))); } public async discoverPlugins(): Promise { @@ -258,7 +258,11 @@ export class LegacyService implements CoreService { ) { const coreStart: CoreStart = { capabilities: startDeps.core.capabilities, - savedObjects: { getScopedClient: startDeps.core.savedObjects.getScopedClient }, + savedObjects: { + getScopedClient: startDeps.core.savedObjects.getScopedClient, + createScopedRepository: startDeps.core.savedObjects.createScopedRepository, + createInternalRepository: startDeps.core.savedObjects.createInternalRepository, + }, uiSettings: { asScopedToClient: startDeps.core.uiSettings.asScopedToClient }, }; @@ -286,10 +290,8 @@ export class LegacyService implements CoreService { isTlsEnabled: setupDeps.core.http.isTlsEnabled, }, savedObjects: { - setClientFactory: setupDeps.core.savedObjects.setClientFactory, + setClientFactoryProvider: setupDeps.core.savedObjects.setClientFactoryProvider, addClientWrapper: setupDeps.core.savedObjects.addClientWrapper, - createInternalRepository: setupDeps.core.savedObjects.createInternalRepository, - createScopedRepository: setupDeps.core.savedObjects.createScopedRepository, }, uiSettings: { register: setupDeps.core.uiSettings.register, diff --git a/src/core/server/legacy/plugins/find_legacy_plugin_specs.ts b/src/core/server/legacy/plugins/find_legacy_plugin_specs.ts index 9867274d224bd..a19133c30659b 100644 --- a/src/core/server/legacy/plugins/find_legacy_plugin_specs.ts +++ b/src/core/server/legacy/plugins/find_legacy_plugin_specs.ts @@ -65,6 +65,7 @@ function getUiAppsNavLinks({ uiAppSpecs = [] }: LegacyUiExports, pluginSpecs: Le return { id, + category: spec.category, title: spec.title, order: typeof spec.order === 'number' ? spec.order : 0, icon: spec.icon, @@ -79,6 +80,7 @@ function getNavLinks(uiExports: LegacyUiExports, pluginSpecs: LegacyPluginSpec[] return (uiExports.navLinkSpecs || []) .map(spec => ({ id: spec.id, + category: spec.category, title: spec.title, order: typeof spec.order === 'number' ? spec.order : 0, url: spec.url, diff --git a/src/core/server/legacy/types.ts b/src/core/server/legacy/types.ts index 40b8244a31890..d51058ca561c6 100644 --- a/src/core/server/legacy/types.ts +++ b/src/core/server/legacy/types.ts @@ -139,7 +139,7 @@ export type LegacyNavLinkSpec = Record & ChromeNavLink; */ export type LegacyAppSpec = Pick< ChromeNavLink, - 'title' | 'order' | 'icon' | 'euiIconType' | 'url' | 'linkToLastSubUrl' | 'hidden' + 'title' | 'order' | 'icon' | 'euiIconType' | 'url' | 'linkToLastSubUrl' | 'hidden' | 'category' > & { pluginId?: string; id?: string; listed?: boolean }; /** diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index c0a8973d98a54..846c2e4d8c507 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -33,6 +33,7 @@ import { capabilitiesServiceMock } from './capabilities/capabilities_service.moc export { httpServerMock } from './http/http_server.mocks'; export { sessionStorageMock } from './http/cookie_session_storage.mocks'; export { configServiceMock } from './config/config_service.mock'; +import { createMockEnv } from './config/env.mock'; export { elasticsearchServiceMock } from './elasticsearch/elasticsearch_service.mock'; export { httpServiceMock } from './http/http_service.mock'; export { loggingServiceMock } from './logging/logging_service.mock'; @@ -97,7 +98,7 @@ function createCoreSetupMock() { registerOnPostAuth: httpService.registerOnPostAuth, registerOnPreResponse: httpService.registerOnPreResponse, basePath: httpService.basePath, - csp: CspConfig.DEFAULT, + csp: new CspConfig(createMockEnv()), isTlsEnabled: httpService.isTlsEnabled, createRouter: jest.fn(), registerRouteHandlerContext: jest.fn(), diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index f266172cb4bd9..99cd4eda7374c 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -165,10 +165,8 @@ export function createPluginSetupContext( isTlsEnabled: deps.http.isTlsEnabled, }, savedObjects: { - setClientFactory: deps.savedObjects.setClientFactory, + setClientFactoryProvider: deps.savedObjects.setClientFactoryProvider, addClientWrapper: deps.savedObjects.addClientWrapper, - createInternalRepository: deps.savedObjects.createInternalRepository, - createScopedRepository: deps.savedObjects.createScopedRepository, }, uiSettings: { register: deps.uiSettings.register, @@ -203,6 +201,8 @@ export function createPluginStartContext( }, savedObjects: { getScopedClient: deps.savedObjects.getScopedClient, + createInternalRepository: deps.savedObjects.createInternalRepository, + createScopedRepository: deps.savedObjects.createScopedRepository, }, uiSettings: { asScopedToClient: deps.uiSettings.asScopedToClient, diff --git a/src/core/server/saved_objects/index.ts b/src/core/server/saved_objects/index.ts index 1100c18bcc72f..181025d73817d 100644 --- a/src/core/server/saved_objects/index.ts +++ b/src/core/server/saved_objects/index.ts @@ -41,6 +41,7 @@ export { SavedObjectsServiceStart, SavedObjectsServiceSetup, InternalSavedObjectsServiceSetup, + SavedObjectsRepositoryFactory, } from './saved_objects_service'; export { diff --git a/src/core/server/saved_objects/saved_objects_service.mock.ts b/src/core/server/saved_objects/saved_objects_service.mock.ts index b2596146a02d4..a15d1f5b864b7 100644 --- a/src/core/server/saved_objects/saved_objects_service.mock.ts +++ b/src/core/server/saved_objects/saved_objects_service.mock.ts @@ -33,29 +33,27 @@ const createStartContractMock = () => { const startContract: jest.Mocked = { clientProvider: savedObjectsClientProviderMock.create(), getScopedClient: jest.fn(), + createInternalRepository: jest.fn(), + createScopedRepository: jest.fn(), migrator: mockKibanaMigrator.create(), }; + startContract.getScopedClient.mockReturnValue(savedObjectsClientMock.create()); + startContract.createInternalRepository.mockReturnValue(savedObjectsRepositoryMock.create()); + startContract.createScopedRepository.mockReturnValue(savedObjectsRepositoryMock.create()); + return startContract; }; const createSetupContractMock = () => { const setupContract: jest.Mocked = { - getScopedClient: jest.fn(), - setClientFactory: jest.fn(), + setClientFactoryProvider: jest.fn(), addClientWrapper: jest.fn(), - createInternalRepository: jest.fn(), - createScopedRepository: jest.fn(), }; - - setupContract.getScopedClient.mockReturnValue(savedObjectsClientMock.create()); - setupContract.createInternalRepository.mockReturnValue(savedObjectsRepositoryMock.create()); - setupContract.createScopedRepository.mockReturnValue(savedObjectsRepositoryMock.create()); - return setupContract; }; -const createsavedObjectsServiceMock = () => { +const createSavedObjectsServiceMock = () => { const mocked: jest.Mocked = { setup: jest.fn(), start: jest.fn(), @@ -69,7 +67,7 @@ const createsavedObjectsServiceMock = () => { }; export const savedObjectsServiceMock = { - create: createsavedObjectsServiceMock, + create: createSavedObjectsServiceMock, createSetupContract: createSetupContractMock, createStartContract: createStartContractMock, }; diff --git a/src/legacy/server/url_shortening/routes/lib/short_url_assert_valid.js b/src/core/server/saved_objects/saved_objects_service.test.mocks.ts similarity index 54% rename from src/legacy/server/url_shortening/routes/lib/short_url_assert_valid.js rename to src/core/server/saved_objects/saved_objects_service.test.mocks.ts index ff2b0f214e5ee..a9ad0778d4757 100644 --- a/src/legacy/server/url_shortening/routes/lib/short_url_assert_valid.js +++ b/src/core/server/saved_objects/saved_objects_service.test.mocks.ts @@ -17,25 +17,16 @@ * under the License. */ -import { parse } from 'url'; -import { trim } from 'lodash'; -import Boom from 'boom'; +import { mockKibanaMigrator } from './migrations/kibana/kibana_migrator.mock'; +import { savedObjectsClientProviderMock } from './service/lib/scoped_client_provider.mock'; -export function shortUrlAssertValid(url) { - const { protocol, hostname, pathname } = parse(url); +export const migratorInstanceMock = mockKibanaMigrator.create(); +export const KibanaMigratorMock = jest.fn().mockImplementation(() => migratorInstanceMock); +jest.doMock('./migrations/kibana/kibana_migrator', () => ({ + KibanaMigrator: KibanaMigratorMock, +})); - if (protocol) { - throw Boom.notAcceptable(`Short url targets cannot have a protocol, found "${protocol}"`); - } - - if (hostname) { - throw Boom.notAcceptable(`Short url targets cannot have a hostname, found "${hostname}"`); - } - - const pathnameParts = trim(pathname, '/').split('/'); - if (pathnameParts.length !== 2) { - throw Boom.notAcceptable( - `Short url target path must be in the format "/app/{{appId}}", found "${pathname}"` - ); - } -} +export const clientProviderInstanceMock = savedObjectsClientProviderMock.create(); +jest.doMock('./service/lib/scoped_client_provider', () => ({ + SavedObjectsClientProvider: jest.fn().mockImplementation(() => clientProviderInstanceMock), +})); diff --git a/src/core/server/saved_objects/saved_objects_service.test.ts b/src/core/server/saved_objects/saved_objects_service.test.ts index deb6f98e17b7b..6668d57045a95 100644 --- a/src/core/server/saved_objects/saved_objects_service.test.ts +++ b/src/core/server/saved_objects/saved_objects_service.test.ts @@ -17,95 +17,149 @@ * under the License. */ -jest.mock('./migrations/kibana/kibana_migrator'); +import { + KibanaMigratorMock, + migratorInstanceMock, + clientProviderInstanceMock, +} from './saved_objects_service.test.mocks'; -import { SavedObjectsService, SavedObjectsSetupDeps } from './saved_objects_service'; +import { SavedObjectsService } from './saved_objects_service'; import { mockCoreContext } from '../core_context.mock'; -// @ts-ignore Typescript doesn't know about the jest mock -import { KibanaMigrator, mockKibanaMigratorInstance } from './migrations/kibana/kibana_migrator'; import * as legacyElasticsearch from 'elasticsearch'; import { Env } from '../config'; import { configServiceMock } from '../mocks'; - -afterEach(() => { - jest.clearAllMocks(); -}); +import { elasticsearchServiceMock } from '../elasticsearch/elasticsearch_service.mock'; +import { legacyServiceMock } from '../legacy/legacy_service.mock'; +import { SavedObjectsClientFactoryProvider } from './service/lib'; describe('SavedObjectsService', () => { + const createSetupDeps = () => { + return { + elasticsearch: elasticsearchServiceMock.createInternalSetup(), + legacyPlugins: legacyServiceMock.createDiscoverPlugins(), + }; + }; + + afterEach(() => { + jest.clearAllMocks(); + }); + describe('#setup()', () => { + describe('#setClientFactoryProvider', () => { + it('registers the factory to the clientProvider', async () => { + const coreContext = mockCoreContext.create(); + const soService = new SavedObjectsService(coreContext); + const setup = await soService.setup(createSetupDeps()); + + const factory = jest.fn(); + const factoryProvider: SavedObjectsClientFactoryProvider = () => factory; + + setup.setClientFactoryProvider(factoryProvider); + + await soService.start({}); + + expect(clientProviderInstanceMock.setClientFactory).toHaveBeenCalledWith(factory); + }); + it('throws if a factory is already registered', async () => { + const coreContext = mockCoreContext.create(); + const soService = new SavedObjectsService(coreContext); + const setup = await soService.setup(createSetupDeps()); + + const firstFactory = () => jest.fn(); + const secondFactory = () => jest.fn(); + + setup.setClientFactoryProvider(firstFactory); + + expect(() => { + setup.setClientFactoryProvider(secondFactory); + }).toThrowErrorMatchingInlineSnapshot( + `"custom client factory is already set, and can only be set once"` + ); + }); + }); + + describe('#addClientWrapper', () => { + it('registers the wrapper to the clientProvider', async () => { + const coreContext = mockCoreContext.create(); + const soService = new SavedObjectsService(coreContext); + const setup = await soService.setup(createSetupDeps()); + + const wrapperA = jest.fn(); + const wrapperB = jest.fn(); + + setup.addClientWrapper(1, 'A', wrapperA); + setup.addClientWrapper(2, 'B', wrapperB); + + await soService.start({}); + + expect(clientProviderInstanceMock.addClientWrapperFactory).toHaveBeenCalledTimes(2); + expect(clientProviderInstanceMock.addClientWrapperFactory).toHaveBeenCalledWith( + 1, + 'A', + wrapperA + ); + expect(clientProviderInstanceMock.addClientWrapperFactory).toHaveBeenCalledWith( + 2, + 'B', + wrapperB + ); + }); + }); + }); + + describe('#start()', () => { it('creates a KibanaMigrator which retries NoConnections errors from callAsInternalUser', async () => { const coreContext = mockCoreContext.create(); - let i = 0; - const clusterClient = { - callAsInternalUser: jest - .fn() - .mockImplementation(() => - i++ <= 2 - ? Promise.reject(new legacyElasticsearch.errors.NoConnections()) - : Promise.resolve('success') - ), - }; const soService = new SavedObjectsService(coreContext); - const coreSetup = ({ - elasticsearch: { adminClient: clusterClient }, - legacyPlugins: { uiExports: { savedObjectMappings: [] }, pluginExtendedConfig: {} }, - } as unknown) as SavedObjectsSetupDeps; + const coreSetup = createSetupDeps(); + + let i = 0; + coreSetup.elasticsearch.adminClient.callAsInternalUser = jest + .fn() + .mockImplementation(() => + i++ <= 2 + ? Promise.reject(new legacyElasticsearch.errors.NoConnections()) + : Promise.resolve('success') + ); - await soService.setup(coreSetup, 1); + await soService.setup(coreSetup); + await soService.start({}, 1); - return expect((KibanaMigrator as jest.Mock).mock.calls[0][0].callCluster()).resolves.toMatch( - 'success' - ); + return expect(KibanaMigratorMock.mock.calls[0][0].callCluster()).resolves.toMatch('success'); }); - }); - describe('#start()', () => { it('skips KibanaMigrator migrations when --optimize=true', async () => { const coreContext = mockCoreContext.create({ env: ({ cliArgs: { optimize: true }, packageInfo: { version: 'x.x.x' } } as unknown) as Env, }); const soService = new SavedObjectsService(coreContext); - const coreSetup = ({ - elasticsearch: { adminClient: { callAsInternalUser: jest.fn() } }, - legacyPlugins: { uiExports: {}, pluginExtendedConfig: {} }, - } as unknown) as SavedObjectsSetupDeps; - await soService.setup(coreSetup); + await soService.setup(createSetupDeps()); await soService.start({}); - expect(mockKibanaMigratorInstance.runMigrations).toHaveBeenCalledWith(true); + expect(migratorInstanceMock.runMigrations).toHaveBeenCalledWith(true); }); it('skips KibanaMigrator migrations when migrations.skip=true', async () => { const configService = configServiceMock.create({ atPath: { skip: true } }); const coreContext = mockCoreContext.create({ configService }); const soService = new SavedObjectsService(coreContext); - const coreSetup = ({ - elasticsearch: { adminClient: { callAsInternalUser: jest.fn() } }, - legacyPlugins: { uiExports: {}, pluginExtendedConfig: {} }, - } as unknown) as SavedObjectsSetupDeps; - - await soService.setup(coreSetup); + await soService.setup(createSetupDeps()); await soService.start({}); - expect(mockKibanaMigratorInstance.runMigrations).toHaveBeenCalledWith(true); + expect(migratorInstanceMock.runMigrations).toHaveBeenCalledWith(true); }); it('resolves with KibanaMigrator after waiting for migrations to complete', async () => { const configService = configServiceMock.create({ atPath: { skip: false } }); const coreContext = mockCoreContext.create({ configService }); const soService = new SavedObjectsService(coreContext); - const coreSetup = ({ - elasticsearch: { adminClient: { callAsInternalUser: jest.fn() } }, - legacyPlugins: { uiExports: {}, pluginExtendedConfig: {} }, - } as unknown) as SavedObjectsSetupDeps; - - await soService.setup(coreSetup); - expect(mockKibanaMigratorInstance.runMigrations).toHaveBeenCalledTimes(0); + await soService.setup(createSetupDeps()); + expect(migratorInstanceMock.runMigrations).toHaveBeenCalledTimes(0); const startContract = await soService.start({}); - expect(startContract.migrator).toBe(mockKibanaMigratorInstance); - expect(mockKibanaMigratorInstance.runMigrations).toHaveBeenCalledWith(false); - expect(mockKibanaMigratorInstance.runMigrations).toHaveBeenCalledTimes(1); + expect(startContract.migrator).toBe(migratorInstanceMock); + expect(migratorInstanceMock.runMigrations).toHaveBeenCalledWith(false); + expect(migratorInstanceMock.runMigrations).toHaveBeenCalledTimes(1); }); }); }); diff --git a/src/core/server/saved_objects/saved_objects_service.ts b/src/core/server/saved_objects/saved_objects_service.ts index 69ca8306ca4da..b08033a19242b 100644 --- a/src/core/server/saved_objects/saved_objects_service.ts +++ b/src/core/server/saved_objects/saved_objects_service.ts @@ -37,13 +37,17 @@ import { KibanaRequest } from '../http'; import { SavedObjectsClientContract } from './types'; import { ISavedObjectsRepository, SavedObjectsRepository } from './service/lib/repository'; import { - SavedObjectsClientFactory, + SavedObjectsClientFactoryProvider, SavedObjectsClientWrapperFactory, } from './service/lib/scoped_client_provider'; -import { Logger } from '..'; +import { Logger } from '../logging'; +import { SavedObjectsMapping } from './mappings'; +import { MigrationDefinition } from './migrations/core/document_migrator'; +import { SavedObjectsSchemaDefinition } from './schema'; +import { PropertyValidators } from './validation'; /** - * Saved Objects is Kibana's data persisentence mechanism allowing plugins to + * Saved Objects is Kibana's data persistence mechanism allowing plugins to * use Elasticsearch for storing and querying state. The * SavedObjectsServiceSetup API exposes methods for creating and registering * Saved Object client wrappers. @@ -66,11 +70,11 @@ import { Logger } from '..'; * constructor. * * @example - * import {SavedObjectsClient, CoreSetup} from 'src/core/server'; + * import { SavedObjectsClient, CoreSetup } from 'src/core/server'; * * export class Plugin() { * setup: (core: CoreSetup) => { - * core.savedObjects.setClientFactory(({request: KibanaRequest}) => { + * core.savedObjects.setClientFactory(({ request: KibanaRequest }) => { * return new SavedObjectsClient(core.savedObjects.scopedRepository(request)); * }) * } @@ -80,61 +84,25 @@ import { Logger } from '..'; */ export interface SavedObjectsServiceSetup { /** - * Set a default factory for creating Saved Objects clients. Only one client - * factory can be set, subsequent calls to this method will fail. + * Set the default {@link SavedObjectsClientFactoryProvider | factory provider} for creating Saved Objects clients. + * Only one provider can be set, subsequent calls to this method will fail. */ - setClientFactory: (customClientFactory: SavedObjectsClientFactory) => void; + setClientFactoryProvider: (clientFactoryProvider: SavedObjectsClientFactoryProvider) => void; /** - * Add a client wrapper with the given priority. + * Add a {@link SavedObjectsClientWrapperFactory | client wrapper factory} with the given priority. */ addClientWrapper: ( priority: number, id: string, - factory: SavedObjectsClientWrapperFactory + factory: SavedObjectsClientWrapperFactory ) => void; - - /** - * Creates a {@link ISavedObjectsRepository | Saved Objects repository} that - * uses the credentials from the passed in request to authenticate with - * Elasticsearch. - * - * @remarks - * The repository should only be used for creating and registering a client - * factory or client wrapper. Using the repository directly for interacting - * with Saved Objects is an anti-pattern. Use the Saved Objects client from - * the - * {@link SavedObjectsServiceStart | SavedObjectsServiceStart#getScopedClient } - * method or the {@link RequestHandlerContext | route handler context} - * instead. - */ - createScopedRepository: (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository; - - /** - * Creates a {@link ISavedObjectsRepository | Saved Objects repository} that - * uses the internal Kibana user for authenticating with Elasticsearch. - * - * @remarks - * The repository should only be used for creating and registering a client - * factory or client wrapper. Using the repository directly for interacting - * with Saved Objects is an anti-pattern. Use the Saved Objects client from - * the - * {@link SavedObjectsServiceStart | SavedObjectsServiceStart#getScopedClient } - * method or the {@link RequestHandlerContext | route handler context} - * instead. - */ - createInternalRepository: (extraTypes?: string[]) => ISavedObjectsRepository; } /** * @internal */ -export interface InternalSavedObjectsServiceSetup extends SavedObjectsServiceSetup { - getScopedClient: ( - req: KibanaRequest, - options?: SavedObjectsClientProviderOptions - ) => SavedObjectsClientContract; -} +export type InternalSavedObjectsServiceSetup = SavedObjectsServiceSetup; /** * Saved Objects is Kibana's data persisentence mechanism allowing plugins to @@ -158,6 +126,26 @@ export interface SavedObjectsServiceStart { req: KibanaRequest, options?: SavedObjectsClientProviderOptions ) => SavedObjectsClientContract; + /** + * Creates a {@link ISavedObjectsRepository | Saved Objects repository} that + * uses the credentials from the passed in request to authenticate with + * Elasticsearch. + * + * @param req - The request to create the scoped repository from. + * @param extraTypes - A list of additional hidden types the repository should have access to. + * + * @remarks + * Prefer using `getScopedClient`. This should only be used when using methods + * not exposed on {@link SavedObjectsClientContract} + */ + createScopedRepository: (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository; + /** + * Creates a {@link ISavedObjectsRepository | Saved Objects repository} that + * uses the internal Kibana user for authenticating with Elasticsearch. + * + * @param extraTypes - A list of additional hidden types the repository should have access to. + */ + createInternalRepository: (extraTypes?: string[]) => ISavedObjectsRepository; } export interface InternalSavedObjectsServiceStart extends SavedObjectsServiceStart { @@ -171,130 +159,200 @@ export interface InternalSavedObjectsServiceStart extends SavedObjectsServiceSta clientProvider: ISavedObjectsClientProvider; } +/** + * Factory provided when invoking a {@link SavedObjectsClientFactoryProvider | client factory provider} + * See {@link SavedObjectsServiceSetup.setClientFactoryProvider} + * + * @public + */ +export interface SavedObjectsRepositoryFactory { + /** + * Creates a {@link ISavedObjectsRepository | Saved Objects repository} that + * uses the credentials from the passed in request to authenticate with + * Elasticsearch. + * + * @param extraTypes - A list of additional hidden types the repository should have access to. + */ + createScopedRepository: (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository; + /** + * Creates a {@link ISavedObjectsRepository | Saved Objects repository} that + * uses the internal Kibana user for authenticating with Elasticsearch. + * + * @param extraTypes - A list of additional hidden types the repository should have access to. + */ + createInternalRepository: (extraTypes?: string[]) => ISavedObjectsRepository; +} + /** @internal */ export interface SavedObjectsSetupDeps { legacyPlugins: LegacyServiceDiscoverPlugins; elasticsearch: InternalElasticsearchServiceSetup; } +interface WrappedClientFactoryWrapper { + priority: number; + id: string; + factory: SavedObjectsClientWrapperFactory; +} + /** @internal */ // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface SavedObjectsStartDeps {} export class SavedObjectsService implements CoreService { - private migrator: KibanaMigrator | undefined; private logger: Logger; - private clientProvider: ISavedObjectsClientProvider | undefined; + + private setupDeps?: SavedObjectsSetupDeps; + private clientFactoryProvider?: SavedObjectsClientFactoryProvider; + private clientFactoryWrappers: WrappedClientFactoryWrapper[] = []; + + private mappings: SavedObjectsMapping[] = []; + private migrations: MigrationDefinition = {}; + private schemas: SavedObjectsSchemaDefinition = {}; + private validations: PropertyValidators = {}; constructor(private readonly coreContext: CoreContext) { this.logger = coreContext.logger.get('savedobjects-service'); } - public async setup( - setupDeps: SavedObjectsSetupDeps, - migrationsRetryDelay?: number - ): Promise { + public async setup(setupDeps: SavedObjectsSetupDeps): Promise { this.logger.debug('Setting up SavedObjects service'); + this.setupDeps = setupDeps; + const { savedObjectSchemas: savedObjectsSchemasDefinition, savedObjectMappings, savedObjectMigrations, savedObjectValidations, } = setupDeps.legacyPlugins.uiExports; + this.mappings = savedObjectMappings; + this.migrations = savedObjectMigrations; + this.schemas = savedObjectsSchemasDefinition; + this.validations = savedObjectValidations; - const savedObjectSchemas = new SavedObjectsSchema(savedObjectsSchemasDefinition); + return { + setClientFactoryProvider: provider => { + if (this.clientFactoryProvider) { + throw new Error('custom client factory is already set, and can only be set once'); + } + this.clientFactoryProvider = provider; + }, + addClientWrapper: (priority, id, factory) => { + this.clientFactoryWrappers.push({ + priority, + id, + factory, + }); + }, + }; + } + + public async start( + core: SavedObjectsStartDeps, + migrationsRetryDelay?: number + ): Promise { + if (!this.setupDeps) { + throw new Error('#setup() needs to be run first'); + } + + this.logger.debug('Starting SavedObjects service'); const kibanaConfig = await this.coreContext.configService .atPath('kibana') .pipe(first()) .toPromise(); - const savedObjectsConfig = await this.coreContext.configService .atPath('migrations') .pipe(first()) .toPromise(); + const adminClient = this.setupDeps!.elasticsearch.adminClient; + const migrator = this.createMigrator(kibanaConfig, savedObjectsConfig, migrationsRetryDelay); - const adminClient = setupDeps.elasticsearch.adminClient; + /** + * Note: We want to ensure that migrations have completed before + * continuing with further Core start steps that might use SavedObjects + * such as running the legacy server, legacy plugins and allowing incoming + * HTTP requests. + * + * However, our build system optimize step and some tests depend on the + * HTTP server running without an Elasticsearch server being available. + * So, when the `migrations.skip` is true, we skip migrations altogether. + */ + const cliArgs = this.coreContext.env.cliArgs; + const skipMigrations = cliArgs.optimize || savedObjectsConfig.skip; - const migrator = (this.migrator = new KibanaMigrator({ - savedObjectSchemas, - savedObjectMappings, - savedObjectMigrations, - savedObjectValidations, - logger: this.coreContext.logger.get('migrations'), - kibanaVersion: this.coreContext.env.packageInfo.version, - config: setupDeps.legacyPlugins.pluginExtendedConfig, - savedObjectsConfig, - kibanaConfig, - callCluster: migrationsRetryCallCluster( - adminClient.callAsInternalUser, - this.coreContext.logger.get('migrations'), - migrationsRetryDelay - ), - })); + this.logger.debug('Starting saved objects migration'); + await migrator.runMigrations(skipMigrations); + this.logger.debug('Saved objects migration completed'); - const createSORepository = (callCluster: APICaller, extraTypes: string[] = []) => { + const createRepository = (callCluster: APICaller, extraTypes: string[] = []) => { return SavedObjectsRepository.createRepository( migrator, - savedObjectSchemas, - setupDeps.legacyPlugins.pluginExtendedConfig, + new SavedObjectsSchema(this.schemas), + this.setupDeps!.legacyPlugins.pluginExtendedConfig, kibanaConfig.index, callCluster, extraTypes ); }; - this.clientProvider = new SavedObjectsClientProvider({ + const repositoryFactory: SavedObjectsRepositoryFactory = { + createInternalRepository: (extraTypes?: string[]) => + createRepository(adminClient.callAsInternalUser, extraTypes), + createScopedRepository: (req: KibanaRequest, extraTypes?: string[]) => + createRepository(adminClient.asScoped(req).callAsCurrentUser, extraTypes), + }; + + const clientProvider = new SavedObjectsClientProvider({ defaultClientFactory({ request }) { - const repository = createSORepository(adminClient.asScoped(request).callAsCurrentUser); + const repository = repositoryFactory.createScopedRepository(request); return new SavedObjectsClient(repository); }, }); + if (this.clientFactoryProvider) { + const clientFactory = this.clientFactoryProvider(repositoryFactory); + clientProvider.setClientFactory(clientFactory); + } + this.clientFactoryWrappers.forEach(({ id, factory, priority }) => { + clientProvider.addClientWrapperFactory(priority, id, factory); + }); return { - getScopedClient: this.clientProvider.getClient.bind(this.clientProvider), - setClientFactory: this.clientProvider.setClientFactory.bind(this.clientProvider), - addClientWrapper: this.clientProvider.addClientWrapperFactory.bind(this.clientProvider), - createInternalRepository: (extraTypes?: string[]) => - createSORepository(adminClient.callAsInternalUser, extraTypes), - createScopedRepository: (req: KibanaRequest, extraTypes?: string[]) => - createSORepository(adminClient.asScoped(req).callAsCurrentUser, extraTypes), + migrator, + clientProvider, + getScopedClient: clientProvider.getClient.bind(clientProvider), + createScopedRepository: repositoryFactory.createScopedRepository, + createInternalRepository: repositoryFactory.createInternalRepository, }; } - public async start(core: SavedObjectsStartDeps): Promise { - if (!this.clientProvider) { - throw new Error('#setup() needs to be run first'); - } - - this.logger.debug('Starting SavedObjects service'); + public async stop() {} - /** - * Note: We want to ensure that migrations have completed before - * continuing with further Core startup steps that might use SavedObjects - * such as running the legacy server, legacy plugins and allowing incoming - * HTTP requests. - * - * However, our build system optimize step and some tests depend on the - * HTTP server running without an Elasticsearch server being available. - * So, when the `migrations.skip` is true, we skip migrations altogether. - */ - const cliArgs = this.coreContext.env.cliArgs; - const savedObjectsConfig = await this.coreContext.configService - .atPath('migrations') - .pipe(first()) - .toPromise(); - const skipMigrations = cliArgs.optimize || savedObjectsConfig.skip; - await this.migrator!.runMigrations(skipMigrations); + private createMigrator( + kibanaConfig: KibanaConfigType, + savedObjectsConfig: SavedObjectsConfigType, + migrationsRetryDelay?: number + ): KibanaMigrator { + const savedObjectSchemas = new SavedObjectsSchema(this.schemas); + const adminClient = this.setupDeps!.elasticsearch.adminClient; - return { - migrator: this.migrator!, - clientProvider: this.clientProvider, - getScopedClient: this.clientProvider.getClient.bind(this.clientProvider), - }; + return new KibanaMigrator({ + savedObjectSchemas, + savedObjectMappings: this.mappings, + savedObjectMigrations: this.migrations, + savedObjectValidations: this.validations, + logger: this.coreContext.logger.get('migrations'), + kibanaVersion: this.coreContext.env.packageInfo.version, + config: this.setupDeps!.legacyPlugins.pluginExtendedConfig, + savedObjectsConfig, + kibanaConfig, + callCluster: migrationsRetryCallCluster( + adminClient.callAsInternalUser, + this.coreContext.logger.get('migrations'), + migrationsRetryDelay + ), + }); } - - public async stop() {} } diff --git a/src/core/server/saved_objects/service/index.ts b/src/core/server/saved_objects/service/index.ts index f50ee1759dad7..9f625b4732e26 100644 --- a/src/core/server/saved_objects/service/index.ts +++ b/src/core/server/saved_objects/service/index.ts @@ -29,13 +29,11 @@ import { SavedObjectsResolveImportErrorsOptions } from '../import/types'; * @internal * @deprecated */ -export interface SavedObjectsLegacyService { +export interface SavedObjectsLegacyService { // ATTENTION: these types are incomplete - addScopedSavedObjectsClientWrapperFactory: SavedObjectsClientProvider< - Request - >['addClientWrapperFactory']; - setScopedSavedObjectsClientFactory: SavedObjectsClientProvider['setClientFactory']; - getScopedSavedObjectsClient: SavedObjectsClientProvider['getClient']; + addScopedSavedObjectsClientWrapperFactory: SavedObjectsClientProvider['addClientWrapperFactory']; + setScopedSavedObjectsClientFactory: SavedObjectsClientProvider['setClientFactory']; + getScopedSavedObjectsClient: SavedObjectsClientProvider['getClient']; SavedObjectsClient: typeof SavedObjectsClient; types: string[]; schema: SavedObjectsSchema; @@ -59,6 +57,7 @@ export { SavedObjectsClientWrapperOptions, SavedObjectsErrorHelpers, SavedObjectsClientFactory, + SavedObjectsClientFactoryProvider, } from './lib'; export * from './saved_objects_client'; diff --git a/src/core/server/saved_objects/service/lib/index.ts b/src/core/server/saved_objects/service/lib/index.ts index c26128acc5050..e103120388e35 100644 --- a/src/core/server/saved_objects/service/lib/index.ts +++ b/src/core/server/saved_objects/service/lib/index.ts @@ -26,6 +26,7 @@ export { SavedObjectsClientProvider, SavedObjectsClientProviderOptions, SavedObjectsClientFactory, + SavedObjectsClientFactoryProvider, } from './scoped_client_provider'; export { SavedObjectsErrorHelpers } from './errors'; diff --git a/src/core/server/saved_objects/service/lib/scoped_client_provider.ts b/src/core/server/saved_objects/service/lib/scoped_client_provider.ts index 42f75e2517126..8aadc4f57317c 100644 --- a/src/core/server/saved_objects/service/lib/scoped_client_provider.ts +++ b/src/core/server/saved_objects/service/lib/scoped_client_provider.ts @@ -18,34 +18,44 @@ */ import { PriorityCollection } from './priority_collection'; import { SavedObjectsClientContract } from '../../types'; +import { SavedObjectsRepositoryFactory } from '../../saved_objects_service'; +import { KibanaRequest } from '../../../http'; /** * Options passed to each SavedObjectsClientWrapperFactory to aid in creating the wrapper instance. * @public */ -export interface SavedObjectsClientWrapperOptions { +export interface SavedObjectsClientWrapperOptions { client: SavedObjectsClientContract; - request: Request; + request: KibanaRequest; } /** * Describes the factory used to create instances of Saved Objects Client Wrappers. * @public */ -export type SavedObjectsClientWrapperFactory = ( - options: SavedObjectsClientWrapperOptions +export type SavedObjectsClientWrapperFactory = ( + options: SavedObjectsClientWrapperOptions ) => SavedObjectsClientContract; /** * Describes the factory used to create instances of the Saved Objects Client. * @public */ -export type SavedObjectsClientFactory = ({ +export type SavedObjectsClientFactory = ({ request, }: { - request: Request; + request: KibanaRequest; }) => SavedObjectsClientContract; +/** + * Provider to invoke to retrieve a {@link SavedObjectsClientFactory}. + * @public + */ +export type SavedObjectsClientFactoryProvider = ( + repositoryFactory: SavedObjectsRepositoryFactory +) => SavedObjectsClientFactory; + /** * Options to control the creation of the Saved Objects Client. * @public @@ -57,8 +67,8 @@ export interface SavedObjectsClientProviderOptions { /** * @internal */ -export type ISavedObjectsClientProvider = Pick< - SavedObjectsClientProvider, +export type ISavedObjectsClientProvider = Pick< + SavedObjectsClientProvider, keyof SavedObjectsClientProvider >; @@ -67,26 +77,22 @@ export type ISavedObjectsClientProvider = Pick< * * @internal */ -export class SavedObjectsClientProvider { +export class SavedObjectsClientProvider { private readonly _wrapperFactories = new PriorityCollection<{ id: string; - factory: SavedObjectsClientWrapperFactory; + factory: SavedObjectsClientWrapperFactory; }>(); - private _clientFactory: SavedObjectsClientFactory; - private readonly _originalClientFactory: SavedObjectsClientFactory; - - constructor({ - defaultClientFactory, - }: { - defaultClientFactory: SavedObjectsClientFactory; - }) { + private _clientFactory: SavedObjectsClientFactory; + private readonly _originalClientFactory: SavedObjectsClientFactory; + + constructor({ defaultClientFactory }: { defaultClientFactory: SavedObjectsClientFactory }) { this._originalClientFactory = this._clientFactory = defaultClientFactory; } addClientWrapperFactory( priority: number, id: string, - factory: SavedObjectsClientWrapperFactory + factory: SavedObjectsClientWrapperFactory ): void { if (this._wrapperFactories.has(entry => entry.id === id)) { throw new Error(`wrapper factory with id ${id} is already defined`); @@ -95,7 +101,7 @@ export class SavedObjectsClientProvider { this._wrapperFactories.add(priority, { id, factory }); } - setClientFactory(customClientFactory: SavedObjectsClientFactory) { + setClientFactory(customClientFactory: SavedObjectsClientFactory) { if (this._clientFactory !== this._originalClientFactory) { throw new Error(`custom client factory is already set, unable to replace the current one`); } @@ -104,7 +110,7 @@ export class SavedObjectsClientProvider { } getClient( - request: Request, + request: KibanaRequest, options: SavedObjectsClientProviderOptions = {} ): SavedObjectsClientContract { const client = this._clientFactory({ diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 6e41a4aefba30..060587448642f 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -583,14 +583,14 @@ export interface CoreStart { // @public export class CspConfig implements ICspConfig { // @internal - constructor(rawCspConfig?: Partial>); - // (undocumented) - static readonly DEFAULT: CspConfig; + constructor(env: Env, rawCspConfig?: Partial>); // (undocumented) readonly header: string; // (undocumented) readonly rules: string[]; // (undocumented) + readonly rulesChangedFromDefault: boolean; + // (undocumented) readonly strict: boolean; // (undocumented) readonly warnLegacyBrowsers: boolean; @@ -774,6 +774,7 @@ export type IContextProvider, TContextName export interface ICspConfig { readonly header: string; readonly rules: string[]; + readonly rulesChangedFromDefault: boolean; readonly strict: boolean; readonly warnLegacyBrowsers: boolean; } @@ -879,6 +880,7 @@ export class KibanaRequest; +} + // @public export interface KibanaRequestRoute { // (undocumented) @@ -1491,10 +1498,13 @@ export class SavedObjectsClient { export type SavedObjectsClientContract = Pick; // @public -export type SavedObjectsClientFactory = ({ request, }: { - request: Request; +export type SavedObjectsClientFactory = ({ request, }: { + request: KibanaRequest; }) => SavedObjectsClientContract; +// @public +export type SavedObjectsClientFactoryProvider = (repositoryFactory: SavedObjectsRepositoryFactory) => SavedObjectsClientFactory; + // @public export interface SavedObjectsClientProviderOptions { // (undocumented) @@ -1502,14 +1512,14 @@ export interface SavedObjectsClientProviderOptions { } // @public -export type SavedObjectsClientWrapperFactory = (options: SavedObjectsClientWrapperOptions) => SavedObjectsClientContract; +export type SavedObjectsClientWrapperFactory = (options: SavedObjectsClientWrapperOptions) => SavedObjectsClientContract; // @public -export interface SavedObjectsClientWrapperOptions { +export interface SavedObjectsClientWrapperOptions { // (undocumented) client: SavedObjectsClientContract; // (undocumented) - request: Request; + request: KibanaRequest; } // @public (undocumented) @@ -1745,15 +1755,15 @@ export interface SavedObjectsIncrementCounterOptions extends SavedObjectsBaseOpt } // @internal @deprecated (undocumented) -export interface SavedObjectsLegacyService { +export interface SavedObjectsLegacyService { // Warning: (ae-forgotten-export) The symbol "SavedObjectsClientProvider" needs to be exported by the entry point index.d.ts // // (undocumented) - addScopedSavedObjectsClientWrapperFactory: SavedObjectsClientProvider['addClientWrapperFactory']; + addScopedSavedObjectsClientWrapperFactory: SavedObjectsClientProvider['addClientWrapperFactory']; // (undocumented) getSavedObjectsRepository(...rest: any[]): any; // (undocumented) - getScopedSavedObjectsClient: SavedObjectsClientProvider['getClient']; + getScopedSavedObjectsClient: SavedObjectsClientProvider['getClient']; // (undocumented) importExport: { objectLimit: number; @@ -1766,7 +1776,7 @@ export interface SavedObjectsLegacyService { // (undocumented) schema: SavedObjectsSchema; // (undocumented) - setScopedSavedObjectsClientFactory: SavedObjectsClientProvider['setClientFactory']; + setScopedSavedObjectsClientFactory: SavedObjectsClientProvider['setClientFactory']; // (undocumented) types: string[]; } @@ -1829,6 +1839,12 @@ export class SavedObjectsRepository { update(type: string, id: string, attributes: Partial, options?: SavedObjectsUpdateOptions): Promise>; } +// @public +export interface SavedObjectsRepositoryFactory { + createInternalRepository: (extraTypes?: string[]) => ISavedObjectsRepository; + createScopedRepository: (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository; +} + // @public export interface SavedObjectsResolveImportErrorsOptions { // (undocumented) @@ -1871,14 +1887,14 @@ export class SavedObjectsSerializer { // @public export interface SavedObjectsServiceSetup { - addClientWrapper: (priority: number, id: string, factory: SavedObjectsClientWrapperFactory) => void; - createInternalRepository: (extraTypes?: string[]) => ISavedObjectsRepository; - createScopedRepository: (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository; - setClientFactory: (customClientFactory: SavedObjectsClientFactory) => void; + addClientWrapper: (priority: number, id: string, factory: SavedObjectsClientWrapperFactory) => void; + setClientFactoryProvider: (clientFactoryProvider: SavedObjectsClientFactoryProvider) => void; } // @public export interface SavedObjectsServiceStart { + createInternalRepository: (extraTypes?: string[]) => ISavedObjectsRepository; + createScopedRepository: (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository; getScopedClient: (req: KibanaRequest, options?: SavedObjectsClientProviderOptions) => SavedObjectsClientContract; } diff --git a/src/core/server/server.ts b/src/core/server/server.ts index 89a5bdc4802fd..96adb3bbcd210 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -47,7 +47,7 @@ import { config as uiSettingsConfig } from './ui_settings'; import { mapToObject } from '../utils'; import { ContextService } from './context'; import { RequestHandlerContext } from '.'; -import { InternalCoreSetup } from './internal_types'; +import { InternalCoreSetup, InternalCoreStart } from './internal_types'; import { CapabilitiesService } from './capabilities'; import { UuidService } from './uuid'; @@ -68,6 +68,8 @@ export class Server { private readonly uiSettings: UiSettingsService; private readonly uuid: UuidService; + private coreStart?: InternalCoreStart; + constructor( rawConfigProvider: RawConfigurationProvider, public readonly env: Env, @@ -174,21 +176,24 @@ export class Server { uiSettings: uiSettingsStart, }); - const coreStart = { + this.coreStart = { capabilities: capabilitiesStart, savedObjects: savedObjectsStart, uiSettings: uiSettingsStart, - plugins: pluginsStart, }; + await this.legacy.start({ - core: coreStart, + core: { + ...this.coreStart, + plugins: pluginsStart, + }, plugins: mapToObject(pluginsStart.contracts), }); await this.http.start(); await this.rendering.start(); - return coreStart; + return this.coreStart; } public async stop() { @@ -215,7 +220,7 @@ export class Server { coreId, 'core', async (context, req, res): Promise => { - const savedObjectsClient = coreSetup.savedObjects.getScopedClient(req); + const savedObjectsClient = this.coreStart!.savedObjects.getScopedClient(req); const uiSettingsClient = coreSetup.uiSettings.asScopedToClient(savedObjectsClient); return { diff --git a/src/core/server/ui_settings/create_or_upgrade_saved_config/integration_tests/create_or_upgrade.test.ts b/src/core/server/ui_settings/create_or_upgrade_saved_config/integration_tests/create_or_upgrade.test.ts index f7dbf992e8728..218de8e7acb3a 100644 --- a/src/core/server/ui_settings/create_or_upgrade_saved_config/integration_tests/create_or_upgrade.test.ts +++ b/src/core/server/ui_settings/create_or_upgrade_saved_config/integration_tests/create_or_upgrade.test.ts @@ -27,6 +27,7 @@ import { } from '../../../../../test_utils/kbn_server'; import { createOrUpgradeSavedConfig } from '../create_or_upgrade_saved_config'; import { loggingServiceMock } from '../../../logging/logging_service.mock'; +import { httpServerMock } from '../../../http/http_server.mocks'; const logger = loggingServiceMock.create().get(); describe('createOrUpgradeSavedConfig()', () => { @@ -48,7 +49,9 @@ describe('createOrUpgradeSavedConfig()', () => { kbnServer = kbn.kbnServer; const savedObjects = kbnServer.server.savedObjects; - savedObjectsClient = savedObjects.getScopedSavedObjectsClient({}); + savedObjectsClient = savedObjects.getScopedSavedObjectsClient( + httpServerMock.createKibanaRequest() + ); await savedObjectsClient.bulkCreate([ { diff --git a/src/core/server/ui_settings/integration_tests/lib/servers.ts b/src/core/server/ui_settings/integration_tests/lib/servers.ts index 1abe6dc2d0683..57448541d68c5 100644 --- a/src/core/server/ui_settings/integration_tests/lib/servers.ts +++ b/src/core/server/ui_settings/integration_tests/lib/servers.ts @@ -26,6 +26,7 @@ import { TestUtils, } from '../../../../../test_utils/kbn_server'; import { APICaller } from '../../../elasticsearch/'; +import { httpServerMock } from '../../../http/http_server.mocks'; let servers: TestUtils; let esServer: TestElasticsearchUtils; @@ -83,7 +84,9 @@ export function getServices() { const callCluster = esServer.es.getCallCluster(); const savedObjects = kbnServer.server.savedObjects; - const savedObjectsClient = savedObjects.getScopedSavedObjectsClient({}); + const savedObjectsClient = savedObjects.getScopedSavedObjectsClient( + httpServerMock.createKibanaRequest() + ); const uiSettings = kbnServer.server.uiSettingsServiceFactory({ savedObjectsClient, diff --git a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/get_app_state_mock.ts b/src/core/types/app_category.ts similarity index 50% rename from src/legacy/core_plugins/kibana/public/dashboard/__tests__/get_app_state_mock.ts rename to src/core/types/app_category.ts index d9dea35a8a1c0..83a3693f009b6 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/get_app_state_mock.ts +++ b/src/core/types/app_category.ts @@ -17,32 +17,36 @@ * under the License. */ -import { AppStateClass } from '../legacy_imports'; +/** @public */ /** - * A poor excuse for a mock just to get some basic tests to run in jest without requiring the injector. - * This could be improved if we extract the appState and state classes externally of their angular providers. - * @return {AppStateMock} + * A category definition for nav links to know where to sort them in the left hand nav + * @public */ -export function getAppStateMock(): AppStateClass { - class AppStateMock { - constructor(defaults: any) { - Object.assign(this, defaults); - } +export interface AppCategory { + /** + * Label used for cateogry name. + * Also used as aria-label if one isn't set. + */ + label: string; - on() {} - off() {} - toJSON() { - return ''; - } - save() {} - translateHashToRison(stateHashOrRison: string | string[]) { - return stateHashOrRison; - } - getQueryParamName() { - return ''; - } - } + /** + * If the visual label isn't appropriate for screen readers, + * can override it here + */ + ariaLabel?: string; - return AppStateMock; + /** + * The order that categories will be sorted in + * Prefer large steps between categories to allow for further editing + * (Default categories are in steps of 1000) + */ + order?: number; + + /** + * Define an icon to be used for the category + * If the category is only 1 item, and no icon is defined, will default to the product icon + * Defaults to initials if no icon is defined + */ + euiIconType?: string; } diff --git a/src/core/types/index.ts b/src/core/types/index.ts index d01b514c770a7..7ddb6b0d8dfbb 100644 --- a/src/core/types/index.ts +++ b/src/core/types/index.ts @@ -23,3 +23,4 @@ */ export * from './core_service'; export * from './capabilities'; +export * from './app_category'; diff --git a/src/legacy/ui/public/registry/vis_editor_types.js b/src/core/utils/default_app_categories.ts similarity index 53% rename from src/legacy/ui/public/registry/vis_editor_types.js rename to src/core/utils/default_app_categories.ts index 8009663b69c4a..3e3cc2fef2a22 100644 --- a/src/legacy/ui/public/registry/vis_editor_types.js +++ b/src/core/utils/default_app_categories.ts @@ -17,10 +17,32 @@ * under the License. */ -import { uiRegistry } from './_registry'; +import { i18n } from '@kbn/i18n'; -export const VisEditorTypesRegistryProvider = uiRegistry({ - name: 'visEditorTypes', - index: ['name'], - order: ['title'], +/** @internal */ +export const DEFAULT_APP_CATEGORIES = Object.freeze({ + analyze: { + label: i18n.translate('core.ui.analyzeNavList.label', { + defaultMessage: 'Analyze', + }), + order: 1000, + }, + observability: { + label: i18n.translate('core.ui.observabilityNavList.label', { + defaultMessage: 'Observability', + }), + order: 2000, + }, + security: { + label: i18n.translate('core.ui.securityNavList.label', { + defaultMessage: 'Security', + }), + order: 3000, + }, + management: { + label: i18n.translate('core.ui.managementNavList.label', { + defaultMessage: 'Management', + }), + euiIconType: 'managementApp', + }, }); diff --git a/src/core/utils/index.ts b/src/core/utils/index.ts index 7c8ed481c0a7d..7317c222d3bc3 100644 --- a/src/core/utils/index.ts +++ b/src/core/utils/index.ts @@ -28,3 +28,4 @@ export * from './pick'; export * from './promise'; export * from './url'; export * from './unset'; +export * from './default_app_categories'; diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker index 31a5a4c13a4be..7a82ca0b1609c 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker +++ b/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker @@ -59,7 +59,6 @@ kibana_vars=( path.data pid.file regionmap - regionmap.includeElasticMapsService server.basePath server.customResponseHeaders server.defaultRoute @@ -94,6 +93,7 @@ kibana_vars=( timelion.enabled vega.enableExternalUrls xpack.apm.enabled + xpack.apm.serviceMapEnabled xpack.apm.ui.enabled xpack.apm.ui.maxTraceItems apm_oss.apmAgentConfigurationIndex diff --git a/src/dev/build/tasks/os_packages/docker_generator/run.js b/src/dev/build/tasks/os_packages/docker_generator/run.js index 7424b6f01b82b..b6dab43887f14 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/run.js +++ b/src/dev/build/tasks/os_packages/docker_generator/run.js @@ -42,6 +42,7 @@ export async function runDockerGenerator(config, log, build, ubi = false) { const versionTag = config.getBuildVersion(); const artifactTarball = `kibana${imageFlavor}-${versionTag}-linux-x86_64.tar.gz`; const artifactsDir = config.resolveFromTarget('.'); + const dockerBuildDate = new Date().toISOString(); // That would produce oss, default and default-ubi7 const dockerBuildDir = config.resolveFromRepo( 'build', @@ -62,6 +63,7 @@ export async function runDockerGenerator(config, log, build, ubi = false) { dockerOutputDir, baseOSImage, ubiImageFlavor, + dockerBuildDate, }; // Verify if we have the needed kibana target in order diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.js b/src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.js index 25ae71856c12d..6ad34c439a233 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.js +++ b/src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.js @@ -26,6 +26,7 @@ function generator({ usePublicArtifact, baseOSImage, ubiImageFlavor, + dockerBuildDate, }) { const copyArtifactTarballInsideDockerOptFolder = () => { if (usePublicArtifact) { @@ -115,6 +116,8 @@ function generator({ org.label-schema.url="https://www.elastic.co/products/kibana" \\ org.label-schema.vcs-url="https://github.com/elastic/kibana" \\ org.label-schema.license="${license}" \\ + org.label-schema.usage="https://www.elastic.co/guide/en/kibana/index.html" \\ + org.label-schema.build-date="${dockerBuildDate}" \\ license="${license}" ENTRYPOINT ["/usr/local/bin/dumb-init", "--"] diff --git a/src/legacy/core_plugins/console/public/np_ready/application/styles/_app.scss b/src/legacy/core_plugins/console/public/np_ready/application/styles/_app.scss index c69440225236b..b89a43f7455a5 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/styles/_app.scss +++ b/src/legacy/core_plugins/console/public/np_ready/application/styles/_app.scss @@ -27,6 +27,9 @@ // Required on IE11 to render ace editor correctly after first input. position: relative; + // Give the aria selection border priority when the divider is selected + z-index: 0; + &__spinner { width: 100%; } @@ -35,6 +38,8 @@ .conApp__output { display: flex; flex: 1 1 1px; + // Give the aria selection border priority when the divider is selected + z-index: -1; } .conApp__textAreaLabelHack, diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/filter_state_manager.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/filter_state_manager.ts index 61821b7ad45e9..ad5c91d2e19de 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_manager/filter_state_manager.ts +++ b/src/legacy/core_plugins/data/public/filter/filter_manager/filter_state_manager.ts @@ -18,10 +18,18 @@ */ import _ from 'lodash'; +import { Subscription } from 'rxjs'; import { State } from 'ui/state_management/state'; import { FilterManager, esFilters } from '../../../../../../plugins/data/public'; -type GetAppStateFunc = () => State | undefined | null; +import { + compareFilters, + COMPARE_ALL_OPTIONS, + // this whole file will soon be deprecated by new state management. + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../../../plugins/data/public/query/filter_manager/lib/compare_filters'; + +type GetAppStateFunc = () => { filters?: esFilters.Filter[]; save?: () => void } | undefined | null; /** * FilterStateManager is responsible for watching for filter changes @@ -29,10 +37,12 @@ type GetAppStateFunc = () => State | undefined | null; * back to the URL. **/ export class FilterStateManager { + private filterManagerUpdatesSubscription: Subscription; + filterManager: FilterManager; globalState: State; getAppState: GetAppStateFunc; - interval: NodeJS.Timeout | undefined; + interval: number | undefined; constructor(globalState: State, getAppState: GetAppStateFunc, filterManager: FilterManager) { this.getAppState = getAppState; @@ -41,7 +51,7 @@ export class FilterStateManager { this.watchFilterState(); - this.filterManager.getUpdates$().subscribe(() => { + this.filterManagerUpdatesSubscription = this.filterManager.getUpdates$().subscribe(() => { this.updateAppState(); }); } @@ -50,12 +60,13 @@ export class FilterStateManager { if (this.interval) { clearInterval(this.interval); } + this.filterManagerUpdatesSubscription.unsubscribe(); } private watchFilterState() { // This is a temporary solution to remove rootscope. // Moving forward, state should provide observable subscriptions. - this.interval = setInterval(() => { + this.interval = window.setInterval(() => { const appState = this.getAppState(); const stateUndefined = !appState || !this.globalState; if (stateUndefined) return; @@ -63,8 +74,16 @@ export class FilterStateManager { const globalFilters = this.globalState.filters || []; const appFilters = (appState && appState.filters) || []; - const globalFilterChanged = !_.isEqual(this.filterManager.getGlobalFilters(), globalFilters); - const appFilterChanged = !_.isEqual(this.filterManager.getAppFilters(), appFilters); + const globalFilterChanged = !compareFilters( + this.filterManager.getGlobalFilters(), + globalFilters, + COMPARE_ALL_OPTIONS + ); + const appFilterChanged = !compareFilters( + this.filterManager.getAppFilters(), + appFilters, + COMPARE_ALL_OPTIONS + ); const filterStateChanged = globalFilterChanged || appFilterChanged; if (!filterStateChanged) return; @@ -80,7 +99,7 @@ export class FilterStateManager { private saveState() { const appState = this.getAppState(); - if (appState) appState.save(); + if (appState && appState.save) appState.save(); this.globalState.save(); } diff --git a/src/legacy/core_plugins/data/public/plugin.ts b/src/legacy/core_plugins/data/public/plugin.ts index 893e477b38583..5329702348207 100644 --- a/src/legacy/core_plugins/data/public/plugin.ts +++ b/src/legacy/core_plugins/data/public/plugin.ts @@ -20,15 +20,24 @@ import { CoreSetup, CoreStart, Plugin } from 'kibana/public'; import { SearchService, SearchStart } from './search'; import { DataPublicPluginStart } from '../../../../plugins/data/public'; +import { ExpressionsSetup } from '../../../../plugins/expressions/public'; import { setFieldFormats, setNotifications, setIndexPatterns, setQueryService, + setSearchService, + setUiSettings, + setInjectedMetadata, + setHttp, // eslint-disable-next-line @kbn/eslint/no-restricted-paths } from '../../../../plugins/data/public/services'; +export interface DataPluginSetupDependencies { + expressions: ExpressionsSetup; +} + export interface DataPluginStartDependencies { data: DataPublicPluginStart; } @@ -54,18 +63,24 @@ export interface DataStart { * or static code. */ -export class DataPlugin implements Plugin { +export class DataPlugin + implements Plugin { private readonly search = new SearchService(); - public setup(core: CoreSetup) {} + public setup(core: CoreSetup) { + setInjectedMetadata(core.injectedMetadata); + } public start(core: CoreStart, { data }: DataPluginStartDependencies): DataStart { // This is required for when Angular code uses Field and FieldList. setFieldFormats(data.fieldFormats); setQueryService(data.query); + setSearchService(data.search); setIndexPatterns(data.indexPatterns); setFieldFormats(data.fieldFormats); setNotifications(core.notifications); + setUiSettings(core.uiSettings); + setHttp(core.http); return { search: this.search.start(core), diff --git a/src/legacy/ui/public/inspector/build_tabular_inspector_data.ts b/src/legacy/core_plugins/data/public/search/expressions/build_tabular_inspector_data.ts similarity index 95% rename from src/legacy/ui/public/inspector/build_tabular_inspector_data.ts rename to src/legacy/core_plugins/data/public/search/expressions/build_tabular_inspector_data.ts index b09ed60e7186f..6e6d2a15fa2ac 100644 --- a/src/legacy/ui/public/inspector/build_tabular_inspector_data.ts +++ b/src/legacy/core_plugins/data/public/search/expressions/build_tabular_inspector_data.ts @@ -19,8 +19,8 @@ import { set } from 'lodash'; // @ts-ignore -import { createFilter } from '../../../core_plugins/visualizations/public'; -import { FormattedData } from './adapters'; +import { createFilter } from '../../../../visualizations/public'; +import { FormattedData } from '../../../../../../plugins/inspector/public'; interface Column { id: string; diff --git a/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts b/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts index 4ec4dbd7f88d6..889c747c9a62e 100644 --- a/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts +++ b/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts @@ -34,14 +34,8 @@ import { getTime, FilterManager, } from '../../../../../../plugins/data/public'; -import { - SearchSource, - ISearchSource, - getRequestInspectorStats, - getResponseInspectorStats, -} from '../../../../../ui/public/courier'; -import { buildTabularInspectorData } from '../../../../../ui/public/inspector/build_tabular_inspector_data'; +import { buildTabularInspectorData } from './build_tabular_inspector_data'; import { calculateObjectHash } from '../../../../visualizations/public'; // @ts-ignore import { tabifyAggResponse } from '../../../../../ui/public/agg_response/tabify/tabify'; @@ -49,6 +43,8 @@ import { PersistedState } from '../../../../../ui/public/persisted_state'; import { Adapters } from '../../../../../../plugins/inspector/public'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { getQueryService, getIndexPatterns } from '../../../../../../plugins/data/public/services'; +import { ISearchSource, getRequestInspectorStats, getResponseInspectorStats } from '../..'; +import { SearchSource } from '../search_source'; export interface RequestHandlerParams { searchSource: ISearchSource; diff --git a/src/legacy/core_plugins/data/public/search/fetch/call_client.test.ts b/src/legacy/core_plugins/data/public/search/fetch/call_client.test.ts index 74c87d77dd4fd..24a36c9db9df7 100644 --- a/src/legacy/core_plugins/data/public/search/fetch/call_client.test.ts +++ b/src/legacy/core_plugins/data/public/search/fetch/call_client.test.ts @@ -76,7 +76,7 @@ describe('callClient', () => { test('Passes the additional arguments it is given to the search strategy', () => { const searchRequests = [{ _searchStrategyId: 0 }]; - const args = { es: {}, config: {}, esShardTimeout: 0 } as FetchHandlers; + const args = { searchService: {}, config: {}, esShardTimeout: 0 } as FetchHandlers; callClient(searchRequests, [], args); diff --git a/src/legacy/core_plugins/data/public/search/fetch/call_client.ts b/src/legacy/core_plugins/data/public/search/fetch/call_client.ts index 43da27f941e4e..ad18775d5f144 100644 --- a/src/legacy/core_plugins/data/public/search/fetch/call_client.ts +++ b/src/legacy/core_plugins/data/public/search/fetch/call_client.ts @@ -26,7 +26,7 @@ import { SearchRequest } from '../types'; export function callClient( searchRequests: SearchRequest[], requestsOptions: FetchOptions[] = [], - { es, config, esShardTimeout }: FetchHandlers + fetchHandlers: FetchHandlers ) { // Correlate the options with the request that they're associated with const requestOptionEntries: Array<[ @@ -53,9 +53,7 @@ export function callClient( // then an error would have been thrown above const { searching, abort } = searchStrategy!.search({ searchRequests: requests, - es, - config, - esShardTimeout, + ...fetchHandlers, }); requests.forEach((request, i) => { diff --git a/src/legacy/core_plugins/data/public/search/fetch/fetch_soon.ts b/src/legacy/core_plugins/data/public/search/fetch/fetch_soon.ts index 75de85e02a1a2..4830464047ad6 100644 --- a/src/legacy/core_plugins/data/public/search/fetch/fetch_soon.ts +++ b/src/legacy/core_plugins/data/public/search/fetch/fetch_soon.ts @@ -28,10 +28,10 @@ import { SearchRequest, SearchResponse } from '../types'; export async function fetchSoon( request: SearchRequest, options: FetchOptions, - { es, config, esShardTimeout }: FetchHandlers + fetchHandlers: FetchHandlers ) { - const msToDelay = config.get('courier:batchSearches') ? 50 : 0; - return delayedFetch(request, options, { es, config, esShardTimeout }, msToDelay); + const msToDelay = fetchHandlers.config.get('courier:batchSearches') ? 50 : 0; + return delayedFetch(request, options, fetchHandlers, msToDelay); } /** @@ -64,7 +64,7 @@ let fetchInProgress: Promise | null = null; async function delayedFetch( request: SearchRequest, options: FetchOptions, - { es, config, esShardTimeout }: FetchHandlers, + fetchHandlers: FetchHandlers, ms: number ) { const i = requestsToFetch.length; @@ -73,7 +73,7 @@ async function delayedFetch( const responses = await (fetchInProgress = fetchInProgress || delay(() => { - const response = callClient(requestsToFetch, requestOptions, { es, config, esShardTimeout }); + const response = callClient(requestsToFetch, requestOptions, fetchHandlers); requestsToFetch = []; requestOptions = []; fetchInProgress = null; diff --git a/src/legacy/core_plugins/data/public/search/fetch/types.ts b/src/legacy/core_plugins/data/public/search/fetch/types.ts index 0887a1f84c7c8..fba14119d83c3 100644 --- a/src/legacy/core_plugins/data/public/search/fetch/types.ts +++ b/src/legacy/core_plugins/data/public/search/fetch/types.ts @@ -17,6 +17,7 @@ * under the License. */ +import { ISearchStart } from 'src/plugins/data/public'; import { IUiSettingsClient } from '../../../../../../core/public'; import { SearchRequest, SearchResponse } from '../types'; @@ -35,7 +36,7 @@ export interface FetchOptions { } export interface FetchHandlers { - es: ApiCaller; + searchService: ISearchStart; config: IUiSettingsClient; esShardTimeout: number; } diff --git a/src/legacy/core_plugins/data/public/search/search_source/search_source.test.ts b/src/legacy/core_plugins/data/public/search/search_source/search_source.test.ts index 28f8dba9a75de..ebeee60b67c8a 100644 --- a/src/legacy/core_plugins/data/public/search/search_source/search_source.test.ts +++ b/src/legacy/core_plugins/data/public/search/search_source/search_source.test.ts @@ -19,19 +19,34 @@ import { SearchSource } from '../search_source'; import { IndexPattern } from '../../../../../../plugins/data/public'; - -jest.mock('ui/new_platform'); +import { + setSearchService, + setUiSettings, + setInjectedMetadata, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../../../plugins/data/public/services'; + +import { + injectedMetadataServiceMock, + uiSettingsServiceMock, +} from '../../../../../../core/public/mocks'; + +setUiSettings(uiSettingsServiceMock.createStartContract()); +setInjectedMetadata(injectedMetadataServiceMock.createSetupContract()); +setSearchService({ + search: jest.fn(), + __LEGACY: { + esClient: { + search: jest.fn(), + msearch: jest.fn(), + }, + }, +}); jest.mock('../fetch', () => ({ fetchSoon: jest.fn().mockResolvedValue({}), })); -jest.mock('ui/chrome', () => ({ - dangerouslyGetActiveInjector: () => ({ - get: jest.fn(), - }), -})); - const getComputedFields = () => ({ storedFields: [], scriptFields: [], diff --git a/src/legacy/core_plugins/data/public/search/search_source/search_source.ts b/src/legacy/core_plugins/data/public/search/search_source/search_source.ts index 6efcae4d4b88d..e977db713ebaa 100644 --- a/src/legacy/core_plugins/data/public/search/search_source/search_source.ts +++ b/src/legacy/core_plugins/data/public/search/search_source/search_source.ts @@ -70,8 +70,6 @@ */ import _ from 'lodash'; -import { npSetup } from 'ui/new_platform'; -import chrome from 'ui/chrome'; import { normalizeSortRequest } from './normalize_sort_request'; import { fetchSoon } from '../fetch'; import { fieldWildcardFilter } from '../../../../../../plugins/kibana_utils/public'; @@ -79,10 +77,14 @@ import { getHighlightRequest, esFilters, esQuery } from '../../../../../../plugi import { RequestFailure } from '../fetch/errors'; import { filterDocvalueFields } from './filter_docvalue_fields'; import { SearchSourceOptions, SearchSourceFields, SearchRequest } from './types'; -import { FetchOptions, ApiCaller } from '../fetch/types'; +import { FetchOptions } from '../fetch/types'; -const esShardTimeout = npSetup.core.injectedMetadata.getInjectedVar('esShardTimeout') as number; -const config = npSetup.core.uiSettings; +import { + getSearchService, + getUiSettings, + getInjectedMetadata, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../../../plugins/data/public/services'; export type ISearchSource = Pick; @@ -192,21 +194,23 @@ export class SearchSource { * @async */ async fetch(options: FetchOptions = {}) { - const $injector = await chrome.dangerouslyGetActiveInjector(); - const es = $injector.get('es') as ApiCaller; - await this.requestIsStarting(options); const searchRequest = await this.flatten(); this.history = [searchRequest]; + const esShardTimeout = getInjectedMetadata().getInjectedVar('esShardTimeout') as number; const response = await fetchSoon( searchRequest, { ...(this.searchStrategyId && { searchStrategyId: this.searchStrategyId }), ...options, }, - { es, config, esShardTimeout } + { + searchService: getSearchService(), + config: getUiSettings(), + esShardTimeout, + } ); if (response.error) { @@ -313,7 +317,11 @@ export class SearchSource { case 'source': return addToBody('_source', val); case 'sort': - const sort = normalizeSortRequest(val, this.getField('index'), config.get('sort:options')); + const sort = normalizeSortRequest( + val, + this.getField('index'), + getUiSettings().get('sort:options') + ); return addToBody(key, sort); default: return addToBody(key, val); @@ -359,7 +367,7 @@ export class SearchSource { if (body._source) { // exclude source fields for this index pattern specified by the user - const filter = fieldWildcardFilter(body._source.excludes, config.get('metaFields')); + const filter = fieldWildcardFilter(body._source.excludes, getUiSettings().get('metaFields')); body.docvalue_fields = body.docvalue_fields.filter((docvalueField: any) => filter(docvalueField.field) ); @@ -377,11 +385,11 @@ export class SearchSource { _.set(body, '_source.includes', remainingFields); } - const esQueryConfigs = esQuery.getEsQueryConfig(config); + const esQueryConfigs = esQuery.getEsQueryConfig(getUiSettings()); body.query = esQuery.buildEsQuery(index, query, filters, esQueryConfigs); if (highlightAll && body.query) { - body.highlight = getHighlightRequest(body.query, config.get('doc_table:highlight')); + body.highlight = getHighlightRequest(body.query, getUiSettings().get('doc_table:highlight')); delete searchRequest.highlightAll; } diff --git a/src/legacy/core_plugins/data/public/search/search_strategy/default_search_strategy.test.ts b/src/legacy/core_plugins/data/public/search/search_strategy/default_search_strategy.test.ts index 0ec6a6c2e143e..8caf20c50cd3a 100644 --- a/src/legacy/core_plugins/data/public/search/search_strategy/default_search_strategy.test.ts +++ b/src/legacy/core_plugins/data/public/search/search_strategy/default_search_strategy.test.ts @@ -37,9 +37,16 @@ const searchMockResponse: any = Promise.resolve([]); searchMockResponse.abort = jest.fn(); const searchMock = jest.fn().mockReturnValue(searchMockResponse); +const newSearchMockResponse: any = Promise.resolve([]); +newSearchMockResponse.abort = jest.fn(); +const newSearchMock = jest.fn().mockReturnValue({ + toPromise: () => searchMockResponse, +}); + describe('defaultSearchStrategy', function() { describe('search', function() { let searchArgs: MockedKeys>; + let es: any; beforeEach(() => { msearchMockResponse.abort.mockClear(); @@ -55,17 +62,24 @@ describe('defaultSearchStrategy', function() { }, ], esShardTimeout: 0, - es: { - msearch: msearchMock, - search: searchMock, + searchService: { + search: newSearchMock, + __LEGACY: { + esClient: { + search: searchMock, + msearch: msearchMock, + }, + }, }, }; + + es = searchArgs.searchService.__LEGACY.esClient; }); test('does not send max_concurrent_shard_requests by default', async () => { const config = getConfigStub({ 'courier:batchSearches': true }); await search({ ...searchArgs, config }); - expect(searchArgs.es.msearch.mock.calls[0][0].max_concurrent_shard_requests).toBe(undefined); + expect(es.msearch.mock.calls[0][0].max_concurrent_shard_requests).toBe(undefined); }); test('allows configuration of max_concurrent_shard_requests', async () => { @@ -74,13 +88,13 @@ describe('defaultSearchStrategy', function() { 'courier:maxConcurrentShardRequests': 42, }); await search({ ...searchArgs, config }); - expect(searchArgs.es.msearch.mock.calls[0][0].max_concurrent_shard_requests).toBe(42); + expect(es.msearch.mock.calls[0][0].max_concurrent_shard_requests).toBe(42); }); test('should set rest_total_hits_as_int to true on a request', async () => { const config = getConfigStub({ 'courier:batchSearches': true }); await search({ ...searchArgs, config }); - expect(searchArgs.es.msearch.mock.calls[0][0]).toHaveProperty('rest_total_hits_as_int', true); + expect(es.msearch.mock.calls[0][0]).toHaveProperty('rest_total_hits_as_int', true); }); test('should set ignore_throttled=false when including frozen indices', async () => { @@ -89,7 +103,7 @@ describe('defaultSearchStrategy', function() { 'search:includeFrozen': true, }); await search({ ...searchArgs, config }); - expect(searchArgs.es.msearch.mock.calls[0][0]).toHaveProperty('ignore_throttled', false); + expect(es.msearch.mock.calls[0][0]).toHaveProperty('ignore_throttled', false); }); test('should properly call abort with msearch', () => { @@ -100,12 +114,18 @@ describe('defaultSearchStrategy', function() { expect(msearchMockResponse.abort).toHaveBeenCalled(); }); - test('should properly abort with search', async () => { - const config = getConfigStub({ - 'courier:batchSearches': false, - }); + test('should call new search service', () => { + const config = getConfigStub(); + search({ ...searchArgs, config }); + expect(searchMock).toHaveBeenCalled(); + expect(newSearchMock).toHaveBeenCalledTimes(0); + }); + + test('should properly abort with new search service', async () => { + const abortSpy = jest.spyOn(AbortController.prototype, 'abort'); + const config = getConfigStub({}); search({ ...searchArgs, config }).abort(); - expect(searchMockResponse.abort).toHaveBeenCalled(); + expect(abortSpy).toHaveBeenCalled(); }); }); }); diff --git a/src/legacy/core_plugins/data/public/search/search_strategy/default_search_strategy.ts b/src/legacy/core_plugins/data/public/search/search_strategy/default_search_strategy.ts index 9bfa1df71aa81..39789504de0a7 100644 --- a/src/legacy/core_plugins/data/public/search/search_strategy/default_search_strategy.ts +++ b/src/legacy/core_plugins/data/public/search/search_strategy/default_search_strategy.ts @@ -38,7 +38,14 @@ export const defaultSearchStrategy: SearchStrategyProvider = { }, }; -function msearch({ searchRequests, es, config, esShardTimeout }: SearchStrategySearchParams) { +// @deprecated +function msearch({ + searchRequests, + searchService, + config, + esShardTimeout, +}: SearchStrategySearchParams) { + const es = searchService.__LEGACY.esClient; const inlineRequests = searchRequests.map(({ index, body, search_type: searchType }) => { const inlineHeader = { index: index.title || index, @@ -57,19 +64,39 @@ function msearch({ searchRequests, es, config, esShardTimeout }: SearchStrategyS ...getMSearchParams(config), body: `${inlineRequests.join('\n')}\n`, }); + return { - searching: searching.then(({ responses }) => responses), + searching: searching.then(({ responses }: any) => responses), abort: searching.abort, }; } -function search({ searchRequests, es, config, esShardTimeout }: SearchStrategySearchParams) { +function search({ + searchRequests, + searchService, + config, + esShardTimeout, +}: SearchStrategySearchParams) { const abortController = new AbortController(); const searchParams = getSearchParams(config, esShardTimeout); + const es = searchService.__LEGACY.esClient; const promises = searchRequests.map(({ index, body }) => { const searching = es.search({ index: index.title || index, body, ...searchParams }); abortController.signal.addEventListener('abort', searching.abort); - return searching.catch(({ response }) => JSON.parse(response)); + return searching.catch(({ response }: any) => JSON.parse(response)); + /* + * Once #44302 is resolved, replace the old implementation with this one - + * const params = { + * index: index.title || index, + * body, + * ...searchParams, + * }; + * const { signal } = abortController; + * return searchService + * .search({ params }, { signal }) + * .toPromise() + * .then(({ rawResponse }) => rawResponse); + */ }); return { searching: Promise.all(promises), diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.test.tsx b/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.test.tsx index 4e7c9bafbf510..3419d773bd09e 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.test.tsx +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.test.tsx @@ -21,8 +21,7 @@ import React from 'react'; import { shallowWithIntl, mountWithIntl } from 'test_utils/enzyme_helpers'; // @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; -import { getDepsMock } from './__tests__/get_deps_mock'; -import { getIndexPatternMock } from './__tests__/get_index_pattern_mock'; +import { getDepsMock, getIndexPatternMock } from '../../test_utils'; import { ControlsTab, ControlsTabUiProps } from './controls_tab'; const indexPatternsMock = { diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.test.tsx b/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.test.tsx index f08a68b0da179..942517e24514e 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.test.tsx +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.test.tsx @@ -25,11 +25,10 @@ import { shallow } from 'enzyme'; import { findTestSubject } from '@elastic/eui/lib/test'; import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { getDepsMock } from './__tests__/get_deps_mock'; -import { getIndexPatternMock } from './__tests__/get_index_pattern_mock'; +import { getIndexPatternMock } from '../../test_utils/get_index_pattern_mock'; import { ListControlEditor } from './list_control_editor'; import { ControlParams } from '../../editor_utils'; -import { updateComponent } from './__tests__/update_component'; +import { getDepsMock, updateComponent } from '../../test_utils'; const controlParamsBase: ControlParams = { id: '1', diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.test.tsx b/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.test.tsx index 55c4c71ce430b..8f564e09bf4b2 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.test.tsx +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.test.tsx @@ -24,12 +24,11 @@ import { mountWithIntl } from 'test_utils/enzyme_helpers'; // @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; -import { getIndexPatternMock } from './__tests__/get_index_pattern_mock'; import { RangeControlEditor } from './range_control_editor'; import { ControlParams } from '../../editor_utils'; -import { getDepsMock } from './__tests__/get_deps_mock'; -import { updateComponent } from './__tests__/update_component'; +import { getDepsMock } from '../../test_utils/get_deps_mock'; +import { getIndexPatternMock, updateComponent } from '../../test_utils'; const controlParams: ControlParams = { id: '1', diff --git a/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.test.ts b/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.test.ts index 2420907727638..e6426e5a4c69d 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.test.ts +++ b/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.test.ts @@ -19,8 +19,7 @@ import { listControlFactory, ListControl } from './list_control_factory'; import { ControlParams, CONTROL_TYPES } from '../editor_utils'; -import { getDepsMock } from '../components/editor/__tests__/get_deps_mock'; -import { getSearchSourceMock } from '../components/editor/__tests__/get_search_service_mock'; +import { getDepsMock, getSearchSourceMock } from '../test_utils'; const MockSearchSource = getSearchSourceMock(); const deps = getDepsMock(); diff --git a/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.test.ts b/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.test.ts index 5328aeb6c6a47..32df9de8ac983 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.test.ts +++ b/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.test.ts @@ -19,8 +19,7 @@ import { rangeControlFactory } from './range_control_factory'; import { ControlParams, CONTROL_TYPES } from '../editor_utils'; -import { getSearchSourceMock } from '../components/editor/__tests__/get_search_service_mock'; -import { getDepsMock } from '../components/editor/__tests__/get_deps_mock'; +import { getDepsMock, getSearchSourceMock } from '../test_utils'; const deps = getDepsMock(); diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_deps_mock.tsx b/src/legacy/core_plugins/input_control_vis/public/test_utils/get_deps_mock.tsx similarity index 97% rename from src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_deps_mock.tsx rename to src/legacy/core_plugins/input_control_vis/public/test_utils/get_deps_mock.tsx index a9505aefa3113..7b10eea3dde44 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_deps_mock.tsx +++ b/src/legacy/core_plugins/input_control_vis/public/test_utils/get_deps_mock.tsx @@ -19,7 +19,7 @@ import React from 'react'; import { FieldList } from 'src/plugins/data/public'; -import { InputControlVisDependencies } from '../../../plugin'; +import { InputControlVisDependencies } from '../plugin'; const fields: FieldList = [] as any; fields.push({ name: 'myField' } as any); diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_index_pattern_mock.ts b/src/legacy/core_plugins/input_control_vis/public/test_utils/get_index_pattern_mock.ts similarity index 94% rename from src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_index_pattern_mock.ts rename to src/legacy/core_plugins/input_control_vis/public/test_utils/get_index_pattern_mock.ts index 638dd7170cb8d..da4663dc33429 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_index_pattern_mock.ts +++ b/src/legacy/core_plugins/input_control_vis/public/test_utils/get_index_pattern_mock.ts @@ -17,7 +17,7 @@ * under the License. */ -import { IIndexPattern } from '../../../../../../../plugins/data/public'; +import { IIndexPattern } from 'src/plugins/data/public'; /** * Returns forced **Partial** IndexPattern for use in tests diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_index_patterns_mock.ts b/src/legacy/core_plugins/input_control_vis/public/test_utils/get_index_patterns_mock.ts similarity index 100% rename from src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_index_patterns_mock.ts rename to src/legacy/core_plugins/input_control_vis/public/test_utils/get_index_patterns_mock.ts diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_search_service_mock.ts b/src/legacy/core_plugins/input_control_vis/public/test_utils/get_search_service_mock.ts similarity index 96% rename from src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_search_service_mock.ts rename to src/legacy/core_plugins/input_control_vis/public/test_utils/get_search_service_mock.ts index 9da47bedcc784..94a460086e9da 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/get_search_service_mock.ts +++ b/src/legacy/core_plugins/input_control_vis/public/test_utils/get_search_service_mock.ts @@ -17,7 +17,7 @@ * under the License. */ -import { SearchSource } from '../../../legacy_imports'; +import { SearchSource } from '../legacy_imports'; export const getSearchSourceMock = (esSearchResponse?: any): SearchSource => jest.fn().mockImplementation(() => ({ diff --git a/src/legacy/server/url_shortening/routes/create_routes.js b/src/legacy/core_plugins/input_control_vis/public/test_utils/index.ts similarity index 73% rename from src/legacy/server/url_shortening/routes/create_routes.js rename to src/legacy/core_plugins/input_control_vis/public/test_utils/index.ts index 9540e7441a268..b474d39a54074 100644 --- a/src/legacy/server/url_shortening/routes/create_routes.js +++ b/src/legacy/core_plugins/input_control_vis/public/test_utils/index.ts @@ -17,11 +17,8 @@ * under the License. */ -import { shortUrlLookupProvider } from './lib/short_url_lookup'; -import { createGotoRoute } from './goto'; - -export function createRoutes(server) { - const shortUrlLookup = shortUrlLookupProvider(server); - - server.route(createGotoRoute({ server, shortUrlLookup })); -} +export { getDepsMock } from './get_deps_mock'; +export { getIndexPatternMock } from './get_index_pattern_mock'; +export { getIndexPatternsMock } from './get_index_patterns_mock'; +export { getSearchSourceMock } from './get_search_service_mock'; +export { updateComponent } from './update_component'; diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/update_component.ts b/src/legacy/core_plugins/input_control_vis/public/test_utils/update_component.ts similarity index 100% rename from src/legacy/core_plugins/input_control_vis/public/components/editor/__tests__/update_component.ts rename to src/legacy/core_plugins/input_control_vis/public/test_utils/update_component.ts diff --git a/src/legacy/core_plugins/kibana/common/lib/__tests__/convert_pattern_and_ingest_name.js b/src/legacy/core_plugins/kibana/common/lib/__tests__/convert_pattern_and_ingest_name.js deleted file mode 100644 index 2d138df67ebf9..0000000000000 --- a/src/legacy/core_plugins/kibana/common/lib/__tests__/convert_pattern_and_ingest_name.js +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import { patternToIngest, ingestToPattern } from '../convert_pattern_and_ingest_name'; - -describe('convertPatternAndTemplateName', function() { - describe('ingestToPattern', function() { - it("should convert an index template's name to its matching index pattern's title", function() { - expect(ingestToPattern('kibana-logstash-*')).to.be('logstash-*'); - }); - - it("should throw an error if the template name isn't a valid kibana namespaced name", function() { - expect(ingestToPattern) - .withArgs('logstash-*') - .to.throwException('not a valid kibana namespaced template name'); - expect(ingestToPattern) - .withArgs('') - .to.throwException(/not a valid kibana namespaced template name/); - }); - }); - - describe('patternToIngest', function() { - it("should convert an index pattern's title to its matching index template's name", function() { - expect(patternToIngest('logstash-*')).to.be('kibana-logstash-*'); - }); - - it('should throw an error if the pattern is empty', function() { - expect(patternToIngest) - .withArgs('') - .to.throwException(/pattern must not be empty/); - }); - }); -}); diff --git a/src/legacy/core_plugins/kibana/common/lib/convert_pattern_and_ingest_name.js b/src/legacy/core_plugins/kibana/common/lib/convert_pattern_and_ingest_name.js deleted file mode 100644 index 216dc7d882a2c..0000000000000 --- a/src/legacy/core_plugins/kibana/common/lib/convert_pattern_and_ingest_name.js +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -// To avoid index template naming collisions the index pattern creation API -// namespaces template names by prepending 'kibana-' to the matching pattern's title. -// e.g. a pattern with title `logstash-*` will have a matching template named `kibana-logstash-*`. -// This module provides utility functions for easily converting between template and pattern names. - -export function ingestToPattern(templateName) { - if (templateName.indexOf('kibana-') === -1) { - throw new Error('not a valid kibana namespaced template name'); - } - - return templateName.slice(templateName.indexOf('-') + 1); -} - -export function patternToIngest(patternName) { - if (patternName === '') { - throw new Error('pattern must not be empty'); - } - - return `kibana-${patternName.toLowerCase()}`; -} diff --git a/src/legacy/core_plugins/kibana/common/tutorials/tutorial_schema.js b/src/legacy/core_plugins/kibana/common/tutorials/tutorial_schema.js deleted file mode 100644 index 1d04e0d83fa31..0000000000000 --- a/src/legacy/core_plugins/kibana/common/tutorials/tutorial_schema.js +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import Joi from 'joi'; -import { PARAM_TYPES } from './param_types'; -import { TUTORIAL_CATEGORY } from './tutorial_category'; - -const dashboardSchema = Joi.object({ - id: Joi.string().required(), // Dashboard saved object id - linkLabel: Joi.string().when('isOverview', { - is: true, - then: Joi.required(), - }), - // Is this an Overview / Entry Point dashboard? - isOverview: Joi.boolean().required(), -}); - -const artifactsSchema = Joi.object({ - // Fields present in Elasticsearch documents created by this product. - exportedFields: Joi.object({ - documentationUrl: Joi.string().required(), - }), - // Kibana dashboards created by this product. - dashboards: Joi.array() - .items(dashboardSchema) - .required(), - application: Joi.object({ - path: Joi.string().required(), - label: Joi.string().required(), - }), -}); - -const statusCheckSchema = Joi.object({ - title: Joi.string(), - text: Joi.string(), - btnLabel: Joi.string(), - success: Joi.string(), - error: Joi.string(), - esHitsCheck: Joi.object({ - index: Joi.alternatives() - .try(Joi.string(), Joi.array().items(Joi.string())) - .required(), - query: Joi.object().required(), - }).required(), -}); - -const instructionSchema = Joi.object({ - title: Joi.string(), - textPre: Joi.string(), - commands: Joi.array().items(Joi.string().allow('')), - textPost: Joi.string(), -}); - -const instructionVariantSchema = Joi.object({ - id: Joi.string().required(), - instructions: Joi.array() - .items(instructionSchema) - .required(), -}); - -const instructionSetSchema = Joi.object({ - title: Joi.string(), - callOut: Joi.object({ - title: Joi.string().required(), - message: Joi.string(), - iconType: Joi.string(), - }), - // Variants (OSes, languages, etc.) for which tutorial instructions are specified. - instructionVariants: Joi.array() - .items(instructionVariantSchema) - .required(), - statusCheck: statusCheckSchema, -}); - -const paramSchema = Joi.object({ - defaultValue: Joi.required(), - id: Joi.string() - .regex(/^[a-zA-Z_]+$/) - .required(), - label: Joi.string().required(), - type: Joi.string() - .valid(Object.values(PARAM_TYPES)) - .required(), -}); - -const instructionsSchema = Joi.object({ - instructionSets: Joi.array() - .items(instructionSetSchema) - .required(), - params: Joi.array().items(paramSchema), -}); - -export const tutorialSchema = { - id: Joi.string() - .regex(/^[a-zA-Z0-9-]+$/) - .required(), - category: Joi.string() - .valid(Object.values(TUTORIAL_CATEGORY)) - .required(), - name: Joi.string().required(), - isBeta: Joi.boolean().default(false), - shortDescription: Joi.string().required(), - euiIconType: Joi.string(), //EUI icon type string, one of https://elastic.github.io/eui/#/icons - longDescription: Joi.string().required(), - completionTimeMinutes: Joi.number().integer(), - previewImagePath: Joi.string(), - - // kibana and elastic cluster running on prem - onPrem: instructionsSchema.required(), - - // kibana and elastic cluster running in elastic's cloud - elasticCloud: instructionsSchema, - - // kibana running on prem and elastic cluster running in elastic's cloud - onPremElasticCloud: instructionsSchema, - - // Elastic stack artifacts produced by product when it is setup and run. - artifacts: artifactsSchema, - - // saved objects used by data module. - savedObjects: Joi.array().items(), - savedObjectsInstallMsg: Joi.string(), -}; diff --git a/src/legacy/core_plugins/kibana/index.js b/src/legacy/core_plugins/kibana/index.js index e6a0420534ef2..682a39a516e11 100644 --- a/src/legacy/core_plugins/kibana/index.js +++ b/src/legacy/core_plugins/kibana/index.js @@ -24,16 +24,15 @@ import { promisify } from 'util'; import { migrations } from './migrations'; import { importApi } from './server/routes/api/import'; import { exportApi } from './server/routes/api/export'; -import { homeApi } from './server/routes/api/home'; import { managementApi } from './server/routes/api/management'; import { registerFieldFormats } from './server/field_formats/register'; -import { registerTutorials } from './server/tutorials/register'; import * as systemApi from './server/lib/system_api'; import mappings from './mappings.json'; import { getUiSettingDefaults } from './ui_setting_defaults'; import { registerCspCollector } from './server/lib/csp_usage_collector'; import { injectVars } from './inject_vars'; import { i18n } from '@kbn/i18n'; +import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/utils'; const mkdirAsync = promisify(Fs.mkdir); @@ -60,7 +59,12 @@ export default function(kibana) { }, uiExports: { - hacks: ['plugins/kibana/discover', 'plugins/kibana/dev_tools', 'plugins/kibana/visualize'], + hacks: [ + 'plugins/kibana/discover/legacy', + 'plugins/kibana/dev_tools', + 'plugins/kibana/visualize/legacy', + 'plugins/kibana/dashboard/legacy', + ], savedObjectTypes: ['plugins/kibana/dashboard/saved_dashboard/saved_dashboard_register'], app: { id: 'kibana', @@ -78,6 +82,7 @@ export default function(kibana) { order: -1003, url: `${kbnBaseUrl}#/discover`, euiIconType: 'discoverApp', + category: DEFAULT_APP_CATEGORIES.analyze, }, { id: 'kibana:visualize', @@ -87,6 +92,7 @@ export default function(kibana) { order: -1002, url: `${kbnBaseUrl}#/visualize`, euiIconType: 'visualizeApp', + category: DEFAULT_APP_CATEGORIES.analyze, }, { id: 'kibana:dashboard', @@ -102,6 +108,7 @@ export default function(kibana) { // to determine what url to use for the app link. subUrlBase: `${kbnBaseUrl}#/dashboard`, euiIconType: 'dashboardApp', + category: DEFAULT_APP_CATEGORIES.analyze, }, { id: 'kibana:dev_tools', @@ -111,16 +118,18 @@ export default function(kibana) { order: 9001, url: '/app/kibana#/dev_tools', euiIconType: 'devToolsApp', + category: DEFAULT_APP_CATEGORIES.management, }, { - id: 'kibana:management', + id: 'kibana:stack_management', title: i18n.translate('kbn.managementTitle', { - defaultMessage: 'Management', + defaultMessage: 'Stack Management', }), order: 9003, url: `${kbnBaseUrl}#/management`, euiIconType: 'managementApp', linkToLastSubUrl: false, + category: DEFAULT_APP_CATEGORIES.management, }, ], @@ -321,10 +330,8 @@ export default function(kibana) { // routes importApi(server); exportApi(server); - homeApi(server); managementApi(server); registerFieldFormats(server); - registerTutorials(server); registerCspCollector(usageCollection, server); server.expose('systemApi', systemApi); server.injectUiAppVars('kibana', () => injectVars(server)); diff --git a/src/legacy/core_plugins/kibana/public/.eslintrc.js b/src/legacy/core_plugins/kibana/public/.eslintrc.js index 160adcc5b63f1..9b45217287dc8 100644 --- a/src/legacy/core_plugins/kibana/public/.eslintrc.js +++ b/src/legacy/core_plugins/kibana/public/.eslintrc.js @@ -51,6 +51,7 @@ function buildRestrictedPaths(shimmedPlugins) { from: [ `src/legacy/core_plugins/kibana/public/${shimmedPlugin}/**/*`, `!src/legacy/core_plugins/kibana/public/${shimmedPlugin}/index.ts`, + `!src/legacy/core_plugins/kibana/public/${shimmedPlugin}/legacy.ts`, ], allowSameFolder: false, errorMessage: `kibana/public/${shimmedPlugin} is behaving like a NP plugin and does not allow deep imports. If you need something from within ${shimmedPlugin} in another plugin, consider re-exporting it from the top level index module`, diff --git a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/index.ts b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/index.ts index ab8dfe81163e4..2b992f95695f3 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/index.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/index.ts @@ -17,6 +17,5 @@ * under the License. */ -export { getAppStateMock } from './get_app_state_mock'; export { getSavedDashboardMock } from './get_saved_dashboard_mock'; export { getEmbeddableFactoryMock } from './get_embeddable_factories_mock'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/index.ts b/src/legacy/core_plugins/kibana/public/dashboard/index.ts index fd39f28a7673a..4a8decab6b00e 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/index.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/index.ts @@ -17,40 +17,12 @@ * under the License. */ -import { npSetup, npStart, legacyChrome } from './legacy_imports'; -import { DashboardPlugin, LegacyAngularInjectedDependencies } from './plugin'; -import { start as data } from '../../../data/public/legacy'; -import { start as embeddables } from '../../../embeddable_api/public/np_ready/public/legacy'; -import './saved_dashboard/saved_dashboard_register'; -import './dashboard_config'; +import { PluginInitializerContext } from 'kibana/public'; +import { DashboardPlugin } from './plugin'; export * from './np_ready/dashboard_constants'; -/** - * Get dependencies relying on the global angular context. - * They also have to get resolved together with the legacy imports above - */ -async function getAngularDependencies(): Promise { - const injector = await legacyChrome.dangerouslyGetActiveInjector(); - - return { - dashboardConfig: injector.get('dashboardConfig'), - }; -} - -(async () => { - const instance = new DashboardPlugin(); - instance.setup(npSetup.core, { - ...npSetup.plugins, - __LEGACY: { - getAngularDependencies, - }, - }); - instance.start(npStart.core, { - ...npStart.plugins, - data, - npData: npStart.plugins.data, - embeddables, - navigation: npStart.plugins.navigation, - }); -})(); +// Core will be looking for this when loading our plugin in the new platform +export const plugin = (context: PluginInitializerContext) => { + return new DashboardPlugin(); +}; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/legacy.ts b/src/legacy/core_plugins/kibana/public/dashboard/legacy.ts new file mode 100644 index 0000000000000..068a8378f936a --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/dashboard/legacy.ts @@ -0,0 +1,56 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { PluginInitializerContext } from 'kibana/public'; +import { npSetup, npStart, legacyChrome } from './legacy_imports'; +import { LegacyAngularInjectedDependencies } from './plugin'; +import { start as data } from '../../../data/public/legacy'; +import { start as embeddables } from '../../../embeddable_api/public/np_ready/public/legacy'; +import './saved_dashboard/saved_dashboard_register'; +import './dashboard_config'; +import { plugin } from './index'; + +/** + * Get dependencies relying on the global angular context. + * They also have to get resolved together with the legacy imports above + */ +async function getAngularDependencies(): Promise { + const injector = await legacyChrome.dangerouslyGetActiveInjector(); + + return { + dashboardConfig: injector.get('dashboardConfig'), + }; +} + +(async () => { + const instance = plugin({} as PluginInitializerContext); + instance.setup(npSetup.core, { + ...npSetup.plugins, + __LEGACY: { + getAngularDependencies, + }, + }); + instance.start(npStart.core, { + ...npStart.plugins, + data, + npData: npStart.plugins.data, + embeddables, + navigation: npStart.plugins.navigation, + }); +})(); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts b/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts index b44d1993db23a..244a58e8a65e5 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts @@ -28,8 +28,6 @@ import chrome from 'ui/chrome'; export const legacyChrome = chrome; export { State } from 'ui/state_management/state'; -export { AppState } from 'ui/state_management/app_state'; -export { AppStateClass } from 'ui/state_management/app_state'; export { SavedObjectSaveOpts } from 'ui/saved_objects/types'; export { npSetup, npStart } from 'ui/new_platform'; export { IPrivate } from 'ui/private'; @@ -45,8 +43,6 @@ export { GlobalStateProvider } from 'ui/state_management/global_state'; // @ts-ignore export { StateManagementConfigProvider } from 'ui/state_management/config_provider'; // @ts-ignore -export { AppStateProvider } from 'ui/state_management/app_state'; -// @ts-ignore export { PrivateProvider } from 'ui/private/private'; // @ts-ignore export { EventsProvider } from 'ui/events'; @@ -60,9 +56,7 @@ export { KbnUrlProvider, RedirectWhenMissingProvider } from 'ui/url/index'; // @ts-ignore export { confirmModalFactory } from 'ui/modals/confirm_modal'; export { configureAppAngularModule } from 'ui/legacy_compat'; -export { stateMonitorFactory, StateMonitor } from 'ui/state_management/state_monitor_factory'; export { ensureDefaultIndexPattern } from 'ui/legacy_compat'; -export { unhashUrl } from '../../../../../plugins/kibana_utils/public'; export { IInjector } from 'ui/chrome'; export { SavedObjectLoader } from 'ui/saved_objects'; export { VISUALIZE_EMBEDDABLE_TYPE } from '../../../visualizations/public/embeddable'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/application.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/application.ts index 7f7bf7cf47bda..429a7f7279996 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/application.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/application.ts @@ -31,7 +31,6 @@ import { Storage } from '../../../../../../plugins/kibana_utils/public'; import { GlobalStateProvider, StateManagementConfigProvider, - AppStateProvider, PrivateProvider, EventsProvider, PersistedState, @@ -155,12 +154,6 @@ function createLocalStateModule() { 'app/dashboard/Promise', 'app/dashboard/PersistedState', ]) - .factory('AppState', function(Private: any) { - return Private(AppStateProvider); - }) - .service('getAppState', function(Private: any) { - return Private(AppStateProvider).getAppState; - }) .service('globalState', function(Private: any) { return Private(GlobalStateProvider); }); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.tsx b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.tsx index e9fdc335ba572..f56990ae82e56 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.tsx @@ -20,12 +20,7 @@ import moment from 'moment'; import { Subscription } from 'rxjs'; -import { - AppStateClass as TAppStateClass, - AppState as TAppState, - IInjector, - KbnUrl, -} from '../legacy_imports'; +import { IInjector } from '../legacy_imports'; import { ViewMode } from '../../../../embeddable_api/public/np_ready/public'; import { SavedObjectDashboard } from '../saved_dashboard/saved_dashboard'; @@ -43,7 +38,7 @@ import { RenderDeps } from './application'; export interface DashboardAppScope extends ng.IScope { dash: SavedObjectDashboard; - appState: TAppState; + appState: DashboardAppState; screenTitle: string; model: { query: Query; @@ -60,7 +55,6 @@ export interface DashboardAppScope extends ng.IScope { refreshInterval: any; panels: SavedDashboardPanel[]; indexPatterns: IIndexPattern[]; - $evalAsync: any; dashboardViewMode: ViewMode; expandedPanel?: string; getShouldShowEditHelp: () => boolean; @@ -91,8 +85,6 @@ export interface DashboardAppScope extends ng.IScope { export function initDashboardAppDirective(app: any, deps: RenderDeps) { app.directive('dashboardApp', function($injector: IInjector) { - const AppState = $injector.get>('AppState'); - const kbnUrl = $injector.get('kbnUrl'); const confirmModal = $injector.get('confirmModal'); const config = deps.uiSettings; @@ -105,17 +97,13 @@ export function initDashboardAppDirective(app: any, deps: RenderDeps) { $routeParams: { id?: string; }, - getAppState: any, globalState: any ) => new DashboardAppController({ $route, $scope, $routeParams, - getAppState, globalState, - kbnUrl, - AppStateClass: AppState, config, confirmModal, indexPatterns: deps.npDataStart.indexPatterns, diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx index 2706b588a2ec4..4da445166df45 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx @@ -17,57 +17,55 @@ * under the License. */ -import _ from 'lodash'; +import _, { uniq } from 'lodash'; import { i18n } from '@kbn/i18n'; import React from 'react'; import angular from 'angular'; -import { uniq } from 'lodash'; import { Subscription } from 'rxjs'; +import { createHashHistory } from 'history'; import { DashboardEmptyScreen, DashboardEmptyScreenProps } from './dashboard_empty_screen'; import { - subscribeWithScope, ConfirmationButtonTypes, - showSaveModal, - SaveResult, migrateLegacyQuery, - State, - AppStateClass as TAppStateClass, - KbnUrl, SavedObjectSaveOpts, - unhashUrl, + SaveResult, + showSaveModal, + State, + subscribeWithScope, } from '../legacy_imports'; import { FilterStateManager } from '../../../../data/public'; import { + esFilters, IndexPattern, + IndexPatternsContract, Query, SavedQuery, - IndexPatternsContract, } from '../../../../../../plugins/data/public'; import { - DashboardContainer, DASHBOARD_CONTAINER_TYPE, + DashboardContainer, DashboardContainerFactory, DashboardContainerInput, DashboardPanelState, } from '../../../../dashboard_embeddable_container/public/np_ready/public'; import { - isErrorEmbeddable, + EmbeddableFactoryNotFoundError, ErrorEmbeddable, - ViewMode, + isErrorEmbeddable, openAddPanelFlyout, - EmbeddableFactoryNotFoundError, + ViewMode, } from '../../../../embeddable_api/public/np_ready/public'; -import { DashboardAppState, NavAction, ConfirmModalFn, SavedDashboardPanel } from './types'; +import { ConfirmModalFn, NavAction, SavedDashboardPanel } from './types'; import { showOptionsPopover } from './top_nav/show_options_popover'; import { DashboardSaveModal } from './top_nav/save_modal'; import { showCloneModal } from './top_nav/show_clone_modal'; import { saveDashboard } from './lib'; import { DashboardStateManager } from './dashboard_state_manager'; -import { DashboardConstants, createDashboardEditUrl } from './dashboard_constants'; +import { createDashboardEditUrl, DashboardConstants } from './dashboard_constants'; import { getTopNavConfig } from './top_nav/get_top_nav_config'; import { TopNavIds } from './top_nav/top_nav_ids'; import { getDashboardTitle } from './dashboard_strings'; @@ -78,17 +76,15 @@ import { SavedObjectFinderProps, SavedObjectFinderUi, } from '../../../../../../plugins/kibana_react/public'; +import { removeQueryParam, unhashUrl } from '../../../../../../plugins/kibana_utils/public'; export interface DashboardAppControllerDependencies extends RenderDeps { $scope: DashboardAppScope; $route: any; $routeParams: any; - getAppState: any; globalState: State; indexPatterns: IndexPatternsContract; dashboardConfig: any; - kbnUrl: KbnUrl; - AppStateClass: TAppStateClass; config: any; confirmModal: ConfirmModalFn; } @@ -103,12 +99,9 @@ export class DashboardAppController { $scope, $route, $routeParams, - getAppState, globalState, dashboardConfig, localStorage, - kbnUrl, - AppStateClass, indexPatterns, config, confirmModal, @@ -124,7 +117,6 @@ export class DashboardAppController { }, core: { notifications, overlays, chrome, injectedMetadata, uiSettings, savedObjects, http }, }: DashboardAppControllerDependencies) { - new FilterStateManager(globalState, getAppState, filterManager); const queryFilter = filterManager; let lastReloadRequestTime = 0; @@ -134,14 +126,30 @@ export class DashboardAppController { chrome.docTitle.change(dash.title); } + const history = createHashHistory(); const dashboardStateManager = new DashboardStateManager({ savedDashboard: dash, - AppStateClass, + useHashedUrl: config.get('state:storeInSessionStorage'), hideWriteControls: dashboardConfig.getHideWriteControls(), kibanaVersion: injectedMetadata.getKibanaVersion(), + history, }); - $scope.appState = dashboardStateManager.getAppState(); + const filterStateManager = new FilterStateManager( + globalState, + () => { + // Temporary AppState replacement + return { + set filters(_filters: esFilters.Filter[]) { + dashboardStateManager.setFilters(_filters); + }, + get filters() { + return dashboardStateManager.appState.filters; + }, + }; + }, + filterManager + ); // The hash check is so we only update the time filter on dashboard open, not during // normal cross app navigation. @@ -316,8 +324,8 @@ export class DashboardAppController { dirty = true; } + dashboardStateManager.handleDashboardContainerChanges(container); $scope.$evalAsync(() => { - dashboardStateManager.handleDashboardContainerChanges(container); if (dirty) { updateState(); } @@ -337,8 +345,8 @@ export class DashboardAppController { const type = $routeParams[DashboardConstants.ADD_EMBEDDABLE_TYPE]; const id = $routeParams[DashboardConstants.ADD_EMBEDDABLE_ID]; container.addSavedObjectEmbeddable(type, id); - kbnUrl.removeParam(DashboardConstants.ADD_EMBEDDABLE_TYPE); - kbnUrl.removeParam(DashboardConstants.ADD_EMBEDDABLE_ID); + removeQueryParam(history, DashboardConstants.ADD_EMBEDDABLE_TYPE); + removeQueryParam(history, DashboardConstants.ADD_EMBEDDABLE_ID); } } @@ -409,7 +417,9 @@ export class DashboardAppController { key ]; if (!_.isEqual(containerValue, appStateValue)) { - (differences as { [key: string]: unknown })[key] = appStateValue; + // cloneDeep hack is needed, as there are multiple place, where container's input mutated, + // but values from appStateValue are deeply frozen, as they can't be mutated directly + (differences as { [key: string]: unknown })[key] = _.cloneDeep(appStateValue); } }); @@ -470,18 +480,21 @@ export class DashboardAppController { language: localStorage.get('kibana.userQueryLanguage') || config.get('search:queryLanguage'), }, - [] + queryFilter.getGlobalFilters() ); // Making this method sync broke the updates. // Temporary fix, until we fix the complex state in this file. - setTimeout(queryFilter.removeAll, 0); + setTimeout(() => { + queryFilter.setFilters(queryFilter.getGlobalFilters()); + }, 0); }; const updateStateFromSavedQuery = (savedQuery: SavedQuery) => { - dashboardStateManager.applyFilters( - savedQuery.attributes.query, - savedQuery.attributes.filters || [] - ); + const savedQueryFilters = savedQuery.attributes.filters || []; + const globalFilters = queryFilter.getGlobalFilters(); + const allFilters = [...globalFilters, ...savedQueryFilters]; + + dashboardStateManager.applyFilters(savedQuery.attributes.query, allFilters); if (savedQuery.attributes.timefilter) { timefilter.setTime({ from: savedQuery.attributes.timefilter.from, @@ -494,7 +507,7 @@ export class DashboardAppController { // Making this method sync broke the updates. // Temporary fix, until we fix the complex state in this file. setTimeout(() => { - queryFilter.setFilters(savedQuery.attributes.filters || []); + queryFilter.setFilters(allFilters); }, 0); }; @@ -560,11 +573,6 @@ export class DashboardAppController { ); function updateViewMode(newMode: ViewMode) { - $scope.topNavMenu = getTopNavConfig( - newMode, - navActions, - dashboardConfig.getHideWriteControls() - ); // eslint-disable-line no-use-before-define dashboardStateManager.switchViewMode(newMode); } @@ -580,17 +588,28 @@ export class DashboardAppController { function revertChangesAndExitEditMode() { dashboardStateManager.resetState(); - kbnUrl.change( - dash.id ? createDashboardEditUrl(dash.id) : DashboardConstants.CREATE_NEW_DASHBOARD_URL - ); // This is only necessary for new dashboards, which will default to Edit mode. updateViewMode(ViewMode.VIEW); + // Angular's $location skips this update because of history updates from syncState which happen simultaneously + // when calling kbnUrl.change() angular schedules url update and when angular finally starts to process it, + // the update is considered outdated and angular skips it + // so have to use implementation of dashboardStateManager.changeDashboardUrl, which workarounds those issues + dashboardStateManager.changeDashboardUrl( + dash.id ? createDashboardEditUrl(dash.id) : DashboardConstants.CREATE_NEW_DASHBOARD_URL + ); + // We need to do a hard reset of the timepicker. appState will not reload like // it does on 'open' because it's been saved to the url and the getAppState.previouslyStored() check on // reload will cause it not to sync. if (dashboardStateManager.getIsTimeSavedWithDashboard()) { - dashboardStateManager.syncTimefilterWithDashboard(timefilter); + // have to use $evalAsync here until '_g' is migrated from $location to state sync utility ('history') + // When state sync utility changes url, angular's $location is missing it's own updates which happen during the same digest cycle + // temporary solution is to delay $location updates to next digest cycle + // unfortunately, these causes 2 browser history entries, but this is temporary and will be fixed after migrating '_g' to state_sync utilities + $scope.$evalAsync(() => { + dashboardStateManager.syncTimefilterWithDashboard(timefilter); + }); } } @@ -642,7 +661,11 @@ export class DashboardAppController { }); if (dash.id !== $routeParams.id) { - kbnUrl.change(createDashboardEditUrl(dash.id)); + // Angular's $location skips this update because of history updates from syncState which happen simultaneously + // when calling kbnUrl.change() angular schedules url update and when angular finally starts to process it, + // the update is considered outdated and angular skips it + // so have to use implementation of dashboardStateManager.changeDashboardUrl, which workarounds those issues + dashboardStateManager.changeDashboardUrl(createDashboardEditUrl(dash.id)); } else { chrome.docTitle.change(dash.lastSavedTitle); updateViewMode(ViewMode.VIEW); @@ -844,6 +867,15 @@ export class DashboardAppController { }); }); + dashboardStateManager.registerChangeListener(() => { + // view mode could have changed, so trigger top nav update + $scope.topNavMenu = getTopNavConfig( + dashboardStateManager.getViewMode(), + navActions, + dashboardConfig.getHideWriteControls() + ); + }); + $scope.$on('$destroy', () => { updateSubscription.unsubscribe(); visibleSubscription.unsubscribe(); @@ -859,6 +891,9 @@ export class DashboardAppController { if (dashboardContainer) { dashboardContainer.destroy(); } + if (filterStateManager) { + filterStateManager.destroy(); + } }); } } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state.test.ts index d9a93b2ceedc3..8806684aab13c 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state.test.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state.test.ts @@ -18,16 +18,12 @@ */ import './np_core.test.mocks'; +import { createBrowserHistory } from 'history'; import { DashboardStateManager } from './dashboard_state_manager'; -import { getAppStateMock, getSavedDashboardMock } from '../__tests__'; -import { AppStateClass } from '../legacy_imports'; -import { DashboardAppState } from './types'; -import { TimeRange, TimefilterContract, InputTimeRange } from 'src/plugins/data/public'; +import { getSavedDashboardMock } from '../__tests__'; +import { InputTimeRange, TimefilterContract, TimeRange } from 'src/plugins/data/public'; import { ViewMode } from 'src/plugins/embeddable/public'; -jest.mock('ui/state_management/state', () => ({ - State: {}, -})); jest.mock('ui/agg_types', () => ({ aggTypes: { metrics: [], @@ -52,9 +48,10 @@ describe('DashboardState', function() { function initDashboardState() { dashboardState = new DashboardStateManager({ savedDashboard, - AppStateClass: getAppStateMock() as AppStateClass, + useHashedUrl: false, hideWriteControls: false, kibanaVersion: '7.0.0', + history: createBrowserHistory(), }); } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state_manager.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state_manager.ts index 6df18757da6f5..451e7c8ff96db 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state_manager.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state_manager.ts @@ -19,20 +19,16 @@ import { i18n } from '@kbn/i18n'; import _ from 'lodash'; - +import { History } from 'history'; +import { Subscription } from 'rxjs'; import { Moment } from 'moment'; import { DashboardContainer } from 'src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public'; import { ViewMode } from '../../../../../../plugins/embeddable/public'; +import { migrateLegacyQuery } from '../legacy_imports'; import { - stateMonitorFactory, - StateMonitor, - AppStateClass as TAppStateClass, - migrateLegacyQuery, -} from '../legacy_imports'; -import { - Query, esFilters, + Query, TimefilterContract as Timefilter, } from '../../../../../../plugins/data/public'; @@ -41,7 +37,20 @@ import { convertPanelStateToSavedDashboardPanel } from './lib/embeddable_saved_o import { FilterUtils } from './lib/filter_utils'; import { SavedObjectDashboard } from '../saved_dashboard/saved_dashboard'; -import { SavedDashboardPanel, DashboardAppState, DashboardAppStateDefaults } from './types'; +import { + DashboardAppState, + DashboardAppStateDefaults, + DashboardAppStateTransitions, + SavedDashboardPanel, +} from './types'; +import { + createKbnUrlStateStorage, + createStateContainer, + IKbnUrlStateStorage, + ISyncStateRef, + ReduxLikeStateContainer, + syncState, +} from '../../../../../../plugins/kibana_utils/public'; /** * Dashboard state manager handles connecting angular and redux state between the angular and react portions of the @@ -51,7 +60,6 @@ import { SavedDashboardPanel, DashboardAppState, DashboardAppStateDefaults } fro */ export class DashboardStateManager { public savedDashboard: SavedObjectDashboard; - public appState: DashboardAppState; public lastSavedDashboardFilters: { timeTo?: string | Moment; timeFrom?: string | Moment; @@ -63,38 +71,78 @@ export class DashboardStateManager { private kibanaVersion: string; public isDirty: boolean; private changeListeners: Array<(status: { dirty: boolean }) => void>; - private stateMonitor: StateMonitor; + + public get appState(): DashboardAppState { + return this.stateContainer.get(); + } + + private readonly stateContainer: ReduxLikeStateContainer< + DashboardAppState, + DashboardAppStateTransitions + >; + private readonly stateContainerChangeSub: Subscription; + private readonly STATE_STORAGE_KEY = '_a'; + private readonly kbnUrlStateStorage: IKbnUrlStateStorage; + private readonly stateSyncRef: ISyncStateRef; + private readonly history: History; /** * * @param savedDashboard - * @param AppState The AppState class to use when instantiating a new AppState instance. * @param hideWriteControls true if write controls should be hidden. + * @param kibanaVersion current kibanaVersion + * @param */ constructor({ savedDashboard, - AppStateClass, hideWriteControls, kibanaVersion, + useHashedUrl, + history, }: { savedDashboard: SavedObjectDashboard; - AppStateClass: TAppStateClass; hideWriteControls: boolean; kibanaVersion: string; + useHashedUrl: boolean; + history: History; }) { + this.history = history; this.kibanaVersion = kibanaVersion; this.savedDashboard = savedDashboard; this.hideWriteControls = hideWriteControls; - this.stateDefaults = getAppStateDefaults(this.savedDashboard, this.hideWriteControls); + // get state defaults from saved dashboard, make sure it is migrated + this.stateDefaults = migrateAppState( + getAppStateDefaults(this.savedDashboard, this.hideWriteControls), + kibanaVersion + ); + + this.kbnUrlStateStorage = createKbnUrlStateStorage({ useHash: useHashedUrl, history }); - this.appState = new AppStateClass(this.stateDefaults); + // setup initial state by merging defaults with state from url + // also run migration, as state in url could be of older version + const initialState = migrateAppState( + { + ...this.stateDefaults, + ...this.kbnUrlStateStorage.get(this.STATE_STORAGE_KEY), + }, + kibanaVersion + ); - // Initializing appState does two things - first it translates the defaults into AppState, second it updates - // appState based on the URL (the url trumps the defaults). This means if we update the state format at all and - // want to handle BWC, we must not only migrate the data stored with saved Dashboard, but also any old state in the - // url. - migrateAppState(this.appState, kibanaVersion); + // setup state container using initial state both from defaults and from url + this.stateContainer = createStateContainer( + initialState, + { + set: state => (prop, value) => ({ ...state, [prop]: value }), + setOption: state => (option, value) => ({ + ...state, + options: { + ...state.options, + [option]: value, + }, + }), + } + ); this.isDirty = false; @@ -104,29 +152,35 @@ export class DashboardStateManager { // in the 'lose changes' warning message. this.lastSavedDashboardFilters = this.getFilterState(); - /** - * Creates a state monitor and saves it to this.stateMonitor. Used to track unsaved changes made to appState. - */ - this.stateMonitor = stateMonitorFactory.create( - this.appState, - this.stateDefaults - ); - - this.stateMonitor.ignoreProps('viewMode'); - // Filters need to be compared manually because they sometimes have a $$hashkey stored on the object. - this.stateMonitor.ignoreProps('filters'); - // Query needs to be compared manually because saved legacy queries get migrated in app state automatically - this.stateMonitor.ignoreProps('query'); + this.changeListeners = []; - this.stateMonitor.onChange((status: { dirty: boolean }) => { - this.isDirty = status.dirty; + this.stateContainerChangeSub = this.stateContainer.state$.subscribe(() => { + this.isDirty = this.checkIsDirty(); + this.changeListeners.forEach(listener => listener({ dirty: this.isDirty })); }); - this.changeListeners = []; - - this.stateMonitor.onChange((status: { dirty: boolean }) => { - this.changeListeners.forEach(listener => listener(status)); + // make sure url ('_a') matches initial state + this.kbnUrlStateStorage.set(this.STATE_STORAGE_KEY, initialState, { replace: true }); + + // setup state syncing utils. state container will be synched with url into `this.STATE_STORAGE_KEY` query param + this.stateSyncRef = syncState({ + storageKey: this.STATE_STORAGE_KEY, + stateContainer: { + ...this.stateContainer, + set: (state: DashboardAppState | null) => { + // sync state required state container to be able to handle null + // overriding set() so it could handle null coming from url + this.stateContainer.set({ + ...this.stateDefaults, + ...state, + }); + }, + }, + stateStorage: this.kbnUrlStateStorage, }); + + // actually start syncing state with container + this.stateSyncRef.start(); } public registerChangeListener(callback: (status: { dirty: boolean }) => void) { @@ -172,7 +226,7 @@ export class DashboardStateManager { }); if (dirty) { - this.appState.panels = Object.values(convertedPanelStateMap); + this.stateContainer.transitions.set('panels', Object.values(convertedPanelStateMap)); } if (input.isFullScreenMode !== this.getFullScreenMode()) { @@ -184,7 +238,6 @@ export class DashboardStateManager { } this.changeListeners.forEach(listener => listener({ dirty })); - this.saveState(); } public getFullScreenMode() { @@ -192,8 +245,11 @@ export class DashboardStateManager { } public setFullScreenMode(fullScreenMode: boolean) { - this.appState.fullScreenMode = fullScreenMode; - this.saveState(); + this.stateContainer.transitions.set('fullScreenMode', fullScreenMode); + } + + public setFilters(filters: esFilters.Filter[]) { + this.stateContainer.transitions.set('filters', filters); } /** @@ -210,7 +266,10 @@ export class DashboardStateManager { // The right way to fix this might be to ensure the defaults object stored on state is a deep // clone, but given how much code uses the state object, I determined that to be too risky of a change for // now. TODO: revisit this! - this.stateDefaults = getAppStateDefaults(this.savedDashboard, this.hideWriteControls); + this.stateDefaults = migrateAppState( + getAppStateDefaults(this.savedDashboard, this.hideWriteControls), + this.kibanaVersion + ); // The original query won't be restored by the above because the query on this.savedDashboard is applied // in place in order for it to affect the visualizations. this.stateDefaults.query = this.lastSavedDashboardFilters.query; @@ -218,9 +277,7 @@ export class DashboardStateManager { this.stateDefaults.filters = [...this.getLastSavedFilterBars()]; this.isDirty = false; - this.appState.setDefaults(this.stateDefaults); - this.appState.reset(); - this.stateMonitor.setInitialState(this.appState.toJSON()); + this.stateContainer.set(this.stateDefaults); } /** @@ -252,31 +309,28 @@ export class DashboardStateManager { } public setDescription(description: string) { - this.appState.description = description; - this.saveState(); + this.stateContainer.transitions.set('description', description); } public setTitle(title: string) { - this.appState.title = title; this.savedDashboard.title = title; - this.saveState(); + this.stateContainer.transitions.set('title', title); } public getAppState() { - return this.appState; + return this.stateContainer.get(); } public getQuery(): Query { - return migrateLegacyQuery(this.appState.query); + return migrateLegacyQuery(this.stateContainer.get().query); } public getSavedQueryId() { - return this.appState.savedQuery; + return this.stateContainer.get().savedQuery; } public setSavedQueryId(id?: string) { - this.appState.savedQuery = id; - this.saveState(); + this.stateContainer.transitions.set('savedQuery', id); } public getUseMargins() { @@ -287,8 +341,7 @@ export class DashboardStateManager { } public setUseMargins(useMargins: boolean) { - this.appState.options.useMargins = useMargins; - this.saveState(); + this.stateContainer.transitions.setOption('useMargins', useMargins); } public getHidePanelTitles() { @@ -296,8 +349,7 @@ export class DashboardStateManager { } public setHidePanelTitles(hidePanelTitles: boolean) { - this.appState.options.hidePanelTitles = hidePanelTitles; - this.saveState(); + this.stateContainer.transitions.setOption('hidePanelTitles', hidePanelTitles); } public getTimeRestore() { @@ -305,8 +357,7 @@ export class DashboardStateManager { } public setTimeRestore(timeRestore: boolean) { - this.appState.timeRestore = timeRestore; - this.saveState(); + this.stateContainer.transitions.set('timeRestore', timeRestore); } public getIsTimeSavedWithDashboard() { @@ -397,7 +448,6 @@ export class DashboardStateManager { (panel: SavedDashboardPanel) => panel.panelIndex === panelIndex ); Object.assign(foundPanel, panelAttributes); - this.saveState(); return foundPanel; } @@ -456,15 +506,37 @@ export class DashboardStateManager { } /** - * Saves the current application state to the URL. + * Synchronously writes current state to url + * returned boolean indicates whether the update happened and if history was updated */ - public saveState() { - this.appState.save(); + private saveState({ replace }: { replace: boolean }): boolean { + // schedules setting current state to url + this.kbnUrlStateStorage.set( + this.STATE_STORAGE_KEY, + this.stateContainer.get() + ); + // immediately forces scheduled updates and changes location + return this.kbnUrlStateStorage.flush({ replace }); + } + + // TODO: find nicer solution for this + // this function helps to make just 1 browser history update, when we imperatively changing the dashboard url + // It could be that there is pending *dashboardStateManager* updates, which aren't flushed yet to the url. + // So to prevent 2 browser updates: + // 1. Force flush any pending state updates (syncing state to query) + // 2. If url was updated, then apply path change with replace + public changeDashboardUrl(pathname: string) { + // synchronously persist current state to url with push() + const updated = this.saveState({ replace: false }); + // change pathname + this.history[updated ? 'replace' : 'push']({ + ...this.history.location, + pathname, + }); } public setQuery(query: Query) { - this.appState.query = query; - this.saveState(); + this.stateContainer.transitions.set('query', query); } /** @@ -472,24 +544,33 @@ export class DashboardStateManager { * @param filter An array of filter bar filters. */ public applyFilters(query: Query, filters: esFilters.Filter[]) { - this.appState.query = query; this.savedDashboard.searchSource.setField('query', query); this.savedDashboard.searchSource.setField('filter', filters); - this.saveState(); + this.stateContainer.transitions.set('query', query); } public switchViewMode(newMode: ViewMode) { - this.appState.viewMode = newMode; - this.saveState(); + this.stateContainer.transitions.set('viewMode', newMode); } /** * Destroys and cleans up this object when it's no longer used. */ public destroy() { - if (this.stateMonitor) { - this.stateMonitor.destroy(); - } + this.stateContainerChangeSub.unsubscribe(); this.savedDashboard.destroy(); + if (this.stateSyncRef) { + this.stateSyncRef.stop(); + } + } + + private checkIsDirty() { + // Filters need to be compared manually because they sometimes have a $$hashkey stored on the object. + // Query needs to be compared manually because saved legacy queries get migrated in app state automatically + const propsToIgnore: Array = ['viewMode', 'filters', 'query']; + + const initial = _.omit(this.stateDefaults, propsToIgnore); + const current = _.omit(this.stateContainer.get(), propsToIgnore); + return !_.isEqual(initial, current); } } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/legacy_app.js b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/legacy_app.js index 540bfcf5aa684..7dc408ea4b801 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/legacy_app.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/legacy_app.js @@ -35,6 +35,7 @@ import { import { DashboardListing, EMPTY_FILTER } from './listing/dashboard_listing'; import { addHelpMenuToAppChrome } from './help_menu/help_menu_util'; import { syncOnMount } from './global_state_sync'; +import { createHashHistory } from 'history'; export function initDashboardApp(app, deps) { initDashboardAppDirective(app, deps); @@ -190,7 +191,7 @@ export function initDashboardApp(app, deps) { template: dashboardTemplate, controller: createNewDashboardCtrl, resolve: { - dash: function($rootScope, $route, redirectWhenMissing, kbnUrl, AppState) { + dash: function($rootScope, $route, redirectWhenMissing, kbnUrl) { const id = $route.current.params.id; return ensureDefaultIndexPattern(deps.core, deps.npDataStart, $rootScope, kbnUrl) @@ -216,8 +217,13 @@ export function initDashboardApp(app, deps) { // Preserve BWC of v5.3.0 links for new, unsaved dashboards. // See https://github.com/elastic/kibana/issues/10951 for more context. if (error instanceof SavedObjectNotFound && id === 'create') { - // Note "new AppState" is necessary so the state in the url is preserved through the redirect. - kbnUrl.redirect(DashboardConstants.CREATE_NEW_DASHBOARD_URL, {}, new AppState()); + // Note preserve querystring part is necessary so the state is preserved through the redirect. + const history = createHashHistory(); + history.replace({ + ...history.location, // preserve query, + pathname: DashboardConstants.CREATE_NEW_DASHBOARD_URL, + }); + deps.toastNotifications.addWarning( i18n.translate('kbn.dashboard.urlWasRemovedInSixZeroWarningMessage', { defaultMessage: diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/migrate_app_state.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/migrate_app_state.test.ts index 4aa2461bb6593..73336ec951894 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/migrate_app_state.test.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/migrate_app_state.test.ts @@ -23,7 +23,6 @@ import { SavedDashboardPanel } from '../types'; import { migrateAppState } from './migrate_app_state'; test('migrate app state from 6.0', async () => { - const mockSave = jest.fn(); const appState = { uiState: { 'P-1': { vis: { defaultColors: { '0+-+100': 'rgb(0,104,55)' } } }, @@ -39,11 +38,8 @@ test('migrate app state from 6.0', async () => { type: 'visualization', }, ], - translateHashToRison: () => 'a', - getQueryParamName: () => 'a', - save: mockSave, }; - migrateAppState(appState, '8.0'); + migrateAppState(appState as any, '8.0'); expect(appState.uiState).toBeUndefined(); const newPanel = (appState.panels[0] as unknown) as SavedDashboardPanel; @@ -54,12 +50,10 @@ test('migrate app state from 6.0', async () => { expect(newPanel.gridData.y).toBe(0); expect((newPanel.embeddableConfig as any).vis.defaultColors['0+-+100']).toBe('rgb(0,104,55)'); - expect(mockSave).toBeCalledTimes(1); }); test('migrate sort from 6.1', async () => { const TARGET_VERSION = '8.0'; - const mockSave = jest.fn(); const appState = { uiState: { 'P-1': { vis: { defaultColors: { '0+-+100': 'rgb(0,104,55)' } } }, @@ -76,12 +70,9 @@ test('migrate sort from 6.1', async () => { sort: 'sort', }, ], - translateHashToRison: () => 'a', - getQueryParamName: () => 'a', - save: mockSave, useMargins: false, }; - migrateAppState(appState, TARGET_VERSION); + migrateAppState(appState as any, TARGET_VERSION); expect(appState.uiState).toBeUndefined(); const newPanel = (appState.panels[0] as unknown) as SavedDashboardPanel; @@ -91,11 +82,9 @@ test('migrate sort from 6.1', async () => { expect((newPanel.embeddableConfig as any).sort).toBe('sort'); expect((newPanel.embeddableConfig as any).vis.defaultColors['0+-+100']).toBe('rgb(0,104,55)'); - expect(mockSave).toBeCalledTimes(1); }); test('migrates 6.0 even when uiState does not exist', async () => { - const mockSave = jest.fn(); const appState = { panels: [ { @@ -109,11 +98,8 @@ test('migrates 6.0 even when uiState does not exist', async () => { sort: 'sort', }, ], - translateHashToRison: () => 'a', - getQueryParamName: () => 'a', - save: mockSave, }; - migrateAppState(appState, '8.0'); + migrateAppState(appState as any, '8.0'); expect((appState as any).uiState).toBeUndefined(); const newPanel = (appState.panels[0] as unknown) as SavedDashboardPanel; @@ -122,11 +108,9 @@ test('migrates 6.0 even when uiState does not exist', async () => { expect((newPanel as any).sort).toBeUndefined(); expect((newPanel.embeddableConfig as any).sort).toBe('sort'); - expect(mockSave).toBeCalledTimes(1); }); test('6.2 migration adjusts w & h without margins', async () => { - const mockSave = jest.fn(); const appState = { panels: [ { @@ -143,12 +127,9 @@ test('6.2 migration adjusts w & h without margins', async () => { version: '6.2.0', }, ], - translateHashToRison: () => 'a', - getQueryParamName: () => 'a', - save: mockSave, useMargins: false, }; - migrateAppState(appState, '8.0'); + migrateAppState(appState as any, '8.0'); expect((appState as any).uiState).toBeUndefined(); const newPanel = (appState.panels[0] as unknown) as SavedDashboardPanel; @@ -159,11 +140,9 @@ test('6.2 migration adjusts w & h without margins', async () => { expect((newPanel as any).sort).toBeUndefined(); expect((newPanel.embeddableConfig as any).sort).toBe('sort'); - expect(mockSave).toBeCalledTimes(1); }); test('6.2 migration adjusts w & h with margins', async () => { - const mockSave = jest.fn(); const appState = { panels: [ { @@ -180,12 +159,9 @@ test('6.2 migration adjusts w & h with margins', async () => { version: '6.2.0', }, ], - translateHashToRison: () => 'a', - getQueryParamName: () => 'a', - save: mockSave, useMargins: true, }; - migrateAppState(appState, '8.0'); + migrateAppState(appState as any, '8.0'); expect((appState as any).uiState).toBeUndefined(); const newPanel = (appState.panels[0] as unknown) as SavedDashboardPanel; @@ -196,5 +172,4 @@ test('6.2 migration adjusts w & h with margins', async () => { expect((newPanel as any).sort).toBeUndefined(); expect((newPanel.embeddableConfig as any).sort).toBe('sort'); - expect(mockSave).toBeCalledTimes(1); }); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/migrate_app_state.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/migrate_app_state.ts index 4083900c7ede7..0cd958ced0eb1 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/migrate_app_state.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/migrate_app_state.ts @@ -28,6 +28,7 @@ import { SavedDashboardPanel630, SavedDashboardPanel640To720, SavedDashboardPanel620, + SavedDashboardPanel, } from '../types'; import { migratePanelsTo730 } from '../../migrations/migrate_to_730_panels'; @@ -37,9 +38,9 @@ import { migratePanelsTo730 } from '../../migrations/migrate_to_730_panels'; * Once we hit a major version, we can remove support for older style URLs and get rid of this logic. */ export function migrateAppState( - appState: { [key: string]: unknown } | DashboardAppState, + appState: { [key: string]: unknown } & DashboardAppState, kibanaVersion: string -) { +): DashboardAppState { if (!appState.panels) { throw new Error( i18n.translate('kbn.dashboard.panel.invalidData', { @@ -76,11 +77,11 @@ export function migrateAppState( | SavedDashboardPanel640To720 >, kibanaVersion, - appState.useMargins, - appState.uiState - ); + appState.useMargins as boolean, + appState.uiState as Record> + ) as SavedDashboardPanel[]; delete appState.uiState; - - appState.save(); } + + return appState; } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/save_dashboard.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/save_dashboard.ts index 691c87122564f..d80208ce27ffe 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/save_dashboard.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/save_dashboard.ts @@ -36,16 +36,19 @@ export function saveDashboard( dashboardStateManager: DashboardStateManager, saveOptions: SavedObjectSaveOpts ): Promise { - dashboardStateManager.saveState(); - const savedDashboard = dashboardStateManager.savedDashboard; const appState = dashboardStateManager.appState; updateSavedDashboard(savedDashboard, appState, timeFilter, toJson); return savedDashboard.save(saveOptions).then((id: string) => { - dashboardStateManager.lastSavedDashboardFilters = dashboardStateManager.getFilterState(); - dashboardStateManager.resetState(); + if (id) { + // reset state only when save() was successful + // e.g. save() could be interrupted if title is duplicated and not confirmed + dashboardStateManager.lastSavedDashboardFilters = dashboardStateManager.getFilterState(); + dashboardStateManager.resetState(); + } + return id; }); } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/update_saved_dashboard.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/update_saved_dashboard.ts index 2072b5d4f6eb0..ec8073c0f72f7 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/update_saved_dashboard.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/update_saved_dashboard.ts @@ -19,13 +19,13 @@ import _ from 'lodash'; import { RefreshInterval, TimefilterContract } from 'src/plugins/data/public'; -import { AppState } from '../../legacy_imports'; import { FilterUtils } from './filter_utils'; import { SavedObjectDashboard } from '../../saved_dashboard/saved_dashboard'; +import { DashboardAppState } from '../types'; export function updateSavedDashboard( savedDashboard: SavedObjectDashboard, - appState: AppState, + appState: DashboardAppState, timeFilter: TimefilterContract, toJson: (object: T) => string ) { diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/types.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/types.ts index e3eb25a208856..3151fbf821b9f 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/types.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/types.ts @@ -18,7 +18,6 @@ */ import { ViewMode } from 'src/plugins/embeddable/public'; -import { AppState } from '../legacy_imports'; import { RawSavedDashboardPanelTo60, RawSavedDashboardPanel610, @@ -93,11 +92,7 @@ export type SavedDashboardPanelTo60 = Pick< readonly type: string; }; -export type DashboardAppStateDefaults = DashboardAppStateParameters & { - description?: string; -}; - -export interface DashboardAppStateParameters { +export interface DashboardAppState { panels: SavedDashboardPanel[]; fullScreenMode: boolean; title: string; @@ -113,9 +108,24 @@ export interface DashboardAppStateParameters { savedQuery?: string; } -// This could probably be improved if we flesh out AppState more... though AppState will be going away -// so maybe not worth too much time atm. -export type DashboardAppState = DashboardAppStateParameters & AppState; +export type DashboardAppStateDefaults = DashboardAppState & { + description?: string; +}; + +export interface DashboardAppStateTransitions { + set: ( + state: DashboardAppState + ) => ( + prop: T, + value: DashboardAppState[T] + ) => DashboardAppState; + setOption: ( + state: DashboardAppState + ) => ( + prop: T, + value: DashboardAppState['options'][T] + ) => DashboardAppState; +} export interface SavedDashboardPanelMap { [key: string]: SavedDashboardPanel; @@ -139,18 +149,3 @@ export type ConfirmModalFn = ( title: string; } ) => void; - -export type AddFilterFn = ( - { - field, - value, - operator, - index, - }: { - field: string; - value: string; - operator: string; - index: string; - }, - appState: AppState -) => void; diff --git a/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/discover_field.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/discover_field.js index 18d83595f8fa3..6ffda87ac2be8 100644 --- a/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/discover_field.js +++ b/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/discover_field.js @@ -22,7 +22,7 @@ import _ from 'lodash'; import sinon from 'sinon'; import ngMock from 'ng_mock'; import expect from '@kbn/expect'; -import { pluginInstance } from 'plugins/kibana/discover/index'; +import { pluginInstance } from 'plugins/kibana/discover/legacy'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; // Load the kibana app dependencies. diff --git a/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_calculator.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_calculator.js index 378a9e9325655..f302d684135f6 100644 --- a/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_calculator.js +++ b/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_calculator.js @@ -18,7 +18,7 @@ */ import _ from 'lodash'; -import { pluginInstance } from 'plugins/kibana/discover/index'; +import { pluginInstance } from 'plugins/kibana/discover/legacy'; import ngMock from 'ng_mock'; import { fieldCalculator } from '../../np_ready/components/field_chooser/lib/field_calculator'; import expect from '@kbn/expect'; diff --git a/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_chooser.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_chooser.js index 5f6898ae2bd16..f74e145865475 100644 --- a/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_chooser.js +++ b/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_chooser.js @@ -23,7 +23,7 @@ import _ from 'lodash'; import sinon from 'sinon'; import expect from '@kbn/expect'; import $ from 'jquery'; -import { pluginInstance } from 'plugins/kibana/discover/index'; +import { pluginInstance } from 'plugins/kibana/discover/legacy'; import FixturesHitsProvider from 'fixtures/hits'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; import { SimpleSavedObject } from '../../../../../../../core/public'; diff --git a/src/legacy/core_plugins/kibana/public/discover/__tests__/doc_table/doc_table.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/doc_table/doc_table.js index b57f452b637af..6b97da79fc589 100644 --- a/src/legacy/core_plugins/kibana/public/discover/__tests__/doc_table/doc_table.js +++ b/src/legacy/core_plugins/kibana/public/discover/__tests__/doc_table/doc_table.js @@ -22,7 +22,7 @@ import expect from '@kbn/expect'; import _ from 'lodash'; import ngMock from 'ng_mock'; import 'ui/private'; -import { pluginInstance } from 'plugins/kibana/discover/index'; +import { pluginInstance } from 'plugins/kibana/discover/legacy'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; import hits from 'fixtures/real_hits'; diff --git a/src/legacy/core_plugins/kibana/public/discover/__tests__/doc_table/lib/rows_headers.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/doc_table/lib/rows_headers.js index 012f2b6061ee4..c19e033ccb72d 100644 --- a/src/legacy/core_plugins/kibana/public/discover/__tests__/doc_table/lib/rows_headers.js +++ b/src/legacy/core_plugins/kibana/public/discover/__tests__/doc_table/lib/rows_headers.js @@ -24,7 +24,7 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; import { getFakeRow, getFakeRowVals } from 'fixtures/fake_row'; import $ from 'jquery'; -import { pluginInstance } from 'plugins/kibana/discover/index'; +import { pluginInstance } from 'plugins/kibana/discover/legacy'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; describe('Doc Table', function() { diff --git a/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_add_filter.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_add_filter.js index 90614cf3c132c..f2acbf363d825 100644 --- a/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_add_filter.js +++ b/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_add_filter.js @@ -22,7 +22,7 @@ import ngMock from 'ng_mock'; import { createStateStub } from './_utils'; import { getQueryParameterActions } from '../../np_ready/angular/context/query_parameters/actions'; import { createIndexPatternsStub } from '../../np_ready/angular/context/api/__tests__/_stubs'; -import { pluginInstance } from 'plugins/kibana/discover/index'; +import { pluginInstance } from 'plugins/kibana/discover/legacy'; import { npStart } from 'ui/new_platform'; describe('context app', function() { diff --git a/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_predecessor_count.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_predecessor_count.js index 1ad4bdbea210d..9ba425bb0e489 100644 --- a/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_predecessor_count.js +++ b/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_predecessor_count.js @@ -19,7 +19,7 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; -import { pluginInstance } from 'plugins/kibana/discover/index'; +import { pluginInstance } from 'plugins/kibana/discover/legacy'; import { createStateStub } from './_utils'; import { getQueryParameterActions } from '../../np_ready/angular/context/query_parameters/actions'; diff --git a/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_query_parameters.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_query_parameters.js index e9ec2c300faa1..39dde2d8bb7cf 100644 --- a/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_query_parameters.js +++ b/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_query_parameters.js @@ -19,7 +19,7 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; -import { pluginInstance } from 'plugins/kibana/discover/index'; +import { pluginInstance } from 'plugins/kibana/discover/legacy'; import { createStateStub } from './_utils'; import { getQueryParameterActions } from '../../np_ready/angular/context/query_parameters/actions'; diff --git a/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_successor_count.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_successor_count.js index 15f3eefac3fd1..c05f5b4aff3bc 100644 --- a/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_successor_count.js +++ b/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_successor_count.js @@ -19,7 +19,7 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; -import { pluginInstance } from 'plugins/kibana/discover/index'; +import { pluginInstance } from 'plugins/kibana/discover/legacy'; import { createStateStub } from './_utils'; import { getQueryParameterActions } from '../../np_ready/angular/context/query_parameters/actions'; diff --git a/src/legacy/core_plugins/kibana/public/discover/index.ts b/src/legacy/core_plugins/kibana/public/discover/index.ts index 7bde30e0d0f0e..d851cb96a18c4 100644 --- a/src/legacy/core_plugins/kibana/public/discover/index.ts +++ b/src/legacy/core_plugins/kibana/public/discover/index.ts @@ -16,24 +16,13 @@ * specific language governing permissions and limitations * under the License. */ -import chrome from 'ui/chrome'; + import { PluginInitializerContext } from 'kibana/public'; -import { npSetup, npStart } from 'ui/new_platform'; import { DiscoverPlugin } from './plugin'; +export { createSavedSearchesService } from './saved_searches/saved_searches'; + // Core will be looking for this when loading our plugin in the new platform export const plugin = (context: PluginInitializerContext) => { return new DiscoverPlugin(); }; - -// Legacy compatiblity part - to be removed at cutover, replaced by a kibana.json file -export const pluginInstance = plugin({} as PluginInitializerContext); -export const setup = pluginInstance.setup(npSetup.core, { - ...npSetup.plugins, - __LEGACY: { - chrome, - }, -}); -export const start = pluginInstance.start(npStart.core, npStart.plugins); - -export { createSavedSearchesService } from './saved_searches/saved_searches'; diff --git a/src/legacy/core_plugins/state_session_storage_redirect/public/index.js b/src/legacy/core_plugins/kibana/public/discover/legacy.ts similarity index 62% rename from src/legacy/core_plugins/state_session_storage_redirect/public/index.js rename to src/legacy/core_plugins/kibana/public/discover/legacy.ts index 701a5736c7d3b..2ec64177156f9 100644 --- a/src/legacy/core_plugins/state_session_storage_redirect/public/index.js +++ b/src/legacy/core_plugins/kibana/public/discover/legacy.ts @@ -16,25 +16,17 @@ * specific language governing permissions and limitations * under the License. */ - import chrome from 'ui/chrome'; -import { hashUrl } from '../../../../plugins/kibana_utils/public'; -import uiRoutes from 'ui/routes'; -import { fatalError } from 'ui/notify'; - -uiRoutes.enable(); -uiRoutes.when('/', { - resolve: { - url: function(AppState, globalState, $window) { - const redirectUrl = chrome.getInjected('redirectUrl'); - try { - const hashedUrl = hashUrl(redirectUrl); - const url = chrome.addBasePath(hashedUrl); +import { PluginInitializerContext } from 'kibana/public'; +import { npSetup, npStart } from 'ui/new_platform'; +import { plugin } from './index'; - $window.location = url; - } catch (e) { - fatalError(e); - } - }, +// Legacy compatiblity part - to be removed at cutover, replaced by a kibana.json file +export const pluginInstance = plugin({} as PluginInitializerContext); +export const setup = pluginInstance.setup(npSetup.core, { + ...npSetup.plugins, + __LEGACY: { + chrome, }, }); +export const start = pluginInstance.start(npStart.core, npStart.plugins); diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/anchor.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/anchor.js index 45ce6cc9d0af2..debcccebbd11c 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/anchor.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/anchor.js @@ -19,7 +19,7 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; -import { pluginInstance } from 'plugins/kibana/discover/index'; +import { pluginInstance } from 'plugins/kibana/discover/legacy'; import { createIndexPatternsStub, createSearchSourceStub } from './_stubs'; diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/predecessors.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/predecessors.js index 266a505f6be14..c24b6ac6307ff 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/predecessors.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/predecessors.js @@ -21,7 +21,7 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; import moment from 'moment'; import * as _ from 'lodash'; -import { pluginInstance } from 'plugins/kibana/discover/index'; +import { pluginInstance } from 'plugins/kibana/discover/legacy'; import { createIndexPatternsStub, createContextSearchSourceStub } from './_stubs'; diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/successors.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/successors.js index e06d414ba260c..d4c00930c9383 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/successors.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/successors.js @@ -21,7 +21,7 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; import moment from 'moment'; import * as _ from 'lodash'; -import { pluginInstance } from 'plugins/kibana/discover/index'; +import { pluginInstance } from 'plugins/kibana/discover/legacy'; import { createIndexPatternsStub, createContextSearchSourceStub } from './_stubs'; diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js index 2927565e61dce..cde0b5d27bdc5 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js @@ -637,7 +637,7 @@ function discoverController( // fetch data when filters fire fetch event subscriptions.add( - subscribeWithScope($scope, filterManager.getUpdates$(), { + subscribeWithScope($scope, filterManager.getFetches$(), { next: $scope.fetch, }) ); @@ -1000,7 +1000,7 @@ function discoverController( query: '', language: localStorage.get('kibana.userQueryLanguage') || config.get('search:queryLanguage'), }; - filterManager.removeAll(); + filterManager.setFilters(filterManager.getGlobalFilters()); $state.save(); $scope.fetch(); }; @@ -1008,7 +1008,9 @@ function discoverController( const updateStateFromSavedQuery = savedQuery => { $state.query = savedQuery.attributes.query; $state.save(); - filterManager.setFilters(savedQuery.attributes.filters || []); + const savedQueryFilters = savedQuery.attributes.filters || []; + const globalFilters = filterManager.getGlobalFilters(); + filterManager.setFilters([...globalFilters, ...savedQueryFilters]); if (savedQuery.attributes.timefilter) { timefilter.setTime({ diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/get_painless_error.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/get_painless_error.ts index 212fd870a5aeb..2bbeea9d675c7 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/get_painless_error.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/get_painless_error.ts @@ -25,6 +25,7 @@ export function getPainlessError(error: Error) { error, 'resp.error.root_cause' ); + const message: string = get(error, 'message'); if (!rootCause) { return; @@ -43,6 +44,6 @@ export function getPainlessError(error: Error) { defaultMessage: "Error with Painless scripted field '{script}'.", values: { script }, }), - error: error.message, + error: message, }; } diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/fetch_error/fetch_error.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/fetch_error/fetch_error.tsx index d2dda32f318fe..1aad7e953b8de 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/fetch_error/fetch_error.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/fetch_error/fetch_error.tsx @@ -39,7 +39,7 @@ const DiscoverFetchError = ({ fetchError }: Props) => { if (fetchError.lang === 'painless') { const { chrome } = getServices(); - const mangagementUrlObj = chrome.navLinks.get('kibana:management'); + const mangagementUrlObj = chrome.navLinks.get('kibana:stack_management'); const managementUrl = mangagementUrlObj ? mangagementUrlObj.url : ''; const url = `${managementUrl}/kibana/index_patterns`; diff --git a/src/legacy/core_plugins/kibana/public/home/index.ts b/src/legacy/core_plugins/kibana/public/home/index.ts index b2d90f1444654..27d09a53ba20d 100644 --- a/src/legacy/core_plugins/kibana/public/home/index.ts +++ b/src/legacy/core_plugins/kibana/public/home/index.ts @@ -22,11 +22,8 @@ import { npSetup, npStart } from 'ui/new_platform'; import chrome from 'ui/chrome'; import { IPrivate } from 'ui/private'; import { HomePlugin, LegacyAngularInjectedDependencies } from './plugin'; -import { createUiStatsReporter, METRIC_TYPE } from '../../../ui_metric/public'; import { TelemetryOptInProvider } from '../../../telemetry/public/services'; -export const trackUiMetric = createUiStatsReporter('Kibana_home'); - /** * Get dependencies relying on the global angular context. * They also have to get resolved together with the legacy imports above @@ -54,9 +51,7 @@ let copiedLegacyCatalogue = false; instance.setup(npSetup.core, { ...npSetup.plugins, __LEGACY: { - trackUiMetric, metadata: npStart.core.injectedMetadata.getLegacyMetadata(), - METRIC_TYPE, getFeatureCatalogueEntries: async () => { if (!copiedLegacyCatalogue) { const injector = await chrome.dangerouslyGetActiveInjector(); diff --git a/src/legacy/core_plugins/kibana/public/home/kibana_services.ts b/src/legacy/core_plugins/kibana/public/home/kibana_services.ts index 0eb55a3902eda..4d9177735556d 100644 --- a/src/legacy/core_plugins/kibana/public/home/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/home/kibana_services.ts @@ -55,7 +55,6 @@ export interface HomeKibanaServices { savedObjectsClient: SavedObjectsClientContract; toastNotifications: NotificationsSetup['toasts']; banners: OverlayStart['banners']; - METRIC_TYPE: any; trackUiMetric: (type: UiStatsMetricType, eventNames: string | string[], count?: number) => void; getBasePath: () => string; shouldShowTelemetryOptIn: boolean; diff --git a/src/legacy/core_plugins/kibana/public/home/np_ready/components/home.test.js b/src/legacy/core_plugins/kibana/public/home/np_ready/components/home.test.js index be2ceb66f69d0..27d4f1a8b1c1f 100644 --- a/src/legacy/core_plugins/kibana/public/home/np_ready/components/home.test.js +++ b/src/legacy/core_plugins/kibana/public/home/np_ready/components/home.test.js @@ -129,8 +129,8 @@ describe('home', () => { test('should not render directory entry when showOnHomePage is false', async () => { const directoryEntry = { - id: 'management', - title: 'Management', + id: 'stack-management', + title: 'Stack Management', description: 'Your center console for managing the Elastic Stack.', icon: 'managementApp', path: 'management_landing_page', diff --git a/src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/instruction_set.js b/src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/instruction_set.js index 4f60de00819e7..15bda33534185 100644 --- a/src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/instruction_set.js +++ b/src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/instruction_set.js @@ -22,7 +22,7 @@ import PropTypes from 'prop-types'; import { Instruction } from './instruction'; import { ParameterForm } from './parameter_form'; import { Content } from './content'; -import { getDisplayText } from '../../../../../common/tutorials/instruction_variant'; +import { getDisplayText } from '../../../../../../../../plugins/home/server/tutorials/instructions/instruction_variant'; import { EuiTabs, EuiTab, diff --git a/src/legacy/core_plugins/kibana/public/home/np_ready/components/welcome.test.tsx b/src/legacy/core_plugins/kibana/public/home/np_ready/components/welcome.test.tsx index 28bdab14193c4..55c469fa58fc6 100644 --- a/src/legacy/core_plugins/kibana/public/home/np_ready/components/welcome.test.tsx +++ b/src/legacy/core_plugins/kibana/public/home/np_ready/components/welcome.test.tsx @@ -25,10 +25,6 @@ jest.mock('../../kibana_services', () => ({ getServices: () => ({ addBasePath: (path: string) => `root${path}`, trackUiMetric: () => {}, - METRIC_TYPE: { - LOADED: 'loaded', - CLICK: 'click', - }, }), })); diff --git a/src/legacy/core_plugins/kibana/public/home/np_ready/components/welcome.tsx b/src/legacy/core_plugins/kibana/public/home/np_ready/components/welcome.tsx index 9bbb7aaceb915..1b7761d068d2f 100644 --- a/src/legacy/core_plugins/kibana/public/home/np_ready/components/welcome.tsx +++ b/src/legacy/core_plugins/kibana/public/home/np_ready/components/welcome.tsx @@ -35,6 +35,7 @@ import { EuiIcon, EuiPortal, } from '@elastic/eui'; +import { METRIC_TYPE } from '@kbn/analytics'; import { FormattedMessage } from '@kbn/i18n/react'; import { getServices } from '../../kibana_services'; @@ -64,17 +65,17 @@ export class Welcome extends React.Component { } private onSampleDataDecline = () => { - this.services.trackUiMetric(this.services.METRIC_TYPE.CLICK, 'sampleDataDecline'); + this.services.trackUiMetric(METRIC_TYPE.CLICK, 'sampleDataDecline'); this.props.onSkip(); }; private onSampleDataConfirm = () => { - this.services.trackUiMetric(this.services.METRIC_TYPE.CLICK, 'sampleDataConfirm'); + this.services.trackUiMetric(METRIC_TYPE.CLICK, 'sampleDataConfirm'); this.redirecToSampleData(); }; componentDidMount() { - this.services.trackUiMetric(this.services.METRIC_TYPE.LOADED, 'welcomeScreenMount'); + this.services.trackUiMetric(METRIC_TYPE.LOADED, 'welcomeScreenMount'); this.props.onOptInSeen(); document.addEventListener('keydown', this.hideOnEsc); } diff --git a/src/legacy/core_plugins/kibana/public/home/np_ready/load_tutorials.js b/src/legacy/core_plugins/kibana/public/home/np_ready/load_tutorials.js index 6a0a01ebda8db..2f6d4d631baf5 100644 --- a/src/legacy/core_plugins/kibana/public/home/np_ready/load_tutorials.js +++ b/src/legacy/core_plugins/kibana/public/home/np_ready/load_tutorials.js @@ -21,7 +21,6 @@ import _ from 'lodash'; import { getServices } from '../kibana_services'; import { i18n } from '@kbn/i18n'; -const baseUrlLP = getServices().addBasePath('/api/kibana/home/tutorials_LP'); const baseUrl = getServices().addBasePath('/api/kibana/home/tutorials'); const headers = new Headers(); headers.append('Accept', 'application/json'); @@ -29,42 +28,25 @@ headers.append('Content-Type', 'application/json'); headers.append('kbn-xsrf', 'kibana'); let tutorials = []; -let tutorialsLegacyPlatform = []; -let tutorialsNewPlatform = []; let tutorialsLoaded = false; async function loadTutorials() { try { - const responseLegacyPlatform = await fetch(baseUrlLP, { + const response = await fetch(baseUrl, { method: 'get', credentials: 'include', headers: headers, }); - if (responseLegacyPlatform.status >= 300) { + if (response.status >= 300) { throw new Error( i18n.translate('kbn.home.loadTutorials.requestFailedErrorMessage', { defaultMessage: 'Request failed with status code: {status}', - values: { status: responseLegacyPlatform.status }, - }) - ); - } - const responseNewPlatform = await fetch(baseUrl, { - method: 'get', - credentials: 'include', - headers: headers, - }); - if (responseNewPlatform.status >= 300) { - throw new Error( - i18n.translate('kbn.home.loadTutorials.requestFailedErrorMessage', { - defaultMessage: 'Request failed with status code: {status}', - values: { status: responseNewPlatform.status }, + values: { status: response.status }, }) ); } - tutorialsLegacyPlatform = await responseLegacyPlatform.json(); - tutorialsNewPlatform = await responseNewPlatform.json(); - tutorials = tutorialsLegacyPlatform.concat(tutorialsNewPlatform); + tutorials = await response.json(); tutorialsLoaded = true; } catch (err) { getServices().toastNotifications.addDanger({ diff --git a/src/legacy/core_plugins/kibana/public/home/plugin.ts b/src/legacy/core_plugins/kibana/public/home/plugin.ts index 42ab049eb5b3a..502c8f45646cf 100644 --- a/src/legacy/core_plugins/kibana/public/home/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/home/plugin.ts @@ -18,11 +18,11 @@ */ import { CoreSetup, CoreStart, LegacyNavLink, Plugin, UiSettingsState } from 'kibana/public'; -import { UiStatsMetricType } from '@kbn/analytics'; import { DataPublicPluginStart } from 'src/plugins/data/public'; import { setServices } from './kibana_services'; import { KibanaLegacySetup } from '../../../../../plugins/kibana_legacy/public'; +import { UsageCollectionSetup } from '../../../../../plugins/usage_collection/public'; import { Environment, FeatureCatalogueEntry, @@ -41,8 +41,6 @@ export interface HomePluginStartDependencies { export interface HomePluginSetupDependencies { __LEGACY: { - trackUiMetric: (type: UiStatsMetricType, eventNames: string | string[], count?: number) => void; - METRIC_TYPE: any; metadata: { app: unknown; bundleId: string; @@ -59,6 +57,7 @@ export interface HomePluginSetupDependencies { getFeatureCatalogueEntries: () => Promise; getAngularDependencies: () => Promise; }; + usageCollection: UsageCollectionSetup; kibana_legacy: KibanaLegacySetup; } @@ -71,6 +70,7 @@ export class HomePlugin implements Plugin { core: CoreSetup, { kibana_legacy, + usageCollection, __LEGACY: { getAngularDependencies, ...legacyServices }, }: HomePluginSetupDependencies ) { @@ -78,9 +78,11 @@ export class HomePlugin implements Plugin { id: 'home', title: 'Home', mount: async ({ core: contextCore }, params) => { + const trackUiMetric = usageCollection.reportUiStats.bind(usageCollection, 'Kibana_home'); const angularDependencies = await getAngularDependencies(); setServices({ ...legacyServices, + trackUiMetric, http: contextCore.http, toastNotifications: core.notifications.toasts, banners: contextCore.overlays.banners, diff --git a/src/legacy/core_plugins/kibana/public/kibana.js b/src/legacy/core_plugins/kibana/public/kibana.js index 4100ae7205869..bd947b9cb9d7f 100644 --- a/src/legacy/core_plugins/kibana/public/kibana.js +++ b/src/legacy/core_plugins/kibana/public/kibana.js @@ -47,9 +47,9 @@ import 'uiExports/interpreter'; import 'ui/autoload/all'; import 'ui/kbn_top_nav'; import './home'; -import './discover'; -import './visualize'; -import './dashboard'; +import './discover/legacy'; +import './visualize/legacy'; +import './dashboard/legacy'; import './management'; import './dev_tools'; import 'ui/color_maps'; diff --git a/src/legacy/core_plugins/kibana/public/management/index.js b/src/legacy/core_plugins/kibana/public/management/index.js index d62770956b88e..1305310b6f615 100644 --- a/src/legacy/core_plugins/kibana/public/management/index.js +++ b/src/legacy/core_plugins/kibana/public/management/index.js @@ -74,7 +74,7 @@ export function updateLandingPage(version) {

@@ -93,7 +93,7 @@ export function updateLandingPage(version) {

@@ -173,11 +173,11 @@ uiModules.get('apps/management').directive('kbnManagementLanding', function(kbnV FeatureCatalogueRegistryProvider.register(() => { return { - id: 'management', - title: i18n.translate('kbn.management.managementLabel', { - defaultMessage: 'Management', + id: 'stack-management', + title: i18n.translate('kbn.stackManagement.managementLabel', { + defaultMessage: 'Stack Management', }), - description: i18n.translate('kbn.management.managementDescription', { + description: i18n.translate('kbn.stackManagement.managementDescription', { defaultMessage: 'Your center console for managing the Elastic Stack.', }), icon: 'managementApp', diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.ts b/src/legacy/core_plugins/kibana/public/visualize/index.ts index f113c81256f8e..a39779792b83a 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/index.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/index.ts @@ -17,50 +17,15 @@ * under the License. */ -import { - IPrivate, - legacyChrome, - npSetup, - npStart, - VisEditorTypesRegistryProvider, -} from './legacy_imports'; -import { VisualizePlugin, LegacyAngularInjectedDependencies } from './plugin'; -import { start as embeddables } from '../../../embeddable_api/public/np_ready/public/legacy'; -import { start as visualizations } from '../../../visualizations/public/np_ready/public/legacy'; +import { PluginInitializerContext } from 'kibana/public'; +import { VisualizePlugin } from './plugin'; export * from './np_ready/visualize_constants'; export { showNewVisModal } from './np_ready/wizard'; -/** - * Get dependencies relying on the global angular context. - * They also have to get resolved together with the legacy imports above - */ -async function getAngularDependencies(): Promise { - const injector = await legacyChrome.dangerouslyGetActiveInjector(); - - const Private = injector.get('Private'); - - const editorTypes = Private(VisEditorTypesRegistryProvider); - - return { - legacyChrome, - editorTypes, - }; -} - -(() => { - const instance = new VisualizePlugin(); - instance.setup(npSetup.core, { - ...npSetup.plugins, - __LEGACY: { - getAngularDependencies, - }, - }); - instance.start(npStart.core, { - ...npStart.plugins, - embeddables, - visualizations, - }); -})(); - export { createSavedVisLoader } from './saved_visualizations/saved_visualizations'; + +// Core will be looking for this when loading our plugin in the new platform +export const plugin = (context: PluginInitializerContext) => { + return new VisualizePlugin(); +}; diff --git a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts index ea4cd0669a7cd..f7fd19e8288e7 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts @@ -33,17 +33,17 @@ import { DataPublicPluginStart, IndexPatternsContract } from '../../../../../plu import { VisualizationsStart } from '../../../visualizations/public'; import { SavedVisualizations } from './np_ready/types'; import { UsageCollectionSetup } from '../../../../../plugins/usage_collection/public'; +import { Chrome } from './legacy_imports'; export interface VisualizeKibanaServices { addBasePath: (url: string) => string; chrome: ChromeStart; core: LegacyCoreStart; data: DataPublicPluginStart; - editorTypes: any; embeddables: IEmbeddableStart; getBasePath: () => string; indexPatterns: IndexPatternsContract; - legacyChrome: any; + legacyChrome: Chrome; localStorage: Storage; navigation: NavigationStart; toastNotifications: ToastsStart; diff --git a/src/legacy/core_plugins/state_session_storage_redirect/index.js b/src/legacy/core_plugins/kibana/public/visualize/legacy.ts similarity index 56% rename from src/legacy/core_plugins/state_session_storage_redirect/index.js rename to src/legacy/core_plugins/kibana/public/visualize/legacy.ts index 2d4d7c97232c0..2d615e3132b01 100644 --- a/src/legacy/core_plugins/state_session_storage_redirect/index.js +++ b/src/legacy/core_plugins/kibana/public/visualize/legacy.ts @@ -17,16 +17,23 @@ * under the License. */ -export default function(kibana) { - return new kibana.Plugin({ - uiExports: { - app: { - require: ['kibana'], - title: 'Redirecting', - id: 'stateSessionStorageRedirect', - main: 'plugins/state_session_storage_redirect', - hidden: true, - }, +import { PluginInitializerContext } from 'kibana/public'; +import { legacyChrome, npSetup, npStart } from './legacy_imports'; +import { start as embeddables } from '../../../embeddable_api/public/np_ready/public/legacy'; +import { start as visualizations } from '../../../visualizations/public/np_ready/public/legacy'; +import { plugin } from './index'; + +(() => { + const instance = plugin({} as PluginInitializerContext); + instance.setup(npSetup.core, { + ...npSetup.plugins, + __LEGACY: { + legacyChrome, }, }); -} + instance.start(npStart.core, { + ...npStart.plugins, + embeddables, + visualizations, + }); +})(); diff --git a/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts index 7d0a07323378a..e5165cac5c11e 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts @@ -27,6 +27,7 @@ import chrome from 'ui/chrome'; export const legacyChrome = chrome; +export { Chrome } from 'ui/chrome'; // @ts-ignore export { AppState, AppStateProvider } from 'ui/state_management/app_state'; @@ -58,8 +59,6 @@ export { PromiseServiceCreator } from 'ui/promises/promises'; export { confirmModalFactory } from 'ui/modals/confirm_modal'; export { configureAppAngularModule, ensureDefaultIndexPattern } from 'ui/legacy_compat'; export { registerTimefilterWithGlobalStateFactory } from 'ui/timefilter/setup_router'; -// @ts-ignore -export { VisEditorTypesRegistryProvider } from 'ui/registry/vis_editor_types'; // @ts-ignore export { KbnUrlProvider, RedirectWhenMissingProvider } from 'ui/url'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js index 0e085b8553bf0..261e3331e4796 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js @@ -58,7 +58,7 @@ export function initEditorDirective(app, deps) { }; }); - initVisEditorDirective(app, deps); + initVisEditorDirective(app); initVisualizationDirective(app, deps); } @@ -501,7 +501,7 @@ function VisualizeAppController( language: localStorage.get('kibana.userQueryLanguage') || uiSettings.get('search:queryLanguage'), }; - queryFilter.removeAll(); + queryFilter.setFilters(queryFilter.getGlobalFilters()); $state.save(); $scope.fetch(); }; @@ -510,7 +510,9 @@ function VisualizeAppController( $state.query = savedQuery.attributes.query; $state.save(); - queryFilter.setFilters(savedQuery.attributes.filters || []); + const savedQueryFilters = savedQuery.attributes.filters || []; + const globalFilters = queryFilter.getGlobalFilters(); + queryFilter.setFilters([...globalFilters, ...savedQueryFilters]); if (savedQuery.attributes.timefilter) { timefilter.setTime({ diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/visualization_editor.js b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/visualization_editor.js index f6e67d4559f01..930dd1d930019 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/visualization_editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/visualization_editor.js @@ -17,7 +17,7 @@ * under the License. */ -export function initVisEditorDirective(app, deps) { +export function initVisEditorDirective(app) { app.directive('visualizationEditor', function($timeout, getAppState) { return { restrict: 'E', @@ -29,12 +29,7 @@ export function initVisEditorDirective(app, deps) { query: '=', }, link: function($scope, element) { - const editorType = $scope.savedObj.vis.type.editor; - const Editor = - typeof editorType === 'function' - ? editorType - : deps.editorTypes.find(editor => editor.key === editorType); - + const Editor = $scope.savedObj.vis.type.editor; const editor = new Editor(element[0], $scope.savedObj); $scope.renderFunction = () => { diff --git a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts index 80e17b1631f3e..998fd2e2d9161 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts @@ -47,11 +47,7 @@ import { UsageCollectionSetup } from '../../../../../plugins/usage_collection/pu import { createSavedVisLoader } from './saved_visualizations/saved_visualizations'; // @ts-ignore import { savedObjectManagementRegistry } from '../management/saved_object_registry'; - -export interface LegacyAngularInjectedDependencies { - legacyChrome: any; - editorTypes: any; -} +import { Chrome } from './legacy_imports'; export interface VisualizePluginStartDependencies { data: DataPublicPluginStart; @@ -63,7 +59,7 @@ export interface VisualizePluginStartDependencies { export interface VisualizePluginSetupDependencies { __LEGACY: { - getAngularDependencies: () => Promise; + legacyChrome: Chrome; }; home: HomePublicPluginSetup; kibana_legacy: KibanaLegacySetup; @@ -82,12 +78,7 @@ export class VisualizePlugin implements Plugin { public async setup( core: CoreSetup, - { - home, - kibana_legacy, - __LEGACY: { getAngularDependencies }, - usageCollection, - }: VisualizePluginSetupDependencies + { home, kibana_legacy, __LEGACY, usageCollection }: VisualizePluginSetupDependencies ) { kibana_legacy.registerLegacyApp({ id: 'visualize', @@ -106,7 +97,6 @@ export class VisualizePlugin implements Plugin { share, } = this.startDependencies; - const angularDependencies = await getAngularDependencies(); const savedVisualizations = createSavedVisLoader({ savedObjectsClient, indexPatterns: data.indexPatterns, @@ -115,7 +105,7 @@ export class VisualizePlugin implements Plugin { visualizations, }); const deps: VisualizeKibanaServices = { - ...angularDependencies, + ...__LEGACY, addBasePath: contextCore.http.basePath.prepend, core: contextCore as LegacyCoreStart, chrome: contextCore.chrome, diff --git a/src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/csp_collector.test.ts b/src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/csp_collector.test.ts index 395cb60587832..584621a1ce13f 100644 --- a/src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/csp_collector.test.ts +++ b/src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/csp_collector.test.ts @@ -18,6 +18,8 @@ */ import { CspConfig, ICspConfig } from '../../../../../../core/server'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { createMockEnv } from '../../../../../../core/server/config/env.mock'; import { createCspCollector } from './csp_collector'; const createMockKbnServer = () => ({ @@ -25,7 +27,7 @@ const createMockKbnServer = () => ({ setup: { core: { http: { - csp: new CspConfig(), + csp: new CspConfig(createMockEnv()), }, }, }, @@ -36,7 +38,7 @@ describe('csp collector', () => { let kbnServer: ReturnType; function updateCsp(config: Partial) { - kbnServer.newPlatform.setup.core.http.csp = new CspConfig(config); + kbnServer.newPlatform.setup.core.http.csp = new CspConfig(createMockEnv(), config); } beforeEach(() => { diff --git a/src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/csp_collector.ts b/src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/csp_collector.ts index 6622ed4bef478..dce4c7fe6fefc 100644 --- a/src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/csp_collector.ts +++ b/src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/csp_collector.ts @@ -18,7 +18,6 @@ */ import { Server } from 'hapi'; -import { CspConfig } from '../../../../../../core/server'; import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/server'; export function createCspCollector(server: Server) { @@ -26,7 +25,11 @@ export function createCspCollector(server: Server) { type: 'csp', isReady: () => true, async fetch() { - const { strict, warnLegacyBrowsers, header } = server.newPlatform.setup.core.http.csp; + const { + strict, + warnLegacyBrowsers, + rulesChangedFromDefault, + } = server.newPlatform.setup.core.http.csp; return { strict, @@ -34,7 +37,7 @@ export function createCspCollector(server: Server) { // It's important that we do not send the value of csp.header here as it // can be customized with values that can be identifiable to given // installs, such as URLs - rulesChangedFromDefault: header !== CspConfig.DEFAULT.header, + rulesChangedFromDefault, }; }, }; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/apm/index.js b/src/legacy/core_plugins/kibana/server/tutorials/apm/index.js deleted file mode 100644 index d86810a8bdf18..0000000000000 --- a/src/legacy/core_plugins/kibana/server/tutorials/apm/index.js +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; -import { onPremInstructions } from './envs/on_prem'; -import { createElasticCloudInstructions } from './envs/elastic_cloud'; -import apmIndexPattern from './index_pattern.json'; - -const apmIntro = i18n.translate('kbn.server.tutorials.apm.introduction', { - defaultMessage: 'Collect in-depth performance metrics and errors from inside your applications.', -}); - -function isEnabled(config) { - const ENABLED_KEY = 'xpack.apm.ui.enabled'; - if (config.has(ENABLED_KEY)) { - return config.get(ENABLED_KEY); - } - - return false; -} - -export function apmSpecProvider(server) { - const config = server.config(); - const apmIndexPatternTitle = config.get('apm_oss.indexPattern'); - const { cloud } = server.newPlatform.setup; - - const savedObjects = [ - { - ...apmIndexPattern, - attributes: { - ...apmIndexPattern.attributes, - title: apmIndexPatternTitle, - }, - }, - ]; - - const artifacts = { - dashboards: [ - { - id: '8d3ed660-7828-11e7-8c47-65b845b5cfb3', - linkLabel: i18n.translate( - 'kbn.server.tutorials.apm.specProvider.artifacts.dashboards.linkLabel', - { - defaultMessage: 'APM dashboard', - } - ), - isOverview: true, - }, - ], - }; - - if (isEnabled(config)) { - artifacts.application = { - path: '/app/apm', - label: i18n.translate('kbn.server.tutorials.apm.specProvider.artifacts.application.label', { - defaultMessage: 'Launch APM', - }), - }; - } - - return { - id: 'apm', - name: i18n.translate('kbn.server.tutorials.apm.specProvider.name', { - defaultMessage: 'APM', - }), - category: TUTORIAL_CATEGORY.OTHER, - shortDescription: apmIntro, - longDescription: i18n.translate('kbn.server.tutorials.apm.specProvider.longDescription', { - defaultMessage: - 'Application Performance Monitoring (APM) collects in-depth \ -performance metrics and errors from inside your application. \ -It allows you to monitor the performance of thousands of applications in real time. \ -[Learn more]({learnMoreLink}).', - values: { - learnMoreLink: - '{config.docs.base_url}guide/en/apm/get-started/{config.docs.version}/index.html', - }, - }), - euiIconType: 'logoAPM', - artifacts, - onPrem: onPremInstructions(config), - elasticCloud: createElasticCloudInstructions(cloud), - previewImagePath: '/plugins/kibana/home/tutorial_resources/apm/apm.png', - savedObjects, - savedObjectsInstallMsg: i18n.translate( - 'kbn.server.tutorials.apm.specProvider.savedObjectsInstallMsg', - { - defaultMessage: 'An APM index pattern is required for some features in the APM UI.', - } - ), - }; -} diff --git a/src/legacy/core_plugins/kibana/server/tutorials/register.js b/src/legacy/core_plugins/kibana/server/tutorials/register.js deleted file mode 100644 index 2f69e7dbcbc7d..0000000000000 --- a/src/legacy/core_plugins/kibana/server/tutorials/register.js +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import { systemLogsSpecProvider } from './system_logs'; -import { systemMetricsSpecProvider } from './system_metrics'; -import { apacheLogsSpecProvider } from './apache_logs'; -import { apacheMetricsSpecProvider } from './apache_metrics'; -import { elasticsearchLogsSpecProvider } from './elasticsearch_logs'; -import { iisLogsSpecProvider } from './iis_logs'; -import { kafkaLogsSpecProvider } from './kafka_logs'; -import { logstashLogsSpecProvider } from './logstash_logs'; -import { nginxLogsSpecProvider } from './nginx_logs'; -import { nginxMetricsSpecProvider } from './nginx_metrics'; -import { mysqlLogsSpecProvider } from './mysql_logs'; -import { mysqlMetricsSpecProvider } from './mysql_metrics'; -import { mongodbMetricsSpecProvider } from './mongodb_metrics'; -import { osqueryLogsSpecProvider } from './osquery_logs'; -import { phpfpmMetricsSpecProvider } from './php_fpm_metrics'; -import { postgresqlMetricsSpecProvider } from './postgresql_metrics'; -import { postgresqlLogsSpecProvider } from './postgresql_logs'; -import { rabbitmqMetricsSpecProvider } from './rabbitmq_metrics'; -import { redisLogsSpecProvider } from './redis_logs'; -import { redisMetricsSpecProvider } from './redis_metrics'; -import { suricataLogsSpecProvider } from './suricata_logs'; -import { dockerMetricsSpecProvider } from './docker_metrics'; -import { kubernetesMetricsSpecProvider } from './kubernetes_metrics'; -import { uwsgiMetricsSpecProvider } from './uwsgi_metrics'; -import { netflowSpecProvider } from './netflow'; -import { traefikLogsSpecProvider } from './traefik_logs'; -import { apmSpecProvider } from './apm'; -import { cephMetricsSpecProvider } from './ceph_metrics'; -import { aerospikeMetricsSpecProvider } from './aerospike_metrics'; -import { couchbaseMetricsSpecProvider } from './couchbase_metrics'; -import { dropwizardMetricsSpecProvider } from './dropwizard_metrics'; -import { elasticsearchMetricsSpecProvider } from './elasticsearch_metrics'; -import { etcdMetricsSpecProvider } from './etcd_metrics'; -import { haproxyMetricsSpecProvider } from './haproxy_metrics'; -import { kafkaMetricsSpecProvider } from './kafka_metrics'; -import { kibanaMetricsSpecProvider } from './kibana_metrics'; -import { memcachedMetricsSpecProvider } from './memcached_metrics'; -import { muninMetricsSpecProvider } from './munin_metrics'; -import { vSphereMetricsSpecProvider } from './vsphere_metrics'; -import { windowsMetricsSpecProvider } from './windows_metrics'; -import { windowsEventLogsSpecProvider } from './windows_event_logs'; -import { golangMetricsSpecProvider } from './golang_metrics'; -import { logstashMetricsSpecProvider } from './logstash_metrics'; -import { prometheusMetricsSpecProvider } from './prometheus_metrics'; -import { zookeeperMetricsSpecProvider } from './zookeeper_metrics'; -import { uptimeMonitorsSpecProvider } from './uptime_monitors'; -import { cloudwatchLogsSpecProvider } from './cloudwatch_logs'; -import { awsMetricsSpecProvider } from './aws_metrics'; -import { mssqlMetricsSpecProvider } from './mssql_metrics'; -import { natsMetricsSpecProvider } from './nats_metrics'; -import { natsLogsSpecProvider } from './nats_logs'; -import { zeekLogsSpecProvider } from './zeek_logs'; -import { corednsMetricsSpecProvider } from './coredns_metrics'; -import { corednsLogsSpecProvider } from './coredns_logs'; -import { auditbeatSpecProvider } from './auditbeat'; -import { iptablesLogsSpecProvider } from './iptables_logs'; -import { ciscoLogsSpecProvider } from './cisco_logs'; -import { envoyproxyLogsSpecProvider } from './envoyproxy_logs'; -import { couchdbMetricsSpecProvider } from './couchdb_metrics'; -import { emsBoundariesSpecProvider } from './ems'; -import { consulMetricsSpecProvider } from './consul_metrics'; -import { cockroachdbMetricsSpecProvider } from './cockroachdb_metrics'; -import { traefikMetricsSpecProvider } from './traefik_metrics'; -import { awsLogsSpecProvider } from './aws_logs'; -import { activemqLogsSpecProvider } from './activemq_logs'; -import { activemqMetricsSpecProvider } from './activemq_metrics'; -import { azureMetricsSpecProvider } from './azure_metrics'; -import { ibmmqLogsSpecProvider } from './ibmmq_logs'; -import { ibmmqMetricsSpecProvider } from './ibmmq_metrics'; -import { stanMetricsSpecProvider } from './stan_metrics'; -import { envoyproxyMetricsSpecProvider } from './envoyproxy_metrics'; - -export function registerTutorials(server) { - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(systemLogsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(systemMetricsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(apacheLogsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(apacheMetricsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(elasticsearchLogsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(iisLogsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(kafkaLogsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(logstashLogsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(nginxLogsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(nginxMetricsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(mysqlLogsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(mysqlMetricsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(mongodbMetricsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(osqueryLogsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(phpfpmMetricsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(postgresqlMetricsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(postgresqlLogsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(rabbitmqMetricsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(redisLogsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(redisMetricsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(suricataLogsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(dockerMetricsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(kubernetesMetricsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(uwsgiMetricsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(netflowSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(traefikLogsSpecProvider); - server.registerTutorial(apmSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(cephMetricsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(aerospikeMetricsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(couchbaseMetricsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(dropwizardMetricsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial( - elasticsearchMetricsSpecProvider - ); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(etcdMetricsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(haproxyMetricsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(kafkaMetricsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(kibanaMetricsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(memcachedMetricsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(muninMetricsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(vSphereMetricsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(windowsMetricsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(windowsEventLogsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(golangMetricsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(logstashMetricsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(prometheusMetricsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(zookeeperMetricsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(uptimeMonitorsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(cloudwatchLogsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(awsMetricsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(mssqlMetricsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(natsMetricsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(natsLogsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(zeekLogsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(corednsMetricsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(corednsLogsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(auditbeatSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(iptablesLogsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(ciscoLogsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(envoyproxyLogsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(couchdbMetricsSpecProvider); - server.registerTutorial(emsBoundariesSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(consulMetricsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(cockroachdbMetricsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(traefikMetricsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(awsLogsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(activemqLogsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(activemqMetricsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(azureMetricsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(ibmmqLogsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(ibmmqMetricsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(stanMetricsSpecProvider); - server.newPlatform.setup.plugins.home.tutorials.registerTutorial(envoyproxyMetricsSpecProvider); -} diff --git a/src/legacy/core_plugins/kibana/ui_setting_defaults.js b/src/legacy/core_plugins/kibana/ui_setting_defaults.js index dc8fee4a849c5..9b848666541ce 100644 --- a/src/legacy/core_plugins/kibana/ui_setting_defaults.js +++ b/src/legacy/core_plugins/kibana/ui_setting_defaults.js @@ -1170,5 +1170,24 @@ export function getUiSettingDefaults() { category: ['accessibility'], requiresPageReload: true, }, + pageNavigation: { + name: i18n.translate('kbn.advancedSettings.pageNavigationName', { + defaultMessage: 'Side nav style', + }), + value: 'grouped', + description: i18n.translate('kbn.advancedSettings.pageNavigationDesc', { + defaultMessage: 'Change the style of navigation', + }), + type: 'select', + options: ['grouped', 'individual'], + optionLabels: { + grouped: i18n.translate('kbn.advancedSettings.pageNavigationGrouped', { + defaultMessage: 'Grouped', + }), + individual: i18n.translate('kbn.advancedSettings.pageNavigationIndividual', { + defaultMessage: 'Individual', + }), + }, + }, }; } diff --git a/src/legacy/core_plugins/management/index.ts b/src/legacy/core_plugins/management/index.ts index 65601b5371815..4962c948f842f 100644 --- a/src/legacy/core_plugins/management/index.ts +++ b/src/legacy/core_plugins/management/index.ts @@ -23,7 +23,7 @@ import { Legacy } from '../../../../kibana'; // eslint-disable-next-line import/no-default-export export default function ManagementPlugin(kibana: any) { const config: Legacy.PluginSpecOptions = { - id: 'management', + id: 'stack-management', publicDir: resolve(__dirname, 'public'), config: (Joi: any) => { return Joi.object({ diff --git a/src/legacy/core_plugins/region_map/public/components/region_map_options.tsx b/src/legacy/core_plugins/region_map/public/components/region_map_options.tsx index 73fe07ec60102..287fb87e735a2 100644 --- a/src/legacy/core_plugins/region_map/public/components/region_map_options.tsx +++ b/src/legacy/core_plugins/region_map/public/components/region_map_options.tsx @@ -24,11 +24,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { FileLayerField, VectorLayer, ServiceSettings } from 'ui/vis/map/service_settings'; import { VisOptionsProps } from 'ui/vis/editors/default'; -import { - NumberInputOption, - SelectOption, - SwitchOption, -} from '../../../vis_type_vislib/public/components'; +import { NumberInputOption, SelectOption, SwitchOption } from '../../../vis_type_vislib/public'; import { WmsOptions } from '../../../tile_map/public/components/wms_options'; import { RegionMapVisParams } from '../types'; diff --git a/src/legacy/core_plugins/state_session_storage_redirect/package.json b/src/legacy/core_plugins/state_session_storage_redirect/package.json deleted file mode 100644 index 21956e5d76d5b..0000000000000 --- a/src/legacy/core_plugins/state_session_storage_redirect/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "state_session_storage_redirect", - "version": "kibana", - "description": "When using the state:storeInSessionStorage setting with the short-urls, we need some way to get the full URL's hashed states into sessionStorage, this app will grab the URL from the injected state and and put the URL hashed states into sessionStorage before redirecting the user." -} diff --git a/src/legacy/core_plugins/telemetry/common/constants.ts b/src/legacy/core_plugins/telemetry/common/constants.ts index cb4ff79969a32..cf2c9c883871b 100644 --- a/src/legacy/core_plugins/telemetry/common/constants.ts +++ b/src/legacy/core_plugins/telemetry/common/constants.ts @@ -80,4 +80,4 @@ export const PATH_TO_ADVANCED_SETTINGS = 'kibana#/management/kibana/settings'; * The type name used within the Monitoring index to publish management stats. * @type {string} */ -export const KIBANA_MANAGEMENT_STATS_TYPE = 'management'; +export const KIBANA_STACK_MANAGEMENT_STATS_TYPE = 'stack_management'; diff --git a/src/legacy/core_plugins/telemetry/server/collectors/management/telemetry_management_collector.ts b/src/legacy/core_plugins/telemetry/server/collectors/management/telemetry_management_collector.ts index f45cf7fc6bb33..481b1e9af2a79 100644 --- a/src/legacy/core_plugins/telemetry/server/collectors/management/telemetry_management_collector.ts +++ b/src/legacy/core_plugins/telemetry/server/collectors/management/telemetry_management_collector.ts @@ -19,7 +19,7 @@ import { Server } from 'hapi'; import { size } from 'lodash'; -import { KIBANA_MANAGEMENT_STATS_TYPE } from '../../../common/constants'; +import { KIBANA_STACK_MANAGEMENT_STATS_TYPE } from '../../../common/constants'; import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/server'; import { SavedObjectsClient } from '../../../../../../core/server'; @@ -32,7 +32,7 @@ export async function getTranslationCount(loader: any, locale: string): Promise< export function createCollectorFetch(server: Server) { return async function fetchUsageStats(): Promise { - const internalRepo = server.newPlatform.setup.core.savedObjects.createInternalRepository(); + const internalRepo = server.newPlatform.start.core.savedObjects.createInternalRepository(); const uiSettingsClient = server.newPlatform.start.core.uiSettings.asScopedToClient( new SavedObjectsClient(internalRepo) ); @@ -54,7 +54,7 @@ export function registerManagementUsageCollector( server: any ) { const collector = usageCollection.makeUsageCollector({ - type: KIBANA_MANAGEMENT_STATS_TYPE, + type: KIBANA_STACK_MANAGEMENT_STATS_TYPE, isReady: () => true, fetch: createCollectorFetch(server), }); diff --git a/src/legacy/core_plugins/tile_map/public/components/tile_map_options.tsx b/src/legacy/core_plugins/tile_map/public/components/tile_map_options.tsx index e57cea8467d12..4ab9f95ee4c3c 100644 --- a/src/legacy/core_plugins/tile_map/public/components/tile_map_options.tsx +++ b/src/legacy/core_plugins/tile_map/public/components/tile_map_options.tsx @@ -27,7 +27,7 @@ import { RangeOption, SelectOption, SwitchOption, -} from '../../../vis_type_vislib/public/components'; +} from '../../../vis_type_vislib/public'; import { WmsOptions } from './wms_options'; import { TileMapVisParams } from '../types'; import { MapTypes } from '../map_types'; diff --git a/src/legacy/core_plugins/tile_map/public/components/wms_internal_options.tsx b/src/legacy/core_plugins/tile_map/public/components/wms_internal_options.tsx index 2989f6ce7ebd5..b81667400303d 100644 --- a/src/legacy/core_plugins/tile_map/public/components/wms_internal_options.tsx +++ b/src/legacy/core_plugins/tile_map/public/components/wms_internal_options.tsx @@ -21,7 +21,7 @@ import React from 'react'; import { EuiLink, EuiSpacer, EuiText, EuiScreenReaderOnly } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { TextInputOption } from '../../../vis_type_vislib/public/components'; +import { TextInputOption } from '../../../vis_type_vislib/public'; import { WMSOptions } from '../types'; interface WmsInternalOptions { diff --git a/src/legacy/core_plugins/tile_map/public/components/wms_options.tsx b/src/legacy/core_plugins/tile_map/public/components/wms_options.tsx index d9dca5afd7377..a0b7a0a844f55 100644 --- a/src/legacy/core_plugins/tile_map/public/components/wms_options.tsx +++ b/src/legacy/core_plugins/tile_map/public/components/wms_options.tsx @@ -25,7 +25,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { TmsLayer } from 'ui/vis/map/service_settings'; import { Vis } from 'ui/vis'; import { RegionMapVisParams } from '../../../region_map/public/types'; -import { SelectOption, SwitchOption } from '../../../vis_type_vislib/public/components'; +import { SelectOption, SwitchOption } from '../../../vis_type_vislib/public'; import { WmsInternalOptions } from './wms_internal_options'; import { WMSOptions, TileMapVisParams } from '../types'; diff --git a/src/legacy/core_plugins/timelion/index.ts b/src/legacy/core_plugins/timelion/index.ts index ec121647f4e47..d725327e2365b 100644 --- a/src/legacy/core_plugins/timelion/index.ts +++ b/src/legacy/core_plugins/timelion/index.ts @@ -22,6 +22,7 @@ import { i18n } from '@kbn/i18n'; import { Legacy } from 'kibana'; import { LegacyPluginApi, LegacyPluginInitializer } from 'src/legacy/plugin_discovery/types'; import { CoreSetup, PluginInitializerContext } from 'src/core/server'; +import { DEFAULT_APP_CATEGORIES } from '../../../core/utils'; import { plugin } from './server'; import { CustomCoreSetup } from './server/plugin'; @@ -60,6 +61,7 @@ const timelionPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPl icon: 'plugins/timelion/icon.svg', euiIconType: 'timelionApp', main: 'plugins/timelion/app', + category: DEFAULT_APP_CATEGORIES.analyze, }, styleSheetPaths: resolve(__dirname, 'public/index.scss'), hacks: [resolve(__dirname, 'public/legacy')], diff --git a/src/legacy/core_plugins/vis_type_markdown/public/settings_options.tsx b/src/legacy/core_plugins/vis_type_markdown/public/settings_options.tsx index 125577815c207..18852b549b1ed 100644 --- a/src/legacy/core_plugins/vis_type_markdown/public/settings_options.tsx +++ b/src/legacy/core_plugins/vis_type_markdown/public/settings_options.tsx @@ -22,7 +22,7 @@ import { EuiPanel } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { VisOptionsProps } from 'ui/vis/editors/default'; -import { RangeOption, SwitchOption } from '../../vis_type_vislib/public/components'; +import { RangeOption, SwitchOption } from '../../vis_type_vislib/public'; import { MarkdownVisParams } from './types'; function SettingsOptions({ stateParams, setValue }: VisOptionsProps) { diff --git a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_options.tsx b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_options.tsx index 032f66d92624c..e144c055d8023 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_options.tsx +++ b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_options.tsx @@ -31,13 +31,13 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { VisOptionsProps } from '../legacy_imports'; import { + ColorModes, ColorRanges, ColorSchemaOptions, SwitchOption, RangeOption, SetColorSchemaOptionsValue, -} from '../../../vis_type_vislib/public/components'; -import { ColorModes } from '../../../vis_type_vislib/public/utils/collections'; +} from '../../../vis_type_vislib/public'; import { MetricVisParam, VisParams } from '../types'; import { SetColorRangeValue } from '../../../vis_type_vislib/public/components/common/color_ranges'; diff --git a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.ts b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.ts index 45110ca113003..4ebd8bf97c55a 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.ts @@ -27,7 +27,7 @@ import { Render, Style, } from '../../../../plugins/expressions/public'; -import { ColorModes } from '../../vis_type_vislib/public/utils/collections'; +import { ColorModes } from '../../vis_type_vislib/public'; import { visType, DimensionsVisParam, VisParams } from './types'; export type Context = KibanaDatatable; diff --git a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.ts b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.ts index 0d9019ee0579c..ee7ead0b7331b 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.ts @@ -21,8 +21,8 @@ import { i18n } from '@kbn/i18n'; import { MetricVisComponent } from './components/metric_vis_component'; import { MetricVisOptions } from './components/metric_vis_options'; +import { ColorModes } from '../../vis_type_vislib/public'; import { Schemas, AggGroupNames, colorSchemas, ColorSchemas } from './legacy_imports'; -import { ColorModes } from '../../vis_type_vislib/public/utils/collections'; export const metricVisTypeDefinition = { name: 'metric', diff --git a/src/legacy/core_plugins/vis_type_metric/public/types.ts b/src/legacy/core_plugins/vis_type_metric/public/types.ts index 71c1c12b4f8f0..34cb1b209a3ae 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/types.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/types.ts @@ -20,8 +20,7 @@ import { ColorSchemas } from './legacy_imports'; import { Range } from '../../../../plugins/expressions/public'; import { SchemaConfig } from '../../visualizations/public'; -import { ColorModes } from '../../vis_type_vislib/public/utils/collections'; -import { Labels, Style } from '../../vis_type_vislib/public/types'; +import { ColorModes, Labels, Style } from '../../vis_type_vislib/public'; export const visType = 'metric'; diff --git a/src/legacy/core_plugins/vis_type_table/public/components/table_vis_options.tsx b/src/legacy/core_plugins/vis_type_table/public/components/table_vis_options.tsx index 33d7480de5a8e..529439a800682 100644 --- a/src/legacy/core_plugins/vis_type_table/public/components/table_vis_options.tsx +++ b/src/legacy/core_plugins/vis_type_table/public/components/table_vis_options.tsx @@ -24,11 +24,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { tabifyGetColumns, VisOptionsProps } from '../legacy_imports'; -import { - NumberInputOption, - SwitchOption, - SelectOption, -} from '../../../vis_type_vislib/public/components/common'; +import { NumberInputOption, SwitchOption, SelectOption } from '../../../vis_type_vislib/public'; import { TableVisParams } from '../types'; import { totalAggregations, isAggConfigNumeric } from './utils'; diff --git a/src/legacy/core_plugins/vis_type_tagcloud/public/components/tag_cloud_options.tsx b/src/legacy/core_plugins/vis_type_tagcloud/public/components/tag_cloud_options.tsx index c500b5d888b05..76117c8b6b398 100644 --- a/src/legacy/core_plugins/vis_type_tagcloud/public/components/tag_cloud_options.tsx +++ b/src/legacy/core_plugins/vis_type_tagcloud/public/components/tag_cloud_options.tsx @@ -23,7 +23,7 @@ import { i18n } from '@kbn/i18n'; import { ValidatedDualRange } from 'ui/validated_range'; import { VisOptionsProps } from 'ui/vis/editors/default'; -import { SelectOption, SwitchOption } from '../../../vis_type_vislib/public/components'; +import { SelectOption, SwitchOption } from '../../../vis_type_vislib/public'; import { TagCloudVisParams } from '../types'; function TagCloudOptions({ stateParams, setValue, vis }: VisOptionsProps) { diff --git a/src/legacy/core_plugins/vis_type_timelion/README.md b/src/legacy/core_plugins/vis_type_timelion/README.md new file mode 100644 index 0000000000000..c306e03abf2c6 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/README.md @@ -0,0 +1,10 @@ +# Vis type Timelion + +# Generate a parser +If your grammar was changed in `public/chain.peg` you need to re-generate the static parser. You could use a grunt task: + +``` +grunt peg:timelion_chain +``` + +The generated parser will be appeared at `public/_generated_` folder, which is included in `.eslintignore` \ No newline at end of file diff --git a/src/legacy/core_plugins/vis_type_timelion/public/_generated_/chain.js b/src/legacy/core_plugins/vis_type_timelion/public/_generated_/chain.js new file mode 100644 index 0000000000000..f812b94238d43 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/_generated_/chain.js @@ -0,0 +1,1780 @@ +module.exports = (function() { + "use strict"; + + /* + * Generated by PEG.js 0.9.0. + * + * http://pegjs.org/ + */ + + function peg$subclass(child, parent) { + function ctor() { this.constructor = child; } + ctor.prototype = parent.prototype; + child.prototype = new ctor(); + } + + function peg$SyntaxError(message, expected, found, location) { + this.message = message; + this.expected = expected; + this.found = found; + this.location = location; + this.name = "SyntaxError"; + + if (typeof Error.captureStackTrace === "function") { + Error.captureStackTrace(this, peg$SyntaxError); + } + } + + peg$subclass(peg$SyntaxError, Error); + + function peg$parse(input) { + var options = arguments.length > 1 ? arguments[1] : {}, + parser = this, + + peg$FAILED = {}, + + peg$startRuleFunctions = { start: peg$parsestart }, + peg$startRuleFunction = peg$parsestart, + + peg$c0 = function(tree) { + return { + tree: tree.filter(function (o) {return o != null}), + functions: functions, + args: args, + variables: variables + } + }, + peg$c1 = ",", + peg$c2 = { type: "literal", value: ",", description: "\",\"" }, + peg$c3 = function(first, arg) {return arg}, + peg$c4 = function(first, rest) { + return [first].concat(rest); + }, + peg$c5 = "=", + peg$c6 = { type: "literal", value: "=", description: "\"=\"" }, + peg$c7 = function(name, value) { + var arg = { + type: 'namedArg', + name: name, + value: value, + location: simpleLocation(location()), + text: text() + }; + currentArgs.push(arg); + return arg; + }, + peg$c8 = function(value) { + var exception = { + type: 'incompleteArgument', + currentArgs: currentArgs, + currentFunction: currentFunction, + location: simpleLocation(location()), + text: text() + } + error(JSON.stringify(exception)); + }, + peg$c9 = function(name) { + var exception = { + type: 'incompleteArgumentValue', + currentArgs: currentArgs, + currentFunction: currentFunction, + name: name, + location: simpleLocation(location()), + text: text() + } + error(JSON.stringify(exception)); + }, + peg$c10 = function(element) {return element}, + peg$c11 = function(literal) { + var result = ltoo(literal); + result.location = simpleLocation(location()), + result.text = text(); + return result; + }, + peg$c12 = "$", + peg$c13 = { type: "literal", value: "$", description: "\"$\"" }, + peg$c14 = function(name) { + if (variables[name]) { + return variables[name]; + } else { + error('$' + name + ' is not defined') + } + }, + peg$c15 = function(name, value) { + variables[name] = value; + }, + peg$c16 = function(first, series) {return series}, + peg$c17 = function(first, rest) { + return [first].concat(rest) + }, + peg$c18 = /^[a-zA-Z]/, + peg$c19 = { type: "class", value: "[a-zA-Z]", description: "[a-zA-Z]" }, + peg$c20 = /^[.a-zA-Z0-9_\-]/, + peg$c21 = { type: "class", value: "[.a-zA-Z0-9_-]", description: "[.a-zA-Z0-9_-]" }, + peg$c22 = function(first, rest) { + currentFunction = first.join('') + rest.join(''); + currentArgs = []; + return currentFunction; + }, + peg$c23 = function(first, rest) { return first.join('') + rest.join('') }, + peg$c24 = { type: "other", description: "function" }, + peg$c25 = ".", + peg$c26 = { type: "literal", value: ".", description: "\".\"" }, + peg$c27 = "(", + peg$c28 = { type: "literal", value: "(", description: "\"(\"" }, + peg$c29 = ")", + peg$c30 = { type: "literal", value: ")", description: "\")\"" }, + peg$c31 = function(name, arg_list) { + var result = { + type: 'function', + function: name, + arguments: arg_list || [], + location: simpleLocation(location()), + text: text() + } + + result.arguments.forEach(function (arg) { + arg.function = name; + args.push(arg); + }) + + functions.push(result) + return result; + }, + peg$c32 = function(func) { + var exception = { + type: 'incompleteFunction', + function: func, + location: simpleLocation(location()), + text: text() + } + error(JSON.stringify(exception)); + }, + peg$c33 = "@", + peg$c34 = { type: "literal", value: "@", description: "\"@\"" }, + peg$c35 = ":", + peg$c36 = { type: "literal", value: ":", description: "\":\"" }, + peg$c37 = function(plot, series) { + return { + type: 'reference', + plot: plot, + series: series + } + }, + peg$c38 = function(plot) { + return { + type: 'reference', + plot: plot + } + }, + peg$c39 = function(func, rest) {return {type: 'chain', chain: [func].concat(rest)}}, + peg$c40 = function(grouped, functions) { + var first = { + type: 'chainList', + list: grouped + } + first.label = text(); + + return {type: "chain", chain: [first].concat(functions)}; + }, + peg$c41 = { type: "other", description: "literal" }, + peg$c42 = "\"", + peg$c43 = { type: "literal", value: "\"", description: "\"\\\"\"" }, + peg$c44 = function(chars) { return chars.join(''); }, + peg$c45 = "'", + peg$c46 = { type: "literal", value: "'", description: "\"'\"" }, + peg$c47 = "true", + peg$c48 = { type: "literal", value: "true", description: "\"true\"" }, + peg$c49 = function() { return true; }, + peg$c50 = "false", + peg$c51 = { type: "literal", value: "false", description: "\"false\"" }, + peg$c52 = function() { return false; }, + peg$c53 = "null", + peg$c54 = { type: "literal", value: "null", description: "\"null\"" }, + peg$c55 = function() { return null; }, + peg$c56 = /^[^()"',= \t]/, + peg$c57 = { type: "class", value: "[^()\"',=\\ \\t]", description: "[^()\"',=\\ \\t]" }, + peg$c58 = function(string) { // this also matches numbers via Number() + var result = string.join(''); + // Sort of hacky, but PEG doesn't have backtracking so + // a number rule is hard to read, and performs worse + if (isNaN(Number(result))) return result; + return Number(result) + }, + peg$c59 = /^[ \t\r\n]/, + peg$c60 = { type: "class", value: "[\\ \\t\\r\\n]", description: "[\\ \\t\\r\\n]" }, + peg$c61 = "\\", + peg$c62 = { type: "literal", value: "\\", description: "\"\\\\\"" }, + peg$c63 = function(sequence) { return sequence; }, + peg$c64 = /^[^"]/, + peg$c65 = { type: "class", value: "[^\"]", description: "[^\"]" }, + peg$c66 = /^[^']/, + peg$c67 = { type: "class", value: "[^']", description: "[^']" }, + peg$c68 = /^[0-9]/, + peg$c69 = { type: "class", value: "[0-9]", description: "[0-9]" }, + peg$c70 = function(digits) {return parseInt(digits.join(''))}, + + peg$currPos = 0, + peg$savedPos = 0, + peg$posDetailsCache = [{ line: 1, column: 1, seenCR: false }], + peg$maxFailPos = 0, + peg$maxFailExpected = [], + peg$silentFails = 0, + + peg$result; + + if ("startRule" in options) { + if (!(options.startRule in peg$startRuleFunctions)) { + throw new Error("Can't start parsing from rule \"" + options.startRule + "\"."); + } + + peg$startRuleFunction = peg$startRuleFunctions[options.startRule]; + } + + function text() { + return input.substring(peg$savedPos, peg$currPos); + } + + function location() { + return peg$computeLocation(peg$savedPos, peg$currPos); + } + + function expected(description) { + throw peg$buildException( + null, + [{ type: "other", description: description }], + input.substring(peg$savedPos, peg$currPos), + peg$computeLocation(peg$savedPos, peg$currPos) + ); + } + + function error(message) { + throw peg$buildException( + message, + null, + input.substring(peg$savedPos, peg$currPos), + peg$computeLocation(peg$savedPos, peg$currPos) + ); + } + + function peg$computePosDetails(pos) { + var details = peg$posDetailsCache[pos], + p, ch; + + if (details) { + return details; + } else { + p = pos - 1; + while (!peg$posDetailsCache[p]) { + p--; + } + + details = peg$posDetailsCache[p]; + details = { + line: details.line, + column: details.column, + seenCR: details.seenCR + }; + + while (p < pos) { + ch = input.charAt(p); + if (ch === "\n") { + if (!details.seenCR) { details.line++; } + details.column = 1; + details.seenCR = false; + } else if (ch === "\r" || ch === "\u2028" || ch === "\u2029") { + details.line++; + details.column = 1; + details.seenCR = true; + } else { + details.column++; + details.seenCR = false; + } + + p++; + } + + peg$posDetailsCache[pos] = details; + return details; + } + } + + function peg$computeLocation(startPos, endPos) { + var startPosDetails = peg$computePosDetails(startPos), + endPosDetails = peg$computePosDetails(endPos); + + return { + start: { + offset: startPos, + line: startPosDetails.line, + column: startPosDetails.column + }, + end: { + offset: endPos, + line: endPosDetails.line, + column: endPosDetails.column + } + }; + } + + function peg$fail(expected) { + if (peg$currPos < peg$maxFailPos) { return; } + + if (peg$currPos > peg$maxFailPos) { + peg$maxFailPos = peg$currPos; + peg$maxFailExpected = []; + } + + peg$maxFailExpected.push(expected); + } + + function peg$buildException(message, expected, found, location) { + function cleanupExpected(expected) { + var i = 1; + + expected.sort(function(a, b) { + if (a.description < b.description) { + return -1; + } else if (a.description > b.description) { + return 1; + } else { + return 0; + } + }); + + while (i < expected.length) { + if (expected[i - 1] === expected[i]) { + expected.splice(i, 1); + } else { + i++; + } + } + } + + function buildMessage(expected, found) { + function stringEscape(s) { + function hex(ch) { return ch.charCodeAt(0).toString(16).toUpperCase(); } + + return s + .replace(/\\/g, '\\\\') + .replace(/"/g, '\\"') + .replace(/\x08/g, '\\b') + .replace(/\t/g, '\\t') + .replace(/\n/g, '\\n') + .replace(/\f/g, '\\f') + .replace(/\r/g, '\\r') + .replace(/[\x00-\x07\x0B\x0E\x0F]/g, function(ch) { return '\\x0' + hex(ch); }) + .replace(/[\x10-\x1F\x80-\xFF]/g, function(ch) { return '\\x' + hex(ch); }) + .replace(/[\u0100-\u0FFF]/g, function(ch) { return '\\u0' + hex(ch); }) + .replace(/[\u1000-\uFFFF]/g, function(ch) { return '\\u' + hex(ch); }); + } + + var expectedDescs = new Array(expected.length), + expectedDesc, foundDesc, i; + + for (i = 0; i < expected.length; i++) { + expectedDescs[i] = expected[i].description; + } + + expectedDesc = expected.length > 1 + ? expectedDescs.slice(0, -1).join(", ") + + " or " + + expectedDescs[expected.length - 1] + : expectedDescs[0]; + + foundDesc = found ? "\"" + stringEscape(found) + "\"" : "end of input"; + + return "Expected " + expectedDesc + " but " + foundDesc + " found."; + } + + if (expected !== null) { + cleanupExpected(expected); + } + + return new peg$SyntaxError( + message !== null ? message : buildMessage(expected, found), + expected, + found, + location + ); + } + + function peg$parsestart() { + var s0, s1, s2; + + s0 = peg$currPos; + s1 = peg$parsespace(); + if (s1 === peg$FAILED) { + s1 = null; + } + if (s1 !== peg$FAILED) { + s2 = peg$parseseries(); + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c0(s2); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsearg_list() { + var s0, s1, s2, s3, s4, s5, s6, s7; + + s0 = peg$currPos; + s1 = peg$parseargument(); + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$currPos; + s4 = peg$parsespace(); + if (s4 === peg$FAILED) { + s4 = null; + } + if (s4 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 44) { + s5 = peg$c1; + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c2); } + } + if (s5 !== peg$FAILED) { + s6 = peg$parsespace(); + if (s6 === peg$FAILED) { + s6 = null; + } + if (s6 !== peg$FAILED) { + s7 = peg$parseargument(); + if (s7 !== peg$FAILED) { + peg$savedPos = s3; + s4 = peg$c3(s1, s7); + s3 = s4; + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$currPos; + s4 = peg$parsespace(); + if (s4 === peg$FAILED) { + s4 = null; + } + if (s4 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 44) { + s5 = peg$c1; + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c2); } + } + if (s5 !== peg$FAILED) { + s6 = peg$parsespace(); + if (s6 === peg$FAILED) { + s6 = null; + } + if (s6 !== peg$FAILED) { + s7 = peg$parseargument(); + if (s7 !== peg$FAILED) { + peg$savedPos = s3; + s4 = peg$c3(s1, s7); + s3 = s4; + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } + if (s2 !== peg$FAILED) { + s3 = peg$parsespace(); + if (s3 === peg$FAILED) { + s3 = null; + } + if (s3 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 44) { + s4 = peg$c1; + peg$currPos++; + } else { + s4 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c2); } + } + if (s4 === peg$FAILED) { + s4 = null; + } + if (s4 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c4(s1, s2); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseargument() { + var s0, s1, s2, s3, s4, s5; + + s0 = peg$currPos; + s1 = peg$parseargument_name(); + if (s1 !== peg$FAILED) { + s2 = peg$parsespace(); + if (s2 === peg$FAILED) { + s2 = null; + } + if (s2 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 61) { + s3 = peg$c5; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c6); } + } + if (s3 !== peg$FAILED) { + s4 = peg$parsespace(); + if (s4 === peg$FAILED) { + s4 = null; + } + if (s4 !== peg$FAILED) { + s5 = peg$parsearg_type(); + if (s5 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c7(s1, s5); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$parsespace(); + if (s1 === peg$FAILED) { + s1 = null; + } + if (s1 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 61) { + s2 = peg$c5; + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c6); } + } + if (s2 !== peg$FAILED) { + s3 = peg$parsespace(); + if (s3 === peg$FAILED) { + s3 = null; + } + if (s3 !== peg$FAILED) { + s4 = peg$parsearg_type(); + if (s4 === peg$FAILED) { + s4 = null; + } + if (s4 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c8(s4); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$parseargument_name(); + if (s1 !== peg$FAILED) { + s2 = peg$parsespace(); + if (s2 === peg$FAILED) { + s2 = null; + } + if (s2 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 61) { + s3 = peg$c5; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c6); } + } + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c9(s1); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$parsearg_type(); + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c10(s1); + } + s0 = s1; + } + } + } + + return s0; + } + + function peg$parsearg_type() { + var s0, s1; + + s0 = peg$parsevariable_get(); + if (s0 === peg$FAILED) { + s0 = peg$parseseries_type(); + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$parseliteral(); + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c11(s1); + } + s0 = s1; + } + } + + return s0; + } + + function peg$parsevariable_get() { + var s0, s1, s2; + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 36) { + s1 = peg$c12; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c13); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parseargument_name(); + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c14(s2); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsevariable_set() { + var s0, s1, s2, s3, s4, s5, s6; + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 36) { + s1 = peg$c12; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c13); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parseargument_name(); + if (s2 !== peg$FAILED) { + s3 = peg$parsespace(); + if (s3 === peg$FAILED) { + s3 = null; + } + if (s3 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 61) { + s4 = peg$c5; + peg$currPos++; + } else { + s4 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c6); } + } + if (s4 !== peg$FAILED) { + s5 = peg$parsespace(); + if (s5 === peg$FAILED) { + s5 = null; + } + if (s5 !== peg$FAILED) { + s6 = peg$parsearg_type(); + if (s6 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c15(s2, s6); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseseries_type() { + var s0; + + s0 = peg$parsevariable_set(); + if (s0 === peg$FAILED) { + s0 = peg$parsevariable_get(); + if (s0 === peg$FAILED) { + s0 = peg$parsegroup(); + if (s0 === peg$FAILED) { + s0 = peg$parsechain(); + if (s0 === peg$FAILED) { + s0 = peg$parsereference(); + } + } + } + } + + return s0; + } + + function peg$parseseries() { + var s0, s1, s2, s3, s4, s5, s6, s7; + + s0 = peg$currPos; + s1 = peg$parseseries_type(); + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$currPos; + s4 = peg$parsespace(); + if (s4 === peg$FAILED) { + s4 = null; + } + if (s4 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 44) { + s5 = peg$c1; + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c2); } + } + if (s5 !== peg$FAILED) { + s6 = peg$parsespace(); + if (s6 === peg$FAILED) { + s6 = null; + } + if (s6 !== peg$FAILED) { + s7 = peg$parseseries_type(); + if (s7 !== peg$FAILED) { + peg$savedPos = s3; + s4 = peg$c16(s1, s7); + s3 = s4; + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$currPos; + s4 = peg$parsespace(); + if (s4 === peg$FAILED) { + s4 = null; + } + if (s4 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 44) { + s5 = peg$c1; + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c2); } + } + if (s5 !== peg$FAILED) { + s6 = peg$parsespace(); + if (s6 === peg$FAILED) { + s6 = null; + } + if (s6 !== peg$FAILED) { + s7 = peg$parseseries_type(); + if (s7 !== peg$FAILED) { + peg$savedPos = s3; + s4 = peg$c16(s1, s7); + s3 = s4; + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } + if (s2 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 44) { + s3 = peg$c1; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c2); } + } + if (s3 === peg$FAILED) { + s3 = null; + } + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c17(s1, s2); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsefunction_name() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + s1 = []; + if (peg$c18.test(input.charAt(peg$currPos))) { + s2 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c19); } + } + if (s2 !== peg$FAILED) { + while (s2 !== peg$FAILED) { + s1.push(s2); + if (peg$c18.test(input.charAt(peg$currPos))) { + s2 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c19); } + } + } + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + s2 = []; + if (peg$c20.test(input.charAt(peg$currPos))) { + s3 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c21); } + } + while (s3 !== peg$FAILED) { + s2.push(s3); + if (peg$c20.test(input.charAt(peg$currPos))) { + s3 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c21); } + } + } + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c22(s1, s2); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseargument_name() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + s1 = []; + if (peg$c18.test(input.charAt(peg$currPos))) { + s2 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c19); } + } + if (s2 !== peg$FAILED) { + while (s2 !== peg$FAILED) { + s1.push(s2); + if (peg$c18.test(input.charAt(peg$currPos))) { + s2 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c19); } + } + } + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + s2 = []; + if (peg$c20.test(input.charAt(peg$currPos))) { + s3 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c21); } + } + while (s3 !== peg$FAILED) { + s2.push(s3); + if (peg$c20.test(input.charAt(peg$currPos))) { + s3 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c21); } + } + } + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c23(s1, s2); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsefunction() { + var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10; + + peg$silentFails++; + s0 = peg$currPos; + s1 = peg$parsespace(); + if (s1 === peg$FAILED) { + s1 = null; + } + if (s1 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 46) { + s2 = peg$c25; + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c26); } + } + if (s2 !== peg$FAILED) { + s3 = peg$parsefunction_name(); + if (s3 !== peg$FAILED) { + s4 = peg$parsespace(); + if (s4 === peg$FAILED) { + s4 = null; + } + if (s4 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 40) { + s5 = peg$c27; + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c28); } + } + if (s5 !== peg$FAILED) { + s6 = peg$parsespace(); + if (s6 === peg$FAILED) { + s6 = null; + } + if (s6 !== peg$FAILED) { + s7 = peg$parsearg_list(); + if (s7 === peg$FAILED) { + s7 = null; + } + if (s7 !== peg$FAILED) { + s8 = peg$parsespace(); + if (s8 === peg$FAILED) { + s8 = null; + } + if (s8 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 41) { + s9 = peg$c29; + peg$currPos++; + } else { + s9 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c30); } + } + if (s9 !== peg$FAILED) { + s10 = peg$parsespace(); + if (s10 === peg$FAILED) { + s10 = null; + } + if (s10 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c31(s3, s7); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 46) { + s1 = peg$c25; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c26); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parsefunction_name(); + if (s2 === peg$FAILED) { + s2 = null; + } + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c32(s2); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c24); } + } + + return s0; + } + + function peg$parsereference() { + var s0, s1, s2, s3, s4; + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 64) { + s1 = peg$c33; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c34); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parseinteger(); + if (s2 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 58) { + s3 = peg$c35; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c36); } + } + if (s3 !== peg$FAILED) { + s4 = peg$parseinteger(); + if (s4 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c37(s2, s4); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 64) { + s1 = peg$c33; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c34); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parseinteger(); + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c38(s2); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } + + return s0; + } + + function peg$parsechain() { + var s0, s1, s2, s3, s4; + + s0 = peg$currPos; + s1 = peg$parsefunction(); + if (s1 !== peg$FAILED) { + s2 = peg$parsespace(); + if (s2 === peg$FAILED) { + s2 = null; + } + if (s2 !== peg$FAILED) { + s3 = []; + s4 = peg$parsefunction(); + while (s4 !== peg$FAILED) { + s3.push(s4); + s4 = peg$parsefunction(); + } + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c39(s1, s3); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsegroup() { + var s0, s1, s2, s3, s4, s5, s6, s7; + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 40) { + s1 = peg$c27; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c28); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parsespace(); + if (s2 === peg$FAILED) { + s2 = null; + } + if (s2 !== peg$FAILED) { + s3 = peg$parseseries(); + if (s3 !== peg$FAILED) { + s4 = peg$parsespace(); + if (s4 === peg$FAILED) { + s4 = null; + } + if (s4 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 41) { + s5 = peg$c29; + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c30); } + } + if (s5 !== peg$FAILED) { + s6 = []; + s7 = peg$parsefunction(); + while (s7 !== peg$FAILED) { + s6.push(s7); + s7 = peg$parsefunction(); + } + if (s6 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c40(s3, s6); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseliteral() { + var s0, s1, s2, s3; + + peg$silentFails++; + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 34) { + s1 = peg$c42; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c43); } + } + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parsedq_char(); + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parsedq_char(); + } + if (s2 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 34) { + s3 = peg$c42; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c43); } + } + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c44(s2); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 39) { + s1 = peg$c45; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c46); } + } + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parsesq_char(); + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parsesq_char(); + } + if (s2 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 39) { + s3 = peg$c45; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c46); } + } + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c44(s2); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + if (input.substr(peg$currPos, 4) === peg$c47) { + s1 = peg$c47; + peg$currPos += 4; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c48); } + } + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c49(); + } + s0 = s1; + if (s0 === peg$FAILED) { + s0 = peg$currPos; + if (input.substr(peg$currPos, 5) === peg$c50) { + s1 = peg$c50; + peg$currPos += 5; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c51); } + } + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c52(); + } + s0 = s1; + if (s0 === peg$FAILED) { + s0 = peg$currPos; + if (input.substr(peg$currPos, 4) === peg$c53) { + s1 = peg$c53; + peg$currPos += 4; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c54); } + } + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c55(); + } + s0 = s1; + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = []; + if (peg$c56.test(input.charAt(peg$currPos))) { + s2 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c57); } + } + if (s2 !== peg$FAILED) { + while (s2 !== peg$FAILED) { + s1.push(s2); + if (peg$c56.test(input.charAt(peg$currPos))) { + s2 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c57); } + } + } + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c58(s1); + } + s0 = s1; + } + } + } + } + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c41); } + } + + return s0; + } + + function peg$parsespace() { + var s0, s1; + + s0 = []; + if (peg$c59.test(input.charAt(peg$currPos))) { + s1 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c60); } + } + if (s1 !== peg$FAILED) { + while (s1 !== peg$FAILED) { + s0.push(s1); + if (peg$c59.test(input.charAt(peg$currPos))) { + s1 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c60); } + } + } + } else { + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsedq_char() { + var s0, s1, s2; + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 92) { + s1 = peg$c61; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c62); } + } + if (s1 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 34) { + s2 = peg$c42; + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c43); } + } + if (s2 === peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 92) { + s2 = peg$c61; + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c62); } + } + } + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c63(s2); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + if (peg$c64.test(input.charAt(peg$currPos))) { + s0 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c65); } + } + } + + return s0; + } + + function peg$parsesq_char() { + var s0, s1, s2; + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 92) { + s1 = peg$c61; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c62); } + } + if (s1 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 39) { + s2 = peg$c45; + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c46); } + } + if (s2 === peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 92) { + s2 = peg$c61; + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c62); } + } + } + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c63(s2); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + if (peg$c66.test(input.charAt(peg$currPos))) { + s0 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c67); } + } + } + + return s0; + } + + function peg$parseinteger() { + var s0, s1, s2; + + s0 = peg$currPos; + s1 = []; + if (peg$c68.test(input.charAt(peg$currPos))) { + s2 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c69); } + } + if (s2 !== peg$FAILED) { + while (s2 !== peg$FAILED) { + s1.push(s2); + if (peg$c68.test(input.charAt(peg$currPos))) { + s2 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c69); } + } + } + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c70(s1); + } + s0 = s1; + + return s0; + } + + + function ltoo (literal) { + return {type: 'literal', value: literal} + } + + function simpleLocation (location) { + // Returns an object representing the position of the function within the expression, + // demarcated by the position of its first character and last character. We calculate these values + // using the offset because the expression could span multiple lines, and we don't want to deal + // with column and line values. + return { + min: location.start.offset, + max: location.end.offset + } + } + + var currentFunction; + var currentArgs = []; + + var functions = []; + var args = []; + var variables = {}; + + + + peg$result = peg$startRuleFunction(); + + if (peg$result !== peg$FAILED && peg$currPos === input.length) { + return peg$result; + } else { + if (peg$result !== peg$FAILED && peg$currPos < input.length) { + peg$fail({ type: "end", description: "end of input" }); + } + + throw peg$buildException( + null, + peg$maxFailExpected, + peg$maxFailPos < input.length ? input.charAt(peg$maxFailPos) : null, + peg$maxFailPos < input.length + ? peg$computeLocation(peg$maxFailPos, peg$maxFailPos + 1) + : peg$computeLocation(peg$maxFailPos, peg$maxFailPos) + ); + } + } + + return { + SyntaxError: peg$SyntaxError, + parse: peg$parse + }; +})(); \ No newline at end of file diff --git a/src/legacy/core_plugins/timelion/public/directives/__tests__/timelion_expression_input_helpers.js b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.test.ts similarity index 70% rename from src/legacy/core_plugins/timelion/public/directives/__tests__/timelion_expression_input_helpers.js rename to src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.test.ts index ea2d44bcaefe0..18a0c0872dc03 100644 --- a/src/legacy/core_plugins/timelion/public/directives/__tests__/timelion_expression_input_helpers.js +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.test.ts @@ -17,19 +17,16 @@ * under the License. */ -import expect from '@kbn/expect'; -import PEG from 'pegjs'; -import grammar from 'raw-loader!../../../../vis_type_timelion/public/chain.peg'; -import { SUGGESTION_TYPE, suggest } from '../timelion_expression_input_helpers'; -import { getArgValueSuggestions } from '../../../../vis_type_timelion/public/helpers/arg_value_suggestions'; -import { - setIndexPatterns, - setSavedObjectsClient, -} from '../../../../vis_type_timelion/public/helpers/plugin_services'; +import { SUGGESTION_TYPE, suggest } from './timelion_expression_input_helpers'; +import { getArgValueSuggestions } from '../helpers/arg_value_suggestions'; +import { setIndexPatterns, setSavedObjectsClient } from '../helpers/plugin_services'; +import { IndexPatterns } from 'src/plugins/data/public'; +import { SavedObjectsClient } from 'kibana/public'; +import { ITimelionFunction } from '../../common/types'; describe('Timelion expression suggestions', () => { - setIndexPatterns({}); - setSavedObjectsClient({}); + setIndexPatterns({} as IndexPatterns); + setSavedObjectsClient({} as SavedObjectsClient); const argValueSuggestions = getArgValueSuggestions(); @@ -45,17 +42,13 @@ describe('Timelion expression suggestions', () => { suggestions: [{ name: 'value1' }], }, ], - }; + } as ITimelionFunction; const myFunc2 = { name: 'myFunc2', chainable: false, args: [{ name: 'argA' }, { name: 'argAB' }, { name: 'argABC' }], - }; + } as ITimelionFunction; const functionList = [func1, myFunc2]; - let Parser; - beforeEach(function() { - Parser = PEG.generate(grammar); - }); describe('parse exception', () => { describe('incompleteFunction', () => { @@ -65,17 +58,12 @@ describe('Timelion expression suggestions', () => { const suggestions = await suggest( expression, functionList, - Parser, cursorPosition, argValueSuggestions ); - expect(suggestions).to.eql({ + expect(suggestions).toEqual({ list: [func1, myFunc2], - location: { - min: 0, - max: 1, - }, - type: 'functions', + type: SUGGESTION_TYPE.FUNCTIONS, }); }); it('should filter function suggestions by function name', async () => { @@ -84,17 +72,12 @@ describe('Timelion expression suggestions', () => { const suggestions = await suggest( expression, functionList, - Parser, cursorPosition, argValueSuggestions ); - expect(suggestions).to.eql({ + expect(suggestions).toEqual({ list: [myFunc2], - location: { - min: 0, - max: 4, - }, - type: 'functions', + type: SUGGESTION_TYPE.FUNCTIONS, }); }); }); @@ -106,17 +89,12 @@ describe('Timelion expression suggestions', () => { const suggestions = await suggest( expression, functionList, - Parser, cursorPosition, argValueSuggestions ); - expect(suggestions).to.eql({ + expect(suggestions).toEqual({ list: [], - location: { - min: 11, - max: 12, - }, - type: 'arguments', + type: SUGGESTION_TYPE.ARGUMENTS, }); }); @@ -126,17 +104,12 @@ describe('Timelion expression suggestions', () => { const suggestions = await suggest( expression, functionList, - Parser, cursorPosition, argValueSuggestions ); - expect(suggestions).to.eql({ + expect(suggestions).toEqual({ list: myFunc2.args, - location: { - min: 9, - max: 10, - }, - type: 'arguments', + type: SUGGESTION_TYPE.ARGUMENTS, }); }); @@ -146,17 +119,12 @@ describe('Timelion expression suggestions', () => { const suggestions = await suggest( expression, functionList, - Parser, cursorPosition, argValueSuggestions ); - expect(suggestions).to.eql({ + expect(suggestions).toEqual({ list: myFunc2.args, - location: { - min: 9, - max: 25, - }, - type: 'arguments', + type: SUGGESTION_TYPE.ARGUMENTS, }); }); @@ -166,17 +134,12 @@ describe('Timelion expression suggestions', () => { const suggestions = await suggest( expression, functionList, - Parser, cursorPosition, argValueSuggestions ); - expect(suggestions).to.eql({ + expect(suggestions).toEqual({ list: [{ name: 'argA' }, { name: 'argAB', suggestions: [{ name: 'value1' }] }], - location: { - min: 7, - max: 8, - }, - type: 'arguments', + type: SUGGESTION_TYPE.ARGUMENTS, }); }); @@ -186,17 +149,12 @@ describe('Timelion expression suggestions', () => { const suggestions = await suggest( expression, functionList, - Parser, cursorPosition, argValueSuggestions ); - expect(suggestions).to.eql({ + expect(suggestions).toEqual({ list: [{ name: 'argA' }, { name: 'argABC' }], - location: { - min: 24, - max: 25, - }, - type: 'arguments', + type: SUGGESTION_TYPE.ARGUMENTS, }); }); }); @@ -208,17 +166,12 @@ describe('Timelion expression suggestions', () => { const suggestions = await suggest( expression, functionList, - Parser, cursorPosition, argValueSuggestions ); - expect(suggestions).to.eql({ + expect(suggestions).toEqual({ list: [], - location: { - min: 11, - max: 11, - }, - type: 'argument_value', + type: SUGGESTION_TYPE.ARGUMENT_VALUE, }); }); @@ -228,17 +181,12 @@ describe('Timelion expression suggestions', () => { const suggestions = await suggest( expression, functionList, - Parser, cursorPosition, argValueSuggestions ); - expect(suggestions).to.eql({ + expect(suggestions).toEqual({ list: [{ name: 'value1' }], - location: { - min: 11, - max: 11, - }, - type: 'argument_value', + type: SUGGESTION_TYPE.ARGUMENT_VALUE, }); }); }); @@ -252,17 +200,12 @@ describe('Timelion expression suggestions', () => { const suggestions = await suggest( expression, functionList, - Parser, cursorPosition, argValueSuggestions ); - expect(suggestions).to.eql({ + expect(suggestions).toEqual({ list: [func1], - location: { - min: 0, - max: 8, - }, - type: 'functions', + type: SUGGESTION_TYPE.FUNCTIONS, }); }); }); @@ -275,17 +218,12 @@ describe('Timelion expression suggestions', () => { const suggestions = await suggest( expression, functionList, - Parser, cursorPosition, argValueSuggestions ); - expect(suggestions).to.eql({ + expect(suggestions).toEqual({ list: myFunc2.args, - location: { - min: 9, - max: 9, - }, - type: 'arguments', + type: SUGGESTION_TYPE.ARGUMENTS, }); }); it('should not provide argument suggestions for argument that is all ready set in function def', async () => { @@ -294,18 +232,12 @@ describe('Timelion expression suggestions', () => { const suggestions = await suggest( expression, functionList, - Parser, cursorPosition, argValueSuggestions ); - expect(suggestions.type).to.equal(SUGGESTION_TYPE.ARGUMENTS); - expect(suggestions).to.eql({ + expect(suggestions).toEqual({ list: [{ name: 'argA' }, { name: 'argABC' }], - location: { - min: 24, - max: 24, - }, - type: 'arguments', + type: SUGGESTION_TYPE.ARGUMENTS, }); }); it('should filter argument suggestions by argument name', async () => { @@ -314,17 +246,12 @@ describe('Timelion expression suggestions', () => { const suggestions = await suggest( expression, functionList, - Parser, cursorPosition, argValueSuggestions ); - expect(suggestions).to.eql({ + expect(suggestions).toEqual({ list: [{ name: 'argAB' }, { name: 'argABC' }], - location: { - min: 9, - max: 14, - }, - type: 'arguments', + type: SUGGESTION_TYPE.ARGUMENTS, }); }); it('should not show first argument for chainable functions', async () => { @@ -333,17 +260,12 @@ describe('Timelion expression suggestions', () => { const suggestions = await suggest( expression, functionList, - Parser, cursorPosition, argValueSuggestions ); - expect(suggestions).to.eql({ + expect(suggestions).toEqual({ list: [{ name: 'argA' }, { name: 'argAB', suggestions: [{ name: 'value1' }] }], - location: { - min: 7, - max: 7, - }, - type: 'arguments', + type: SUGGESTION_TYPE.ARGUMENTS, }); }); }); @@ -354,17 +276,12 @@ describe('Timelion expression suggestions', () => { const suggestions = await suggest( expression, functionList, - Parser, cursorPosition, argValueSuggestions ); - expect(suggestions).to.eql({ + expect(suggestions).toEqual({ list: [], - location: { - min: 14, - max: 16, - }, - type: 'argument_value', + type: SUGGESTION_TYPE.ARGUMENT_VALUE, }); }); @@ -374,17 +291,12 @@ describe('Timelion expression suggestions', () => { const suggestions = await suggest( expression, functionList, - Parser, cursorPosition, argValueSuggestions ); - expect(suggestions).to.eql({ + expect(suggestions).toEqual({ list: [{ name: 'value1' }], - location: { - min: 13, - max: 16, - }, - type: 'argument_value', + type: SUGGESTION_TYPE.ARGUMENT_VALUE, }); }); }); diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.ts b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.ts index 5aa05fb16466b..d7818680c9543 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.ts @@ -18,18 +18,17 @@ */ import { get, startsWith } from 'lodash'; -import PEG from 'pegjs'; import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; +import { i18n } from '@kbn/i18n'; +import { Parser } from 'pegjs'; + // @ts-ignore -import grammar from 'raw-loader!../chain.peg'; +import { parse } from '../_generated_/chain'; -import { i18n } from '@kbn/i18n'; import { ITimelionFunction, TimelionFunctionArgs } from '../../common/types'; import { ArgValueSuggestions, FunctionArg, Location } from '../helpers/arg_value_suggestions'; -const Parser = PEG.generate(grammar); - export enum SUGGESTION_TYPE { ARGUMENTS = 'arguments', ARGUMENT_VALUE = 'argument_value', @@ -57,7 +56,7 @@ function getArgumentsHelp( } async function extractSuggestionsFromParsedResult( - result: ReturnType, + result: ReturnType, cursorPosition: number, functionList: ITimelionFunction[], argValueSuggestions: ArgValueSuggestions @@ -141,7 +140,7 @@ export async function suggest( argValueSuggestions: ArgValueSuggestions ) { try { - const result = await Parser.parse(expression); + const result = await parse(expression); return await extractSuggestionsFromParsedResult( result, diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/agg_row.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/agg_row.js index b07e1cef4fc81..a2f1640904dd0 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/agg_row.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/agg_row.js @@ -22,14 +22,13 @@ import React from 'react'; import { last } from 'lodash'; import { AddDeleteButtons } from '../add_delete_buttons'; import { EuiIcon, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { injectI18n } from '@kbn/i18n/react'; import { SeriesDragHandler } from '../series_drag_handler'; +import { i18n } from '@kbn/i18n'; -function AggRowUi(props) { +export function AggRow(props) { let iconType = 'eyeClosed'; let iconColor = 'subdued'; const lastSibling = last(props.siblings); - const { intl } = props; if (lastSibling.id === props.model.id) { iconType = 'eye'; @@ -57,12 +56,10 @@ function AggRowUi(props) { { return value === option.value && isMetricEnabled(option.value, uiRestrictions); @@ -247,8 +247,7 @@ function AggSelectUi(props) { options = [ { - label: intl.formatMessage({ - id: 'visTypeTimeseries.aggSelect.aggGroups.metricAggLabel', + label: i18n.translate('visTypeTimeseries.aggSelect.aggGroups.metricAggLabel', { defaultMessage: 'Metric Aggregations', }), options: metricAggs.map(agg => ({ @@ -257,22 +256,19 @@ function AggSelectUi(props) { })), }, { - label: intl.formatMessage({ - id: 'visTypeTimeseries.aggSelect.aggGroups.parentPipelineAggLabel', + label: i18n.translate('visTypeTimeseries.aggSelect.aggGroups.parentPipelineAggLabel', { defaultMessage: 'Parent Pipeline Aggregations', }), options: pipelineAggs.filter(filterByPanelType(panelType)).map(disableSiblingAggs), }, { - label: intl.formatMessage({ - id: 'visTypeTimeseries.aggSelect.aggGroups.siblingPipelineAggLabel', + label: i18n.translate('visTypeTimeseries.aggSelect.aggGroups.siblingPipelineAggLabel', { defaultMessage: 'Sibling Pipeline Aggregations', }), options: siblingAggs.map(disableSiblingAggs), }, { - label: intl.formatMessage({ - id: 'visTypeTimeseries.aggSelect.aggGroups.specialAggLabel', + label: i18n.translate('visTypeTimeseries.aggSelect.aggGroups.specialAggLabel', { defaultMessage: 'Special Aggregations', }), options: specialAggs.map(disableSiblingAggs), @@ -289,8 +285,7 @@ function AggSelectUi(props) {
Array.isArray(model.variables) && model.script !== undefined; - render() { - const { siblings } = this.props; +export function CalculationAgg(props) { + const htmlId = htmlIdGenerator(); + const { siblings, model } = props; - const defaults = { script: '' }; - const model = { ...defaults, ...this.props.model }; + const handleChange = createChangeHandler(props.onChange, model); + const handleSelectChange = createSelectHandler(handleChange); + const handleTextChange = createTextHandler(handleChange); - const handleChange = createChangeHandler(this.props.onChange, model); - const handleSelectChange = createSelectHandler(handleChange); - const handleTextChange = createTextHandler(handleChange); + useEffect(() => { + if (!checkModel(model)) { + handleChange({ + variables: [newVariable()], + script: '', + }); + } + }, [handleChange, model]); - const htmlId = htmlIdGenerator(); + return ( + + + + + + + + + - return ( - - - - - - - - + + - + + + + - - + + - - - - - - - - } - fullWidth - helpText={ -
- params, - paramsName: params.<name>, - paramsInterval: params._interval, - }} - /> -
- } - > - -
-
-
-
- ); - } + values={{ + params: params, + paramsName: params.<name>, + paramsInterval: params._interval, + }} + /> +
+ } + > + + +
+ + + ); } CalculationAgg.propTypes = { diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/math.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/math.js index c62012927f951..5ffcef12c3fcb 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/math.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/math.js @@ -17,17 +17,15 @@ * under the License. */ -import React, { Component } from 'react'; +import React, { useEffect } from 'react'; import PropTypes from 'prop-types'; -import _ from 'lodash'; -import uuid from 'uuid'; import { AggRow } from './agg_row'; import { AggSelect } from './agg_select'; import { createChangeHandler } from '../lib/create_change_handler'; import { createSelectHandler } from '../lib/create_select_handler'; import { createTextHandler } from '../lib/create_text_handler'; -import { CalculationVars } from './vars'; +import { CalculationVars, newVariable } from './vars'; import { htmlIdGenerator, EuiFlexGroup, @@ -41,125 +39,121 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -export class MathAgg extends Component { - UNSAFE_componentWillMount() { - if (!this.props.model.variables) { - this.props.onChange( - _.assign({}, this.props.model, { - variables: [{ id: uuid.v1() }], - }) - ); - } - } +const checkModel = model => Array.isArray(model.variables) && model.script !== undefined; - render() { - const { siblings } = this.props; - const htmlId = htmlIdGenerator(); +export function MathAgg(props) { + const { siblings, model } = props; + const htmlId = htmlIdGenerator(); - const defaults = { script: '' }; - const model = { ...defaults, ...this.props.model }; + const handleChange = createChangeHandler(props.onChange, model); + const handleSelectChange = createSelectHandler(handleChange); + const handleTextChange = createTextHandler(handleChange); - const handleChange = createChangeHandler(this.props.onChange, model); - const handleSelectChange = createSelectHandler(handleChange); - const handleTextChange = createTextHandler(handleChange); + useEffect(() => { + if (!checkModel(model)) { + handleChange({ + variables: [newVariable()], + script: '', + }); + } + }, [handleChange, model]); - return ( - - - - - - - - + + + + - + + + + - - - - - - + + - + + + + - - - } - fullWidth - helpText={ - + } + fullWidth + helpText={ + - - - ), - params: params, - paramsName: params.<name>, - paramsValues: params._all.<name>.values, - paramsTimestamps: params._all.<name>.timestamps, - paramsTimestamp: params._timestamp, - paramsIndex: params._index, - paramsInterval: params._interval, - }} - /> - } - > - + + + ), + params: params, + paramsName: params.<name>, + paramsValues: params._all.<name>.values, + paramsTimestamps: params._all.<name>.timestamps, + paramsTimestamp: params._timestamp, + paramsIndex: params._index, + paramsInterval: params._interval, + }} /> - - - - - ); - } + } + > + + + + + + ); } MathAgg.propTypes = { diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile.js index 3ce5be5b6875a..500bba14d66c9 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile.js @@ -18,8 +18,7 @@ */ import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import _ from 'lodash'; +import React, { useEffect } from 'react'; import { AggSelect } from './agg_select'; import { FieldSelect } from './field_select'; import { AggRow } from './agg_row'; @@ -39,82 +38,78 @@ import { Percentiles, newPercentile } from './percentile_ui'; const RESTRICT_FIELDS = [KBN_FIELD_TYPES.NUMBER]; -export class PercentileAgg extends Component { - // eslint-disable-line react/no-multi-comp +const checkModel = model => Array.isArray(model.percentiles); - UNSAFE_componentWillMount() { - if (!this.props.model.percentiles) { - this.props.onChange( - _.assign({}, this.props.model, { - percentiles: [newPercentile({ value: 50 })], - }) - ); - } - } +export function PercentileAgg(props) { + const { series, model, panel, fields } = props; + const htmlId = htmlIdGenerator(); - render() { - const { series, model, panel, fields } = this.props; + const handleChange = createChangeHandler(props.onChange, model); + const handleSelectChange = createSelectHandler(handleChange); + const indexPattern = + (series.override_index_pattern && series.series_index_pattern) || panel.index_pattern; - const handleChange = createChangeHandler(this.props.onChange, model); - const handleSelectChange = createSelectHandler(handleChange); - const indexPattern = - (series.override_index_pattern && series.series_index_pattern) || panel.index_pattern; - const htmlId = htmlIdGenerator(); + useEffect(() => { + if (!checkModel(model)) { + handleChange({ + percentiles: [newPercentile({ value: 50 })], + }); + } + }, [handleChange, model]); - return ( - - - - + return ( + + + + + + + + + + + - - - + - - - - } - > - - - - + + + - + - - - ); - } + + + ); } PercentileAgg.propTypes = { diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/vars.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/vars.js index 8553f08af1353..3d4ce601c7244 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/vars.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/vars.js @@ -19,14 +19,17 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; +import uuid from 'uuid'; +import { i18n } from '@kbn/i18n'; import _ from 'lodash'; import { AddDeleteButtons } from '../add_delete_buttons'; import { collectionActions } from '../lib/collection_actions'; import { MetricSelect } from './metric_select'; import { EuiFlexGroup, EuiFlexItem, EuiFieldText } from '@elastic/eui'; -import { injectI18n } from '@kbn/i18n/react'; -class CalculationVarsUi extends Component { +export const newVariable = opts => ({ id: uuid.v1(), name: '', field: '', ...opts }); + +export class CalculationVars extends Component { constructor(props) { super(props); this.renderRow = this.renderRow.bind(this); @@ -42,21 +45,19 @@ class CalculationVarsUi extends Component { } renderRow(row, i, items) { - const handleAdd = collectionActions.handleAdd.bind(null, this.props); + const handleAdd = collectionActions.handleAdd.bind(null, this.props, newVariable); const handleDelete = collectionActions.handleDelete.bind(null, this.props, row); - const { intl } = this.props; + return ( ({ name: 'area', - title: i18n.translate('kbnVislibVisTypes.area.areaTitle', { defaultMessage: 'Area' }), + title: i18n.translate('visTypeVislib.area.areaTitle', { defaultMessage: 'Area' }), icon: 'visArea', - description: i18n.translate('kbnVislibVisTypes.area.areaDescription', { + description: i18n.translate('visTypeVislib.area.areaDescription', { defaultMessage: 'Emphasize the quantity beneath a line chart', }), visualization: createVislibVisController(deps), @@ -136,7 +136,7 @@ export const createAreaVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) { group: AggGroupNames.Metrics, name: 'metric', - title: i18n.translate('kbnVislibVisTypes.area.metricsTitle', { + title: i18n.translate('visTypeVislib.area.metricsTitle', { defaultMessage: 'Y-axis', }), aggFilter: ['!geo_centroid', '!geo_bounds'], @@ -146,7 +146,7 @@ export const createAreaVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) { group: AggGroupNames.Metrics, name: 'radius', - title: i18n.translate('kbnVislibVisTypes.area.radiusTitle', { + title: i18n.translate('visTypeVislib.area.radiusTitle', { defaultMessage: 'Dot size', }), min: 0, @@ -156,7 +156,7 @@ export const createAreaVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) { group: AggGroupNames.Buckets, name: 'segment', - title: i18n.translate('kbnVislibVisTypes.area.segmentTitle', { + title: i18n.translate('visTypeVislib.area.segmentTitle', { defaultMessage: 'X-axis', }), min: 0, @@ -166,7 +166,7 @@ export const createAreaVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) { group: AggGroupNames.Buckets, name: 'group', - title: i18n.translate('kbnVislibVisTypes.area.groupTitle', { + title: i18n.translate('visTypeVislib.area.groupTitle', { defaultMessage: 'Split series', }), min: 0, @@ -176,7 +176,7 @@ export const createAreaVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) { group: AggGroupNames.Buckets, name: 'split', - title: i18n.translate('kbnVislibVisTypes.area.splitTitle', { + title: i18n.translate('visTypeVislib.area.splitTitle', { defaultMessage: 'Split chart', }), min: 0, diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/common/basic_options.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/common/basic_options.tsx index 81174d63060e5..229945621fe76 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/common/basic_options.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/common/basic_options.tsx @@ -37,7 +37,7 @@ function BasicOptions({ return ( <> ({ setValue={setValue} /> @@ -79,11 +79,11 @@ function ColorSchemaOptions({ disabled={disabled} helpText={ showHelpText && - i18n.translate('kbnVislibVisTypes.controls.colorSchema.howToChangeColorsDescription', { + i18n.translate('visTypeVislib.controls.colorSchema.howToChangeColorsDescription', { defaultMessage: 'Individual colors can be changed in the legend.', }) } - label={i18n.translate('kbnVislibVisTypes.controls.colorSchema.colorSchemaLabel', { + label={i18n.translate('visTypeVislib.controls.colorSchema.colorSchemaLabel', { defaultMessage: 'Color schema', })} labelAppend={isCustomColors && resetColorsButton} @@ -95,7 +95,7 @@ function ColorSchemaOptions({ ({ const [stateValue, setStateValue] = useState(value); const [isValidState, setIsValidState] = useState(true); - const error = i18n.translate('kbnVislibVisTypes.controls.rangeErrorMessage', { + const error = i18n.translate('visTypeVislib.controls.rangeErrorMessage', { defaultMessage: 'Values must be on or between {min} and {max}', values: { min, max }, }); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/common/select.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/common/select.tsx index 45b1bdd44f460..8ce7f4d640898 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/common/select.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/common/select.tsx @@ -30,6 +30,7 @@ interface SelectOptionProps void; + 'data-test-subj'?: string; } const emptyValue = { text: '', value: 'EMPTY_VALUE', disabled: true, hidden: true }; @@ -44,6 +45,7 @@ function SelectOption) { const availableOptions = useMemo(() => [emptyValue, ...options], [options]); @@ -63,6 +65,7 @@ function SelectOption setValue(paramName, ev.target.value as ValidParamValues)} fullWidth={true} + data-test-subj={dataTestSubj} /> ); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/common/truncate_labels.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/common/truncate_labels.tsx index 81772107bc729..2f0cb701848d0 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/common/truncate_labels.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/common/truncate_labels.tsx @@ -33,7 +33,7 @@ function TruncateLabelsOption({ disabled, value = null, setValue }: TruncateLabe return (

@@ -39,7 +39,7 @@ function LabelsPanel({ stateParams, setValue, setGaugeValue }: GaugeOptionsInter

@@ -76,10 +76,10 @@ function RangesPanel({

@@ -44,7 +44,7 @@ function StylePanel({ aggs, setGaugeValue, stateParams, vis }: GaugeOptionsInter ) {

@@ -78,13 +78,13 @@ function HeatmapOptions(props: VisOptionsProps) { ) {

@@ -114,7 +114,7 @@ function HeatmapOptions(props: VisOptionsProps) { ) { /> ) { ) { data-test-subj="heatmapColorsNumber" disabled={stateParams.setColorRange} isInvalid={isColorsNumberInvalid} - label={i18n.translate('kbnVislibVisTypes.controls.heatmapOptions.colorsNumberLabel', { + label={i18n.translate('visTypeVislib.controls.heatmapOptions.colorsNumberLabel', { defaultMessage: 'Number of colors', })} max={10} @@ -160,7 +159,7 @@ function HeatmapOptions(props: VisOptionsProps) {

@@ -73,7 +73,7 @@ function LabelsPanel({ valueAxis, setValue }: LabelsPanelProps) { diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/category_axis_panel.test.tsx.snap b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/category_axis_panel.test.tsx.snap index d88654cfdc0c4..037989a86af01 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/category_axis_panel.test.tsx.snap +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/category_axis_panel.test.tsx.snap @@ -10,7 +10,7 @@ exports[`CategoryAxisPanel component should init with the default set of props 1

@@ -19,6 +19,7 @@ exports[`CategoryAxisPanel component should init with the default set of props 1 size="s" /> diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/value_axes_panel.test.tsx.snap b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/value_axes_panel.test.tsx.snap index 8d20765fe3591..f589a69eecbc3 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/value_axes_panel.test.tsx.snap +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/value_axes_panel.test.tsx.snap @@ -18,7 +18,7 @@ exports[`ValueAxesPanel component should init with the default set of props 1`]

diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/value_axis_options.test.tsx.snap b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/value_axis_options.test.tsx.snap index f2ee088450fbd..caf38c09783f0 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/value_axis_options.test.tsx.snap +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/value_axis_options.test.tsx.snap @@ -85,6 +85,7 @@ exports[`ValueAxisOptions component should init with the default set of props 1` margin="m" />

@@ -70,17 +70,18 @@ function CategoryAxisPanel(props: CategoryAxisPanelProps) {

@@ -73,12 +73,9 @@ function LabelOptions({ stateParams, setValue, axis, axesName, index }: LabelOpt

@@ -57,7 +57,7 @@ function SeriesPanel(props: SeriesPanelProps) { buttonContent={chart.data.label} buttonContentClassName="visEditorSidebar__aggGroupAccordionButtonContent eui-textTruncate" aria-label={i18n.translate( - 'kbnVislibVisTypes.controls.pointSeries.seriesAccordionAriaLabel', + 'visTypeVislib.controls.pointSeries.seriesAccordionAriaLabel', { defaultMessage: 'Toggle {agg} options', values: { agg: chart.data.label }, diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axes_panel.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axes_panel.tsx index eb0ab4333af59..b94f5ebbcce44 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axes_panel.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axes_panel.tsx @@ -60,7 +60,7 @@ function ValueAxesPanel(props: ValueAxesPanelProps) { const removeButtonTooltip = useMemo( () => - i18n.translate('kbnVislibVisTypes.controls.pointSeries.valueAxes.removeButtonTooltip', { + i18n.translate('visTypeVislib.controls.pointSeries.valueAxes.removeButtonTooltip', { defaultMessage: 'Remove Y-axis', }), [] @@ -83,7 +83,7 @@ function ValueAxesPanel(props: ValueAxesPanelProps) { const addButtonTooltip = useMemo( () => - i18n.translate('kbnVislibVisTypes.controls.pointSeries.valueAxes.addButtonTooltip', { + i18n.translate('visTypeVislib.controls.pointSeries.valueAxes.addButtonTooltip', { defaultMessage: 'Add Y-axis', }), [] @@ -111,7 +111,7 @@ function ValueAxesPanel(props: ValueAxesPanelProps) {

@@ -142,7 +142,7 @@ function ValueAxesPanel(props: ValueAxesPanelProps) { buttonClassName="eui-textTruncate" buttonContentClassName="visEditorSidebar__aggGroupAccordionButtonContent eui-textTruncate" aria-label={i18n.translate( - 'kbnVislibVisTypes.controls.pointSeries.valueAxes.toggleOptionsAriaLabel', + 'visTypeVislib.controls.pointSeries.valueAxes.toggleOptionsAriaLabel', { defaultMessage: 'Toggle {axisName} options', values: { axisName: axis.name }, diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axis_options.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axis_options.tsx index b4ea4cb42ee60..0ebe62a70a7b1 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axis_options.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axis_options.tsx @@ -119,7 +119,7 @@ function ValueAxisOptions(props: ValueAxisOptionsParams) { return ( <> ) {

) {

) { setValue={setLabels} /> ) { setValue={setLabels} />

@@ -72,14 +72,14 @@ function GridPanel({ stateParams, setValue, hasHistogramAgg }: VisOptionsProps)

@@ -46,7 +46,7 @@ function PointSeriesOptions(props: ValidationVisOptionsProps) {vis.hasSchemaAgg('segment', 'date_histogram') ? ( ) /> ) : ( ) {vis.type.type === 'histogram' && (

@@ -63,7 +63,7 @@ function ThresholdPanel({ diff --git a/src/legacy/core_plugins/vis_type_vislib/public/gauge.ts b/src/legacy/core_plugins/vis_type_vislib/public/gauge.ts index 5dcc8ad16918d..f235ed4bb04e4 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/gauge.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/gauge.ts @@ -56,9 +56,9 @@ export interface GaugeVisParams { export const createGaugeVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) => ({ name: 'gauge', - title: i18n.translate('kbnVislibVisTypes.gauge.gaugeTitle', { defaultMessage: 'Gauge' }), + title: i18n.translate('visTypeVislib.gauge.gaugeTitle', { defaultMessage: 'Gauge' }), icon: 'visGauge', - description: i18n.translate('kbnVislibVisTypes.gauge.gaugeDescription', { + description: i18n.translate('visTypeVislib.gauge.gaugeDescription', { defaultMessage: "Gauges indicate the status of a metric. Use it to show how a metric's value relates to reference threshold values.", }), @@ -116,7 +116,7 @@ export const createGaugeVisTypeDefinition = (deps: KbnVislibVisTypesDependencies { group: AggGroupNames.Metrics, name: 'metric', - title: i18n.translate('kbnVislibVisTypes.gauge.metricTitle', { defaultMessage: 'Metric' }), + title: i18n.translate('visTypeVislib.gauge.metricTitle', { defaultMessage: 'Metric' }), min: 1, aggFilter: [ '!std_dev', @@ -134,7 +134,7 @@ export const createGaugeVisTypeDefinition = (deps: KbnVislibVisTypesDependencies { group: AggGroupNames.Buckets, name: 'group', - title: i18n.translate('kbnVislibVisTypes.gauge.groupTitle', { + title: i18n.translate('visTypeVislib.gauge.groupTitle', { defaultMessage: 'Split group', }), min: 0, diff --git a/src/legacy/core_plugins/vis_type_vislib/public/goal.ts b/src/legacy/core_plugins/vis_type_vislib/public/goal.ts index 302d5f6393ef9..94262629669be 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/goal.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/goal.ts @@ -27,9 +27,9 @@ import { KbnVislibVisTypesDependencies } from './plugin'; export const createGoalVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) => ({ name: 'goal', - title: i18n.translate('kbnVislibVisTypes.goal.goalTitle', { defaultMessage: 'Goal' }), + title: i18n.translate('visTypeVislib.goal.goalTitle', { defaultMessage: 'Goal' }), icon: 'visGoal', - description: i18n.translate('kbnVislibVisTypes.goal.goalDescription', { + description: i18n.translate('visTypeVislib.goal.goalDescription', { defaultMessage: 'A goal chart indicates how close you are to your final goal.', }), visualization: createVislibVisController(deps), @@ -80,7 +80,7 @@ export const createGoalVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) { group: AggGroupNames.Metrics, name: 'metric', - title: i18n.translate('kbnVislibVisTypes.goal.metricTitle', { defaultMessage: 'Metric' }), + title: i18n.translate('visTypeVislib.goal.metricTitle', { defaultMessage: 'Metric' }), min: 1, aggFilter: [ '!std_dev', @@ -98,7 +98,7 @@ export const createGoalVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) { group: AggGroupNames.Buckets, name: 'group', - title: i18n.translate('kbnVislibVisTypes.goal.groupTitle', { + title: i18n.translate('visTypeVislib.goal.groupTitle', { defaultMessage: 'Split group', }), min: 0, diff --git a/src/legacy/core_plugins/vis_type_vislib/public/heatmap.ts b/src/legacy/core_plugins/vis_type_vislib/public/heatmap.ts index eb5f84b409838..470e978027ca2 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/heatmap.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/heatmap.ts @@ -41,9 +41,9 @@ export interface HeatmapVisParams extends CommonVislibParams, ColorSchemaVislibP export const createHeatmapVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) => ({ name: 'heatmap', - title: i18n.translate('kbnVislibVisTypes.heatmap.heatmapTitle', { defaultMessage: 'Heat Map' }), + title: i18n.translate('visTypeVislib.heatmap.heatmapTitle', { defaultMessage: 'Heat Map' }), icon: 'visHeatmap', - description: i18n.translate('kbnVislibVisTypes.heatmap.heatmapDescription', { + description: i18n.translate('visTypeVislib.heatmap.heatmapDescription', { defaultMessage: 'Shade cells within a matrix', }), visualization: createVislibVisController(deps), @@ -90,7 +90,7 @@ export const createHeatmapVisTypeDefinition = (deps: KbnVislibVisTypesDependenci { group: AggGroupNames.Metrics, name: 'metric', - title: i18n.translate('kbnVislibVisTypes.heatmap.metricTitle', { defaultMessage: 'Value' }), + title: i18n.translate('visTypeVislib.heatmap.metricTitle', { defaultMessage: 'Value' }), min: 1, max: 1, aggFilter: [ @@ -109,7 +109,7 @@ export const createHeatmapVisTypeDefinition = (deps: KbnVislibVisTypesDependenci { group: AggGroupNames.Buckets, name: 'segment', - title: i18n.translate('kbnVislibVisTypes.heatmap.segmentTitle', { + title: i18n.translate('visTypeVislib.heatmap.segmentTitle', { defaultMessage: 'X-axis', }), min: 0, @@ -119,7 +119,7 @@ export const createHeatmapVisTypeDefinition = (deps: KbnVislibVisTypesDependenci { group: AggGroupNames.Buckets, name: 'group', - title: i18n.translate('kbnVislibVisTypes.heatmap.groupTitle', { defaultMessage: 'Y-axis' }), + title: i18n.translate('visTypeVislib.heatmap.groupTitle', { defaultMessage: 'Y-axis' }), min: 0, max: 1, aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'], @@ -127,7 +127,7 @@ export const createHeatmapVisTypeDefinition = (deps: KbnVislibVisTypesDependenci { group: AggGroupNames.Buckets, name: 'split', - title: i18n.translate('kbnVislibVisTypes.heatmap.splitTitle', { + title: i18n.translate('visTypeVislib.heatmap.splitTitle', { defaultMessage: 'Split chart', }), min: 0, diff --git a/src/legacy/core_plugins/vis_type_vislib/public/histogram.ts b/src/legacy/core_plugins/vis_type_vislib/public/histogram.ts index f92875a62cfd7..490b557b7bafb 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/histogram.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/histogram.ts @@ -42,11 +42,11 @@ import { KbnVislibVisTypesDependencies } from './plugin'; export const createHistogramVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) => ({ name: 'histogram', - title: i18n.translate('kbnVislibVisTypes.histogram.histogramTitle', { + title: i18n.translate('visTypeVislib.histogram.histogramTitle', { defaultMessage: 'Vertical Bar', }), icon: 'visBarVertical', - description: i18n.translate('kbnVislibVisTypes.histogram.histogramDescription', { + description: i18n.translate('visTypeVislib.histogram.histogramDescription', { defaultMessage: 'Assign a continuous variable to each axis', }), visualization: createVislibVisController(deps), @@ -139,7 +139,7 @@ export const createHistogramVisTypeDefinition = (deps: KbnVislibVisTypesDependen { group: AggGroupNames.Metrics, name: 'metric', - title: i18n.translate('kbnVislibVisTypes.histogram.metricTitle', { + title: i18n.translate('visTypeVislib.histogram.metricTitle', { defaultMessage: 'Y-axis', }), min: 1, @@ -149,7 +149,7 @@ export const createHistogramVisTypeDefinition = (deps: KbnVislibVisTypesDependen { group: AggGroupNames.Metrics, name: 'radius', - title: i18n.translate('kbnVislibVisTypes.histogram.radiusTitle', { + title: i18n.translate('visTypeVislib.histogram.radiusTitle', { defaultMessage: 'Dot size', }), min: 0, @@ -159,7 +159,7 @@ export const createHistogramVisTypeDefinition = (deps: KbnVislibVisTypesDependen { group: AggGroupNames.Buckets, name: 'segment', - title: i18n.translate('kbnVislibVisTypes.histogram.segmentTitle', { + title: i18n.translate('visTypeVislib.histogram.segmentTitle', { defaultMessage: 'X-axis', }), min: 0, @@ -169,7 +169,7 @@ export const createHistogramVisTypeDefinition = (deps: KbnVislibVisTypesDependen { group: AggGroupNames.Buckets, name: 'group', - title: i18n.translate('kbnVislibVisTypes.histogram.groupTitle', { + title: i18n.translate('visTypeVislib.histogram.groupTitle', { defaultMessage: 'Split series', }), min: 0, @@ -179,7 +179,7 @@ export const createHistogramVisTypeDefinition = (deps: KbnVislibVisTypesDependen { group: AggGroupNames.Buckets, name: 'split', - title: i18n.translate('kbnVislibVisTypes.histogram.splitTitle', { + title: i18n.translate('visTypeVislib.histogram.splitTitle', { defaultMessage: 'Split chart', }), min: 0, diff --git a/src/legacy/core_plugins/vis_type_vislib/public/horizontal_bar.ts b/src/legacy/core_plugins/vis_type_vislib/public/horizontal_bar.ts index ada0c6b44ff70..e8d51fe037a63 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/horizontal_bar.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/horizontal_bar.ts @@ -42,11 +42,11 @@ import { KbnVislibVisTypesDependencies } from './plugin'; export const createHorizontalBarVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) => ({ name: 'horizontal_bar', - title: i18n.translate('kbnVislibVisTypes.horizontalBar.horizontalBarTitle', { + title: i18n.translate('visTypeVislib.horizontalBar.horizontalBarTitle', { defaultMessage: 'Horizontal Bar', }), icon: 'visBarHorizontal', - description: i18n.translate('kbnVislibVisTypes.horizontalBar.horizontalBarDescription', { + description: i18n.translate('visTypeVislib.horizontalBar.horizontalBarDescription', { defaultMessage: 'Assign a continuous variable to each axis', }), visualization: createVislibVisController(deps), @@ -138,7 +138,7 @@ export const createHorizontalBarVisTypeDefinition = (deps: KbnVislibVisTypesDepe { group: AggGroupNames.Metrics, name: 'metric', - title: i18n.translate('kbnVislibVisTypes.horizontalBar.metricTitle', { + title: i18n.translate('visTypeVislib.horizontalBar.metricTitle', { defaultMessage: 'Y-axis', }), min: 1, @@ -148,7 +148,7 @@ export const createHorizontalBarVisTypeDefinition = (deps: KbnVislibVisTypesDepe { group: AggGroupNames.Metrics, name: 'radius', - title: i18n.translate('kbnVislibVisTypes.horizontalBar.radiusTitle', { + title: i18n.translate('visTypeVislib.horizontalBar.radiusTitle', { defaultMessage: 'Dot size', }), min: 0, @@ -158,7 +158,7 @@ export const createHorizontalBarVisTypeDefinition = (deps: KbnVislibVisTypesDepe { group: AggGroupNames.Buckets, name: 'segment', - title: i18n.translate('kbnVislibVisTypes.horizontalBar.segmentTitle', { + title: i18n.translate('visTypeVislib.horizontalBar.segmentTitle', { defaultMessage: 'X-axis', }), min: 0, @@ -168,7 +168,7 @@ export const createHorizontalBarVisTypeDefinition = (deps: KbnVislibVisTypesDepe { group: AggGroupNames.Buckets, name: 'group', - title: i18n.translate('kbnVislibVisTypes.horizontalBar.groupTitle', { + title: i18n.translate('visTypeVislib.horizontalBar.groupTitle', { defaultMessage: 'Split series', }), min: 0, @@ -178,7 +178,7 @@ export const createHorizontalBarVisTypeDefinition = (deps: KbnVislibVisTypesDepe { group: AggGroupNames.Buckets, name: 'split', - title: i18n.translate('kbnVislibVisTypes.horizontalBar.splitTitle', { + title: i18n.translate('visTypeVislib.horizontalBar.splitTitle', { defaultMessage: 'Split chart', }), min: 0, diff --git a/src/legacy/core_plugins/vis_type_vislib/public/index.ts b/src/legacy/core_plugins/vis_type_vislib/public/index.ts index 3b4bcb6bc3a7e..80c078de1a10b 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/index.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/index.ts @@ -24,4 +24,18 @@ export function plugin(initializerContext: PluginInitializerContext) { return new Plugin(initializerContext); } +export { + BasicOptions, + RangeOption, + ColorRanges, + SelectOption, + SetColorSchemaOptionsValue, + ColorSchemaOptions, + NumberInputOption, + SwitchOption, + TextInputOption, +} from './components'; + export { ColorModes } from './utils/collections'; + +export * from './types'; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/legacy.ts b/src/legacy/core_plugins/vis_type_vislib/public/legacy.ts index 3d4cf55adc5e0..775cf63b96f68 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/legacy.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/legacy.ts @@ -21,16 +21,6 @@ import { npSetup, npStart } from 'ui/new_platform'; import { PluginInitializerContext } from 'kibana/public'; /* eslint-disable prettier/prettier */ -import { - initializeHierarchicalTooltipFormatter, - getHierarchicalTooltipFormatter, - // @ts-ignore -} from 'ui/vis/components/tooltip/_hierarchical_tooltip_formatter'; -import { - initializePointSeriesTooltipFormatter, - getPointSeriesTooltipFormatter, - // @ts-ignore -} from 'ui/vis/components/tooltip/_pointseries_tooltip_formatter'; import { vislibSeriesResponseHandlerProvider, vislibSlicesResponseHandlerProvider, @@ -53,10 +43,6 @@ const setupPlugins: Readonly = { expressions: npSetup.plugins.expressions, visualizations: visualizationsSetup, __LEGACY: { - initializeHierarchicalTooltipFormatter, - getHierarchicalTooltipFormatter, - initializePointSeriesTooltipFormatter, - getPointSeriesTooltipFormatter, vislibSeriesResponseHandlerProvider, vislibSlicesResponseHandlerProvider, vislibColor, diff --git a/src/legacy/core_plugins/vis_type_vislib/public/legacy_imports.ts b/src/legacy/core_plugins/vis_type_vislib/public/legacy_imports.ts index 2970942f221e8..6b10fb0d41223 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/legacy_imports.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/legacy_imports.ts @@ -23,13 +23,10 @@ export { RangeValues, RangesParamEditor } from 'ui/vis/editors/default/controls/ export { ColorSchema, ColorSchemas, colorSchemas, getHeatmapColors } from 'ui/color_maps'; export { AggConfig, Vis, VisParams } from 'ui/vis'; export { AggType } from 'ui/agg_types'; -export { CUSTOM_LEGEND_VIS_TYPES, VisLegend } from 'ui/vis/vis_types/vislib_vis_legend'; -// @ts-ignore -export { Tooltip } from 'ui/vis/components/tooltip'; // @ts-ignore export { SimpleEmitter } from 'ui/utils/simple_emitter'; // @ts-ignore export { Binder } from 'ui/binder'; -export { getFormat } from 'ui/visualize/loader/pipeline_helpers/utilities'; +export { getFormat, getTableAggs } from 'ui/visualize/loader/pipeline_helpers/utilities'; // @ts-ignore export { tabifyAggResponse } from 'ui/agg_response/tabify'; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/line.ts b/src/legacy/core_plugins/vis_type_vislib/public/line.ts index 35a059fadddcb..d3f2bc1bb684c 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/line.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/line.ts @@ -42,9 +42,9 @@ import { KbnVislibVisTypesDependencies } from './plugin'; export const createLineVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) => ({ name: 'line', - title: i18n.translate('kbnVislibVisTypes.line.lineTitle', { defaultMessage: 'Line' }), + title: i18n.translate('visTypeVislib.line.lineTitle', { defaultMessage: 'Line' }), icon: 'visLine', - description: i18n.translate('kbnVislibVisTypes.line.lineDescription', { + description: i18n.translate('visTypeVislib.line.lineDescription', { defaultMessage: 'Emphasize trends', }), visualization: createVislibVisController(deps), @@ -136,7 +136,7 @@ export const createLineVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) { group: AggGroupNames.Metrics, name: 'metric', - title: i18n.translate('kbnVislibVisTypes.line.metricTitle', { defaultMessage: 'Y-axis' }), + title: i18n.translate('visTypeVislib.line.metricTitle', { defaultMessage: 'Y-axis' }), min: 1, aggFilter: ['!geo_centroid', '!geo_bounds'], defaults: [{ schema: 'metric', type: 'count' }], @@ -144,7 +144,7 @@ export const createLineVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) { group: AggGroupNames.Metrics, name: 'radius', - title: i18n.translate('kbnVislibVisTypes.line.radiusTitle', { defaultMessage: 'Dot size' }), + title: i18n.translate('visTypeVislib.line.radiusTitle', { defaultMessage: 'Dot size' }), min: 0, max: 1, aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality', 'top_hits'], @@ -152,7 +152,7 @@ export const createLineVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) { group: AggGroupNames.Buckets, name: 'segment', - title: i18n.translate('kbnVislibVisTypes.line.segmentTitle', { defaultMessage: 'X-axis' }), + title: i18n.translate('visTypeVislib.line.segmentTitle', { defaultMessage: 'X-axis' }), min: 0, max: 1, aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'], @@ -160,7 +160,7 @@ export const createLineVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) { group: AggGroupNames.Buckets, name: 'group', - title: i18n.translate('kbnVislibVisTypes.line.groupTitle', { + title: i18n.translate('visTypeVislib.line.groupTitle', { defaultMessage: 'Split series', }), min: 0, @@ -170,7 +170,7 @@ export const createLineVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) { group: AggGroupNames.Buckets, name: 'split', - title: i18n.translate('kbnVislibVisTypes.line.splitTitle', { + title: i18n.translate('visTypeVislib.line.splitTitle', { defaultMessage: 'Split chart', }), min: 0, diff --git a/src/legacy/core_plugins/vis_type_vislib/public/pie.ts b/src/legacy/core_plugins/vis_type_vislib/public/pie.ts index 32307b7a117a1..3c30030ca45f5 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/pie.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/pie.ts @@ -40,9 +40,9 @@ export interface PieVisParams extends CommonVislibParams { export const createPieVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) => ({ name: 'pie', - title: i18n.translate('kbnVislibVisTypes.pie.pieTitle', { defaultMessage: 'Pie' }), + title: i18n.translate('visTypeVislib.pie.pieTitle', { defaultMessage: 'Pie' }), icon: 'visPie', - description: i18n.translate('kbnVislibVisTypes.pie.pieDescription', { + description: i18n.translate('visTypeVislib.pie.pieDescription', { defaultMessage: 'Compare parts of a whole', }), visualization: createVislibVisController(deps), @@ -70,7 +70,7 @@ export const createPieVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) { group: AggGroupNames.Metrics, name: 'metric', - title: i18n.translate('kbnVislibVisTypes.pie.metricTitle', { + title: i18n.translate('visTypeVislib.pie.metricTitle', { defaultMessage: 'Slice size', }), min: 1, @@ -81,7 +81,7 @@ export const createPieVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) { group: AggGroupNames.Buckets, name: 'segment', - title: i18n.translate('kbnVislibVisTypes.pie.segmentTitle', { + title: i18n.translate('visTypeVislib.pie.segmentTitle', { defaultMessage: 'Split slices', }), min: 0, @@ -91,7 +91,7 @@ export const createPieVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) { group: AggGroupNames.Buckets, name: 'split', - title: i18n.translate('kbnVislibVisTypes.pie.splitTitle', { + title: i18n.translate('visTypeVislib.pie.splitTitle', { defaultMessage: 'Split chart', }), mustBeFirst: true, diff --git a/src/legacy/core_plugins/vis_type_vislib/public/pie_fn.ts b/src/legacy/core_plugins/vis_type_vislib/public/pie_fn.ts index 4b536caedb121..af9842fa94fda 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/pie_fn.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/pie_fn.ts @@ -53,7 +53,7 @@ export const createPieVisFn = (deps: KbnVislibVisTypesDependencies) => (): Expre context: { types: ['kibana_datatable'], }, - help: i18n.translate('kbnVislibVisTypes.functions.pie.help', { + help: i18n.translate('visTypeVislib.functions.pie.help', { defaultMessage: 'Pie visualization', }), args: { diff --git a/src/legacy/core_plugins/vis_type_vislib/public/plugin.ts b/src/legacy/core_plugins/vis_type_vislib/public/plugin.ts index a2e8512b2201b..0ab2b2120382b 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/plugin.ts @@ -46,10 +46,6 @@ type ResponseHandlerProvider = () => { type KbnVislibVisTypesCoreSetup = CoreSetup; export interface LegacyDependencies { - initializeHierarchicalTooltipFormatter: () => Promise; - getHierarchicalTooltipFormatter: () => Promise; - initializePointSeriesTooltipFormatter: () => void; - getPointSeriesTooltipFormatter: () => void; vislibSeriesResponseHandlerProvider: ResponseHandlerProvider; vislibSlicesResponseHandlerProvider: ResponseHandlerProvider; vislibColor: (colors: Array, mappings: any) => (value: any) => any; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/utils/collections.ts b/src/legacy/core_plugins/vis_type_vislib/public/utils/collections.ts index 810ddeea73834..d8f9fbf7d756a 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/utils/collections.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/utils/collections.ts @@ -31,25 +31,25 @@ export type Positions = $Values; const getPositions = () => [ { - text: i18n.translate('kbnVislibVisTypes.legendPositions.topText', { + text: i18n.translate('visTypeVislib.legendPositions.topText', { defaultMessage: 'Top', }), value: Positions.TOP, }, { - text: i18n.translate('kbnVislibVisTypes.legendPositions.leftText', { + text: i18n.translate('visTypeVislib.legendPositions.leftText', { defaultMessage: 'Left', }), value: Positions.LEFT, }, { - text: i18n.translate('kbnVislibVisTypes.legendPositions.rightText', { + text: i18n.translate('visTypeVislib.legendPositions.rightText', { defaultMessage: 'Right', }), value: Positions.RIGHT, }, { - text: i18n.translate('kbnVislibVisTypes.legendPositions.bottomText', { + text: i18n.translate('visTypeVislib.legendPositions.bottomText', { defaultMessage: 'Bottom', }), value: Positions.BOTTOM, @@ -65,19 +65,19 @@ export type ChartTypes = $Values; const getChartTypes = () => [ { - text: i18n.translate('kbnVislibVisTypes.chartTypes.lineText', { + text: i18n.translate('visTypeVislib.chartTypes.lineText', { defaultMessage: 'Line', }), value: ChartTypes.LINE, }, { - text: i18n.translate('kbnVislibVisTypes.chartTypes.areaText', { + text: i18n.translate('visTypeVislib.chartTypes.areaText', { defaultMessage: 'Area', }), value: ChartTypes.AREA, }, { - text: i18n.translate('kbnVislibVisTypes.chartTypes.barText', { + text: i18n.translate('visTypeVislib.chartTypes.barText', { defaultMessage: 'Bar', }), value: ChartTypes.HISTOGRAM, @@ -92,13 +92,13 @@ export type ChartModes = $Values; const getChartModes = () => [ { - text: i18n.translate('kbnVislibVisTypes.chartModes.normalText', { + text: i18n.translate('visTypeVislib.chartModes.normalText', { defaultMessage: 'Normal', }), value: ChartModes.NORMAL, }, { - text: i18n.translate('kbnVislibVisTypes.chartModes.stackedText', { + text: i18n.translate('visTypeVislib.chartModes.stackedText', { defaultMessage: 'Stacked', }), value: ChartModes.STACKED, @@ -114,19 +114,19 @@ export type InterpolationModes = $Values; const getInterpolationModes = () => [ { - text: i18n.translate('kbnVislibVisTypes.interpolationModes.straightText', { + text: i18n.translate('visTypeVislib.interpolationModes.straightText', { defaultMessage: 'Straight', }), value: InterpolationModes.LINEAR, }, { - text: i18n.translate('kbnVislibVisTypes.interpolationModes.smoothedText', { + text: i18n.translate('visTypeVislib.interpolationModes.smoothedText', { defaultMessage: 'Smoothed', }), value: InterpolationModes.CARDINAL, }, { - text: i18n.translate('kbnVislibVisTypes.interpolationModes.steppedText', { + text: i18n.translate('visTypeVislib.interpolationModes.steppedText', { defaultMessage: 'Stepped', }), value: InterpolationModes.STEP_AFTER, @@ -148,19 +148,19 @@ export type ScaleTypes = $Values; const getScaleTypes = () => [ { - text: i18n.translate('kbnVislibVisTypes.scaleTypes.linearText', { + text: i18n.translate('visTypeVislib.scaleTypes.linearText', { defaultMessage: 'Linear', }), value: ScaleTypes.LINEAR, }, { - text: i18n.translate('kbnVislibVisTypes.scaleTypes.logText', { + text: i18n.translate('visTypeVislib.scaleTypes.logText', { defaultMessage: 'Log', }), value: ScaleTypes.LOG, }, { - text: i18n.translate('kbnVislibVisTypes.scaleTypes.squareRootText', { + text: i18n.translate('visTypeVislib.scaleTypes.squareRootText', { defaultMessage: 'Square root', }), value: ScaleTypes.SQUARE_ROOT, @@ -177,25 +177,25 @@ export type AxisModes = $Values; const getAxisModes = () => [ { - text: i18n.translate('kbnVislibVisTypes.axisModes.normalText', { + text: i18n.translate('visTypeVislib.axisModes.normalText', { defaultMessage: 'Normal', }), value: AxisModes.NORMAL, }, { - text: i18n.translate('kbnVislibVisTypes.axisModes.percentageText', { + text: i18n.translate('visTypeVislib.axisModes.percentageText', { defaultMessage: 'Percentage', }), value: AxisModes.PERCENTAGE, }, { - text: i18n.translate('kbnVislibVisTypes.axisModes.wiggleText', { + text: i18n.translate('visTypeVislib.axisModes.wiggleText', { defaultMessage: 'Wiggle', }), value: AxisModes.WIGGLE, }, { - text: i18n.translate('kbnVislibVisTypes.axisModes.silhouetteText', { + text: i18n.translate('visTypeVislib.axisModes.silhouetteText', { defaultMessage: 'Silhouette', }), value: AxisModes.SILHOUETTE, @@ -219,19 +219,19 @@ export type ThresholdLineStyles = $Values; const getThresholdLineStyles = () => [ { value: ThresholdLineStyles.FULL, - text: i18n.translate('kbnVislibVisTypes.thresholdLine.style.fullText', { + text: i18n.translate('visTypeVislib.thresholdLine.style.fullText', { defaultMessage: 'Full', }), }, { value: ThresholdLineStyles.DASHED, - text: i18n.translate('kbnVislibVisTypes.thresholdLine.style.dashedText', { + text: i18n.translate('visTypeVislib.thresholdLine.style.dashedText', { defaultMessage: 'Dashed', }), }, { value: ThresholdLineStyles.DOT_DASHED, - text: i18n.translate('kbnVislibVisTypes.thresholdLine.style.dotdashedText', { + text: i18n.translate('visTypeVislib.thresholdLine.style.dotdashedText', { defaultMessage: 'Dot-dashed', }), }, @@ -239,19 +239,19 @@ const getThresholdLineStyles = () => [ const getRotateOptions = () => [ { - text: i18n.translate('kbnVislibVisTypes.categoryAxis.rotate.horizontalText', { + text: i18n.translate('visTypeVislib.categoryAxis.rotate.horizontalText', { defaultMessage: 'Horizontal', }), value: Rotates.HORIZONTAL, }, { - text: i18n.translate('kbnVislibVisTypes.categoryAxis.rotate.verticalText', { + text: i18n.translate('visTypeVislib.categoryAxis.rotate.verticalText', { defaultMessage: 'Vertical', }), value: Rotates.VERTICAL, }, { - text: i18n.translate('kbnVislibVisTypes.categoryAxis.rotate.angledText', { + text: i18n.translate('visTypeVislib.categoryAxis.rotate.angledText', { defaultMessage: 'Angled', }), value: Rotates.ANGLED, @@ -273,13 +273,13 @@ export type ColorModes = $Values; const getGaugeTypes = () => [ { - text: i18n.translate('kbnVislibVisTypes.gauge.gaugeTypes.arcText', { + text: i18n.translate('visTypeVislib.gauge.gaugeTypes.arcText', { defaultMessage: 'Arc', }), value: GaugeTypes.ARC, }, { - text: i18n.translate('kbnVislibVisTypes.gauge.gaugeTypes.circleText', { + text: i18n.translate('visTypeVislib.gauge.gaugeTypes.circleText', { defaultMessage: 'Circle', }), value: GaugeTypes.CIRCLE, @@ -295,19 +295,19 @@ export type Alignments = $Values; const getAlignments = () => [ { - text: i18n.translate('kbnVislibVisTypes.gauge.alignmentAutomaticTitle', { + text: i18n.translate('visTypeVislib.gauge.alignmentAutomaticTitle', { defaultMessage: 'Automatic', }), value: Alignments.AUTOMATIC, }, { - text: i18n.translate('kbnVislibVisTypes.gauge.alignmentHorizontalTitle', { + text: i18n.translate('visTypeVislib.gauge.alignmentHorizontalTitle', { defaultMessage: 'Horizontal', }), value: Alignments.HORIZONTAL, }, { - text: i18n.translate('kbnVislibVisTypes.gauge.alignmentVerticalTitle', { + text: i18n.translate('visTypeVislib.gauge.alignmentVerticalTitle', { defaultMessage: 'Vertical', }), value: Alignments.VERTICAL, diff --git a/src/legacy/core_plugins/vis_type_vislib/public/utils/common_config.tsx b/src/legacy/core_plugins/vis_type_vislib/public/utils/common_config.tsx index adb93ca8011b2..e2f6ba0d8b562 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/utils/common_config.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/utils/common_config.tsx @@ -29,7 +29,7 @@ function getAreaOptionTabs() { return [ { name: 'advanced', - title: i18n.translate('kbnVislibVisTypes.area.tabs.metricsAxesTitle', { + title: i18n.translate('visTypeVislib.area.tabs.metricsAxesTitle', { defaultMessage: 'Metrics & axes', }), editor: (props: VisOptionsProps) => ( @@ -38,7 +38,7 @@ function getAreaOptionTabs() { }, { name: 'options', - title: i18n.translate('kbnVislibVisTypes.area.tabs.panelSettingsTitle', { + title: i18n.translate('visTypeVislib.area.tabs.panelSettingsTitle', { defaultMessage: 'Panel settings', }), editor: (props: VisOptionsProps) => ( @@ -48,7 +48,7 @@ function getAreaOptionTabs() { ]; } -const countLabel = i18n.translate('kbnVislibVisTypes.area.countText', { +const countLabel = i18n.translate('visTypeVislib.area.countText', { defaultMessage: 'Count', }); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vis_controller.tsx b/src/legacy/core_plugins/vis_type_vislib/public/vis_controller.tsx index cff9a0a2e8551..ca49f17e7c264 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vis_controller.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/vis_controller.tsx @@ -20,12 +20,13 @@ import $ from 'jquery'; import React, { RefObject } from 'react'; -import { CUSTOM_LEGEND_VIS_TYPES, VisLegend, Vis, VisParams } from './legacy_imports'; +import { Vis, VisParams } from './legacy_imports'; // @ts-ignore import { Vis as Vislib } from './vislib/vis'; import { Positions } from './utils/collections'; import { KbnVislibVisTypesDependencies } from './plugin'; import { mountReactNode } from '../../../../core/public/utils'; +import { VisLegend, CUSTOM_LEGEND_VIS_TYPES } from './vislib/components/legend'; const legendClassName = { top: 'visLib--legend-top', @@ -76,9 +77,6 @@ export const createVislibVisController = (deps: KbnVislibVisTypesDependencies) = return resolve(); } - await deps.initializeHierarchicalTooltipFormatter(); - await deps.initializePointSeriesTooltipFormatter(); - this.vislibVis = new Vislib(this.chartEl, visParams, deps); this.vislibVis.on('brush', this.vis.API.events.brush); this.vislibVis.on('click', this.vis.API.events.filter); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vis_type_vislib_vis_fn.ts b/src/legacy/core_plugins/vis_type_vislib/public/vis_type_vislib_vis_fn.ts index 0a685cd70e089..8a35fe3a0f6f8 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vis_type_vislib_vis_fn.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/vis_type_vislib_vis_fn.ts @@ -52,7 +52,7 @@ export const createKbnVislibVisTypesFn = ( context: { types: ['kibana_datatable'], }, - help: i18n.translate('kbnVislibVisTypes.functions.vislib.help', { + help: i18n.translate('visTypeVislib.functions.vislib.help', { defaultMessage: 'Vislib visualization', }), args: { diff --git a/src/legacy/ui/public/vis/components/tooltip/__tests__/_tooltip_formatter.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/components/_tooltip_formatter.js similarity index 88% rename from src/legacy/ui/public/vis/components/tooltip/__tests__/_tooltip_formatter.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/components/_tooltip_formatter.js index d0cfab0b54f79..a3aabcb90be62 100644 --- a/src/legacy/ui/public/vis/components/tooltip/__tests__/_tooltip_formatter.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/components/_tooltip_formatter.js @@ -20,18 +20,11 @@ import _ from 'lodash'; import $ from 'jquery'; import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { PointSeriesTooltipFormatterProvider } from '../_pointseries_tooltip_formatter'; -describe('tooltipFormatter', function() { - let tooltipFormatter; +import { pointSeriesTooltipFormatter } from '../../components/tooltip'; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function(Private) { - tooltipFormatter = Private(PointSeriesTooltipFormatterProvider)(); - }) - ); +describe('tooltipFormatter', function() { + const tooltipFormatter = pointSeriesTooltipFormatter(); function cell($row, i) { return $row diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/components/heatmap_color.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/components/heatmap_color.js index 36c5b60abf5c6..c7ffc843876e3 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/components/heatmap_color.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/components/heatmap_color.js @@ -18,7 +18,6 @@ */ import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; import { getHeatmapColors } from '../../../legacy_imports'; @@ -27,8 +26,6 @@ describe('Vislib Heatmap Color Module Test Suite', function() { const nullValue = null; let notAValue; - beforeEach(ngMock.module('kibana')); - it('should throw an error if schema is invalid', function() { expect(function() { getHeatmapColors(4, 'invalid schema'); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/components/labels.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/components/labels.js index 55d629adaf245..db99b881a6e38 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/components/labels.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/components/labels.js @@ -19,7 +19,7 @@ import _ from 'lodash'; import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; + import { labels } from '../../components/labels/labels'; import { dataArray } from '../../components/labels/data_array'; import { uniqLabels } from '../../components/labels/uniq_labels'; @@ -161,21 +161,18 @@ const columnsData = { describe('Vislib Labels Module Test Suite', function() { let uniqSeriesLabels; describe('Labels (main)', function() { - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function() { - seriesLabels = labels(seriesData); - rowsLabels = labels(rowsData); - seriesArr = Array.isArray(seriesLabels); - rowsArr = Array.isArray(rowsLabels); - uniqSeriesLabels = _.chain(rowsData.rows) - .pluck('series') - .flattenDeep() - .pluck('label') - .uniq() - .value(); - }) - ); + beforeEach(() => { + seriesLabels = labels(seriesData); + rowsLabels = labels(rowsData); + seriesArr = Array.isArray(seriesLabels); + rowsArr = Array.isArray(rowsLabels); + uniqSeriesLabels = _.chain(rowsData.rows) + .pluck('series') + .flattenDeep() + .pluck('label') + .uniq() + .value(); + }); it('should be a function', function() { expect(typeof labels).to.be('function'); @@ -224,15 +221,12 @@ describe('Vislib Labels Module Test Suite', function() { let testSeries; let testRows; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function() { - seriesLabels = dataArray(seriesData); - rowsLabels = dataArray(rowsData); - testSeries = Array.isArray(seriesLabels); - testRows = Array.isArray(rowsLabels); - }) - ); + beforeEach(() => { + seriesLabels = dataArray(seriesData); + rowsLabels = dataArray(rowsData); + testSeries = Array.isArray(seriesLabels); + testRows = Array.isArray(rowsLabels); + }); it('should throw an error if the input is not an object', function() { expect(function() { @@ -333,15 +327,12 @@ describe('Vislib Labels Module Test Suite', function() { let uniq; let testArr; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function() { - uniq = uniqLabels(arrObj, function(d) { - return d; - }); - testArr = Array.isArray(uniq); - }) - ); + beforeEach(() => { + uniq = uniqLabels(arrObj, function(d) { + return d; + }); + testArr = Array.isArray(uniq); + }); it('should throw an error if input is not an array', function() { expect(function() { @@ -407,15 +398,12 @@ describe('Vislib Labels Module Test Suite', function() { let columnsArr; let rowsArr; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function() { - columnsLabels = getSeries(columnsData); - rowsLabels = getSeries(rowsData); - columnsArr = Array.isArray(columnsLabels); - rowsArr = Array.isArray(rowsLabels); - }) - ); + beforeEach(() => { + columnsLabels = getSeries(columnsData); + rowsLabels = getSeries(rowsData); + columnsArr = Array.isArray(columnsLabels); + rowsArr = Array.isArray(rowsLabels); + }); it('should throw an error if input is not an object', function() { expect(function() { diff --git a/src/legacy/ui/public/vis/components/tooltip/__tests__/positioning.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/components/positioning.js similarity index 99% rename from src/legacy/ui/public/vis/components/tooltip/__tests__/positioning.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/components/positioning.js index 89d547f2377ba..f1c80c9981020 100644 --- a/src/legacy/ui/public/vis/components/tooltip/__tests__/positioning.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/components/positioning.js @@ -21,7 +21,8 @@ import expect from '@kbn/expect'; import $ from 'jquery'; import _ from 'lodash'; import sinon from 'sinon'; -import { positionTooltip } from '../position_tooltip'; + +import { positionTooltip } from '../../components/tooltip/position_tooltip'; describe('Tooltip Positioning', function() { const sandbox = sinon.createSandbox(); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/index.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/index.js index e30ca17a1fce7..734c6d003278f 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/index.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/index.js @@ -19,18 +19,15 @@ import _ from 'lodash'; import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; + import VislibProvider from '..'; describe('Vislib Index Test Suite', function() { let vislib; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function(Private) { - vislib = Private(VislibProvider); - }) - ); + beforeEach(() => { + vislib = new VislibProvider(); + }); it('should return an object', function() { expect(_.isObject(vislib)).to.be(true); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/axis/axis.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/axis/axis.js index 3081c12415076..bc4a4f9925513 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/axis/axis.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/axis/axis.js @@ -19,17 +19,15 @@ import d3 from 'd3'; import _ from 'lodash'; -import ngMock from 'ng_mock'; -import 'ui/persisted_state'; - import expect from '@kbn/expect'; import $ from 'jquery'; import { Axis } from '../../../lib/axis'; import { VisConfig } from '../../../lib/vis_config'; +import { getMockUiState } from '../fixtures/_vis_fixture'; describe('Vislib Axis Class Test Suite', function() { - let persistedState; + let mockUiState; let yAxis; let el; let fixture; @@ -102,38 +100,34 @@ describe('Vislib Axis Class Test Suite', function() { yAxisLabel: 'Count', }; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function($injector) { - persistedState = new ($injector.get('PersistedState'))(); - - el = d3 - .select('body') - .append('div') - .attr('class', 'visAxis--x') - .style('height', '40px'); + beforeEach(() => { + mockUiState = getMockUiState(); + el = d3 + .select('body') + .append('div') + .attr('class', 'visAxis--x') + .style('height', '40px'); - fixture = el.append('div').attr('class', 'x-axis-div'); + fixture = el.append('div').attr('class', 'x-axis-div'); - const visConfig = new VisConfig( - { - type: 'histogram', - }, - data, - persistedState, - $('.x-axis-div')[0], - () => undefined - ); - yAxis = new Axis(visConfig, { - type: 'value', - id: 'ValueAxis-1', - }); + const visConfig = new VisConfig( + { + type: 'histogram', + }, + data, + mockUiState, + $('.x-axis-div')[0], + () => undefined + ); + yAxis = new Axis(visConfig, { + type: 'value', + id: 'ValueAxis-1', + }); - seriesData = data.series.map(series => { - return series.values; - }); - }) - ); + seriesData = data.series.map(series => { + return series.values; + }); + }); afterEach(function() { fixture.remove(); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/axis_title.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/axis_title.js index cbb294c3b44e4..fd25335dd2cd4 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/axis_title.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/axis_title.js @@ -20,18 +20,15 @@ import d3 from 'd3'; import _ from 'lodash'; import $ from 'jquery'; -import ngMock from 'ng_mock'; - import expect from '@kbn/expect'; -import 'ui/persisted_state'; import { AxisTitle } from '../../lib/axis/axis_title'; import { AxisConfig } from '../../lib/axis/axis_config'; import { VisConfig } from '../../lib/vis_config'; import { Data } from '../../lib/data'; +import { getMockUiState } from './fixtures/_vis_fixture'; describe('Vislib AxisTitle Class Test Suite', function() { - let PersistedState; let el; let dataObj; let xTitle; @@ -96,56 +93,53 @@ describe('Vislib AxisTitle Class Test Suite', function() { yAxisLabel: 'Count', }; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function($injector) { - PersistedState = $injector.get('PersistedState'); - - el = d3 - .select('body') - .append('div') - .attr('class', 'visWrapper'); - - el.append('div') - .attr('class', 'visAxis__column--bottom') - .append('div') - .attr('class', 'axis-title y-axis-title') - .style('height', '20px') - .style('width', '20px'); - - el.append('div') - .attr('class', 'visAxis__column--left') - .append('div') - .attr('class', 'axis-title x-axis-title') - .style('height', '20px') - .style('width', '20px'); - - dataObj = new Data(data, new PersistedState(), () => undefined); - visConfig = new VisConfig( - { - type: 'histogram', - }, - data, - new PersistedState(), - el.node(), - () => undefined - ); - const xAxisConfig = new AxisConfig(visConfig, { - position: 'bottom', - title: { - text: dataObj.get('xAxisLabel'), - }, - }); - const yAxisConfig = new AxisConfig(visConfig, { - position: 'left', - title: { - text: dataObj.get('yAxisLabel'), - }, - }); - xTitle = new AxisTitle(xAxisConfig); - yTitle = new AxisTitle(yAxisConfig); - }) - ); + beforeEach(() => { + el = d3 + .select('body') + .append('div') + .attr('class', 'visWrapper'); + + el.append('div') + .attr('class', 'visAxis__column--bottom') + .append('div') + .attr('class', 'axis-title y-axis-title') + .style('height', '20px') + .style('width', '20px'); + + el.append('div') + .attr('class', 'visAxis__column--left') + .append('div') + .attr('class', 'axis-title x-axis-title') + .style('height', '20px') + .style('width', '20px'); + + const uiState = getMockUiState(); + uiState.set('vis.colors', []); + dataObj = new Data(data, getMockUiState(), () => undefined); + visConfig = new VisConfig( + { + type: 'histogram', + }, + data, + getMockUiState(), + el.node(), + () => undefined + ); + const xAxisConfig = new AxisConfig(visConfig, { + position: 'bottom', + title: { + text: dataObj.get('xAxisLabel'), + }, + }); + const yAxisConfig = new AxisConfig(visConfig, { + position: 'left', + title: { + text: dataObj.get('yAxisLabel'), + }, + }); + xTitle = new AxisTitle(xAxisConfig); + yTitle = new AxisTitle(yAxisConfig); + }); afterEach(function() { el.remove(); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/chart_title.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/chart_title.js index b2086d0749a41..b65571becd83c 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/chart_title.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/chart_title.js @@ -19,14 +19,14 @@ import d3 from 'd3'; import _ from 'lodash'; -import ngMock from 'ng_mock'; import expect from '@kbn/expect'; import { ChartTitle } from '../../lib/chart_title'; import { VisConfig } from '../../lib/vis_config'; +import { getMockUiState } from './fixtures/_vis_fixture'; describe('Vislib ChartTitle Class Test Suite', function() { - let persistedState; + let mockUiState; let chartTitle; let el; const data = { @@ -88,36 +88,32 @@ describe('Vislib ChartTitle Class Test Suite', function() { yAxisLabel: 'Count', }; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function($injector) { - persistedState = new ($injector.get('PersistedState'))(); + beforeEach(() => { + mockUiState = getMockUiState(); + el = d3 + .select('body') + .append('div') + .attr('class', 'visWrapper') + .datum(data); - el = d3 - .select('body') - .append('div') - .attr('class', 'visWrapper') - .datum(data); + el.append('div') + .attr('class', 'chart-title') + .style('height', '20px'); - el.append('div') - .attr('class', 'chart-title') - .style('height', '20px'); - - const visConfig = new VisConfig( - { - type: 'histogram', - title: { - text: 'rows', - }, + const visConfig = new VisConfig( + { + type: 'histogram', + title: { + text: 'rows', }, - data, - persistedState, - el.node(), - () => undefined - ); - chartTitle = new ChartTitle(visConfig); - }) - ); + }, + data, + mockUiState, + el.node(), + () => undefined + ); + chartTitle = new ChartTitle(visConfig); + }); afterEach(function() { el.remove(); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/data.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/data.js index 5811b1d238163..d4ec6f363a75b 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/data.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/data.js @@ -18,11 +18,10 @@ */ import _ from 'lodash'; -import ngMock from 'ng_mock'; import expect from '@kbn/expect'; -import 'ui/persisted_state'; import { Data } from '../../lib/data'; +import { getMockUiState } from './fixtures/_vis_fixture'; const seriesData = { label: '', @@ -153,14 +152,11 @@ const colsData = { }; describe('Vislib Data Class Test Suite', function() { - let persistedState; + let mockUiState; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function($injector) { - persistedState = new ($injector.get('PersistedState'))(); - }) - ); + beforeEach(() => { + mockUiState = getMockUiState(); + }); describe('Data Class (main)', function() { it('should be a function', function() { @@ -168,7 +164,7 @@ describe('Vislib Data Class Test Suite', function() { }); it('should return an object', function() { - const rowIn = new Data(rowsData, persistedState, () => undefined); + const rowIn = new Data(rowsData, mockUiState, () => undefined); expect(_.isObject(rowIn)).to.be(true); }); }); @@ -182,7 +178,7 @@ describe('Vislib Data Class Test Suite', function() { }; beforeEach(function() { - data = new Data(pieData, persistedState, () => undefined); + data = new Data(pieData, mockUiState, () => undefined); }); it('should remove zero values', function() { @@ -196,7 +192,7 @@ describe('Vislib Data Class Test Suite', function() { let serOut; beforeEach(function() { - serIn = new Data(seriesData, persistedState, () => undefined); + serIn = new Data(seriesData, mockUiState, () => undefined); serOut = serIn.flatten(); }); @@ -210,7 +206,7 @@ describe('Vislib Data Class Test Suite', function() { function testLength(inputData) { return function() { - const data = new Data(inputData, persistedState, () => undefined); + const data = new Data(inputData, mockUiState, () => undefined); const len = _.reduce( data.chartData(), function(sum, chart) { @@ -266,7 +262,7 @@ describe('Vislib Data Class Test Suite', function() { }; beforeEach(function() { - data = new Data(geohashGridData, persistedState, () => undefined); + data = new Data(geohashGridData, mockUiState, () => undefined); }); describe('getVisData', function() { @@ -287,7 +283,7 @@ describe('Vislib Data Class Test Suite', function() { describe('null value check', function() { it('should return false', function() { - const data = new Data(rowsData, persistedState, () => undefined); + const data = new Data(rowsData, mockUiState, () => undefined); expect(data.hasNullValues()).to.be(false); }); @@ -307,7 +303,7 @@ describe('Vislib Data Class Test Suite', function() { ], }); - const data = new Data(nullRowData, persistedState, () => undefined); + const data = new Data(nullRowData, mockUiState, () => undefined); expect(data.hasNullValues()).to.be(true); }); }); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/dispatch.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/dispatch.js index a93db5637c89d..760c2e80d8428 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/dispatch.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/dispatch.js @@ -19,14 +19,12 @@ import _ from 'lodash'; import d3 from 'd3'; -import ngMock from 'ng_mock'; - import expect from '@kbn/expect'; -import 'ui/persisted_state'; // Data import data from './fixtures/mock_data/date_histogram/_series'; -import getFixturesVislibVisFixtureProvider from './fixtures/_vis_fixture'; + +import { getVis, getMockUiState } from './fixtures/_vis_fixture'; import { SimpleEmitter } from '../../../legacy_imports'; describe('Vislib Dispatch Class Test Suite', function() { @@ -44,17 +42,13 @@ describe('Vislib Dispatch Class Test Suite', function() { describe('', function() { let vis; - let persistedState; - - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function(Private, $injector) { - const getVis = getFixturesVislibVisFixtureProvider(Private); - vis = getVis(); - persistedState = new ($injector.get('PersistedState'))(); - vis.render(data, persistedState); - }) - ); + let mockUiState; + + beforeEach(() => { + vis = getVis(); + mockUiState = getMockUiState(); + vis.render(data, mockUiState); + }); afterEach(function() { destroyVis(vis); @@ -71,18 +65,14 @@ describe('Vislib Dispatch Class Test Suite', function() { describe('Stock event handlers', function() { let vis; - let persistedState; - - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function(Private, $injector) { - const getVis = getFixturesVislibVisFixtureProvider(Private); - persistedState = new ($injector.get('PersistedState'))(); - vis = getVis(); - vis.on('brush', _.noop); - vis.render(data, persistedState); - }) - ); + let mockUiState; + + beforeEach(() => { + mockUiState = getMockUiState(); + vis = getVis(); + vis.on('brush', _.noop); + vis.render(data, mockUiState); + }); afterEach(function() { destroyVis(vis); @@ -187,42 +177,30 @@ describe('Vislib Dispatch Class Test Suite', function() { describe('Custom event handlers', function() { it('should attach whatever gets passed on vis.on() to chart.events', function(done) { - let vis; - let persistedState; - ngMock.module('kibana'); - ngMock.inject(function(Private, $injector) { - const getVis = getFixturesVislibVisFixtureProvider(Private); - vis = getVis(); - persistedState = new ($injector.get('PersistedState'))(); - vis.on('someEvent', _.noop); - vis.render(data, persistedState); - - vis.handler.charts.forEach(function(chart) { - expect(chart.events.listenerCount('someEvent')).to.be(1); - }); + const vis = getVis(); + const mockUiState = getMockUiState(); + vis.on('someEvent', _.noop); + vis.render(data, mockUiState); - destroyVis(vis); - done(); + vis.handler.charts.forEach(function(chart) { + expect(chart.events.listenerCount('someEvent')).to.be(1); }); + + destroyVis(vis); + done(); }); it('can be added after rendering', function() { - let vis; - let persistedState; - ngMock.module('kibana'); - ngMock.inject(function(Private, $injector) { - const getVis = getFixturesVislibVisFixtureProvider(Private); - vis = getVis(); - persistedState = new ($injector.get('PersistedState'))(); - vis.render(data, persistedState); - vis.on('someEvent', _.noop); - - vis.handler.charts.forEach(function(chart) { - expect(chart.events.listenerCount('someEvent')).to.be(1); - }); + const vis = getVis(); + const mockUiState = getMockUiState(); + vis.render(data, mockUiState); + vis.on('someEvent', _.noop); - destroyVis(vis); + vis.handler.charts.forEach(function(chart) { + expect(chart.events.listenerCount('someEvent')).to.be(1); }); + + destroyVis(vis); }); }); }); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/error_handler.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/error_handler.js index 244386c1b51e6..4523e70ccbb4c 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/error_handler.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/error_handler.js @@ -18,18 +18,15 @@ */ import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; + import { ErrorHandler } from '../../lib/_error_handler'; describe('Vislib ErrorHandler Test Suite', function() { let errorHandler; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function() { - errorHandler = new ErrorHandler(); - }) - ); + beforeEach(() => { + errorHandler = new ErrorHandler(); + }); describe('validateWidthandHeight Method', function() { it('should throw an error when width and/or height is 0', function() { diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/_vis_fixture.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/_vis_fixture.js index c49ca732f0915..c001a04f4b6e8 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/_vis_fixture.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/_vis_fixture.js @@ -22,15 +22,6 @@ import $ from 'jquery'; import { Vis } from '../../../vis'; -// TODO: remove legacy imports when/of converting tests to jest -import { - setHierarchicalTooltipFormatter, - getHierarchicalTooltipFormatter, -} from 'ui/vis/components/tooltip/_hierarchical_tooltip_formatter'; -import { - setPointSeriesTooltipFormatter, - getPointSeriesTooltipFormatter, -} from 'ui/vis/components/tooltip/_pointseries_tooltip_formatter'; import { vislibSeriesResponseHandlerProvider, vislibSlicesResponseHandlerProvider, @@ -73,29 +64,33 @@ const getDeps = () => { return { uiSettings, vislibColor, - getHierarchicalTooltipFormatter, - getPointSeriesTooltipFormatter, vislibSeriesResponseHandlerProvider, vislibSlicesResponseHandlerProvider, }; }; -export default function getVislibFixtures(Private) { - setHierarchicalTooltipFormatter(Private); - setPointSeriesTooltipFormatter(Private); +export const getMockUiState = () => { + const map = new Map(); - return function(visLibParams, element) { - return new Vis( - element || $visCanvas.new(), - _.defaults({}, visLibParams || {}, { - addTooltip: true, - addLegend: true, - defaultYExtents: false, - setYExtents: false, - yAxis: {}, - type: 'histogram', - }), - getDeps() - ); - }; + return (() => ({ + get: (...args) => map.get(...args), + set: (...args) => map.set(...args), + setSilent: (...args) => map.set(...args), + on: () => undefined, + }))(); +}; + +export function getVis(visLibParams, element) { + return new Vis( + element || $visCanvas.new(), + _.defaults({}, visLibParams || {}, { + addTooltip: true, + addLegend: true, + defaultYExtents: false, + setYExtents: false, + yAxis: {}, + type: 'histogram', + }), + getDeps() + ); } diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/handler/handler.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/handler/handler.js index b309c97d24000..8e25015c10186 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/handler/handler.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/handler/handler.js @@ -17,36 +17,29 @@ * under the License. */ -import ngMock from 'ng_mock'; import expect from '@kbn/expect'; +import $ from 'jquery'; // Data import series from '../fixtures/mock_data/date_histogram/_series'; import columns from '../fixtures/mock_data/date_histogram/_columns'; import rows from '../fixtures/mock_data/date_histogram/_rows'; import stackedSeries from '../fixtures/mock_data/date_histogram/_stacked_series'; -import $ from 'jquery'; -import 'ui/persisted_state'; -import getFixturesVislibVisFixtureProvider from '../fixtures/_vis_fixture'; +import { getVis, getMockUiState } from '../fixtures/_vis_fixture'; + const dateHistogramArray = [series, columns, rows, stackedSeries]; const names = ['series', 'columns', 'rows', 'stackedSeries']; dateHistogramArray.forEach(function(data, i) { describe('Vislib Handler Test Suite for ' + names[i] + ' Data', function() { - let vis; - let persistedState; const events = ['click', 'brush']; + let vis; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function(Private, $injector) { - const getVis = getFixturesVislibVisFixtureProvider(Private); - vis = getVis(); - persistedState = new ($injector.get('PersistedState'))(); - vis.render(data, persistedState); - }) - ); + beforeEach(() => { + vis = getVis(); + vis.render(data, getMockUiState()); + }); afterEach(function() { vis.destroy(); @@ -107,9 +100,7 @@ dateHistogramArray.forEach(function(data, i) { describe('removeAll Method', function() { beforeEach(function() { - ngMock.inject(function() { - vis.handler.removeAll(vis.element); - }); + vis.handler.removeAll(vis.element); }); it('should remove all DOM elements from the el', function() { diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/layout.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/layout.js index c8636f34ce6f8..f72794e27e834 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/layout.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/layout.js @@ -18,18 +18,17 @@ */ import d3 from 'd3'; -import ngMock from 'ng_mock'; import expect from '@kbn/expect'; -import 'ui/persisted_state'; +import $ from 'jquery'; // Data import series from '../fixtures/mock_data/date_histogram/_series'; import columns from '../fixtures/mock_data/date_histogram/_columns'; import rows from '../fixtures/mock_data/date_histogram/_rows'; import stackedSeries from '../fixtures/mock_data/date_histogram/_stacked_series'; -import $ from 'jquery'; + import { Layout } from '../../../lib/layout/layout'; -import getFixturesVislibVisFixtureProvider from '../fixtures/_vis_fixture'; +import { getVis, getMockUiState } from '../fixtures/_vis_fixture'; import { VisConfig } from '../../../lib/vis_config'; const dateHistogramArray = [series, columns, rows, stackedSeries]; @@ -38,23 +37,18 @@ const names = ['series', 'columns', 'rows', 'stackedSeries']; dateHistogramArray.forEach(function(data, i) { describe('Vislib Layout Class Test Suite for ' + names[i] + ' Data', function() { let vis; - let persistedState; + let mockUiState; let numberOfCharts; let testLayout; - beforeEach(ngMock.module('kibana')); - - beforeEach(function() { - ngMock.inject(function(Private, $injector) { - const getVis = getFixturesVislibVisFixtureProvider(Private); - vis = getVis(); - persistedState = new ($injector.get('PersistedState'))(); - vis.render(data, persistedState); - numberOfCharts = vis.handler.charts.length; - }); + beforeEach(() => { + vis = getVis(); + mockUiState = getMockUiState(); + vis.render(data, mockUiState); + numberOfCharts = vis.handler.charts.length; }); - afterEach(function() { + afterEach(() => { vis.destroy(); }); @@ -81,7 +75,7 @@ dateHistogramArray.forEach(function(data, i) { type: 'histogram', }, data, - persistedState, + mockUiState, vis.element, () => undefined ); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/layout_types.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/layout_types.js index 4dc52309c76fd..cc6d33a2d98da 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/layout_types.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/layout_types.js @@ -18,19 +18,16 @@ */ import _ from 'lodash'; -import ngMock from 'ng_mock'; import expect from '@kbn/expect'; + import { layoutTypes as layoutType } from '../../../lib/layout/layout_types'; describe('Vislib Layout Types Test Suite', function() { let layoutFunc; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function() { - layoutFunc = layoutType.point_series; - }) - ); + beforeEach(() => { + layoutFunc = layoutType.point_series; + }); it('should be an object', function() { expect(_.isObject(layoutType)).to.be(true); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/splits/column_chart/splits.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/splits/column_chart/splits.js index 2626fd3ace2b0..3942aa18891b8 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/splits/column_chart/splits.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/splits/column_chart/splits.js @@ -18,9 +18,9 @@ */ import d3 from 'd3'; -import ngMock from 'ng_mock'; import expect from '@kbn/expect'; import $ from 'jquery'; + import { chartSplit } from '../../../../../lib/layout/splits/column_chart/chart_split'; import { chartTitleSplit } from '../../../../../lib/layout/splits/column_chart/chart_title_split'; import { xAxisSplit } from '../../../../../lib/layout/splits/column_chart/x_axis_split'; @@ -150,16 +150,13 @@ describe('Vislib Split Function Test Suite', function() { ], }; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function() { - el = d3 - .select('body') - .append('div') - .attr('class', 'visualization') - .datum(data); - }) - ); + beforeEach(() => { + el = d3 + .select('body') + .append('div') + .attr('class', 'visualization') + .datum(data); + }); afterEach(function() { el.remove(); @@ -168,11 +165,9 @@ describe('Vislib Split Function Test Suite', function() { describe('chart split function', function() { let fixture; - beforeEach( - ngMock.inject(function() { - fixture = d3.select('.visualization').call(chartSplit); - }) - ); + beforeEach(function() { + fixture = d3.select('.visualization').call(chartSplit); + }); afterEach(function() { fixture.remove(); @@ -192,28 +187,26 @@ describe('Vislib Split Function Test Suite', function() { let newEl; let fixture; - beforeEach( - ngMock.inject(function() { - visEl = el.append('div').attr('class', 'visWrapper'); - visEl.append('div').attr('class', 'visAxis__splitTitles--x'); - visEl.append('div').attr('class', 'visAxis__splitTitles--y'); - visEl.select('.visAxis__splitTitles--x').call(chartTitleSplit); - visEl.select('.visAxis__splitTitles--y').call(chartTitleSplit); + beforeEach(function() { + visEl = el.append('div').attr('class', 'visWrapper'); + visEl.append('div').attr('class', 'visAxis__splitTitles--x'); + visEl.append('div').attr('class', 'visAxis__splitTitles--y'); + visEl.select('.visAxis__splitTitles--x').call(chartTitleSplit); + visEl.select('.visAxis__splitTitles--y').call(chartTitleSplit); - newEl = d3 - .select('body') - .append('div') - .attr('class', 'visWrapper') - .datum({ series: [] }); + newEl = d3 + .select('body') + .append('div') + .attr('class', 'visWrapper') + .datum({ series: [] }); - newEl.append('div').attr('class', 'visAxis__splitTitles--x'); - newEl.append('div').attr('class', 'visAxis__splitTitles--y'); - newEl.select('.visAxis__splitTitles--x').call(chartTitleSplit); - newEl.select('.visAxis__splitTitles--y').call(chartTitleSplit); + newEl.append('div').attr('class', 'visAxis__splitTitles--x'); + newEl.append('div').attr('class', 'visAxis__splitTitles--y'); + newEl.select('.visAxis__splitTitles--x').call(chartTitleSplit); + newEl.select('.visAxis__splitTitles--y').call(chartTitleSplit); - fixture = newEl.selectAll(this.childNodes)[0].length; - }) - ); + fixture = newEl.selectAll(this.childNodes)[0].length; + }); afterEach(function() { newEl.remove(); @@ -237,17 +230,15 @@ describe('Vislib Split Function Test Suite', function() { let fixture; let divs; - beforeEach( - ngMock.inject(function() { - fixture = d3 - .select('body') - .append('div') - .attr('class', 'columns') - .datum({ columns: [{}, {}] }); - d3.select('.columns').call(xAxisSplit); - divs = d3.selectAll('.x-axis-div')[0]; - }) - ); + beforeEach(function() { + fixture = d3 + .select('body') + .append('div') + .attr('class', 'columns') + .datum({ columns: [{}, {}] }); + d3.select('.columns').call(xAxisSplit); + divs = d3.selectAll('.x-axis-div')[0]; + }); afterEach(function() { fixture.remove(); @@ -263,19 +254,17 @@ describe('Vislib Split Function Test Suite', function() { let fixture; let divs; - beforeEach( - ngMock.inject(function() { - fixture = d3 - .select('body') - .append('div') - .attr('class', 'rows') - .datum({ rows: [{}, {}] }); + beforeEach(function() { + fixture = d3 + .select('body') + .append('div') + .attr('class', 'rows') + .datum({ rows: [{}, {}] }); - d3.select('.rows').call(yAxisSplit); + d3.select('.rows').call(yAxisSplit); - divs = d3.selectAll('.y-axis-div')[0]; - }) - ); + divs = d3.selectAll('.y-axis-div')[0]; + }); afterEach(function() { fixture.remove(); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/splits/gauge_chart/splits.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/splits/gauge_chart/splits.js index 0c744e4fa2b57..8978f80f58dde 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/splits/gauge_chart/splits.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/splits/gauge_chart/splits.js @@ -18,9 +18,9 @@ */ import d3 from 'd3'; -import ngMock from 'ng_mock'; import expect from '@kbn/expect'; import $ from 'jquery'; + import { chartSplit } from '../../../../../lib/layout/splits/gauge_chart/chart_split'; import { chartTitleSplit } from '../../../../../lib/layout/splits/gauge_chart/chart_title_split'; @@ -148,16 +148,13 @@ describe('Vislib Gauge Split Function Test Suite', function() { ], }; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function() { - el = d3 - .select('body') - .append('div') - .attr('class', 'visualization') - .datum(data); - }) - ); + beforeEach(function() { + el = d3 + .select('body') + .append('div') + .attr('class', 'visualization') + .datum(data); + }); afterEach(function() { el.remove(); @@ -166,11 +163,9 @@ describe('Vislib Gauge Split Function Test Suite', function() { describe('chart split function', function() { let fixture; - beforeEach( - ngMock.inject(function() { - fixture = d3.select('.visualization').call(chartSplit); - }) - ); + beforeEach(function() { + fixture = d3.select('.visualization').call(chartSplit); + }); afterEach(function() { fixture.remove(); @@ -188,15 +183,13 @@ describe('Vislib Gauge Split Function Test Suite', function() { describe('chart title split function', function() { let visEl; - beforeEach( - ngMock.inject(function() { - visEl = el.append('div').attr('class', 'visWrapper'); - visEl.append('div').attr('class', 'visAxis__splitTitles--x'); - visEl.append('div').attr('class', 'visAxis__splitTitles--y'); - visEl.select('.visAxis__splitTitles--x').call(chartTitleSplit); - visEl.select('.visAxis__splitTitles--y').call(chartTitleSplit); - }) - ); + beforeEach(function() { + visEl = el.append('div').attr('class', 'visWrapper'); + visEl.append('div').attr('class', 'visAxis__splitTitles--x'); + visEl.append('div').attr('class', 'visAxis__splitTitles--y'); + visEl.select('.visAxis__splitTitles--x').call(chartTitleSplit); + visEl.select('.visAxis__splitTitles--y').call(chartTitleSplit); + }); afterEach(function() { visEl.remove(); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/types/column_layout.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/types/column_layout.js index bdf886033d8d6..e9c2ff0d2fa07 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/types/column_layout.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/types/column_layout.js @@ -19,8 +19,8 @@ import d3 from 'd3'; import _ from 'lodash'; -import ngMock from 'ng_mock'; import expect from '@kbn/expect'; + import { layoutTypes } from '../../../../lib/layout/layout_types'; describe('Vislib Column Layout Test Suite', function() { @@ -85,16 +85,13 @@ describe('Vislib Column Layout Test Suite', function() { yAxisLabel: 'Count', }; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function() { - el = d3 - .select('body') - .append('div') - .attr('class', 'visualization'); - columnLayout = layoutTypes.point_series(el, data); - }) - ); + beforeEach(function() { + el = d3 + .select('body') + .append('div') + .attr('class', 'visualization'); + columnLayout = layoutTypes.point_series(el, data); + }); afterEach(function() { el.remove(); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/types/point_series.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/types/point_series.js index ee3f5a476cd9a..03646d08298dd 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/types/point_series.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/types/point_series.js @@ -17,12 +17,13 @@ * under the License. */ -import ngMock from 'ng_mock'; import expect from '@kbn/expect'; -import { vislibPointSeriesTypes as pointSeriesConfig } from '../../../lib/types/point_series'; + import percentileTestdata from './testdata_linechart_percentile.json'; import percentileTestdataResult from './testdata_linechart_percentile_result.json'; +import { vislibPointSeriesTypes as pointSeriesConfig } from '../../../lib/types/point_series'; + describe('Point Series Config Type Class Test Suite', function() { let parsedConfig; const histogramConfig = { @@ -95,8 +96,6 @@ describe('Point Series Config Type Class Test Suite', function() { }, }; - beforeEach(ngMock.module('kibana')); - describe('histogram chart', function() { beforeEach(function() { parsedConfig = pointSeriesConfig.column(histogramConfig, data); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/vis_config.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/vis_config.js index 3f0253b4a4670..7dfd2ded36a66 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/vis_config.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/vis_config.js @@ -18,10 +18,10 @@ */ import d3 from 'd3'; -import ngMock from 'ng_mock'; import expect from '@kbn/expect'; -import 'ui/persisted_state'; + import { VisConfig } from '../../lib/vis_config'; +import { getMockUiState } from './fixtures/_vis_fixture'; describe('Vislib VisConfig Class Test Suite', function() { let el; @@ -85,27 +85,23 @@ describe('Vislib VisConfig Class Test Suite', function() { yAxisLabel: 'Count', }; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function($injector) { - const PersistedState = $injector.get('PersistedState'); - el = d3 - .select('body') - .append('div') - .attr('class', 'visWrapper') - .node(); + beforeEach(() => { + el = d3 + .select('body') + .append('div') + .attr('class', 'visWrapper') + .node(); - visConfig = new VisConfig( - { - type: 'point_series', - }, - data, - new PersistedState(), - el, - () => undefined - ); - }) - ); + visConfig = new VisConfig( + { + type: 'point_series', + }, + data, + getMockUiState(), + el, + () => undefined + ); + }); afterEach(() => { el.remove(); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/x_axis.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/x_axis.js index 09fe067537c7f..d42562a87b825 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/x_axis.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/x_axis.js @@ -19,15 +19,15 @@ import d3 from 'd3'; import _ from 'lodash'; -import ngMock from 'ng_mock'; import expect from '@kbn/expect'; -import 'ui/persisted_state'; import $ from 'jquery'; + import { Axis } from '../../lib/axis'; import { VisConfig } from '../../lib/vis_config'; +import { getMockUiState } from './fixtures/_vis_fixture'; describe('Vislib xAxis Class Test Suite', function() { - let persistedState; + let mockUiState; let xAxis; let el; let fixture; @@ -105,34 +105,30 @@ describe('Vislib xAxis Class Test Suite', function() { yAxisLabel: 'Count', }; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function($injector) { - persistedState = new ($injector.get('PersistedState'))(); - - el = d3 - .select('body') - .append('div') - .attr('class', 'visAxis--x') - .style('height', '40px'); + beforeEach(() => { + mockUiState = getMockUiState(); + el = d3 + .select('body') + .append('div') + .attr('class', 'visAxis--x') + .style('height', '40px'); - fixture = el.append('div').attr('class', 'x-axis-div'); + fixture = el.append('div').attr('class', 'x-axis-div'); - const visConfig = new VisConfig( - { - type: 'histogram', - }, - data, - persistedState, - $('.x-axis-div')[0], - () => undefined - ); - xAxis = new Axis(visConfig, { - type: 'category', - id: 'CategoryAxis-1', - }); - }) - ); + const visConfig = new VisConfig( + { + type: 'histogram', + }, + data, + mockUiState, + $('.x-axis-div')[0], + () => undefined + ); + xAxis = new Axis(visConfig, { + type: 'category', + id: 'CategoryAxis-1', + }); + }); afterEach(function() { fixture.remove(); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/y_axis.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/y_axis.js index e857aca3bf3ed..f73011d661645 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/y_axis.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/y_axis.js @@ -19,15 +19,15 @@ import _ from 'lodash'; import d3 from 'd3'; -import ngMock from 'ng_mock'; -import expect from '@kbn/expect'; -import 'ui/persisted_state'; import $ from 'jquery'; +import expect from '@kbn/expect'; + import { Axis } from '../../lib/axis'; import { VisConfig } from '../../lib/vis_config'; +import { getMockUiState } from './fixtures/_vis_fixture'; const YAxis = Axis; -let persistedState; +let mockUiState; let el; let buildYAxis; let yAxis; @@ -96,7 +96,7 @@ function createData(seriesData) { type: 'histogram', }, data, - persistedState, + mockUiState, node, () => undefined ); @@ -121,15 +121,10 @@ function createData(seriesData) { } describe('Vislib yAxis Class Test Suite', function() { - beforeEach(ngMock.module('kibana')); - - beforeEach( - ngMock.inject(function($injector) { - persistedState = new ($injector.get('PersistedState'))(); - - expect($('.y-axis-wrapper')).to.have.length(0); - }) - ); + beforeEach(() => { + mockUiState = getMockUiState(); + expect($('.y-axis-wrapper')).to.have.length(0); + }); afterEach(function() { if (el) { diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/vis.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/vis.js index a6d1c4daf5d2c..4852f71d8c45b 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/vis.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/vis.js @@ -19,19 +19,15 @@ import _ from 'lodash'; import $ from 'jquery'; -import ngMock from 'ng_mock'; - import expect from '@kbn/expect'; -import 'ui/persisted_state'; import series from './lib/fixtures/mock_data/date_histogram/_series'; import columns from './lib/fixtures/mock_data/date_histogram/_columns'; import rows from './lib/fixtures/mock_data/date_histogram/_rows'; import stackedSeries from './lib/fixtures/mock_data/date_histogram/_stacked_series'; -import getFixturesVislibVisFixtureProvider from './lib/fixtures/_vis_fixture'; +import { getVis, getMockUiState } from './lib/fixtures/_vis_fixture'; const dataArray = [series, columns, rows, stackedSeries]; - const names = ['series', 'columns', 'rows', 'stackedSeries']; dataArray.forEach(function(data, i) { @@ -39,19 +35,15 @@ dataArray.forEach(function(data, i) { const beforeEvent = 'click'; const afterEvent = 'brush'; let vis; - let persistedState; + let mockUiState; let secondVis; let numberOfCharts; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function(Private, $injector) { - const getVis = getFixturesVislibVisFixtureProvider(Private); - vis = getVis(); - persistedState = new ($injector.get('PersistedState'))(); - secondVis = getVis(); - }) - ); + beforeEach(() => { + vis = getVis(); + secondVis = getVis(); + mockUiState = getMockUiState(); + }); afterEach(function() { vis.destroy(); @@ -60,7 +52,7 @@ dataArray.forEach(function(data, i) { describe('render Method', function() { beforeEach(function() { - vis.render(data, persistedState); + vis.render(data, mockUiState); numberOfCharts = vis.handler.charts.length; }); @@ -78,7 +70,7 @@ dataArray.forEach(function(data, i) { it('should throw an error if no data is provided', function() { expect(function() { - vis.render(null, persistedState); + vis.render(null, mockUiState); }).to.throwError(); }); }); @@ -91,8 +83,8 @@ dataArray.forEach(function(data, i) { describe('destroy Method', function() { beforeEach(function() { - vis.render(data, persistedState); - secondVis.render(data, persistedState); + vis.render(data, mockUiState); + secondVis.render(data, mockUiState); secondVis.destroy(); }); @@ -107,7 +99,7 @@ dataArray.forEach(function(data, i) { describe('set Method', function() { beforeEach(function() { - vis.render(data, persistedState); + vis.render(data, mockUiState); vis.set('addLegend', false); vis.set('offset', 'wiggle'); }); @@ -120,7 +112,7 @@ dataArray.forEach(function(data, i) { describe('get Method', function() { beforeEach(function() { - vis.render(data, persistedState); + vis.render(data, mockUiState); }); it('should get attribute values', function() { @@ -142,7 +134,7 @@ dataArray.forEach(function(data, i) { }); // Render chart - vis.render(data, persistedState); + vis.render(data, mockUiState); // Add event after charts have rendered listeners.forEach(function(listener) { @@ -196,7 +188,7 @@ dataArray.forEach(function(data, i) { vis.off(beforeEvent, listener1); // Render chart - vis.render(data, persistedState); + vis.render(data, mockUiState); // Add event after charts have rendered listeners.forEach(function(listener) { diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/area_chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/area_chart.js index 7fe350bd85e05..c3f5859eb454c 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/area_chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/area_chart.js @@ -18,14 +18,11 @@ */ import d3 from 'd3'; -import ngMock from 'ng_mock'; import _ from 'lodash'; import $ from 'jquery'; - import expect from '@kbn/expect'; -import 'ui/persisted_state'; -import getFixturesVislibVisFixtureProvider from '../lib/fixtures/_vis_fixture'; +import { getVis, getMockUiState } from '../lib/fixtures/_vis_fixture'; const dataTypesArray = { 'series pos': require('../lib/fixtures/mock_data/date_histogram/_series'), @@ -46,18 +43,14 @@ const visLibParams = { _.forOwn(dataTypesArray, function(dataType, dataTypeName) { describe('Vislib Area Chart Test Suite for ' + dataTypeName + ' Data', function() { let vis; - let persistedState; - - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function(Private, $injector) { - const getVis = getFixturesVislibVisFixtureProvider(Private); - vis = getVis(visLibParams); - persistedState = new ($injector.get('PersistedState'))(); - vis.on('brush', _.noop); - vis.render(dataType, persistedState); - }) - ); + let mockUiState; + + beforeEach(() => { + vis = getVis(visLibParams); + mockUiState = getMockUiState(); + vis.on('brush', _.noop); + vis.render(dataType, mockUiState); + }); afterEach(function() { vis.destroy(); @@ -97,17 +90,15 @@ _.forOwn(dataTypesArray, function(dataType, dataTypeName) { let d3selectedPath; let onMouseOver; - beforeEach( - ngMock.inject(function() { - vis.handler.charts.forEach(function(chart) { - path = $(chart.chartEl).find('path')[0]; - d3selectedPath = d3.select(path)[0][0]; + beforeEach(function() { + vis.handler.charts.forEach(function(chart) { + path = $(chart.chartEl).find('path')[0]; + d3selectedPath = d3.select(path)[0][0]; - // d3 instance of click and hover - onMouseOver = !!d3selectedPath.__onmouseover; - }); - }) - ); + // d3 instance of click and hover + onMouseOver = !!d3selectedPath.__onmouseover; + }); + }); it('should attach a hover event', function() { vis.handler.charts.forEach(function() { @@ -124,20 +115,18 @@ _.forOwn(dataTypesArray, function(dataType, dataTypeName) { let onClick; let onMouseOver; - beforeEach( - ngMock.inject(function() { - vis.handler.charts.forEach(function(chart) { - circle = $(chart.chartEl).find('circle')[0]; - brush = $(chart.chartEl).find('.brush'); - d3selectedCircle = d3.select(circle)[0][0]; - - // d3 instance of click and hover - onBrush = !!brush; - onClick = !!d3selectedCircle.__onclick; - onMouseOver = !!d3selectedCircle.__onmouseover; - }); - }) - ); + beforeEach(() => { + vis.handler.charts.forEach(function(chart) { + circle = $(chart.chartEl).find('circle')[0]; + brush = $(chart.chartEl).find('.brush'); + d3selectedCircle = d3.select(circle)[0][0]; + + // d3 instance of click and hover + onBrush = !!brush; + onClick = !!d3selectedCircle.__onclick; + onMouseOver = !!d3selectedCircle.__onmouseover; + }); + }); // D3 brushing requires that a g element is appended that // listens for mousedown events. This g element includes @@ -219,7 +208,7 @@ _.forOwn(dataTypesArray, function(dataType, dataTypeName) { describe('defaultYExtents is true', function() { beforeEach(function() { vis.visConfigArgs.defaultYExtents = true; - vis.render(dataType, persistedState); + vis.render(dataType, mockUiState); }); it('should return yAxis extents equal to data extents', function() { @@ -238,7 +227,7 @@ _.forOwn(dataTypesArray, function(dataType, dataTypeName) { beforeEach(function() { vis.visConfigArgs.defaultYExtents = true; vis.visConfigArgs.boundsMargin = boundsMarginValue; - vis.render(dataType, persistedState); + vis.render(dataType, mockUiState); }); it('should return yAxis extents equal to data extents with boundsMargin', function() { diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/chart.js index 088d3377af4dd..9653f9abab6fb 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/chart.js @@ -18,16 +18,12 @@ */ import d3 from 'd3'; -import ngMock from 'ng_mock'; - import expect from '@kbn/expect'; -import 'ui/persisted_state'; import { Chart } from '../../visualizations/_chart'; -import getFixturesVislibVisFixtureProvider from '../lib/fixtures/_vis_fixture'; +import { getVis, getMockUiState } from '../lib/fixtures/_vis_fixture'; describe('Vislib _chart Test Suite', function() { - let persistedState; let vis; let el; let myChart; @@ -109,30 +105,24 @@ describe('Vislib _chart Test Suite', function() { yAxisLabel: 'Count', }; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function(Private, $injector) { - const getVis = getFixturesVislibVisFixtureProvider(Private); - persistedState = new ($injector.get('PersistedState'))(); - - el = d3 - .select('body') - .append('div') - .attr('class', 'column-chart'); + beforeEach(() => { + el = d3 + .select('body') + .append('div') + .attr('class', 'column-chart'); - config = { - type: 'histogram', - addTooltip: true, - addLegend: true, - zeroFill: true, - }; + config = { + type: 'histogram', + addTooltip: true, + addLegend: true, + zeroFill: true, + }; - vis = getVis(config, el[0][0]); - vis.render(data, persistedState); + vis = getVis(config, el[0][0]); + vis.render(data, getMockUiState()); - myChart = vis.handler.charts[0]; - }) - ); + myChart = vis.handler.charts[0]; + }); afterEach(function() { el.remove(); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/column_chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/column_chart.js index d02060ef29bdd..2216294fcbac1 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/column_chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/column_chart.js @@ -17,12 +17,10 @@ * under the License. */ -import ngMock from 'ng_mock'; import _ from 'lodash'; import d3 from 'd3'; - +import $ from 'jquery'; import expect from '@kbn/expect'; -import 'ui/persisted_state'; // Data import series from '../lib/fixtures/mock_data/date_histogram/_series'; @@ -31,11 +29,11 @@ import seriesNeg from '../lib/fixtures/mock_data/date_histogram/_series_neg'; import termsColumns from '../lib/fixtures/mock_data/terms/_columns'; import histogramRows from '../lib/fixtures/mock_data/histogram/_rows'; import stackedSeries from '../lib/fixtures/mock_data/date_histogram/_stacked_series'; + import { seriesMonthlyInterval } from '../lib/fixtures/mock_data/date_histogram/_series_monthly_interval'; import { rowsSeriesWithHoles } from '../lib/fixtures/mock_data/date_histogram/_rows_series_with_holes'; import rowsWithZeros from '../lib/fixtures/mock_data/date_histogram/_rows'; -import $ from 'jquery'; -import getFixturesVislibVisFixtureProvider from '../lib/fixtures/_vis_fixture'; +import { getVis, getMockUiState } from '../lib/fixtures/_vis_fixture'; // tuple, with the format [description, mode, data] const dataTypesArray = [ @@ -54,7 +52,7 @@ dataTypesArray.forEach(function(dataType) { describe('Vislib Column Chart Test Suite for ' + name + ' Data', function() { let vis; - let persistedState; + let mockUiState; const visLibParams = { type: 'histogram', addLegend: true, @@ -67,16 +65,12 @@ dataTypesArray.forEach(function(dataType) { }, }; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function(Private, $injector) { - const getVis = getFixturesVislibVisFixtureProvider(Private); - vis = getVis(visLibParams); - persistedState = new ($injector.get('PersistedState'))(); - vis.on('brush', _.noop); - vis.render(data, persistedState); - }) - ); + beforeEach(() => { + vis = getVis(visLibParams); + mockUiState = getMockUiState(); + vis.on('brush', _.noop); + vis.render(data, mockUiState); + }); afterEach(function() { vis.destroy(); @@ -200,7 +194,7 @@ dataTypesArray.forEach(function(dataType) { describe('defaultYExtents is true', function() { beforeEach(function() { vis.visConfigArgs.defaultYExtents = true; - vis.render(data, persistedState); + vis.render(data, mockUiState); }); it('should return yAxis extents equal to data extents', function() { @@ -219,7 +213,7 @@ dataTypesArray.forEach(function(dataType) { beforeEach(function() { vis.visConfigArgs.defaultYExtents = true; vis.visConfigArgs.boundsMargin = boundsMarginValue; - vis.render(data, persistedState); + vis.render(data, mockUiState); }); it('should return yAxis extents equal to data extents with boundsMargin', function() { @@ -247,7 +241,7 @@ dataTypesArray.forEach(function(dataType) { describe('stackData method - data set with zeros in percentage mode', function() { let vis; - let persistedState; + let mockUiState; const visLibParams = { type: 'histogram', addLegend: true, @@ -256,22 +250,18 @@ describe('stackData method - data set with zeros in percentage mode', function() zeroFill: true, }; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function(Private, $injector) { - const getVis = getFixturesVislibVisFixtureProvider(Private); - vis = getVis(visLibParams); - persistedState = new ($injector.get('PersistedState'))(); - vis.on('brush', _.noop); - }) - ); + beforeEach(() => { + vis = getVis(visLibParams); + mockUiState = getMockUiState(); + vis.on('brush', _.noop); + }); afterEach(function() { vis.destroy(); }); it('should not mutate the injected zeros', function() { - vis.render(seriesMonthlyInterval, persistedState); + vis.render(seriesMonthlyInterval, mockUiState); expect(vis.handler.charts).to.have.length(1); const chart = vis.handler.charts[0]; @@ -284,7 +274,7 @@ describe('stackData method - data set with zeros in percentage mode', function() }); it('should not mutate zeros that exist in the data', function() { - vis.render(rowsWithZeros, persistedState); + vis.render(rowsWithZeros, mockUiState); expect(vis.handler.charts).to.have.length(2); const chart = vis.handler.charts[0]; @@ -298,7 +288,7 @@ describe('stackData method - data set with zeros in percentage mode', function() describe('datumWidth - split chart data set with holes', function() { let vis; - let persistedState; + let mockUiState; const visLibParams = { type: 'histogram', addLegend: true, @@ -307,16 +297,12 @@ describe('datumWidth - split chart data set with holes', function() { zeroFill: true, }; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function(Private, $injector) { - const getVis = getFixturesVislibVisFixtureProvider(Private); - vis = getVis(visLibParams); - persistedState = new ($injector.get('PersistedState'))(); - vis.on('brush', _.noop); - vis.render(rowsSeriesWithHoles, persistedState); - }) - ); + beforeEach(() => { + vis = getVis(visLibParams); + mockUiState = getMockUiState(); + vis.on('brush', _.noop); + vis.render(rowsSeriesWithHoles, mockUiState); + }); afterEach(function() { vis.destroy(); @@ -336,7 +322,7 @@ describe('datumWidth - split chart data set with holes', function() { describe('datumWidth - monthly interval', function() { let vis; - let persistedState; + let mockUiState; const visLibParams = { type: 'histogram', addLegend: true, @@ -345,16 +331,12 @@ describe('datumWidth - monthly interval', function() { zeroFill: true, }; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function(Private, $injector) { - const getVis = getFixturesVislibVisFixtureProvider(Private); - vis = getVis(visLibParams); - persistedState = new ($injector.get('PersistedState'))(); - vis.on('brush', _.noop); - vis.render(seriesMonthlyInterval, persistedState); - }) - ); + beforeEach(() => { + vis = getVis(visLibParams); + mockUiState = getMockUiState(); + vis.on('brush', _.noop); + vis.render(seriesMonthlyInterval, mockUiState); + }); afterEach(function() { vis.destroy(); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/gauge_chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/gauge_chart.js index 074b34e1c03c4..fe25734fcbfde 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/gauge_chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/gauge_chart.js @@ -17,21 +17,15 @@ * under the License. */ -import ngMock from 'ng_mock'; import $ from 'jquery'; import _ from 'lodash'; - import expect from '@kbn/expect'; -import 'ui/persisted_state'; import data from '../lib/fixtures/mock_data/terms/_seriesMultiple'; -import getFixturesVislibVisFixtureProvider from '../lib/fixtures/_vis_fixture'; +import { getVis, getMockUiState } from '../lib/fixtures/_vis_fixture'; describe('Vislib Gauge Chart Test Suite', function() { - let PersistedState; - let vislibVis; let vis; - let persistedState; let chartEl; const visLibParams = { type: 'gauge', @@ -81,21 +75,15 @@ describe('Vislib Gauge Chart Test Suite', function() { vis.destroy(); $('.visChart').remove(); } - vis = vislibVis(config); - persistedState = new PersistedState(); + vis = getVis(config); vis.on('brush', _.noop); - vis.render(data, persistedState); + vis.render(data, getMockUiState()); chartEl = vis.handler.charts[0].chartEl; } - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function(Private, $injector) { - vislibVis = getFixturesVislibVisFixtureProvider(Private); - PersistedState = $injector.get('PersistedState'); - generateVis(); - }) - ); + beforeEach(() => { + generateVis(); + }); afterEach(function() { vis.destroy(); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/heatmap_chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/heatmap_chart.js index bf1dbad0b44cf..f4c952be191de 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/heatmap_chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/heatmap_chart.js @@ -17,12 +17,10 @@ * under the License. */ -import ngMock from 'ng_mock'; import _ from 'lodash'; +import $ from 'jquery'; import d3 from 'd3'; - import expect from '@kbn/expect'; -import 'ui/persisted_state'; // Data import series from '../lib/fixtures/mock_data/date_histogram/_series'; @@ -30,8 +28,8 @@ import seriesPosNeg from '../lib/fixtures/mock_data/date_histogram/_series_pos_n import seriesNeg from '../lib/fixtures/mock_data/date_histogram/_series_neg'; import termsColumns from '../lib/fixtures/mock_data/terms/_columns'; import stackedSeries from '../lib/fixtures/mock_data/date_histogram/_stacked_series'; -import $ from 'jquery'; -import getFixturesVislibVisFixtureProvider from '../lib/fixtures/_vis_fixture'; + +import { getVis, getMockUiState } from '../lib/fixtures/_vis_fixture'; // tuple, with the format [description, mode, data] const dataTypesArray = [ @@ -48,10 +46,8 @@ describe('Vislib Heatmap Chart Test Suite', function() { const data = dataType[1]; describe('for ' + name + ' Data', function() { - let PersistedState; - let vislibVis; let vis; - let persistedState; + let mockUiState; const visLibParams = { type: 'heatmap', addLegend: true, @@ -66,20 +62,15 @@ describe('Vislib Heatmap Chart Test Suite', function() { function generateVis(opts = {}) { const config = _.defaultsDeep({}, opts, visLibParams); - vis = vislibVis(config); - persistedState = new PersistedState(); + vis = getVis(config); + mockUiState = getMockUiState(); vis.on('brush', _.noop); - vis.render(data, persistedState); + vis.render(data, mockUiState); } - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function(Private, $injector) { - vislibVis = getFixturesVislibVisFixtureProvider(Private); - PersistedState = $injector.get('PersistedState'); - generateVis(); - }) - ); + beforeEach(() => { + generateVis(); + }); afterEach(function() { vis.destroy(); @@ -174,7 +165,7 @@ describe('Vislib Heatmap Chart Test Suite', function() { }); it('should define default colors', function() { - expect(persistedState.get('vis.defaultColors')).to.not.be(undefined); + expect(mockUiState.get('vis.defaultColors')).to.not.be(undefined); }); it('should set custom range', function() { diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/line_chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/line_chart.js index d010944a19e47..1269fe7bcf62e 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/line_chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/line_chart.js @@ -19,12 +19,9 @@ import d3 from 'd3'; import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; import $ from 'jquery'; import _ from 'lodash'; -import 'ui/persisted_state'; - // Data import seriesPos from '../lib/fixtures/mock_data/date_histogram/_series'; import seriesPosNeg from '../lib/fixtures/mock_data/date_histogram/_series_pos_neg'; @@ -32,7 +29,8 @@ import seriesNeg from '../lib/fixtures/mock_data/date_histogram/_series_neg'; import histogramColumns from '../lib/fixtures/mock_data/histogram/_columns'; import rangeRows from '../lib/fixtures/mock_data/range/_rows'; import termSeries from '../lib/fixtures/mock_data/terms/_series'; -import getFixturesVislibVisFixtureProvider from '../lib/fixtures/_vis_fixture'; + +import { getVis, getMockUiState } from '../lib/fixtures/_vis_fixture'; const dataTypes = [ ['series pos', seriesPos], @@ -50,25 +48,21 @@ describe('Vislib Line Chart', function() { describe(name + ' Data', function() { let vis; - let persistedState; - - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function(Private, $injector) { - const getVis = getFixturesVislibVisFixtureProvider(Private); - const visLibParams = { - type: 'line', - addLegend: true, - addTooltip: true, - drawLinesBetweenPoints: true, - }; - - vis = getVis(visLibParams); - persistedState = new ($injector.get('PersistedState'))(); - vis.render(data, persistedState); - vis.on('brush', _.noop); - }) - ); + let mockUiState; + + beforeEach(() => { + const visLibParams = { + type: 'line', + addLegend: true, + addTooltip: true, + drawLinesBetweenPoints: true, + }; + + vis = getVis(visLibParams); + mockUiState = getMockUiState(); + vis.render(data, mockUiState); + vis.on('brush', _.noop); + }); afterEach(function() { vis.destroy(); @@ -82,20 +76,18 @@ describe('Vislib Line Chart', function() { let onClick; let onMouseOver; - beforeEach( - ngMock.inject(function() { - vis.handler.charts.forEach(function(chart) { - circle = $(chart.chartEl).find('.circle')[0]; - brush = $(chart.chartEl).find('.brush'); - d3selectedCircle = d3.select(circle)[0][0]; - - // d3 instance of click and hover - onBrush = !!brush; - onClick = !!d3selectedCircle.__onclick; - onMouseOver = !!d3selectedCircle.__onmouseover; - }); - }) - ); + beforeEach(function() { + vis.handler.charts.forEach(function(chart) { + circle = $(chart.chartEl).find('.circle')[0]; + brush = $(chart.chartEl).find('.brush'); + d3selectedCircle = d3.select(circle)[0][0]; + + // d3 instance of click and hover + onBrush = !!brush; + onClick = !!d3selectedCircle.__onclick; + onMouseOver = !!d3selectedCircle.__onmouseover; + }); + }); // D3 brushing requires that a g element is appended that // listens for mousedown events. This g element includes @@ -177,7 +169,7 @@ describe('Vislib Line Chart', function() { describe('defaultYExtents is true', function() { beforeEach(function() { vis.visConfigArgs.defaultYExtents = true; - vis.render(data, persistedState); + vis.render(data, mockUiState); }); it('should return yAxis extents equal to data extents', function() { @@ -196,7 +188,7 @@ describe('Vislib Line Chart', function() { beforeEach(function() { vis.visConfigArgs.defaultYExtents = true; vis.visConfigArgs.boundsMargin = boundsMarginValue; - vis.render(data, persistedState); + vis.render(data, mockUiState); }); it('should return yAxis extents equal to data extents with boundsMargin', function() { diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/pie_chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/pie_chart.js index 381dfcd387cc2..f38fa47393a14 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/pie_chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/pie_chart.js @@ -18,19 +18,16 @@ */ import d3 from 'd3'; -import ngMock from 'ng_mock'; import _ from 'lodash'; import $ from 'jquery'; - import expect from '@kbn/expect'; -// TODO: Remove ui imports once converting to jest -import 'ui/persisted_state'; + import { vislibSlicesResponseHandlerProvider } from 'ui/vis/response_handlers/vislib'; import fixtures from 'fixtures/fake_hierarchical_data'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; -import getFixturesVislibVisFixtureProvider from '../lib/fixtures/_vis_fixture'; +import { getVis, getMockUiState } from '../lib/fixtures/_vis_fixture'; import { Vis, tabifyAggResponse } from '../../../legacy_imports'; const rowAgg = [ @@ -123,35 +120,31 @@ describe('No global chart settings', function() { addTooltip: true, }; let chart1; - let persistedState; + let mockUiState; let indexPattern; let responseHandler; let data1; let stubVis1; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function(Private, $injector) { - const getVis = getFixturesVislibVisFixtureProvider(Private); - chart1 = getVis(visLibParams1); - persistedState = new ($injector.get('PersistedState'))(); - indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - responseHandler = vislibSlicesResponseHandlerProvider().handler; - - let id1 = 1; - stubVis1 = new Vis(indexPattern, { - type: 'pie', - aggs: rowAgg, - }); + beforeEach(() => { + chart1 = getVis(visLibParams1); + mockUiState = getMockUiState(); + indexPattern = new FixturesStubbedLogstashIndexPatternProvider(); + responseHandler = vislibSlicesResponseHandlerProvider().handler; - stubVis1.isHierarchical = () => true; + let id1 = 1; + stubVis1 = new Vis(indexPattern, { + type: 'pie', + aggs: rowAgg, + }); - // We need to set the aggs to a known value. - _.each(stubVis1.aggs.aggs, function(agg) { - agg.id = 'agg_' + id1++; - }); - }) - ); + stubVis1.isHierarchical = () => true; + + // We need to set the aggs to a known value. + _.each(stubVis1.aggs.aggs, function(agg) { + agg.id = 'agg_' + id1++; + }); + }); beforeEach(async () => { const table1 = tabifyAggResponse(stubVis1.aggs, fixtures.threeTermBuckets, { @@ -159,7 +152,7 @@ describe('No global chart settings', function() { }); data1 = await responseHandler(table1, rowAggDimensions); - chart1.render(data1, persistedState); + chart1.render(data1, mockUiState); }); afterEach(function() { @@ -216,40 +209,37 @@ describe('Vislib PieChart Class Test Suite', function() { addTooltip: true, }; let vis; - let persistedState; + let mockUiState; let indexPattern; let data; let stubVis; let responseHandler; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function(Private, $injector) { - const getVis = getFixturesVislibVisFixtureProvider(Private); - vis = getVis(visLibParams); - persistedState = new ($injector.get('PersistedState'))(); - indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - responseHandler = vislibSlicesResponseHandlerProvider().handler; - - let id = 1; - stubVis = new Vis(indexPattern, { - type: 'pie', - aggs: dataAgg, - }); + beforeEach(() => { + vis = getVis(visLibParams); + mockUiState = getMockUiState(); + indexPattern = new FixturesStubbedLogstashIndexPatternProvider(); + responseHandler = vislibSlicesResponseHandlerProvider().handler; - // We need to set the aggs to a known value. - _.each(stubVis.aggs.aggs, function(agg) { - agg.id = 'agg_' + id++; - }); - }) - ); + let id = 1; + stubVis = new Vis(indexPattern, { + type: 'pie', + aggs: dataAgg, + }); + + // We need to set the aggs to a known value. + _.each(stubVis.aggs.aggs, function(agg) { + agg.id = 'agg_' + id++; + }); + }); beforeEach(async () => { const table = tabifyAggResponse(stubVis.aggs, fixtures.threeTermBuckets, { metricsAtAllLevels: true, }); data = await responseHandler(table, dataDimensions); - vis.render(data, persistedState); + + vis.render(data, mockUiState); }); afterEach(function() { diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/time_marker.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/time_marker.js index ec22d43c08cb2..d69f952325ed0 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/time_marker.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/time_marker.js @@ -18,11 +18,11 @@ */ import d3 from 'd3'; +import $ from 'jquery'; import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; + import series from '../lib/fixtures/mock_data/date_histogram/_series'; import terms from '../lib/fixtures/mock_data/terms/_columns'; -import $ from 'jquery'; import { TimeMarker } from '../../visualizations/time_marker'; describe('Vislib Time Marker Test Suite', function() { @@ -57,26 +57,23 @@ describe('Vislib Time Marker Test Suite', function() { let maxDomain; let domain; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function() { - minDomain = getExtent(series.series, d3.min); - maxDomain = getExtent(series.series, d3.max); - domain = [minDomain, maxDomain]; - xScale = d3.time - .scale() - .domain(domain) - .range([0, 500]); - defaultMarker = new TimeMarker(times, xScale, height); - customMarker = new TimeMarker(myTimes, xScale, height); - - selection = d3 - .select('body') - .append('div') - .attr('class', 'marker'); - selection.datum(series); - }) - ); + beforeEach(function() { + minDomain = getExtent(series.series, d3.min); + maxDomain = getExtent(series.series, d3.max); + domain = [minDomain, maxDomain]; + xScale = d3.time + .scale() + .domain(domain) + .range([0, 500]); + defaultMarker = new TimeMarker(times, xScale, height); + customMarker = new TimeMarker(myTimes, xScale, height); + + selection = d3 + .select('body') + .append('div') + .attr('class', 'marker'); + selection.datum(series); + }); afterEach(function() { selection.remove('*'); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/vis_types.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/vis_types.js index e150ca58c15cf..c8f0faf8dcca5 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/vis_types.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/vis_types.js @@ -17,20 +17,17 @@ * under the License. */ -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; import _ from 'lodash'; +import expect from '@kbn/expect'; + import { visTypes } from '../../visualizations/vis_types'; describe('Vislib Vis Types Test Suite', function() { let visFunc; - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function() { - visFunc = visTypes.point_series; - }) - ); + beforeEach(function() { + visFunc = visTypes.point_series; + }); it('should be an object', function() { expect(_.isObject(visTypes)).to.be(true); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/_index.scss b/src/legacy/core_plugins/vis_type_vislib/public/vislib/_index.scss index 0344fbb5359ec..78e16224a67a3 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/_index.scss +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/_index.scss @@ -1,6 +1,9 @@ @import './variables'; +@import './vislib_vis_type'; @import './lib/index'; +@import './components/tooltip/index'; +@import './components/legend/index'; @import './visualizations/point_series/index'; @import './visualizations/gauges/index'; diff --git a/src/legacy/ui/public/vis/vis_types/_vislib_vis_type.scss b/src/legacy/core_plugins/vis_type_vislib/public/vislib/_vislib_vis_type.scss similarity index 99% rename from src/legacy/ui/public/vis/vis_types/_vislib_vis_type.scss rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/_vislib_vis_type.scss index c1f369d3e3536..c03aa19140de0 100644 --- a/src/legacy/ui/public/vis/vis_types/_vislib_vis_type.scss +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/_vislib_vis_type.scss @@ -7,12 +7,15 @@ &.visLib--legend-left { flex-direction: row-reverse; } + &.visLib--legend-right { flex-direction: row; } + &.visLib--legend-top { flex-direction: column-reverse; } + &.visLib--legend-bottom { flex-direction: column; } diff --git a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/__snapshots__/vislib_vis_legend.test.tsx.snap b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/__snapshots__/legend.test.tsx.snap similarity index 100% rename from src/legacy/ui/public/vis/vis_types/vislib_vis_legend/__snapshots__/vislib_vis_legend.test.tsx.snap rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/__snapshots__/legend.test.tsx.snap diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/_index.scss b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/_index.scss new file mode 100644 index 0000000000000..53617a984dcf5 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/_index.scss @@ -0,0 +1 @@ +@import './_legend'; diff --git a/src/legacy/ui/public/vis/vis_types/_vislib_vis_legend.scss b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/_legend.scss similarity index 85% rename from src/legacy/ui/public/vis/vis_types/_vislib_vis_legend.scss rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/_legend.scss index 62050ce4e99fd..b1a59f88a348a 100644 --- a/src/legacy/ui/public/vis/vis_types/_vislib_vis_legend.scss +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/_legend.scss @@ -1,4 +1,4 @@ -@import '../../../../core_plugins/vis_type_vislib/public/vislib/variables'; +@import '../../variables'; // NOTE: Some of the styles attempt to align with the TSVB legend @@ -14,17 +14,16 @@ $visLegendLineHeight: $euiSize; display: flex; padding: $euiSizeXS; background-color: $euiColorEmptyShade; - transition: opacity $euiAnimSpeedFast $euiAnimSlightResistance, - background-color $euiAnimSpeedFast $euiAnimSlightResistance $euiAnimSpeedExtraSlow; + transition: opacity $euiAnimSpeedFast $euiAnimSlightResistance, background-color $euiAnimSpeedFast $euiAnimSlightResistance $euiAnimSpeedExtraSlow; &:focus { box-shadow: none; - background-color: $euiFocusBackgroundColor !important; + background-color: $euiFocusBackgroundColor !important; // sass-lint:disable-line no-important } } .visLegend__toggle--isOpen { - background-color: transparentize($euiColorDarkestShade, 0.9); + background-color: transparentize($euiColorDarkestShade, .9); opacity: 1; } diff --git a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/index.ts b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/index.ts similarity index 94% rename from src/legacy/ui/public/vis/vis_types/vislib_vis_legend/index.ts rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/index.ts index ebf132f0ab697..230cda4c35ff8 100644 --- a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/index.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/index.ts @@ -17,5 +17,5 @@ * under the License. */ -export { VisLegend } from './vislib_vis_legend'; +export { VisLegend } from './legend'; export { CUSTOM_LEGEND_VIS_TYPES } from './models'; diff --git a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.test.tsx b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/legend.test.tsx similarity index 96% rename from src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.test.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/legend.test.tsx index 839dc0024bbea..6f0a5a3784b07 100644 --- a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.test.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/legend.test.tsx @@ -23,7 +23,7 @@ import { mount, ReactWrapper } from 'enzyme'; import { I18nProvider } from '@kbn/i18n/react'; import { EuiButtonGroup } from '@elastic/eui'; -import { VisLegend, VisLegendProps } from '../vislib_vis_legend/vislib_vis_legend'; +import { VisLegend, VisLegendProps } from './legend'; import { legendColors } from './models'; jest.mock('@elastic/eui', () => ({ @@ -31,10 +31,10 @@ jest.mock('@elastic/eui', () => ({ htmlIdGenerator: jest.fn().mockReturnValue(() => 'legendId'), })); -jest.mock('../../../visualize/loader/pipeline_helpers/utilities', () => ({ +jest.mock('../../../legacy_imports', () => ({ getTableAggs: jest.fn(), })); -jest.mock('../../../../../core_plugins/visualizations/public', () => ({ +jest.mock('../../../../../visualizations/public', () => ({ createFiltersFromEvent: jest.fn().mockReturnValue(['yes']), })); diff --git a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.tsx b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/legend.tsx similarity index 93% rename from src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/legend.tsx index d98590f9885b9..0eec557dd334e 100644 --- a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/legend.tsx @@ -24,11 +24,11 @@ import { i18n } from '@kbn/i18n'; import { EuiPopoverProps, EuiIcon, keyCodes, htmlIdGenerator } from '@elastic/eui'; // @ts-ignore -import { createFiltersFromEvent } from '../../../../../core_plugins/visualizations/public'; +import { createFiltersFromEvent } from '../../../../../visualizations/public'; import { CUSTOM_LEGEND_VIS_TYPES, LegendItem } from './models'; -import { VisLegendItem } from './vislib_vis_legend_item'; +import { VisLegendItem } from './legend_item'; import { getPieNames } from './pie_utils'; -import { getTableAggs } from '../../../visualize/loader/pipeline_helpers/utilities'; +import { getTableAggs } from '../../../legacy_imports'; export interface VisLegendProps { vis: any; @@ -138,7 +138,7 @@ export class VisLegend extends PureComponent { this.setState({ labels: [ { - label: i18n.translate('common.ui.vis.visTypes.legend.loadingLabel', { + label: i18n.translate('visTypeVislib.vislib.legend.loadingLabel', { defaultMessage: 'loading…', }), }, @@ -244,13 +244,13 @@ export class VisLegend extends PureComponent { className={classNames('visLegend__toggle kbn-resetFocusState', { 'visLegend__toggle--isOpen': open, })} - aria-label={i18n.translate('common.ui.vis.visTypes.legend.toggleLegendButtonAriaLabel', { + aria-label={i18n.translate('visTypeVislib.vislib.legend.toggleLegendButtonAriaLabel', { defaultMessage: 'Toggle legend', })} aria-expanded={Boolean(open)} aria-controls={this.legendId} data-test-subj="vislibToggleLegend" - title={i18n.translate('common.ui.vis.visTypes.legend.toggleLegendButtonTitle', { + title={i18n.translate('visTypeVislib.vislib.legend.toggleLegendButtonTitle', { defaultMessage: 'Toggle legend', })} > diff --git a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend_item.tsx b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/legend_item.tsx similarity index 92% rename from src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend_item.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/legend_item.tsx index 7376fabfe738b..09c8a838532bf 100644 --- a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend_item.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/legend_item.tsx @@ -77,7 +77,7 @@ const VisLegendItemComponent = ({ const filterOptions: EuiButtonGroupOption[] = [ { id: 'filterIn', - label: i18n.translate('common.ui.vis.visTypes.legend.filterForValueButtonAriaLabel', { + label: i18n.translate('visTypeVislib.vislib.legend.filterForValueButtonAriaLabel', { defaultMessage: 'Filter for value {legendDataLabel}', values: { legendDataLabel: item.label }, }), @@ -86,7 +86,7 @@ const VisLegendItemComponent = ({ }, { id: 'filterOut', - label: i18n.translate('common.ui.vis.visTypes.legend.filterOutValueButtonAriaLabel', { + label: i18n.translate('visTypeVislib.vislib.legend.filterOutValueButtonAriaLabel', { defaultMessage: 'Filter out value {legendDataLabel}', values: { legendDataLabel: item.label }, }), @@ -105,7 +105,7 @@ const VisLegendItemComponent = ({ type="multi" isIconOnly isFullWidth - legend={i18n.translate('common.ui.vis.visTypes.legend.filterOptionsLegend', { + legend={i18n.translate('visTypeVislib.vislib.legend.filterOptionsLegend', { defaultMessage: '{legendDataLabel}, filter options', values: { legendDataLabel: item.label }, })} @@ -131,7 +131,7 @@ const VisLegendItemComponent = ({ onBlur={onUnhighlight} data-label={item.label} title={item.label} - aria-label={i18n.translate('common.ui.vis.visTypes.legend.toggleOptionsButtonAriaLabel', { + aria-label={i18n.translate('visTypeVislib.vislib.legend.toggleOptionsButtonAriaLabel', { defaultMessage: '{legendDataLabel}, toggle options', values: { legendDataLabel: item.label }, })} @@ -163,7 +163,7 @@ const VisLegendItemComponent = ({
diff --git a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/models.ts b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/models.ts similarity index 100% rename from src/legacy/ui/public/vis/vis_types/vislib_vis_legend/models.ts rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/models.ts diff --git a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/pie_utils.ts b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/pie_utils.ts similarity index 100% rename from src/legacy/ui/public/vis/vis_types/vislib_vis_legend/pie_utils.ts rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/pie_utils.ts diff --git a/src/legacy/ui/public/vis/components/tooltip/_collect_branch.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/_collect_branch.js similarity index 100% rename from src/legacy/ui/public/vis/components/tooltip/_collect_branch.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/_collect_branch.js diff --git a/src/legacy/ui/public/vis/components/tooltip/_collect_branch.test.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/_collect_branch.test.js similarity index 100% rename from src/legacy/ui/public/vis/components/tooltip/_collect_branch.test.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/_collect_branch.test.js diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/_hierarchical_tooltip_formatter.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/_hierarchical_tooltip_formatter.js new file mode 100644 index 0000000000000..22fff9cf6a317 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/_hierarchical_tooltip_formatter.js @@ -0,0 +1,93 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import _ from 'lodash'; +import numeral from 'numeral'; +import { renderToStaticMarkup } from 'react-dom/server'; + +import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; + +import { collectBranch } from './_collect_branch'; + +export function hierarchicalTooltipFormatter(metricFieldFormatter) { + return function({ datum }) { + // Collect the current leaf and parents into an array of values + const rows = collectBranch(datum); + + // Map those values to what the tooltipSource.rows format. + _.forEachRight(rows, function(row) { + row.spacer = _.escape(_.repeat(' ', row.depth)); + + let percent; + if (row.item.percentOfGroup !== null && row.item.percentOfGroup !== undefined) { + percent = row.item.percentOfGroup; + } + + row.metric = metricFieldFormatter ? metricFieldFormatter.convert(row.metric) : row.metric; + + if (percent !== null && percent !== undefined) { + row.metric += ' (' + numeral(percent).format('0.[00]%') + ')'; + } + + return row; + }); + + return renderToStaticMarkup( + + + + + + + + + + + + {rows.map((row, index) => ( + + + + + + ))} + +
{/* {metricCol.label} */}
+
+ + {row.field} +
+
+
{row.bucket}
+
{row.metric}
+
+ ); + }; +} diff --git a/src/legacy/ui/public/vis/components/tooltip/_index.scss b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/_index.scss similarity index 100% rename from src/legacy/ui/public/vis/components/tooltip/_index.scss rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/_index.scss diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/_pointseries_tooltip_formatter.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/_pointseries_tooltip_formatter.js new file mode 100644 index 0000000000000..13c9b8024aac3 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/_pointseries_tooltip_formatter.js @@ -0,0 +1,79 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { renderToStaticMarkup } from 'react-dom/server'; + +export function pointSeriesTooltipFormatter() { + return function tooltipFormatter({ datum, data }) { + if (!datum) return ''; + + const details = []; + + const currentSeries = data.series && data.series.find(serie => serie.rawId === datum.seriesId); + const addDetail = (label, value) => details.push({ label, value }); + + if (datum.extraMetrics) { + datum.extraMetrics.forEach(metric => { + addDetail(metric.label, metric.value); + }); + } + + if (datum.x !== null && datum.x !== undefined) { + addDetail(data.xAxisLabel, data.xAxisFormatter(datum.x)); + } + + if (datum.y !== null && datum.y !== undefined) { + const value = datum.yScale ? datum.yScale * datum.y : datum.y; + addDetail(currentSeries.label, currentSeries.yAxisFormatter(value)); + } + + if (datum.z !== null && datum.z !== undefined) { + addDetail(currentSeries.zLabel, currentSeries.zAxisFormatter(datum.z)); + } + if (datum.series && datum.parent) { + const dimension = datum.parent; + addDetail(dimension.title, datum.series); + } + if (datum.tableRaw) { + addDetail(datum.tableRaw.title, datum.tableRaw.value); + } + + return renderToStaticMarkup( + + + {details.map((detail, index) => ( + + + + + + ))} + +
+
{detail.label}
+
+
+ {detail.value} + {detail.percent && ({detail.percent})} +
+
+ ); + }; +} diff --git a/src/legacy/ui/public/vis/components/tooltip/_tooltip.scss b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/_tooltip.scss similarity index 97% rename from src/legacy/ui/public/vis/components/tooltip/_tooltip.scss rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/_tooltip.scss index 5a30ded7c2e5b..bafec7edf3b94 100644 --- a/src/legacy/ui/public/vis/components/tooltip/_tooltip.scss +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/_tooltip.scss @@ -28,7 +28,7 @@ } .visTooltip__header { - margin: 0 0 $euiSizeS 0; + margin: 0 0 $euiSizeS; padding: $euiSizeXS $euiSizeS; display: flex; align-items: center; @@ -41,6 +41,7 @@ margin-top: $euiSizeS; } } + .visTooltip__labelContainer, .visTooltip__valueContainer { overflow-wrap: break-word; diff --git a/src/legacy/core_plugins/kibana/server/routes/api/home/index.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/index.js similarity index 80% rename from src/legacy/core_plugins/kibana/server/routes/api/home/index.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/index.js index 6a4303819f3e9..e394981125a1f 100644 --- a/src/legacy/core_plugins/kibana/server/routes/api/home/index.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/index.js @@ -17,8 +17,6 @@ * under the License. */ -import { registerTutorials } from './register_tutorials'; - -export function homeApi(server) { - registerTutorials(server); -} +export { Tooltip } from './tooltip'; +export { hierarchicalTooltipFormatter } from './_hierarchical_tooltip_formatter'; +export { pointSeriesTooltipFormatter } from './_pointseries_tooltip_formatter'; diff --git a/src/legacy/ui/public/vis/components/tooltip/position_tooltip.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/position_tooltip.js similarity index 100% rename from src/legacy/ui/public/vis/components/tooltip/position_tooltip.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/position_tooltip.js diff --git a/src/legacy/ui/public/vis/components/tooltip/tooltip.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/tooltip.js similarity index 97% rename from src/legacy/ui/public/vis/components/tooltip/tooltip.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/tooltip.js index 764ab24da6740..f7d29164eec6f 100644 --- a/src/legacy/ui/public/vis/components/tooltip/tooltip.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/tooltip/tooltip.js @@ -19,9 +19,10 @@ import d3 from 'd3'; import _ from 'lodash'; -import { Binder } from '../../../binder'; -import { positionTooltip } from './position_tooltip'; import $ from 'jquery'; + +import { Binder } from '../../../legacy_imports'; +import { positionTooltip } from './position_tooltip'; import theme from '@elastic/eui/dist/eui_theme_light.json'; let allContents = []; @@ -217,7 +218,7 @@ Tooltip.prototype.render = function() { return content.id !== id; }); - if (html) allContents.push({ id: id, html: html, order: order }); + if (html) allContents.push({ id, html, order }); const allHtml = _(allContents) .sortBy('order') @@ -253,7 +254,3 @@ Tooltip.prototype.destroy = function() { this.hide(); this.binder.destroy(); }; - -export function TooltipProvider() { - return Tooltip; -} diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/axis/axis_labels.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/axis/axis_labels.js index b8afd826662d7..543e689e8b741 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/axis/axis_labels.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/axis/axis_labels.js @@ -92,32 +92,43 @@ export class AxisLabels { filterAxisLabels() { const self = this; const config = this.axisConfig; - let startPos = 0; const padding = 1.1; + let lastTickStartEdge = Number.POSITIVE_INFINITY; + let lastTickEndEdge = Number.NEGATIVE_INFINITY; return function(selection) { if (!config.get('labels.filter')) return; const el = $(config.get('rootEl')).find(config.get('elSelector')); - const maxSize = config.isHorizontal() ? el.width() : el.height(); + const upperBound = config.isHorizontal() ? el.width() : el.height(); + const lowerBound = 0; const scaleRange = self.axisScale.scale.range(); const scaleWidth = Math.abs(scaleRange[scaleRange.length - 1] - scaleRange[0]); - const scaleStartPad = 0.5 * (maxSize - scaleWidth); + const scaleStartPad = 0.5 * (upperBound - scaleWidth); selection.selectAll('.tick text').text(function(d) { - const par = d3.select(this.parentNode).node(); - const myPos = + const parentNode = d3.select(this.parentNode).node(); + const currentTickCenter = scaleStartPad + - (config.isHorizontal() ? self.axisScale.scale(d) : maxSize - self.axisScale.scale(d)); - const mySize = - (config.isHorizontal() ? par.getBBox().width : par.getBBox().height) * padding; - const halfSize = mySize / 2; - - if (startPos + halfSize < myPos && maxSize > myPos + halfSize) { - startPos = myPos + halfSize; - return this.textContent; - } else { + (config.isHorizontal() ? self.axisScale.scale(d) : upperBound - self.axisScale.scale(d)); + const currentTickSize = + (config.isHorizontal() ? parentNode.getBBox().width : parentNode.getBBox().height) * + padding; + const currentTickHalfSize = currentTickSize / 2; + const currentTickStartEdge = currentTickCenter - currentTickHalfSize; + const currentTickEndEdge = currentTickCenter + currentTickHalfSize; + + const outsideUpperBound = currentTickEndEdge > upperBound; + const outsideLowerBound = currentTickStartEdge < lowerBound; + const overlapsLastTick = + currentTickEndEdge >= lastTickStartEdge && currentTickStartEdge <= lastTickEndEdge; + + if (outsideUpperBound || outsideLowerBound || overlapsLastTick) { d3.select(this.parentNode).remove(); + } else { + lastTickStartEdge = currentTickCenter - currentTickHalfSize; + lastTickEndEdge = currentTickCenter + currentTickHalfSize; + return this.textContent; } }); }; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/chart_title.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/chart_title.js index 1c84f98614b05..3caaf99cbb5c1 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/chart_title.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/chart_title.js @@ -21,7 +21,7 @@ import d3 from 'd3'; import _ from 'lodash'; import { ErrorHandler } from './_error_handler'; -import { Tooltip } from '../../legacy_imports'; +import { Tooltip } from '../components/tooltip'; export class ChartTitle extends ErrorHandler { constructor(visConfig) { diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/types/point_series.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/types/point_series.js index eab3bc02f4eec..ebaf64874d729 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/types/point_series.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/types/point_series.js @@ -229,7 +229,7 @@ export const vislibPointSeriesTypes = { const tooManySeries = defaults.charts.length && defaults.charts[0].series.length > cfg.heatmapMaxBuckets; if (hasCharts && tooManySeries) { - defaults.error = i18n.translate('kbnVislibVisTypes.vislib.heatmap.maxBucketsText', { + defaults.error = i18n.translate('visTypeVislib.vislib.heatmap.maxBucketsText', { defaultMessage: 'There are too many series defined ({nr}). The configured maximum is {max}.', values: { diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/_chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/_chart.js index ac6e8130a846a..a36c7c4774dad 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/_chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/_chart.js @@ -22,7 +22,12 @@ import _ from 'lodash'; import { dataLabel } from '../lib/_data_label'; import { Dispatch } from '../lib/dispatch'; -import { Tooltip, getFormat } from '../../legacy_imports'; +import { getFormat } from '../../legacy_imports'; +import { + Tooltip, + hierarchicalTooltipFormatter, + pointSeriesTooltipFormatter, +} from '../components/tooltip'; /** * The Base Class for all visualizations. @@ -45,8 +50,8 @@ export class Chart { const fieldFormatter = getFormat(this.handler.data.get('tooltipFormatter')); const tooltipFormatterProvider = this.handler.visConfig.get('type') === 'pie' - ? deps.getHierarchicalTooltipFormatter() - : deps.getPointSeriesTooltipFormatter(); + ? hierarchicalTooltipFormatter + : pointSeriesTooltipFormatter; const tooltipFormatter = tooltipFormatterProvider(fieldFormatter); if (this.handler.visConfig && this.handler.visConfig.get('addTooltip', false)) { diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series.js index c838c51d34bf5..53f06c79d178c 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series.js @@ -21,7 +21,7 @@ import d3 from 'd3'; import _ from 'lodash'; import $ from 'jquery'; -import { Tooltip } from '../../legacy_imports'; +import { Tooltip } from '../components/tooltip'; import { Chart } from './_chart'; import { TimeMarker } from './time_marker'; import { seriesTypes } from './point_series/series_types'; @@ -153,18 +153,10 @@ export class PointSeries extends Chart { .attr('class', 'endzone') .append('rect') .attr('class', 'zone') - .attr('x', function(d) { - return isHorizontal ? d.x : 0; - }) - .attr('y', function(d) { - return isHorizontal ? 0 : d.x; - }) - .attr('height', function(d) { - return isHorizontal ? height : d.w; - }) - .attr('width', function(d) { - return isHorizontal ? d.w : width; - }); + .attr('x', d => (isHorizontal ? d.x : 0)) + .attr('y', d => (isHorizontal ? 0 : d.x)) + .attr('height', d => (isHorizontal ? height : d.w)) + .attr('width', d => (isHorizontal ? d.w : width)); function callPlay(event) { const boundData = event.target.__data__; diff --git a/src/legacy/core_plugins/visualizations/public/legacy_imports.ts b/src/legacy/core_plugins/visualizations/public/legacy_imports.ts index b750557c24b94..fd40c831ce0ef 100644 --- a/src/legacy/core_plugins/visualizations/public/legacy_imports.ts +++ b/src/legacy/core_plugins/visualizations/public/legacy_imports.ts @@ -18,7 +18,6 @@ */ export { PersistedState } from '../../../ui/public/persisted_state'; -export { SearchError } from '../../../ui/public/courier/search_strategy/search_error'; export { AggConfig } from '../../../ui/public/agg_types/agg_config'; export { AggConfigs } from '../../../ui/public/agg_types/agg_configs'; export { diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization_requesterror.tsx b/src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization_requesterror.tsx index 8b9fded919f13..1af9aa3c3e602 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization_requesterror.tsx +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization_requesterror.tsx @@ -19,7 +19,7 @@ import { EuiIcon, EuiSpacer, EuiText } from '@elastic/eui'; import React from 'react'; -import { SearchError } from '../../../legacy_imports'; +import { SearchError } from '../../../../../data/public/search/search_strategy'; interface VisualizationRequestErrorProps { onInit?: () => void; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/index.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/index.ts index 2e9d055858a48..29ff812b95473 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/index.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/index.ts @@ -52,3 +52,5 @@ export { buildPipeline, buildVislibDimensions, SchemaConfig } from './legacy/bui // @ts-ignore export { updateOldState } from './legacy/vis_update_state'; export { calculateObjectHash } from './legacy/calculate_object_hash'; +// @ts-ignore +export { createFiltersFromEvent } from './filters/vis_filters'; diff --git a/src/legacy/plugin_discovery/types.ts b/src/legacy/plugin_discovery/types.ts index fe886b9d17811..9425003eae874 100644 --- a/src/legacy/plugin_discovery/types.ts +++ b/src/legacy/plugin_discovery/types.ts @@ -24,6 +24,7 @@ import { Capabilities } from '../../core/server'; import { SavedObjectsSchemaDefinition } from '../../core/server/saved_objects/schema'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { SavedObjectsManagementDefinition } from '../../core/server/saved_objects/management'; +import { AppCategory } from '../../core/types'; /** * Usage @@ -53,6 +54,7 @@ export interface LegacyPluginOptions { uiExports: Partial<{ app: Partial<{ title: string; + category?: AppCategory; description: string; main: string; icon: string; diff --git a/src/legacy/server/kbn_server.js b/src/legacy/server/kbn_server.js index 8ab55d4c517b5..6991527a9503c 100644 --- a/src/legacy/server/kbn_server.js +++ b/src/legacy/server/kbn_server.js @@ -36,7 +36,6 @@ import * as Plugins from './plugins'; import { indexPatternsMixin } from './index_patterns'; import { savedObjectsMixin } from './saved_objects/saved_objects_mixin'; import { capabilitiesMixin } from './capabilities'; -import { urlShorteningMixin } from './url_shortening'; import { serverExtensionsMixin } from './server_extensions'; import { uiMixin } from '../ui'; import { sassMixin } from './sass'; @@ -123,9 +122,6 @@ export default class KbnServer { // setup capabilities routes capabilitiesMixin, - // setup routes for short urls - urlShorteningMixin, - // ensure that all bundles are built, or that the // watch bundle server is running optimizeMixin, diff --git a/src/legacy/server/status/routes/api/register_status.js b/src/legacy/server/status/routes/api/register_status.js index 259a00667810f..55bf81f2c5f9a 100644 --- a/src/legacy/server/status/routes/api/register_status.js +++ b/src/legacy/server/status/routes/api/register_status.js @@ -18,6 +18,7 @@ */ import { wrapAuthConfig } from '../../wrap_auth_config'; +import { IS_KIBANA_DISTRIBUTABLE } from '../../../../utils/artifact_type'; const matchSnapshot = /-SNAPSHOT$/; @@ -35,6 +36,8 @@ export function registerStatusApi(kbnServer, server, config) { return { name: config.get('server.name'), uuid: config.get('server.uuid'), + // flag to help tests know that kibana is running from source, not included in distributable response + ...(IS_KIBANA_DISTRIBUTABLE ? {} : { running_from_source: true }), version: { number: config.get('pkg.version').replace(matchSnapshot, ''), build_hash: config.get('pkg.buildSha'), diff --git a/src/legacy/server/url_shortening/routes/lib/short_url_assert_valid.test.js b/src/legacy/server/url_shortening/routes/lib/short_url_assert_valid.test.js deleted file mode 100644 index f83073e6aefe9..0000000000000 --- a/src/legacy/server/url_shortening/routes/lib/short_url_assert_valid.test.js +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { shortUrlAssertValid } from './short_url_assert_valid'; - -describe('shortUrlAssertValid()', () => { - const invalid = [ - ['protocol', 'http://localhost:5601/app/kibana'], - ['protocol', 'https://localhost:5601/app/kibana'], - ['protocol', 'mailto:foo@bar.net'], - ['protocol', 'javascript:alert("hi")'], // eslint-disable-line no-script-url - ['hostname', 'localhost/app/kibana'], - ['hostname and port', 'local.host:5601/app/kibana'], - ['hostname and auth', 'user:pass@localhost.net/app/kibana'], - ['path traversal', '/app/../../not-kibana'], - ['deep path', '/app/kibana/foo'], - ['deep path', '/app/kibana/foo/bar'], - ['base path', '/base/app/kibana'], - ]; - - invalid.forEach(([desc, url]) => { - it(`fails when url has ${desc}`, () => { - try { - shortUrlAssertValid(url); - throw new Error(`expected assertion to throw`); - } catch (err) { - if (!err || !err.isBoom) { - throw err; - } - } - }); - }); - - const valid = [ - '/app/kibana', - '/app/monitoring#angular/route', - '/app/text#document-id', - '/app/some?with=query', - '/app/some?with=query#and-a-hash', - ]; - - valid.forEach(url => { - it(`allows ${url}`, () => { - shortUrlAssertValid(url); - }); - }); -}); diff --git a/src/legacy/server/url_shortening/routes/lib/short_url_error.test.js b/src/legacy/server/url_shortening/routes/lib/short_url_error.test.js deleted file mode 100644 index 4eca6320ec834..0000000000000 --- a/src/legacy/server/url_shortening/routes/lib/short_url_error.test.js +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import { handleShortUrlError } from './short_url_error'; - -function createErrorWithStatus(status) { - const error = new Error(); - error.status = status; - return error; -} - -function createErrorWithStatusCode(statusCode) { - const error = new Error(); - error.statusCode = statusCode; - return error; -} - -describe('handleShortUrlError()', () => { - const caughtErrorsWithStatus = [ - createErrorWithStatus(401), - createErrorWithStatus(403), - createErrorWithStatus(404), - ]; - - const caughtErrorsWithStatusCode = [ - createErrorWithStatusCode(401), - createErrorWithStatusCode(403), - createErrorWithStatusCode(404), - ]; - - const uncaughtErrors = [new Error(), createErrorWithStatus(500), createErrorWithStatusCode(500)]; - - caughtErrorsWithStatus.forEach(err => { - it(`should handle errors with status of ${err.status}`, function() { - expect(_.get(handleShortUrlError(err), 'output.statusCode')).toBe(err.status); - }); - }); - - caughtErrorsWithStatusCode.forEach(err => { - it(`should handle errors with statusCode of ${err.statusCode}`, function() { - expect(_.get(handleShortUrlError(err), 'output.statusCode')).toBe(err.statusCode); - }); - }); - - uncaughtErrors.forEach(err => { - it(`should not handle unknown errors`, function() { - expect(_.get(handleShortUrlError(err), 'output.statusCode')).toBe(500); - }); - }); -}); diff --git a/src/legacy/server/url_shortening/routes/lib/short_url_lookup.test.js b/src/legacy/server/url_shortening/routes/lib/short_url_lookup.test.js deleted file mode 100644 index e8bf72a142d11..0000000000000 --- a/src/legacy/server/url_shortening/routes/lib/short_url_lookup.test.js +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import sinon from 'sinon'; -import { shortUrlLookupProvider } from './short_url_lookup'; -import { SavedObjectsClient } from '../../../../../core/server'; - -describe('shortUrlLookupProvider', () => { - const ID = 'bf00ad16941fc51420f91a93428b27a0'; - const TYPE = 'url'; - const URL = 'http://elastic.co'; - const server = { log: sinon.stub() }; - const sandbox = sinon.createSandbox(); - - let savedObjectsClient; - let req; - let shortUrl; - - beforeEach(() => { - savedObjectsClient = { - get: sandbox.stub(), - create: sandbox.stub().returns(Promise.resolve({ id: ID })), - update: sandbox.stub(), - errors: SavedObjectsClient.errors, - }; - - req = { getSavedObjectsClient: () => savedObjectsClient }; - shortUrl = shortUrlLookupProvider(server); - }); - - afterEach(() => { - sandbox.restore(); - }); - - describe('getUrl', () => { - beforeEach(() => { - const attributes = { accessCount: 2, url: URL }; - savedObjectsClient.get.returns({ id: ID, attributes }); - }); - - it('provides the ID to savedObjectsClient', async () => { - await shortUrl.getUrl(ID, req); - - sinon.assert.calledOnce(savedObjectsClient.get); - const [type, id] = savedObjectsClient.get.getCall(0).args; - - expect(type).toEqual(TYPE); - expect(id).toEqual(ID); - }); - - it('returns the url', async () => { - const response = await shortUrl.getUrl(ID, req); - expect(response).toEqual(URL); - }); - - it('increments accessCount', async () => { - await shortUrl.getUrl(ID, req); - - sinon.assert.calledOnce(savedObjectsClient.update); - const [type, id, attributes] = savedObjectsClient.update.getCall(0).args; - - expect(type).toEqual(TYPE); - expect(id).toEqual(ID); - expect(Object.keys(attributes).sort()).toEqual(['accessCount', 'accessDate']); - expect(attributes.accessCount).toEqual(3); - }); - }); -}); diff --git a/src/legacy/server/url_shortening/url_shortening_mixin.js b/src/legacy/server/url_shortening/url_shortening_mixin.js deleted file mode 100644 index 867898cac845a..0000000000000 --- a/src/legacy/server/url_shortening/url_shortening_mixin.js +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import { createRoutes } from './routes/create_routes'; - -export function urlShorteningMixin(kbnServer, server) { - createRoutes(server); -} diff --git a/src/legacy/ui/public/chrome/api/controls.ts b/src/legacy/ui/public/chrome/api/controls.ts index 6dde26be20df2..a95d53ec2eab6 100644 --- a/src/legacy/ui/public/chrome/api/controls.ts +++ b/src/legacy/ui/public/chrome/api/controls.ts @@ -42,4 +42,6 @@ export function initChromeControlsApi(chrome: { [key: string]: any }) { * might be incorrect in the moments just before the UI is updated. */ chrome.getVisible = () => visible$.getValue(); + + chrome.visible$ = visible$.asObservable(); } diff --git a/src/legacy/ui/public/chrome/directives/kbn_chrome.js b/src/legacy/ui/public/chrome/directives/kbn_chrome.js index 72d26a37a60a1..4c5d7981e962a 100644 --- a/src/legacy/ui/public/chrome/directives/kbn_chrome.js +++ b/src/legacy/ui/public/chrome/directives/kbn_chrome.js @@ -30,6 +30,7 @@ import { chromeHeaderNavControlsRegistry, NavControlSide, } from '../../registry/chrome_header_nav_controls'; +import { subscribeWithScope } from '../../utils/subscribe_with_scope'; export function kbnChromeProvider(chrome, internals) { uiModules.get('kibana').directive('kbnChrome', () => { @@ -83,6 +84,15 @@ export function kbnChromeProvider(chrome, internals) { ); } + const chromeVisibility = subscribeWithScope($scope, chrome.visible$, { + next: () => { + // just makes sure change detection is triggered when chrome visibility changes + }, + }); + $scope.$on('$destroy', () => { + chromeVisibility.unsubscribe(); + }); + return chrome; }, }; diff --git a/src/legacy/ui/public/directives/__tests__/css_truncate.js b/src/legacy/ui/public/directives/__tests__/css_truncate.js index 9d470c10358cc..bf102f5a29fdb 100644 --- a/src/legacy/ui/public/directives/__tests__/css_truncate.js +++ b/src/legacy/ui/public/directives/__tests__/css_truncate.js @@ -20,7 +20,7 @@ import angular from 'angular'; import expect from '@kbn/expect'; import ngMock from 'ng_mock'; -import 'plugins/kibana/discover/index'; +import 'plugins/kibana/discover/legacy'; let $parentScope; diff --git a/src/legacy/ui/public/management/breadcrumbs.ts b/src/legacy/ui/public/management/breadcrumbs.ts index fe53bcfde9e1f..936e99caff565 100644 --- a/src/legacy/ui/public/management/breadcrumbs.ts +++ b/src/legacy/ui/public/management/breadcrumbs.ts @@ -20,8 +20,8 @@ import { i18n } from '@kbn/i18n'; export const MANAGEMENT_BREADCRUMB = Object.freeze({ - text: i18n.translate('common.ui.management.breadcrumb', { - defaultMessage: 'Management', + text: i18n.translate('common.ui.stackManagement.breadcrumb', { + defaultMessage: 'Stack Management', }), href: '#/management', }); diff --git a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js index d3f74a540b960..dfd26bc4be039 100644 --- a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js +++ b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js @@ -236,6 +236,14 @@ export const npStart = { history: sinon.fake(), }, }, + search: { + __LEGACY: { + esClient: { + search: sinon.fake(), + msearch: sinon.fake(), + }, + }, + }, fieldFormats: getFieldFormatsRegistry(mockCore), }, share: { diff --git a/src/legacy/ui/public/state_management/state.js b/src/legacy/ui/public/state_management/state.js index 289d4b8006cba..c2274eae59f50 100644 --- a/src/legacy/ui/public/state_management/state.js +++ b/src/legacy/ui/public/state_management/state.js @@ -29,7 +29,6 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; import angular from 'angular'; import rison from 'rison-node'; -import { applyDiff } from './utils/diff_object'; import { EventsProvider } from '../events'; import { fatalError, toastNotifications } from '../notify'; import './config_provider'; @@ -38,6 +37,7 @@ import { hashedItemStore, isStateHash, createStateHash, + applyDiff, } from '../../../../plugins/kibana_utils/public'; export function StateProvider( diff --git a/src/legacy/ui/public/vis/_index.scss b/src/legacy/ui/public/vis/_index.scss index 4fb07557977d3..36d586abdb147 100644 --- a/src/legacy/ui/public/vis/_index.scss +++ b/src/legacy/ui/public/vis/_index.scss @@ -1,4 +1,2 @@ -@import './components/index'; @import './editors/index'; @import './map/index'; -@import './vis_types/index'; diff --git a/src/legacy/ui/public/vis/components/_index.scss b/src/legacy/ui/public/vis/components/_index.scss deleted file mode 100644 index 0d79aa9c458ac..0000000000000 --- a/src/legacy/ui/public/vis/components/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './tooltip/index'; diff --git a/src/legacy/ui/public/vis/components/tooltip/_hierarchical_tooltip.html b/src/legacy/ui/public/vis/components/tooltip/_hierarchical_tooltip.html deleted file mode 100644 index 43c4793384bf1..0000000000000 --- a/src/legacy/ui/public/vis/components/tooltip/_hierarchical_tooltip.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - -
{{metricCol.label}}
-
{{row.field}}
-
-
{{row.bucket}} -
{{row.metric}}
diff --git a/src/legacy/ui/public/vis/components/tooltip/_hierarchical_tooltip_formatter.js b/src/legacy/ui/public/vis/components/tooltip/_hierarchical_tooltip_formatter.js deleted file mode 100644 index aef7bc3913a49..0000000000000 --- a/src/legacy/ui/public/vis/components/tooltip/_hierarchical_tooltip_formatter.js +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import $ from 'jquery'; - -import chrome from 'ui/chrome'; - -import { collectBranch } from './_collect_branch'; -import numeral from 'numeral'; -import template from './_hierarchical_tooltip.html'; - -export function HierarchicalTooltipFormatterProvider($rootScope, $compile, $sce) { - const $tooltip = $(template); - const $tooltipScope = $rootScope.$new(); - - $compile($tooltip)($tooltipScope); - - return function(metricFieldFormatter) { - return function(event) { - const datum = event.datum; - - // Collect the current leaf and parents into an array of values - $tooltipScope.rows = collectBranch(datum); - - // Map those values to what the tooltipSource.rows format. - _.forEachRight($tooltipScope.rows, function(row) { - row.spacer = $sce.trustAsHtml(_.repeat(' ', row.depth)); - - let percent; - if (row.item.percentOfGroup != null) { - percent = row.item.percentOfGroup; - } - - row.metric = metricFieldFormatter ? metricFieldFormatter.convert(row.metric) : row.metric; - - if (percent != null) { - row.metric += ' (' + numeral(percent).format('0.[00]%') + ')'; - } - - return row; - }); - - $tooltipScope.$apply(); - return $tooltip[0].outerHTML; - }; - }; -} - -let _tooltipFormatter; -export const getHierarchicalTooltipFormatter = () => { - if (!_tooltipFormatter) { - throw new Error('tooltip formatter not initialized'); - } - return _tooltipFormatter; -}; - -export const initializeHierarchicalTooltipFormatter = async () => { - const $injector = await chrome.dangerouslyGetActiveInjector(); - const Private = $injector.get('Private'); - _tooltipFormatter = Private(HierarchicalTooltipFormatterProvider); -}; - -export const setHierarchicalTooltipFormatter = Private => { - _tooltipFormatter = Private(HierarchicalTooltipFormatterProvider); -}; diff --git a/src/legacy/ui/public/vis/components/tooltip/_pointseries_tooltip.html b/src/legacy/ui/public/vis/components/tooltip/_pointseries_tooltip.html deleted file mode 100644 index 9e82739a57f0f..0000000000000 --- a/src/legacy/ui/public/vis/components/tooltip/_pointseries_tooltip.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - -
-
{{detail.label}}
-
-
{{detail.value}} ({{detail.percent}})
-
diff --git a/src/legacy/ui/public/vis/components/tooltip/_pointseries_tooltip_formatter.js b/src/legacy/ui/public/vis/components/tooltip/_pointseries_tooltip_formatter.js deleted file mode 100644 index 88c9e3d67b4a9..0000000000000 --- a/src/legacy/ui/public/vis/components/tooltip/_pointseries_tooltip_formatter.js +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import $ from 'jquery'; - -import chrome from 'ui/chrome'; - -import template from './_pointseries_tooltip.html'; - -export function PointSeriesTooltipFormatterProvider($compile, $rootScope) { - const $tooltipScope = $rootScope.$new(); - const $tooltip = $(template); - $compile($tooltip)($tooltipScope); - - return function() { - return function tooltipFormatter(event) { - const data = event.data; - const datum = event.datum; - if (!datum) return ''; - - const details = ($tooltipScope.details = []); - - const currentSeries = - data.series && data.series.find(serie => serie.rawId === datum.seriesId); - const addDetail = (label, value) => details.push({ label, value }); - - if (datum.extraMetrics) { - datum.extraMetrics.forEach(metric => { - addDetail(metric.label, metric.value); - }); - } - - if (datum.x) { - addDetail(data.xAxisLabel, data.xAxisFormatter(datum.x)); - } - if (datum.y) { - const value = datum.yScale ? datum.yScale * datum.y : datum.y; - addDetail(currentSeries.label, currentSeries.yAxisFormatter(value)); - } - if (datum.z) { - addDetail(currentSeries.zLabel, currentSeries.zAxisFormatter(datum.z)); - } - if (datum.series && datum.parent) { - const dimension = datum.parent; - addDetail(dimension.title, datum.series); - } - if (datum.tableRaw) { - addDetail(datum.tableRaw.title, datum.tableRaw.value); - } - - $tooltipScope.$apply(); - return $tooltip[0].outerHTML; - }; - }; -} - -let _tooltipFormatter; -export const getPointSeriesTooltipFormatter = () => { - if (!_tooltipFormatter) { - throw new Error('tooltip formatter not initialized'); - } - return _tooltipFormatter; -}; - -export const initializePointSeriesTooltipFormatter = async () => { - const $injector = await chrome.dangerouslyGetActiveInjector(); - const Private = $injector.get('Private'); - _tooltipFormatter = Private(PointSeriesTooltipFormatterProvider); -}; - -export const setPointSeriesTooltipFormatter = Private => { - _tooltipFormatter = Private(PointSeriesTooltipFormatterProvider); -}; diff --git a/src/legacy/ui/public/vis/vis_types/_index.scss b/src/legacy/ui/public/vis/vis_types/_index.scss deleted file mode 100644 index 9d86383ec40b2..0000000000000 --- a/src/legacy/ui/public/vis/vis_types/_index.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import './vislib_vis_type'; -@import './vislib_vis_legend'; diff --git a/src/legacy/ui/tutorials_mixin.js b/src/legacy/ui/tutorials_mixin.js deleted file mode 100644 index efaf4ee7e78f0..0000000000000 --- a/src/legacy/ui/tutorials_mixin.js +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import Joi from 'joi'; -import { tutorialSchema } from '../core_plugins/kibana/common/tutorials/tutorial_schema'; - -export function tutorialsMixin(kbnServer, server) { - const tutorialProviders = []; - const scopedTutorialContextFactories = []; - - server.decorate('server', 'getTutorials', request => { - const initialContext = {}; - const scopedContext = scopedTutorialContextFactories.reduce( - (accumulatedContext, contextFactory) => { - return { ...accumulatedContext, ...contextFactory(request) }; - }, - initialContext - ); - - return tutorialProviders.map(tutorialProvider => { - return tutorialProvider(server, scopedContext); - }); - }); - - server.decorate('server', 'registerTutorial', specProvider => { - // registration during setup - const emptyContext = {}; - const { error } = Joi.validate(specProvider(server, emptyContext), tutorialSchema); - - if (error) { - throw new Error(`Unable to register tutorial spec because its invalid. ${error}`); - } - - tutorialProviders.push(specProvider); - }); - - server.decorate('server', 'addScopedTutorialContextFactory', scopedTutorialContextFactory => { - // returned by the setup method of the new plugin, they will do the same thing as now - if (typeof scopedTutorialContextFactory !== 'function') { - throw new Error( - `Unable to add scoped(request) context factory because you did not provide a function` - ); - } - - scopedTutorialContextFactories.push(scopedTutorialContextFactory); - }); -} diff --git a/src/legacy/ui/tutorials_mixin.test.js b/src/legacy/ui/tutorials_mixin.test.js deleted file mode 100644 index 58841987e0281..0000000000000 --- a/src/legacy/ui/tutorials_mixin.test.js +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { tutorialsMixin } from './tutorials_mixin'; - -const validTutorial = { - id: 'spec1', - category: 'other', - name: 'spec1', - shortDescription: 'short description', - longDescription: 'long description', - onPrem: { - instructionSets: [ - { - instructionVariants: [ - { - id: 'instructionVariant1', - instructions: [{}], - }, - ], - }, - ], - }, -}; - -describe('tutorial mixins', () => { - let getTutorials; - let registerTutorial; - let addScopedTutorialContextFactory; - const serverMock = { decorate: jest.fn() }; - beforeEach(async () => { - await tutorialsMixin({}, serverMock); - - [ - [, , getTutorials], - [, , registerTutorial], - [, , addScopedTutorialContextFactory], - ] = serverMock.decorate.mock.calls; - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - describe('scoped context', () => { - const mockRequest = {}; - const spacesContextFactory = request => { - if (request !== mockRequest) { - throw new Error('context factory not called with request object'); - } - return { - spaceId: 'my-space', - }; - }; - const specProvider = (server, context) => { - const tutorial = { ...validTutorial }; - tutorial.shortDescription = `I have been provided with scoped context, spaceId: ${context.spaceId}`; - return tutorial; - }; - beforeEach(async () => { - addScopedTutorialContextFactory(spacesContextFactory); - registerTutorial(specProvider); - }); - - test('passes scoped context to specProviders', () => { - const tutorials = getTutorials(mockRequest); - expect(tutorials.length).toBe(1); - expect(tutorials[0].shortDescription).toBe( - 'I have been provided with scoped context, spaceId: my-space' - ); - }); - }); -}); diff --git a/src/legacy/ui/ui_apps/ui_app.js b/src/legacy/ui/ui_apps/ui_app.js index 9c82ff2abedb5..1cfd54588b516 100644 --- a/src/legacy/ui/ui_apps/ui_app.js +++ b/src/legacy/ui/ui_apps/ui_app.js @@ -32,6 +32,7 @@ export class UiApp { hidden, linkToLastSubUrl, listed, + category, url = `/app/${id}`, } = spec; @@ -46,6 +47,7 @@ export class UiApp { this._icon = icon; this._euiIconType = euiIconType; this._linkToLastSubUrl = linkToLastSubUrl; + this._category = category; this._hidden = hidden; this._listed = listed; this._url = url; @@ -68,6 +70,7 @@ export class UiApp { euiIconType: this._euiIconType, url: this._url, linkToLastSubUrl: this._linkToLastSubUrl, + category: this._category, }); } } @@ -115,6 +118,7 @@ export class UiApp { main: this._main, navLink: this._navLink, linkToLastSubUrl: this._linkToLastSubUrl, + category: this._category, }; } } diff --git a/src/legacy/ui/ui_exports/ui_export_types/ui_apps.js b/src/legacy/ui/ui_exports/ui_export_types/ui_apps.js index d7ac49d9d49a3..639a5a7c58e18 100644 --- a/src/legacy/ui/ui_exports/ui_export_types/ui_apps.js +++ b/src/legacy/ui/ui_exports/ui_export_types/ui_apps.js @@ -34,6 +34,7 @@ function applySpecDefaults(spec, type, pluginSpec) { linkToLastSubUrl = true, listed = !hidden, url = `/app/${id}`, + category, } = spec; if (spec.injectVars) { @@ -61,6 +62,7 @@ function applySpecDefaults(spec, type, pluginSpec) { linkToLastSubUrl, listed, url, + category, }; } diff --git a/src/legacy/ui/ui_mixin.js b/src/legacy/ui/ui_mixin.js index 24d22efe4fa06..addfed05cdb48 100644 --- a/src/legacy/ui/ui_mixin.js +++ b/src/legacy/ui/ui_mixin.js @@ -18,7 +18,6 @@ */ import { fieldFormatsMixin } from './field_formats'; -import { tutorialsMixin } from './tutorials_mixin'; import { uiAppsMixin } from './ui_apps'; import { uiBundlesMixin } from './ui_bundles'; import { uiRenderMixin } from './ui_render'; @@ -29,6 +28,5 @@ export async function uiMixin(kbnServer) { await kbnServer.mixin(uiBundlesMixin); await kbnServer.mixin(uiSettingsMixin); await kbnServer.mixin(fieldFormatsMixin); - await kbnServer.mixin(tutorialsMixin); await kbnServer.mixin(uiRenderMixin); } diff --git a/src/legacy/ui/ui_nav_links/__tests__/ui_nav_link.js b/src/legacy/ui/ui_nav_links/__tests__/ui_nav_link.js index 37e023127ed41..543fe05b13e43 100644 --- a/src/legacy/ui/ui_nav_links/__tests__/ui_nav_link.js +++ b/src/legacy/ui/ui_nav_links/__tests__/ui_nav_link.js @@ -45,6 +45,7 @@ describe('UiNavLink', () => { euiIconType: spec.euiIconType, hidden: spec.hidden, disabled: spec.disabled, + category: undefined, // defaults linkToLastSubUrl: true, diff --git a/src/legacy/ui/ui_nav_links/ui_nav_link.js b/src/legacy/ui/ui_nav_links/ui_nav_link.js index 7537a60adbcf2..5888c21a53c95 100644 --- a/src/legacy/ui/ui_nav_links/ui_nav_link.js +++ b/src/legacy/ui/ui_nav_links/ui_nav_link.js @@ -31,6 +31,7 @@ export class UiNavLink { hidden = false, disabled = false, tooltip = '', + category, } = spec; this._id = id; @@ -44,6 +45,7 @@ export class UiNavLink { this._hidden = hidden; this._disabled = disabled; this._tooltip = tooltip; + this._category = category; } getOrder() { @@ -63,6 +65,7 @@ export class UiNavLink { hidden: this._hidden, disabled: this._disabled, tooltip: this._tooltip, + category: this._category, }; } } diff --git a/src/plugins/data/public/mocks.ts b/src/plugins/data/public/mocks.ts index 08a7a3ef11537..f44d40f533eed 100644 --- a/src/plugins/data/public/mocks.ts +++ b/src/plugins/data/public/mocks.ts @@ -69,13 +69,28 @@ const createStartContract = (): Start => { const startContract = { autocomplete: autocompleteMock, getSuggestions: jest.fn(), - search: { search: jest.fn() }, + search: { + search: jest.fn(), + + __LEGACY: { + esClient: { + search: jest.fn(), + msearch: jest.fn(), + }, + }, + }, fieldFormats: fieldFormatsMock as FieldFormatsStart, query: queryStartMock, ui: { IndexPatternSelect: jest.fn(), SearchBar: jest.fn(), }, + __LEGACY: { + esClient: { + search: jest.fn(), + msearch: jest.fn(), + }, + }, indexPatterns: {} as IndexPatternsContract, }; return startContract; diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts index 78abcbcfec467..ce8ca0317bd7d 100644 --- a/src/plugins/data/public/plugin.ts +++ b/src/plugins/data/public/plugin.ts @@ -17,7 +17,13 @@ * under the License. */ -import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'src/core/public'; +import { + PluginInitializerContext, + CoreSetup, + CoreStart, + Plugin, + PackageInfo, +} from 'src/core/public'; import { Storage, IStorageWrapper } from '../../kibana_utils/public'; import { DataPublicPluginSetup, @@ -31,7 +37,13 @@ import { FieldFormatsService } from './field_formats_provider'; import { QueryService } from './query'; import { createIndexPatternSelect } from './ui/index_pattern_select'; import { IndexPatterns } from './index_patterns'; -import { setNotifications, setFieldFormats, setOverlays, setIndexPatterns } from './services'; +import { + setNotifications, + setFieldFormats, + setOverlays, + setIndexPatterns, + setHttp, +} from './services'; import { createFilterAction, GLOBAL_APPLY_FILTER_ACTION } from './actions'; import { APPLY_FILTER_TRIGGER } from '../../embeddable/public'; import { createSearchBar } from './ui/search_bar/create_search_bar'; @@ -42,12 +54,14 @@ export class DataPublicPlugin implements Plugin { - if (a!.store === b!.store) { - return 0; - } else { - return a!.store === esFilters.FilterStateStore.GLOBAL_STATE && - b!.store !== esFilters.FilterStateStore.GLOBAL_STATE - ? -1 - : 1; - } - }); + newFilters.sort(sortFilters); - const filtersUpdated = !_.isEqual(this.filters, newFilters); + const filtersUpdated = !compareFilters(this.filters, newFilters, COMPARE_ALL_OPTIONS); const updatedOnlyDisabledFilters = onlyDisabledFiltersChanged(newFilters, this.filters); this.filters = newFilters; diff --git a/src/plugins/data/public/query/filter_manager/lib/compare_filters.test.ts b/src/plugins/data/public/query/filter_manager/lib/compare_filters.test.ts index 34fd662c4ba46..9cc5938750c4e 100644 --- a/src/plugins/data/public/query/filter_manager/lib/compare_filters.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/compare_filters.test.ts @@ -17,7 +17,7 @@ * under the License. */ -import { compareFilters } from './compare_filters'; +import { compareFilters, COMPARE_ALL_OPTIONS } from './compare_filters'; import { esFilters } from '../../../../common'; describe('filter manager utilities', () => { @@ -83,5 +83,134 @@ describe('filter manager utilities', () => { expect(compareFilters(f1, f2)).toBeTruthy(); }); + + test('should compare filters array to non array', () => { + const f1 = esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index', + '' + ); + + const f2 = esFilters.buildQueryFilter( + { _type: { match: { query: 'mochi', type: 'phrase' } } }, + 'index', + '' + ); + + expect(compareFilters([f1, f2], f1)).toBeFalsy(); + }); + + test('should compare filters array to partial array', () => { + const f1 = esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index', + '' + ); + + const f2 = esFilters.buildQueryFilter( + { _type: { match: { query: 'mochi', type: 'phrase' } } }, + 'index', + '' + ); + + expect(compareFilters([f1, f2], [f1])).toBeFalsy(); + }); + + test('should compare filters array to exact array', () => { + const f1 = esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index', + '' + ); + + const f2 = esFilters.buildQueryFilter( + { _type: { match: { query: 'mochi', type: 'phrase' } } }, + 'index', + '' + ); + + expect(compareFilters([f1, f2], [f1, f2])).toBeTruthy(); + }); + + test('should compare array of duplicates, ignoring meta attributes', () => { + const f1 = esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index1', + '' + ); + const f2 = esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index2', + '' + ); + + expect(compareFilters([f1], [f2])).toBeTruthy(); + }); + + test('should compare array of duplicates, ignoring $state attributes', () => { + const f1 = { + $state: { store: esFilters.FilterStateStore.APP_STATE }, + ...esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index', + '' + ), + }; + const f2 = { + $state: { store: esFilters.FilterStateStore.GLOBAL_STATE }, + ...esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index', + '' + ), + }; + + expect(compareFilters([f1], [f2])).toBeTruthy(); + }); + + test('should compare duplicates with COMPARE_ALL_OPTIONS should check store', () => { + const f1 = { + $state: { store: esFilters.FilterStateStore.APP_STATE }, + ...esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index', + '' + ), + }; + const f2 = { + $state: { store: esFilters.FilterStateStore.GLOBAL_STATE }, + ...esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index', + '' + ), + }; + + expect(compareFilters([f1], [f2], COMPARE_ALL_OPTIONS)).toBeFalsy(); + }); + + test('should compare duplicates with COMPARE_ALL_OPTIONS should not check key and value ', () => { + const f1 = { + $state: { store: esFilters.FilterStateStore.GLOBAL_STATE }, + ...esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index', + '' + ), + }; + const f2 = { + $state: { store: esFilters.FilterStateStore.GLOBAL_STATE }, + ...esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index', + '' + ), + }; + + f2.meta.key = 'wassup'; + f2.meta.value = 'dog'; + + expect(compareFilters([f1], [f2], COMPARE_ALL_OPTIONS)).toBeTruthy(); + }); }); }); diff --git a/src/plugins/data/public/query/filter_manager/lib/compare_filters.ts b/src/plugins/data/public/query/filter_manager/lib/compare_filters.ts index 9b171ab0aacb2..218b9d492b61f 100644 --- a/src/plugins/data/public/query/filter_manager/lib/compare_filters.ts +++ b/src/plugins/data/public/query/filter_manager/lib/compare_filters.ts @@ -17,32 +17,64 @@ * under the License. */ -import { defaults, isEqual, omit } from 'lodash'; +import { defaults, isEqual, omit, map } from 'lodash'; import { esFilters } from '../../../../common'; +export interface FilterCompareOptions { + disabled?: boolean; + negate?: boolean; + state?: boolean; +} + +/** + * Include disabled, negate and store when comparing filters + */ +export const COMPARE_ALL_OPTIONS: FilterCompareOptions = { + disabled: true, + negate: true, + state: true, +}; + +const mapFilter = ( + filter: esFilters.Filter, + comparators: FilterCompareOptions, + excludedAttributes: string[] +) => { + const cleaned: esFilters.FilterMeta = omit(filter, excludedAttributes); + + if (comparators.negate) cleaned.negate = filter.meta && Boolean(filter.meta.negate); + if (comparators.disabled) cleaned.disabled = filter.meta && Boolean(filter.meta.disabled); + + return cleaned; +}; + +const mapFilterArray = ( + filters: esFilters.Filter[], + comparators: FilterCompareOptions, + excludedAttributes: string[] +) => { + return map(filters, (filter: esFilters.Filter) => + mapFilter(filter, comparators, excludedAttributes) + ); +}; + /** - * Compare two filters to see if they match + * Compare two filters or filter arrays to see if they match. + * For filter arrays, the assumption is they are sorted. * - * @param {object} first The first filter to compare - * @param {object} second The second filter to compare - * @param {object} comparatorOptions Parameters to use for comparison + * @param {esFilters.Filter | esFilters.Filter[]} first The first filter or filter array to compare + * @param {esFilters.Filter | esFilters.Filter[]} second The second filter or filter array to compare + * @param {FilterCompareOptions} comparatorOptions Parameters to use for comparison * * @returns {bool} Filters are the same */ export const compareFilters = ( - first: esFilters.Filter, - second: esFilters.Filter, - comparatorOptions: any = {} + first: esFilters.Filter | esFilters.Filter[], + second: esFilters.Filter | esFilters.Filter[], + comparatorOptions: FilterCompareOptions = {} ) => { - let comparators: any = {}; - const mapFilter = (filter: esFilters.Filter) => { - const cleaned: esFilters.FilterMeta = omit(filter, excludedAttributes); - - if (comparators.negate) cleaned.negate = filter.meta && Boolean(filter.meta.negate); - if (comparators.disabled) cleaned.disabled = filter.meta && Boolean(filter.meta.disabled); + let comparators: FilterCompareOptions = {}; - return cleaned; - }; const excludedAttributes: string[] = ['$$hashKey', 'meta']; comparators = defaults(comparatorOptions || {}, { @@ -53,5 +85,17 @@ export const compareFilters = ( if (!comparators.state) excludedAttributes.push('$state'); - return isEqual(mapFilter(first), mapFilter(second)); + if (Array.isArray(first) && Array.isArray(second)) { + return isEqual( + mapFilterArray(first, comparators, excludedAttributes), + mapFilterArray(second, comparators, excludedAttributes) + ); + } else if (!Array.isArray(first) && !Array.isArray(second)) { + return isEqual( + mapFilter(first, comparators, excludedAttributes), + mapFilter(second, comparators, excludedAttributes) + ); + } else { + return false; + } }; diff --git a/src/plugins/data/public/query/filter_manager/lib/dedup_filters.ts b/src/plugins/data/public/query/filter_manager/lib/dedup_filters.ts index 6dae14f480b4f..897a645e87b5a 100644 --- a/src/plugins/data/public/query/filter_manager/lib/dedup_filters.ts +++ b/src/plugins/data/public/query/filter_manager/lib/dedup_filters.ts @@ -18,7 +18,7 @@ */ import { filter, find } from 'lodash'; -import { compareFilters } from './compare_filters'; +import { compareFilters, FilterCompareOptions } from './compare_filters'; import { esFilters } from '../../../../common'; /** @@ -33,7 +33,7 @@ import { esFilters } from '../../../../common'; export const dedupFilters = ( existingFilters: esFilters.Filter[], filters: esFilters.Filter[], - comparatorOptions: any = {} + comparatorOptions: FilterCompareOptions = {} ) => { if (!Array.isArray(filters)) { filters = [filters]; diff --git a/src/plugins/data/public/query/filter_manager/lib/only_disabled.ts b/src/plugins/data/public/query/filter_manager/lib/only_disabled.ts index c040d2f2960c7..3f35c94a3f858 100644 --- a/src/plugins/data/public/query/filter_manager/lib/only_disabled.ts +++ b/src/plugins/data/public/query/filter_manager/lib/only_disabled.ts @@ -17,8 +17,9 @@ * under the License. */ -import { filter, isEqual } from 'lodash'; +import { filter } from 'lodash'; import { esFilters } from '../../../../common'; +import { compareFilters, COMPARE_ALL_OPTIONS } from './compare_filters'; const isEnabled = (f: esFilters.Filter) => f && f.meta && !f.meta.disabled; @@ -35,5 +36,5 @@ export const onlyDisabledFiltersChanged = ( const newEnabledFilters = filter(newFilters || [], isEnabled); const oldEnabledFilters = filter(oldFilters || [], isEnabled); - return isEqual(oldEnabledFilters, newEnabledFilters); + return compareFilters(oldEnabledFilters, newEnabledFilters, COMPARE_ALL_OPTIONS); }; diff --git a/src/plugins/data/public/query/filter_manager/lib/sort_filters.test.ts b/src/plugins/data/public/query/filter_manager/lib/sort_filters.test.ts new file mode 100644 index 0000000000000..949c57e43ce74 --- /dev/null +++ b/src/plugins/data/public/query/filter_manager/lib/sort_filters.test.ts @@ -0,0 +1,91 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { sortFilters } from './sort_filters'; +import { esFilters } from '../../../../common'; + +describe('sortFilters', () => { + describe('sortFilters()', () => { + test('Not sort two application level filters', () => { + const f1 = { + $state: { store: esFilters.FilterStateStore.APP_STATE }, + ...esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index', + '' + ), + }; + const f2 = { + $state: { store: esFilters.FilterStateStore.APP_STATE }, + ...esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index', + '' + ), + }; + + const filters = [f1, f2].sort(sortFilters); + expect(filters[0]).toBe(f1); + }); + + test('Not sort two global level filters', () => { + const f1 = { + $state: { store: esFilters.FilterStateStore.GLOBAL_STATE }, + ...esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index', + '' + ), + }; + const f2 = { + $state: { store: esFilters.FilterStateStore.GLOBAL_STATE }, + ...esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index', + '' + ), + }; + + const filters = [f1, f2].sort(sortFilters); + expect(filters[0]).toBe(f1); + }); + + test('Move global level filter to the beginning of the array', () => { + const f1 = { + $state: { store: esFilters.FilterStateStore.APP_STATE }, + ...esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index', + '' + ), + }; + const f2 = { + $state: { store: esFilters.FilterStateStore.GLOBAL_STATE }, + ...esFilters.buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index', + '' + ), + }; + + const filters = [f1, f2].sort(sortFilters); + expect(filters[0]).toBe(f2); + }); + }); +}); diff --git a/src/legacy/core_plugins/kibana/common/tutorials/tutorial_category.js b/src/plugins/data/public/query/filter_manager/lib/sort_filters.ts similarity index 57% rename from src/legacy/core_plugins/kibana/common/tutorials/tutorial_category.js rename to src/plugins/data/public/query/filter_manager/lib/sort_filters.ts index 8ac4d2490c17f..657c80fe0ccb2 100644 --- a/src/legacy/core_plugins/kibana/common/tutorials/tutorial_category.js +++ b/src/plugins/data/public/query/filter_manager/lib/sort_filters.ts @@ -17,9 +17,26 @@ * under the License. */ -export const TUTORIAL_CATEGORY = { - LOGGING: 'logging', - SIEM: 'siem', - METRICS: 'metrics', - OTHER: 'other', +import { esFilters } from '../../../../common'; + +/** + * Sort filters according to their store - global filters go first + * + * @param {object} first The first filter to compare + * @param {object} second The second filter to compare + * + * @returns {number} Sorting order of filters + */ +export const sortFilters = ( + { $state: a }: esFilters.Filter, + { $state: b }: esFilters.Filter +): number => { + if (a!.store === b!.store) { + return 0; + } else { + return a!.store === esFilters.FilterStateStore.GLOBAL_STATE && + b!.store !== esFilters.FilterStateStore.GLOBAL_STATE + ? -1 + : 1; + } }; diff --git a/src/plugins/data/public/search/create_app_mount_context_search.test.ts b/src/plugins/data/public/search/create_app_mount_context_search.test.ts index fa7cdbcda3082..15b85ee270bed 100644 --- a/src/plugins/data/public/search/create_app_mount_context_search.test.ts +++ b/src/plugins/data/public/search/create_app_mount_context_search.test.ts @@ -18,32 +18,35 @@ */ import { createAppMountSearchContext } from './create_app_mount_context_search'; -import { from } from 'rxjs'; +import { from, BehaviorSubject } from 'rxjs'; describe('Create app mount search context', () => { it('Returns search fn when there are no strategies', () => { - const context = createAppMountSearchContext({}); + const context = createAppMountSearchContext({}, new BehaviorSubject(0)); expect(context.search).toBeDefined(); }); it(`Search throws an error when the strategy doesn't exist`, () => { - const context = createAppMountSearchContext({}); + const context = createAppMountSearchContext({}, new BehaviorSubject(0)); expect(() => context.search({}, {}, 'noexist').toPromise()).toThrowErrorMatchingInlineSnapshot( `"Strategy with name noexist does not exist"` ); }); it(`Search fn is called on appropriate strategy name`, done => { - const context = createAppMountSearchContext({ - mysearch: search => - Promise.resolve({ - search: () => from(Promise.resolve({ percentComplete: 98 })), - }), - anothersearch: search => - Promise.resolve({ - search: () => from(Promise.resolve({ percentComplete: 0 })), - }), - }); + const context = createAppMountSearchContext( + { + mysearch: search => + Promise.resolve({ + search: () => from(Promise.resolve({ percentComplete: 98 })), + }), + anothersearch: search => + Promise.resolve({ + search: () => from(Promise.resolve({ percentComplete: 0 })), + }), + }, + new BehaviorSubject(0) + ); context.search({}, {}, 'mysearch').subscribe(response => { expect(response).toEqual({ percentComplete: 98 }); @@ -52,16 +55,19 @@ describe('Create app mount search context', () => { }); it(`Search fn is called with the passed in request object`, done => { - const context = createAppMountSearchContext({ - mysearch: search => { - return Promise.resolve({ - search: request => { - expect(request).toEqual({ greeting: 'hi' }); - return from(Promise.resolve({})); - }, - }); + const context = createAppMountSearchContext( + { + mysearch: search => { + return Promise.resolve({ + search: request => { + expect(request).toEqual({ greeting: 'hi' }); + return from(Promise.resolve({})); + }, + }); + }, }, - }); + new BehaviorSubject(0) + ); context.search({ greeting: 'hi' } as any, {}, 'mysearch').subscribe( response => {}, () => {}, diff --git a/src/plugins/data/public/search/create_app_mount_context_search.ts b/src/plugins/data/public/search/create_app_mount_context_search.ts index 5659a9c863dc1..f480b8f3e042e 100644 --- a/src/plugins/data/public/search/create_app_mount_context_search.ts +++ b/src/plugins/data/public/search/create_app_mount_context_search.ts @@ -17,8 +17,8 @@ * under the License. */ -import { mergeMap } from 'rxjs/operators'; -import { from } from 'rxjs'; +import { mergeMap, tap } from 'rxjs/operators'; +import { from, BehaviorSubject } from 'rxjs'; import { ISearchAppMountContext } from './i_search_app_mount_context'; import { ISearchGeneric } from './i_search'; import { @@ -30,7 +30,8 @@ import { TStrategyTypes } from './strategy_types'; import { DEFAULT_SEARCH_STRATEGY } from '../../common/search'; export const createAppMountSearchContext = ( - searchStrategies: TSearchStrategiesMap + searchStrategies: TSearchStrategiesMap, + loadingCount$: BehaviorSubject ): ISearchAppMountContext => { const getSearchStrategy = ( strategyName?: K @@ -48,7 +49,13 @@ export const createAppMountSearchContext = ( const strategyPromise = getSearchStrategy(strategyName); return from(strategyPromise).pipe( mergeMap(strategy => { - return strategy.search(request, options); + loadingCount$.next(loadingCount$.getValue() + 1); + return strategy.search(request, options).pipe( + tap( + error => loadingCount$.next(loadingCount$.getValue() - 1), + complete => loadingCount$.next(loadingCount$.getValue() - 1) + ) + ); }) ); }; diff --git a/src/plugins/data/public/search/es_client/get_es_client.ts b/src/plugins/data/public/search/es_client/get_es_client.ts new file mode 100644 index 0000000000000..6c271643ba012 --- /dev/null +++ b/src/plugins/data/public/search/es_client/get_es_client.ts @@ -0,0 +1,93 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// @ts-ignore +import { default as es } from 'elasticsearch-browser/elasticsearch'; +import { CoreStart, PackageInfo } from 'kibana/public'; +import { BehaviorSubject } from 'rxjs'; + +export function getEsClient( + injectedMetadata: CoreStart['injectedMetadata'], + http: CoreStart['http'], + packageInfo: PackageInfo, + loadingCount$: BehaviorSubject +) { + const esRequestTimeout = injectedMetadata.getInjectedVar('esRequestTimeout') as number; + const esApiVersion = injectedMetadata.getInjectedVar('esApiVersion') as string; + + // Use legacy es client for msearch. + const client = es.Client({ + host: getEsUrl(http, packageInfo), + log: 'info', + requestTimeout: esRequestTimeout, + apiVersion: esApiVersion, + }); + + return { + search: wrapEsClientMethod(client, 'search', loadingCount$), + msearch: wrapEsClientMethod(client, 'msearch', loadingCount$), + create: wrapEsClientMethod(client, 'create', loadingCount$), + }; +} + +function wrapEsClientMethod(esClient: any, method: string, loadingCount$: BehaviorSubject) { + return (args: any) => { + // esClient returns a promise, with an additional abort handler + // To tap into the abort handling, we have to override that abort handler. + const customPromiseThingy = esClient[method](args); + const { abort } = customPromiseThingy; + let resolved = false; + + // Start LoadingIndicator + loadingCount$.next(loadingCount$.getValue() + 1); + + // Stop LoadingIndicator when user aborts + customPromiseThingy.abort = () => { + abort(); + if (!resolved) { + resolved = true; + loadingCount$.next(loadingCount$.getValue() - 1); + } + }; + + // Stop LoadingIndicator when promise finishes + customPromiseThingy.finally(() => { + resolved = true; + loadingCount$.next(loadingCount$.getValue() - 1); + }); + + return customPromiseThingy; + }; +} + +function getEsUrl(http: CoreStart['http'], packageInfo: PackageInfo) { + const a = document.createElement('a'); + a.href = http.basePath.prepend('/elasticsearch'); + const protocolPort = /https/.test(a.protocol) ? 443 : 80; + const port = a.port || protocolPort; + return { + host: a.hostname, + port, + protocol: a.protocol, + pathname: a.pathname, + headers: { + 'kbn-version': packageInfo.version, + }, + }; +} diff --git a/src/legacy/ui/public/vis/components/tooltip/index.js b/src/plugins/data/public/search/es_client/index.ts similarity index 93% rename from src/legacy/ui/public/vis/components/tooltip/index.js rename to src/plugins/data/public/search/es_client/index.ts index e596a8af86533..bf1a3f5d6e7c4 100644 --- a/src/legacy/ui/public/vis/components/tooltip/index.js +++ b/src/plugins/data/public/search/es_client/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export { Tooltip, TooltipProvider } from './tooltip'; +export { getEsClient } from './get_es_client'; diff --git a/src/plugins/data/public/search/search_service.ts b/src/plugins/data/public/search/search_service.ts index 6030884c9f6b1..6f3e228939d6d 100644 --- a/src/plugins/data/public/search/search_service.ts +++ b/src/plugins/data/public/search/search_service.ts @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ +import { BehaviorSubject } from 'rxjs'; import { Plugin, CoreSetup, @@ -23,6 +24,7 @@ import { CoreStart, IContextContainer, PluginOpaqueId, + PackageInfo, } from '../../../../core/public'; import { ISearchAppMountContext } from './i_search_app_mount_context'; @@ -37,6 +39,7 @@ import { import { TStrategyTypes } from './strategy_types'; import { esSearchService } from './es_search'; import { ISearchGeneric } from './i_search'; +import { getEsClient } from './es_client'; /** * Extends the AppMountContext so other plugins have access @@ -50,6 +53,9 @@ declare module 'kibana/public' { export interface ISearchStart { search: ISearchGeneric; + __LEGACY: { + esClient: any; + }; } /** @@ -74,11 +80,16 @@ export class SearchService implements Plugin { private contextContainer?: IContextContainer>; private search?: ISearchGeneric; + private readonly loadingCount$ = new BehaviorSubject(0); constructor(private initializerContext: PluginInitializerContext) {} public setup(core: CoreSetup): ISearchSetup { - const search = (this.search = createAppMountSearchContext(this.searchStrategies).search); + core.http.addLoadingCountSource(this.loadingCount$); + const search = (this.search = createAppMountSearchContext( + this.searchStrategies, + this.loadingCount$ + ).search); core.application.registerMountContext<'search'>('search', () => { return { search }; }); @@ -115,11 +126,16 @@ export class SearchService implements Plugin { return api; } - public start(core: CoreStart) { + public start(core: CoreStart, packageInfo: PackageInfo) { if (!this.search) { throw new Error('Search should always be defined'); } - return { search: this.search }; + return { + search: this.search, + __LEGACY: { + esClient: getEsClient(core.injectedMetadata, core.http, packageInfo, this.loadingCount$), + }, + }; } public stop() {} diff --git a/src/plugins/data/public/services.ts b/src/plugins/data/public/services.ts index 76b3283220f67..6a15893f573d8 100644 --- a/src/plugins/data/public/services.ts +++ b/src/plugins/data/public/services.ts @@ -18,7 +18,7 @@ */ import { NotificationsStart } from 'src/core/public'; -import { CoreStart } from 'kibana/public'; +import { CoreSetup, CoreStart } from 'kibana/public'; import { FieldFormatsStart } from '.'; import { createGetterSetter } from '../../kibana_utils/public'; import { IndexPatternsContract } from './index_patterns'; @@ -28,6 +28,12 @@ export const [getNotifications, setNotifications] = createGetterSetter( + 'UiSettings' +); + +export const [getHttp, setHttp] = createGetterSetter('Http'); + export const [getFieldFormats, setFieldFormats] = createGetterSetter( 'FieldFormats' ); @@ -41,3 +47,11 @@ export const [getIndexPatterns, setIndexPatterns] = createGetterSetter('Query'); + +export const [getInjectedMetadata, setInjectedMetadata] = createGetterSetter< + CoreSetup['injectedMetadata'] +>('InjectedMetadata'); + +export const [getSearchService, setSearchService] = createGetterSetter< + DataPublicPluginStart['search'] +>('Search'); diff --git a/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap b/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap index bc08c87304fca..f38adff892099 100644 --- a/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap +++ b/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap @@ -150,6 +150,12 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA "setIsVisible": [MockFunction], }, "data": Object { + "__LEGACY": Object { + "esClient": Object { + "msearch": [MockFunction], + "search": [MockFunction], + }, + }, "autocomplete": Object { "getQuerySuggestions": [MockFunction], "getValueSuggestions": [MockFunction], @@ -205,6 +211,12 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA }, }, "search": Object { + "__LEGACY": Object { + "esClient": Object { + "msearch": [MockFunction], + "search": [MockFunction], + }, + }, "search": [MockFunction], }, "ui": Object { @@ -304,6 +316,10 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA }, }, }, + "fatalErrors": Object { + "add": [MockFunction], + "get$": [MockFunction], + }, "http": Object { "addLoadingCountSource": [MockFunction], "anonymousPaths": Object { @@ -776,6 +792,12 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA "setIsVisible": [MockFunction], }, "data": Object { + "__LEGACY": Object { + "esClient": Object { + "msearch": [MockFunction], + "search": [MockFunction], + }, + }, "autocomplete": Object { "getQuerySuggestions": [MockFunction], "getValueSuggestions": [MockFunction], @@ -831,6 +853,12 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA }, }, "search": Object { + "__LEGACY": Object { + "esClient": Object { + "msearch": [MockFunction], + "search": [MockFunction], + }, + }, "search": [MockFunction], }, "ui": Object { @@ -930,6 +958,10 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA }, }, }, + "fatalErrors": Object { + "add": [MockFunction], + "get$": [MockFunction], + }, "http": Object { "addLoadingCountSource": [MockFunction], "anonymousPaths": Object { @@ -1384,6 +1416,12 @@ exports[`QueryStringInput Should pass the query language to the language switche "setIsVisible": [MockFunction], }, "data": Object { + "__LEGACY": Object { + "esClient": Object { + "msearch": [MockFunction], + "search": [MockFunction], + }, + }, "autocomplete": Object { "getQuerySuggestions": [MockFunction], "getValueSuggestions": [MockFunction], @@ -1439,6 +1477,12 @@ exports[`QueryStringInput Should pass the query language to the language switche }, }, "search": Object { + "__LEGACY": Object { + "esClient": Object { + "msearch": [MockFunction], + "search": [MockFunction], + }, + }, "search": [MockFunction], }, "ui": Object { @@ -1538,6 +1582,10 @@ exports[`QueryStringInput Should pass the query language to the language switche }, }, }, + "fatalErrors": Object { + "add": [MockFunction], + "get$": [MockFunction], + }, "http": Object { "addLoadingCountSource": [MockFunction], "anonymousPaths": Object { @@ -2007,6 +2055,12 @@ exports[`QueryStringInput Should pass the query language to the language switche "setIsVisible": [MockFunction], }, "data": Object { + "__LEGACY": Object { + "esClient": Object { + "msearch": [MockFunction], + "search": [MockFunction], + }, + }, "autocomplete": Object { "getQuerySuggestions": [MockFunction], "getValueSuggestions": [MockFunction], @@ -2062,6 +2116,12 @@ exports[`QueryStringInput Should pass the query language to the language switche }, }, "search": Object { + "__LEGACY": Object { + "esClient": Object { + "msearch": [MockFunction], + "search": [MockFunction], + }, + }, "search": [MockFunction], }, "ui": Object { @@ -2161,6 +2221,10 @@ exports[`QueryStringInput Should pass the query language to the language switche }, }, }, + "fatalErrors": Object { + "add": [MockFunction], + "get$": [MockFunction], + }, "http": Object { "addLoadingCountSource": [MockFunction], "anonymousPaths": Object { @@ -2615,6 +2679,12 @@ exports[`QueryStringInput Should render the given query 1`] = ` "setIsVisible": [MockFunction], }, "data": Object { + "__LEGACY": Object { + "esClient": Object { + "msearch": [MockFunction], + "search": [MockFunction], + }, + }, "autocomplete": Object { "getQuerySuggestions": [MockFunction], "getValueSuggestions": [MockFunction], @@ -2670,6 +2740,12 @@ exports[`QueryStringInput Should render the given query 1`] = ` }, }, "search": Object { + "__LEGACY": Object { + "esClient": Object { + "msearch": [MockFunction], + "search": [MockFunction], + }, + }, "search": [MockFunction], }, "ui": Object { @@ -2769,6 +2845,10 @@ exports[`QueryStringInput Should render the given query 1`] = ` }, }, }, + "fatalErrors": Object { + "add": [MockFunction], + "get$": [MockFunction], + }, "http": Object { "addLoadingCountSource": [MockFunction], "anonymousPaths": Object { @@ -3238,6 +3318,12 @@ exports[`QueryStringInput Should render the given query 1`] = ` "setIsVisible": [MockFunction], }, "data": Object { + "__LEGACY": Object { + "esClient": Object { + "msearch": [MockFunction], + "search": [MockFunction], + }, + }, "autocomplete": Object { "getQuerySuggestions": [MockFunction], "getValueSuggestions": [MockFunction], @@ -3293,6 +3379,12 @@ exports[`QueryStringInput Should render the given query 1`] = ` }, }, "search": Object { + "__LEGACY": Object { + "esClient": Object { + "msearch": [MockFunction], + "search": [MockFunction], + }, + }, "search": [MockFunction], }, "ui": Object { @@ -3392,6 +3484,10 @@ exports[`QueryStringInput Should render the given query 1`] = ` }, }, }, + "fatalErrors": Object { + "add": [MockFunction], + "get$": [MockFunction], + }, "http": Object { "addLoadingCountSource": [MockFunction], "anonymousPaths": Object { diff --git a/src/plugins/data/server/kql_telemetry/kql_telemetry_service.ts b/src/plugins/data/server/kql_telemetry/kql_telemetry_service.ts index 8f68e63703d40..e45ad796bd9d4 100644 --- a/src/plugins/data/server/kql_telemetry/kql_telemetry_service.ts +++ b/src/plugins/data/server/kql_telemetry/kql_telemetry_service.ts @@ -27,12 +27,12 @@ export class KqlTelemetryService implements Plugin { constructor(private initializerContext: PluginInitializerContext) {} public setup( - { http, savedObjects }: CoreSetup, + { http, getStartServices }: CoreSetup, { usageCollection }: { usageCollection?: UsageCollectionSetup } ) { registerKqlTelemetryRoute( http.createRouter(), - savedObjects, + getStartServices, this.initializerContext.logger.get('data', 'kql-telemetry') ); diff --git a/src/plugins/data/server/kql_telemetry/route.ts b/src/plugins/data/server/kql_telemetry/route.ts index 3185da22b12b3..d5725c859c9a9 100644 --- a/src/plugins/data/server/kql_telemetry/route.ts +++ b/src/plugins/data/server/kql_telemetry/route.ts @@ -22,7 +22,7 @@ import { schema } from '@kbn/config-schema'; export function registerKqlTelemetryRoute( router: IRouter, - savedObjects: CoreSetup['savedObjects'], + getStartServices: CoreSetup['getStartServices'], logger: Logger ) { router.post( @@ -35,6 +35,7 @@ export function registerKqlTelemetryRoute( }, }, async (context, request, response) => { + const [{ savedObjects }] = await getStartServices(); const internalRepository = savedObjects.createScopedRepository(request); const { diff --git a/src/plugins/data/server/search/routes.test.ts b/src/plugins/data/server/search/routes.test.ts index a2394d88f3931..6ea0799f790fc 100644 --- a/src/plugins/data/server/search/routes.test.ts +++ b/src/plugins/data/server/search/routes.test.ts @@ -65,8 +65,13 @@ describe('Search service', () => { expect(mockResponse.ok.mock.calls[0][0]).toEqual({ body: 'yay' }); }); - it('handler throws internal error if the search throws an error', async () => { - const mockSearch = jest.fn().mockRejectedValue('oh no'); + it('handler throws an error if the search throws an error', async () => { + const mockSearch = jest.fn().mockRejectedValue({ + message: 'oh no', + body: { + error: 'oops', + }, + }); const mockContext = { core: { elasticsearch: { @@ -93,7 +98,9 @@ describe('Search service', () => { expect(mockSearch).toBeCalled(); expect(mockSearch.mock.calls[0][0]).toStrictEqual(mockBody); expect(mockSearch.mock.calls[0][2]).toBe(mockParams.strategy); - expect(mockResponse.internalError).toBeCalled(); - expect(mockResponse.internalError.mock.calls[0][0]).toEqual({ body: 'oh no' }); + expect(mockResponse.customError).toBeCalled(); + const error: any = mockResponse.customError.mock.calls[0][0]; + expect(error.body.message).toBe('oh no'); + expect(error.body.attributes.error).toBe('oops'); }); }); diff --git a/src/plugins/data/server/search/routes.ts b/src/plugins/data/server/search/routes.ts index eaa72548e08ee..6f726771c41b2 100644 --- a/src/plugins/data/server/search/routes.ts +++ b/src/plugins/data/server/search/routes.ts @@ -39,7 +39,15 @@ export function registerSearchRoute(router: IRouter): void { const response = await context.search!.search(searchRequest, {}, strategy); return res.ok({ body: response }); } catch (err) { - return res.internalError({ body: err }); + return res.customError({ + statusCode: err.statusCode, + body: { + message: err.message, + attributes: { + error: err.body.error, + }, + }, + }); } } ); diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_array.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_array.ts index d03ff93a47218..3f71f83c55694 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_array.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_array.ts @@ -17,13 +17,14 @@ * under the License. */ -import { useState, useRef } from 'react'; +import { useState, useEffect, useRef } from 'react'; import { useFormContext } from '../form_context'; interface Props { path: string; initialNumberOfItems?: number; + readDefaultValueOnForm?: boolean; children: (args: { items: ArrayItem[]; addItem: () => void; @@ -52,9 +53,15 @@ export interface ArrayItem { * * Look at the README.md for some examples. */ -export const UseArray = ({ path, initialNumberOfItems, children }: Props) => { +export const UseArray = ({ + path, + initialNumberOfItems, + readDefaultValueOnForm = true, + children, +}: Props) => { + const didMountRef = useRef(false); const form = useFormContext(); - const defaultValues = form.getFieldDefaultValue(path) as any[]; + const defaultValues = readDefaultValueOnForm && (form.getFieldDefaultValue(path) as any[]); const uniqueId = useRef(0); const getInitialItemsFromValues = (values: any[]): ArrayItem[] => @@ -99,5 +106,13 @@ export const UseArray = ({ path, initialNumberOfItems, children }: Props) => { }); }; + useEffect(() => { + if (didMountRef.current) { + setItems(updatePaths(items)); + } else { + didMountRef.current = true; + } + }, [path]); + return children({ items, addItem, removeItem }); }; diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.tsx b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.tsx index 021d52fbe9edb..bdcf47c865701 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.tsx +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.tsx @@ -29,17 +29,27 @@ export interface Props { defaultValue?: unknown; component?: FunctionComponent | 'input'; componentProps?: Record; + readDefaultValueOnForm?: boolean; onChange?: (value: unknown) => void; children?: (field: FieldHook) => JSX.Element; } export const UseField = React.memo( - ({ path, config, defaultValue, component, componentProps, onChange, children }: Props) => { + ({ + path, + config, + defaultValue, + component, + componentProps, + readDefaultValueOnForm = true, + onChange, + children, + }: Props) => { const form = useFormContext(); component = component === undefined ? 'input' : component; componentProps = componentProps === undefined ? {} : componentProps; - if (typeof defaultValue === 'undefined') { + if (typeof defaultValue === 'undefined' && readDefaultValueOnForm) { defaultValue = form.getFieldDefaultValue(path); } diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts index d8b2f35e117a6..2ee68eb4d7a1a 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts @@ -198,6 +198,12 @@ export function useForm( }); formData$.current.next(currentFormData as T); + + /** + * After removing a field, the form validity might have changed + * (an invalid field might have been removed and now the form is valid) + */ + updateFormValidity(); }; const setFieldValue: FormHook['setFieldValue'] = (fieldName, value) => { diff --git a/src/plugins/home/server/index.ts b/src/plugins/home/server/index.ts index ed336f4a41d6e..0961c729698b9 100644 --- a/src/plugins/home/server/index.ts +++ b/src/plugins/home/server/index.ts @@ -24,3 +24,6 @@ import { PluginInitializerContext } from 'src/core/server'; import { HomeServerPlugin } from './plugin'; export const plugin = (initContext: PluginInitializerContext) => new HomeServerPlugin(initContext); + +export { INSTRUCTION_VARIANT } from './tutorials/instructions/instruction_variant'; +export { ArtifactsSchema, TutorialsCategory } from './services/tutorials'; diff --git a/src/plugins/home/server/services/sample_data/sample_data_registry.ts b/src/plugins/home/server/services/sample_data/sample_data_registry.ts index 7a4909668fff2..aac680211e52e 100644 --- a/src/plugins/home/server/services/sample_data/sample_data_registry.ts +++ b/src/plugins/home/server/services/sample_data/sample_data_registry.ts @@ -51,7 +51,7 @@ export class SampleDataRegistry { makeSampleDataUsageCollector(usageCollections, this.initContext); } const usageTracker = usage( - core.savedObjects, + core.getStartServices().then(([coreStart]) => coreStart.savedObjects), this.initContext.logger.get('sample_data', 'telemetry') ); const router = core.http.createRouter(); diff --git a/src/plugins/home/server/services/sample_data/usage/usage.ts b/src/plugins/home/server/services/sample_data/usage/usage.ts index a06dde387bb36..59599a1bee68f 100644 --- a/src/plugins/home/server/services/sample_data/usage/usage.ts +++ b/src/plugins/home/server/services/sample_data/usage/usage.ts @@ -17,7 +17,7 @@ * under the License. */ -import { Logger, SavedObjectsServiceSetup } from 'kibana/server'; +import { Logger, SavedObjectsServiceStart } from 'kibana/server'; const SAVED_OBJECT_ID = 'sample-data-telemetry'; @@ -27,7 +27,7 @@ export interface SampleDataUsageTracker { } export function usage( - savedObjects: SavedObjectsServiceSetup, + savedObjects: Promise, logger: Logger ): SampleDataUsageTracker { const handleIncrementError = (err: Error) => { @@ -37,11 +37,12 @@ export function usage( logger.warn(`saved objects repository incrementCounter encountered an error: ${err}`); }; - const internalRepository = savedObjects.createInternalRepository(); + const internalRepositoryPromise = savedObjects.then(so => so.createInternalRepository()); return { addInstall: async (dataSet: string) => { try { + const internalRepository = await internalRepositoryPromise; await internalRepository.incrementCounter(SAVED_OBJECT_ID, dataSet, `installCount`); } catch (err) { handleIncrementError(err); @@ -49,6 +50,7 @@ export function usage( }, addUninstall: async (dataSet: string) => { try { + const internalRepository = await internalRepositoryPromise; await internalRepository.incrementCounter(SAVED_OBJECT_ID, dataSet, `unInstallCount`); } catch (err) { handleIncrementError(err); diff --git a/src/plugins/home/server/services/tutorials/lib/tutorials_registry_types.ts b/src/plugins/home/server/services/tutorials/lib/tutorials_registry_types.ts index 951ce935760a1..80849513a3fad 100644 --- a/src/plugins/home/server/services/tutorials/lib/tutorials_registry_types.ts +++ b/src/plugins/home/server/services/tutorials/lib/tutorials_registry_types.ts @@ -26,17 +26,30 @@ export enum TutorialsCategory { METRICS = 'metrics', OTHER = 'other', } +export type Platform = 'WINDOWS' | 'OSX' | 'DEB' | 'RPM'; + export interface ParamTypes { NUMBER: string; STRING: string; } +export interface Instruction { + title?: string; + textPre?: string; + commands?: string[]; + textPost?: string; +} +export interface InstructionVariant { + id: string; + instructions: Instruction[]; +} export interface InstructionSetSchema { - readonly title: string; - readonly callOut: { + readonly title?: string; + readonly callOut?: { title: string; - message: string; - iconType: IconType; + message?: string; + iconType?: IconType; }; + instructionVariants: InstructionVariant[]; } export interface ParamsSchema { defaultValue: any; @@ -46,22 +59,19 @@ export interface ParamsSchema { } export interface InstructionsSchema { readonly instructionSets: InstructionSetSchema[]; - readonly params: ParamsSchema[]; + readonly params?: ParamsSchema[]; } export interface DashboardSchema { id: string; - linkLabel?: { - is: boolean; - then: any; - }; + linkLabel?: string; isOverview: boolean; } export interface ArtifactsSchema { - readonly exportedFields: { + exportedFields?: { documentationUrl: string; }; - readonly dashboards: DashboardSchema[]; - readonly application: { + dashboards: DashboardSchema[]; + application?: { path: string; label: string; }; @@ -70,29 +80,32 @@ export interface TutorialSchema { id: string; category: TutorialsCategory; name: string; - isBeta: boolean; + isBeta?: boolean; shortDescription: string; - euiIconType: IconType; // EUI icon type string, one of https://elastic.github.io/eui/#/icon; + euiIconType?: IconType; // EUI icon type string, one of https://elastic.github.io/eui/#/icon; longDescription: string; - completionTimeMinutes: number; - previewImagePath: string; + completionTimeMinutes?: number; + previewImagePath?: string; // kibana and elastic cluster running on prem onPrem: InstructionsSchema; // kibana and elastic cluster running in elastic's cloud - elasticCloud: InstructionsSchema; + elasticCloud?: InstructionsSchema; // kibana running on prem and elastic cluster running in elastic's cloud - onPremElasticCloud: InstructionsSchema; + onPremElasticCloud?: InstructionsSchema; // Elastic stack artifacts produced by product when it is setup and run. - artifacts: ArtifactsSchema; + artifacts?: ArtifactsSchema; // saved objects used by data module. - savedObjects: any[]; - savedObjectsInstallMsg: string; + savedObjects?: any[]; + savedObjectsInstallMsg?: string; +} +export interface TutorialContext { + [key: string]: unknown; } -export type TutorialProvider = (context: { [key: string]: unknown }) => TutorialSchema; +export type TutorialProvider = (context: TutorialContext) => TutorialSchema; export type TutorialContextFactory = (req: KibanaRequest) => { [key: string]: unknown }; export type ScopedTutorialContextFactory = (...args: any[]) => any; diff --git a/src/plugins/home/server/services/tutorials/tutorials_registry.ts b/src/plugins/home/server/services/tutorials/tutorials_registry.ts index be0302cbd8188..e820924d7608d 100644 --- a/src/plugins/home/server/services/tutorials/tutorials_registry.ts +++ b/src/plugins/home/server/services/tutorials/tutorials_registry.ts @@ -25,6 +25,7 @@ import { ScopedTutorialContextFactory, } from './lib/tutorials_registry_types'; import { tutorialSchema } from './lib/tutorial_schema'; +import { builtInTutorials } from '../../tutorials/register'; export class TutorialsRegistry { private readonly tutorialProviders: TutorialProvider[] = []; // pre-register all the tutorials we know we want in here @@ -77,6 +78,8 @@ export class TutorialsRegistry { } public start() { + // pre-populate with built in tutorials + this.tutorialProviders.push(...builtInTutorials); return {}; } } diff --git a/src/legacy/core_plugins/kibana/server/tutorials/activemq_logs/index.js b/src/plugins/home/server/tutorials/activemq_logs/index.ts similarity index 70% rename from src/legacy/core_plugins/kibana/server/tutorials/activemq_logs/index.js rename to src/plugins/home/server/tutorials/activemq_logs/index.ts index fa5fe3b0ecfe3..6511a21b15c44 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/activemq_logs/index.js +++ b/src/plugins/home/server/tutorials/activemq_logs/index.ts @@ -18,26 +18,30 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/filebeat_instructions'; +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function activemqLogsSpecProvider(server, context) { +export function activemqLogsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'activemq'; - const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS']; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; return { id: 'activemqLogs', - name: i18n.translate('kbn.server.tutorials.activemqLogs.nameTitle', { + name: i18n.translate('home.tutorials.activemqLogs.nameTitle', { defaultMessage: 'ActiveMQ logs', }), - category: TUTORIAL_CATEGORY.LOGGING, - shortDescription: i18n.translate('kbn.server.tutorials.activemqLogs.shortDescription', { + category: TutorialsCategory.LOGGING, + shortDescription: i18n.translate('home.tutorials.activemqLogs.shortDescription', { defaultMessage: 'Collect ActiveMQ logs with Filebeat.', }), - longDescription: i18n.translate('kbn.server.tutorials.activemqLogs.longDescription', { + longDescription: i18n.translate('home.tutorials.activemqLogs.longDescription', { defaultMessage: 'Collect ActiveMQ logs with Filebeat. \ [Learn more]({learnMoreLink}).', values: { @@ -49,12 +53,9 @@ export function activemqLogsSpecProvider(server, context) { dashboards: [ { id: '26434790-1464-11ea-8fd8-030a13064883', - linkLabel: i18n.translate( - 'kbn.server.tutorials.activemqLogs.artifacts.dashboards.linkLabel', - { - defaultMessage: 'ActiveMQ Application Events', - } - ), + linkLabel: i18n.translate('home.tutorials.activemqLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'ActiveMQ Application Events', + }), isOverview: true, }, ], diff --git a/src/legacy/core_plugins/kibana/server/tutorials/activemq_metrics/index.js b/src/plugins/home/server/tutorials/activemq_metrics/index.ts similarity index 73% rename from src/legacy/core_plugins/kibana/server/tutorials/activemq_metrics/index.js rename to src/plugins/home/server/tutorials/activemq_metrics/index.ts index 7ed2d370debd1..3898e2b5338b1 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/activemq_metrics/index.js +++ b/src/plugins/home/server/tutorials/activemq_metrics/index.ts @@ -18,25 +18,29 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/metricbeat_instructions'; +} from '../instructions/metricbeat_instructions'; +import { + TutorialContext, + TutorialsCategory, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function activemqMetricsSpecProvider(context) { +export function activemqMetricsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'activemq'; return { id: 'activemqMetrics', - name: i18n.translate('kbn.server.tutorials.activemqMetrics.nameTitle', { + name: i18n.translate('home.tutorials.activemqMetrics.nameTitle', { defaultMessage: 'ActiveMQ metrics', }), - category: TUTORIAL_CATEGORY.METRICS, - shortDescription: i18n.translate('kbn.server.tutorials.activemqMetrics.shortDescription', { + category: TutorialsCategory.METRICS, + shortDescription: i18n.translate('home.tutorials.activemqMetrics.shortDescription', { defaultMessage: 'Fetch monitoring metrics from ActiveMQ instances.', }), - longDescription: i18n.translate('kbn.server.tutorials.activemqMetrics.longDescription', { + longDescription: i18n.translate('home.tutorials.activemqMetrics.longDescription', { defaultMessage: 'The `activemq` Metricbeat module fetches monitoring metrics from ActiveMQ instances \ [Learn more]({learnMoreLink}).', @@ -48,7 +52,7 @@ export function activemqMetricsSpecProvider(context) { isBeta: true, artifacts: { application: { - label: i18n.translate('kbn.server.tutorials.activemqMetrics.artifacts.application.label', { + label: i18n.translate('home.tutorials.activemqMetrics.artifacts.application.label', { defaultMessage: 'Discover', }), path: '/app/kibana#/discover', @@ -59,7 +63,7 @@ export function activemqMetricsSpecProvider(context) { }, }, completionTimeMinutes: 10, - onPrem: onPremInstructions(moduleName, null, null, null, context), + onPrem: onPremInstructions(moduleName, context), elasticCloud: cloudInstructions(moduleName), onPremElasticCloud: onPremCloudInstructions(moduleName), }; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/aerospike_metrics/index.js b/src/plugins/home/server/tutorials/aerospike_metrics/index.ts similarity index 72% rename from src/legacy/core_plugins/kibana/server/tutorials/aerospike_metrics/index.js rename to src/plugins/home/server/tutorials/aerospike_metrics/index.ts index aeb00dfa526fe..656e7feceed0c 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/aerospike_metrics/index.js +++ b/src/plugins/home/server/tutorials/aerospike_metrics/index.ts @@ -18,26 +18,30 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/metricbeat_instructions'; +} from '../instructions/metricbeat_instructions'; +import { + TutorialContext, + TutorialsCategory, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function aerospikeMetricsSpecProvider(context) { +export function aerospikeMetricsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'aerospike'; return { id: 'aerospikeMetrics', - name: i18n.translate('kbn.server.tutorials.aerospikeMetrics.nameTitle', { + name: i18n.translate('home.tutorials.aerospikeMetrics.nameTitle', { defaultMessage: 'Aerospike metrics', }), isBeta: false, - category: TUTORIAL_CATEGORY.METRICS, - shortDescription: i18n.translate('kbn.server.tutorials.aerospikeMetrics.shortDescription', { + category: TutorialsCategory.METRICS, + shortDescription: i18n.translate('home.tutorials.aerospikeMetrics.shortDescription', { defaultMessage: 'Fetch internal metrics from the Aerospike server.', }), - longDescription: i18n.translate('kbn.server.tutorials.aerospikeMetrics.longDescription', { + longDescription: i18n.translate('home.tutorials.aerospikeMetrics.longDescription', { defaultMessage: 'The `aerospike` Metricbeat module fetches internal metrics from Aerospike. \ [Learn more]({learnMoreLink}).', @@ -48,7 +52,7 @@ export function aerospikeMetricsSpecProvider(context) { euiIconType: 'logoAerospike', artifacts: { application: { - label: i18n.translate('kbn.server.tutorials.aerospikeMetrics.artifacts.application.label', { + label: i18n.translate('home.tutorials.aerospikeMetrics.artifacts.application.label', { defaultMessage: 'Discover', }), path: '/app/kibana#/discover', @@ -59,7 +63,7 @@ export function aerospikeMetricsSpecProvider(context) { }, }, completionTimeMinutes: 10, - onPrem: onPremInstructions(moduleName, null, null, null, context), + onPrem: onPremInstructions(moduleName, context), elasticCloud: cloudInstructions(moduleName), onPremElasticCloud: onPremCloudInstructions(moduleName), }; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/apache_logs/index.js b/src/plugins/home/server/tutorials/apache_logs/index.ts similarity index 71% rename from src/legacy/core_plugins/kibana/server/tutorials/apache_logs/index.js rename to src/plugins/home/server/tutorials/apache_logs/index.ts index d60884e196498..adf94f5567096 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/apache_logs/index.js +++ b/src/plugins/home/server/tutorials/apache_logs/index.ts @@ -18,26 +18,30 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/filebeat_instructions'; +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function apacheLogsSpecProvider(context) { +export function apacheLogsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'apache'; - const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS']; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; return { id: 'apacheLogs', - name: i18n.translate('kbn.server.tutorials.apacheLogs.nameTitle', { + name: i18n.translate('home.tutorials.apacheLogs.nameTitle', { defaultMessage: 'Apache logs', }), - category: TUTORIAL_CATEGORY.LOGGING, - shortDescription: i18n.translate('kbn.server.tutorials.apacheLogs.shortDescription', { + category: TutorialsCategory.LOGGING, + shortDescription: i18n.translate('home.tutorials.apacheLogs.shortDescription', { defaultMessage: 'Collect and parse access and error logs created by the Apache HTTP server.', }), - longDescription: i18n.translate('kbn.server.tutorials.apacheLogs.longDescription', { + longDescription: i18n.translate('home.tutorials.apacheLogs.longDescription', { defaultMessage: 'The apache Filebeat module parses access and error logs created by the Apache HTTP server. \ [Learn more]({learnMoreLink}).', @@ -50,12 +54,9 @@ export function apacheLogsSpecProvider(context) { dashboards: [ { id: 'Filebeat-Apache-Dashboard-ecs', - linkLabel: i18n.translate( - 'kbn.server.tutorials.apacheLogs.artifacts.dashboards.linkLabel', - { - defaultMessage: 'Apache logs dashboard', - } - ), + linkLabel: i18n.translate('home.tutorials.apacheLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'Apache logs dashboard', + }), isOverview: true, }, ], diff --git a/src/legacy/core_plugins/kibana/server/tutorials/apache_metrics/index.js b/src/plugins/home/server/tutorials/apache_metrics/index.ts similarity index 70% rename from src/legacy/core_plugins/kibana/server/tutorials/apache_metrics/index.js rename to src/plugins/home/server/tutorials/apache_metrics/index.ts index 5e20bfd1e9650..e272f3efb5abe 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/apache_metrics/index.js +++ b/src/plugins/home/server/tutorials/apache_metrics/index.ts @@ -18,25 +18,29 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/metricbeat_instructions'; +} from '../instructions/metricbeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function apacheMetricsSpecProvider(context) { +export function apacheMetricsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'apache'; return { id: 'apacheMetrics', - name: i18n.translate('kbn.server.tutorials.apacheMetrics.nameTitle', { + name: i18n.translate('home.tutorials.apacheMetrics.nameTitle', { defaultMessage: 'Apache metrics', }), - category: TUTORIAL_CATEGORY.METRICS, - shortDescription: i18n.translate('kbn.server.tutorials.apacheMetrics.shortDescription', { + category: TutorialsCategory.METRICS, + shortDescription: i18n.translate('home.tutorials.apacheMetrics.shortDescription', { defaultMessage: 'Fetch internal metrics from the Apache 2 HTTP server.', }), - longDescription: i18n.translate('kbn.server.tutorials.apacheMetrics.longDescription', { + longDescription: i18n.translate('home.tutorials.apacheMetrics.longDescription', { defaultMessage: 'The `apache` Metricbeat module fetches internal metrics from the Apache 2 HTTP server. \ [Learn more]({learnMoreLink}).', @@ -49,12 +53,9 @@ export function apacheMetricsSpecProvider(context) { dashboards: [ { id: 'Metricbeat-Apache-HTTPD-server-status-ecs', - linkLabel: i18n.translate( - 'kbn.server.tutorials.apacheMetrics.artifacts.dashboards.linkLabel', - { - defaultMessage: 'Apache metrics dashboard', - } - ), + linkLabel: i18n.translate('home.tutorials.apacheMetrics.artifacts.dashboards.linkLabel', { + defaultMessage: 'Apache metrics dashboard', + }), isOverview: true, }, ], @@ -64,7 +65,7 @@ export function apacheMetricsSpecProvider(context) { }, completionTimeMinutes: 10, previewImagePath: '/plugins/kibana/home/tutorial_resources/apache_metrics/screenshot.png', - onPrem: onPremInstructions(moduleName, null, null, null, context), + onPrem: onPremInstructions(moduleName, context), elasticCloud: cloudInstructions(moduleName), onPremElasticCloud: onPremCloudInstructions(moduleName), }; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/auditbeat/index.js b/src/plugins/home/server/tutorials/auditbeat/index.ts similarity index 73% rename from src/legacy/core_plugins/kibana/server/tutorials/auditbeat/index.js rename to src/plugins/home/server/tutorials/auditbeat/index.ts index b67a872dcc0aa..6d94e7507ff42 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/auditbeat/index.js +++ b/src/plugins/home/server/tutorials/auditbeat/index.ts @@ -18,25 +18,29 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/auditbeat_instructions'; +} from '../instructions/auditbeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function auditbeatSpecProvider(context) { - const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS']; +export function auditbeatSpecProvider(context: TutorialContext): TutorialSchema { + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; return { id: 'auditbeat', - name: i18n.translate('kbn.server.tutorials.auditbeat.nameTitle', { + name: i18n.translate('home.tutorials.auditbeat.nameTitle', { defaultMessage: 'Auditbeat', }), - category: TUTORIAL_CATEGORY.SIEM, - shortDescription: i18n.translate('kbn.server.tutorials.auditbeat.shortDescription', { + category: TutorialsCategory.SIEM, + shortDescription: i18n.translate('home.tutorials.auditbeat.shortDescription', { defaultMessage: 'Collect audit data from your hosts.', }), - longDescription: i18n.translate('kbn.server.tutorials.auditbeat.longDescription', { + longDescription: i18n.translate('home.tutorials.auditbeat.longDescription', { defaultMessage: 'Use Auditbeat to collect auditing data from your hosts. These include \ processes, users, logins, sockets information, file accesses, and more. \ @@ -50,7 +54,7 @@ processes, users, logins, sockets information, file accesses, and more. \ dashboards: [], application: { path: '/app/siem', - label: i18n.translate('kbn.server.tutorials.auditbeat.artifacts.dashboards.linkLabel', { + label: i18n.translate('home.tutorials.auditbeat.artifacts.dashboards.linkLabel', { defaultMessage: 'SIEM App', }), }, diff --git a/src/legacy/core_plugins/kibana/server/tutorials/aws_logs/index.js b/src/plugins/home/server/tutorials/aws_logs/index.ts similarity index 74% rename from src/legacy/core_plugins/kibana/server/tutorials/aws_logs/index.js rename to src/plugins/home/server/tutorials/aws_logs/index.ts index 5f7131cb4dcde..8908838bd558a 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/aws_logs/index.js +++ b/src/plugins/home/server/tutorials/aws_logs/index.ts @@ -18,26 +18,30 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/filebeat_instructions'; +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function awsLogsSpecProvider(server, context) { +export function awsLogsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'aws'; - const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS']; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; return { id: 'awsLogs', - name: i18n.translate('kbn.server.tutorials.awsLogs.nameTitle', { + name: i18n.translate('home.tutorials.awsLogs.nameTitle', { defaultMessage: 'AWS S3 based logs', }), - category: TUTORIAL_CATEGORY.LOGGING, - shortDescription: i18n.translate('kbn.server.tutorials.awsLogs.shortDescription', { + category: TutorialsCategory.LOGGING, + shortDescription: i18n.translate('home.tutorials.awsLogs.shortDescription', { defaultMessage: 'Collect AWS logs from S3 bucket with Filebeat.', }), - longDescription: i18n.translate('kbn.server.tutorials.awsLogs.longDescription', { + longDescription: i18n.translate('home.tutorials.awsLogs.longDescription', { defaultMessage: 'Collect AWS logs by exporting them to an S3 bucket which is configured with SQS notification. \ [Learn more]({learnMoreLink}).', @@ -50,7 +54,7 @@ export function awsLogsSpecProvider(server, context) { dashboards: [ { id: '4746e000-bacd-11e9-9f70-1f7bda85a5eb', - linkLabel: i18n.translate('kbn.server.tutorials.awsLogs.artifacts.dashboards.linkLabel', { + linkLabel: i18n.translate('home.tutorials.awsLogs.artifacts.dashboards.linkLabel', { defaultMessage: 'AWS S3 server access log dashboard', }), isOverview: true, diff --git a/src/legacy/core_plugins/kibana/server/tutorials/aws_metrics/index.js b/src/plugins/home/server/tutorials/aws_metrics/index.ts similarity index 71% rename from src/legacy/core_plugins/kibana/server/tutorials/aws_metrics/index.js rename to src/plugins/home/server/tutorials/aws_metrics/index.ts index e7b6644ecea1c..d00951b524530 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/aws_metrics/index.js +++ b/src/plugins/home/server/tutorials/aws_metrics/index.ts @@ -18,26 +18,30 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/metricbeat_instructions'; +} from '../instructions/metricbeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function awsMetricsSpecProvider(context) { +export function awsMetricsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'aws'; return { id: 'awsMetrics', - name: i18n.translate('kbn.server.tutorials.awsMetrics.nameTitle', { + name: i18n.translate('home.tutorials.awsMetrics.nameTitle', { defaultMessage: 'AWS metrics', }), - category: TUTORIAL_CATEGORY.METRICS, - shortDescription: i18n.translate('kbn.server.tutorials.awsMetrics.shortDescription', { + category: TutorialsCategory.METRICS, + shortDescription: i18n.translate('home.tutorials.awsMetrics.shortDescription', { defaultMessage: 'Fetch monitoring metrics for EC2 instances from the AWS APIs and Cloudwatch.', }), - longDescription: i18n.translate('kbn.server.tutorials.awsMetrics.longDescription', { + longDescription: i18n.translate('home.tutorials.awsMetrics.longDescription', { defaultMessage: 'The `aws` Metricbeat module fetches monitoring metrics from the AWS APIs and Cloudwatch. \ [Learn more]({learnMoreLink}).', @@ -51,12 +55,9 @@ export function awsMetricsSpecProvider(context) { dashboards: [ { id: 'c5846400-f7fb-11e8-af03-c999c9dea608-ecs', - linkLabel: i18n.translate( - 'kbn.server.tutorials.awsMetrics.artifacts.dashboards.linkLabel', - { - defaultMessage: 'AWS metrics dashboard', - } - ), + linkLabel: i18n.translate('home.tutorials.awsMetrics.artifacts.dashboards.linkLabel', { + defaultMessage: 'AWS metrics dashboard', + }), isOverview: true, }, ], @@ -66,7 +67,7 @@ export function awsMetricsSpecProvider(context) { }, completionTimeMinutes: 10, previewImagePath: '/plugins/kibana/home/tutorial_resources/aws_metrics/screenshot.png', - onPrem: onPremInstructions(moduleName, null, null, null, context), + onPrem: onPremInstructions(moduleName, context), elasticCloud: cloudInstructions(moduleName), onPremElasticCloud: onPremCloudInstructions(moduleName), }; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/azure_metrics/index.js b/src/plugins/home/server/tutorials/azure_metrics/index.ts similarity index 71% rename from src/legacy/core_plugins/kibana/server/tutorials/azure_metrics/index.js rename to src/plugins/home/server/tutorials/azure_metrics/index.ts index cbefd713a7381..72befcf8d2065 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/azure_metrics/index.js +++ b/src/plugins/home/server/tutorials/azure_metrics/index.ts @@ -18,26 +18,30 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/metricbeat_instructions'; +} from '../instructions/metricbeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function azureMetricsSpecProvider(context) { +export function azureMetricsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'azure'; return { id: 'azureMetrics', - name: i18n.translate('kbn.server.tutorials.azureMetrics.nameTitle', { + name: i18n.translate('home.tutorials.azureMetrics.nameTitle', { defaultMessage: 'Azure metrics', }), isBeta: true, - category: TUTORIAL_CATEGORY.METRICS, - shortDescription: i18n.translate('kbn.server.tutorials.azureMetrics.shortDescription', { + category: TutorialsCategory.METRICS, + shortDescription: i18n.translate('home.tutorials.azureMetrics.shortDescription', { defaultMessage: 'Fetch Azure Monitor metrics.', }), - longDescription: i18n.translate('kbn.server.tutorials.azureMetrics.longDescription', { + longDescription: i18n.translate('home.tutorials.azureMetrics.longDescription', { defaultMessage: 'The `azure` Metricbeat module fetches Azure Monitor metrics. \ [Learn more]({learnMoreLink}).', @@ -48,7 +52,7 @@ export function azureMetricsSpecProvider(context) { euiIconType: 'logoAzure', artifacts: { application: { - label: i18n.translate('kbn.server.tutorials.azureMetrics.artifacts.application.label', { + label: i18n.translate('home.tutorials.azureMetrics.artifacts.application.label', { defaultMessage: 'Discover', }), path: '/app/kibana#/discover', @@ -59,7 +63,7 @@ export function azureMetricsSpecProvider(context) { }, }, completionTimeMinutes: 10, - onPrem: onPremInstructions(moduleName, null, null, null, context), + onPrem: onPremInstructions(moduleName, context), elasticCloud: cloudInstructions(moduleName), onPremElasticCloud: onPremCloudInstructions(moduleName), }; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/ceph_metrics/index.js b/src/plugins/home/server/tutorials/ceph_metrics/index.ts similarity index 71% rename from src/legacy/core_plugins/kibana/server/tutorials/ceph_metrics/index.js rename to src/plugins/home/server/tutorials/ceph_metrics/index.ts index d844dd016657c..6b5f9c4c6fe5f 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/ceph_metrics/index.js +++ b/src/plugins/home/server/tutorials/ceph_metrics/index.ts @@ -18,26 +18,30 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/metricbeat_instructions'; +} from '../instructions/metricbeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function cephMetricsSpecProvider(context) { +export function cephMetricsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'ceph'; return { id: 'cephMetrics', - name: i18n.translate('kbn.server.tutorials.cephMetrics.nameTitle', { + name: i18n.translate('home.tutorials.cephMetrics.nameTitle', { defaultMessage: 'Ceph metrics', }), isBeta: false, - category: TUTORIAL_CATEGORY.METRICS, - shortDescription: i18n.translate('kbn.server.tutorials.cephMetrics.shortDescription', { + category: TutorialsCategory.METRICS, + shortDescription: i18n.translate('home.tutorials.cephMetrics.shortDescription', { defaultMessage: 'Fetch internal metrics from the Ceph server.', }), - longDescription: i18n.translate('kbn.server.tutorials.cephMetrics.longDescription', { + longDescription: i18n.translate('home.tutorials.cephMetrics.longDescription', { defaultMessage: 'The `ceph` Metricbeat module fetches internal metrics from Ceph. \ [Learn more]({learnMoreLink}).', @@ -48,7 +52,7 @@ export function cephMetricsSpecProvider(context) { euiIconType: 'logoCeph', artifacts: { application: { - label: i18n.translate('kbn.server.tutorials.cephMetrics.artifacts.application.label', { + label: i18n.translate('home.tutorials.cephMetrics.artifacts.application.label', { defaultMessage: 'Discover', }), path: '/app/kibana#/discover', @@ -59,7 +63,7 @@ export function cephMetricsSpecProvider(context) { }, }, completionTimeMinutes: 10, - onPrem: onPremInstructions(moduleName, null, null, null, context), + onPrem: onPremInstructions(moduleName, context), elasticCloud: cloudInstructions(moduleName), onPremElasticCloud: onPremCloudInstructions(moduleName), }; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/cisco_logs/index.js b/src/plugins/home/server/tutorials/cisco_logs/index.ts similarity index 72% rename from src/legacy/core_plugins/kibana/server/tutorials/cisco_logs/index.js rename to src/plugins/home/server/tutorials/cisco_logs/index.ts index 90190bd0d6877..303dbd9a9d856 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/cisco_logs/index.js +++ b/src/plugins/home/server/tutorials/cisco_logs/index.ts @@ -18,26 +18,30 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/filebeat_instructions'; +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function ciscoLogsSpecProvider(context) { +export function ciscoLogsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'cisco'; - const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS']; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; return { id: 'ciscoLogs', - name: i18n.translate('kbn.server.tutorials.ciscoLogs.nameTitle', { + name: i18n.translate('home.tutorials.ciscoLogs.nameTitle', { defaultMessage: 'Cisco', }), - category: TUTORIAL_CATEGORY.SIEM, - shortDescription: i18n.translate('kbn.server.tutorials.ciscoLogs.shortDescription', { + category: TutorialsCategory.SIEM, + shortDescription: i18n.translate('home.tutorials.ciscoLogs.shortDescription', { defaultMessage: 'Collect and parse logs received from Cisco ASA firewalls.', }), - longDescription: i18n.translate('kbn.server.tutorials.ciscoLogs.longDescription', { + longDescription: i18n.translate('home.tutorials.ciscoLogs.longDescription', { defaultMessage: 'This is a module for Cisco network device’s logs. Currently \ supports the "asa" fileset for Cisco ASA firewall logs received over syslog or read from a file. \ @@ -46,12 +50,12 @@ supports the "asa" fileset for Cisco ASA firewall logs received over syslog or r learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-cisco.html', }, }), - //euiIconType: 'logoCisco', + // euiIconType: 'logoCisco', artifacts: { dashboards: [], application: { path: '/app/siem', - label: i18n.translate('kbn.server.tutorials.ciscoLogs.artifacts.dashboards.linkLabel', { + label: i18n.translate('home.tutorials.ciscoLogs.artifacts.dashboards.linkLabel', { defaultMessage: 'SIEM App', }), }, diff --git a/src/legacy/core_plugins/kibana/server/tutorials/cloudwatch_logs/index.js b/src/plugins/home/server/tutorials/cloudwatch_logs/index.ts similarity index 69% rename from src/legacy/core_plugins/kibana/server/tutorials/cloudwatch_logs/index.js rename to src/plugins/home/server/tutorials/cloudwatch_logs/index.ts index 6abc7161d8bf2..10f0eb3e4f34f 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/cloudwatch_logs/index.js +++ b/src/plugins/home/server/tutorials/cloudwatch_logs/index.ts @@ -18,24 +18,28 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/functionbeat_instructions'; +} from '../instructions/functionbeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function cloudwatchLogsSpecProvider(context) { +export function cloudwatchLogsSpecProvider(context: TutorialContext): TutorialSchema { return { id: 'cloudwatchLogs', - name: i18n.translate('kbn.server.tutorials.cloudwatchLogs.nameTitle', { + name: i18n.translate('home.tutorials.cloudwatchLogs.nameTitle', { defaultMessage: 'AWS Cloudwatch logs', }), - category: TUTORIAL_CATEGORY.LOGGING, - shortDescription: i18n.translate('kbn.server.tutorials.cloudwatchLogs.shortDescription', { + category: TutorialsCategory.LOGGING, + shortDescription: i18n.translate('home.tutorials.cloudwatchLogs.shortDescription', { defaultMessage: 'Collect Cloudwatch logs with Functionbeat.', }), - longDescription: i18n.translate('kbn.server.tutorials.cloudwatchLogs.longDescription', { + longDescription: i18n.translate('home.tutorials.cloudwatchLogs.longDescription', { defaultMessage: 'Collect Cloudwatch logs by deploying Functionbeat to run as \ an AWS Lambda function. \ @@ -54,8 +58,8 @@ export function cloudwatchLogsSpecProvider(context) { }, }, completionTimeMinutes: 10, - //previewImagePath: '/plugins/kibana/home/tutorial_resources/uptime_monitors/screenshot.png', - onPrem: onPremInstructions(null, null, null, context), + // previewImagePath: '/plugins/kibana/home/tutorial_resources/uptime_monitors/screenshot.png', + onPrem: onPremInstructions([], context), elasticCloud: cloudInstructions(), onPremElasticCloud: onPremCloudInstructions(), }; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/cockroachdb_metrics/index.js b/src/plugins/home/server/tutorials/cockroachdb_metrics/index.ts similarity index 74% rename from src/legacy/core_plugins/kibana/server/tutorials/cockroachdb_metrics/index.js rename to src/plugins/home/server/tutorials/cockroachdb_metrics/index.ts index 0eb54700b50c2..a8146e024a37e 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/cockroachdb_metrics/index.js +++ b/src/plugins/home/server/tutorials/cockroachdb_metrics/index.ts @@ -18,25 +18,29 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/metricbeat_instructions'; +} from '../instructions/metricbeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function cockroachdbMetricsSpecProvider(context) { +export function cockroachdbMetricsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'cockroachdb'; return { id: 'cockroachdbMetrics', - name: i18n.translate('kbn.server.tutorials.cockroachdbMetrics.nameTitle', { + name: i18n.translate('home.tutorials.cockroachdbMetrics.nameTitle', { defaultMessage: 'CockroachDB metrics', }), - category: TUTORIAL_CATEGORY.METRICS, - shortDescription: i18n.translate('kbn.server.tutorials.cockroachdbMetrics.shortDescription', { + category: TutorialsCategory.METRICS, + shortDescription: i18n.translate('home.tutorials.cockroachdbMetrics.shortDescription', { defaultMessage: 'Fetch monitoring metrics from the CockroachDB server.', }), - longDescription: i18n.translate('kbn.server.tutorials.cockroachdbMetrics.longDescription', { + longDescription: i18n.translate('home.tutorials.cockroachdbMetrics.longDescription', { defaultMessage: 'The `cockroachdb` Metricbeat module fetches monitoring metrics from CockroachDB. \ [Learn more]({learnMoreLink}).', @@ -50,7 +54,7 @@ export function cockroachdbMetricsSpecProvider(context) { { id: 'e3ba0c30-9766-11e9-9eea-6f554992ec1f', linkLabel: i18n.translate( - 'kbn.server.tutorials.cockroachdbMetrics.artifacts.dashboards.linkLabel', + 'home.tutorials.cockroachdbMetrics.artifacts.dashboards.linkLabel', { defaultMessage: 'CockroachDB metrics dashboard', } @@ -64,7 +68,7 @@ export function cockroachdbMetricsSpecProvider(context) { }, completionTimeMinutes: 10, previewImagePath: '/plugins/kibana/home/tutorial_resources/cockroachdb_metrics/screenshot.png', - onPrem: onPremInstructions(moduleName, null, null, null, context), + onPrem: onPremInstructions(moduleName, context), elasticCloud: cloudInstructions(moduleName), onPremElasticCloud: onPremCloudInstructions(moduleName), }; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/consul_metrics/index.js b/src/plugins/home/server/tutorials/consul_metrics/index.ts similarity index 70% rename from src/legacy/core_plugins/kibana/server/tutorials/consul_metrics/index.js rename to src/plugins/home/server/tutorials/consul_metrics/index.ts index 361fd1db39e5a..8b12f38274ee9 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/consul_metrics/index.js +++ b/src/plugins/home/server/tutorials/consul_metrics/index.ts @@ -18,25 +18,29 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/metricbeat_instructions'; +} from '../instructions/metricbeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function consulMetricsSpecProvider(context) { +export function consulMetricsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'consul'; return { id: 'consulMetrics', - name: i18n.translate('kbn.server.tutorials.consulMetrics.nameTitle', { + name: i18n.translate('home.tutorials.consulMetrics.nameTitle', { defaultMessage: 'Consul metrics', }), - category: TUTORIAL_CATEGORY.METRICS, - shortDescription: i18n.translate('kbn.server.tutorials.consulMetrics.shortDescription', { + category: TutorialsCategory.METRICS, + shortDescription: i18n.translate('home.tutorials.consulMetrics.shortDescription', { defaultMessage: 'Fetch monitoring metrics from the Consul server.', }), - longDescription: i18n.translate('kbn.server.tutorials.consulMetrics.longDescription', { + longDescription: i18n.translate('home.tutorials.consulMetrics.longDescription', { defaultMessage: 'The `consul` Metricbeat module fetches monitoring metrics from Consul. \ [Learn more]({learnMoreLink}).', @@ -49,12 +53,9 @@ export function consulMetricsSpecProvider(context) { dashboards: [ { id: '496910f0-b952-11e9-a579-f5c0a5d81340', - linkLabel: i18n.translate( - 'kbn.server.tutorials.consulMetrics.artifacts.dashboards.linkLabel', - { - defaultMessage: 'Consul metrics dashboard', - } - ), + linkLabel: i18n.translate('home.tutorials.consulMetrics.artifacts.dashboards.linkLabel', { + defaultMessage: 'Consul metrics dashboard', + }), isOverview: true, }, ], @@ -64,7 +65,7 @@ export function consulMetricsSpecProvider(context) { }, completionTimeMinutes: 10, previewImagePath: '/plugins/kibana/home/tutorial_resources/consul_metrics/screenshot.png', - onPrem: onPremInstructions(moduleName, null, null, null, context), + onPrem: onPremInstructions(moduleName, context), elasticCloud: cloudInstructions(moduleName), onPremElasticCloud: onPremCloudInstructions(moduleName), }; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/coredns_logs/index.js b/src/plugins/home/server/tutorials/coredns_logs/index.ts similarity index 72% rename from src/legacy/core_plugins/kibana/server/tutorials/coredns_logs/index.js rename to src/plugins/home/server/tutorials/coredns_logs/index.ts index ca308ac969e49..e2f976c0f377b 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/coredns_logs/index.js +++ b/src/plugins/home/server/tutorials/coredns_logs/index.ts @@ -18,26 +18,30 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/filebeat_instructions'; +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function corednsLogsSpecProvider(server, context) { +export function corednsLogsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'coredns'; - const platforms = ['OSX', 'DEB', 'RPM']; + const platforms = ['OSX', 'DEB', 'RPM'] as const; return { id: 'corednsLogs', - name: i18n.translate('kbn.server.tutorials.corednsLogs.nameTitle', { + name: i18n.translate('home.tutorials.corednsLogs.nameTitle', { defaultMessage: 'CoreDNS logs', }), - category: TUTORIAL_CATEGORY.SIEM, - shortDescription: i18n.translate('kbn.server.tutorials.corednsLogs.shortDescription', { + category: TutorialsCategory.SIEM, + shortDescription: i18n.translate('home.tutorials.corednsLogs.shortDescription', { defaultMessage: 'Collect the logs created by Coredns.', }), - longDescription: i18n.translate('kbn.server.tutorials.corednsLogs.longDescription', { + longDescription: i18n.translate('home.tutorials.corednsLogs.longDescription', { defaultMessage: 'The `coredns` Filebeat module collects the logs from \ [CoreDNS](https://coredns.io/manual/toc/). \ @@ -51,12 +55,9 @@ export function corednsLogsSpecProvider(server, context) { dashboards: [ { id: '53aa1f70-443e-11e9-8548-ab7fbe04f038', - linkLabel: i18n.translate( - 'kbn.server.tutorials.corednsLogs.artifacts.dashboards.linkLabel', - { - defaultMessage: 'CoreDNS logs dashboard', - } - ), + linkLabel: i18n.translate('home.tutorials.corednsLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'CoreDNS logs dashboard', + }), isOverview: true, }, ], diff --git a/src/legacy/core_plugins/kibana/server/tutorials/coredns_metrics/index.js b/src/plugins/home/server/tutorials/coredns_metrics/index.ts similarity index 72% rename from src/legacy/core_plugins/kibana/server/tutorials/coredns_metrics/index.js rename to src/plugins/home/server/tutorials/coredns_metrics/index.ts index 58bd8f656bafb..ad0ce4a58c738 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/coredns_metrics/index.js +++ b/src/plugins/home/server/tutorials/coredns_metrics/index.ts @@ -18,25 +18,29 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/metricbeat_instructions'; +} from '../instructions/metricbeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function corednsMetricsSpecProvider(context) { +export function corednsMetricsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'coredns'; return { id: 'corednsMetrics', - name: i18n.translate('kbn.server.tutorials.corednsMetrics.nameTitle', { + name: i18n.translate('home.tutorials.corednsMetrics.nameTitle', { defaultMessage: 'CoreDNS metrics', }), - category: TUTORIAL_CATEGORY.METRICS, - shortDescription: i18n.translate('kbn.server.tutorials.corednsMetrics.shortDescription', { + category: TutorialsCategory.METRICS, + shortDescription: i18n.translate('home.tutorials.corednsMetrics.shortDescription', { defaultMessage: 'Fetch monitoring metrics from the CoreDNS server.', }), - longDescription: i18n.translate('kbn.server.tutorials.corednsMetrics.longDescription', { + longDescription: i18n.translate('home.tutorials.corednsMetrics.longDescription', { defaultMessage: 'The `coredns` Metricbeat module fetches monitoring metrics from CoreDNS. \ [Learn more]({learnMoreLink}).', @@ -47,7 +51,7 @@ export function corednsMetricsSpecProvider(context) { euiIconType: '/plugins/kibana/home/tutorial_resources/logos/coredns.svg', artifacts: { application: { - label: i18n.translate('kbn.server.tutorials.corednsMetrics.artifacts.application.label', { + label: i18n.translate('home.tutorials.corednsMetrics.artifacts.application.label', { defaultMessage: 'Discover', }), path: '/app/kibana#/discover', @@ -59,7 +63,7 @@ export function corednsMetricsSpecProvider(context) { }, completionTimeMinutes: 10, previewImagePath: '/plugins/kibana/home/tutorial_resources/coredns_metrics/screenshot.png', - onPrem: onPremInstructions(moduleName, null, null, null, context), + onPrem: onPremInstructions(moduleName, context), elasticCloud: cloudInstructions(moduleName), onPremElasticCloud: onPremCloudInstructions(moduleName), }; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/couchbase_metrics/index.js b/src/plugins/home/server/tutorials/couchbase_metrics/index.ts similarity index 71% rename from src/legacy/core_plugins/kibana/server/tutorials/couchbase_metrics/index.js rename to src/plugins/home/server/tutorials/couchbase_metrics/index.ts index 3348b9edb7b9c..66b70ba2fb456 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/couchbase_metrics/index.js +++ b/src/plugins/home/server/tutorials/couchbase_metrics/index.ts @@ -18,26 +18,30 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/metricbeat_instructions'; +} from '../instructions/metricbeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function couchbaseMetricsSpecProvider(context) { +export function couchbaseMetricsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'couchbase'; return { id: 'couchbaseMetrics', - name: i18n.translate('kbn.server.tutorials.couchbaseMetrics.nameTitle', { + name: i18n.translate('home.tutorials.couchbaseMetrics.nameTitle', { defaultMessage: 'Couchbase metrics', }), isBeta: false, - category: TUTORIAL_CATEGORY.METRICS, - shortDescription: i18n.translate('kbn.server.tutorials.couchbaseMetrics.shortDescription', { + category: TutorialsCategory.METRICS, + shortDescription: i18n.translate('home.tutorials.couchbaseMetrics.shortDescription', { defaultMessage: 'Fetch internal metrics from Couchbase.', }), - longDescription: i18n.translate('kbn.server.tutorials.couchbaseMetrics.longDescription', { + longDescription: i18n.translate('home.tutorials.couchbaseMetrics.longDescription', { defaultMessage: 'The `couchbase` Metricbeat module fetches internal metrics from Couchbase. \ [Learn more]({learnMoreLink}).', @@ -48,7 +52,7 @@ export function couchbaseMetricsSpecProvider(context) { euiIconType: 'logoCouchbase', artifacts: { application: { - label: i18n.translate('kbn.server.tutorials.couchbaseMetrics.artifacts.application.label', { + label: i18n.translate('home.tutorials.couchbaseMetrics.artifacts.application.label', { defaultMessage: 'Discover', }), path: '/app/kibana#/discover', @@ -59,7 +63,7 @@ export function couchbaseMetricsSpecProvider(context) { }, }, completionTimeMinutes: 10, - onPrem: onPremInstructions(moduleName, null, null, null, context), + onPrem: onPremInstructions(moduleName, context), elasticCloud: cloudInstructions(moduleName), onPremElasticCloud: onPremCloudInstructions(moduleName), }; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/couchdb_metrics/index.js b/src/plugins/home/server/tutorials/couchdb_metrics/index.ts similarity index 74% rename from src/legacy/core_plugins/kibana/server/tutorials/couchdb_metrics/index.js rename to src/plugins/home/server/tutorials/couchdb_metrics/index.ts index cf7cec60c1f13..e1423e96b1d47 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/couchdb_metrics/index.js +++ b/src/plugins/home/server/tutorials/couchdb_metrics/index.ts @@ -18,25 +18,29 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/metricbeat_instructions'; +} from '../instructions/metricbeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function couchdbMetricsSpecProvider(context) { +export function couchdbMetricsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'couchdb'; return { id: 'couchdbMetrics', - name: i18n.translate('kbn.server.tutorials.couchdbMetrics.nameTitle', { + name: i18n.translate('home.tutorials.couchdbMetrics.nameTitle', { defaultMessage: 'CouchDB metrics', }), - category: TUTORIAL_CATEGORY.METRICS, - shortDescription: i18n.translate('kbn.server.tutorials.couchdbMetrics.shortDescription', { + category: TutorialsCategory.METRICS, + shortDescription: i18n.translate('home.tutorials.couchdbMetrics.shortDescription', { defaultMessage: 'Fetch monitoring metrics from the CouchdB server.', }), - longDescription: i18n.translate('kbn.server.tutorials.couchdbMetrics.longDescription', { + longDescription: i18n.translate('home.tutorials.couchdbMetrics.longDescription', { defaultMessage: 'The `couchdb` Metricbeat module fetches monitoring metrics from CouchDB. \ [Learn more]({learnMoreLink}).', @@ -50,7 +54,7 @@ export function couchdbMetricsSpecProvider(context) { { id: '496910f0-b952-11e9-a579-f5c0a5d81340', linkLabel: i18n.translate( - 'kbn.server.tutorials.couchdbMetrics.artifacts.dashboards.linkLabel', + 'home.tutorials.couchdbMetrics.artifacts.dashboards.linkLabel', { defaultMessage: 'CouchDB metrics dashboard', } @@ -64,7 +68,7 @@ export function couchdbMetricsSpecProvider(context) { }, completionTimeMinutes: 10, previewImagePath: '/plugins/kibana/home/tutorial_resources/couchdb_metrics/screenshot.png', - onPrem: onPremInstructions(moduleName, null, null, null, context), + onPrem: onPremInstructions(moduleName, context), elasticCloud: cloudInstructions(moduleName), onPremElasticCloud: onPremCloudInstructions(moduleName), }; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/docker_metrics/index.js b/src/plugins/home/server/tutorials/docker_metrics/index.ts similarity index 69% rename from src/legacy/core_plugins/kibana/server/tutorials/docker_metrics/index.js rename to src/plugins/home/server/tutorials/docker_metrics/index.ts index 0ed852afec7b7..4d9d0c9ee68d7 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/docker_metrics/index.js +++ b/src/plugins/home/server/tutorials/docker_metrics/index.ts @@ -18,25 +18,29 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/metricbeat_instructions'; +} from '../instructions/metricbeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function dockerMetricsSpecProvider(context) { +export function dockerMetricsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'docker'; return { id: 'dockerMetrics', - name: i18n.translate('kbn.server.tutorials.dockerMetrics.nameTitle', { + name: i18n.translate('home.tutorials.dockerMetrics.nameTitle', { defaultMessage: 'Docker metrics', }), - category: TUTORIAL_CATEGORY.METRICS, - shortDescription: i18n.translate('kbn.server.tutorials.dockerMetrics.shortDescription', { + category: TutorialsCategory.METRICS, + shortDescription: i18n.translate('home.tutorials.dockerMetrics.shortDescription', { defaultMessage: 'Fetch metrics about your Docker containers.', }), - longDescription: i18n.translate('kbn.server.tutorials.dockerMetrics.longDescription', { + longDescription: i18n.translate('home.tutorials.dockerMetrics.longDescription', { defaultMessage: 'The `docker` Metricbeat module fetches metrics from the Docker server. \ [Learn more]({learnMoreLink}).', @@ -49,12 +53,9 @@ export function dockerMetricsSpecProvider(context) { dashboards: [ { id: 'AV4REOpp5NkDleZmzKkE-ecs', - linkLabel: i18n.translate( - 'kbn.server.tutorials.dockerMetrics.artifacts.dashboards.linkLabel', - { - defaultMessage: 'Docker metrics dashboard', - } - ), + linkLabel: i18n.translate('home.tutorials.dockerMetrics.artifacts.dashboards.linkLabel', { + defaultMessage: 'Docker metrics dashboard', + }), isOverview: true, }, ], @@ -64,7 +65,7 @@ export function dockerMetricsSpecProvider(context) { }, completionTimeMinutes: 10, previewImagePath: '/plugins/kibana/home/tutorial_resources/docker_metrics/screenshot.png', - onPrem: onPremInstructions(moduleName, null, null, null, context), + onPrem: onPremInstructions(moduleName, context), elasticCloud: cloudInstructions(moduleName), onPremElasticCloud: onPremCloudInstructions(moduleName), }; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/dropwizard_metrics/index.js b/src/plugins/home/server/tutorials/dropwizard_metrics/index.ts similarity index 69% rename from src/legacy/core_plugins/kibana/server/tutorials/dropwizard_metrics/index.js rename to src/plugins/home/server/tutorials/dropwizard_metrics/index.ts index c32779fb36368..164214ffd749c 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/dropwizard_metrics/index.js +++ b/src/plugins/home/server/tutorials/dropwizard_metrics/index.ts @@ -18,26 +18,30 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/metricbeat_instructions'; +} from '../instructions/metricbeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function dropwizardMetricsSpecProvider(context) { +export function dropwizardMetricsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'dropwizard'; return { id: 'dropwizardMetrics', - name: i18n.translate('kbn.server.tutorials.dropwizardMetrics.nameTitle', { + name: i18n.translate('home.tutorials.dropwizardMetrics.nameTitle', { defaultMessage: 'Dropwizard metrics', }), isBeta: false, - category: TUTORIAL_CATEGORY.METRICS, - shortDescription: i18n.translate('kbn.server.tutorials.dropwizardMetrics.shortDescription', { + category: TutorialsCategory.METRICS, + shortDescription: i18n.translate('home.tutorials.dropwizardMetrics.shortDescription', { defaultMessage: 'Fetch internal metrics from Dropwizard Java application.', }), - longDescription: i18n.translate('kbn.server.tutorials.dropwizardMetrics.longDescription', { + longDescription: i18n.translate('home.tutorials.dropwizardMetrics.longDescription', { defaultMessage: 'The `dropwizard` Metricbeat module fetches internal metrics from Dropwizard Java Application. \ [Learn more]({learnMoreLink}).', @@ -48,12 +52,9 @@ export function dropwizardMetricsSpecProvider(context) { euiIconType: 'logoDropwizard', artifacts: { application: { - label: i18n.translate( - 'kbn.server.tutorials.dropwizardMetrics.artifacts.application.label', - { - defaultMessage: 'Discover', - } - ), + label: i18n.translate('home.tutorials.dropwizardMetrics.artifacts.application.label', { + defaultMessage: 'Discover', + }), path: '/app/kibana#/discover', }, dashboards: [], @@ -62,7 +63,7 @@ export function dropwizardMetricsSpecProvider(context) { }, }, completionTimeMinutes: 10, - onPrem: onPremInstructions(moduleName, null, null, null, context), + onPrem: onPremInstructions(moduleName, context), elasticCloud: cloudInstructions(moduleName), onPremElasticCloud: onPremCloudInstructions(moduleName), }; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/elasticsearch_logs/index.js b/src/plugins/home/server/tutorials/elasticsearch_logs/index.ts similarity index 70% rename from src/legacy/core_plugins/kibana/server/tutorials/elasticsearch_logs/index.js rename to src/plugins/home/server/tutorials/elasticsearch_logs/index.ts index 20bdf38a5f779..4369c805dc7c4 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/elasticsearch_logs/index.js +++ b/src/plugins/home/server/tutorials/elasticsearch_logs/index.ts @@ -18,27 +18,31 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/filebeat_instructions'; +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function elasticsearchLogsSpecProvider(context) { +export function elasticsearchLogsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'elasticsearch'; - const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS']; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; return { id: 'elasticsearchLogs', - name: i18n.translate('kbn.server.tutorials.elasticsearchLogs.nameTitle', { + name: i18n.translate('home.tutorials.elasticsearchLogs.nameTitle', { defaultMessage: 'Elasticsearch logs', }), - category: TUTORIAL_CATEGORY.LOGGING, + category: TutorialsCategory.LOGGING, isBeta: true, - shortDescription: i18n.translate('kbn.server.tutorials.elasticsearchLogs.shortDescription', { + shortDescription: i18n.translate('home.tutorials.elasticsearchLogs.shortDescription', { defaultMessage: 'Collect and parse logs created by Elasticsearch.', }), - longDescription: i18n.translate('kbn.server.tutorials.elasticsearchLogs.longDescription', { + longDescription: i18n.translate('home.tutorials.elasticsearchLogs.longDescription', { defaultMessage: 'The `elasticsearch` Filebeat module parses logs created by Elasticsearch. \ [Learn more]({learnMoreLink}).', @@ -49,12 +53,9 @@ export function elasticsearchLogsSpecProvider(context) { euiIconType: 'logoElasticsearch', artifacts: { application: { - label: i18n.translate( - 'kbn.server.tutorials.elasticsearchLogs.artifacts.application.label', - { - defaultMessage: 'Discover', - } - ), + label: i18n.translate('home.tutorials.elasticsearchLogs.artifacts.application.label', { + defaultMessage: 'Discover', + }), path: '/app/kibana#/discover', }, dashboards: [], diff --git a/src/legacy/core_plugins/kibana/server/tutorials/elasticsearch_metrics/index.js b/src/plugins/home/server/tutorials/elasticsearch_metrics/index.ts similarity index 69% rename from src/legacy/core_plugins/kibana/server/tutorials/elasticsearch_metrics/index.js rename to src/plugins/home/server/tutorials/elasticsearch_metrics/index.ts index a4d193ebc1d1f..d9004e72733f9 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/elasticsearch_metrics/index.js +++ b/src/plugins/home/server/tutorials/elasticsearch_metrics/index.ts @@ -18,26 +18,30 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/metricbeat_instructions'; +} from '../instructions/metricbeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function elasticsearchMetricsSpecProvider(context) { +export function elasticsearchMetricsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'elasticsearch'; return { id: 'elasticsearchMetrics', - name: i18n.translate('kbn.server.tutorials.elasticsearchMetrics.nameTitle', { + name: i18n.translate('home.tutorials.elasticsearchMetrics.nameTitle', { defaultMessage: 'Elasticsearch metrics', }), isBeta: false, - category: TUTORIAL_CATEGORY.METRICS, - shortDescription: i18n.translate('kbn.server.tutorials.elasticsearchMetrics.shortDescription', { + category: TutorialsCategory.METRICS, + shortDescription: i18n.translate('home.tutorials.elasticsearchMetrics.shortDescription', { defaultMessage: 'Fetch internal metrics from Elasticsearch.', }), - longDescription: i18n.translate('kbn.server.tutorials.elasticsearchMetrics.longDescription', { + longDescription: i18n.translate('home.tutorials.elasticsearchMetrics.longDescription', { defaultMessage: 'The `elasticsearch` Metricbeat module fetches internal metrics from Elasticsearch. \ [Learn more]({learnMoreLink}).', @@ -48,12 +52,9 @@ export function elasticsearchMetricsSpecProvider(context) { euiIconType: 'logoElasticsearch', artifacts: { application: { - label: i18n.translate( - 'kbn.server.tutorials.elasticsearchMetrics.artifacts.application.label', - { - defaultMessage: 'Discover', - } - ), + label: i18n.translate('home.tutorials.elasticsearchMetrics.artifacts.application.label', { + defaultMessage: 'Discover', + }), path: '/app/kibana#/discover', }, dashboards: [], @@ -62,7 +63,7 @@ export function elasticsearchMetricsSpecProvider(context) { }, }, completionTimeMinutes: 10, - onPrem: onPremInstructions(moduleName, null, null, null, context), + onPrem: onPremInstructions(moduleName, context), elasticCloud: cloudInstructions(moduleName), onPremElasticCloud: onPremCloudInstructions(moduleName), }; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/envoyproxy_logs/index.js b/src/plugins/home/server/tutorials/envoyproxy_logs/index.ts similarity index 71% rename from src/legacy/core_plugins/kibana/server/tutorials/envoyproxy_logs/index.js rename to src/plugins/home/server/tutorials/envoyproxy_logs/index.ts index 977decd413f08..ac2db66dff6b6 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/envoyproxy_logs/index.js +++ b/src/plugins/home/server/tutorials/envoyproxy_logs/index.ts @@ -18,26 +18,30 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/filebeat_instructions'; +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function envoyproxyLogsSpecProvider(context) { +export function envoyproxyLogsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'envoyproxy'; - const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS']; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; return { id: 'envoyproxyLogs', - name: i18n.translate('kbn.server.tutorials.envoyproxyLogs.nameTitle', { + name: i18n.translate('home.tutorials.envoyproxyLogs.nameTitle', { defaultMessage: 'Envoyproxy', }), - category: TUTORIAL_CATEGORY.SIEM, - shortDescription: i18n.translate('kbn.server.tutorials.envoyproxyLogs.shortDescription', { + category: TutorialsCategory.SIEM, + shortDescription: i18n.translate('home.tutorials.envoyproxyLogs.shortDescription', { defaultMessage: 'Collect and parse logs received from the Envoy proxy.', }), - longDescription: i18n.translate('kbn.server.tutorials.envoyproxyLogs.longDescription', { + longDescription: i18n.translate('home.tutorials.envoyproxyLogs.longDescription', { defaultMessage: 'This is a filebeat module for [Envoy proxy access log](https://www.envoyproxy.io/docs/envoy/v1.10.0/configuration/access_log). \ It supports both standalone deployment and Envoy proxy deployment in Kubernetes. \ @@ -46,17 +50,14 @@ It supports both standalone deployment and Envoy proxy deployment in Kubernetes. learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-envoyproxy.html', }, }), - //euiIconType: 'logoCisco', + // euiIconType: 'logoCisco', artifacts: { dashboards: [], application: { path: '/app/siem', - label: i18n.translate( - 'kbn.server.tutorials.envoyproxyLogs.artifacts.dashboards.linkLabel', - { - defaultMessage: 'SIEM App', - } - ), + label: i18n.translate('home.tutorials.envoyproxyLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'SIEM App', + }), }, exportedFields: { documentationUrl: '{config.docs.beats.filebeat}/exported-fields-envoyproxy.html', diff --git a/src/legacy/core_plugins/kibana/server/tutorials/envoyproxy_metrics/index.js b/src/plugins/home/server/tutorials/envoyproxy_metrics/index.ts similarity index 74% rename from src/legacy/core_plugins/kibana/server/tutorials/envoyproxy_metrics/index.js rename to src/plugins/home/server/tutorials/envoyproxy_metrics/index.ts index 4e5301149b35b..d405e77918546 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/envoyproxy_metrics/index.js +++ b/src/plugins/home/server/tutorials/envoyproxy_metrics/index.ts @@ -18,25 +18,29 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/metricbeat_instructions'; +} from '../instructions/metricbeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function envoyproxyMetricsSpecProvider(server, context) { +export function envoyproxyMetricsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'envoyproxy'; return { id: 'envoyproxyMetrics', - name: i18n.translate('kbn.server.tutorials.envoyproxyMetrics.nameTitle', { + name: i18n.translate('home.tutorials.envoyproxyMetrics.nameTitle', { defaultMessage: 'Envoy Proxy metrics', }), - category: TUTORIAL_CATEGORY.METRICS, - shortDescription: i18n.translate('kbn.server.tutorials.envoyproxyMetrics.shortDescription', { + category: TutorialsCategory.METRICS, + shortDescription: i18n.translate('home.tutorials.envoyproxyMetrics.shortDescription', { defaultMessage: 'Fetch monitoring metrics from Envoy Proxy.', }), - longDescription: i18n.translate('kbn.server.tutorials.envoyproxyMetrics.longDescription', { + longDescription: i18n.translate('home.tutorials.envoyproxyMetrics.longDescription', { defaultMessage: 'The `envoyproxy` Metricbeat module fetches monitoring metrics from Envoy Proxy. \ [Learn more]({learnMoreLink}).', @@ -53,7 +57,7 @@ export function envoyproxyMetricsSpecProvider(server, context) { }, completionTimeMinutes: 10, // previewImagePath: '/plugins/kibana/home/tutorial_resources/envoyproxy_metrics/screenshot.png', - onPrem: onPremInstructions(moduleName, null, null, null, context), + onPrem: onPremInstructions(moduleName, context), elasticCloud: cloudInstructions(moduleName), onPremElasticCloud: onPremCloudInstructions(moduleName), }; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/etcd_metrics/index.js b/src/plugins/home/server/tutorials/etcd_metrics/index.ts similarity index 71% rename from src/legacy/core_plugins/kibana/server/tutorials/etcd_metrics/index.js rename to src/plugins/home/server/tutorials/etcd_metrics/index.ts index fdd0956bb25bb..919700356d98a 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/etcd_metrics/index.js +++ b/src/plugins/home/server/tutorials/etcd_metrics/index.ts @@ -18,26 +18,30 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/metricbeat_instructions'; +} from '../instructions/metricbeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function etcdMetricsSpecProvider(context) { +export function etcdMetricsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'etcd'; return { id: 'etcdMetrics', - name: i18n.translate('kbn.server.tutorials.etcdMetrics.nameTitle', { + name: i18n.translate('home.tutorials.etcdMetrics.nameTitle', { defaultMessage: 'Etcd metrics', }), isBeta: false, - category: TUTORIAL_CATEGORY.METRICS, - shortDescription: i18n.translate('kbn.server.tutorials.etcdMetrics.shortDescription', { + category: TutorialsCategory.METRICS, + shortDescription: i18n.translate('home.tutorials.etcdMetrics.shortDescription', { defaultMessage: 'Fetch internal metrics from the Etcd server.', }), - longDescription: i18n.translate('kbn.server.tutorials.etcdMetrics.longDescription', { + longDescription: i18n.translate('home.tutorials.etcdMetrics.longDescription', { defaultMessage: 'The `etcd` Metricbeat module fetches internal metrics from Etcd. \ [Learn more]({learnMoreLink}).', @@ -48,7 +52,7 @@ export function etcdMetricsSpecProvider(context) { euiIconType: 'logoEtcd', artifacts: { application: { - label: i18n.translate('kbn.server.tutorials.etcdMetrics.artifacts.application.label', { + label: i18n.translate('home.tutorials.etcdMetrics.artifacts.application.label', { defaultMessage: 'Discover', }), path: '/app/kibana#/discover', @@ -59,7 +63,7 @@ export function etcdMetricsSpecProvider(context) { }, }, completionTimeMinutes: 10, - onPrem: onPremInstructions(moduleName, null, null, null, context), + onPrem: onPremInstructions(moduleName, context), elasticCloud: cloudInstructions(moduleName), onPremElasticCloud: onPremCloudInstructions(moduleName), }; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/golang_metrics/index.js b/src/plugins/home/server/tutorials/golang_metrics/index.ts similarity index 69% rename from src/legacy/core_plugins/kibana/server/tutorials/golang_metrics/index.js rename to src/plugins/home/server/tutorials/golang_metrics/index.ts index 7eb6e340c863d..c53f8b2bba281 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/golang_metrics/index.js +++ b/src/plugins/home/server/tutorials/golang_metrics/index.ts @@ -18,26 +18,30 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/metricbeat_instructions'; +} from '../instructions/metricbeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function golangMetricsSpecProvider(context) { +export function golangMetricsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'golang'; return { id: moduleName + 'Metrics', - name: i18n.translate('kbn.server.tutorials.golangMetrics.nameTitle', { + name: i18n.translate('home.tutorials.golangMetrics.nameTitle', { defaultMessage: 'Golang metrics', }), isBeta: true, - category: TUTORIAL_CATEGORY.METRICS, - shortDescription: i18n.translate('kbn.server.tutorials.golangMetrics.shortDescription', { + category: TutorialsCategory.METRICS, + shortDescription: i18n.translate('home.tutorials.golangMetrics.shortDescription', { defaultMessage: 'Fetch internal metrics from a Golang app.', }), - longDescription: i18n.translate('kbn.server.tutorials.golangMetrics.longDescription', { + longDescription: i18n.translate('home.tutorials.golangMetrics.longDescription', { defaultMessage: 'The `{moduleName}` Metricbeat module fetches internal metrics from a Golang app. \ [Learn more]({learnMoreLink}).', @@ -51,12 +55,9 @@ export function golangMetricsSpecProvider(context) { dashboards: [ { id: 'f2dc7320-f519-11e6-a3c9-9d1f7c42b045-ecs', - linkLabel: i18n.translate( - 'kbn.server.tutorials.golangMetrics.artifacts.dashboards.linkLabel', - { - defaultMessage: 'Golang metrics dashboard', - } - ), + linkLabel: i18n.translate('home.tutorials.golangMetrics.artifacts.dashboards.linkLabel', { + defaultMessage: 'Golang metrics dashboard', + }), isOverview: true, }, ], @@ -65,7 +66,7 @@ export function golangMetricsSpecProvider(context) { }, }, completionTimeMinutes: 10, - onPrem: onPremInstructions(moduleName, null, null, null, context), + onPrem: onPremInstructions(moduleName, context), elasticCloud: cloudInstructions(moduleName), onPremElasticCloud: onPremCloudInstructions(moduleName), }; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/haproxy_metrics/index.js b/src/plugins/home/server/tutorials/haproxy_metrics/index.ts similarity index 71% rename from src/legacy/core_plugins/kibana/server/tutorials/haproxy_metrics/index.js rename to src/plugins/home/server/tutorials/haproxy_metrics/index.ts index 7e2ca4af7fd9c..8e1c0a0da8b18 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/haproxy_metrics/index.js +++ b/src/plugins/home/server/tutorials/haproxy_metrics/index.ts @@ -18,26 +18,30 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/metricbeat_instructions'; +} from '../instructions/metricbeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function haproxyMetricsSpecProvider(context) { +export function haproxyMetricsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'haproxy'; return { id: 'haproxyMetrics', - name: i18n.translate('kbn.server.tutorials.haproxyMetrics.nameTitle', { + name: i18n.translate('home.tutorials.haproxyMetrics.nameTitle', { defaultMessage: 'HAProxy metrics', }), isBeta: false, - category: TUTORIAL_CATEGORY.METRICS, - shortDescription: i18n.translate('kbn.server.tutorials.haproxyMetrics.shortDescription', { + category: TutorialsCategory.METRICS, + shortDescription: i18n.translate('home.tutorials.haproxyMetrics.shortDescription', { defaultMessage: 'Fetch internal metrics from the HAProxy server.', }), - longDescription: i18n.translate('kbn.server.tutorials.haproxyMetrics.longDescription', { + longDescription: i18n.translate('home.tutorials.haproxyMetrics.longDescription', { defaultMessage: 'The `haproxy` Metricbeat module fetches internal metrics from HAProxy. \ [Learn more]({learnMoreLink}).', @@ -48,7 +52,7 @@ export function haproxyMetricsSpecProvider(context) { euiIconType: 'logoHAproxy', artifacts: { application: { - label: i18n.translate('kbn.server.tutorials.haproxyMetrics.artifacts.application.label', { + label: i18n.translate('home.tutorials.haproxyMetrics.artifacts.application.label', { defaultMessage: 'Discover', }), path: '/app/kibana#/discover', @@ -59,7 +63,7 @@ export function haproxyMetricsSpecProvider(context) { }, }, completionTimeMinutes: 10, - onPrem: onPremInstructions(moduleName, null, null, null, context), + onPrem: onPremInstructions(moduleName, context), elasticCloud: cloudInstructions(moduleName), onPremElasticCloud: onPremCloudInstructions(moduleName), }; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/ibmmq_logs/index.js b/src/plugins/home/server/tutorials/ibmmq_logs/index.ts similarity index 71% rename from src/legacy/core_plugins/kibana/server/tutorials/ibmmq_logs/index.js rename to src/plugins/home/server/tutorials/ibmmq_logs/index.ts index 4ffda2d7a523c..9922cb0e6341e 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/ibmmq_logs/index.js +++ b/src/plugins/home/server/tutorials/ibmmq_logs/index.ts @@ -18,26 +18,30 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/filebeat_instructions'; +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function ibmmqLogsSpecProvider(server, context) { +export function ibmmqLogsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'ibmmq'; - const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS']; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; return { id: 'ibmmqLogs', - name: i18n.translate('kbn.server.tutorials.ibmmqLogs.nameTitle', { + name: i18n.translate('home.tutorials.ibmmqLogs.nameTitle', { defaultMessage: 'IBM MQ logs', }), - category: TUTORIAL_CATEGORY.LOGGING, - shortDescription: i18n.translate('kbn.server.tutorials.ibmmqLogs.shortDescription', { + category: TutorialsCategory.LOGGING, + shortDescription: i18n.translate('home.tutorials.ibmmqLogs.shortDescription', { defaultMessage: 'Collect IBM MQ logs with Filebeat.', }), - longDescription: i18n.translate('kbn.server.tutorials.ibmmqLogs.longDescription', { + longDescription: i18n.translate('home.tutorials.ibmmqLogs.longDescription', { defaultMessage: 'Collect IBM MQ logs with Filebeat. \ [Learn more]({learnMoreLink}).', values: { @@ -49,12 +53,9 @@ export function ibmmqLogsSpecProvider(server, context) { dashboards: [ { id: 'ba1d8830-7c7b-11e9-9645-e37efaf5baff', - linkLabel: i18n.translate( - 'kbn.server.tutorials.ibmmqLogs.artifacts.dashboards.linkLabel', - { - defaultMessage: 'IBM MQ Events', - } - ), + linkLabel: i18n.translate('home.tutorials.ibmmqLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'IBM MQ Events', + }), isOverview: true, }, ], diff --git a/src/legacy/core_plugins/kibana/server/tutorials/ibmmq_metrics/index.js b/src/plugins/home/server/tutorials/ibmmq_metrics/index.ts similarity index 73% rename from src/legacy/core_plugins/kibana/server/tutorials/ibmmq_metrics/index.js rename to src/plugins/home/server/tutorials/ibmmq_metrics/index.ts index b2824832dc14c..2055196f833b2 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/ibmmq_metrics/index.js +++ b/src/plugins/home/server/tutorials/ibmmq_metrics/index.ts @@ -18,25 +18,29 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/metricbeat_instructions'; +} from '../instructions/metricbeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function ibmmqMetricsSpecProvider(context) { +export function ibmmqMetricsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'ibmmq'; return { id: 'ibmmqMetrics', - name: i18n.translate('kbn.server.tutorials.ibmmqMetrics.nameTitle', { + name: i18n.translate('home.tutorials.ibmmqMetrics.nameTitle', { defaultMessage: 'IBM MQ metrics', }), - category: TUTORIAL_CATEGORY.METRICS, - shortDescription: i18n.translate('kbn.server.tutorials.ibmmqMetrics.shortDescription', { + category: TutorialsCategory.METRICS, + shortDescription: i18n.translate('home.tutorials.ibmmqMetrics.shortDescription', { defaultMessage: 'Fetch monitoring metrics from IBM MQ instances.', }), - longDescription: i18n.translate('kbn.server.tutorials.ibmmqMetrics.longDescription', { + longDescription: i18n.translate('home.tutorials.ibmmqMetrics.longDescription', { defaultMessage: 'The `ibmmq` Metricbeat module fetches monitoring metrics from IBM MQ instances \ [Learn more]({learnMoreLink}).', @@ -48,7 +52,7 @@ export function ibmmqMetricsSpecProvider(context) { isBeta: true, artifacts: { application: { - label: i18n.translate('kbn.server.tutorials.ibmmqMetrics.artifacts.application.label', { + label: i18n.translate('home.tutorials.ibmmqMetrics.artifacts.application.label', { defaultMessage: 'Discover', }), path: '/app/kibana#/discover', @@ -60,7 +64,7 @@ export function ibmmqMetricsSpecProvider(context) { }, completionTimeMinutes: 10, previewImagePath: '/plugins/kibana/home/tutorial_resources/ibmmq_metrics/screenshot.png', - onPrem: onPremInstructions(moduleName, null, null, null, context), + onPrem: onPremInstructions(moduleName, context), elasticCloud: cloudInstructions(moduleName), onPremElasticCloud: onPremCloudInstructions(moduleName), }; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/iis_logs/index.js b/src/plugins/home/server/tutorials/iis_logs/index.ts similarity index 74% rename from src/legacy/core_plugins/kibana/server/tutorials/iis_logs/index.js rename to src/plugins/home/server/tutorials/iis_logs/index.ts index d2a5b5f113d29..ff9996c1b0187 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/iis_logs/index.js +++ b/src/plugins/home/server/tutorials/iis_logs/index.ts @@ -18,26 +18,30 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/filebeat_instructions'; +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function iisLogsSpecProvider(context) { +export function iisLogsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'iis'; - const platforms = ['WINDOWS']; + const platforms = ['WINDOWS'] as const; return { id: 'iisLogs', - name: i18n.translate('kbn.server.tutorials.iisLogs.nameTitle', { + name: i18n.translate('home.tutorials.iisLogs.nameTitle', { defaultMessage: 'IIS logs', }), - category: TUTORIAL_CATEGORY.LOGGING, - shortDescription: i18n.translate('kbn.server.tutorials.iisLogs.shortDescription', { + category: TutorialsCategory.LOGGING, + shortDescription: i18n.translate('home.tutorials.iisLogs.shortDescription', { defaultMessage: 'Collect and parse access and error logs created by the IIS HTTP server.', }), - longDescription: i18n.translate('kbn.server.tutorials.iisLogs.longDescription', { + longDescription: i18n.translate('home.tutorials.iisLogs.longDescription', { defaultMessage: 'The `iis` Filebeat module parses access and error logs created by the IIS HTTP server. \ [Learn more]({learnMoreLink}).', @@ -50,7 +54,7 @@ export function iisLogsSpecProvider(context) { dashboards: [ { id: '4278ad30-fe16-11e7-a3b0-d13028918f9f-ecs', - linkLabel: i18n.translate('kbn.server.tutorials.iisLogs.artifacts.dashboards.linkLabel', { + linkLabel: i18n.translate('home.tutorials.iisLogs.artifacts.dashboards.linkLabel', { defaultMessage: 'IIS logs dashboard', }), isOverview: true, diff --git a/src/legacy/core_plugins/kibana/common/tutorials/auditbeat_instructions.js b/src/plugins/home/server/tutorials/instructions/auditbeat_instructions.ts similarity index 67% rename from src/legacy/core_plugins/kibana/common/tutorials/auditbeat_instructions.js rename to src/plugins/home/server/tutorials/instructions/auditbeat_instructions.ts index 3d95738ab21f7..6a9dba69b193f 100644 --- a/src/legacy/core_plugins/kibana/common/tutorials/auditbeat_instructions.js +++ b/src/plugins/home/server/tutorials/instructions/auditbeat_instructions.ts @@ -20,15 +20,16 @@ import { i18n } from '@kbn/i18n'; import { INSTRUCTION_VARIANT } from './instruction_variant'; import { createTrycloudOption1, createTrycloudOption2 } from './onprem_cloud_instructions'; -import { getSpaceIdForBeatsTutorial } from '../lib/get_space_id_for_beats_tutorial'; +import { getSpaceIdForBeatsTutorial } from './get_space_id_for_beats_tutorial'; +import { Platform, TutorialContext } from '../../services/tutorials/lib/tutorials_registry_types'; -export const createAuditbeatInstructions = context => ({ +export const createAuditbeatInstructions = (context?: TutorialContext) => ({ INSTALL: { OSX: { - title: i18n.translate('kbn.common.tutorials.auditbeatInstructions.install.osxTitle', { + title: i18n.translate('home.tutorials.common.auditbeatInstructions.install.osxTitle', { defaultMessage: 'Download and install Auditbeat', }), - textPre: i18n.translate('kbn.common.tutorials.auditbeatInstructions.install.osxTextPre', { + textPre: i18n.translate('home.tutorials.common.auditbeatInstructions.install.osxTextPre', { defaultMessage: 'First time using Auditbeat? See the [Getting Started Guide]({linkUrl}).', values: { linkUrl: '{config.docs.beats.auditbeat}/auditbeat-getting-started.html', @@ -41,10 +42,10 @@ export const createAuditbeatInstructions = context => ({ ], }, DEB: { - title: i18n.translate('kbn.common.tutorials.auditbeatInstructions.install.debTitle', { + title: i18n.translate('home.tutorials.common.auditbeatInstructions.install.debTitle', { defaultMessage: 'Download and install Auditbeat', }), - textPre: i18n.translate('kbn.common.tutorials.auditbeatInstructions.install.debTextPre', { + textPre: i18n.translate('home.tutorials.common.auditbeatInstructions.install.debTextPre', { defaultMessage: 'First time using Auditbeat? See the [Getting Started Guide]({linkUrl}).', values: { linkUrl: '{config.docs.beats.auditbeat}/auditbeat-getting-started.html', @@ -54,7 +55,7 @@ export const createAuditbeatInstructions = context => ({ 'curl -L -O https://artifacts.elastic.co/downloads/beats/auditbeat/auditbeat-{config.kibana.version}-amd64.deb', 'sudo dpkg -i auditbeat-{config.kibana.version}-amd64.deb', ], - textPost: i18n.translate('kbn.common.tutorials.auditbeatInstructions.install.debTextPost', { + textPost: i18n.translate('home.tutorials.common.auditbeatInstructions.install.debTextPost', { defaultMessage: 'Looking for the 32-bit packages? See the [Download page]({linkUrl}).', values: { linkUrl: 'https://www.elastic.co/downloads/beats/auditbeat', @@ -62,10 +63,10 @@ export const createAuditbeatInstructions = context => ({ }), }, RPM: { - title: i18n.translate('kbn.common.tutorials.auditbeatInstructions.install.rpmTitle', { + title: i18n.translate('home.tutorials.common.auditbeatInstructions.install.rpmTitle', { defaultMessage: 'Download and install Auditbeat', }), - textPre: i18n.translate('kbn.common.tutorials.auditbeatInstructions.install.rpmTextPre', { + textPre: i18n.translate('home.tutorials.common.auditbeatInstructions.install.rpmTextPre', { defaultMessage: 'First time using Auditbeat? See the [Getting Started Guide]({linkUrl}).', values: { linkUrl: '{config.docs.beats.auditbeat}/auditbeat-getting-started.html', @@ -75,7 +76,7 @@ export const createAuditbeatInstructions = context => ({ 'curl -L -O https://artifacts.elastic.co/downloads/beats/auditbeat/auditbeat-{config.kibana.version}-x86_64.rpm', 'sudo rpm -vi auditbeat-{config.kibana.version}-x86_64.rpm', ], - textPost: i18n.translate('kbn.common.tutorials.auditbeatInstructions.install.rpmTextPost', { + textPost: i18n.translate('home.tutorials.common.auditbeatInstructions.install.rpmTextPost', { defaultMessage: 'Looking for the 32-bit packages? See the [Download page]({linkUrl}).', values: { linkUrl: 'https://www.elastic.co/downloads/beats/auditbeat', @@ -83,28 +84,31 @@ export const createAuditbeatInstructions = context => ({ }), }, WINDOWS: { - title: i18n.translate('kbn.common.tutorials.auditbeatInstructions.install.windowsTitle', { + title: i18n.translate('home.tutorials.common.auditbeatInstructions.install.windowsTitle', { defaultMessage: 'Download and install Auditbeat', }), - textPre: i18n.translate('kbn.common.tutorials.auditbeatInstructions.install.windowsTextPre', { - defaultMessage: - 'First time using Auditbeat? See the [Getting Started Guide]({guideLinkUrl}).\n\ + textPre: i18n.translate( + 'home.tutorials.common.auditbeatInstructions.install.windowsTextPre', + { + defaultMessage: + 'First time using Auditbeat? See the [Getting Started Guide]({guideLinkUrl}).\n\ 1. Download the Auditbeat Windows zip file from the [Download]({auditbeatLinkUrl}) page.\n\ 2. Extract the contents of the zip file into {folderPath}.\n\ 3. Rename the `{directoryName}` directory to `Auditbeat`.\n\ 4. Open a PowerShell prompt as an Administrator (right-click the PowerShell icon and select \ **Run As Administrator**). If you are running Windows XP, you might need to download and install PowerShell.\n\ 5. From the PowerShell prompt, run the following commands to install Auditbeat as a Windows service.', - values: { - folderPath: '`C:\\Program Files`', - guideLinkUrl: '{config.docs.beats.auditbeat}/auditbeat-getting-started.html', - auditbeatLinkUrl: 'https://www.elastic.co/downloads/beats/auditbeat', - directoryName: 'auditbeat-{config.kibana.version}-windows', - }, - }), + values: { + folderPath: '`C:\\Program Files`', + guideLinkUrl: '{config.docs.beats.auditbeat}/auditbeat-getting-started.html', + auditbeatLinkUrl: 'https://www.elastic.co/downloads/beats/auditbeat', + directoryName: 'auditbeat-{config.kibana.version}-windows', + }, + } + ), commands: ['cd "C:\\Program Files\\Auditbeat"', '.\\install-service-auditbeat.ps1'], textPost: i18n.translate( - 'kbn.common.tutorials.auditbeatInstructions.install.windowsTextPost', + 'home.tutorials.common.auditbeatInstructions.install.windowsTextPost', { defaultMessage: 'Modify the settings under {propertyName} in the {auditbeatPath} file to point to your Elasticsearch installation.', @@ -118,40 +122,40 @@ export const createAuditbeatInstructions = context => ({ }, START: { OSX: { - title: i18n.translate('kbn.common.tutorials.auditbeatInstructions.start.osxTitle', { + title: i18n.translate('home.tutorials.common.auditbeatInstructions.start.osxTitle', { defaultMessage: 'Start Auditbeat', }), - textPre: i18n.translate('kbn.common.tutorials.auditbeatInstructions.start.osxTextPre', { + textPre: i18n.translate('home.tutorials.common.auditbeatInstructions.start.osxTextPre', { defaultMessage: 'The `setup` command loads the Kibana dashboards. If the dashboards are already set up, omit this command.', }), commands: ['./auditbeat setup', './auditbeat -e'], }, DEB: { - title: i18n.translate('kbn.common.tutorials.auditbeatInstructions.start.debTitle', { + title: i18n.translate('home.tutorials.common.auditbeatInstructions.start.debTitle', { defaultMessage: 'Start Auditbeat', }), - textPre: i18n.translate('kbn.common.tutorials.auditbeatInstructions.start.debTextPre', { + textPre: i18n.translate('home.tutorials.common.auditbeatInstructions.start.debTextPre', { defaultMessage: 'The `setup` command loads the Kibana dashboards. If the dashboards are already set up, omit this command.', }), commands: ['sudo auditbeat setup', 'sudo service auditbeat start'], }, RPM: { - title: i18n.translate('kbn.common.tutorials.auditbeatInstructions.start.rpmTitle', { + title: i18n.translate('home.tutorials.common.auditbeatInstructions.start.rpmTitle', { defaultMessage: 'Start Auditbeat', }), - textPre: i18n.translate('kbn.common.tutorials.auditbeatInstructions.start.rpmTextPre', { + textPre: i18n.translate('home.tutorials.common.auditbeatInstructions.start.rpmTextPre', { defaultMessage: 'The `setup` command loads the Kibana dashboards. If the dashboards are already set up, omit this command.', }), commands: ['sudo auditbeat setup', 'sudo service auditbeat start'], }, WINDOWS: { - title: i18n.translate('kbn.common.tutorials.auditbeatInstructions.start.windowsTitle', { + title: i18n.translate('home.tutorials.common.auditbeatInstructions.start.windowsTitle', { defaultMessage: 'Start Auditbeat', }), - textPre: i18n.translate('kbn.common.tutorials.auditbeatInstructions.start.windowsTextPre', { + textPre: i18n.translate('home.tutorials.common.auditbeatInstructions.start.windowsTextPre', { defaultMessage: 'The `setup` command loads the Kibana dashboards. If the dashboards are already set up, omit this command.', }), @@ -160,10 +164,10 @@ export const createAuditbeatInstructions = context => ({ }, CONFIG: { OSX: { - title: i18n.translate('kbn.common.tutorials.auditbeatInstructions.config.osxTitle', { + title: i18n.translate('home.tutorials.common.auditbeatInstructions.config.osxTitle', { defaultMessage: 'Edit the configuration', }), - textPre: i18n.translate('kbn.common.tutorials.auditbeatInstructions.config.osxTextPre', { + textPre: i18n.translate('home.tutorials.common.auditbeatInstructions.config.osxTextPre', { defaultMessage: 'Modify {path} to set the connection information:', values: { path: '`auditbeat.yml`', @@ -178,7 +182,7 @@ export const createAuditbeatInstructions = context => ({ ' host: ""', getSpaceIdForBeatsTutorial(context), ], - textPost: i18n.translate('kbn.common.tutorials.auditbeatInstructions.config.osxTextPost', { + textPost: i18n.translate('home.tutorials.common.auditbeatInstructions.config.osxTextPost', { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of Elasticsearch, \ and {kibanaUrlTemplate} is the URL of Kibana.', @@ -190,10 +194,10 @@ and {kibanaUrlTemplate} is the URL of Kibana.', }), }, DEB: { - title: i18n.translate('kbn.common.tutorials.auditbeatInstructions.config.debTitle', { + title: i18n.translate('home.tutorials.common.auditbeatInstructions.config.debTitle', { defaultMessage: 'Edit the configuration', }), - textPre: i18n.translate('kbn.common.tutorials.auditbeatInstructions.config.debTextPre', { + textPre: i18n.translate('home.tutorials.common.auditbeatInstructions.config.debTextPre', { defaultMessage: 'Modify {path} to set the connection information:', values: { path: '`/etc/auditbeat/auditbeat.yml`', @@ -208,7 +212,7 @@ and {kibanaUrlTemplate} is the URL of Kibana.', ' host: ""', getSpaceIdForBeatsTutorial(context), ], - textPost: i18n.translate('kbn.common.tutorials.auditbeatInstructions.config.debTextPost', { + textPost: i18n.translate('home.tutorials.common.auditbeatInstructions.config.debTextPost', { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of Elasticsearch, \ and {kibanaUrlTemplate} is the URL of Kibana.', @@ -220,10 +224,10 @@ and {kibanaUrlTemplate} is the URL of Kibana.', }), }, RPM: { - title: i18n.translate('kbn.common.tutorials.auditbeatInstructions.config.rpmTitle', { + title: i18n.translate('home.tutorials.common.auditbeatInstructions.config.rpmTitle', { defaultMessage: 'Edit the configuration', }), - textPre: i18n.translate('kbn.common.tutorials.auditbeatInstructions.config.rpmTextPre', { + textPre: i18n.translate('home.tutorials.common.auditbeatInstructions.config.rpmTextPre', { defaultMessage: 'Modify {path} to set the connection information:', values: { path: '`/etc/auditbeat/auditbeat.yml`', @@ -238,7 +242,7 @@ and {kibanaUrlTemplate} is the URL of Kibana.', ' host: ""', getSpaceIdForBeatsTutorial(context), ], - textPost: i18n.translate('kbn.common.tutorials.auditbeatInstructions.config.rpmTextPost', { + textPost: i18n.translate('home.tutorials.common.auditbeatInstructions.config.rpmTextPost', { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of Elasticsearch, \ and {kibanaUrlTemplate} is the URL of Kibana.', @@ -250,10 +254,10 @@ and {kibanaUrlTemplate} is the URL of Kibana.', }), }, WINDOWS: { - title: i18n.translate('kbn.common.tutorials.auditbeatInstructions.config.windowsTitle', { + title: i18n.translate('home.tutorials.common.auditbeatInstructions.config.windowsTitle', { defaultMessage: 'Edit the configuration', }), - textPre: i18n.translate('kbn.common.tutorials.auditbeatInstructions.config.windowsTextPre', { + textPre: i18n.translate('home.tutorials.common.auditbeatInstructions.config.windowsTextPre', { defaultMessage: 'Modify {path} to set the connection information:', values: { path: '`C:\\Program Files\\Auditbeat\\auditbeat.yml`', @@ -269,7 +273,7 @@ and {kibanaUrlTemplate} is the URL of Kibana.', getSpaceIdForBeatsTutorial(context), ], textPost: i18n.translate( - 'kbn.common.tutorials.auditbeatInstructions.config.windowsTextPost', + 'home.tutorials.common.auditbeatInstructions.config.windowsTextPost', { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of Elasticsearch, \ @@ -288,18 +292,21 @@ and {kibanaUrlTemplate} is the URL of Kibana.', export const createAuditbeatCloudInstructions = () => ({ CONFIG: { OSX: { - title: i18n.translate('kbn.common.tutorials.auditbeatCloudInstructions.config.osxTitle', { + title: i18n.translate('home.tutorials.common.auditbeatCloudInstructions.config.osxTitle', { defaultMessage: 'Edit the configuration', }), - textPre: i18n.translate('kbn.common.tutorials.auditbeatCloudInstructions.config.osxTextPre', { - defaultMessage: 'Modify {path} to set the connection information for Elastic Cloud:', - values: { - path: '`auditbeat.yml`', - }, - }), + textPre: i18n.translate( + 'home.tutorials.common.auditbeatCloudInstructions.config.osxTextPre', + { + defaultMessage: 'Modify {path} to set the connection information for Elastic Cloud:', + values: { + path: '`auditbeat.yml`', + }, + } + ), commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'], textPost: i18n.translate( - 'kbn.common.tutorials.auditbeatCloudInstructions.config.osxTextPost', + 'home.tutorials.common.auditbeatCloudInstructions.config.osxTextPost', { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.', values: { passwordTemplate: '``' }, @@ -307,18 +314,21 @@ export const createAuditbeatCloudInstructions = () => ({ ), }, DEB: { - title: i18n.translate('kbn.common.tutorials.auditbeatCloudInstructions.config.debTitle', { + title: i18n.translate('home.tutorials.common.auditbeatCloudInstructions.config.debTitle', { defaultMessage: 'Edit the configuration', }), - textPre: i18n.translate('kbn.common.tutorials.auditbeatCloudInstructions.config.debTextPre', { - defaultMessage: 'Modify {path} to set the connection information for Elastic Cloud:', - values: { - path: '`/etc/auditbeat/auditbeat.yml`', - }, - }), + textPre: i18n.translate( + 'home.tutorials.common.auditbeatCloudInstructions.config.debTextPre', + { + defaultMessage: 'Modify {path} to set the connection information for Elastic Cloud:', + values: { + path: '`/etc/auditbeat/auditbeat.yml`', + }, + } + ), commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'], textPost: i18n.translate( - 'kbn.common.tutorials.auditbeatCloudInstructions.config.debTextPost', + 'home.tutorials.common.auditbeatCloudInstructions.config.debTextPost', { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.', values: { passwordTemplate: '``' }, @@ -326,18 +336,21 @@ export const createAuditbeatCloudInstructions = () => ({ ), }, RPM: { - title: i18n.translate('kbn.common.tutorials.auditbeatCloudInstructions.config.rpmTitle', { + title: i18n.translate('home.tutorials.common.auditbeatCloudInstructions.config.rpmTitle', { defaultMessage: 'Edit the configuration', }), - textPre: i18n.translate('kbn.common.tutorials.auditbeatCloudInstructions.config.rpmTextPre', { - defaultMessage: 'Modify {path} to set the connection information for Elastic Cloud:', - values: { - path: '`/etc/auditbeat/auditbeat.yml`', - }, - }), + textPre: i18n.translate( + 'home.tutorials.common.auditbeatCloudInstructions.config.rpmTextPre', + { + defaultMessage: 'Modify {path} to set the connection information for Elastic Cloud:', + values: { + path: '`/etc/auditbeat/auditbeat.yml`', + }, + } + ), commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'], textPost: i18n.translate( - 'kbn.common.tutorials.auditbeatCloudInstructions.config.rpmTextPost', + 'home.tutorials.common.auditbeatCloudInstructions.config.rpmTextPost', { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.', values: { passwordTemplate: '``' }, @@ -345,11 +358,14 @@ export const createAuditbeatCloudInstructions = () => ({ ), }, WINDOWS: { - title: i18n.translate('kbn.common.tutorials.auditbeatCloudInstructions.config.windowsTitle', { - defaultMessage: 'Edit the configuration', - }), + title: i18n.translate( + 'home.tutorials.common.auditbeatCloudInstructions.config.windowsTitle', + { + defaultMessage: 'Edit the configuration', + } + ), textPre: i18n.translate( - 'kbn.common.tutorials.auditbeatCloudInstructions.config.windowsTextPre', + 'home.tutorials.common.auditbeatCloudInstructions.config.windowsTextPre', { defaultMessage: 'Modify {path} to set the connection information for Elastic Cloud:', values: { @@ -359,7 +375,7 @@ export const createAuditbeatCloudInstructions = () => ({ ), commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'], textPost: i18n.translate( - 'kbn.common.tutorials.auditbeatCloudInstructions.config.windowsTextPost', + 'home.tutorials.common.auditbeatCloudInstructions.config.windowsTextPost', { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.', values: { passwordTemplate: '``' }, @@ -371,19 +387,19 @@ export const createAuditbeatCloudInstructions = () => ({ export function auditbeatStatusCheck() { return { - title: i18n.translate('kbn.common.tutorials.auditbeatStatusCheck.title', { + title: i18n.translate('home.tutorials.common.auditbeatStatusCheck.title', { defaultMessage: 'Status', }), - text: i18n.translate('kbn.common.tutorials.auditbeatStatusCheck.text', { + text: i18n.translate('home.tutorials.common.auditbeatStatusCheck.text', { defaultMessage: 'Check that data is received from Auditbeat', }), - btnLabel: i18n.translate('kbn.common.tutorials.auditbeatStatusCheck.buttonLabel', { + btnLabel: i18n.translate('home.tutorials.common.auditbeatStatusCheck.buttonLabel', { defaultMessage: 'Check data', }), - success: i18n.translate('kbn.common.tutorials.auditbeatStatusCheck.successText', { + success: i18n.translate('home.tutorials.common.auditbeatStatusCheck.successText', { defaultMessage: 'Data successfully received', }), - error: i18n.translate('kbn.common.tutorials.auditbeatStatusCheck.errorText', { + error: i18n.translate('home.tutorials.common.auditbeatStatusCheck.errorText', { defaultMessage: 'No data has been received yet', }), esHitsCheck: { @@ -401,7 +417,7 @@ export function auditbeatStatusCheck() { }; } -export function onPremInstructions(platforms, context) { +export function onPremInstructions(platforms: readonly Platform[], context?: TutorialContext) { const AUDITBEAT_INSTRUCTIONS = createAuditbeatInstructions(context); const variants = []; @@ -413,14 +429,14 @@ export function onPremInstructions(platforms, context) { instructions.push(AUDITBEAT_INSTRUCTIONS.START[platform]); variants.push({ id: INSTRUCTION_VARIANT[platform], - instructions: instructions, + instructions, }); } return { instructionSets: [ { title: i18n.translate( - 'kbn.common.tutorials.auditbeat.premInstructions.gettingStarted.title', + 'home.tutorials.common.auditbeat.premInstructions.gettingStarted.title', { defaultMessage: 'Getting Started', } @@ -432,7 +448,7 @@ export function onPremInstructions(platforms, context) { }; } -export function onPremCloudInstructions(platforms) { +export function onPremCloudInstructions(platforms: readonly Platform[]) { const AUDITBEAT_INSTRUCTIONS = createAuditbeatInstructions(); const TRYCLOUD_OPTION1 = createTrycloudOption1(); const TRYCLOUD_OPTION2 = createTrycloudOption2(); @@ -456,7 +472,7 @@ export function onPremCloudInstructions(platforms) { instructionSets: [ { title: i18n.translate( - 'kbn.common.tutorials.auditbeat.premCloudInstructions.gettingStarted.title', + 'home.tutorials.common.auditbeat.premCloudInstructions.gettingStarted.title', { defaultMessage: 'Getting Started', } @@ -468,7 +484,7 @@ export function onPremCloudInstructions(platforms) { }; } -export function cloudInstructions(platforms) { +export function cloudInstructions(platforms: readonly Platform[]) { const AUDITBEAT_INSTRUCTIONS = createAuditbeatInstructions(); const AUDITBEAT_CLOUD_INSTRUCTIONS = createAuditbeatCloudInstructions(); @@ -489,7 +505,7 @@ export function cloudInstructions(platforms) { instructionSets: [ { title: i18n.translate( - 'kbn.common.tutorials.auditbeat.cloudInstructions.gettingStarted.title', + 'home.tutorials.common.auditbeat.cloudInstructions.gettingStarted.title', { defaultMessage: 'Getting Started', } diff --git a/src/legacy/core_plugins/kibana/common/tutorials/filebeat_instructions.js b/src/plugins/home/server/tutorials/instructions/filebeat_instructions.ts similarity index 70% rename from src/legacy/core_plugins/kibana/common/tutorials/filebeat_instructions.js rename to src/plugins/home/server/tutorials/instructions/filebeat_instructions.ts index 2180a489a449d..176a3901821f1 100644 --- a/src/legacy/core_plugins/kibana/common/tutorials/filebeat_instructions.js +++ b/src/plugins/home/server/tutorials/instructions/filebeat_instructions.ts @@ -20,15 +20,16 @@ import { i18n } from '@kbn/i18n'; import { INSTRUCTION_VARIANT } from './instruction_variant'; import { createTrycloudOption1, createTrycloudOption2 } from './onprem_cloud_instructions'; -import { getSpaceIdForBeatsTutorial } from '../lib/get_space_id_for_beats_tutorial'; +import { getSpaceIdForBeatsTutorial } from './get_space_id_for_beats_tutorial'; +import { Platform, TutorialContext } from '../../services/tutorials/lib/tutorials_registry_types'; -export const createFilebeatInstructions = context => ({ +export const createFilebeatInstructions = (context?: TutorialContext) => ({ INSTALL: { OSX: { - title: i18n.translate('kbn.common.tutorials.filebeatInstructions.install.osxTitle', { + title: i18n.translate('home.tutorials.common.filebeatInstructions.install.osxTitle', { defaultMessage: 'Download and install Filebeat', }), - textPre: i18n.translate('kbn.common.tutorials.filebeatInstructions.install.osxTextPre', { + textPre: i18n.translate('home.tutorials.common.filebeatInstructions.install.osxTextPre', { defaultMessage: 'First time using Filebeat? See the [Getting Started Guide]({linkUrl}).', values: { linkUrl: '{config.docs.beats.filebeat}/filebeat-getting-started.html', @@ -41,10 +42,10 @@ export const createFilebeatInstructions = context => ({ ], }, DEB: { - title: i18n.translate('kbn.common.tutorials.filebeatInstructions.install.debTitle', { + title: i18n.translate('home.tutorials.common.filebeatInstructions.install.debTitle', { defaultMessage: 'Download and install Filebeat', }), - textPre: i18n.translate('kbn.common.tutorials.filebeatInstructions.install.debTextPre', { + textPre: i18n.translate('home.tutorials.common.filebeatInstructions.install.debTextPre', { defaultMessage: 'First time using Filebeat? See the [Getting Started Guide]({linkUrl}).', values: { linkUrl: '{config.docs.beats.filebeat}/filebeat-getting-started.html', @@ -54,7 +55,7 @@ export const createFilebeatInstructions = context => ({ 'curl -L -O https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-{config.kibana.version}-amd64.deb', 'sudo dpkg -i filebeat-{config.kibana.version}-amd64.deb', ], - textPost: i18n.translate('kbn.common.tutorials.filebeatInstructions.install.debTextPost', { + textPost: i18n.translate('home.tutorials.common.filebeatInstructions.install.debTextPost', { defaultMessage: 'Looking for the 32-bit packages? See the [Download page]({linkUrl}).', values: { linkUrl: 'https://www.elastic.co/downloads/beats/filebeat', @@ -62,10 +63,10 @@ export const createFilebeatInstructions = context => ({ }), }, RPM: { - title: i18n.translate('kbn.common.tutorials.filebeatInstructions.install.rpmTitle', { + title: i18n.translate('home.tutorials.common.filebeatInstructions.install.rpmTitle', { defaultMessage: 'Download and install Filebeat', }), - textPre: i18n.translate('kbn.common.tutorials.filebeatInstructions.install.rpmTextPre', { + textPre: i18n.translate('home.tutorials.common.filebeatInstructions.install.rpmTextPre', { defaultMessage: 'First time using Filebeat? See the [Getting Started Guide]({linkUrl}).', values: { linkUrl: '{config.docs.beats.filebeat}/filebeat-getting-started.html', @@ -75,7 +76,7 @@ export const createFilebeatInstructions = context => ({ 'curl -L -O https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-{config.kibana.version}-x86_64.rpm', 'sudo rpm -vi filebeat-{config.kibana.version}-x86_64.rpm', ], - textPost: i18n.translate('kbn.common.tutorials.filebeatInstructions.install.rpmTextPost', { + textPost: i18n.translate('home.tutorials.common.filebeatInstructions.install.rpmTextPost', { defaultMessage: 'Looking for the 32-bit packages? See the [Download page]({linkUrl}).', values: { linkUrl: 'https://www.elastic.co/downloads/beats/filebeat', @@ -83,10 +84,10 @@ export const createFilebeatInstructions = context => ({ }), }, WINDOWS: { - title: i18n.translate('kbn.common.tutorials.filebeatInstructions.install.windowsTitle', { + title: i18n.translate('home.tutorials.common.filebeatInstructions.install.windowsTitle', { defaultMessage: 'Download and install Filebeat', }), - textPre: i18n.translate('kbn.common.tutorials.filebeatInstructions.install.windowsTextPre', { + textPre: i18n.translate('home.tutorials.common.filebeatInstructions.install.windowsTextPre', { defaultMessage: 'First time using Filebeat? See the [Getting Started Guide]({guideLinkUrl}).\n\ 1. Download the Filebeat Windows zip file from the [Download]({filebeatLinkUrl}) page.\n\ @@ -104,7 +105,7 @@ export const createFilebeatInstructions = context => ({ }), commands: ['cd "C:\\Program Files\\Filebeat"', '.\\install-service-filebeat.ps1'], textPost: i18n.translate( - 'kbn.common.tutorials.filebeatInstructions.install.windowsTextPost', + 'home.tutorials.common.filebeatInstructions.install.windowsTextPost', { defaultMessage: 'Modify the settings under {propertyName} in the {filebeatPath} file to point to your Elasticsearch installation.', @@ -118,40 +119,40 @@ export const createFilebeatInstructions = context => ({ }, START: { OSX: { - title: i18n.translate('kbn.common.tutorials.filebeatInstructions.start.osxTitle', { + title: i18n.translate('home.tutorials.common.filebeatInstructions.start.osxTitle', { defaultMessage: 'Start Filebeat', }), - textPre: i18n.translate('kbn.common.tutorials.filebeatInstructions.start.osxTextPre', { + textPre: i18n.translate('home.tutorials.common.filebeatInstructions.start.osxTextPre', { defaultMessage: 'The `setup` command loads the Kibana dashboards. If the dashboards are already set up, omit this command.', }), commands: ['./filebeat setup', './filebeat -e'], }, DEB: { - title: i18n.translate('kbn.common.tutorials.filebeatInstructions.start.debTitle', { + title: i18n.translate('home.tutorials.common.filebeatInstructions.start.debTitle', { defaultMessage: 'Start Filebeat', }), - textPre: i18n.translate('kbn.common.tutorials.filebeatInstructions.start.debTextPre', { + textPre: i18n.translate('home.tutorials.common.filebeatInstructions.start.debTextPre', { defaultMessage: 'The `setup` command loads the Kibana dashboards. If the dashboards are already set up, omit this command.', }), commands: ['sudo filebeat setup', 'sudo service filebeat start'], }, RPM: { - title: i18n.translate('kbn.common.tutorials.filebeatInstructions.start.rpmTitle', { + title: i18n.translate('home.tutorials.common.filebeatInstructions.start.rpmTitle', { defaultMessage: 'Start Filebeat', }), - textPre: i18n.translate('kbn.common.tutorials.filebeatInstructions.start.rpmTextPre', { + textPre: i18n.translate('home.tutorials.common.filebeatInstructions.start.rpmTextPre', { defaultMessage: 'The `setup` command loads the Kibana dashboards. If the dashboards are already set up, omit this command.', }), commands: ['sudo filebeat setup', 'sudo service filebeat start'], }, WINDOWS: { - title: i18n.translate('kbn.common.tutorials.filebeatInstructions.start.windowsTitle', { + title: i18n.translate('home.tutorials.common.filebeatInstructions.start.windowsTitle', { defaultMessage: 'Start Filebeat', }), - textPre: i18n.translate('kbn.common.tutorials.filebeatInstructions.start.windowsTextPre', { + textPre: i18n.translate('home.tutorials.common.filebeatInstructions.start.windowsTextPre', { defaultMessage: 'The `setup` command loads the Kibana dashboards. If the dashboards are already set up, omit this command.', }), @@ -160,10 +161,10 @@ export const createFilebeatInstructions = context => ({ }, CONFIG: { OSX: { - title: i18n.translate('kbn.common.tutorials.filebeatInstructions.config.osxTitle', { + title: i18n.translate('home.tutorials.common.filebeatInstructions.config.osxTitle', { defaultMessage: 'Edit the configuration', }), - textPre: i18n.translate('kbn.common.tutorials.filebeatInstructions.config.osxTextPre', { + textPre: i18n.translate('home.tutorials.common.filebeatInstructions.config.osxTextPre', { defaultMessage: 'Modify {path} to set the connection information:', values: { path: '`filebeat.yml`', @@ -178,7 +179,7 @@ export const createFilebeatInstructions = context => ({ ' host: ""', getSpaceIdForBeatsTutorial(context), ], - textPost: i18n.translate('kbn.common.tutorials.filebeatInstructions.config.osxTextPost', { + textPost: i18n.translate('home.tutorials.common.filebeatInstructions.config.osxTextPost', { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of Elasticsearch, \ and {kibanaUrlTemplate} is the URL of Kibana.', @@ -190,10 +191,10 @@ and {kibanaUrlTemplate} is the URL of Kibana.', }), }, DEB: { - title: i18n.translate('kbn.common.tutorials.filebeatInstructions.config.debTitle', { + title: i18n.translate('home.tutorials.common.filebeatInstructions.config.debTitle', { defaultMessage: 'Edit the configuration', }), - textPre: i18n.translate('kbn.common.tutorials.filebeatInstructions.config.debTextPre', { + textPre: i18n.translate('home.tutorials.common.filebeatInstructions.config.debTextPre', { defaultMessage: 'Modify {path} to set the connection information:', values: { path: '`/etc/filebeat/filebeat.yml`', @@ -208,7 +209,7 @@ and {kibanaUrlTemplate} is the URL of Kibana.', ' host: ""', getSpaceIdForBeatsTutorial(context), ], - textPost: i18n.translate('kbn.common.tutorials.filebeatInstructions.config.debTextPost', { + textPost: i18n.translate('home.tutorials.common.filebeatInstructions.config.debTextPost', { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of Elasticsearch, \ and {kibanaUrlTemplate} is the URL of Kibana.', @@ -220,10 +221,10 @@ and {kibanaUrlTemplate} is the URL of Kibana.', }), }, RPM: { - title: i18n.translate('kbn.common.tutorials.filebeatInstructions.config.rpmTitle', { + title: i18n.translate('home.tutorials.common.filebeatInstructions.config.rpmTitle', { defaultMessage: 'Edit the configuration', }), - textPre: i18n.translate('kbn.common.tutorials.filebeatInstructions.config.rpmTextPre', { + textPre: i18n.translate('home.tutorials.common.filebeatInstructions.config.rpmTextPre', { defaultMessage: 'Modify {path} to set the connection information:', values: { path: '`/etc/filebeat/filebeat.yml`', @@ -238,7 +239,7 @@ and {kibanaUrlTemplate} is the URL of Kibana.', ' host: ""', getSpaceIdForBeatsTutorial(context), ], - textPost: i18n.translate('kbn.common.tutorials.filebeatInstructions.config.rpmTextPost', { + textPost: i18n.translate('home.tutorials.common.filebeatInstructions.config.rpmTextPost', { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of Elasticsearch, \ and {kibanaUrlTemplate} is the URL of Kibana.', @@ -250,10 +251,10 @@ and {kibanaUrlTemplate} is the URL of Kibana.', }), }, WINDOWS: { - title: i18n.translate('kbn.common.tutorials.filebeatInstructions.config.windowsTitle', { + title: i18n.translate('home.tutorials.common.filebeatInstructions.config.windowsTitle', { defaultMessage: 'Edit the configuration', }), - textPre: i18n.translate('kbn.common.tutorials.filebeatInstructions.config.windowsTextPre', { + textPre: i18n.translate('home.tutorials.common.filebeatInstructions.config.windowsTextPre', { defaultMessage: 'Modify {path} to set the connection information:', values: { path: '`C:\\Program Files\\Filebeat\\filebeat.yml`', @@ -268,16 +269,19 @@ and {kibanaUrlTemplate} is the URL of Kibana.', ' host: ""', getSpaceIdForBeatsTutorial(context), ], - textPost: i18n.translate('kbn.common.tutorials.filebeatInstructions.config.windowsTextPost', { - defaultMessage: - 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of Elasticsearch, \ + textPost: i18n.translate( + 'home.tutorials.common.filebeatInstructions.config.windowsTextPost', + { + defaultMessage: + 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of Elasticsearch, \ and {kibanaUrlTemplate} is the URL of Kibana.', - values: { - passwordTemplate: '``', - esUrlTemplate: '``', - kibanaUrlTemplate: '``', - }, - }), + values: { + passwordTemplate: '``', + esUrlTemplate: '``', + kibanaUrlTemplate: '``', + }, + } + ), }, }, }); @@ -285,10 +289,10 @@ and {kibanaUrlTemplate} is the URL of Kibana.', export const createFilebeatCloudInstructions = () => ({ CONFIG: { OSX: { - title: i18n.translate('kbn.common.tutorials.filebeatCloudInstructions.config.osxTitle', { + title: i18n.translate('home.tutorials.common.filebeatCloudInstructions.config.osxTitle', { defaultMessage: 'Edit the configuration', }), - textPre: i18n.translate('kbn.common.tutorials.filebeatCloudInstructions.config.osxTextPre', { + textPre: i18n.translate('home.tutorials.common.filebeatCloudInstructions.config.osxTextPre', { defaultMessage: 'Modify {path} to set the connection information for Elastic Cloud:', values: { path: '`filebeat.yml`', @@ -296,7 +300,7 @@ export const createFilebeatCloudInstructions = () => ({ }), commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'], textPost: i18n.translate( - 'kbn.common.tutorials.filebeatCloudInstructions.config.osxTextPost', + 'home.tutorials.common.filebeatCloudInstructions.config.osxTextPost', { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.', values: { passwordTemplate: '``' }, @@ -304,10 +308,10 @@ export const createFilebeatCloudInstructions = () => ({ ), }, DEB: { - title: i18n.translate('kbn.common.tutorials.filebeatCloudInstructions.config.debTitle', { + title: i18n.translate('home.tutorials.common.filebeatCloudInstructions.config.debTitle', { defaultMessage: 'Edit the configuration', }), - textPre: i18n.translate('kbn.common.tutorials.filebeatCloudInstructions.config.debTextPre', { + textPre: i18n.translate('home.tutorials.common.filebeatCloudInstructions.config.debTextPre', { defaultMessage: 'Modify {path} to set the connection information for Elastic Cloud:', values: { path: '`/etc/filebeat/filebeat.yml`', @@ -315,7 +319,7 @@ export const createFilebeatCloudInstructions = () => ({ }), commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'], textPost: i18n.translate( - 'kbn.common.tutorials.filebeatCloudInstructions.config.debTextPost', + 'home.tutorials.common.filebeatCloudInstructions.config.debTextPost', { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.', values: { passwordTemplate: '``' }, @@ -323,10 +327,10 @@ export const createFilebeatCloudInstructions = () => ({ ), }, RPM: { - title: i18n.translate('kbn.common.tutorials.filebeatCloudInstructions.config.rpmTitle', { + title: i18n.translate('home.tutorials.common.filebeatCloudInstructions.config.rpmTitle', { defaultMessage: 'Edit the configuration', }), - textPre: i18n.translate('kbn.common.tutorials.filebeatCloudInstructions.config.rpmTextPre', { + textPre: i18n.translate('home.tutorials.common.filebeatCloudInstructions.config.rpmTextPre', { defaultMessage: 'Modify {path} to set the connection information for Elastic Cloud:', values: { path: '`/etc/filebeat/filebeat.yml`', @@ -334,7 +338,7 @@ export const createFilebeatCloudInstructions = () => ({ }), commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'], textPost: i18n.translate( - 'kbn.common.tutorials.filebeatCloudInstructions.config.rpmTextPost', + 'home.tutorials.common.filebeatCloudInstructions.config.rpmTextPost', { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.', values: { passwordTemplate: '``' }, @@ -342,11 +346,11 @@ export const createFilebeatCloudInstructions = () => ({ ), }, WINDOWS: { - title: i18n.translate('kbn.common.tutorials.filebeatCloudInstructions.config.windowsTitle', { + title: i18n.translate('home.tutorials.common.filebeatCloudInstructions.config.windowsTitle', { defaultMessage: 'Edit the configuration', }), textPre: i18n.translate( - 'kbn.common.tutorials.filebeatCloudInstructions.config.windowsTextPre', + 'home.tutorials.common.filebeatCloudInstructions.config.windowsTextPre', { defaultMessage: 'Modify {path} to set the connection information for Elastic Cloud:', values: { @@ -356,7 +360,7 @@ export const createFilebeatCloudInstructions = () => ({ ), commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'], textPost: i18n.translate( - 'kbn.common.tutorials.filebeatCloudInstructions.config.windowsTextPost', + 'home.tutorials.common.filebeatCloudInstructions.config.windowsTextPost', { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.', values: { passwordTemplate: '``' }, @@ -366,57 +370,57 @@ export const createFilebeatCloudInstructions = () => ({ }, }); -export function filebeatEnableInstructions(moduleName) { +export function filebeatEnableInstructions(moduleName: string) { return { OSX: { - title: i18n.translate('kbn.common.tutorials.filebeatEnableInstructions.osxTitle', { + title: i18n.translate('home.tutorials.common.filebeatEnableInstructions.osxTitle', { defaultMessage: 'Enable and configure the {moduleName} module', values: { moduleName }, }), - textPre: i18n.translate('kbn.common.tutorials.filebeatEnableInstructions.osxTextPre', { + textPre: i18n.translate('home.tutorials.common.filebeatEnableInstructions.osxTextPre', { defaultMessage: 'From the installation directory, run:', }), commands: ['./filebeat modules enable ' + moduleName], - textPost: i18n.translate('kbn.common.tutorials.filebeatEnableInstructions.osxTextPost', { + textPost: i18n.translate('home.tutorials.common.filebeatEnableInstructions.osxTextPost', { defaultMessage: 'Modify the settings in the `modules.d/{moduleName}.yml` file.', values: { moduleName }, }), }, DEB: { - title: i18n.translate('kbn.common.tutorials.filebeatEnableInstructions.debTitle', { + title: i18n.translate('home.tutorials.common.filebeatEnableInstructions.debTitle', { defaultMessage: 'Enable and configure the {moduleName} module', values: { moduleName }, }), commands: ['sudo filebeat modules enable ' + moduleName], - textPost: i18n.translate('kbn.common.tutorials.filebeatEnableInstructions.debTextPost', { + textPost: i18n.translate('home.tutorials.common.filebeatEnableInstructions.debTextPost', { defaultMessage: 'Modify the settings in the `/etc/filebeat/modules.d/{moduleName}.yml` file.', values: { moduleName }, }), }, RPM: { - title: i18n.translate('kbn.common.tutorials.filebeatEnableInstructions.rpmTitle', { + title: i18n.translate('home.tutorials.common.filebeatEnableInstructions.rpmTitle', { defaultMessage: 'Enable and configure the {moduleName} module', values: { moduleName }, }), commands: ['sudo filebeat modules enable ' + moduleName], - textPost: i18n.translate('kbn.common.tutorials.filebeatEnableInstructions.rpmTextPost', { + textPost: i18n.translate('home.tutorials.common.filebeatEnableInstructions.rpmTextPost', { defaultMessage: 'Modify the settings in the `/etc/filebeat/modules.d/{moduleName}.yml` file.', values: { moduleName }, }), }, WINDOWS: { - title: i18n.translate('kbn.common.tutorials.filebeatEnableInstructions.windowsTitle', { + title: i18n.translate('home.tutorials.common.filebeatEnableInstructions.windowsTitle', { defaultMessage: 'Enable and configure the {moduleName} module', values: { moduleName }, }), - textPre: i18n.translate('kbn.common.tutorials.filebeatEnableInstructions.windowsTextPre', { + textPre: i18n.translate('home.tutorials.common.filebeatEnableInstructions.windowsTextPre', { defaultMessage: 'From the {path} folder, run:', values: { path: `C:\\Program Files\\Filebeat` }, }), commands: ['filebeat.exe modules enable ' + moduleName], - textPost: i18n.translate('kbn.common.tutorials.filebeatEnableInstructions.windowsTextPost', { + textPost: i18n.translate('home.tutorials.common.filebeatEnableInstructions.windowsTextPost', { defaultMessage: 'Modify the settings in the `modules.d/{moduleName}.yml` file.', values: { moduleName }, }), @@ -424,22 +428,22 @@ export function filebeatEnableInstructions(moduleName) { }; } -export function filebeatStatusCheck(moduleName) { +export function filebeatStatusCheck(moduleName: string) { return { - title: i18n.translate('kbn.common.tutorials.filebeatStatusCheck.title', { + title: i18n.translate('home.tutorials.common.filebeatStatusCheck.title', { defaultMessage: 'Module status', }), - text: i18n.translate('kbn.common.tutorials.filebeatStatusCheck.text', { + text: i18n.translate('home.tutorials.common.filebeatStatusCheck.text', { defaultMessage: 'Check that data is received from the Filebeat `{moduleName}` module', values: { moduleName }, }), - btnLabel: i18n.translate('kbn.common.tutorials.filebeatStatusCheck.buttonLabel', { + btnLabel: i18n.translate('home.tutorials.common.filebeatStatusCheck.buttonLabel', { defaultMessage: 'Check data', }), - success: i18n.translate('kbn.common.tutorials.filebeatStatusCheck.successText', { + success: i18n.translate('home.tutorials.common.filebeatStatusCheck.successText', { defaultMessage: 'Data successfully received from this module', }), - error: i18n.translate('kbn.common.tutorials.filebeatStatusCheck.errorText', { + error: i18n.translate('home.tutorials.common.filebeatStatusCheck.errorText', { defaultMessage: 'No data has been received from this module yet', }), esHitsCheck: { @@ -457,7 +461,11 @@ export function filebeatStatusCheck(moduleName) { }; } -export function onPremInstructions(moduleName, platforms, context) { +export function onPremInstructions( + moduleName: string, + platforms: readonly Platform[] = [], + context?: TutorialContext +) { const FILEBEAT_INSTRUCTIONS = createFilebeatInstructions(context); const variants = []; @@ -470,14 +478,14 @@ export function onPremInstructions(moduleName, platforms, context) { instructions.push(FILEBEAT_INSTRUCTIONS.START[platform]); variants.push({ id: INSTRUCTION_VARIANT[platform], - instructions: instructions, + instructions, }); } return { instructionSets: [ { title: i18n.translate( - 'kbn.common.tutorials.filebeat.premInstructions.gettingStarted.title', + 'home.tutorials.common.filebeat.premInstructions.gettingStarted.title', { defaultMessage: 'Getting Started', } @@ -489,7 +497,7 @@ export function onPremInstructions(moduleName, platforms, context) { }; } -export function onPremCloudInstructions(moduleName, platforms) { +export function onPremCloudInstructions(moduleName: string, platforms: readonly Platform[] = []) { const FILEBEAT_INSTRUCTIONS = createFilebeatInstructions(); const TRYCLOUD_OPTION1 = createTrycloudOption1(); const TRYCLOUD_OPTION2 = createTrycloudOption2(); @@ -514,7 +522,7 @@ export function onPremCloudInstructions(moduleName, platforms) { instructionSets: [ { title: i18n.translate( - 'kbn.common.tutorials.filebeat.premCloudInstructions.gettingStarted.title', + 'home.tutorials.common.filebeat.premCloudInstructions.gettingStarted.title', { defaultMessage: 'Getting Started', } @@ -526,7 +534,7 @@ export function onPremCloudInstructions(moduleName, platforms) { }; } -export function cloudInstructions(moduleName, platforms) { +export function cloudInstructions(moduleName: string, platforms: readonly Platform[] = []) { const FILEBEAT_INSTRUCTIONS = createFilebeatInstructions(); const FILEBEAT_CLOUD_INSTRUCTIONS = createFilebeatCloudInstructions(); @@ -548,7 +556,7 @@ export function cloudInstructions(moduleName, platforms) { instructionSets: [ { title: i18n.translate( - 'kbn.common.tutorials.filebeat.cloudInstructions.gettingStarted.title', + 'home.tutorials.common.filebeat.cloudInstructions.gettingStarted.title', { defaultMessage: 'Getting Started', } diff --git a/src/legacy/core_plugins/kibana/common/tutorials/functionbeat_instructions.js b/src/plugins/home/server/tutorials/instructions/functionbeat_instructions.ts similarity index 79% rename from src/legacy/core_plugins/kibana/common/tutorials/functionbeat_instructions.js rename to src/plugins/home/server/tutorials/instructions/functionbeat_instructions.ts index f4116191c1cac..385880ba9780f 100644 --- a/src/legacy/core_plugins/kibana/common/tutorials/functionbeat_instructions.js +++ b/src/plugins/home/server/tutorials/instructions/functionbeat_instructions.ts @@ -20,15 +20,16 @@ import { i18n } from '@kbn/i18n'; import { INSTRUCTION_VARIANT } from './instruction_variant'; import { createTrycloudOption1, createTrycloudOption2 } from './onprem_cloud_instructions'; -import { getSpaceIdForBeatsTutorial } from '../lib/get_space_id_for_beats_tutorial'; +import { getSpaceIdForBeatsTutorial } from './get_space_id_for_beats_tutorial'; +import { Platform, TutorialContext } from '../../services/tutorials/lib/tutorials_registry_types'; -export const createFunctionbeatInstructions = context => ({ +export const createFunctionbeatInstructions = (context?: TutorialContext) => ({ INSTALL: { OSX: { - title: i18n.translate('kbn.common.tutorials.functionbeatInstructions.install.osxTitle', { + title: i18n.translate('home.tutorials.common.functionbeatInstructions.install.osxTitle', { defaultMessage: 'Download and install Functionbeat', }), - textPre: i18n.translate('kbn.common.tutorials.functionbeatInstructions.install.osxTextPre', { + textPre: i18n.translate('home.tutorials.common.functionbeatInstructions.install.osxTextPre', { defaultMessage: 'First time using Functionbeat? See the [Getting Started Guide]({link}).', values: { link: '{config.docs.beats.functionbeat}/functionbeat-getting-started.html' }, }), @@ -39,11 +40,11 @@ export const createFunctionbeatInstructions = context => ({ ], }, LINUX: { - title: i18n.translate('kbn.common.tutorials.functionbeatInstructions.install.linuxTitle', { + title: i18n.translate('home.tutorials.common.functionbeatInstructions.install.linuxTitle', { defaultMessage: 'Download and install Functionbeat', }), textPre: i18n.translate( - 'kbn.common.tutorials.functionbeatInstructions.install.linuxTextPre', + 'home.tutorials.common.functionbeatInstructions.install.linuxTextPre', { defaultMessage: 'First time using Functionbeat? See the [Getting Started Guide]({link}).', values: { link: '{config.docs.beats.functionbeat}/functionbeat-getting-started.html' }, @@ -56,11 +57,11 @@ export const createFunctionbeatInstructions = context => ({ ], }, WINDOWS: { - title: i18n.translate('kbn.common.tutorials.functionbeatInstructions.install.windowsTitle', { + title: i18n.translate('home.tutorials.common.functionbeatInstructions.install.windowsTitle', { defaultMessage: 'Download and install Functionbeat', }), textPre: i18n.translate( - 'kbn.common.tutorials.functionbeatInstructions.install.windowsTextPre', + 'home.tutorials.common.functionbeatInstructions.install.windowsTextPre', { defaultMessage: 'First time using Functionbeat? See the [Getting Started Guide]({functionbeatLink}).\n\ @@ -83,10 +84,10 @@ export const createFunctionbeatInstructions = context => ({ }, DEPLOY: { OSX_LINUX: { - title: i18n.translate('kbn.common.tutorials.functionbeatInstructions.deploy.osxTitle', { + title: i18n.translate('home.tutorials.common.functionbeatInstructions.deploy.osxTitle', { defaultMessage: 'Deploy Functionbeat to AWS Lambda', }), - textPre: i18n.translate('kbn.common.tutorials.functionbeatInstructions.deploy.osxTextPre', { + textPre: i18n.translate('home.tutorials.common.functionbeatInstructions.deploy.osxTextPre', { defaultMessage: 'This installs Functionbeat as a Lambda function.\ The `setup` command checks the Elasticsearch configuration and loads the \ @@ -95,11 +96,11 @@ Kibana index pattern. It is normally safe to omit this command.', commands: ['./functionbeat setup', './functionbeat deploy fn-cloudwatch-logs'], }, WINDOWS: { - title: i18n.translate('kbn.common.tutorials.functionbeatInstructions.deploy.windowsTitle', { + title: i18n.translate('home.tutorials.common.functionbeatInstructions.deploy.windowsTitle', { defaultMessage: 'Deploy Functionbeat to AWS Lambda', }), textPre: i18n.translate( - 'kbn.common.tutorials.functionbeatInstructions.deploy.windowsTextPre', + 'home.tutorials.common.functionbeatInstructions.deploy.windowsTextPre', { defaultMessage: 'This installs Functionbeat as a Lambda function.\ @@ -112,10 +113,10 @@ Kibana index pattern. It is normally safe to omit this command.', }, CONFIG: { OSX_LINUX: { - title: i18n.translate('kbn.common.tutorials.functionbeatInstructions.config.osxTitle', { + title: i18n.translate('home.tutorials.common.functionbeatInstructions.config.osxTitle', { defaultMessage: 'Configure the Elastic cluster', }), - textPre: i18n.translate('kbn.common.tutorials.functionbeatInstructions.config.osxTextPre', { + textPre: i18n.translate('home.tutorials.common.functionbeatInstructions.config.osxTextPre', { defaultMessage: 'Modify {path} to set the connection information:', values: { path: '`functionbeat.yml`', @@ -130,23 +131,26 @@ Kibana index pattern. It is normally safe to omit this command.', ' host: ""', getSpaceIdForBeatsTutorial(context), ], - textPost: i18n.translate('kbn.common.tutorials.functionbeatInstructions.config.osxTextPost', { - defaultMessage: - 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of Elasticsearch, \ + textPost: i18n.translate( + 'home.tutorials.common.functionbeatInstructions.config.osxTextPost', + { + defaultMessage: + 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of Elasticsearch, \ and {kibanaUrlTemplate} is the URL of Kibana.', - values: { - passwordTemplate: '``', - esUrlTemplate: '``', - kibanaUrlTemplate: '``', - }, - }), + values: { + passwordTemplate: '``', + esUrlTemplate: '``', + kibanaUrlTemplate: '``', + }, + } + ), }, WINDOWS: { - title: i18n.translate('kbn.common.tutorials.functionbeatInstructions.config.windowsTitle', { + title: i18n.translate('home.tutorials.common.functionbeatInstructions.config.windowsTitle', { defaultMessage: 'Edit the configuration', }), textPre: i18n.translate( - 'kbn.common.tutorials.functionbeatInstructions.config.windowsTextPre', + 'home.tutorials.common.functionbeatInstructions.config.windowsTextPre', { defaultMessage: 'Modify {path} to set the connection information:', values: { @@ -164,7 +168,7 @@ and {kibanaUrlTemplate} is the URL of Kibana.', getSpaceIdForBeatsTutorial(context), ], textPost: i18n.translate( - 'kbn.common.tutorials.functionbeatInstructions.config.windowsTextPost', + 'home.tutorials.common.functionbeatInstructions.config.windowsTextPost', { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of Elasticsearch, \ @@ -183,11 +187,11 @@ and {kibanaUrlTemplate} is the URL of Kibana.', export const createFunctionbeatCloudInstructions = () => ({ CONFIG: { OSX_LINUX: { - title: i18n.translate('kbn.common.tutorials.functionbeatCloudInstructions.config.osxTitle', { + title: i18n.translate('home.tutorials.common.functionbeatCloudInstructions.config.osxTitle', { defaultMessage: 'Edit the configuration', }), textPre: i18n.translate( - 'kbn.common.tutorials.functionbeatCloudInstructions.config.osxTextPre', + 'home.tutorials.common.functionbeatCloudInstructions.config.osxTextPre', { defaultMessage: 'Modify {path} to set the connection information for Elastic Cloud:', values: { @@ -197,7 +201,7 @@ export const createFunctionbeatCloudInstructions = () => ({ ), commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'], textPost: i18n.translate( - 'kbn.common.tutorials.functionbeatCloudInstructions.config.osxTextPost', + 'home.tutorials.common.functionbeatCloudInstructions.config.osxTextPost', { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.', values: { passwordTemplate: '``' }, @@ -206,13 +210,13 @@ export const createFunctionbeatCloudInstructions = () => ({ }, WINDOWS: { title: i18n.translate( - 'kbn.common.tutorials.functionbeatCloudInstructions.config.windowsTitle', + 'home.tutorials.common.functionbeatCloudInstructions.config.windowsTitle', { defaultMessage: 'Edit the configuration', } ), textPre: i18n.translate( - 'kbn.common.tutorials.functionbeatCloudInstructions.config.windowsTextPre', + 'home.tutorials.common.functionbeatCloudInstructions.config.windowsTextPre', { defaultMessage: 'Modify {path} to set the connection information for Elastic Cloud:', values: { @@ -222,7 +226,7 @@ export const createFunctionbeatCloudInstructions = () => ({ ), commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'], textPost: i18n.translate( - 'kbn.common.tutorials.functionbeatCloudInstructions.config.windowsTextPost', + 'home.tutorials.common.functionbeatCloudInstructions.config.windowsTextPost', { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.', values: { passwordTemplate: '``' }, @@ -234,7 +238,7 @@ export const createFunctionbeatCloudInstructions = () => ({ export function functionbeatEnableInstructions() { const defaultTitle = i18n.translate( - 'kbn.common.tutorials.functionbeatEnableOnPremInstructions.defaultTitle', + 'home.tutorials.common.functionbeatEnableOnPremInstructions.defaultTitle', { defaultMessage: 'Configure the Cloudwatch log group', } @@ -249,7 +253,7 @@ export function functionbeatEnableInstructions() { 'functionbeat.provider.aws.deploy_bucket: ', ]; const defaultTextPost = i18n.translate( - 'kbn.common.tutorials.functionbeatEnableOnPremInstructions.defaultTextPost', + 'home.tutorials.common.functionbeatEnableOnPremInstructions.defaultTextPost', { defaultMessage: 'Where `` is the name of the log group you want to ingest, \ @@ -261,7 +265,7 @@ Functionbeat deploy.', OSX_LINUX: { title: defaultTitle, textPre: i18n.translate( - 'kbn.common.tutorials.functionbeatEnableOnPremInstructionsOSXLinux.textPre', + 'home.tutorials.common.functionbeatEnableOnPremInstructionsOSXLinux.textPre', { defaultMessage: 'Modify the settings in the `functionbeat.yml` file.', } @@ -272,7 +276,7 @@ Functionbeat deploy.', WINDOWS: { title: defaultTitle, textPre: i18n.translate( - 'kbn.common.tutorials.functionbeatEnableOnPremInstructionsWindows.textPre', + 'home.tutorials.common.functionbeatEnableOnPremInstructionsWindows.textPre', { defaultMessage: 'Modify the settings in the {path} file.', values: { @@ -287,13 +291,13 @@ Functionbeat deploy.', } export function functionbeatAWSInstructions() { - const defaultTitle = i18n.translate('kbn.common.tutorials.functionbeatAWSInstructions.title', { + const defaultTitle = i18n.translate('home.tutorials.common.functionbeatAWSInstructions.title', { defaultMessage: 'Set AWS credentials', }); - const defaultPre = i18n.translate('kbn.common.tutorials.functionbeatAWSInstructions.textPre', { + const defaultPre = i18n.translate('home.tutorials.common.functionbeatAWSInstructions.textPre', { defaultMessage: 'Set your AWS account credentials in the environment:', }); - const defaultPost = i18n.translate('kbn.common.tutorials.functionbeatAWSInstructions.textPost', { + const defaultPost = i18n.translate('home.tutorials.common.functionbeatAWSInstructions.textPost', { defaultMessage: 'Where `` and `` are your account credentials and \ `us-east-1` is the desired region.', @@ -325,19 +329,19 @@ export function functionbeatAWSInstructions() { export function functionbeatStatusCheck() { return { - title: i18n.translate('kbn.common.tutorials.functionbeatStatusCheck.title', { + title: i18n.translate('home.tutorials.common.functionbeatStatusCheck.title', { defaultMessage: 'Functionbeat status', }), - text: i18n.translate('kbn.common.tutorials.functionbeatStatusCheck.text', { + text: i18n.translate('home.tutorials.common.functionbeatStatusCheck.text', { defaultMessage: 'Check that data is received from Functionbeat', }), - btnLabel: i18n.translate('kbn.common.tutorials.functionbeatStatusCheck.buttonLabel', { + btnLabel: i18n.translate('home.tutorials.common.functionbeatStatusCheck.buttonLabel', { defaultMessage: 'Check data', }), - success: i18n.translate('kbn.common.tutorials.functionbeatStatusCheck.successText', { + success: i18n.translate('home.tutorials.common.functionbeatStatusCheck.successText', { defaultMessage: 'Data successfully received from Functionbeat', }), - error: i18n.translate('kbn.common.tutorials.functionbeatStatusCheck.errorText', { + error: i18n.translate('home.tutorials.common.functionbeatStatusCheck.errorText', { defaultMessage: 'No data has been received from Functionbeat yet', }), esHitsCheck: { @@ -349,14 +353,14 @@ export function functionbeatStatusCheck() { }; } -export function onPremInstructions(platforms, context) { +export function onPremInstructions(platforms: Platform[], context?: TutorialContext) { const FUNCTIONBEAT_INSTRUCTIONS = createFunctionbeatInstructions(context); return { instructionSets: [ { title: i18n.translate( - 'kbn.common.tutorials.functionbeat.premInstructions.gettingStarted.title', + 'home.tutorials.common.functionbeat.premInstructions.gettingStarted.title', { defaultMessage: 'Getting Started', } @@ -408,7 +412,7 @@ export function onPremCloudInstructions() { instructionSets: [ { title: i18n.translate( - 'kbn.common.tutorials.functionbeat.premCloudInstructions.gettingStarted.title', + 'home.tutorials.common.functionbeat.premCloudInstructions.gettingStarted.title', { defaultMessage: 'Getting Started', } @@ -465,7 +469,7 @@ export function cloudInstructions() { instructionSets: [ { title: i18n.translate( - 'kbn.common.tutorials.functionbeat.cloudInstructions.gettingStarted.title', + 'home.tutorials.common.functionbeat.cloudInstructions.gettingStarted.title', { defaultMessage: 'Getting Started', } diff --git a/src/legacy/core_plugins/kibana/common/lib/get_space_id_for_beats_tutorial.js b/src/plugins/home/server/tutorials/instructions/get_space_id_for_beats_tutorial.ts similarity index 87% rename from src/legacy/core_plugins/kibana/common/lib/get_space_id_for_beats_tutorial.js rename to src/plugins/home/server/tutorials/instructions/get_space_id_for_beats_tutorial.ts index f7da589526003..894ffc6b126d6 100644 --- a/src/legacy/core_plugins/kibana/common/lib/get_space_id_for_beats_tutorial.js +++ b/src/plugins/home/server/tutorials/instructions/get_space_id_for_beats_tutorial.ts @@ -17,13 +17,15 @@ * under the License. */ +import { TutorialContext } from '../../services/tutorials/lib/tutorials_registry_types'; + /** * Returns valid configuration for a beat.yml file for adding the space id * if there is an active space and that space is not the default one. * * @param {object} context - Context object generated from tutorial factory (see #22760) */ -export function getSpaceIdForBeatsTutorial(context) { +export function getSpaceIdForBeatsTutorial(context?: TutorialContext) { if (!context || !context.spaceId || context.isInDefaultSpace) { return ''; } diff --git a/src/legacy/core_plugins/kibana/common/tutorials/heartbeat_instructions.js b/src/plugins/home/server/tutorials/instructions/heartbeat_instructions.ts similarity index 71% rename from src/legacy/core_plugins/kibana/common/tutorials/heartbeat_instructions.js rename to src/plugins/home/server/tutorials/instructions/heartbeat_instructions.ts index d9af2126174f7..406bf55da4321 100644 --- a/src/legacy/core_plugins/kibana/common/tutorials/heartbeat_instructions.js +++ b/src/plugins/home/server/tutorials/instructions/heartbeat_instructions.ts @@ -20,15 +20,16 @@ import { i18n } from '@kbn/i18n'; import { INSTRUCTION_VARIANT } from './instruction_variant'; import { createTrycloudOption1, createTrycloudOption2 } from './onprem_cloud_instructions'; -import { getSpaceIdForBeatsTutorial } from '../lib/get_space_id_for_beats_tutorial'; +import { getSpaceIdForBeatsTutorial } from './get_space_id_for_beats_tutorial'; +import { Platform, TutorialContext } from '../../services/tutorials/lib/tutorials_registry_types'; -export const createHeartbeatInstructions = context => ({ +export const createHeartbeatInstructions = (context?: TutorialContext) => ({ INSTALL: { OSX: { - title: i18n.translate('kbn.common.tutorials.heartbeatInstructions.install.osxTitle', { + title: i18n.translate('home.tutorials.common.heartbeatInstructions.install.osxTitle', { defaultMessage: 'Download and install Heartbeat', }), - textPre: i18n.translate('kbn.common.tutorials.heartbeatInstructions.install.osxTextPre', { + textPre: i18n.translate('home.tutorials.common.heartbeatInstructions.install.osxTextPre', { defaultMessage: 'First time using Heartbeat? See the [Getting Started Guide]({link}).', values: { link: '{config.docs.beats.heartbeat}/heartbeat-getting-started.html' }, }), @@ -39,10 +40,10 @@ export const createHeartbeatInstructions = context => ({ ], }, DEB: { - title: i18n.translate('kbn.common.tutorials.heartbeatInstructions.install.debTitle', { + title: i18n.translate('home.tutorials.common.heartbeatInstructions.install.debTitle', { defaultMessage: 'Download and install Heartbeat', }), - textPre: i18n.translate('kbn.common.tutorials.heartbeatInstructions.install.debTextPre', { + textPre: i18n.translate('home.tutorials.common.heartbeatInstructions.install.debTextPre', { defaultMessage: 'First time using Heartbeat? See the [Getting Started Guide]({link}).', values: { link: '{config.docs.beats.heartbeat}/heartbeat-getting-started.html' }, }), @@ -50,16 +51,16 @@ export const createHeartbeatInstructions = context => ({ 'curl -L -O https://artifacts.elastic.co/downloads/beats/heartbeat/heartbeat-{config.kibana.version}-amd64.deb', 'sudo dpkg -i heartbeat-{config.kibana.version}-amd64.deb', ], - textPost: i18n.translate('kbn.common.tutorials.heartbeatInstructions.install.debTextPost', { + textPost: i18n.translate('home.tutorials.common.heartbeatInstructions.install.debTextPost', { defaultMessage: 'Looking for the 32-bit packages? See the [Download page]({link}).', values: { link: 'https://www.elastic.co/downloads/beats/heartbeat' }, }), }, RPM: { - title: i18n.translate('kbn.common.tutorials.heartbeatInstructions.install.rpmTitle', { + title: i18n.translate('home.tutorials.common.heartbeatInstructions.install.rpmTitle', { defaultMessage: 'Download and install Heartbeat', }), - textPre: i18n.translate('kbn.common.tutorials.heartbeatInstructions.install.rpmTextPre', { + textPre: i18n.translate('home.tutorials.common.heartbeatInstructions.install.rpmTextPre', { defaultMessage: 'First time using Heartbeat? See the [Getting Started Guide]({link}).', values: { link: '{config.docs.beats.heartbeat}/heartbeat-getting-started.html' }, }), @@ -67,67 +68,70 @@ export const createHeartbeatInstructions = context => ({ 'curl -L -O https://artifacts.elastic.co/downloads/beats/heartbeat/heartbeat-{config.kibana.version}-x86_64.rpm', 'sudo rpm -vi heartbeat-{config.kibana.version}-x86_64.rpm', ], - textPost: i18n.translate('kbn.common.tutorials.heartbeatInstructions.install.debTextPost', { + textPost: i18n.translate('home.tutorials.common.heartbeatInstructions.install.debTextPost', { defaultMessage: 'Looking for the 32-bit packages? See the [Download page]({link}).', values: { link: 'https://www.elastic.co/downloads/beats/heartbeat' }, }), }, WINDOWS: { - title: i18n.translate('kbn.common.tutorials.heartbeatInstructions.install.windowsTitle', { + title: i18n.translate('home.tutorials.common.heartbeatInstructions.install.windowsTitle', { defaultMessage: 'Download and install Heartbeat', }), - textPre: i18n.translate('kbn.common.tutorials.heartbeatInstructions.install.windowsTextPre', { - defaultMessage: - 'First time using Heartbeat? See the [Getting Started Guide]({heartbeatLink}).\n\ + textPre: i18n.translate( + 'home.tutorials.common.heartbeatInstructions.install.windowsTextPre', + { + defaultMessage: + 'First time using Heartbeat? See the [Getting Started Guide]({heartbeatLink}).\n\ 1. Download the Heartbeat Windows zip file from the [Download]({elasticLink}) page.\n\ 2. Extract the contents of the zip file into {folderPath}.\n\ 3. Rename the {directoryName} directory to `Heartbeat`.\n\ 4. Open a PowerShell prompt as an Administrator (right-click the PowerShell icon and select \ **Run As Administrator**). If you are running Windows XP, you might need to download and install PowerShell.\n\ 5. From the PowerShell prompt, run the following commands to install Heartbeat as a Windows service.', - values: { - directoryName: '`heartbeat-{config.kibana.version}-windows`', - folderPath: '`C:\\Program Files`', - heartbeatLink: '{config.docs.beats.heartbeat}/heartbeat-getting-started.html', - elasticLink: 'https://www.elastic.co/downloads/beats/heartbeat', - }, - }), + values: { + directoryName: '`heartbeat-{config.kibana.version}-windows`', + folderPath: '`C:\\Program Files`', + heartbeatLink: '{config.docs.beats.heartbeat}/heartbeat-getting-started.html', + elasticLink: 'https://www.elastic.co/downloads/beats/heartbeat', + }, + } + ), commands: ['cd "C:\\Program Files\\Heartbeat"', '.\\install-service-heartbeat.ps1'], }, }, START: { OSX: { - title: i18n.translate('kbn.common.tutorials.heartbeatInstructions.start.osxTitle', { + title: i18n.translate('home.tutorials.common.heartbeatInstructions.start.osxTitle', { defaultMessage: 'Start Heartbeat', }), - textPre: i18n.translate('kbn.common.tutorials.heartbeatInstructions.start.osxTextPre', { + textPre: i18n.translate('home.tutorials.common.heartbeatInstructions.start.osxTextPre', { defaultMessage: 'The `setup` command loads the Kibana index pattern.', }), commands: ['./heartbeat setup', './heartbeat -e'], }, DEB: { - title: i18n.translate('kbn.common.tutorials.heartbeatInstructions.start.debTitle', { + title: i18n.translate('home.tutorials.common.heartbeatInstructions.start.debTitle', { defaultMessage: 'Start Heartbeat', }), - textPre: i18n.translate('kbn.common.tutorials.heartbeatInstructions.start.debTextPre', { + textPre: i18n.translate('home.tutorials.common.heartbeatInstructions.start.debTextPre', { defaultMessage: 'The `setup` command loads the Kibana index pattern.', }), commands: ['sudo heartbeat setup', 'sudo service heartbeat-elastic start'], }, RPM: { - title: i18n.translate('kbn.common.tutorials.heartbeatInstructions.start.rpmTitle', { + title: i18n.translate('home.tutorials.common.heartbeatInstructions.start.rpmTitle', { defaultMessage: 'Start Heartbeat', }), - textPre: i18n.translate('kbn.common.tutorials.heartbeatInstructions.start.rpmTextPre', { + textPre: i18n.translate('home.tutorials.common.heartbeatInstructions.start.rpmTextPre', { defaultMessage: 'The `setup` command loads the Kibana index pattern.', }), commands: ['sudo heartbeat setup', 'sudo service heartbeat-elastic start'], }, WINDOWS: { - title: i18n.translate('kbn.common.tutorials.heartbeatInstructions.start.windowsTitle', { + title: i18n.translate('home.tutorials.common.heartbeatInstructions.start.windowsTitle', { defaultMessage: 'Start Heartbeat', }), - textPre: i18n.translate('kbn.common.tutorials.heartbeatInstructions.start.windowsTextPre', { + textPre: i18n.translate('home.tutorials.common.heartbeatInstructions.start.windowsTextPre', { defaultMessage: 'The `setup` command loads the Kibana index pattern.', }), commands: ['.\\heartbeat.exe setup', 'Start-Service heartbeat'], @@ -135,10 +139,10 @@ export const createHeartbeatInstructions = context => ({ }, CONFIG: { OSX: { - title: i18n.translate('kbn.common.tutorials.heartbeatInstructions.config.osxTitle', { + title: i18n.translate('home.tutorials.common.heartbeatInstructions.config.osxTitle', { defaultMessage: 'Edit the configuration', }), - textPre: i18n.translate('kbn.common.tutorials.heartbeatInstructions.config.osxTextPre', { + textPre: i18n.translate('home.tutorials.common.heartbeatInstructions.config.osxTextPre', { defaultMessage: 'Modify {path} to set the connection information:', values: { path: '`heartbeat.yml`', @@ -153,7 +157,7 @@ export const createHeartbeatInstructions = context => ({ ' host: ""', getSpaceIdForBeatsTutorial(context), ], - textPost: i18n.translate('kbn.common.tutorials.heartbeatInstructions.config.osxTextPost', { + textPost: i18n.translate('home.tutorials.common.heartbeatInstructions.config.osxTextPost', { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of Elasticsearch, \ and {kibanaUrlTemplate} is the URL of Kibana.', @@ -165,10 +169,10 @@ and {kibanaUrlTemplate} is the URL of Kibana.', }), }, DEB: { - title: i18n.translate('kbn.common.tutorials.heartbeatInstructions.config.debTitle', { + title: i18n.translate('home.tutorials.common.heartbeatInstructions.config.debTitle', { defaultMessage: 'Edit the configuration', }), - textPre: i18n.translate('kbn.common.tutorials.heartbeatInstructions.config.debTextPre', { + textPre: i18n.translate('home.tutorials.common.heartbeatInstructions.config.debTextPre', { defaultMessage: 'Modify {path} to set the connection information:', values: { path: '`/etc/heartbeat/heartbeat.yml`', @@ -183,7 +187,7 @@ and {kibanaUrlTemplate} is the URL of Kibana.', ' host: ""', getSpaceIdForBeatsTutorial(context), ], - textPost: i18n.translate('kbn.common.tutorials.heartbeatInstructions.config.debTextPost', { + textPost: i18n.translate('home.tutorials.common.heartbeatInstructions.config.debTextPost', { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of Elasticsearch, \ and {kibanaUrlTemplate} is the URL of Kibana.', @@ -195,10 +199,10 @@ and {kibanaUrlTemplate} is the URL of Kibana.', }), }, RPM: { - title: i18n.translate('kbn.common.tutorials.heartbeatInstructions.config.rpmTitle', { + title: i18n.translate('home.tutorials.common.heartbeatInstructions.config.rpmTitle', { defaultMessage: 'Edit the configuration', }), - textPre: i18n.translate('kbn.common.tutorials.heartbeatInstructions.config.rpmTextPre', { + textPre: i18n.translate('home.tutorials.common.heartbeatInstructions.config.rpmTextPre', { defaultMessage: 'Modify {path} to set the connection information:', values: { path: '`/etc/heartbeat/heartbeat.yml`', @@ -213,7 +217,7 @@ and {kibanaUrlTemplate} is the URL of Kibana.', ' host: ""', getSpaceIdForBeatsTutorial(context), ], - textPost: i18n.translate('kbn.common.tutorials.heartbeatInstructions.config.rpmTextPost', { + textPost: i18n.translate('home.tutorials.common.heartbeatInstructions.config.rpmTextPost', { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of Elasticsearch, \ and {kibanaUrlTemplate} is the URL of Kibana.', @@ -225,10 +229,10 @@ and {kibanaUrlTemplate} is the URL of Kibana.', }), }, WINDOWS: { - title: i18n.translate('kbn.common.tutorials.heartbeatInstructions.config.windowsTitle', { + title: i18n.translate('home.tutorials.common.heartbeatInstructions.config.windowsTitle', { defaultMessage: 'Edit the configuration', }), - textPre: i18n.translate('kbn.common.tutorials.heartbeatInstructions.config.windowsTextPre', { + textPre: i18n.translate('home.tutorials.common.heartbeatInstructions.config.windowsTextPre', { defaultMessage: 'Modify {path} to set the connection information:', values: { path: '`C:\\Program Files\\Heartbeat\\heartbeat.yml`', @@ -244,7 +248,7 @@ and {kibanaUrlTemplate} is the URL of Kibana.', getSpaceIdForBeatsTutorial(context), ], textPost: i18n.translate( - 'kbn.common.tutorials.heartbeatInstructions.config.windowsTextPost', + 'home.tutorials.common.heartbeatInstructions.config.windowsTextPost', { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of Elasticsearch, \ @@ -263,18 +267,21 @@ and {kibanaUrlTemplate} is the URL of Kibana.', export const createHeartbeatCloudInstructions = () => ({ CONFIG: { OSX: { - title: i18n.translate('kbn.common.tutorials.heartbeatCloudInstructions.config.osxTitle', { + title: i18n.translate('home.tutorials.common.heartbeatCloudInstructions.config.osxTitle', { defaultMessage: 'Edit the configuration', }), - textPre: i18n.translate('kbn.common.tutorials.heartbeatCloudInstructions.config.osxTextPre', { - defaultMessage: 'Modify {path} to set the connection information for Elastic Cloud:', - values: { - path: '`heartbeat.yml`', - }, - }), + textPre: i18n.translate( + 'home.tutorials.common.heartbeatCloudInstructions.config.osxTextPre', + { + defaultMessage: 'Modify {path} to set the connection information for Elastic Cloud:', + values: { + path: '`heartbeat.yml`', + }, + } + ), commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'], textPost: i18n.translate( - 'kbn.common.tutorials.heartbeatCloudInstructions.config.osxTextPost', + 'home.tutorials.common.heartbeatCloudInstructions.config.osxTextPost', { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.', values: { passwordTemplate: '``' }, @@ -282,18 +289,21 @@ export const createHeartbeatCloudInstructions = () => ({ ), }, DEB: { - title: i18n.translate('kbn.common.tutorials.heartbeatCloudInstructions.config.debTitle', { + title: i18n.translate('home.tutorials.common.heartbeatCloudInstructions.config.debTitle', { defaultMessage: 'Edit the configuration', }), - textPre: i18n.translate('kbn.common.tutorials.heartbeatCloudInstructions.config.debTextPre', { - defaultMessage: 'Modify {path} to set the connection information for Elastic Cloud:', - values: { - path: '`/etc/heartbeat/heartbeat.yml`', - }, - }), + textPre: i18n.translate( + 'home.tutorials.common.heartbeatCloudInstructions.config.debTextPre', + { + defaultMessage: 'Modify {path} to set the connection information for Elastic Cloud:', + values: { + path: '`/etc/heartbeat/heartbeat.yml`', + }, + } + ), commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'], textPost: i18n.translate( - 'kbn.common.tutorials.heartbeatCloudInstructions.config.debTextPost', + 'home.tutorials.common.heartbeatCloudInstructions.config.debTextPost', { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.', values: { passwordTemplate: '``' }, @@ -301,18 +311,21 @@ export const createHeartbeatCloudInstructions = () => ({ ), }, RPM: { - title: i18n.translate('kbn.common.tutorials.heartbeatCloudInstructions.config.rpmTitle', { + title: i18n.translate('home.tutorials.common.heartbeatCloudInstructions.config.rpmTitle', { defaultMessage: 'Edit the configuration', }), - textPre: i18n.translate('kbn.common.tutorials.heartbeatCloudInstructions.config.rpmTextPre', { - defaultMessage: 'Modify {path} to set the connection information for Elastic Cloud:', - values: { - path: '`/etc/heartbeat/heartbeat.yml`', - }, - }), + textPre: i18n.translate( + 'home.tutorials.common.heartbeatCloudInstructions.config.rpmTextPre', + { + defaultMessage: 'Modify {path} to set the connection information for Elastic Cloud:', + values: { + path: '`/etc/heartbeat/heartbeat.yml`', + }, + } + ), commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'], textPost: i18n.translate( - 'kbn.common.tutorials.heartbeatCloudInstructions.config.rpmTextPost', + 'home.tutorials.common.heartbeatCloudInstructions.config.rpmTextPost', { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.', values: { passwordTemplate: '``' }, @@ -320,11 +333,14 @@ export const createHeartbeatCloudInstructions = () => ({ ), }, WINDOWS: { - title: i18n.translate('kbn.common.tutorials.heartbeatCloudInstructions.config.windowsTitle', { - defaultMessage: 'Edit the configuration', - }), + title: i18n.translate( + 'home.tutorials.common.heartbeatCloudInstructions.config.windowsTitle', + { + defaultMessage: 'Edit the configuration', + } + ), textPre: i18n.translate( - 'kbn.common.tutorials.heartbeatCloudInstructions.config.windowsTextPre', + 'home.tutorials.common.heartbeatCloudInstructions.config.windowsTextPre', { defaultMessage: 'Modify {path} to set the connection information for Elastic Cloud:', values: { @@ -334,7 +350,7 @@ export const createHeartbeatCloudInstructions = () => ({ ), commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'], textPost: i18n.translate( - 'kbn.common.tutorials.heartbeatCloudInstructions.config.windowsTextPost', + 'home.tutorials.common.heartbeatCloudInstructions.config.windowsTextPost', { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.', values: { passwordTemplate: '``' }, @@ -346,7 +362,7 @@ export const createHeartbeatCloudInstructions = () => ({ export function heartbeatEnableInstructionsOnPrem() { const defaultTitle = i18n.translate( - 'kbn.common.tutorials.heartbeatEnableOnPremInstructions.defaultTitle', + 'home.tutorials.common.heartbeatEnableOnPremInstructions.defaultTitle', { defaultMessage: 'Edit the configuration - Add monitors', } @@ -358,7 +374,7 @@ export function heartbeatEnableInstructionsOnPrem() { ' schedule: "@every 10s"', ]; const defaultTextPost = i18n.translate( - 'kbn.common.tutorials.heartbeatEnableOnPremInstructions.defaultTextPost', + 'home.tutorials.common.heartbeatEnableOnPremInstructions.defaultTextPost', { defaultMessage: 'Where {hostTemplate} is your monitored URL, For more details on how to configure Monitors in \ @@ -372,32 +388,41 @@ export function heartbeatEnableInstructionsOnPrem() { return { OSX: { title: defaultTitle, - textPre: i18n.translate('kbn.common.tutorials.heartbeatEnableOnPremInstructions.osxTextPre', { - defaultMessage: 'Edit the `heartbeat.monitors` setting in the `heartbeat.yml` file.', - }), + textPre: i18n.translate( + 'home.tutorials.common.heartbeatEnableOnPremInstructions.osxTextPre', + { + defaultMessage: 'Edit the `heartbeat.monitors` setting in the `heartbeat.yml` file.', + } + ), commands: defaultCommands, textPost: defaultTextPost, }, DEB: { title: defaultTitle, - textPre: i18n.translate('kbn.common.tutorials.heartbeatEnableOnPremInstructions.debTextPre', { - defaultMessage: 'Edit the `heartbeat.monitors` setting in the `heartbeat.yml` file.', - }), + textPre: i18n.translate( + 'home.tutorials.common.heartbeatEnableOnPremInstructions.debTextPre', + { + defaultMessage: 'Edit the `heartbeat.monitors` setting in the `heartbeat.yml` file.', + } + ), commands: defaultCommands, textPost: defaultTextPost, }, RPM: { title: defaultTitle, - textPre: i18n.translate('kbn.common.tutorials.heartbeatEnableOnPremInstructions.rpmTextPre', { - defaultMessage: 'Edit the `heartbeat.monitors` setting in the `heartbeat.yml` file.', - }), + textPre: i18n.translate( + 'home.tutorials.common.heartbeatEnableOnPremInstructions.rpmTextPre', + { + defaultMessage: 'Edit the `heartbeat.monitors` setting in the `heartbeat.yml` file.', + } + ), commands: defaultCommands, textPost: defaultTextPost, }, WINDOWS: { title: defaultTitle, textPre: i18n.translate( - 'kbn.common.tutorials.heartbeatEnableOnPremInstructions.windowsTextPre', + 'home.tutorials.common.heartbeatEnableOnPremInstructions.windowsTextPre', { defaultMessage: 'Edit the `heartbeat.monitors` setting in the `heartbeat.yml` file.', } @@ -410,7 +435,7 @@ export function heartbeatEnableInstructionsOnPrem() { export function heartbeatEnableInstructionsCloud() { const defaultTitle = i18n.translate( - 'kbn.common.tutorials.heartbeatEnableCloudInstructions.defaultTitle', + 'home.tutorials.common.heartbeatEnableCloudInstructions.defaultTitle', { defaultMessage: 'Edit the configuration - Add monitors', } @@ -422,7 +447,7 @@ export function heartbeatEnableInstructionsCloud() { ' schedule: "@every 10s"', ]; const defaultTextPost = i18n.translate( - 'kbn.common.tutorials.heartbeatEnableCloudInstructions.defaultTextPost', + 'home.tutorials.common.heartbeatEnableCloudInstructions.defaultTextPost', { defaultMessage: 'For more details on how to configure Monitors in Heartbeat, read the [Heartbeat configuration docs.]({configureLink})', @@ -432,7 +457,7 @@ export function heartbeatEnableInstructionsCloud() { return { OSX: { title: defaultTitle, - textPre: i18n.translate('kbn.common.tutorials.heartbeatEnableCloudInstructions.osxTextPre', { + textPre: i18n.translate('home.tutorials.common.heartbeatEnableCloudInstructions.osxTextPre', { defaultMessage: 'Edit the `heartbeat.monitors` setting in the `heartbeat.yml` file.', }), commands: defaultCommands, @@ -440,7 +465,7 @@ export function heartbeatEnableInstructionsCloud() { }, DEB: { title: defaultTitle, - textPre: i18n.translate('kbn.common.tutorials.heartbeatEnableCloudInstructions.debTextPre', { + textPre: i18n.translate('home.tutorials.common.heartbeatEnableCloudInstructions.debTextPre', { defaultMessage: 'Edit the `heartbeat.monitors` setting in the `heartbeat.yml` file.', }), commands: defaultCommands, @@ -448,7 +473,7 @@ export function heartbeatEnableInstructionsCloud() { }, RPM: { title: defaultTitle, - textPre: i18n.translate('kbn.common.tutorials.heartbeatEnableCloudInstructions.rpmTextPre', { + textPre: i18n.translate('home.tutorials.common.heartbeatEnableCloudInstructions.rpmTextPre', { defaultMessage: 'Edit the `heartbeat.monitors` setting in the `heartbeat.yml` file.', }), commands: defaultCommands, @@ -457,7 +482,7 @@ export function heartbeatEnableInstructionsCloud() { WINDOWS: { title: defaultTitle, textPre: i18n.translate( - 'kbn.common.tutorials.heartbeatEnableCloudInstructions.windowsTextPre', + 'home.tutorials.common.heartbeatEnableCloudInstructions.windowsTextPre', { defaultMessage: 'Edit the `heartbeat.monitors` setting in the `heartbeat.yml` file.', } @@ -470,19 +495,19 @@ export function heartbeatEnableInstructionsCloud() { export function heartbeatStatusCheck() { return { - title: i18n.translate('kbn.common.tutorials.heartbeatStatusCheck.title', { + title: i18n.translate('home.tutorials.common.heartbeatStatusCheck.title', { defaultMessage: 'Heartbeat status', }), - text: i18n.translate('kbn.common.tutorials.heartbeatStatusCheck.text', { + text: i18n.translate('home.tutorials.common.heartbeatStatusCheck.text', { defaultMessage: 'Check that data is received from Heartbeat', }), - btnLabel: i18n.translate('kbn.common.tutorials.heartbeatStatusCheck.buttonLabel', { + btnLabel: i18n.translate('home.tutorials.common.heartbeatStatusCheck.buttonLabel', { defaultMessage: 'Check data', }), - success: i18n.translate('kbn.common.tutorials.heartbeatStatusCheck.successText', { + success: i18n.translate('home.tutorials.common.heartbeatStatusCheck.successText', { defaultMessage: 'Data successfully received from Heartbeat', }), - error: i18n.translate('kbn.common.tutorials.heartbeatStatusCheck.errorText', { + error: i18n.translate('home.tutorials.common.heartbeatStatusCheck.errorText', { defaultMessage: 'No data has been received from Heartbeat yet', }), esHitsCheck: { @@ -494,14 +519,14 @@ export function heartbeatStatusCheck() { }; } -export function onPremInstructions(platforms, context) { +export function onPremInstructions(platforms: Platform[], context?: TutorialContext) { const HEARTBEAT_INSTRUCTIONS = createHeartbeatInstructions(context); return { instructionSets: [ { title: i18n.translate( - 'kbn.common.tutorials.heartbeat.premInstructions.gettingStarted.title', + 'home.tutorials.common.heartbeat.premInstructions.gettingStarted.title', { defaultMessage: 'Getting Started', } @@ -559,7 +584,7 @@ export function onPremCloudInstructions() { instructionSets: [ { title: i18n.translate( - 'kbn.common.tutorials.heartbeat.premCloudInstructions.gettingStarted.title', + 'home.tutorials.common.heartbeat.premCloudInstructions.gettingStarted.title', { defaultMessage: 'Getting Started', } @@ -624,7 +649,7 @@ export function cloudInstructions() { instructionSets: [ { title: i18n.translate( - 'kbn.common.tutorials.heartbeat.cloudInstructions.gettingStarted.title', + 'home.tutorials.common.heartbeat.cloudInstructions.gettingStarted.title', { defaultMessage: 'Getting Started', } diff --git a/src/legacy/core_plugins/kibana/common/tutorials/instruction_variant.js b/src/plugins/home/server/tutorials/instructions/instruction_variant.ts similarity index 96% rename from src/legacy/core_plugins/kibana/common/tutorials/instruction_variant.js rename to src/plugins/home/server/tutorials/instructions/instruction_variant.ts index 0c3be21044241..d026a3bdf2208 100644 --- a/src/legacy/core_plugins/kibana/common/tutorials/instruction_variant.js +++ b/src/plugins/home/server/tutorials/instructions/instruction_variant.ts @@ -61,7 +61,7 @@ const DISPLAY_MAP = { * @params {String} id - instruction variant id as defined from INSTRUCTION_VARIANT * @return {String} display name */ -export function getDisplayText(id) { +export function getDisplayText(id: keyof typeof INSTRUCTION_VARIANT) { if (id in DISPLAY_MAP) { return DISPLAY_MAP[id]; } diff --git a/src/legacy/core_plugins/kibana/common/tutorials/logstash_instructions.js b/src/plugins/home/server/tutorials/instructions/logstash_instructions.ts similarity index 82% rename from src/legacy/core_plugins/kibana/common/tutorials/logstash_instructions.js rename to src/plugins/home/server/tutorials/instructions/logstash_instructions.ts index 9e25402abe183..3e5345565a753 100644 --- a/src/legacy/core_plugins/kibana/common/tutorials/logstash_instructions.js +++ b/src/plugins/home/server/tutorials/instructions/logstash_instructions.ts @@ -23,11 +23,11 @@ export const createLogstashInstructions = () => ({ INSTALL: { OSX: [ { - title: i18n.translate('kbn.common.tutorials.logstashInstructions.install.java.osxTitle', { + title: i18n.translate('home.tutorials.common.logstashInstructions.install.java.osxTitle', { defaultMessage: 'Download and install the Java Runtime Environment', }), textPre: i18n.translate( - 'kbn.common.tutorials.logstashInstructions.install.java.osxTextPre', + 'home.tutorials.common.logstashInstructions.install.java.osxTextPre', { defaultMessage: 'Follow the installation instructions [here]({link}).', values: { @@ -38,13 +38,13 @@ export const createLogstashInstructions = () => ({ }, { title: i18n.translate( - 'kbn.common.tutorials.logstashInstructions.install.logstash.osxTitle', + 'home.tutorials.common.logstashInstructions.install.logstash.osxTitle', { defaultMessage: 'Download and install Logstash', } ), textPre: i18n.translate( - 'kbn.common.tutorials.logstashInstructions.install.logstash.osxTextPre', + 'home.tutorials.common.logstashInstructions.install.logstash.osxTextPre', { defaultMessage: 'First time using Logstash? See the [Getting Started Guide]({link}).', values: { @@ -62,13 +62,13 @@ export const createLogstashInstructions = () => ({ WINDOWS: [ { title: i18n.translate( - 'kbn.common.tutorials.logstashInstructions.install.java.windowsTitle', + 'home.tutorials.common.logstashInstructions.install.java.windowsTitle', { defaultMessage: 'Download and install the Java Runtime Environment', } ), textPre: i18n.translate( - 'kbn.common.tutorials.logstashInstructions.install.java.windowsTextPre', + 'home.tutorials.common.logstashInstructions.install.java.windowsTextPre', { defaultMessage: 'Follow the installation instructions [here]({link}).', values: { @@ -80,13 +80,13 @@ export const createLogstashInstructions = () => ({ }, { title: i18n.translate( - 'kbn.common.tutorials.logstashInstructions.install.logstash.windowsTitle', + 'home.tutorials.common.logstashInstructions.install.logstash.windowsTitle', { defaultMessage: 'Download and install Logstash', } ), textPre: i18n.translate( - 'kbn.common.tutorials.logstashInstructions.install.logstash.windowsTextPre', + 'home.tutorials.common.logstashInstructions.install.logstash.windowsTextPre', { defaultMessage: 'First time using Logstash? See the [Getting Started Guide]({logstashLink}).\n\ diff --git a/src/legacy/core_plugins/kibana/common/tutorials/metricbeat_instructions.js b/src/plugins/home/server/tutorials/instructions/metricbeat_instructions.ts similarity index 75% rename from src/legacy/core_plugins/kibana/common/tutorials/metricbeat_instructions.js rename to src/plugins/home/server/tutorials/instructions/metricbeat_instructions.ts index b7b1bec851e94..77efe0958a615 100644 --- a/src/legacy/core_plugins/kibana/common/tutorials/metricbeat_instructions.js +++ b/src/plugins/home/server/tutorials/instructions/metricbeat_instructions.ts @@ -20,15 +20,16 @@ import { i18n } from '@kbn/i18n'; import { INSTRUCTION_VARIANT } from './instruction_variant'; import { createTrycloudOption1, createTrycloudOption2 } from './onprem_cloud_instructions'; -import { getSpaceIdForBeatsTutorial } from '../lib/get_space_id_for_beats_tutorial'; +import { getSpaceIdForBeatsTutorial } from './get_space_id_for_beats_tutorial'; +import { TutorialContext } from '../../services/tutorials/lib/tutorials_registry_types'; -export const createMetricbeatInstructions = context => ({ +export const createMetricbeatInstructions = (context?: TutorialContext) => ({ INSTALL: { OSX: { - title: i18n.translate('kbn.common.tutorials.metricbeatInstructions.install.osxTitle', { + title: i18n.translate('home.tutorials.common.metricbeatInstructions.install.osxTitle', { defaultMessage: 'Download and install Metricbeat', }), - textPre: i18n.translate('kbn.common.tutorials.metricbeatInstructions.install.osxTextPre', { + textPre: i18n.translate('home.tutorials.common.metricbeatInstructions.install.osxTextPre', { defaultMessage: 'First time using Metricbeat? See the [Getting Started Guide]({link}).', values: { link: '{config.docs.beats.metricbeat}/metricbeat-getting-started.html' }, }), @@ -39,10 +40,10 @@ export const createMetricbeatInstructions = context => ({ ], }, DEB: { - title: i18n.translate('kbn.common.tutorials.metricbeatInstructions.install.debTitle', { + title: i18n.translate('home.tutorials.common.metricbeatInstructions.install.debTitle', { defaultMessage: 'Download and install Metricbeat', }), - textPre: i18n.translate('kbn.common.tutorials.metricbeatInstructions.install.debTextPre', { + textPre: i18n.translate('home.tutorials.common.metricbeatInstructions.install.debTextPre', { defaultMessage: 'First time using Metricbeat? See the [Getting Started Guide]({link}).', values: { link: '{config.docs.beats.metricbeat}/metricbeat-getting-started.html' }, }), @@ -50,16 +51,16 @@ export const createMetricbeatInstructions = context => ({ 'curl -L -O https://artifacts.elastic.co/downloads/beats/metricbeat/metricbeat-{config.kibana.version}-amd64.deb', 'sudo dpkg -i metricbeat-{config.kibana.version}-amd64.deb', ], - textPost: i18n.translate('kbn.common.tutorials.metricbeatInstructions.install.debTextPost', { + textPost: i18n.translate('home.tutorials.common.metricbeatInstructions.install.debTextPost', { defaultMessage: 'Looking for the 32-bit packages? See the [Download page]({link}).', values: { link: 'https://www.elastic.co/downloads/beats/metricbeat' }, }), }, RPM: { - title: i18n.translate('kbn.common.tutorials.metricbeatInstructions.install.rpmTitle', { + title: i18n.translate('home.tutorials.common.metricbeatInstructions.install.rpmTitle', { defaultMessage: 'Download and install Metricbeat', }), - textPre: i18n.translate('kbn.common.tutorials.metricbeatInstructions.install.rpmTextPre', { + textPre: i18n.translate('home.tutorials.common.metricbeatInstructions.install.rpmTextPre', { defaultMessage: 'First time using Metricbeat? See the [Getting Started Guide]({link}).', values: { link: '{config.docs.beats.metricbeat}/metricbeat-getting-started.html' }, }), @@ -67,17 +68,17 @@ export const createMetricbeatInstructions = context => ({ 'curl -L -O https://artifacts.elastic.co/downloads/beats/metricbeat/metricbeat-{config.kibana.version}-x86_64.rpm', 'sudo rpm -vi metricbeat-{config.kibana.version}-x86_64.rpm', ], - textPost: i18n.translate('kbn.common.tutorials.metricbeatInstructions.install.debTextPost', { + textPost: i18n.translate('home.tutorials.common.metricbeatInstructions.install.debTextPost', { defaultMessage: 'Looking for the 32-bit packages? See the [Download page]({link}).', values: { link: 'https://www.elastic.co/downloads/beats/metricbeat' }, }), }, WINDOWS: { - title: i18n.translate('kbn.common.tutorials.metricbeatInstructions.install.windowsTitle', { + title: i18n.translate('home.tutorials.common.metricbeatInstructions.install.windowsTitle', { defaultMessage: 'Download and install Metricbeat', }), textPre: i18n.translate( - 'kbn.common.tutorials.metricbeatInstructions.install.windowsTextPre', + 'home.tutorials.common.metricbeatInstructions.install.windowsTextPre', { defaultMessage: 'First time using Metricbeat? See the [Getting Started Guide]({metricbeatLink}).\n\ @@ -97,7 +98,7 @@ export const createMetricbeatInstructions = context => ({ ), commands: ['cd "C:\\Program Files\\Metricbeat"', '.\\install-service-metricbeat.ps1'], textPost: i18n.translate( - 'kbn.common.tutorials.metricbeatInstructions.install.windowsTextPost', + 'home.tutorials.common.metricbeatInstructions.install.windowsTextPost', { defaultMessage: 'Modify the settings under `output.elasticsearch` in the {path} file to point to your Elasticsearch installation.', @@ -108,40 +109,40 @@ export const createMetricbeatInstructions = context => ({ }, START: { OSX: { - title: i18n.translate('kbn.common.tutorials.metricbeatInstructions.start.osxTitle', { + title: i18n.translate('home.tutorials.common.metricbeatInstructions.start.osxTitle', { defaultMessage: 'Start Metricbeat', }), - textPre: i18n.translate('kbn.common.tutorials.metricbeatInstructions.start.osxTextPre', { + textPre: i18n.translate('home.tutorials.common.metricbeatInstructions.start.osxTextPre', { defaultMessage: 'The `setup` command loads the Kibana dashboards. If the dashboards are already set up, omit this command.', }), commands: ['./metricbeat setup', './metricbeat -e'], }, DEB: { - title: i18n.translate('kbn.common.tutorials.metricbeatInstructions.start.debTitle', { + title: i18n.translate('home.tutorials.common.metricbeatInstructions.start.debTitle', { defaultMessage: 'Start Metricbeat', }), - textPre: i18n.translate('kbn.common.tutorials.metricbeatInstructions.start.debTextPre', { + textPre: i18n.translate('home.tutorials.common.metricbeatInstructions.start.debTextPre', { defaultMessage: 'The `setup` command loads the Kibana dashboards. If the dashboards are already set up, omit this command.', }), commands: ['sudo metricbeat setup', 'sudo service metricbeat start'], }, RPM: { - title: i18n.translate('kbn.common.tutorials.metricbeatInstructions.start.rpmTitle', { + title: i18n.translate('home.tutorials.common.metricbeatInstructions.start.rpmTitle', { defaultMessage: 'Start Metricbeat', }), - textPre: i18n.translate('kbn.common.tutorials.metricbeatInstructions.start.rpmTextPre', { + textPre: i18n.translate('home.tutorials.common.metricbeatInstructions.start.rpmTextPre', { defaultMessage: 'The `setup` command loads the Kibana dashboards. If the dashboards are already set up, omit this command.', }), commands: ['sudo metricbeat setup', 'sudo service metricbeat start'], }, WINDOWS: { - title: i18n.translate('kbn.common.tutorials.metricbeatInstructions.start.windowsTitle', { + title: i18n.translate('home.tutorials.common.metricbeatInstructions.start.windowsTitle', { defaultMessage: 'Start Metricbeat', }), - textPre: i18n.translate('kbn.common.tutorials.metricbeatInstructions.start.windowsTextPre', { + textPre: i18n.translate('home.tutorials.common.metricbeatInstructions.start.windowsTextPre', { defaultMessage: 'The `setup` command loads the Kibana dashboards. If the dashboards are already set up, omit this command.', }), @@ -150,10 +151,10 @@ export const createMetricbeatInstructions = context => ({ }, CONFIG: { OSX: { - title: i18n.translate('kbn.common.tutorials.metricbeatInstructions.config.osxTitle', { + title: i18n.translate('home.tutorials.common.metricbeatInstructions.config.osxTitle', { defaultMessage: 'Edit the configuration', }), - textPre: i18n.translate('kbn.common.tutorials.metricbeatInstructions.config.osxTextPre', { + textPre: i18n.translate('home.tutorials.common.metricbeatInstructions.config.osxTextPre', { defaultMessage: 'Modify {path} to set the connection information:', values: { path: '`metricbeat.yml`', @@ -168,7 +169,7 @@ export const createMetricbeatInstructions = context => ({ ' host: ""', getSpaceIdForBeatsTutorial(context), ], - textPost: i18n.translate('kbn.common.tutorials.metricbeatInstructions.config.osxTextPost', { + textPost: i18n.translate('home.tutorials.common.metricbeatInstructions.config.osxTextPost', { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of Elasticsearch, \ and {kibanaUrlTemplate} is the URL of Kibana.', @@ -180,10 +181,10 @@ and {kibanaUrlTemplate} is the URL of Kibana.', }), }, DEB: { - title: i18n.translate('kbn.common.tutorials.metricbeatInstructions.config.debTitle', { + title: i18n.translate('home.tutorials.common.metricbeatInstructions.config.debTitle', { defaultMessage: 'Edit the configuration', }), - textPre: i18n.translate('kbn.common.tutorials.metricbeatInstructions.config.debTextPre', { + textPre: i18n.translate('home.tutorials.common.metricbeatInstructions.config.debTextPre', { defaultMessage: 'Modify {path} to set the connection information:', values: { path: '`/etc/metricbeat/metricbeat.yml`', @@ -198,7 +199,7 @@ and {kibanaUrlTemplate} is the URL of Kibana.', ' host: ""', getSpaceIdForBeatsTutorial(context), ], - textPost: i18n.translate('kbn.common.tutorials.metricbeatInstructions.config.debTextPost', { + textPost: i18n.translate('home.tutorials.common.metricbeatInstructions.config.debTextPost', { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of Elasticsearch, \ and {kibanaUrlTemplate} is the URL of Kibana.', @@ -210,10 +211,10 @@ and {kibanaUrlTemplate} is the URL of Kibana.', }), }, RPM: { - title: i18n.translate('kbn.common.tutorials.metricbeatInstructions.config.rpmTitle', { + title: i18n.translate('home.tutorials.common.metricbeatInstructions.config.rpmTitle', { defaultMessage: 'Edit the configuration', }), - textPre: i18n.translate('kbn.common.tutorials.metricbeatInstructions.config.rpmTextPre', { + textPre: i18n.translate('home.tutorials.common.metricbeatInstructions.config.rpmTextPre', { defaultMessage: 'Modify {path} to set the connection information:', values: { path: '`/etc/metricbeat/metricbeat.yml`', @@ -228,7 +229,7 @@ and {kibanaUrlTemplate} is the URL of Kibana.', ' host: ""', getSpaceIdForBeatsTutorial(context), ], - textPost: i18n.translate('kbn.common.tutorials.metricbeatInstructions.config.rpmTextPost', { + textPost: i18n.translate('home.tutorials.common.metricbeatInstructions.config.rpmTextPost', { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of Elasticsearch, \ and {kibanaUrlTemplate} is the URL of Kibana.', @@ -240,15 +241,18 @@ and {kibanaUrlTemplate} is the URL of Kibana.', }), }, WINDOWS: { - title: i18n.translate('kbn.common.tutorials.metricbeatInstructions.config.windowsTitle', { + title: i18n.translate('home.tutorials.common.metricbeatInstructions.config.windowsTitle', { defaultMessage: 'Edit the configuration', }), - textPre: i18n.translate('kbn.common.tutorials.metricbeatInstructions.config.windowsTextPre', { - defaultMessage: 'Modify {path} to set the connection information:', - values: { - path: '`C:\\Program Files\\Metricbeat\\metricbeat.yml`', - }, - }), + textPre: i18n.translate( + 'home.tutorials.common.metricbeatInstructions.config.windowsTextPre', + { + defaultMessage: 'Modify {path} to set the connection information:', + values: { + path: '`C:\\Program Files\\Metricbeat\\metricbeat.yml`', + }, + } + ), commands: [ 'output.elasticsearch:', ' hosts: [""]', @@ -259,7 +263,7 @@ and {kibanaUrlTemplate} is the URL of Kibana.', getSpaceIdForBeatsTutorial(context), ], textPost: i18n.translate( - 'kbn.common.tutorials.metricbeatInstructions.config.windowsTextPost', + 'home.tutorials.common.metricbeatInstructions.config.windowsTextPost', { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of Elasticsearch, \ @@ -278,11 +282,11 @@ and {kibanaUrlTemplate} is the URL of Kibana.', export const createMetricbeatCloudInstructions = () => ({ CONFIG: { OSX: { - title: i18n.translate('kbn.common.tutorials.metricbeatCloudInstructions.config.osxTitle', { + title: i18n.translate('home.tutorials.common.metricbeatCloudInstructions.config.osxTitle', { defaultMessage: 'Edit the configuration', }), textPre: i18n.translate( - 'kbn.common.tutorials.metricbeatCloudInstructions.config.osxTextPre', + 'home.tutorials.common.metricbeatCloudInstructions.config.osxTextPre', { defaultMessage: 'Modify {path} to set the connection information for Elastic Cloud:', values: { @@ -292,7 +296,7 @@ export const createMetricbeatCloudInstructions = () => ({ ), commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'], textPost: i18n.translate( - 'kbn.common.tutorials.metricbeatCloudInstructions.config.osxTextPost', + 'home.tutorials.common.metricbeatCloudInstructions.config.osxTextPost', { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.', values: { passwordTemplate: '``' }, @@ -300,11 +304,11 @@ export const createMetricbeatCloudInstructions = () => ({ ), }, DEB: { - title: i18n.translate('kbn.common.tutorials.metricbeatCloudInstructions.config.debTitle', { + title: i18n.translate('home.tutorials.common.metricbeatCloudInstructions.config.debTitle', { defaultMessage: 'Edit the configuration', }), textPre: i18n.translate( - 'kbn.common.tutorials.metricbeatCloudInstructions.config.debTextPre', + 'home.tutorials.common.metricbeatCloudInstructions.config.debTextPre', { defaultMessage: 'Modify {path} to set the connection information for Elastic Cloud:', values: { @@ -314,7 +318,7 @@ export const createMetricbeatCloudInstructions = () => ({ ), commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'], textPost: i18n.translate( - 'kbn.common.tutorials.metricbeatCloudInstructions.config.debTextPost', + 'home.tutorials.common.metricbeatCloudInstructions.config.debTextPost', { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.', values: { passwordTemplate: '``' }, @@ -322,11 +326,11 @@ export const createMetricbeatCloudInstructions = () => ({ ), }, RPM: { - title: i18n.translate('kbn.common.tutorials.metricbeatCloudInstructions.config.rpmTitle', { + title: i18n.translate('home.tutorials.common.metricbeatCloudInstructions.config.rpmTitle', { defaultMessage: 'Edit the configuration', }), textPre: i18n.translate( - 'kbn.common.tutorials.metricbeatCloudInstructions.config.rpmTextPre', + 'home.tutorials.common.metricbeatCloudInstructions.config.rpmTextPre', { defaultMessage: 'Modify {path} to set the connection information for Elastic Cloud:', values: { @@ -336,7 +340,7 @@ export const createMetricbeatCloudInstructions = () => ({ ), commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'], textPost: i18n.translate( - 'kbn.common.tutorials.metricbeatCloudInstructions.config.rpmTextPost', + 'home.tutorials.common.metricbeatCloudInstructions.config.rpmTextPost', { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.', values: { passwordTemplate: '``' }, @@ -345,13 +349,13 @@ export const createMetricbeatCloudInstructions = () => ({ }, WINDOWS: { title: i18n.translate( - 'kbn.common.tutorials.metricbeatCloudInstructions.config.windowsTitle', + 'home.tutorials.common.metricbeatCloudInstructions.config.windowsTitle', { defaultMessage: 'Edit the configuration', } ), textPre: i18n.translate( - 'kbn.common.tutorials.metricbeatCloudInstructions.config.windowsTextPre', + 'home.tutorials.common.metricbeatCloudInstructions.config.windowsTextPre', { defaultMessage: 'Modify {path} to set the connection information for Elastic Cloud:', values: { @@ -361,7 +365,7 @@ export const createMetricbeatCloudInstructions = () => ({ ), commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'], textPost: i18n.translate( - 'kbn.common.tutorials.metricbeatCloudInstructions.config.windowsTextPost', + 'home.tutorials.common.metricbeatCloudInstructions.config.windowsTextPost', { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.', values: { passwordTemplate: '``' }, @@ -371,58 +375,58 @@ export const createMetricbeatCloudInstructions = () => ({ }, }); -export function metricbeatEnableInstructions(moduleName) { +export function metricbeatEnableInstructions(moduleName: string) { return { OSX: { - title: i18n.translate('kbn.common.tutorials.metricbeatEnableInstructions.osxTitle', { + title: i18n.translate('home.tutorials.common.metricbeatEnableInstructions.osxTitle', { defaultMessage: 'Enable and configure the {moduleName} module', values: { moduleName }, }), - textPre: i18n.translate('kbn.common.tutorials.metricbeatEnableInstructions.osxTextPre', { + textPre: i18n.translate('home.tutorials.common.metricbeatEnableInstructions.osxTextPre', { defaultMessage: 'From the installation directory, run:', }), commands: ['./metricbeat modules enable ' + moduleName], - textPost: i18n.translate('kbn.common.tutorials.metricbeatEnableInstructions.osxTextPost', { + textPost: i18n.translate('home.tutorials.common.metricbeatEnableInstructions.osxTextPost', { defaultMessage: 'Modify the settings in the `modules.d/{moduleName}.yml` file.', values: { moduleName }, }), }, DEB: { - title: i18n.translate('kbn.common.tutorials.metricbeatEnableInstructions.debTitle', { + title: i18n.translate('home.tutorials.common.metricbeatEnableInstructions.debTitle', { defaultMessage: 'Enable and configure the {moduleName} module', values: { moduleName }, }), commands: ['sudo metricbeat modules enable ' + moduleName], - textPost: i18n.translate('kbn.common.tutorials.metricbeatEnableInstructions.debTextPost', { + textPost: i18n.translate('home.tutorials.common.metricbeatEnableInstructions.debTextPost', { defaultMessage: 'Modify the settings in the `/etc/metricbeat/modules.d/{moduleName}.yml` file.', values: { moduleName }, }), }, RPM: { - title: i18n.translate('kbn.common.tutorials.metricbeatEnableInstructions.rpmTitle', { + title: i18n.translate('home.tutorials.common.metricbeatEnableInstructions.rpmTitle', { defaultMessage: 'Enable and configure the {moduleName} module', values: { moduleName }, }), commands: ['sudo metricbeat modules enable ' + moduleName], - textPost: i18n.translate('kbn.common.tutorials.metricbeatEnableInstructions.rpmTextPost', { + textPost: i18n.translate('home.tutorials.common.metricbeatEnableInstructions.rpmTextPost', { defaultMessage: 'Modify the settings in the `/etc/metricbeat/modules.d/{moduleName}.yml` file.', values: { moduleName }, }), }, WINDOWS: { - title: i18n.translate('kbn.common.tutorials.metricbeatEnableInstructions.windowsTitle', { + title: i18n.translate('home.tutorials.common.metricbeatEnableInstructions.windowsTitle', { defaultMessage: 'Enable and configure the {moduleName} module', values: { moduleName }, }), - textPre: i18n.translate('kbn.common.tutorials.metricbeatEnableInstructions.windowsTextPre', { + textPre: i18n.translate('home.tutorials.common.metricbeatEnableInstructions.windowsTextPre', { defaultMessage: 'From the {path} folder, run:', values: { path: `C:\\Program Files\\Metricbeat` }, }), commands: ['.\\metricbeat.exe modules enable ' + moduleName], textPost: i18n.translate( - 'kbn.common.tutorials.metricbeatEnableInstructions.windowsTextPost', + 'home.tutorials.common.metricbeatEnableInstructions.windowsTextPost', { defaultMessage: 'Modify the settings in the `modules.d/{moduleName}.yml` file.', values: { moduleName }, @@ -432,22 +436,22 @@ export function metricbeatEnableInstructions(moduleName) { }; } -export function metricbeatStatusCheck(moduleName) { +export function metricbeatStatusCheck(moduleName: string) { return { - title: i18n.translate('kbn.common.tutorials.metricbeatStatusCheck.title', { + title: i18n.translate('home.tutorials.common.metricbeatStatusCheck.title', { defaultMessage: 'Module status', }), - text: i18n.translate('kbn.common.tutorials.metricbeatStatusCheck.text', { + text: i18n.translate('home.tutorials.common.metricbeatStatusCheck.text', { defaultMessage: 'Check that data is received from the Metricbeat `{moduleName}` module', values: { moduleName }, }), - btnLabel: i18n.translate('kbn.common.tutorials.metricbeatStatusCheck.buttonLabel', { + btnLabel: i18n.translate('home.tutorials.common.metricbeatStatusCheck.buttonLabel', { defaultMessage: 'Check data', }), - success: i18n.translate('kbn.common.tutorials.metricbeatStatusCheck.successText', { + success: i18n.translate('home.tutorials.common.metricbeatStatusCheck.successText', { defaultMessage: 'Data successfully received from this module', }), - error: i18n.translate('kbn.common.tutorials.metricbeatStatusCheck.errorText', { + error: i18n.translate('home.tutorials.common.metricbeatStatusCheck.errorText', { defaultMessage: 'No data has been received from this module yet', }), esHitsCheck: { @@ -465,14 +469,14 @@ export function metricbeatStatusCheck(moduleName) { }; } -export function onPremInstructions(moduleName, platforms, context) { +export function onPremInstructions(moduleName: string, context?: TutorialContext) { const METRICBEAT_INSTRUCTIONS = createMetricbeatInstructions(context); return { instructionSets: [ { title: i18n.translate( - 'kbn.common.tutorials.metricbeat.premInstructions.gettingStarted.title', + 'home.tutorials.common.metricbeat.premInstructions.gettingStarted.title', { defaultMessage: 'Getting Started', } @@ -521,7 +525,7 @@ export function onPremInstructions(moduleName, platforms, context) { }; } -export function onPremCloudInstructions(moduleName) { +export function onPremCloudInstructions(moduleName: string) { const TRYCLOUD_OPTION1 = createTrycloudOption1(); const TRYCLOUD_OPTION2 = createTrycloudOption2(); const METRICBEAT_INSTRUCTIONS = createMetricbeatInstructions(); @@ -530,7 +534,7 @@ export function onPremCloudInstructions(moduleName) { instructionSets: [ { title: i18n.translate( - 'kbn.common.tutorials.metricbeat.premCloudInstructions.gettingStarted.title', + 'home.tutorials.common.metricbeat.premCloudInstructions.gettingStarted.title', { defaultMessage: 'Getting Started', } @@ -587,7 +591,7 @@ export function onPremCloudInstructions(moduleName) { }; } -export function cloudInstructions(moduleName) { +export function cloudInstructions(moduleName: string) { const METRICBEAT_INSTRUCTIONS = createMetricbeatInstructions(); const METRICBEAT_CLOUD_INSTRUCTIONS = createMetricbeatCloudInstructions(); @@ -595,7 +599,7 @@ export function cloudInstructions(moduleName) { instructionSets: [ { title: i18n.translate( - 'kbn.common.tutorials.metricbeat.cloudInstructions.gettingStarted.title', + 'home.tutorials.common.metricbeat.cloudInstructions.gettingStarted.title', { defaultMessage: 'Getting Started', } diff --git a/src/legacy/core_plugins/kibana/common/tutorials/onprem_cloud_instructions.js b/src/plugins/home/server/tutorials/instructions/onprem_cloud_instructions.ts similarity index 85% rename from src/legacy/core_plugins/kibana/common/tutorials/onprem_cloud_instructions.js rename to src/plugins/home/server/tutorials/instructions/onprem_cloud_instructions.ts index ec66d9037eaee..d3b17410c4ffa 100644 --- a/src/legacy/core_plugins/kibana/common/tutorials/onprem_cloud_instructions.js +++ b/src/plugins/home/server/tutorials/instructions/onprem_cloud_instructions.ts @@ -20,10 +20,10 @@ import { i18n } from '@kbn/i18n'; export const createTrycloudOption1 = () => ({ - title: i18n.translate('kbn.common.tutorials.premCloudInstructions.option1.title', { + title: i18n.translate('home.tutorials.common.premCloudInstructions.option1.title', { defaultMessage: 'Option 1: Try in Elastic Cloud', }), - textPre: i18n.translate('kbn.common.tutorials.premCloudInstructions.option1.textPre', { + textPre: i18n.translate('home.tutorials.common.premCloudInstructions.option1.textPre', { defaultMessage: 'Go to [Elastic Cloud]({link}). Register if you \ do not already have an account. Free 14-day trial available.\n\n\ @@ -41,10 +41,10 @@ To create a cluster, in Elastic Cloud console:\n\ }); export const createTrycloudOption2 = () => ({ - title: i18n.translate('kbn.common.tutorials.premCloudInstructions.option2.title', { + title: i18n.translate('home.tutorials.common.premCloudInstructions.option2.title', { defaultMessage: 'Option 2: Connect local Kibana to a Cloud instance', }), - textPre: i18n.translate('kbn.common.tutorials.premCloudInstructions.option2.textPre', { + textPre: i18n.translate('home.tutorials.common.premCloudInstructions.option2.textPre', { defaultMessage: 'If you are running this Kibana instance against a hosted Elasticsearch instance, \ proceed with manual setup.\n\n\ diff --git a/src/legacy/core_plugins/kibana/common/tutorials/param_types.js b/src/plugins/home/server/tutorials/instructions/param_types.ts similarity index 100% rename from src/legacy/core_plugins/kibana/common/tutorials/param_types.js rename to src/plugins/home/server/tutorials/instructions/param_types.ts diff --git a/src/legacy/core_plugins/kibana/common/tutorials/winlogbeat_instructions.js b/src/plugins/home/server/tutorials/instructions/winlogbeat_instructions.ts similarity index 77% rename from src/legacy/core_plugins/kibana/common/tutorials/winlogbeat_instructions.js rename to src/plugins/home/server/tutorials/instructions/winlogbeat_instructions.ts index 509ab0e6aba2d..cc18f2ce9705d 100644 --- a/src/legacy/core_plugins/kibana/common/tutorials/winlogbeat_instructions.js +++ b/src/plugins/home/server/tutorials/instructions/winlogbeat_instructions.ts @@ -20,16 +20,17 @@ import { i18n } from '@kbn/i18n'; import { INSTRUCTION_VARIANT } from './instruction_variant'; import { createTrycloudOption1, createTrycloudOption2 } from './onprem_cloud_instructions'; -import { getSpaceIdForBeatsTutorial } from '../lib/get_space_id_for_beats_tutorial'; +import { getSpaceIdForBeatsTutorial } from './get_space_id_for_beats_tutorial'; +import { TutorialContext } from '../../services/tutorials/lib/tutorials_registry_types'; -export const createWinlogbeatInstructions = context => ({ +export const createWinlogbeatInstructions = (context?: TutorialContext) => ({ INSTALL: { WINDOWS: { - title: i18n.translate('kbn.common.tutorials.winlogbeatInstructions.install.windowsTitle', { + title: i18n.translate('home.tutorials.common.winlogbeatInstructions.install.windowsTitle', { defaultMessage: 'Download and install Winlogbeat', }), textPre: i18n.translate( - 'kbn.common.tutorials.winlogbeatInstructions.install.windowsTextPre', + 'home.tutorials.common.winlogbeatInstructions.install.windowsTextPre', { defaultMessage: 'First time using Winlogbeat? See the [Getting Started Guide]({winlogbeatLink}).\n\ @@ -49,7 +50,7 @@ export const createWinlogbeatInstructions = context => ({ ), commands: ['cd "C:\\Program Files\\Winlogbeat"', '.\\install-service-winlogbeat.ps1'], textPost: i18n.translate( - 'kbn.common.tutorials.winlogbeatInstructions.install.windowsTextPost', + 'home.tutorials.common.winlogbeatInstructions.install.windowsTextPost', { defaultMessage: 'Modify the settings under `output.elasticsearch` in the {path} file to point to your Elasticsearch installation.', @@ -60,10 +61,10 @@ export const createWinlogbeatInstructions = context => ({ }, START: { WINDOWS: { - title: i18n.translate('kbn.common.tutorials.winlogbeatInstructions.start.windowsTitle', { + title: i18n.translate('home.tutorials.common.winlogbeatInstructions.start.windowsTitle', { defaultMessage: 'Start Winlogbeat', }), - textPre: i18n.translate('kbn.common.tutorials.winlogbeatInstructions.start.windowsTextPre', { + textPre: i18n.translate('home.tutorials.common.winlogbeatInstructions.start.windowsTextPre', { defaultMessage: 'The `setup` command loads the Kibana dashboards. If the dashboards are already set up, omit this command.', }), @@ -72,15 +73,18 @@ export const createWinlogbeatInstructions = context => ({ }, CONFIG: { WINDOWS: { - title: i18n.translate('kbn.common.tutorials.winlogbeatInstructions.config.windowsTitle', { + title: i18n.translate('home.tutorials.common.winlogbeatInstructions.config.windowsTitle', { defaultMessage: 'Edit the configuration', }), - textPre: i18n.translate('kbn.common.tutorials.winlogbeatInstructions.config.windowsTextPre', { - defaultMessage: 'Modify {path} to set the connection information:', - values: { - path: '`C:\\Program Files\\Winlogbeat\\winlogbeat.yml`', - }, - }), + textPre: i18n.translate( + 'home.tutorials.common.winlogbeatInstructions.config.windowsTextPre', + { + defaultMessage: 'Modify {path} to set the connection information:', + values: { + path: '`C:\\Program Files\\Winlogbeat\\winlogbeat.yml`', + }, + } + ), commands: [ 'output.elasticsearch:', ' hosts: [""]', @@ -91,7 +95,7 @@ export const createWinlogbeatInstructions = context => ({ getSpaceIdForBeatsTutorial(context), ], textPost: i18n.translate( - 'kbn.common.tutorials.winlogbeatInstructions.config.windowsTextPost', + 'home.tutorials.common.winlogbeatInstructions.config.windowsTextPost', { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user, {esUrlTemplate} is the URL of Elasticsearch, \ @@ -111,13 +115,13 @@ export const createWinlogbeatCloudInstructions = () => ({ CONFIG: { WINDOWS: { title: i18n.translate( - 'kbn.common.tutorials.winlogbeatCloudInstructions.config.windowsTitle', + 'home.tutorials.common.winlogbeatCloudInstructions.config.windowsTitle', { defaultMessage: 'Edit the configuration', } ), textPre: i18n.translate( - 'kbn.common.tutorials.winlogbeatCloudInstructions.config.windowsTextPre', + 'home.tutorials.common.winlogbeatCloudInstructions.config.windowsTextPre', { defaultMessage: 'Modify {path} to set the connection information for Elastic Cloud:', values: { @@ -127,7 +131,7 @@ export const createWinlogbeatCloudInstructions = () => ({ ), commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'], textPost: i18n.translate( - 'kbn.common.tutorials.winlogbeatCloudInstructions.config.windowsTextPost', + 'home.tutorials.common.winlogbeatCloudInstructions.config.windowsTextPost', { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.', values: { passwordTemplate: '``' }, @@ -139,19 +143,19 @@ export const createWinlogbeatCloudInstructions = () => ({ export function winlogbeatStatusCheck() { return { - title: i18n.translate('kbn.common.tutorials.winlogbeatStatusCheck.title', { + title: i18n.translate('home.tutorials.common.winlogbeatStatusCheck.title', { defaultMessage: 'Module status', }), - text: i18n.translate('kbn.common.tutorials.winlogbeatStatusCheck.text', { + text: i18n.translate('home.tutorials.common.winlogbeatStatusCheck.text', { defaultMessage: 'Check that data is received from Winlogbeat', }), - btnLabel: i18n.translate('kbn.common.tutorials.winlogbeatStatusCheck.buttonLabel', { + btnLabel: i18n.translate('home.tutorials.common.winlogbeatStatusCheck.buttonLabel', { defaultMessage: 'Check data', }), - success: i18n.translate('kbn.common.tutorials.winlogbeatStatusCheck.successText', { + success: i18n.translate('home.tutorials.common.winlogbeatStatusCheck.successText', { defaultMessage: 'Data successfully received', }), - error: i18n.translate('kbn.common.tutorials.winlogbeatStatusCheck.errorText', { + error: i18n.translate('home.tutorials.common.winlogbeatStatusCheck.errorText', { defaultMessage: 'No data has been received yet', }), esHitsCheck: { @@ -169,14 +173,14 @@ export function winlogbeatStatusCheck() { }; } -export function onPremInstructions(platforms, context) { +export function onPremInstructions(context?: TutorialContext) { const WINLOGBEAT_INSTRUCTIONS = createWinlogbeatInstructions(context); return { instructionSets: [ { title: i18n.translate( - 'kbn.common.tutorials.winlogbeat.premInstructions.gettingStarted.title', + 'home.tutorials.common.winlogbeat.premInstructions.gettingStarted.title', { defaultMessage: 'Getting Started', } @@ -206,7 +210,7 @@ export function onPremCloudInstructions() { instructionSets: [ { title: i18n.translate( - 'kbn.common.tutorials.winlogbeat.premCloudInstructions.gettingStarted.title', + 'home.tutorials.common.winlogbeat.premCloudInstructions.gettingStarted.title', { defaultMessage: 'Getting Started', } @@ -237,7 +241,7 @@ export function cloudInstructions() { instructionSets: [ { title: i18n.translate( - 'kbn.common.tutorials.winlogbeat.cloudInstructions.gettingStarted.title', + 'home.tutorials.common.winlogbeat.cloudInstructions.gettingStarted.title', { defaultMessage: 'Getting Started', } diff --git a/src/legacy/core_plugins/kibana/server/tutorials/iptables_logs/index.js b/src/plugins/home/server/tutorials/iptables_logs/index.ts similarity index 74% rename from src/legacy/core_plugins/kibana/server/tutorials/iptables_logs/index.js rename to src/plugins/home/server/tutorials/iptables_logs/index.ts index 63246e44d2d0d..e3f2124347b6b 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/iptables_logs/index.js +++ b/src/plugins/home/server/tutorials/iptables_logs/index.ts @@ -18,26 +18,30 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/filebeat_instructions'; +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function iptablesLogsSpecProvider(context) { +export function iptablesLogsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'iptables'; - const platforms = ['DEB', 'RPM']; + const platforms = ['DEB', 'RPM'] as const; return { id: 'iptablesLogs', - name: i18n.translate('kbn.server.tutorials.iptablesLogs.nameTitle', { + name: i18n.translate('home.tutorials.iptablesLogs.nameTitle', { defaultMessage: 'Iptables / Ubiquiti', }), - category: TUTORIAL_CATEGORY.SIEM, - shortDescription: i18n.translate('kbn.server.tutorials.iptablesLogs.shortDescription', { + category: TutorialsCategory.SIEM, + shortDescription: i18n.translate('home.tutorials.iptablesLogs.shortDescription', { defaultMessage: 'Collect and parse iptables and ip6tables logs or from Ubiqiti firewalls.', }), - longDescription: i18n.translate('kbn.server.tutorials.iptablesLogs.longDescription', { + longDescription: i18n.translate('home.tutorials.iptablesLogs.longDescription', { defaultMessage: 'This is a module for iptables and ip6tables logs. It parses logs \ received over the network via syslog or from a file. Also, it understands the \ @@ -48,12 +52,12 @@ number and the action performed on the traffic (allow/deny).. \ learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-iptables.html', }, }), - //euiIconType: 'logoUbiquiti', + // euiIconType: 'logoUbiquiti', artifacts: { dashboards: [], application: { path: '/app/siem', - label: i18n.translate('kbn.server.tutorials.iptablesLogs.artifacts.dashboards.linkLabel', { + label: i18n.translate('home.tutorials.iptablesLogs.artifacts.dashboards.linkLabel', { defaultMessage: 'SIEM App', }), }, diff --git a/src/legacy/core_plugins/kibana/server/tutorials/kafka_logs/index.js b/src/plugins/home/server/tutorials/kafka_logs/index.ts similarity index 71% rename from src/legacy/core_plugins/kibana/server/tutorials/kafka_logs/index.js rename to src/plugins/home/server/tutorials/kafka_logs/index.ts index 0d98aaa8a8ccf..74aa1ef772c85 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/kafka_logs/index.js +++ b/src/plugins/home/server/tutorials/kafka_logs/index.ts @@ -18,26 +18,30 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/filebeat_instructions'; +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function kafkaLogsSpecProvider(context) { +export function kafkaLogsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'kafka'; - const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS']; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; return { id: 'kafkaLogs', - name: i18n.translate('kbn.server.tutorials.kafkaLogs.nameTitle', { + name: i18n.translate('home.tutorials.kafkaLogs.nameTitle', { defaultMessage: 'Kafka logs', }), - category: TUTORIAL_CATEGORY.LOGGING, - shortDescription: i18n.translate('kbn.server.tutorials.kafkaLogs.shortDescription', { + category: TutorialsCategory.LOGGING, + shortDescription: i18n.translate('home.tutorials.kafkaLogs.shortDescription', { defaultMessage: 'Collect and parse logs created by Kafka.', }), - longDescription: i18n.translate('kbn.server.tutorials.kafkaLogs.longDescription', { + longDescription: i18n.translate('home.tutorials.kafkaLogs.longDescription', { defaultMessage: 'The `kafka` Filebeat module parses logs created by Kafka. \ [Learn more]({learnMoreLink}).', @@ -50,12 +54,9 @@ export function kafkaLogsSpecProvider(context) { dashboards: [ { id: '943caca0-87ee-11e7-ad9c-db80de0bf8d3-ecs', - linkLabel: i18n.translate( - 'kbn.server.tutorials.kafkaLogs.artifacts.dashboards.linkLabel', - { - defaultMessage: 'Kafka logs dashboard', - } - ), + linkLabel: i18n.translate('home.tutorials.kafkaLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'Kafka logs dashboard', + }), isOverview: true, }, ], diff --git a/src/legacy/core_plugins/kibana/server/tutorials/kafka_metrics/index.js b/src/plugins/home/server/tutorials/kafka_metrics/index.ts similarity index 71% rename from src/legacy/core_plugins/kibana/server/tutorials/kafka_metrics/index.js rename to src/plugins/home/server/tutorials/kafka_metrics/index.ts index 088ee876661bb..98be309ca89cd 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/kafka_metrics/index.js +++ b/src/plugins/home/server/tutorials/kafka_metrics/index.ts @@ -18,26 +18,30 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/metricbeat_instructions'; +} from '../instructions/metricbeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function kafkaMetricsSpecProvider(context) { +export function kafkaMetricsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'kafka'; return { id: 'kafkaMetrics', - name: i18n.translate('kbn.server.tutorials.kafkaMetrics.nameTitle', { + name: i18n.translate('home.tutorials.kafkaMetrics.nameTitle', { defaultMessage: 'Kafka metrics', }), isBeta: false, - category: TUTORIAL_CATEGORY.METRICS, - shortDescription: i18n.translate('kbn.server.tutorials.kafkaMetrics.shortDescription', { + category: TutorialsCategory.METRICS, + shortDescription: i18n.translate('home.tutorials.kafkaMetrics.shortDescription', { defaultMessage: 'Fetch internal metrics from the Kafka server.', }), - longDescription: i18n.translate('kbn.server.tutorials.kafkaMetrics.longDescription', { + longDescription: i18n.translate('home.tutorials.kafkaMetrics.longDescription', { defaultMessage: 'The `kafka` Metricbeat module fetches internal metrics from Kafka. \ [Learn more]({learnMoreLink}).', @@ -48,7 +52,7 @@ export function kafkaMetricsSpecProvider(context) { euiIconType: 'logoKafka', artifacts: { application: { - label: i18n.translate('kbn.server.tutorials.kafkaMetrics.artifacts.application.label', { + label: i18n.translate('home.tutorials.kafkaMetrics.artifacts.application.label', { defaultMessage: 'Discover', }), path: '/app/kibana#/discover', @@ -59,7 +63,7 @@ export function kafkaMetricsSpecProvider(context) { }, }, completionTimeMinutes: 10, - onPrem: onPremInstructions(moduleName, null, null, null, context), + onPrem: onPremInstructions(moduleName, context), elasticCloud: cloudInstructions(moduleName), onPremElasticCloud: onPremCloudInstructions(moduleName), }; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/kibana_metrics/index.js b/src/plugins/home/server/tutorials/kibana_metrics/index.ts similarity index 71% rename from src/legacy/core_plugins/kibana/server/tutorials/kibana_metrics/index.js rename to src/plugins/home/server/tutorials/kibana_metrics/index.ts index 64e07a87fe402..c087212c46f78 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/kibana_metrics/index.js +++ b/src/plugins/home/server/tutorials/kibana_metrics/index.ts @@ -18,26 +18,30 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/metricbeat_instructions'; +} from '../instructions/metricbeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function kibanaMetricsSpecProvider(context) { +export function kibanaMetricsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'kibana'; return { id: 'kibanaMetrics', - name: i18n.translate('kbn.server.tutorials.kibanaMetrics.nameTitle', { + name: i18n.translate('home.tutorials.kibanaMetrics.nameTitle', { defaultMessage: 'Kibana metrics', }), isBeta: false, - category: TUTORIAL_CATEGORY.METRICS, - shortDescription: i18n.translate('kbn.server.tutorials.kibanaMetrics.shortDescription', { + category: TutorialsCategory.METRICS, + shortDescription: i18n.translate('home.tutorials.kibanaMetrics.shortDescription', { defaultMessage: 'Fetch internal metrics from Kibana.', }), - longDescription: i18n.translate('kbn.server.tutorials.kibanaMetrics.longDescription', { + longDescription: i18n.translate('home.tutorials.kibanaMetrics.longDescription', { defaultMessage: 'The `kibana` Metricbeat module fetches internal metrics from Kibana. \ [Learn more]({learnMoreLink}).', @@ -48,7 +52,7 @@ export function kibanaMetricsSpecProvider(context) { euiIconType: 'logoKibana', artifacts: { application: { - label: i18n.translate('kbn.server.tutorials.kibanaMetrics.artifacts.application.label', { + label: i18n.translate('home.tutorials.kibanaMetrics.artifacts.application.label', { defaultMessage: 'Discover', }), path: '/app/kibana#/discover', @@ -59,7 +63,7 @@ export function kibanaMetricsSpecProvider(context) { }, }, completionTimeMinutes: 10, - onPrem: onPremInstructions(moduleName, null, null, null, context), + onPrem: onPremInstructions(moduleName, context), elasticCloud: cloudInstructions(moduleName), onPremElasticCloud: onPremCloudInstructions(moduleName), }; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/kubernetes_metrics/index.js b/src/plugins/home/server/tutorials/kubernetes_metrics/index.ts similarity index 73% rename from src/legacy/core_plugins/kibana/server/tutorials/kubernetes_metrics/index.js rename to src/plugins/home/server/tutorials/kubernetes_metrics/index.ts index 4f59816282538..466f713d35e06 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/kubernetes_metrics/index.js +++ b/src/plugins/home/server/tutorials/kubernetes_metrics/index.ts @@ -18,25 +18,29 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/metricbeat_instructions'; +} from '../instructions/metricbeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function kubernetesMetricsSpecProvider(context) { +export function kubernetesMetricsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'kubernetes'; return { id: 'kubernetesMetrics', - name: i18n.translate('kbn.server.tutorials.kubernetesMetrics.nameTitle', { + name: i18n.translate('home.tutorials.kubernetesMetrics.nameTitle', { defaultMessage: 'Kubernetes metrics', }), - category: TUTORIAL_CATEGORY.METRICS, - shortDescription: i18n.translate('kbn.server.tutorials.kubernetesMetrics.shortDescription', { + category: TutorialsCategory.METRICS, + shortDescription: i18n.translate('home.tutorials.kubernetesMetrics.shortDescription', { defaultMessage: 'Fetch metrics from your Kubernetes installation.', }), - longDescription: i18n.translate('kbn.server.tutorials.kubernetesMetrics.longDescription', { + longDescription: i18n.translate('home.tutorials.kubernetesMetrics.longDescription', { defaultMessage: 'The `kubernetes` Metricbeat module fetches metrics from the Kubernetes APIs. \ [Learn more]({learnMoreLink}).', @@ -50,7 +54,7 @@ export function kubernetesMetricsSpecProvider(context) { { id: 'AV4RGUqo5NkDleZmzKuZ-ecs', linkLabel: i18n.translate( - 'kbn.server.tutorials.kubernetesMetrics.artifacts.dashboards.linkLabel', + 'home.tutorials.kubernetesMetrics.artifacts.dashboards.linkLabel', { defaultMessage: 'Kubernetes metrics dashboard', } @@ -64,7 +68,7 @@ export function kubernetesMetricsSpecProvider(context) { }, completionTimeMinutes: 10, previewImagePath: '/plugins/kibana/home/tutorial_resources/kubernetes_metrics/screenshot.png', - onPrem: onPremInstructions(moduleName, null, null, null, context), + onPrem: onPremInstructions(moduleName, context), elasticCloud: cloudInstructions(moduleName), onPremElasticCloud: onPremCloudInstructions(moduleName), }; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/logstash_logs/index.js b/src/plugins/home/server/tutorials/logstash_logs/index.ts similarity index 71% rename from src/legacy/core_plugins/kibana/server/tutorials/logstash_logs/index.js rename to src/plugins/home/server/tutorials/logstash_logs/index.ts index df0da000bc37a..276ceedbbcc68 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/logstash_logs/index.js +++ b/src/plugins/home/server/tutorials/logstash_logs/index.ts @@ -18,26 +18,30 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/filebeat_instructions'; +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function logstashLogsSpecProvider(context) { +export function logstashLogsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'logstash'; - const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS']; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; return { id: 'logstashLogs', - name: i18n.translate('kbn.server.tutorials.logstashLogs.nameTitle', { + name: i18n.translate('home.tutorials.logstashLogs.nameTitle', { defaultMessage: 'Logstash logs', }), - category: TUTORIAL_CATEGORY.LOGGING, - shortDescription: i18n.translate('kbn.server.tutorials.logstashLogs.shortDescription', { + category: TutorialsCategory.LOGGING, + shortDescription: i18n.translate('home.tutorials.logstashLogs.shortDescription', { defaultMessage: 'Collect and parse debug and slow logs created by Logstash itself.', }), - longDescription: i18n.translate('kbn.server.tutorials.logstashLogs.longDescription', { + longDescription: i18n.translate('home.tutorials.logstashLogs.longDescription', { defaultMessage: 'The `logstash` Filebeat module parses debug and slow logs created by Logstash itself. \ [Learn more]({learnMoreLink}).', @@ -50,12 +54,9 @@ export function logstashLogsSpecProvider(context) { dashboards: [ { id: 'Filebeat-Logstash-Log-Dashboard-ecs', - linkLabel: i18n.translate( - 'kbn.server.tutorials.logstashLogs.artifacts.dashboards.linkLabel', - { - defaultMessage: 'Logstash logs dashboard', - } - ), + linkLabel: i18n.translate('home.tutorials.logstashLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'Logstash logs dashboard', + }), isOverview: true, }, ], diff --git a/src/legacy/core_plugins/kibana/server/tutorials/logstash_metrics/index.js b/src/plugins/home/server/tutorials/logstash_metrics/index.ts similarity index 71% rename from src/legacy/core_plugins/kibana/server/tutorials/logstash_metrics/index.js rename to src/plugins/home/server/tutorials/logstash_metrics/index.ts index 1ccafa4762e65..122f143e4b834 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/logstash_metrics/index.js +++ b/src/plugins/home/server/tutorials/logstash_metrics/index.ts @@ -18,26 +18,30 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/metricbeat_instructions'; +} from '../instructions/metricbeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function logstashMetricsSpecProvider(context) { +export function logstashMetricsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'logstash'; return { id: moduleName + 'Metrics', - name: i18n.translate('kbn.server.tutorials.logstashMetrics.nameTitle', { + name: i18n.translate('home.tutorials.logstashMetrics.nameTitle', { defaultMessage: 'Logstash metrics', }), isBeta: false, - category: TUTORIAL_CATEGORY.METRICS, - shortDescription: i18n.translate('kbn.server.tutorials.logstashMetrics.shortDescription', { + category: TutorialsCategory.METRICS, + shortDescription: i18n.translate('home.tutorials.logstashMetrics.shortDescription', { defaultMessage: 'Fetch internal metrics from a Logstash server.', }), - longDescription: i18n.translate('kbn.server.tutorials.logstashMetrics.longDescription', { + longDescription: i18n.translate('home.tutorials.logstashMetrics.longDescription', { defaultMessage: 'The `{moduleName}` Metricbeat module fetches internal metrics from a Logstash server. \ [Learn more]({learnMoreLink}).', @@ -49,7 +53,7 @@ export function logstashMetricsSpecProvider(context) { euiIconType: 'logoLogstash', artifacts: { application: { - label: i18n.translate('kbn.server.tutorials.logstashMetrics.artifacts.application.label', { + label: i18n.translate('home.tutorials.logstashMetrics.artifacts.application.label', { defaultMessage: 'Discover', }), path: '/app/kibana#/discover', @@ -60,7 +64,7 @@ export function logstashMetricsSpecProvider(context) { }, }, completionTimeMinutes: 10, - onPrem: onPremInstructions(moduleName, null, null, null, context), + onPrem: onPremInstructions(moduleName, context), elasticCloud: cloudInstructions(moduleName), onPremElasticCloud: onPremCloudInstructions(moduleName), }; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/memcached_metrics/index.js b/src/plugins/home/server/tutorials/memcached_metrics/index.ts similarity index 71% rename from src/legacy/core_plugins/kibana/server/tutorials/memcached_metrics/index.js rename to src/plugins/home/server/tutorials/memcached_metrics/index.ts index 8a9f6bfe8b439..1c9c9d975e4b8 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/memcached_metrics/index.js +++ b/src/plugins/home/server/tutorials/memcached_metrics/index.ts @@ -18,26 +18,30 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/metricbeat_instructions'; +} from '../instructions/metricbeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function memcachedMetricsSpecProvider(context) { +export function memcachedMetricsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'memcached'; return { id: 'memcachedMetrics', - name: i18n.translate('kbn.server.tutorials.memcachedMetrics.nameTitle', { + name: i18n.translate('home.tutorials.memcachedMetrics.nameTitle', { defaultMessage: 'Memcached metrics', }), isBeta: false, - category: TUTORIAL_CATEGORY.METRICS, - shortDescription: i18n.translate('kbn.server.tutorials.memcachedMetrics.shortDescription', { + category: TutorialsCategory.METRICS, + shortDescription: i18n.translate('home.tutorials.memcachedMetrics.shortDescription', { defaultMessage: 'Fetch internal metrics from the Memcached server.', }), - longDescription: i18n.translate('kbn.server.tutorials.memcachedMetrics.longDescription', { + longDescription: i18n.translate('home.tutorials.memcachedMetrics.longDescription', { defaultMessage: 'The `memcached` Metricbeat module fetches internal metrics from Memcached. \ [Learn more]({learnMoreLink}).', @@ -48,7 +52,7 @@ export function memcachedMetricsSpecProvider(context) { euiIconType: 'logoMemcached', artifacts: { application: { - label: i18n.translate('kbn.server.tutorials.memcachedMetrics.artifacts.application.label', { + label: i18n.translate('home.tutorials.memcachedMetrics.artifacts.application.label', { defaultMessage: 'Discover', }), path: '/app/kibana#/discover', @@ -59,7 +63,7 @@ export function memcachedMetricsSpecProvider(context) { }, }, completionTimeMinutes: 10, - onPrem: onPremInstructions(moduleName, null, null, null, context), + onPrem: onPremInstructions(moduleName, context), elasticCloud: cloudInstructions(moduleName), onPremElasticCloud: onPremCloudInstructions(moduleName), }; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/mongodb_metrics/index.js b/src/plugins/home/server/tutorials/mongodb_metrics/index.ts similarity index 73% rename from src/legacy/core_plugins/kibana/server/tutorials/mongodb_metrics/index.js rename to src/plugins/home/server/tutorials/mongodb_metrics/index.ts index 1e19650af73fd..1a10dc3849471 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/mongodb_metrics/index.js +++ b/src/plugins/home/server/tutorials/mongodb_metrics/index.ts @@ -18,25 +18,29 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/metricbeat_instructions'; +} from '../instructions/metricbeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function mongodbMetricsSpecProvider(context) { +export function mongodbMetricsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'mongodb'; return { id: 'mongodbMetrics', - name: i18n.translate('kbn.server.tutorials.mongodbMetrics.nameTitle', { + name: i18n.translate('home.tutorials.mongodbMetrics.nameTitle', { defaultMessage: 'MongoDB metrics', }), - category: TUTORIAL_CATEGORY.METRICS, - shortDescription: i18n.translate('kbn.server.tutorials.mongodbMetrics.shortDescription', { + category: TutorialsCategory.METRICS, + shortDescription: i18n.translate('home.tutorials.mongodbMetrics.shortDescription', { defaultMessage: 'Fetch internal metrics from MongoDB.', }), - longDescription: i18n.translate('kbn.server.tutorials.mongodbMetrics.longDescription', { + longDescription: i18n.translate('home.tutorials.mongodbMetrics.longDescription', { defaultMessage: 'The `mongodb` Metricbeat module fetches internal metrics from the MongoDB server. \ [Learn more]({learnMoreLink}).', @@ -50,7 +54,7 @@ export function mongodbMetricsSpecProvider(context) { { id: 'Metricbeat-MongoDB-ecs', linkLabel: i18n.translate( - 'kbn.server.tutorials.mongodbMetrics.artifacts.dashboards.linkLabel', + 'home.tutorials.mongodbMetrics.artifacts.dashboards.linkLabel', { defaultMessage: 'MongoDB metrics dashboard', } @@ -64,7 +68,7 @@ export function mongodbMetricsSpecProvider(context) { }, completionTimeMinutes: 10, previewImagePath: '/plugins/kibana/home/tutorial_resources/mongodb_metrics/screenshot.png', - onPrem: onPremInstructions(moduleName, null, null, null, context), + onPrem: onPremInstructions(moduleName, context), elasticCloud: cloudInstructions(moduleName), onPremElasticCloud: onPremCloudInstructions(moduleName), }; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/mssql_metrics/index.js b/src/plugins/home/server/tutorials/mssql_metrics/index.ts similarity index 70% rename from src/legacy/core_plugins/kibana/server/tutorials/mssql_metrics/index.js rename to src/plugins/home/server/tutorials/mssql_metrics/index.ts index d12964e122bc6..cea46c3220d5a 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/mssql_metrics/index.js +++ b/src/plugins/home/server/tutorials/mssql_metrics/index.ts @@ -18,25 +18,29 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/metricbeat_instructions'; +} from '../instructions/metricbeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function mssqlMetricsSpecProvider(context) { +export function mssqlMetricsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'mssql'; return { id: 'mssqlMetrics', - name: i18n.translate('kbn.server.tutorials.mssqlMetrics.nameTitle', { + name: i18n.translate('home.tutorials.mssqlMetrics.nameTitle', { defaultMessage: 'Microsoft SQL Server Metrics', }), - category: TUTORIAL_CATEGORY.METRICS, - shortDescription: i18n.translate('kbn.server.tutorials.mssqlMetrics.shortDescription', { + category: TutorialsCategory.METRICS, + shortDescription: i18n.translate('home.tutorials.mssqlMetrics.shortDescription', { defaultMessage: 'Fetch monitoring metrics from a Microsoft SQL Server instance', }), - longDescription: i18n.translate('kbn.server.tutorials.mssqlMetrics.longDescription', { + longDescription: i18n.translate('home.tutorials.mssqlMetrics.longDescription', { defaultMessage: 'The `mssql` Metricbeat module fetches monitoring, log and performance metrics from a Microsoft SQL Server instance. \ [Learn more]({learnMoreLink}).', @@ -50,12 +54,9 @@ export function mssqlMetricsSpecProvider(context) { dashboards: [ { id: 'a2ead240-18bb-11e9-9836-f37dedd3b411-ecs', - linkLabel: i18n.translate( - 'kbn.server.tutorials.mssqlMetrics.artifacts.dashboards.linkLabel', - { - defaultMessage: 'Microsoft SQL Server metrics dashboard', - } - ), + linkLabel: i18n.translate('home.tutorials.mssqlMetrics.artifacts.dashboards.linkLabel', { + defaultMessage: 'Microsoft SQL Server metrics dashboard', + }), isOverview: true, }, ], @@ -65,7 +66,7 @@ export function mssqlMetricsSpecProvider(context) { }, completionTimeMinutes: 10, previewImagePath: '/plugins/kibana/home/tutorial_resources/mssql_metrics/screenshot.png', - onPrem: onPremInstructions(moduleName, null, null, null, context), + onPrem: onPremInstructions(moduleName, context), elasticCloud: cloudInstructions(moduleName), onPremElasticCloud: onPremCloudInstructions(moduleName), }; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/munin_metrics/index.js b/src/plugins/home/server/tutorials/munin_metrics/index.ts similarity index 71% rename from src/legacy/core_plugins/kibana/server/tutorials/munin_metrics/index.js rename to src/plugins/home/server/tutorials/munin_metrics/index.ts index 627c2ddd7dc5b..e438d3015c77c 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/munin_metrics/index.js +++ b/src/plugins/home/server/tutorials/munin_metrics/index.ts @@ -18,26 +18,30 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/metricbeat_instructions'; +} from '../instructions/metricbeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function muninMetricsSpecProvider(context) { +export function muninMetricsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'munin'; return { id: 'muninMetrics', - name: i18n.translate('kbn.server.tutorials.muninMetrics.nameTitle', { + name: i18n.translate('home.tutorials.muninMetrics.nameTitle', { defaultMessage: 'Munin metrics', }), isBeta: true, - category: TUTORIAL_CATEGORY.METRICS, - shortDescription: i18n.translate('kbn.server.tutorials.muninMetrics.shortDescription', { + category: TutorialsCategory.METRICS, + shortDescription: i18n.translate('home.tutorials.muninMetrics.shortDescription', { defaultMessage: 'Fetch internal metrics from the Munin server.', }), - longDescription: i18n.translate('kbn.server.tutorials.muninMetrics.longDescription', { + longDescription: i18n.translate('home.tutorials.muninMetrics.longDescription', { defaultMessage: 'The `munin` Metricbeat module fetches internal metrics from Munin. \ [Learn more]({learnMoreLink}).', @@ -47,7 +51,7 @@ export function muninMetricsSpecProvider(context) { }), artifacts: { application: { - label: i18n.translate('kbn.server.tutorials.muninMetrics.artifacts.application.label', { + label: i18n.translate('home.tutorials.muninMetrics.artifacts.application.label', { defaultMessage: 'Discover', }), path: '/app/kibana#/discover', @@ -58,7 +62,7 @@ export function muninMetricsSpecProvider(context) { }, }, completionTimeMinutes: 10, - onPrem: onPremInstructions(moduleName, null, null, null, context), + onPrem: onPremInstructions(moduleName, context), elasticCloud: cloudInstructions(moduleName), onPremElasticCloud: onPremCloudInstructions(moduleName), }; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/mysql_logs/index.js b/src/plugins/home/server/tutorials/mysql_logs/index.ts similarity index 71% rename from src/legacy/core_plugins/kibana/server/tutorials/mysql_logs/index.js rename to src/plugins/home/server/tutorials/mysql_logs/index.ts index 796c03c9b19e2..e003f4dfd47e4 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/mysql_logs/index.js +++ b/src/plugins/home/server/tutorials/mysql_logs/index.ts @@ -18,26 +18,30 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/filebeat_instructions'; +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function mysqlLogsSpecProvider(context) { +export function mysqlLogsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'mysql'; - const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS']; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; return { id: 'mysqlLogs', - name: i18n.translate('kbn.server.tutorials.mysqlLogs.nameTitle', { + name: i18n.translate('home.tutorials.mysqlLogs.nameTitle', { defaultMessage: 'MySQL logs', }), - category: TUTORIAL_CATEGORY.LOGGING, - shortDescription: i18n.translate('kbn.server.tutorials.mysqlLogs.shortDescription', { + category: TutorialsCategory.LOGGING, + shortDescription: i18n.translate('home.tutorials.mysqlLogs.shortDescription', { defaultMessage: 'Collect and parse error and slow logs created by MySQL.', }), - longDescription: i18n.translate('kbn.server.tutorials.mysqlLogs.longDescription', { + longDescription: i18n.translate('home.tutorials.mysqlLogs.longDescription', { defaultMessage: 'The `mysql` Filebeat module parses error and slow logs created by MySQL. \ [Learn more]({learnMoreLink}).', @@ -50,12 +54,9 @@ export function mysqlLogsSpecProvider(context) { dashboards: [ { id: 'Filebeat-MySQL-Dashboard-ecs', - linkLabel: i18n.translate( - 'kbn.server.tutorials.mysqlLogs.artifacts.dashboards.linkLabel', - { - defaultMessage: 'MySQL logs dashboard', - } - ), + linkLabel: i18n.translate('home.tutorials.mysqlLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'MySQL logs dashboard', + }), isOverview: true, }, ], diff --git a/src/legacy/core_plugins/kibana/server/tutorials/mysql_metrics/index.js b/src/plugins/home/server/tutorials/mysql_metrics/index.ts similarity index 70% rename from src/legacy/core_plugins/kibana/server/tutorials/mysql_metrics/index.js rename to src/plugins/home/server/tutorials/mysql_metrics/index.ts index 55087d03237d8..d18cc31512e71 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/mysql_metrics/index.js +++ b/src/plugins/home/server/tutorials/mysql_metrics/index.ts @@ -18,25 +18,29 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/metricbeat_instructions'; +} from '../instructions/metricbeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function mysqlMetricsSpecProvider(context) { +export function mysqlMetricsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'mysql'; return { id: 'mysqlMetrics', - name: i18n.translate('kbn.server.tutorials.mysqlMetrics.nameTitle', { + name: i18n.translate('home.tutorials.mysqlMetrics.nameTitle', { defaultMessage: 'MySQL metrics', }), - category: TUTORIAL_CATEGORY.METRICS, - shortDescription: i18n.translate('kbn.server.tutorials.mysqlMetrics.shortDescription', { + category: TutorialsCategory.METRICS, + shortDescription: i18n.translate('home.tutorials.mysqlMetrics.shortDescription', { defaultMessage: 'Fetch internal metrics from MySQL.', }), - longDescription: i18n.translate('kbn.server.tutorials.mysqlMetrics.longDescription', { + longDescription: i18n.translate('home.tutorials.mysqlMetrics.longDescription', { defaultMessage: 'The `mysql` Metricbeat module fetches internal metrics from the MySQL server. \ [Learn more]({learnMoreLink}).', @@ -49,12 +53,9 @@ export function mysqlMetricsSpecProvider(context) { dashboards: [ { id: '66881e90-0006-11e7-bf7f-c9acc3d3e306-ecs', - linkLabel: i18n.translate( - 'kbn.server.tutorials.mysqlMetrics.artifacts.dashboards.linkLabel', - { - defaultMessage: 'MySQL metrics dashboard', - } - ), + linkLabel: i18n.translate('home.tutorials.mysqlMetrics.artifacts.dashboards.linkLabel', { + defaultMessage: 'MySQL metrics dashboard', + }), isOverview: true, }, ], @@ -64,7 +65,7 @@ export function mysqlMetricsSpecProvider(context) { }, completionTimeMinutes: 10, previewImagePath: '/plugins/kibana/home/tutorial_resources/mysql_metrics/screenshot.png', - onPrem: onPremInstructions(moduleName, null, null, null, context), + onPrem: onPremInstructions(moduleName, context), elasticCloud: cloudInstructions(moduleName), onPremElasticCloud: onPremCloudInstructions(moduleName), }; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/nats_logs/index.js b/src/plugins/home/server/tutorials/nats_logs/index.ts similarity index 69% rename from src/legacy/core_plugins/kibana/server/tutorials/nats_logs/index.js rename to src/plugins/home/server/tutorials/nats_logs/index.ts index bbf221c91d5e2..3f6cb36d8d49e 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/nats_logs/index.js +++ b/src/plugins/home/server/tutorials/nats_logs/index.ts @@ -18,29 +18,31 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/filebeat_instructions'; +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function natsLogsSpecProvider(context) { +export function natsLogsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'nats'; - const geoipRequired = false; - const uaRequired = false; - const platforms = ['DEB', 'RPM']; + const platforms = ['DEB', 'RPM'] as const; return { id: 'natsLogs', - name: i18n.translate('kbn.server.tutorials.natsLogs.nameTitle', { + name: i18n.translate('home.tutorials.natsLogs.nameTitle', { defaultMessage: 'NATS logs', }), - category: TUTORIAL_CATEGORY.LOGGING, + category: TutorialsCategory.LOGGING, isBeta: true, - shortDescription: i18n.translate('kbn.server.tutorials.natsLogs.shortDescription', { + shortDescription: i18n.translate('home.tutorials.natsLogs.shortDescription', { defaultMessage: 'Collect and parse logs created by Nats.', }), - longDescription: i18n.translate('kbn.server.tutorials.natsLogs.longDescription', { + longDescription: i18n.translate('home.tutorials.natsLogs.longDescription', { defaultMessage: 'The `nats` Filebeat module parses logs created by Nats. \ [Learn more]({learnMoreLink}).', @@ -53,12 +55,9 @@ export function natsLogsSpecProvider(context) { dashboards: [ { id: 'Filebeat-nats-overview-ecs', - linkLabel: i18n.translate( - 'kbn.server.tutorials.natsLogs.artifacts.dashboards.linkLabel', - { - defaultMessage: 'NATS logs dashboard', - } - ), + linkLabel: i18n.translate('home.tutorials.natsLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'NATS logs dashboard', + }), isOverview: true, }, ], @@ -68,7 +67,7 @@ export function natsLogsSpecProvider(context) { }, completionTimeMinutes: 10, previewImagePath: '/plugins/kibana/home/tutorial_resources/nats_logs/screenshot.png', - onPrem: onPremInstructions(moduleName, platforms, geoipRequired, uaRequired, context), + onPrem: onPremInstructions(moduleName, platforms, context), elasticCloud: cloudInstructions(moduleName, platforms), onPremElasticCloud: onPremCloudInstructions(moduleName, platforms), }; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/nats_metrics/index.js b/src/plugins/home/server/tutorials/nats_metrics/index.ts similarity index 70% rename from src/legacy/core_plugins/kibana/server/tutorials/nats_metrics/index.js rename to src/plugins/home/server/tutorials/nats_metrics/index.ts index 54f01b70e2d15..27b5507ff6672 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/nats_metrics/index.js +++ b/src/plugins/home/server/tutorials/nats_metrics/index.ts @@ -18,25 +18,29 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/metricbeat_instructions'; +} from '../instructions/metricbeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function natsMetricsSpecProvider(context) { +export function natsMetricsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'nats'; return { id: 'natsMetrics', - name: i18n.translate('kbn.server.tutorials.natsMetrics.nameTitle', { + name: i18n.translate('home.tutorials.natsMetrics.nameTitle', { defaultMessage: 'NATS metrics', }), - category: TUTORIAL_CATEGORY.METRICS, - shortDescription: i18n.translate('kbn.server.tutorials.natsMetrics.shortDescription', { + category: TutorialsCategory.METRICS, + shortDescription: i18n.translate('home.tutorials.natsMetrics.shortDescription', { defaultMessage: 'Fetch monitoring metrics from the Nats server.', }), - longDescription: i18n.translate('kbn.server.tutorials.natsMetrics.longDescription', { + longDescription: i18n.translate('home.tutorials.natsMetrics.longDescription', { defaultMessage: 'The `nats` Metricbeat module fetches monitoring metrics from Nats. \ [Learn more]({learnMoreLink}).', @@ -49,12 +53,9 @@ export function natsMetricsSpecProvider(context) { dashboards: [ { id: 'Metricbeat-Nats-Dashboard-ecs', - linkLabel: i18n.translate( - 'kbn.server.tutorials.natsMetrics.artifacts.dashboards.linkLabel', - { - defaultMessage: 'NATS metrics dashboard', - } - ), + linkLabel: i18n.translate('home.tutorials.natsMetrics.artifacts.dashboards.linkLabel', { + defaultMessage: 'NATS metrics dashboard', + }), isOverview: true, }, ], @@ -64,7 +65,7 @@ export function natsMetricsSpecProvider(context) { }, completionTimeMinutes: 10, previewImagePath: '/plugins/kibana/home/tutorial_resources/nats_metrics/screenshot.png', - onPrem: onPremInstructions(moduleName, null, null, null, context), + onPrem: onPremInstructions(moduleName, context), elasticCloud: cloudInstructions(moduleName), onPremElasticCloud: onPremCloudInstructions(moduleName), }; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/netflow/common_instructions.js b/src/plugins/home/server/tutorials/netflow/common_instructions.ts similarity index 72% rename from src/legacy/core_plugins/kibana/server/tutorials/netflow/common_instructions.js rename to src/plugins/home/server/tutorials/netflow/common_instructions.ts index 194e315d92ed7..8fe24ba9c7994 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/netflow/common_instructions.js +++ b/src/plugins/home/server/tutorials/netflow/common_instructions.ts @@ -25,51 +25,39 @@ export function createCommonNetflowInstructions() { ON_PREM: { OSX: [ { - title: i18n.translate('kbn.server.tutorials.netflow.common.config.onPrem.osxTitle', { + title: i18n.translate('home.tutorials.netflow.common.config.onPrem.osxTitle', { defaultMessage: 'Edit the configuration', }), - textPre: i18n.translate( - 'kbn.server.tutorials.netflow.common.config.onPrem.osxTextPre', - { - defaultMessage: 'Modify {logstashConfigPath} to set the configuration parameters:', - values: { - logstashConfigPath: '`config/logstash.yml`', - }, - } - ), + textPre: i18n.translate('home.tutorials.netflow.common.config.onPrem.osxTextPre', { + defaultMessage: 'Modify {logstashConfigPath} to set the configuration parameters:', + values: { + logstashConfigPath: '`config/logstash.yml`', + }, + }), commands: ['modules:', ' - name: netflow', ' var.input.udp.port: '], - textPost: i18n.translate( - 'kbn.server.tutorials.netflow.common.config.onPrem.osxTextPost', - { - defaultMessage: - 'Where {udpPort} is the UDP port on which Logstash will receive Netflow data.', - values: { - udpPort: '``', - }, - } - ), + textPost: i18n.translate('home.tutorials.netflow.common.config.onPrem.osxTextPost', { + defaultMessage: + 'Where {udpPort} is the UDP port on which Logstash will receive Netflow data.', + values: { + udpPort: '``', + }, + }), }, ], WINDOWS: [ { - title: i18n.translate( - 'kbn.server.tutorials.netflow.common.config.onPrem.windowsTitle', - { - defaultMessage: 'Edit the configuration', - } - ), - textPre: i18n.translate( - 'kbn.server.tutorials.netflow.common.config.onPrem.windowsTextPre', - { - defaultMessage: 'Modify {logstashConfigPath} to set the configuration parameters:', - values: { - logstashConfigPath: '`config\\logstash.yml`', - }, - } - ), + title: i18n.translate('home.tutorials.netflow.common.config.onPrem.windowsTitle', { + defaultMessage: 'Edit the configuration', + }), + textPre: i18n.translate('home.tutorials.netflow.common.config.onPrem.windowsTextPre', { + defaultMessage: 'Modify {logstashConfigPath} to set the configuration parameters:', + values: { + logstashConfigPath: '`config\\logstash.yml`', + }, + }), commands: ['modules:', ' - name: netflow', ' var.input.udp.port: '], textPost: i18n.translate( - 'kbn.server.tutorials.netflow.common.config.onPrem.windowsTextPost', + 'home.tutorials.netflow.common.config.onPrem.windowsTextPost', { defaultMessage: 'Where {udpPort} is the UDP port on which Logstash will receive Netflow data.', @@ -85,13 +73,13 @@ export function createCommonNetflowInstructions() { OSX: [ { title: i18n.translate( - 'kbn.server.tutorials.netflow.common.config.onPremElasticCloud.osxTitle', + 'home.tutorials.netflow.common.config.onPremElasticCloud.osxTitle', { defaultMessage: 'Edit the configuration', } ), textPre: i18n.translate( - 'kbn.server.tutorials.netflow.common.config.onPremElasticCloud.osxTextPre', + 'home.tutorials.netflow.common.config.onPremElasticCloud.osxTextPre', { defaultMessage: 'Modify {logstashConfigPath} to set the configuration parameters:', values: { @@ -108,7 +96,7 @@ export function createCommonNetflowInstructions() { ' var.elasticsearch.password: ', ], textPost: i18n.translate( - 'kbn.server.tutorials.netflow.common.config.onPremElasticCloud.osxTextPost', + 'home.tutorials.netflow.common.config.onPremElasticCloud.osxTextPost', { defaultMessage: 'Where {udpPort} is the UDP port on which Logstash will receive Netflow data, \ @@ -127,13 +115,13 @@ export function createCommonNetflowInstructions() { WINDOWS: [ { title: i18n.translate( - 'kbn.server.tutorials.netflow.common.config.onPremElasticCloud.windowsTitle', + 'home.tutorials.netflow.common.config.onPremElasticCloud.windowsTitle', { defaultMessage: 'Edit the configuration', } ), textPre: i18n.translate( - 'kbn.server.tutorials.netflow.common.config.onPremElasticCloud.windowsTextPre', + 'home.tutorials.netflow.common.config.onPremElasticCloud.windowsTextPre', { defaultMessage: 'Modify {logstashConfigPath} to set the configuration parameters:', values: { @@ -150,7 +138,7 @@ export function createCommonNetflowInstructions() { ' var.elasticsearch.password: ', ], textPost: i18n.translate( - 'kbn.server.tutorials.netflow.common.config.onPremElasticCloud.windowsTextPost', + 'home.tutorials.netflow.common.config.onPremElasticCloud.windowsTextPost', { defaultMessage: 'Where {udpPort} is the UDP port on which Logstash will receive Netflow data, \ @@ -170,14 +158,11 @@ export function createCommonNetflowInstructions() { ELASTIC_CLOUD: { OSX: [ { - title: i18n.translate( - 'kbn.server.tutorials.netflow.common.config.elasticCloud.osxTitle', - { - defaultMessage: 'Edit the configuration', - } - ), + title: i18n.translate('home.tutorials.netflow.common.config.elasticCloud.osxTitle', { + defaultMessage: 'Edit the configuration', + }), textPre: i18n.translate( - 'kbn.server.tutorials.netflow.common.config.elasticCloud.osxTextPre', + 'home.tutorials.netflow.common.config.elasticCloud.osxTextPre', { defaultMessage: 'Modify {logstashConfigPath} to set the configuration parameters:', values: { @@ -194,7 +179,7 @@ export function createCommonNetflowInstructions() { ' var.input.udp.port: ', ], textPost: i18n.translate( - 'kbn.server.tutorials.netflow.common.config.elasticCloud.osxTextPost', + 'home.tutorials.netflow.common.config.elasticCloud.osxTextPost', { defaultMessage: 'Where {udpPort} is the UDP port on which Logstash will receive Netflow data and \ @@ -211,13 +196,13 @@ export function createCommonNetflowInstructions() { WINDOWS: [ { title: i18n.translate( - 'kbn.server.tutorials.netflow.common.config.elasticCloud.windowsTitle', + 'home.tutorials.netflow.common.config.elasticCloud.windowsTitle', { defaultMessage: 'Edit the configuration', } ), textPre: i18n.translate( - 'kbn.server.tutorials.netflow.common.config.elasticCloud.windowsTextPre', + 'home.tutorials.netflow.common.config.elasticCloud.windowsTextPre', { defaultMessage: 'Modify {logstashConfigPath} to set the configuration parameters:', values: { @@ -234,7 +219,7 @@ export function createCommonNetflowInstructions() { ' var.input.udp.port: ', ], textPost: i18n.translate( - 'kbn.server.tutorials.netflow.common.config.elasticCloud.windowsTextPost', + 'home.tutorials.netflow.common.config.elasticCloud.windowsTextPost', { defaultMessage: 'Where {udpPort} is the UDP port on which Logstash will receive Netflow data and \ @@ -253,14 +238,14 @@ export function createCommonNetflowInstructions() { SETUP: { OSX: [ { - title: i18n.translate('kbn.server.tutorials.netflow.common.setup.osxTitle', { + title: i18n.translate('home.tutorials.netflow.common.setup.osxTitle', { defaultMessage: 'Run the Netflow module', }), - textPre: i18n.translate('kbn.server.tutorials.netflow.common.setup.osxTextPre', { + textPre: i18n.translate('home.tutorials.netflow.common.setup.osxTextPre', { defaultMessage: 'Run:', }), commands: ['./bin/logstash --modules netflow --setup'], - textPost: i18n.translate('kbn.server.tutorials.netflow.common.setup.osxTextPost', { + textPost: i18n.translate('home.tutorials.netflow.common.setup.osxTextPost', { defaultMessage: 'The {setupOption} option creates a {netflowPrefix} index pattern in Elasticsearch and imports \ Kibana dashboards and visualizations. Omit this option for subsequent runs to avoid overwriting existing dashboards.', @@ -273,14 +258,14 @@ export function createCommonNetflowInstructions() { ], WINDOWS: [ { - title: i18n.translate('kbn.server.tutorials.netflow.common.setup.windowsTitle', { + title: i18n.translate('home.tutorials.netflow.common.setup.windowsTitle', { defaultMessage: 'Run the Netflow module', }), - textPre: i18n.translate('kbn.server.tutorials.netflow.common.setup.windowsTextPre', { + textPre: i18n.translate('home.tutorials.netflow.common.setup.windowsTextPre', { defaultMessage: 'Run:', }), commands: ['bin\\logstash --modules netflow --setup'], - textPost: i18n.translate('kbn.server.tutorials.netflow.common.setup.windowsTextPost', { + textPost: i18n.translate('home.tutorials.netflow.common.setup.windowsTextPost', { defaultMessage: 'The {setupOption} option creates a {netflowPrefix} index pattern in Elasticsearch and imports \ Kibana dashboards and visualizations. Omit this option for subsequent runs to avoid overwriting existing dashboards.', diff --git a/src/legacy/core_plugins/kibana/server/tutorials/netflow/elastic_cloud.js b/src/plugins/home/server/tutorials/netflow/elastic_cloud.ts similarity index 87% rename from src/legacy/core_plugins/kibana/server/tutorials/netflow/elastic_cloud.js rename to src/plugins/home/server/tutorials/netflow/elastic_cloud.ts index 6ba76c2b5fa9c..ac64aef730004 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/netflow/elastic_cloud.js +++ b/src/plugins/home/server/tutorials/netflow/elastic_cloud.ts @@ -19,8 +19,8 @@ import { i18n } from '@kbn/i18n'; -import { INSTRUCTION_VARIANT } from '../../../common/tutorials/instruction_variant'; -import { createLogstashInstructions } from '../../../common/tutorials/logstash_instructions'; +import { INSTRUCTION_VARIANT } from '../instructions/instruction_variant'; +import { createLogstashInstructions } from '../instructions/logstash_instructions'; import { createCommonNetflowInstructions } from './common_instructions'; // TODO: compare with onPremElasticCloud and onPrem scenarios and extract out common bits @@ -31,7 +31,7 @@ export function createElasticCloudInstructions() { return { instructionSets: [ { - title: i18n.translate('kbn.server.tutorials.netflow.elasticCloudInstructions.title', { + title: i18n.translate('home.tutorials.netflow.elasticCloudInstructions.title', { defaultMessage: 'Getting Started', }), instructionVariants: [ diff --git a/src/legacy/core_plugins/kibana/server/tutorials/netflow/index.js b/src/plugins/home/server/tutorials/netflow/index.ts similarity index 83% rename from src/legacy/core_plugins/kibana/server/tutorials/netflow/index.js rename to src/plugins/home/server/tutorials/netflow/index.ts index 10a57fc291d59..7c6fcadcff625 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/netflow/index.js +++ b/src/plugins/home/server/tutorials/netflow/index.ts @@ -19,7 +19,7 @@ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { createOnPremInstructions } from './on_prem'; import { createElasticCloudInstructions } from './elastic_cloud'; import { createOnPremElasticCloudInstructions } from './on_prem_elastic_cloud'; @@ -28,11 +28,11 @@ export function netflowSpecProvider() { return { id: 'netflow', name: 'Netflow', - category: TUTORIAL_CATEGORY.SIEM, - shortDescription: i18n.translate('kbn.server.tutorials.netflow.tutorialShortDescription', { + category: TutorialsCategory.SIEM, + shortDescription: i18n.translate('home.tutorials.netflow.tutorialShortDescription', { defaultMessage: 'Collect Netflow records sent by a Netflow exporter.', }), - longDescription: i18n.translate('kbn.server.tutorials.netflow.tutorialLongDescription', { + longDescription: i18n.translate('home.tutorials.netflow.tutorialLongDescription', { defaultMessage: 'The Logstash Netflow module collects and parses network flow data, \ indexes the events into Elasticsearch, and installs a suite of Kibana dashboards. \ @@ -42,7 +42,7 @@ This module support Netflow Version 5 and 9. [Learn more]({linkUrl}).', }, }), completionTimeMinutes: 10, - //previewImagePath: 'kibana-apache.png', TODO + // previewImagePath: 'kibana-apache.png', TODO onPrem: createOnPremInstructions(), elasticCloud: createElasticCloudInstructions(), onPremElasticCloud: createOnPremElasticCloudInstructions(), diff --git a/src/legacy/core_plugins/kibana/server/tutorials/netflow/on_prem.js b/src/plugins/home/server/tutorials/netflow/on_prem.ts similarity index 87% rename from src/legacy/core_plugins/kibana/server/tutorials/netflow/on_prem.js rename to src/plugins/home/server/tutorials/netflow/on_prem.ts index cc060611361bd..c7cd36d073632 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/netflow/on_prem.js +++ b/src/plugins/home/server/tutorials/netflow/on_prem.ts @@ -19,8 +19,8 @@ import { i18n } from '@kbn/i18n'; -import { INSTRUCTION_VARIANT } from '../../../common/tutorials/instruction_variant'; -import { createLogstashInstructions } from '../../../common/tutorials/logstash_instructions'; +import { INSTRUCTION_VARIANT } from '../instructions/instruction_variant'; +import { createLogstashInstructions } from '../instructions/logstash_instructions'; import { createCommonNetflowInstructions } from './common_instructions'; // TODO: compare with onPremElasticCloud and elasticCloud scenarios and extract out common bits @@ -31,7 +31,7 @@ export function createOnPremInstructions() { return { instructionSets: [ { - title: i18n.translate('kbn.server.tutorials.netflow.onPremInstructions.title', { + title: i18n.translate('home.tutorials.netflow.onPremInstructions.title', { defaultMessage: 'Getting Started', }), instructionVariants: [ diff --git a/src/legacy/core_plugins/kibana/server/tutorials/netflow/on_prem_elastic_cloud.js b/src/plugins/home/server/tutorials/netflow/on_prem_elastic_cloud.ts similarity index 86% rename from src/legacy/core_plugins/kibana/server/tutorials/netflow/on_prem_elastic_cloud.js rename to src/plugins/home/server/tutorials/netflow/on_prem_elastic_cloud.ts index 49674621b70be..c01a9a5382f88 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/netflow/on_prem_elastic_cloud.js +++ b/src/plugins/home/server/tutorials/netflow/on_prem_elastic_cloud.ts @@ -19,12 +19,12 @@ import { i18n } from '@kbn/i18n'; -import { INSTRUCTION_VARIANT } from '../../../common/tutorials/instruction_variant'; -import { createLogstashInstructions } from '../../../common/tutorials/logstash_instructions'; +import { INSTRUCTION_VARIANT } from '../instructions/instruction_variant'; +import { createLogstashInstructions } from '../instructions/logstash_instructions'; import { createTrycloudOption1, createTrycloudOption2, -} from '../../../common/tutorials/onprem_cloud_instructions'; +} from '../instructions/onprem_cloud_instructions'; import { createCommonNetflowInstructions } from './common_instructions'; // TODO: compare with onPrem and elasticCloud scenarios and extract out common bits @@ -37,7 +37,7 @@ export function createOnPremElasticCloudInstructions() { return { instructionSets: [ { - title: i18n.translate('kbn.server.tutorials.netflow.onPremElasticCloudInstructions.title', { + title: i18n.translate('home.tutorials.netflow.onPremElasticCloudInstructions.title', { defaultMessage: 'Getting Started', }), instructionVariants: [ diff --git a/src/legacy/core_plugins/kibana/server/tutorials/nginx_logs/index.js b/src/plugins/home/server/tutorials/nginx_logs/index.ts similarity index 71% rename from src/legacy/core_plugins/kibana/server/tutorials/nginx_logs/index.js rename to src/plugins/home/server/tutorials/nginx_logs/index.ts index ef4dae582790e..756d4a171d858 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/nginx_logs/index.js +++ b/src/plugins/home/server/tutorials/nginx_logs/index.ts @@ -18,26 +18,30 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/filebeat_instructions'; +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function nginxLogsSpecProvider(context) { +export function nginxLogsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'nginx'; - const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS']; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; return { id: 'nginxLogs', - name: i18n.translate('kbn.server.tutorials.nginxLogs.nameTitle', { + name: i18n.translate('home.tutorials.nginxLogs.nameTitle', { defaultMessage: 'Nginx logs', }), - category: TUTORIAL_CATEGORY.LOGGING, - shortDescription: i18n.translate('kbn.server.tutorials.nginxLogs.shortDescription', { + category: TutorialsCategory.LOGGING, + shortDescription: i18n.translate('home.tutorials.nginxLogs.shortDescription', { defaultMessage: 'Collect and parse access and error logs created by the Nginx HTTP server.', }), - longDescription: i18n.translate('kbn.server.tutorials.nginxLogs.longDescription', { + longDescription: i18n.translate('home.tutorials.nginxLogs.longDescription', { defaultMessage: 'The `nginx` Filebeat module parses access and error logs created by the Nginx HTTP server. \ [Learn more]({learnMoreLink}).', @@ -50,12 +54,9 @@ export function nginxLogsSpecProvider(context) { dashboards: [ { id: '55a9e6e0-a29e-11e7-928f-5dbe6f6f5519-ecs', - linkLabel: i18n.translate( - 'kbn.server.tutorials.nginxLogs.artifacts.dashboards.linkLabel', - { - defaultMessage: 'Nginx logs dashboard', - } - ), + linkLabel: i18n.translate('home.tutorials.nginxLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'Nginx logs dashboard', + }), isOverview: true, }, ], diff --git a/src/legacy/core_plugins/kibana/server/tutorials/nginx_metrics/index.js b/src/plugins/home/server/tutorials/nginx_metrics/index.ts similarity index 73% rename from src/legacy/core_plugins/kibana/server/tutorials/nginx_metrics/index.js rename to src/plugins/home/server/tutorials/nginx_metrics/index.ts index 2acb72b01eca9..82af4d6c42dd8 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/nginx_metrics/index.js +++ b/src/plugins/home/server/tutorials/nginx_metrics/index.ts @@ -18,25 +18,29 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/metricbeat_instructions'; +} from '../instructions/metricbeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function nginxMetricsSpecProvider(context) { +export function nginxMetricsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'nginx'; return { id: 'nginxMetrics', - name: i18n.translate('kbn.server.tutorials.nginxMetrics.nameTitle', { + name: i18n.translate('home.tutorials.nginxMetrics.nameTitle', { defaultMessage: 'Nginx metrics', }), - category: TUTORIAL_CATEGORY.METRICS, - shortDescription: i18n.translate('kbn.server.tutorials.nginxMetrics.shortDescription', { + category: TutorialsCategory.METRICS, + shortDescription: i18n.translate('home.tutorials.nginxMetrics.shortDescription', { defaultMessage: 'Fetch internal metrics from the Nginx HTTP server.', }), - longDescription: i18n.translate('kbn.server.tutorials.nginxMetrics.longDescription', { + longDescription: i18n.translate('home.tutorials.nginxMetrics.longDescription', { defaultMessage: 'The `nginx` Metricbeat module fetches internal metrics from the Nginx HTTP server. \ The module scrapes the server status data from the web page generated by the \ @@ -54,12 +58,9 @@ which must be enabled in your Nginx installation. \ dashboards: [ { id: '023d2930-f1a5-11e7-a9ef-93c69af7b129-ecs', - linkLabel: i18n.translate( - 'kbn.server.tutorials.nginxMetrics.artifacts.dashboards.linkLabel', - { - defaultMessage: 'Nginx metrics dashboard', - } - ), + linkLabel: i18n.translate('home.tutorials.nginxMetrics.artifacts.dashboards.linkLabel', { + defaultMessage: 'Nginx metrics dashboard', + }), isOverview: true, }, ], @@ -69,7 +70,7 @@ which must be enabled in your Nginx installation. \ }, completionTimeMinutes: 10, previewImagePath: '/plugins/kibana/home/tutorial_resources/nginx_metrics/screenshot.png', - onPrem: onPremInstructions(moduleName, null, null, null, context), + onPrem: onPremInstructions(moduleName, context), elasticCloud: cloudInstructions(moduleName), onPremElasticCloud: onPremCloudInstructions(moduleName), }; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/osquery_logs/index.js b/src/plugins/home/server/tutorials/osquery_logs/index.ts similarity index 71% rename from src/legacy/core_plugins/kibana/server/tutorials/osquery_logs/index.js rename to src/plugins/home/server/tutorials/osquery_logs/index.ts index eddb81a9762e3..bce928519f66d 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/osquery_logs/index.js +++ b/src/plugins/home/server/tutorials/osquery_logs/index.ts @@ -18,26 +18,30 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/filebeat_instructions'; +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function osqueryLogsSpecProvider(context) { +export function osqueryLogsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'osquery'; - const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS']; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; return { id: 'osqueryLogs', - name: i18n.translate('kbn.server.tutorials.osqueryLogs.nameTitle', { + name: i18n.translate('home.tutorials.osqueryLogs.nameTitle', { defaultMessage: 'Osquery logs', }), - category: TUTORIAL_CATEGORY.SIEM, - shortDescription: i18n.translate('kbn.server.tutorials.osqueryLogs.shortDescription', { + category: TutorialsCategory.SIEM, + shortDescription: i18n.translate('home.tutorials.osqueryLogs.shortDescription', { defaultMessage: 'Collect the result logs created by osqueryd.', }), - longDescription: i18n.translate('kbn.server.tutorials.osqueryLogs.longDescription', { + longDescription: i18n.translate('home.tutorials.osqueryLogs.longDescription', { defaultMessage: 'The `osquery` Filebeat module collects the JSON result logs collected by `osqueryd`. \ [Learn more]({learnMoreLink}).', @@ -50,12 +54,9 @@ export function osqueryLogsSpecProvider(context) { dashboards: [ { id: '69f5ae20-eb02-11e7-8f04-51231daa5b05-ecs', - linkLabel: i18n.translate( - 'kbn.server.tutorials.osqueryLogs.artifacts.dashboards.linkLabel', - { - defaultMessage: 'Osquery logs dashboard', - } - ), + linkLabel: i18n.translate('home.tutorials.osqueryLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'Osquery logs dashboard', + }), isOverview: true, }, ], diff --git a/src/legacy/core_plugins/kibana/server/tutorials/php_fpm_metrics/index.js b/src/plugins/home/server/tutorials/php_fpm_metrics/index.ts similarity index 70% rename from src/legacy/core_plugins/kibana/server/tutorials/php_fpm_metrics/index.js rename to src/plugins/home/server/tutorials/php_fpm_metrics/index.ts index c62cdfa0917ef..a6c98fb16671f 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/php_fpm_metrics/index.js +++ b/src/plugins/home/server/tutorials/php_fpm_metrics/index.ts @@ -18,26 +18,30 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/metricbeat_instructions'; +} from '../instructions/metricbeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function phpfpmMetricsSpecProvider(context) { +export function phpfpmMetricsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'php_fpm'; return { id: 'phpfpmMetrics', - name: i18n.translate('kbn.server.tutorials.phpFpmMetrics.nameTitle', { + name: i18n.translate('home.tutorials.phpFpmMetrics.nameTitle', { defaultMessage: 'PHP-FPM metrics', }), - category: TUTORIAL_CATEGORY.METRICS, + category: TutorialsCategory.METRICS, isBeta: false, - shortDescription: i18n.translate('kbn.server.tutorials.phpFpmMetrics.shortDescription', { + shortDescription: i18n.translate('home.tutorials.phpFpmMetrics.shortDescription', { defaultMessage: 'Fetch internal metrics from PHP-FPM.', }), - longDescription: i18n.translate('kbn.server.tutorials.phpFpmMetrics.longDescription', { + longDescription: i18n.translate('home.tutorials.phpFpmMetrics.longDescription', { defaultMessage: 'The `php_fpm` Metricbeat module fetches internal metrics from the PHP-FPM server. \ [Learn more]({learnMoreLink}).', @@ -48,7 +52,7 @@ export function phpfpmMetricsSpecProvider(context) { euiIconType: 'logoPhp', artifacts: { dashboards: [ - /*{ + /* { id: 'TODO', linkLabel: 'PHP-FPM metrics dashboard', isOverview: true @@ -59,8 +63,8 @@ export function phpfpmMetricsSpecProvider(context) { }, }, completionTimeMinutes: 10, - //previewImagePath: '/plugins/kibana/home/tutorial_resources/php_fpm_metrics/screenshot.png', - onPrem: onPremInstructions(moduleName, null, null, null, context), + // previewImagePath: '/plugins/kibana/home/tutorial_resources/php_fpm_metrics/screenshot.png', + onPrem: onPremInstructions(moduleName, context), elasticCloud: cloudInstructions(moduleName), onPremElasticCloud: onPremCloudInstructions(moduleName), }; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/postgresql_logs/index.js b/src/plugins/home/server/tutorials/postgresql_logs/index.ts similarity index 74% rename from src/legacy/core_plugins/kibana/server/tutorials/postgresql_logs/index.js rename to src/plugins/home/server/tutorials/postgresql_logs/index.ts index 810af1cb025e4..def9f71c9d2df 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/postgresql_logs/index.js +++ b/src/plugins/home/server/tutorials/postgresql_logs/index.ts @@ -18,26 +18,30 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/filebeat_instructions'; +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function postgresqlLogsSpecProvider(context) { +export function postgresqlLogsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'postgresql'; - const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS']; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; return { id: 'postgresqlLogs', - name: i18n.translate('kbn.server.tutorials.postgresqlLogs.nameTitle', { + name: i18n.translate('home.tutorials.postgresqlLogs.nameTitle', { defaultMessage: 'PostgreSQL logs', }), - category: TUTORIAL_CATEGORY.LOGGING, - shortDescription: i18n.translate('kbn.server.tutorials.postgresqlLogs.shortDescription', { + category: TutorialsCategory.LOGGING, + shortDescription: i18n.translate('home.tutorials.postgresqlLogs.shortDescription', { defaultMessage: 'Collect and parse error and slow logs created by PostgreSQL.', }), - longDescription: i18n.translate('kbn.server.tutorials.postgresqlLogs.longDescription', { + longDescription: i18n.translate('home.tutorials.postgresqlLogs.longDescription', { defaultMessage: 'The `postgresql` Filebeat module parses error and slow logs created by PostgreSQL. \ [Learn more]({learnMoreLink}).', @@ -51,7 +55,7 @@ export function postgresqlLogsSpecProvider(context) { { id: '158be870-87f4-11e7-ad9c-db80de0bf8d3-ecs', linkLabel: i18n.translate( - 'kbn.server.tutorials.postgresqlLogs.artifacts.dashboards.linkLabel', + 'home.tutorials.postgresqlLogs.artifacts.dashboards.linkLabel', { defaultMessage: 'PostgreSQL logs dashboard', } diff --git a/src/legacy/core_plugins/kibana/server/tutorials/postgresql_metrics/index.js b/src/plugins/home/server/tutorials/postgresql_metrics/index.ts similarity index 71% rename from src/legacy/core_plugins/kibana/server/tutorials/postgresql_metrics/index.js rename to src/plugins/home/server/tutorials/postgresql_metrics/index.ts index 62203d7f13d33..b16267aeb0de6 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/postgresql_metrics/index.js +++ b/src/plugins/home/server/tutorials/postgresql_metrics/index.ts @@ -18,26 +18,30 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/metricbeat_instructions'; +} from '../instructions/metricbeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function postgresqlMetricsSpecProvider(context) { +export function postgresqlMetricsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'postgresql'; return { id: 'postgresqlMetrics', - name: i18n.translate('kbn.server.tutorials.postgresqlMetrics.nameTitle', { + name: i18n.translate('home.tutorials.postgresqlMetrics.nameTitle', { defaultMessage: 'PostgreSQL metrics', }), - category: TUTORIAL_CATEGORY.METRICS, + category: TutorialsCategory.METRICS, isBeta: false, - shortDescription: i18n.translate('kbn.server.tutorials.postgresqlMetrics.shortDescription', { + shortDescription: i18n.translate('home.tutorials.postgresqlMetrics.shortDescription', { defaultMessage: 'Fetch internal metrics from PostgreSQL.', }), - longDescription: i18n.translate('kbn.server.tutorials.postgresqlMetrics.longDescription', { + longDescription: i18n.translate('home.tutorials.postgresqlMetrics.longDescription', { defaultMessage: 'The `postgresql` Metricbeat module fetches internal metrics from the PostgreSQL server. \ [Learn more]({learnMoreLink}).', @@ -61,8 +65,8 @@ export function postgresqlMetricsSpecProvider(context) { }, }, completionTimeMinutes: 10, - //previewImagePath: '/plugins/kibana/home/tutorial_resources/postgresql_metrics/screenshot.png', - onPrem: onPremInstructions(moduleName, null, null, null, context), + // previewImagePath: '/plugins/kibana/home/tutorial_resources/postgresql_metrics/screenshot.png', + onPrem: onPremInstructions(moduleName, context), elasticCloud: cloudInstructions(moduleName), onPremElasticCloud: onPremCloudInstructions(moduleName), }; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/prometheus_metrics/index.js b/src/plugins/home/server/tutorials/prometheus_metrics/index.ts similarity index 69% rename from src/legacy/core_plugins/kibana/server/tutorials/prometheus_metrics/index.js rename to src/plugins/home/server/tutorials/prometheus_metrics/index.ts index 20a66418042d6..d79ce652db0d0 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/prometheus_metrics/index.js +++ b/src/plugins/home/server/tutorials/prometheus_metrics/index.ts @@ -18,26 +18,30 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/metricbeat_instructions'; +} from '../instructions/metricbeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function prometheusMetricsSpecProvider(context) { +export function prometheusMetricsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'prometheus'; return { id: moduleName + 'Metrics', - name: i18n.translate('kbn.server.tutorials.prometheusMetrics.nameTitle', { + name: i18n.translate('home.tutorials.prometheusMetrics.nameTitle', { defaultMessage: 'Prometheus metrics', }), isBeta: false, - category: TUTORIAL_CATEGORY.METRICS, - shortDescription: i18n.translate('kbn.server.tutorials.prometheusMetrics.shortDescription', { + category: TutorialsCategory.METRICS, + shortDescription: i18n.translate('home.tutorials.prometheusMetrics.shortDescription', { defaultMessage: 'Fetch metrics from a Prometheus exporter.', }), - longDescription: i18n.translate('kbn.server.tutorials.prometheusMetrics.longDescription', { + longDescription: i18n.translate('home.tutorials.prometheusMetrics.longDescription', { defaultMessage: 'The `{moduleName}` Metricbeat module fetches metrics from Prometheus endpoint. \ [Learn more]({learnMoreLink}).', @@ -49,12 +53,9 @@ export function prometheusMetricsSpecProvider(context) { euiIconType: 'logoPrometheus', artifacts: { application: { - label: i18n.translate( - 'kbn.server.tutorials.prometheusMetrics.artifacts.application.label', - { - defaultMessage: 'Discover', - } - ), + label: i18n.translate('home.tutorials.prometheusMetrics.artifacts.application.label', { + defaultMessage: 'Discover', + }), path: '/app/kibana#/discover', }, dashboards: [], @@ -63,7 +64,7 @@ export function prometheusMetricsSpecProvider(context) { }, }, completionTimeMinutes: 10, - onPrem: onPremInstructions(moduleName, null, null, null, context), + onPrem: onPremInstructions(moduleName, context), elasticCloud: cloudInstructions(moduleName), onPremElasticCloud: onPremCloudInstructions(moduleName), }; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/rabbitmq_metrics/index.js b/src/plugins/home/server/tutorials/rabbitmq_metrics/index.ts similarity index 74% rename from src/legacy/core_plugins/kibana/server/tutorials/rabbitmq_metrics/index.js rename to src/plugins/home/server/tutorials/rabbitmq_metrics/index.ts index 34c2c28541d3f..b62a7ff731420 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/rabbitmq_metrics/index.js +++ b/src/plugins/home/server/tutorials/rabbitmq_metrics/index.ts @@ -18,25 +18,29 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/metricbeat_instructions'; +} from '../instructions/metricbeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function rabbitmqMetricsSpecProvider(context) { +export function rabbitmqMetricsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'rabbitmq'; return { id: 'rabbitmqMetrics', - name: i18n.translate('kbn.server.tutorials.rabbitmqMetrics.nameTitle', { + name: i18n.translate('home.tutorials.rabbitmqMetrics.nameTitle', { defaultMessage: 'RabbitMQ metrics', }), - category: TUTORIAL_CATEGORY.METRICS, - shortDescription: i18n.translate('kbn.server.tutorials.rabbitmqMetrics.shortDescription', { + category: TutorialsCategory.METRICS, + shortDescription: i18n.translate('home.tutorials.rabbitmqMetrics.shortDescription', { defaultMessage: 'Fetch internal metrics from the RabbitMQ server.', }), - longDescription: i18n.translate('kbn.server.tutorials.rabbitmqMetrics.longDescription', { + longDescription: i18n.translate('home.tutorials.rabbitmqMetrics.longDescription', { defaultMessage: 'The `rabbitmq` Metricbeat module fetches internal metrics from the RabbitMQ server. \ [Learn more]({learnMoreLink}).', @@ -51,7 +55,7 @@ export function rabbitmqMetricsSpecProvider(context) { { id: 'AV4YobKIge1VCbKU_qVo-ecs', linkLabel: i18n.translate( - 'kbn.server.tutorials.rabbitmqMetrics.artifacts.dashboards.linkLabel', + 'home.tutorials.rabbitmqMetrics.artifacts.dashboards.linkLabel', { defaultMessage: 'RabbitMQ metrics dashboard', } @@ -65,7 +69,7 @@ export function rabbitmqMetricsSpecProvider(context) { }, completionTimeMinutes: 10, previewImagePath: '/plugins/kibana/home/tutorial_resources/rabbitmq_metrics/screenshot.png', - onPrem: onPremInstructions(moduleName, null, null, null, context), + onPrem: onPremInstructions(moduleName, context), elasticCloud: cloudInstructions(moduleName), onPremElasticCloud: onPremCloudInstructions(moduleName), }; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/redis_logs/index.js b/src/plugins/home/server/tutorials/redis_logs/index.ts similarity index 74% rename from src/legacy/core_plugins/kibana/server/tutorials/redis_logs/index.js rename to src/plugins/home/server/tutorials/redis_logs/index.ts index 4d4b5dabad3da..27c288ce9c381 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/redis_logs/index.js +++ b/src/plugins/home/server/tutorials/redis_logs/index.ts @@ -18,26 +18,30 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/filebeat_instructions'; +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function redisLogsSpecProvider(context) { +export function redisLogsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'redis'; - const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS']; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; return { id: 'redisLogs', - name: i18n.translate('kbn.server.tutorials.redisLogs.nameTitle', { + name: i18n.translate('home.tutorials.redisLogs.nameTitle', { defaultMessage: 'Redis logs', }), - category: TUTORIAL_CATEGORY.LOGGING, - shortDescription: i18n.translate('kbn.server.tutorials.redisLogs.shortDescription', { + category: TutorialsCategory.LOGGING, + shortDescription: i18n.translate('home.tutorials.redisLogs.shortDescription', { defaultMessage: 'Collect and parse error and slow logs created by Redis.', }), - longDescription: i18n.translate('kbn.server.tutorials.redisLogs.longDescription', { + longDescription: i18n.translate('home.tutorials.redisLogs.longDescription', { defaultMessage: 'The `redis` Filebeat module parses error and slow logs created by Redis. \ For Redis to write error logs, make sure the `logfile` option, from the \ @@ -56,12 +60,9 @@ Note that the `slowlog` fileset is experimental. \ dashboards: [ { id: '7fea2930-478e-11e7-b1f0-cb29bac6bf8b-ecs', - linkLabel: i18n.translate( - 'kbn.server.tutorials.redisLogs.artifacts.dashboards.linkLabel', - { - defaultMessage: 'Redis logs dashboard', - } - ), + linkLabel: i18n.translate('home.tutorials.redisLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'Redis logs dashboard', + }), isOverview: true, }, ], diff --git a/src/legacy/core_plugins/kibana/server/tutorials/redis_metrics/index.js b/src/plugins/home/server/tutorials/redis_metrics/index.ts similarity index 70% rename from src/legacy/core_plugins/kibana/server/tutorials/redis_metrics/index.js rename to src/plugins/home/server/tutorials/redis_metrics/index.ts index 708c16e041acb..27c7780653168 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/redis_metrics/index.js +++ b/src/plugins/home/server/tutorials/redis_metrics/index.ts @@ -18,25 +18,29 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/metricbeat_instructions'; +} from '../instructions/metricbeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function redisMetricsSpecProvider(context) { +export function redisMetricsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'redis'; return { id: 'redisMetrics', - name: i18n.translate('kbn.server.tutorials.redisMetrics.nameTitle', { + name: i18n.translate('home.tutorials.redisMetrics.nameTitle', { defaultMessage: 'Redis metrics', }), - category: TUTORIAL_CATEGORY.METRICS, - shortDescription: i18n.translate('kbn.server.tutorials.redisMetrics.shortDescription', { + category: TutorialsCategory.METRICS, + shortDescription: i18n.translate('home.tutorials.redisMetrics.shortDescription', { defaultMessage: 'Fetch internal metrics from Redis.', }), - longDescription: i18n.translate('kbn.server.tutorials.redisMetrics.longDescription', { + longDescription: i18n.translate('home.tutorials.redisMetrics.longDescription', { defaultMessage: 'The `redis` Metricbeat module fetches internal metrics from the Redis server. \ [Learn more]({learnMoreLink}).', @@ -49,12 +53,9 @@ export function redisMetricsSpecProvider(context) { dashboards: [ { id: 'AV4YjZ5pux-M-tCAunxK-ecs', - linkLabel: i18n.translate( - 'kbn.server.tutorials.redisMetrics.artifacts.dashboards.linkLabel', - { - defaultMessage: 'Redis metrics dashboard', - } - ), + linkLabel: i18n.translate('home.tutorials.redisMetrics.artifacts.dashboards.linkLabel', { + defaultMessage: 'Redis metrics dashboard', + }), isOverview: true, }, ], @@ -64,7 +65,7 @@ export function redisMetricsSpecProvider(context) { }, completionTimeMinutes: 10, previewImagePath: '/plugins/kibana/home/tutorial_resources/redis_metrics/screenshot.png', - onPrem: onPremInstructions(moduleName, null, null, null, context), + onPrem: onPremInstructions(moduleName, context), elasticCloud: cloudInstructions(moduleName), onPremElasticCloud: onPremCloudInstructions(moduleName), }; diff --git a/src/plugins/home/server/tutorials/register.ts b/src/plugins/home/server/tutorials/register.ts new file mode 100644 index 0000000000000..ae84f1e7ee1d5 --- /dev/null +++ b/src/plugins/home/server/tutorials/register.ts @@ -0,0 +1,159 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { systemLogsSpecProvider } from './system_logs'; +import { systemMetricsSpecProvider } from './system_metrics'; +import { apacheLogsSpecProvider } from './apache_logs'; +import { apacheMetricsSpecProvider } from './apache_metrics'; +import { elasticsearchLogsSpecProvider } from './elasticsearch_logs'; +import { iisLogsSpecProvider } from './iis_logs'; +import { kafkaLogsSpecProvider } from './kafka_logs'; +import { logstashLogsSpecProvider } from './logstash_logs'; +import { nginxLogsSpecProvider } from './nginx_logs'; +import { nginxMetricsSpecProvider } from './nginx_metrics'; +import { mysqlLogsSpecProvider } from './mysql_logs'; +import { mysqlMetricsSpecProvider } from './mysql_metrics'; +import { mongodbMetricsSpecProvider } from './mongodb_metrics'; +import { osqueryLogsSpecProvider } from './osquery_logs'; +import { phpfpmMetricsSpecProvider } from './php_fpm_metrics'; +import { postgresqlMetricsSpecProvider } from './postgresql_metrics'; +import { postgresqlLogsSpecProvider } from './postgresql_logs'; +import { rabbitmqMetricsSpecProvider } from './rabbitmq_metrics'; +import { redisLogsSpecProvider } from './redis_logs'; +import { redisMetricsSpecProvider } from './redis_metrics'; +import { suricataLogsSpecProvider } from './suricata_logs'; +import { dockerMetricsSpecProvider } from './docker_metrics'; +import { kubernetesMetricsSpecProvider } from './kubernetes_metrics'; +import { uwsgiMetricsSpecProvider } from './uwsgi_metrics'; +import { netflowSpecProvider } from './netflow'; +import { traefikLogsSpecProvider } from './traefik_logs'; +import { cephMetricsSpecProvider } from './ceph_metrics'; +import { aerospikeMetricsSpecProvider } from './aerospike_metrics'; +import { couchbaseMetricsSpecProvider } from './couchbase_metrics'; +import { dropwizardMetricsSpecProvider } from './dropwizard_metrics'; +import { elasticsearchMetricsSpecProvider } from './elasticsearch_metrics'; +import { etcdMetricsSpecProvider } from './etcd_metrics'; +import { haproxyMetricsSpecProvider } from './haproxy_metrics'; +import { kafkaMetricsSpecProvider } from './kafka_metrics'; +import { kibanaMetricsSpecProvider } from './kibana_metrics'; +import { memcachedMetricsSpecProvider } from './memcached_metrics'; +import { muninMetricsSpecProvider } from './munin_metrics'; +import { vSphereMetricsSpecProvider } from './vsphere_metrics'; +import { windowsMetricsSpecProvider } from './windows_metrics'; +import { windowsEventLogsSpecProvider } from './windows_event_logs'; +import { golangMetricsSpecProvider } from './golang_metrics'; +import { logstashMetricsSpecProvider } from './logstash_metrics'; +import { prometheusMetricsSpecProvider } from './prometheus_metrics'; +import { zookeeperMetricsSpecProvider } from './zookeeper_metrics'; +import { uptimeMonitorsSpecProvider } from './uptime_monitors'; +import { cloudwatchLogsSpecProvider } from './cloudwatch_logs'; +import { awsMetricsSpecProvider } from './aws_metrics'; +import { mssqlMetricsSpecProvider } from './mssql_metrics'; +import { natsMetricsSpecProvider } from './nats_metrics'; +import { natsLogsSpecProvider } from './nats_logs'; +import { zeekLogsSpecProvider } from './zeek_logs'; +import { corednsMetricsSpecProvider } from './coredns_metrics'; +import { corednsLogsSpecProvider } from './coredns_logs'; +import { auditbeatSpecProvider } from './auditbeat'; +import { iptablesLogsSpecProvider } from './iptables_logs'; +import { ciscoLogsSpecProvider } from './cisco_logs'; +import { envoyproxyLogsSpecProvider } from './envoyproxy_logs'; +import { couchdbMetricsSpecProvider } from './couchdb_metrics'; +import { consulMetricsSpecProvider } from './consul_metrics'; +import { cockroachdbMetricsSpecProvider } from './cockroachdb_metrics'; +import { traefikMetricsSpecProvider } from './traefik_metrics'; +import { awsLogsSpecProvider } from './aws_logs'; +import { activemqLogsSpecProvider } from './activemq_logs'; +import { activemqMetricsSpecProvider } from './activemq_metrics'; +import { azureMetricsSpecProvider } from './azure_metrics'; +import { ibmmqLogsSpecProvider } from './ibmmq_logs'; +import { stanMetricsSpecProvider } from './stan_metrics'; +import { envoyproxyMetricsSpecProvider } from './envoyproxy_metrics'; +import { ibmmqMetricsSpecProvider } from './ibmmq_metrics'; + +export const builtInTutorials = [ + systemLogsSpecProvider, + systemMetricsSpecProvider, + apacheLogsSpecProvider, + apacheMetricsSpecProvider, + elasticsearchLogsSpecProvider, + iisLogsSpecProvider, + kafkaLogsSpecProvider, + logstashLogsSpecProvider, + nginxLogsSpecProvider, + nginxMetricsSpecProvider, + mysqlLogsSpecProvider, + mysqlMetricsSpecProvider, + mongodbMetricsSpecProvider, + osqueryLogsSpecProvider, + phpfpmMetricsSpecProvider, + postgresqlMetricsSpecProvider, + postgresqlLogsSpecProvider, + rabbitmqMetricsSpecProvider, + redisLogsSpecProvider, + redisMetricsSpecProvider, + suricataLogsSpecProvider, + dockerMetricsSpecProvider, + kubernetesMetricsSpecProvider, + uwsgiMetricsSpecProvider, + netflowSpecProvider, + traefikLogsSpecProvider, + cephMetricsSpecProvider, + aerospikeMetricsSpecProvider, + couchbaseMetricsSpecProvider, + dropwizardMetricsSpecProvider, + elasticsearchMetricsSpecProvider, + etcdMetricsSpecProvider, + haproxyMetricsSpecProvider, + kafkaMetricsSpecProvider, + kibanaMetricsSpecProvider, + memcachedMetricsSpecProvider, + muninMetricsSpecProvider, + vSphereMetricsSpecProvider, + windowsMetricsSpecProvider, + windowsEventLogsSpecProvider, + golangMetricsSpecProvider, + logstashMetricsSpecProvider, + prometheusMetricsSpecProvider, + zookeeperMetricsSpecProvider, + uptimeMonitorsSpecProvider, + cloudwatchLogsSpecProvider, + awsMetricsSpecProvider, + mssqlMetricsSpecProvider, + natsMetricsSpecProvider, + natsLogsSpecProvider, + zeekLogsSpecProvider, + corednsMetricsSpecProvider, + corednsLogsSpecProvider, + auditbeatSpecProvider, + iptablesLogsSpecProvider, + ciscoLogsSpecProvider, + envoyproxyLogsSpecProvider, + couchdbMetricsSpecProvider, + consulMetricsSpecProvider, + cockroachdbMetricsSpecProvider, + traefikMetricsSpecProvider, + awsLogsSpecProvider, + activemqLogsSpecProvider, + activemqMetricsSpecProvider, + azureMetricsSpecProvider, + ibmmqLogsSpecProvider, + ibmmqMetricsSpecProvider, + stanMetricsSpecProvider, + envoyproxyMetricsSpecProvider, +]; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/stan_metrics/index.js b/src/plugins/home/server/tutorials/stan_metrics/index.ts similarity index 74% rename from src/legacy/core_plugins/kibana/server/tutorials/stan_metrics/index.js rename to src/plugins/home/server/tutorials/stan_metrics/index.ts index 3f5817ce2890b..9f536e68acf72 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/stan_metrics/index.js +++ b/src/plugins/home/server/tutorials/stan_metrics/index.ts @@ -18,25 +18,29 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/metricbeat_instructions'; +} from '../instructions/metricbeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function stanMetricsSpecProvider(context) { +export function stanMetricsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'stan'; return { id: 'stanMetrics', - name: i18n.translate('kbn.server.tutorials.stanMetrics.nameTitle', { + name: i18n.translate('home.tutorials.stanMetrics.nameTitle', { defaultMessage: 'STAN metrics', }), - category: TUTORIAL_CATEGORY.METRICS, - shortDescription: i18n.translate('kbn.server.tutorials.stanMetrics.shortDescription', { + category: TutorialsCategory.METRICS, + shortDescription: i18n.translate('home.tutorials.stanMetrics.shortDescription', { defaultMessage: 'Fetch monitoring metrics from the STAN server.', }), - longDescription: i18n.translate('kbn.server.tutorials.stanMetrics.longDescription', { + longDescription: i18n.translate('home.tutorials.stanMetrics.longDescription', { defaultMessage: 'The `stan` Metricbeat module fetches monitoring metrics from STAN. \ [Learn more]({learnMoreLink}).', @@ -53,7 +57,7 @@ export function stanMetricsSpecProvider(context) { }, completionTimeMinutes: 10, // previewImagePath: '/plugins/kibana/home/tutorial_resources/stan_metrics/screenshot.png', - onPrem: onPremInstructions(moduleName, null, null, null, context), + onPrem: onPremInstructions(moduleName, context), elasticCloud: cloudInstructions(moduleName), onPremElasticCloud: onPremCloudInstructions(moduleName), }; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/suricata_logs/index.js b/src/plugins/home/server/tutorials/suricata_logs/index.ts similarity index 70% rename from src/legacy/core_plugins/kibana/server/tutorials/suricata_logs/index.js rename to src/plugins/home/server/tutorials/suricata_logs/index.ts index 3f2ecfa78d3b2..ac19cf0987b84 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/suricata_logs/index.js +++ b/src/plugins/home/server/tutorials/suricata_logs/index.ts @@ -18,26 +18,30 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/filebeat_instructions'; +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function suricataLogsSpecProvider(context) { +export function suricataLogsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'suricata'; - const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS']; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; return { id: 'suricataLogs', - name: i18n.translate('kbn.server.tutorials.suricataLogs.nameTitle', { + name: i18n.translate('home.tutorials.suricataLogs.nameTitle', { defaultMessage: 'Suricata logs', }), - category: TUTORIAL_CATEGORY.SIEM, - shortDescription: i18n.translate('kbn.server.tutorials.suricataLogs.shortDescription', { + category: TutorialsCategory.SIEM, + shortDescription: i18n.translate('home.tutorials.suricataLogs.shortDescription', { defaultMessage: 'Collect the result logs created by Suricata IDS/IPS/NSM.', }), - longDescription: i18n.translate('kbn.server.tutorials.suricataLogs.longDescription', { + longDescription: i18n.translate('home.tutorials.suricataLogs.longDescription', { defaultMessage: 'The `suricata` Filebeat module collects the logs from the \ [Suricata Eve JSON output](https://suricata.readthedocs.io/en/latest/output/eve/eve-json-format.html). \ @@ -46,17 +50,14 @@ export function suricataLogsSpecProvider(context) { learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-suricata.html', }, }), - //euiIconType: 'logoSuricata', + // euiIconType: 'logoSuricata', artifacts: { dashboards: [ { id: '69f5ae20-eb02-11e7-8f04-51231daa5b05', - linkLabel: i18n.translate( - 'kbn.server.tutorials.suricataLogs.artifacts.dashboards.linkLabel', - { - defaultMessage: 'Suricata logs dashboard', - } - ), + linkLabel: i18n.translate('home.tutorials.suricataLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'Suricata logs dashboard', + }), isOverview: true, }, ], diff --git a/src/legacy/core_plugins/kibana/server/tutorials/system_logs/index.js b/src/plugins/home/server/tutorials/system_logs/index.ts similarity index 72% rename from src/legacy/core_plugins/kibana/server/tutorials/system_logs/index.js rename to src/plugins/home/server/tutorials/system_logs/index.ts index ec9792c3c4356..fc2fa4f49fd5f 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/system_logs/index.js +++ b/src/plugins/home/server/tutorials/system_logs/index.ts @@ -18,26 +18,30 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/filebeat_instructions'; +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function systemLogsSpecProvider(context) { +export function systemLogsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'system'; - const platforms = ['OSX', 'DEB', 'RPM']; + const platforms = ['OSX', 'DEB', 'RPM'] as const; return { id: 'systemLogs', - name: i18n.translate('kbn.server.tutorials.systemLogs.nameTitle', { + name: i18n.translate('home.tutorials.systemLogs.nameTitle', { defaultMessage: 'System logs', }), - category: TUTORIAL_CATEGORY.LOGGING, - shortDescription: i18n.translate('kbn.server.tutorials.systemLogs.shortDescription', { + category: TutorialsCategory.LOGGING, + shortDescription: i18n.translate('home.tutorials.systemLogs.shortDescription', { defaultMessage: 'Collect and parse logs written by the local Syslog server.', }), - longDescription: i18n.translate('kbn.server.tutorials.systemLogs.longDescription', { + longDescription: i18n.translate('home.tutorials.systemLogs.longDescription', { defaultMessage: 'The `system` Filebeat module collects and parses logs created by the system logging service of common \ Unix/Linux based distributions. This module is not available on Windows. \ @@ -50,12 +54,9 @@ Unix/Linux based distributions. This module is not available on Windows. \ dashboards: [ { id: 'Filebeat-syslog-dashboard-ecs', - linkLabel: i18n.translate( - 'kbn.server.tutorials.systemLogs.artifacts.dashboards.linkLabel', - { - defaultMessage: 'System logs dashboard', - } - ), + linkLabel: i18n.translate('home.tutorials.systemLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'System logs dashboard', + }), isOverview: true, }, ], diff --git a/src/legacy/core_plugins/kibana/server/tutorials/system_metrics/index.js b/src/plugins/home/server/tutorials/system_metrics/index.ts similarity index 71% rename from src/legacy/core_plugins/kibana/server/tutorials/system_metrics/index.js rename to src/plugins/home/server/tutorials/system_metrics/index.ts index 1bebc78146f83..b0355e1118a96 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/system_metrics/index.js +++ b/src/plugins/home/server/tutorials/system_metrics/index.ts @@ -18,25 +18,29 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/metricbeat_instructions'; +} from '../instructions/metricbeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function systemMetricsSpecProvider(context) { +export function systemMetricsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'system'; return { id: 'systemMetrics', - name: i18n.translate('kbn.server.tutorials.systemMetrics.nameTitle', { + name: i18n.translate('home.tutorials.systemMetrics.nameTitle', { defaultMessage: 'System metrics', }), - category: TUTORIAL_CATEGORY.METRICS, - shortDescription: i18n.translate('kbn.server.tutorials.systemMetrics.shortDescription', { + category: TutorialsCategory.METRICS, + shortDescription: i18n.translate('home.tutorials.systemMetrics.shortDescription', { defaultMessage: 'Collect CPU, memory, network, and disk statistics from the host.', }), - longDescription: i18n.translate('kbn.server.tutorials.systemMetrics.longDescription', { + longDescription: i18n.translate('home.tutorials.systemMetrics.longDescription', { defaultMessage: 'The `system` Metricbeat module collects CPU, memory, network, and disk statistics from the host. \ It collects system wide statistics and statistics per process and filesystem. \ @@ -49,12 +53,9 @@ It collects system wide statistics and statistics per process and filesystem. \ dashboards: [ { id: 'Metricbeat-system-overview-ecs', - linkLabel: i18n.translate( - 'kbn.server.tutorials.systemMetrics.artifacts.dashboards.linkLabel', - { - defaultMessage: 'System metrics dashboard', - } - ), + linkLabel: i18n.translate('home.tutorials.systemMetrics.artifacts.dashboards.linkLabel', { + defaultMessage: 'System metrics dashboard', + }), isOverview: true, }, ], @@ -64,7 +65,7 @@ It collects system wide statistics and statistics per process and filesystem. \ }, completionTimeMinutes: 10, previewImagePath: '/plugins/kibana/home/tutorial_resources/system_metrics/screenshot.png', - onPrem: onPremInstructions(moduleName, null, null, null, context), + onPrem: onPremInstructions(moduleName, context), elasticCloud: cloudInstructions(moduleName), onPremElasticCloud: onPremCloudInstructions(moduleName), }; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/traefik_logs/index.js b/src/plugins/home/server/tutorials/traefik_logs/index.ts similarity index 69% rename from src/legacy/core_plugins/kibana/server/tutorials/traefik_logs/index.js rename to src/plugins/home/server/tutorials/traefik_logs/index.ts index 65f697a59ee82..423023a3902e5 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/traefik_logs/index.js +++ b/src/plugins/home/server/tutorials/traefik_logs/index.ts @@ -18,26 +18,30 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/filebeat_instructions'; +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function traefikLogsSpecProvider(context) { +export function traefikLogsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'traefik'; - const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS']; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; return { id: 'traefikLogs', - name: i18n.translate('kbn.server.tutorials.traefikLogs.nameTitle', { + name: i18n.translate('home.tutorials.traefikLogs.nameTitle', { defaultMessage: 'Traefik logs', }), - category: TUTORIAL_CATEGORY.LOGGING, - shortDescription: i18n.translate('kbn.server.tutorials.traefikLogs.shortDescription', { + category: TutorialsCategory.LOGGING, + shortDescription: i18n.translate('home.tutorials.traefikLogs.shortDescription', { defaultMessage: 'Collect and parse access logs created by the Traefik Proxy.', }), - longDescription: i18n.translate('kbn.server.tutorials.traefikLogs.longDescription', { + longDescription: i18n.translate('home.tutorials.traefikLogs.longDescription', { defaultMessage: 'The `traefik` Filebeat module parses access logs created by Traefik. \ [Learn more]({learnMoreLink}).', @@ -45,17 +49,14 @@ export function traefikLogsSpecProvider(context) { learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-traefik.html', }, }), - //euiIconType: 'logoTraefik', + // euiIconType: 'logoTraefik', artifacts: { dashboards: [ { id: 'Filebeat-Traefik-Dashboard-ecs', - linkLabel: i18n.translate( - 'kbn.server.tutorials.traefikLogs.artifacts.dashboards.linkLabel', - { - defaultMessage: 'Traefik logs dashboard', - } - ), + linkLabel: i18n.translate('home.tutorials.traefikLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'Traefik logs dashboard', + }), isOverview: true, }, ], diff --git a/src/legacy/core_plugins/kibana/server/tutorials/traefik_metrics/index.js b/src/plugins/home/server/tutorials/traefik_metrics/index.ts similarity index 74% rename from src/legacy/core_plugins/kibana/server/tutorials/traefik_metrics/index.js rename to src/plugins/home/server/tutorials/traefik_metrics/index.ts index 1a4c043ebc706..8fe81eca4c601 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/traefik_metrics/index.js +++ b/src/plugins/home/server/tutorials/traefik_metrics/index.ts @@ -18,25 +18,26 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory, TutorialSchema } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/metricbeat_instructions'; +} from '../instructions/metricbeat_instructions'; +import { TutorialContext } from '../../services/tutorials/lib/tutorials_registry_types'; -export function traefikMetricsSpecProvider(server, context) { +export function traefikMetricsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'traefik'; return { id: 'traefikMetrics', - name: i18n.translate('kbn.server.tutorials.traefikMetrics.nameTitle', { + name: i18n.translate('home.tutorials.traefikMetrics.nameTitle', { defaultMessage: 'Traefik metrics', }), - category: TUTORIAL_CATEGORY.METRICS, - shortDescription: i18n.translate('kbn.server.tutorials.traefikMetrics.shortDescription', { + category: TutorialsCategory.METRICS, + shortDescription: i18n.translate('home.tutorials.traefikMetrics.shortDescription', { defaultMessage: 'Fetch monitoring metrics from Traefik.', }), - longDescription: i18n.translate('kbn.server.tutorials.traefikMetrics.longDescription', { + longDescription: i18n.translate('home.tutorials.traefikMetrics.longDescription', { defaultMessage: 'The `traefik` Metricbeat module fetches monitoring metrics from Traefik. \ [Learn more]({learnMoreLink}).', @@ -53,7 +54,7 @@ export function traefikMetricsSpecProvider(server, context) { }, completionTimeMinutes: 10, // previewImagePath: '/plugins/kibana/home/tutorial_resources/traefik_metrics/screenshot.png', - onPrem: onPremInstructions(moduleName, null, null, null, context), + onPrem: onPremInstructions(moduleName, context), elasticCloud: cloudInstructions(moduleName), onPremElasticCloud: onPremCloudInstructions(moduleName), }; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/uptime_monitors/index.js b/src/plugins/home/server/tutorials/uptime_monitors/index.ts similarity index 70% rename from src/legacy/core_plugins/kibana/server/tutorials/uptime_monitors/index.js rename to src/plugins/home/server/tutorials/uptime_monitors/index.ts index 7cc90c740eb69..207bc0cb479be 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/uptime_monitors/index.js +++ b/src/plugins/home/server/tutorials/uptime_monitors/index.ts @@ -18,24 +18,28 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/heartbeat_instructions'; +} from '../instructions/heartbeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function uptimeMonitorsSpecProvider(context) { +export function uptimeMonitorsSpecProvider(context: TutorialContext): TutorialSchema { return { id: 'uptimeMonitors', - name: i18n.translate('kbn.server.tutorials.uptimeMonitors.nameTitle', { + name: i18n.translate('home.tutorials.uptimeMonitors.nameTitle', { defaultMessage: 'Uptime Monitors', }), - category: TUTORIAL_CATEGORY.METRICS, - shortDescription: i18n.translate('kbn.server.tutorials.uptimeMonitors.shortDescription', { + category: TutorialsCategory.METRICS, + shortDescription: i18n.translate('home.tutorials.uptimeMonitors.shortDescription', { defaultMessage: 'Monitor services for their availability', }), - longDescription: i18n.translate('kbn.server.tutorials.uptimeMonitors.longDescription', { + longDescription: i18n.translate('home.tutorials.uptimeMonitors.longDescription', { defaultMessage: 'Monitor services for their availability with active probing. \ Given a list of URLs, Heartbeat asks the simple question: Are you alive? \ @@ -49,12 +53,9 @@ export function uptimeMonitorsSpecProvider(context) { dashboards: [], application: { path: '/app/uptime', - label: i18n.translate( - 'kbn.server.tutorials.uptimeMonitors.artifacts.dashboards.linkLabel', - { - defaultMessage: 'Uptime App', - } - ), + label: i18n.translate('home.tutorials.uptimeMonitors.artifacts.dashboards.linkLabel', { + defaultMessage: 'Uptime App', + }), }, exportedFields: { documentationUrl: '{config.docs.beats.heartbeat}/exported-fields.html', @@ -62,7 +63,7 @@ export function uptimeMonitorsSpecProvider(context) { }, completionTimeMinutes: 10, previewImagePath: '/plugins/kibana/home/tutorial_resources/uptime_monitors/screenshot.png', - onPrem: onPremInstructions(null, null, null, context), + onPrem: onPremInstructions([], context), elasticCloud: cloudInstructions(), onPremElasticCloud: onPremCloudInstructions(), }; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/uwsgi_metrics/index.js b/src/plugins/home/server/tutorials/uwsgi_metrics/index.ts similarity index 69% rename from src/legacy/core_plugins/kibana/server/tutorials/uwsgi_metrics/index.js rename to src/plugins/home/server/tutorials/uwsgi_metrics/index.ts index 8ec422f5b81d1..fc36cfe869867 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/uwsgi_metrics/index.js +++ b/src/plugins/home/server/tutorials/uwsgi_metrics/index.ts @@ -18,25 +18,29 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/metricbeat_instructions'; +} from '../instructions/metricbeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function uwsgiMetricsSpecProvider(context) { +export function uwsgiMetricsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'uwsgi'; return { id: 'uwsgiMetrics', - name: i18n.translate('kbn.server.tutorials.uwsgiMetrics.nameTitle', { + name: i18n.translate('home.tutorials.uwsgiMetrics.nameTitle', { defaultMessage: 'uWSGI metrics', }), - category: TUTORIAL_CATEGORY.METRICS, - shortDescription: i18n.translate('kbn.server.tutorials.uwsgiMetrics.shortDescription', { + category: TutorialsCategory.METRICS, + shortDescription: i18n.translate('home.tutorials.uwsgiMetrics.shortDescription', { defaultMessage: 'Fetch internal metrics from the uWSGI server.', }), - longDescription: i18n.translate('kbn.server.tutorials.uwsgiMetrics.longDescription', { + longDescription: i18n.translate('home.tutorials.uwsgiMetrics.longDescription', { defaultMessage: 'The `uwsgi` Metricbeat module fetches internal metrics from the uWSGI server. \ [Learn more]({learnMoreLink}).', @@ -44,18 +48,15 @@ export function uwsgiMetricsSpecProvider(context) { learnMoreLink: '{config.docs.beats.metricbeat}/metricbeat-module-uwsgi.html', }, }), - //euiIconType: 'logouWSGI', + // euiIconType: 'logouWSGI', isBeta: false, artifacts: { dashboards: [ { id: '32fca290-f0af-11e7-b9ff-9f96241065de-ecs', - linkLabel: i18n.translate( - 'kbn.server.tutorials.uwsgiMetrics.artifacts.dashboards.linkLabel', - { - defaultMessage: 'uWSGI metrics dashboard', - } - ), + linkLabel: i18n.translate('home.tutorials.uwsgiMetrics.artifacts.dashboards.linkLabel', { + defaultMessage: 'uWSGI metrics dashboard', + }), isOverview: true, }, ], @@ -65,7 +66,7 @@ export function uwsgiMetricsSpecProvider(context) { }, completionTimeMinutes: 10, previewImagePath: '/plugins/kibana/home/tutorial_resources/uwsgi_metrics/screenshot.png', - onPrem: onPremInstructions(moduleName, null, null, null, context), + onPrem: onPremInstructions(moduleName, context), elasticCloud: cloudInstructions(moduleName), onPremElasticCloud: onPremCloudInstructions(moduleName), }; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/vsphere_metrics/index.js b/src/plugins/home/server/tutorials/vsphere_metrics/index.ts similarity index 67% rename from src/legacy/core_plugins/kibana/server/tutorials/vsphere_metrics/index.js rename to src/plugins/home/server/tutorials/vsphere_metrics/index.ts index dc3a13bde0c76..3ea57cdbc0e44 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/vsphere_metrics/index.js +++ b/src/plugins/home/server/tutorials/vsphere_metrics/index.ts @@ -18,25 +18,29 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/metricbeat_instructions'; +} from '../instructions/metricbeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function vSphereMetricsSpecProvider(context) { +export function vSphereMetricsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'vsphere'; return { id: 'vsphereMetrics', - name: i18n.translate('kbn.server.tutorials.vsphereMetrics.nameTitle', { + name: i18n.translate('home.tutorials.vsphereMetrics.nameTitle', { defaultMessage: 'vSphere metrics', }), - category: TUTORIAL_CATEGORY.METRICS, - shortDescription: i18n.translate('kbn.server.tutorials.vsphereMetrics.shortDescription', { + category: TutorialsCategory.METRICS, + shortDescription: i18n.translate('home.tutorials.vsphereMetrics.shortDescription', { defaultMessage: 'Fetch internal metrics from vSphere.', }), - longDescription: i18n.translate('kbn.server.tutorials.vsphereMetrics.longDescription', { + longDescription: i18n.translate('home.tutorials.vsphereMetrics.longDescription', { defaultMessage: 'The `vsphere` Metricbeat module fetches internal metrics from a vSphere cluster. \ [Learn more]({learnMoreLink}).', @@ -44,11 +48,11 @@ export function vSphereMetricsSpecProvider(context) { learnMoreLink: '{config.docs.beats.metricbeat}/metricbeat-module-vsphere.html', }, }), - //euiIconType: 'logoVSphere', + // euiIconType: 'logoVSphere', isBeta: true, artifacts: { application: { - label: i18n.translate('kbn.server.tutorials.vsphereMetrics.artifacts.application.label', { + label: i18n.translate('home.tutorials.vsphereMetrics.artifacts.application.label', { defaultMessage: 'Discover', }), path: '/app/kibana#/discover', @@ -59,8 +63,8 @@ export function vSphereMetricsSpecProvider(context) { }, }, completionTimeMinutes: 10, - //previewImagePath: '/plugins/kibana/home/tutorial_resources/vsphere_metrics/screenshot.png', - onPrem: onPremInstructions(moduleName, null, null, null, context), + // previewImagePath: '/plugins/kibana/home/tutorial_resources/vsphere_metrics/screenshot.png', + onPrem: onPremInstructions(moduleName, context), elasticCloud: cloudInstructions(moduleName), onPremElasticCloud: onPremCloudInstructions(moduleName), }; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/windows_event_logs/index.js b/src/plugins/home/server/tutorials/windows_event_logs/index.ts similarity index 70% rename from src/legacy/core_plugins/kibana/server/tutorials/windows_event_logs/index.js rename to src/plugins/home/server/tutorials/windows_event_logs/index.ts index 272535ad3e4e1..5349bedb21279 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/windows_event_logs/index.js +++ b/src/plugins/home/server/tutorials/windows_event_logs/index.ts @@ -18,25 +18,29 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/winlogbeat_instructions'; +} from '../instructions/winlogbeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function windowsEventLogsSpecProvider(context) { +export function windowsEventLogsSpecProvider(context: TutorialContext): TutorialSchema { return { id: 'windowsEventLogs', - name: i18n.translate('kbn.server.tutorials.windowsEventLogs.nameTitle', { + name: i18n.translate('home.tutorials.windowsEventLogs.nameTitle', { defaultMessage: 'Windows Event Log', }), isBeta: false, - category: TUTORIAL_CATEGORY.SIEM, - shortDescription: i18n.translate('kbn.server.tutorials.windowsEventLogs.shortDescription', { + category: TutorialsCategory.SIEM, + shortDescription: i18n.translate('home.tutorials.windowsEventLogs.shortDescription', { defaultMessage: 'Fetch logs from the Windows Event Log.', }), - longDescription: i18n.translate('kbn.server.tutorials.windowsEventLogs.longDescription', { + longDescription: i18n.translate('home.tutorials.windowsEventLogs.longDescription', { defaultMessage: 'Use Winlogbeat to collect the logs from the Windows Event Log. \ [Learn more]({learnMoreLink}).', @@ -47,7 +51,7 @@ export function windowsEventLogsSpecProvider(context) { euiIconType: 'logoWindows', artifacts: { application: { - label: i18n.translate('kbn.server.tutorials.windowsEventLogs.artifacts.application.label', { + label: i18n.translate('home.tutorials.windowsEventLogs.artifacts.application.label', { defaultMessage: 'SIEM App', }), path: '/app/siem', @@ -58,7 +62,7 @@ export function windowsEventLogsSpecProvider(context) { }, }, completionTimeMinutes: 10, - onPrem: onPremInstructions(null, null, null, context), + onPrem: onPremInstructions(context), elasticCloud: cloudInstructions(), onPremElasticCloud: onPremCloudInstructions(), }; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/windows_metrics/index.js b/src/plugins/home/server/tutorials/windows_metrics/index.ts similarity index 71% rename from src/legacy/core_plugins/kibana/server/tutorials/windows_metrics/index.js rename to src/plugins/home/server/tutorials/windows_metrics/index.ts index 9f30c330f4ae1..fa855a82b3b77 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/windows_metrics/index.js +++ b/src/plugins/home/server/tutorials/windows_metrics/index.ts @@ -18,26 +18,30 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/metricbeat_instructions'; +} from '../instructions/metricbeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function windowsMetricsSpecProvider(context) { +export function windowsMetricsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'windows'; return { id: 'windowsMetrics', - name: i18n.translate('kbn.server.tutorials.windowsMetrics.nameTitle', { + name: i18n.translate('home.tutorials.windowsMetrics.nameTitle', { defaultMessage: 'Windows metrics', }), isBeta: false, - category: TUTORIAL_CATEGORY.METRICS, - shortDescription: i18n.translate('kbn.server.tutorials.windowsMetrics.shortDescription', { + category: TutorialsCategory.METRICS, + shortDescription: i18n.translate('home.tutorials.windowsMetrics.shortDescription', { defaultMessage: 'Fetch internal metrics from Windows.', }), - longDescription: i18n.translate('kbn.server.tutorials.windowsMetrics.longDescription', { + longDescription: i18n.translate('home.tutorials.windowsMetrics.longDescription', { defaultMessage: 'The `windows` Metricbeat module fetches internal metrics from Windows. \ [Learn more]({learnMoreLink}).', @@ -48,7 +52,7 @@ export function windowsMetricsSpecProvider(context) { euiIconType: 'logoWindows', artifacts: { application: { - label: i18n.translate('kbn.server.tutorials.windowsMetrics.artifacts.application.label', { + label: i18n.translate('home.tutorials.windowsMetrics.artifacts.application.label', { defaultMessage: 'Discover', }), path: '/app/kibana#/discover', @@ -59,7 +63,7 @@ export function windowsMetricsSpecProvider(context) { }, }, completionTimeMinutes: 10, - onPrem: onPremInstructions(moduleName, null, null, null, context), + onPrem: onPremInstructions(moduleName, context), elasticCloud: cloudInstructions(moduleName), onPremElasticCloud: onPremCloudInstructions(moduleName), }; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/zeek_logs/index.js b/src/plugins/home/server/tutorials/zeek_logs/index.ts similarity index 70% rename from src/legacy/core_plugins/kibana/server/tutorials/zeek_logs/index.js rename to src/plugins/home/server/tutorials/zeek_logs/index.ts index 07d46c9c6ae84..c015545046c99 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/zeek_logs/index.js +++ b/src/plugins/home/server/tutorials/zeek_logs/index.ts @@ -18,26 +18,30 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/filebeat_instructions'; +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function zeekLogsSpecProvider(context) { +export function zeekLogsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'zeek'; - const platforms = ['OSX', 'DEB', 'RPM']; + const platforms = ['OSX', 'DEB', 'RPM'] as const; return { id: 'zeekLogs', - name: i18n.translate('kbn.server.tutorials.zeekLogs.nameTitle', { + name: i18n.translate('home.tutorials.zeekLogs.nameTitle', { defaultMessage: 'Zeek logs', }), - category: TUTORIAL_CATEGORY.SIEM, - shortDescription: i18n.translate('kbn.server.tutorials.zeekLogs.shortDescription', { + category: TutorialsCategory.SIEM, + shortDescription: i18n.translate('home.tutorials.zeekLogs.shortDescription', { defaultMessage: 'Collect the logs created by Zeek/Bro.', }), - longDescription: i18n.translate('kbn.server.tutorials.zeekLogs.longDescription', { + longDescription: i18n.translate('home.tutorials.zeekLogs.longDescription', { defaultMessage: 'The `zeek` Filebeat module collects the logs from \ [Zeek](https://www.zeek.org//documentation/index.html). \ @@ -46,17 +50,14 @@ export function zeekLogsSpecProvider(context) { learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-zeek.html', }, }), - //TODO: euiIconType: 'logoZeek', + // TODO: euiIconType: 'logoZeek', artifacts: { dashboards: [ { id: '7cbb5410-3700-11e9-aa6d-ff445a78330c', - linkLabel: i18n.translate( - 'kbn.server.tutorials.zeekLogs.artifacts.dashboards.linkLabel', - { - defaultMessage: 'Zeek logs dashboard', - } - ), + linkLabel: i18n.translate('home.tutorials.zeekLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'Zeek logs dashboard', + }), isOverview: true, }, ], diff --git a/src/legacy/core_plugins/kibana/server/tutorials/zookeeper_metrics/index.js b/src/plugins/home/server/tutorials/zookeeper_metrics/index.ts similarity index 71% rename from src/legacy/core_plugins/kibana/server/tutorials/zookeeper_metrics/index.js rename to src/plugins/home/server/tutorials/zookeeper_metrics/index.ts index 64b45759b3bca..dcecbb6d4a812 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/zookeeper_metrics/index.js +++ b/src/plugins/home/server/tutorials/zookeeper_metrics/index.ts @@ -18,26 +18,30 @@ */ import { i18n } from '@kbn/i18n'; -import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../services/tutorials'; import { onPremInstructions, cloudInstructions, onPremCloudInstructions, -} from '../../../common/tutorials/metricbeat_instructions'; +} from '../instructions/metricbeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; -export function zookeeperMetricsSpecProvider(context) { +export function zookeeperMetricsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'zookeeper'; return { id: moduleName + 'Metrics', - name: i18n.translate('kbn.server.tutorials.zookeeperMetrics.nameTitle', { + name: i18n.translate('home.tutorials.zookeeperMetrics.nameTitle', { defaultMessage: 'Zookeeper metrics', }), isBeta: false, - category: TUTORIAL_CATEGORY.METRICS, - shortDescription: i18n.translate('kbn.server.tutorials.zookeeperMetrics.shortDescription', { + category: TutorialsCategory.METRICS, + shortDescription: i18n.translate('home.tutorials.zookeeperMetrics.shortDescription', { defaultMessage: 'Fetch internal metrics from a Zookeeper server.', }), - longDescription: i18n.translate('kbn.server.tutorials.zookeeperMetrics.longDescription', { + longDescription: i18n.translate('home.tutorials.zookeeperMetrics.longDescription', { defaultMessage: 'The `{moduleName}` Metricbeat module fetches internal metrics from a Zookeeper server. \ [Learn more]({learnMoreLink}).', @@ -48,7 +52,7 @@ export function zookeeperMetricsSpecProvider(context) { }), artifacts: { application: { - label: i18n.translate('kbn.server.tutorials.zookeeperMetrics.artifacts.application.label', { + label: i18n.translate('home.tutorials.zookeeperMetrics.artifacts.application.label', { defaultMessage: 'Discover', }), path: '/app/kibana#/discover', @@ -59,7 +63,7 @@ export function zookeeperMetricsSpecProvider(context) { }, }, completionTimeMinutes: 10, - onPrem: onPremInstructions(moduleName, null, null, null, context), + onPrem: onPremInstructions(moduleName, context), elasticCloud: cloudInstructions(moduleName), onPremElasticCloud: onPremCloudInstructions(moduleName), }; diff --git a/src/legacy/server/url_shortening/index.js b/src/plugins/kibana_utils/public/history/index.ts similarity index 92% rename from src/legacy/server/url_shortening/index.js rename to src/plugins/kibana_utils/public/history/index.ts index 031af0618d7bc..b4b5658c1c886 100644 --- a/src/legacy/server/url_shortening/index.js +++ b/src/plugins/kibana_utils/public/history/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export { urlShorteningMixin } from './url_shortening_mixin'; +export { removeQueryParam } from './remove_query_param'; diff --git a/src/plugins/kibana_utils/public/history/remove_query_param.test.ts b/src/plugins/kibana_utils/public/history/remove_query_param.test.ts new file mode 100644 index 0000000000000..0b2547ae94668 --- /dev/null +++ b/src/plugins/kibana_utils/public/history/remove_query_param.test.ts @@ -0,0 +1,75 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { removeQueryParam } from './remove_query_param'; +import { createMemoryHistory, Location } from 'history'; + +describe('removeQueryParam', () => { + it('should remove query param from url', () => { + const startLocation: Location = { + pathname: '/dashboard/c3a76790-3134-11ea-b024-83a7b4783735', + search: "?_a=(description:'')&_b=3", + state: null, + hash: '', + }; + + const history = createMemoryHistory(); + history.push(startLocation); + removeQueryParam(history, '_a'); + + expect(history.location).toEqual( + expect.objectContaining({ + pathname: '/dashboard/c3a76790-3134-11ea-b024-83a7b4783735', + search: '?_b=3', + state: null, + hash: '', + }) + ); + }); + + it('should not fail if nothing to remove', () => { + const startLocation: Location = { + pathname: '/dashboard/c3a76790-3134-11ea-b024-83a7b4783735', + search: "?_a=(description:'')&_b=3", + state: null, + hash: '', + }; + + const history = createMemoryHistory(); + history.push(startLocation); + removeQueryParam(history, '_c'); + + expect(history.location).toEqual(expect.objectContaining(startLocation)); + }); + + it('should not fail if no search', () => { + const startLocation: Location = { + pathname: '/dashboard/c3a76790-3134-11ea-b024-83a7b4783735', + search: '', + state: null, + hash: '', + }; + + const history = createMemoryHistory(); + history.push(startLocation); + removeQueryParam(history, '_c'); + + expect(history.location).toEqual(expect.objectContaining(startLocation)); + }); +}); diff --git a/src/legacy/server/url_shortening/routes/lib/short_url_lookup.js b/src/plugins/kibana_utils/public/history/remove_query_param.ts similarity index 54% rename from src/legacy/server/url_shortening/routes/lib/short_url_lookup.js rename to src/plugins/kibana_utils/public/history/remove_query_param.ts index a8a01d1433a7a..fbf985998b4cd 100644 --- a/src/legacy/server/url_shortening/routes/lib/short_url_lookup.js +++ b/src/plugins/kibana_utils/public/history/remove_query_param.ts @@ -17,27 +17,23 @@ * under the License. */ -import { get } from 'lodash'; +import { History, Location } from 'history'; +import { parse } from 'querystring'; +import { stringifyQueryString } from '../state_management/url/stringify_query_string'; // TODO: extract it to ../url -export function shortUrlLookupProvider(server) { - async function updateMetadata(doc, req) { - try { - await req.getSavedObjectsClient().update('url', doc.id, { - accessDate: new Date(), - accessCount: get(doc, 'attributes.accessCount', 0) + 1, - }); - } catch (err) { - server.log('Warning: Error updating url metadata', err); - //swallow errors. It isn't critical if there is no update. - } - } - - return { - async getUrl(id, req) { - const doc = await req.getSavedObjectsClient().get('url', id); - updateMetadata(doc, req); - - return doc.attributes.url; - }, +export function removeQueryParam(history: History, param: string, replace: boolean = true) { + const oldLocation = history.location; + const search = (oldLocation.search || '').replace(/^\?/, ''); + const query = parse(search); + delete query[param]; + const newSearch = stringifyQueryString(query); + const newLocation: Location = { + ...oldLocation, + search: newSearch, }; + if (replace) { + history.replace(newLocation); + } else { + history.push(newLocation); + } } diff --git a/src/plugins/kibana_utils/public/index.ts b/src/plugins/kibana_utils/public/index.ts index fa58a61e51232..00c1c95028b4d 100644 --- a/src/plugins/kibana_utils/public/index.ts +++ b/src/plugins/kibana_utils/public/index.ts @@ -58,3 +58,5 @@ export { StartSyncStateFnType, StopSyncStateFnType, } from './state_sync'; +export { removeQueryParam } from './history'; +export { applyDiff } from './state_management/utils/diff_object'; diff --git a/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.test.ts b/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.test.ts index f1c527d3d5309..6e4c505c62ebc 100644 --- a/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.test.ts +++ b/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.test.ts @@ -85,6 +85,7 @@ describe('kbn_url_storage', () => { beforeEach(() => { history = createMemoryHistory(); urlControls = createKbnUrlControls(history); + urlControls.update('/', true); }); const getCurrentUrl = () => createPath(history.location); @@ -143,17 +144,6 @@ describe('kbn_url_storage', () => { expect(cb).toHaveBeenCalledTimes(3); }); - it('should flush async url updates', async () => { - const pr1 = urlControls.updateAsync(() => '/1', false); - const pr2 = urlControls.updateAsync(() => '/2', false); - const pr3 = urlControls.updateAsync(() => '/3', false); - expect(getCurrentUrl()).toBe('/'); - urlControls.flush(); - expect(getCurrentUrl()).toBe('/3'); - await Promise.all([pr1, pr2, pr3]); - expect(getCurrentUrl()).toBe('/3'); - }); - it('flush should take priority over regular replace behaviour', async () => { const pr1 = urlControls.updateAsync(() => '/1', true); const pr2 = urlControls.updateAsync(() => '/2', false); @@ -174,6 +164,48 @@ describe('kbn_url_storage', () => { await Promise.all([pr1, pr2, pr3]); expect(getCurrentUrl()).toBe('/'); }); + + it('should retrieve pending url ', async () => { + const pr1 = urlControls.updateAsync(() => '/1', true); + const pr2 = urlControls.updateAsync(() => '/2', false); + const pr3 = urlControls.updateAsync(() => '/3', true); + expect(urlControls.getPendingUrl()).toEqual('/3'); + expect(getCurrentUrl()).toBe('/'); + await Promise.all([pr1, pr2, pr3]); + expect(getCurrentUrl()).toBe('/3'); + + expect(urlControls.getPendingUrl()).toBeUndefined(); + }); + }); + + describe('urlControls - browser history integration', () => { + let history: History; + let urlControls: IKbnUrlControls; + beforeEach(() => { + history = createBrowserHistory(); + urlControls = createKbnUrlControls(history); + urlControls.update('/', true); + }); + + const getCurrentUrl = () => window.location.href; + + it('should flush async url updates', async () => { + const pr1 = urlControls.updateAsync(() => '/1', false); + const pr2 = urlControls.updateAsync(() => '/2', false); + const pr3 = urlControls.updateAsync(() => '/3', false); + expect(getCurrentUrl()).toBe('http://localhost/'); + expect(urlControls.flush()).toBe('http://localhost/3'); + expect(getCurrentUrl()).toBe('http://localhost/3'); + await Promise.all([pr1, pr2, pr3]); + expect(getCurrentUrl()).toBe('http://localhost/3'); + }); + + it('flush() should return undefined, if no url updates happened', () => { + expect(urlControls.flush()).toBeUndefined(); + urlControls.updateAsync(() => 'http://localhost/1', false); + urlControls.updateAsync(() => 'http://localhost/', false); + expect(urlControls.flush()).toBeUndefined(); + }); }); describe('getRelativeToHistoryPath', () => { diff --git a/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.ts b/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.ts index 03c136ea3d092..1dd204e717213 100644 --- a/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.ts +++ b/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.ts @@ -107,25 +107,34 @@ export interface IKbnUrlControls { listen: (cb: () => void) => () => void; /** - * Updates url synchronously + * Updates url synchronously, if needed + * skips the update and returns undefined in case when trying to update to current url + * otherwise returns new url + * * @param url - url to update to * @param replace - use replace instead of push */ - update: (url: string, replace: boolean) => string; + update: (url: string, replace: boolean) => string | undefined; /** * Schedules url update to next microtask, * Useful to batch sync changes to url to cause only one browser history update * @param updater - fn which receives current url and should return next url to update to * @param replace - use replace instead of push + * */ - updateAsync: (updater: UrlUpdaterFnType, replace?: boolean) => Promise; + updateAsync: (updater: UrlUpdaterFnType, replace?: boolean) => Promise; /** - * Synchronously flushes scheduled url updates + * If there is a pending url update - returns url that is scheduled for update + */ + getPendingUrl: () => string | undefined; + + /** + * Synchronously flushes scheduled url updates. Returns new flushed url, if there was an update. Otherwise - undefined. * @param replace - if replace passed in, then uses it instead of push. Otherwise push or replace is picked depending on updateQueue */ - flush: (replace?: boolean) => string; + flush: (replace?: boolean) => string | undefined; /** * Cancels any pending url updates @@ -143,9 +152,9 @@ export const createKbnUrlControls = ( // if any call in a queue asked to push, then we should push let shouldReplace = true; - function updateUrl(newUrl: string, replace = false): string { + function updateUrl(newUrl: string, replace = false): string | undefined { const currentUrl = getCurrentUrl(); - if (newUrl === currentUrl) return currentUrl; // skip update + if (newUrl === currentUrl) return undefined; // skip update const historyPath = getRelativeToHistoryPath(newUrl, history); @@ -166,15 +175,22 @@ export const createKbnUrlControls = ( // runs scheduled url updates function flush(replace = shouldReplace) { - if (updateQueue.length === 0) return getCurrentUrl(); - const resultUrl = updateQueue.reduce((url, nextUpdate) => nextUpdate(url), getCurrentUrl()); + const nextUrl = getPendingUrl(); - cleanUp(); + if (!nextUrl) return; - const newUrl = updateUrl(resultUrl, replace); + cleanUp(); + const newUrl = updateUrl(nextUrl, replace); return newUrl; } + function getPendingUrl() { + if (updateQueue.length === 0) return undefined; + const resultUrl = updateQueue.reduce((url, nextUpdate) => nextUpdate(url), getCurrentUrl()); + + return resultUrl; + } + return { listen: (cb: () => void) => history.listen(() => { @@ -199,6 +215,9 @@ export const createKbnUrlControls = ( cancel: () => { cleanUp(); }, + getPendingUrl: () => { + return getPendingUrl(); + }, }; }; diff --git a/src/legacy/ui/public/state_management/utils/diff_object.test.ts b/src/plugins/kibana_utils/public/state_management/utils/diff_object.test.ts similarity index 100% rename from src/legacy/ui/public/state_management/utils/diff_object.test.ts rename to src/plugins/kibana_utils/public/state_management/utils/diff_object.test.ts diff --git a/src/legacy/ui/public/state_management/utils/diff_object.ts b/src/plugins/kibana_utils/public/state_management/utils/diff_object.ts similarity index 100% rename from src/legacy/ui/public/state_management/utils/diff_object.ts rename to src/plugins/kibana_utils/public/state_management/utils/diff_object.ts diff --git a/src/plugins/kibana_utils/public/state_sync/state_sync.test.ts b/src/plugins/kibana_utils/public/state_sync/state_sync.test.ts index 08ad1551420d2..17f41483a0a21 100644 --- a/src/plugins/kibana_utils/public/state_sync/state_sync.test.ts +++ b/src/plugins/kibana_utils/public/state_sync/state_sync.test.ts @@ -291,6 +291,42 @@ describe('state_sync', () => { stop(); }); + + it("should preserve reference to unchanged state slices if them didn't change", async () => { + const otherUnchangedSlice = { a: 'test' }; + const oldState = { + todos: container.get().todos, + otherUnchangedSlice, + }; + container.set(oldState as any); + + const { stop, start } = syncStates([ + { + stateContainer: withDefaultState(container, defaultState), + storageKey: key, + stateStorage: urlSyncStrategy, + }, + ]); + await urlSyncStrategy.set('_s', container.get()); + expect(getCurrentUrl()).toMatchInlineSnapshot( + `"/#?_s=(otherUnchangedSlice:(a:test),todos:!((completed:!f,id:0,text:'Learning%20state%20containers')))"` + ); + start(); + + history.replace( + "/#?_s=(otherUnchangedSlice:(a:test),todos:!((completed:!t,id:0,text:'Learning%20state%20containers')))" + ); + + const newState = container.get(); + expect(newState.todos).toEqual([ + { id: 0, text: 'Learning state containers', completed: true }, + ]); + + // reference to unchanged slice is preserved + expect((newState as any).otherUnchangedSlice).toBe(otherUnchangedSlice); + + stop(); + }); }); }); diff --git a/src/plugins/kibana_utils/public/state_sync/state_sync.ts b/src/plugins/kibana_utils/public/state_sync/state_sync.ts index 9c1116e5da531..28d133829e07c 100644 --- a/src/plugins/kibana_utils/public/state_sync/state_sync.ts +++ b/src/plugins/kibana_utils/public/state_sync/state_sync.ts @@ -24,6 +24,7 @@ import { IStateSyncConfig } from './types'; import { IStateStorage } from './state_sync_state_storage'; import { distinctUntilChangedWithInitialValue } from '../../common'; import { BaseState } from '../state_containers'; +import { applyDiff } from '../state_management/utils/diff_object'; /** * Utility for syncing application state wrapped in state container @@ -100,7 +101,18 @@ export function syncState< const updateState = () => { const newState = stateStorage.get(storageKey); const oldState = stateContainer.get(); - if (!defaultComparator(newState, oldState)) { + if (newState) { + // apply only real differences to new state + const mergedState = { ...oldState } as State; + // merges into 'mergedState' all differences from newState, + // but leaves references if they are deeply the same + const diff = applyDiff(mergedState, newState); + + if (diff.keys.length > 0) { + stateContainer.set(mergedState); + } + } else if (oldState !== newState) { + // empty new state case stateContainer.set(newState); } }; diff --git a/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.test.ts b/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.test.ts index 826122176e061..cc3f1df7c1e00 100644 --- a/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.test.ts +++ b/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.test.ts @@ -46,9 +46,11 @@ describe('KbnUrlStateStorage', () => { const key = '_s'; urlStateStorage.set(key, state); expect(getCurrentUrl()).toMatchInlineSnapshot(`"/"`); - urlStateStorage.flush(); + expect(urlStateStorage.flush()).toBe(true); expect(getCurrentUrl()).toMatchInlineSnapshot(`"/#?_s=(ok:1,test:test)"`); expect(urlStateStorage.get(key)).toEqual(state); + + expect(urlStateStorage.flush()).toBe(false); // nothing to flush, not update }); it('should cancel url updates', async () => { @@ -62,6 +64,19 @@ describe('KbnUrlStateStorage', () => { expect(urlStateStorage.get(key)).toEqual(null); }); + it('should cancel url updates if synchronously returned to the same state', async () => { + const state1 = { test: 'test', ok: 1 }; + const state2 = { test: 'test', ok: 2 }; + const key = '_s'; + const pr1 = urlStateStorage.set(key, state1); + await pr1; + const historyLength = history.length; + const pr2 = urlStateStorage.set(key, state2); + const pr3 = urlStateStorage.set(key, state1); + await Promise.all([pr2, pr3]); + expect(history.length).toBe(historyLength); + }); + it('should notify about url changes', async () => { expect(urlStateStorage.change$).toBeDefined(); const key = '_s'; diff --git a/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.ts b/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.ts index 245006349ad55..082eaa5095ab9 100644 --- a/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.ts +++ b/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.ts @@ -28,7 +28,11 @@ import { } from '../../state_management/url'; export interface IKbnUrlStateStorage extends IStateStorage { - set: (key: string, state: State, opts?: { replace: boolean }) => Promise; + set: ( + key: string, + state: State, + opts?: { replace: boolean } + ) => Promise; get: (key: string) => State | null; change$: (key: string) => Observable; @@ -36,7 +40,8 @@ export interface IKbnUrlStateStorage extends IStateStorage { cancel: () => void; // synchronously runs any pending url updates - flush: (opts?: { replace?: boolean }) => void; + // returned boolean indicates if change occurred + flush: (opts?: { replace?: boolean }) => boolean; } /** @@ -60,7 +65,11 @@ export const createKbnUrlStateStorage = ( replace ); }, - get: key => getStateFromKbnUrl(key), + get: key => { + // if there is a pending url update, then state will be extracted from that pending url, + // otherwise current url will be used to retrieve state from + return getStateFromKbnUrl(key, url.getPendingUrl()); + }, change$: (key: string) => new Observable(observer => { const unlisten = url.listen(() => { @@ -75,7 +84,7 @@ export const createKbnUrlStateStorage = ( share() ), flush: ({ replace = false }: { replace?: boolean } = {}) => { - url.flush(replace); + return !!url.flush(replace); }, cancel() { url.cancel(); diff --git a/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.tsx b/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.tsx index cb0b82d0f0bde..69ba813d2347e 100644 --- a/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.tsx +++ b/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.tsx @@ -161,14 +161,15 @@ export class ManagementSidebarNav extends React.Component< } public render() { - const HEADER_ID = 'management-nav-header'; + const HEADER_ID = 'stack-management-nav-header'; return ( <>

{i18n.translate('management.nav.label', { - defaultMessage: 'Management', + // todo + defaultMessage: 'Stack Management', })}

diff --git a/src/plugins/management/public/index.ts b/src/plugins/management/public/index.ts index faec466dbd671..4ece75bbf36da 100644 --- a/src/plugins/management/public/index.ts +++ b/src/plugins/management/public/index.ts @@ -24,7 +24,12 @@ export function plugin(initializerContext: PluginInitializerContext) { return new ManagementPlugin(); } -export { ManagementSetup, ManagementStart, RegisterManagementApp } from './types'; +export { + ManagementSetup, + ManagementStart, + RegisterManagementApp, + RegisterManagementAppArgs, +} from './types'; export { ManagementApp } from './management_app'; export { ManagementSection } from './management_section'; export { ManagementSidebarNav } from './components'; // for use in legacy management apps diff --git a/src/plugins/management/public/legacy/sections_register.js b/src/plugins/management/public/legacy/sections_register.js index 63d919377f89e..ca35db56c340b 100644 --- a/src/plugins/management/public/legacy/sections_register.js +++ b/src/plugins/management/public/legacy/sections_register.js @@ -27,7 +27,8 @@ export class LegacyManagementAdapter { 'management', { display: i18n.translate('management.displayName', { - defaultMessage: 'Management', + // todo + defaultMessage: 'Stack Management', }), }, capabilities @@ -35,6 +36,7 @@ export class LegacyManagementAdapter { this.main.register('data', { display: i18n.translate('management.connectDataDisplayName', { + // todo defaultMessage: 'Connect Data', }), order: 0, diff --git a/src/plugins/management/public/management_app.tsx b/src/plugins/management/public/management_app.tsx index 705d98eaaf2ff..02b3ea306c23d 100644 --- a/src/plugins/management/public/management_app.tsx +++ b/src/plugins/management/public/management_app.tsx @@ -64,7 +64,8 @@ export class ManagementApp { coreStart.chrome.setBreadcrumbs([ { text: i18n.translate('management.breadcrumb', { - defaultMessage: 'Management', + // todo + defaultMessage: 'Stack Management', }), href: '#/management', }, diff --git a/src/plugins/newsfeed/public/plugin.tsx b/src/plugins/newsfeed/public/plugin.tsx index 5ea5e5b324717..c4e042fe452f9 100644 --- a/src/plugins/newsfeed/public/plugin.tsx +++ b/src/plugins/newsfeed/public/plugin.tsx @@ -23,7 +23,7 @@ import ReactDOM from 'react-dom'; import React from 'react'; import { I18nProvider } from '@kbn/i18n/react'; import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'src/core/public'; -import { NewsfeedPluginInjectedConfig } from '../types'; +import { FetchResult, NewsfeedPluginInjectedConfig } from '../types'; import { NewsfeedNavButton, NewsfeedApiFetchResult } from './components/newsfeed_header_nav_button'; import { getApi } from './lib/api'; @@ -54,10 +54,14 @@ export class NewsfeedPublicPlugin implements Plugin { private fetchNewsfeed(core: CoreStart) { const { http, injectedMetadata } = core; - const config = injectedMetadata.getInjectedVar( - 'newsfeed' - ) as NewsfeedPluginInjectedConfig['newsfeed']; + const config = injectedMetadata.getInjectedVar('newsfeed') as + | NewsfeedPluginInjectedConfig['newsfeed'] + | undefined; + if (!config) { + // running in new platform, injected metadata not available + return new Rx.Observable(); + } return getApi(http, config, this.kibanaVersion).pipe( takeUntil(this.stop$), // stop the interval when stop method is called catchError(() => Rx.of(null)) // do not throw error diff --git a/src/legacy/server/url_shortening/routes/lib/short_url_error.js b/src/plugins/share/common/short_url_routes.ts similarity index 67% rename from src/legacy/server/url_shortening/routes/lib/short_url_error.js rename to src/plugins/share/common/short_url_routes.ts index ed44ba21aa4c4..7b42534de2ab1 100644 --- a/src/legacy/server/url_shortening/routes/lib/short_url_error.js +++ b/src/plugins/share/common/short_url_routes.ts @@ -17,10 +17,15 @@ * under the License. */ -import Boom from 'boom'; +export const GOTO_PREFIX = '/goto'; -export function handleShortUrlError(error) { - return Boom.boomify(error, { - statusCode: error.statusCode || error.status || 500, - }); -} +export const getUrlIdFromGotoRoute = (path: string) => + path.match(new RegExp(`${GOTO_PREFIX}/(.*)$`))?.[1]; + +export const getGotoPath = (urlId: string) => `${GOTO_PREFIX}/${urlId}`; + +export const GETTER_PREFIX = '/api/short_url'; + +export const getUrlPath = (urlId: string) => `${GETTER_PREFIX}/${urlId}`; + +export const CREATE_PATH = '/api/shorten_url'; diff --git a/src/plugins/share/public/lib/url_shortener.ts b/src/plugins/share/public/lib/url_shortener.ts index 29d91bdb1aae6..f2259f1fabf7d 100644 --- a/src/plugins/share/public/lib/url_shortener.ts +++ b/src/plugins/share/public/lib/url_shortener.ts @@ -19,6 +19,7 @@ import url from 'url'; import { HttpStart } from 'kibana/public'; +import { CREATE_PATH, getGotoPath } from '../../common/short_url_routes'; export async function shortenUrl( absoluteUrl: string, @@ -34,10 +35,10 @@ export async function shortenUrl( const body = JSON.stringify({ url: relativeUrl }); - const resp = await post('/api/shorten_url', { body }); + const resp = await post(CREATE_PATH, { body }); return url.format({ protocol: parsedUrl.protocol, host: parsedUrl.host, - pathname: `${basePath}/goto/${resp.urlId}`, + pathname: `${basePath}${getGotoPath(resp.urlId)}`, }); } diff --git a/src/plugins/share/public/plugin.test.ts b/src/plugins/share/public/plugin.test.ts index 5610490be33b3..730814fe9ed23 100644 --- a/src/plugins/share/public/plugin.test.ts +++ b/src/plugins/share/public/plugin.test.ts @@ -20,6 +20,7 @@ import { registryMock, managerMock } from './plugin.test.mocks'; import { SharePlugin } from './plugin'; import { CoreStart } from 'kibana/public'; +import { coreMock } from '../../../core/public/mocks'; describe('SharePlugin', () => { beforeEach(() => { @@ -30,16 +31,28 @@ describe('SharePlugin', () => { describe('setup', () => { test('wires up and returns registry', async () => { - const setup = await new SharePlugin().setup(); + const coreSetup = coreMock.createSetup(); + const setup = await new SharePlugin().setup(coreSetup); expect(registryMock.setup).toHaveBeenCalledWith(); expect(setup.register).toBeDefined(); }); + + test('registers redirect app', async () => { + const coreSetup = coreMock.createSetup(); + await new SharePlugin().setup(coreSetup); + expect(coreSetup.application.register).toHaveBeenCalledWith( + expect.objectContaining({ + id: 'short_url_redirect', + }) + ); + }); }); describe('start', () => { test('wires up and returns show function, but not registry', async () => { + const coreSetup = coreMock.createSetup(); const service = new SharePlugin(); - await service.setup(); + await service.setup(coreSetup); const start = await service.start({} as CoreStart); expect(registryMock.start).toHaveBeenCalled(); expect(managerMock.start).toHaveBeenCalledWith( diff --git a/src/plugins/share/public/plugin.ts b/src/plugins/share/public/plugin.ts index 6d78211cf9954..01c248624950a 100644 --- a/src/plugins/share/public/plugin.ts +++ b/src/plugins/share/public/plugin.ts @@ -17,15 +17,17 @@ * under the License. */ -import { CoreStart, Plugin } from 'src/core/public'; +import { CoreSetup, CoreStart, Plugin } from 'src/core/public'; import { ShareMenuManager, ShareMenuManagerStart } from './services'; import { ShareMenuRegistry, ShareMenuRegistrySetup } from './services'; +import { createShortUrlRedirectApp } from './services/short_url_redirect_app'; export class SharePlugin implements Plugin { private readonly shareMenuRegistry = new ShareMenuRegistry(); private readonly shareContextMenu = new ShareMenuManager(); - public async setup() { + public async setup(core: CoreSetup) { + core.application.register(createShortUrlRedirectApp(core, window.location)); return { ...this.shareMenuRegistry.setup(), }; diff --git a/src/plugins/share/public/services/short_url_redirect_app.test.ts b/src/plugins/share/public/services/short_url_redirect_app.test.ts new file mode 100644 index 0000000000000..206e637451ec0 --- /dev/null +++ b/src/plugins/share/public/services/short_url_redirect_app.test.ts @@ -0,0 +1,46 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { createShortUrlRedirectApp } from './short_url_redirect_app'; +import { coreMock } from '../../../../core/public/mocks'; +import { hashUrl } from '../../../kibana_utils/public'; + +jest.mock('../../../kibana_utils/public', () => ({ hashUrl: jest.fn(x => `${x}/hashed`) })); + +describe('short_url_redirect_app', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should fetch url and redirect to hashed version', async () => { + const coreSetup = coreMock.createSetup({ basePath: 'base' }); + coreSetup.http.get.mockResolvedValueOnce({ url: '/app/abc' }); + const locationMock = { pathname: '/base/goto/12345', href: '' } as Location; + + const { mount } = createShortUrlRedirectApp(coreSetup, locationMock); + await mount(); + + // check for fetching the complete URL + expect(coreSetup.http.get).toHaveBeenCalledWith('/api/short_url/12345'); + // check for hashing the URL returned from the server + expect(hashUrl).toHaveBeenCalledWith('/app/abc'); + // check for redirecting to the prepended path + expect(locationMock.href).toEqual('base/app/abc/hashed'); + }); +}); diff --git a/src/plugins/share/public/services/short_url_redirect_app.ts b/src/plugins/share/public/services/short_url_redirect_app.ts new file mode 100644 index 0000000000000..6f72b711f6602 --- /dev/null +++ b/src/plugins/share/public/services/short_url_redirect_app.ts @@ -0,0 +1,45 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { CoreSetup } from 'kibana/public'; +import { getUrlIdFromGotoRoute, getUrlPath, GOTO_PREFIX } from '../../common/short_url_routes'; +import { hashUrl } from '../../../kibana_utils/public'; + +export const createShortUrlRedirectApp = (core: CoreSetup, location: Location) => ({ + id: 'short_url_redirect', + appRoute: GOTO_PREFIX, + chromeless: true, + title: 'Short URL Redirect', + async mount() { + const urlId = getUrlIdFromGotoRoute(location.pathname); + + if (!urlId) { + throw new Error('Url id not present in path'); + } + + const response = await core.http.get<{ url: string }>(getUrlPath(urlId)); + const redirectUrl = response.url; + const hashedUrl = hashUrl(redirectUrl); + const url = core.http.basePath.prepend(hashedUrl); + + location.href = url; + + return () => {}; + }, +}); diff --git a/src/plugins/share/server/routes/create_routes.ts b/src/plugins/share/server/routes/create_routes.ts index bd4b6fdb08791..22d10c25197c9 100644 --- a/src/plugins/share/server/routes/create_routes.ts +++ b/src/plugins/share/server/routes/create_routes.ts @@ -22,11 +22,13 @@ import { CoreSetup, Logger } from 'kibana/server'; import { shortUrlLookupProvider } from './lib/short_url_lookup'; import { createGotoRoute } from './goto'; import { createShortenUrlRoute } from './shorten_url'; +import { createGetterRoute } from './get'; export function createRoutes({ http }: CoreSetup, logger: Logger) { const shortUrlLookup = shortUrlLookupProvider({ logger }); const router = http.createRouter(); createGotoRoute({ router, shortUrlLookup, http }); + createGetterRoute({ router, shortUrlLookup, http }); createShortenUrlRoute({ router, shortUrlLookup }); } diff --git a/src/legacy/server/url_shortening/routes/goto.js b/src/plugins/share/server/routes/get.ts similarity index 55% rename from src/legacy/server/url_shortening/routes/goto.js rename to src/plugins/share/server/routes/get.ts index 7a874786423d8..d6b191341dbe1 100644 --- a/src/legacy/server/url_shortening/routes/goto.js +++ b/src/plugins/share/server/routes/get.ts @@ -17,23 +17,40 @@ * under the License. */ -import { handleShortUrlError } from './lib/short_url_error'; +import { CoreSetup, IRouter } from 'kibana/server'; +import { schema } from '@kbn/config-schema'; + import { shortUrlAssertValid } from './lib/short_url_assert_valid'; +import { ShortUrlLookupService } from './lib/short_url_lookup'; +import { getUrlPath } from '../../common/short_url_routes'; -export const createGotoRoute = ({ server, shortUrlLookup }) => ({ - method: 'GET', - path: '/goto_LP/{urlId}', - handler: async function(request, h) { - try { - const url = await shortUrlLookup.getUrl(request.params.urlId, request); +export const createGetterRoute = ({ + router, + shortUrlLookup, + http, +}: { + router: IRouter; + shortUrlLookup: ShortUrlLookupService; + http: CoreSetup['http']; +}) => { + router.get( + { + path: getUrlPath('{urlId}'), + validate: { + params: schema.object({ urlId: schema.string() }), + }, + }, + router.handleLegacyErrors(async function(context, request, response) { + const url = await shortUrlLookup.getUrl(request.params.urlId, { + savedObjects: context.core.savedObjects.client, + }); shortUrlAssertValid(url); - const app = server.getHiddenUiAppById('stateSessionStorageRedirect'); - return h.renderApp(app, { - redirectUrl: url, + return response.ok({ + body: { + url, + }, }); - } catch (err) { - throw handleShortUrlError(err); - } - }, -}); + }) + ); +}; diff --git a/src/plugins/share/server/routes/goto.ts b/src/plugins/share/server/routes/goto.ts index 7343dc1bd34a2..5c3a4e441099f 100644 --- a/src/plugins/share/server/routes/goto.ts +++ b/src/plugins/share/server/routes/goto.ts @@ -22,6 +22,7 @@ import { schema } from '@kbn/config-schema'; import { shortUrlAssertValid } from './lib/short_url_assert_valid'; import { ShortUrlLookupService } from './lib/short_url_lookup'; +import { getGotoPath } from '../../common/short_url_routes'; export const createGotoRoute = ({ router, @@ -34,7 +35,7 @@ export const createGotoRoute = ({ }) => { router.get( { - path: '/goto/{urlId}', + path: getGotoPath('{urlId}'), validate: { params: schema.object({ urlId: schema.string() }), }, @@ -54,10 +55,13 @@ export const createGotoRoute = ({ }, }); } - return response.redirected({ + const body = await context.core.rendering.render(); + + return response.ok({ headers: { - location: http.basePath.prepend('/goto_LP/' + request.params.urlId), + 'content-security-policy': http.csp.header, }, + body, }); }) ); diff --git a/src/plugins/share/server/routes/shorten_url.ts b/src/plugins/share/server/routes/shorten_url.ts index 116b90c6971c5..41570f8a5f453 100644 --- a/src/plugins/share/server/routes/shorten_url.ts +++ b/src/plugins/share/server/routes/shorten_url.ts @@ -22,6 +22,7 @@ import { schema } from '@kbn/config-schema'; import { shortUrlAssertValid } from './lib/short_url_assert_valid'; import { ShortUrlLookupService } from './lib/short_url_lookup'; +import { CREATE_PATH } from '../../common/short_url_routes'; export const createShortenUrlRoute = ({ shortUrlLookup, @@ -32,7 +33,7 @@ export const createShortenUrlRoute = ({ }) => { router.post( { - path: '/api/shorten_url', + path: CREATE_PATH, validate: { body: schema.object({ url: schema.string() }), }, diff --git a/src/plugins/vis_type_timeseries/server/validation_telemetry/validation_telemetry_service.ts b/src/plugins/vis_type_timeseries/server/validation_telemetry/validation_telemetry_service.ts index 136f5b9e5cfad..e49664265b8bb 100644 --- a/src/plugins/vis_type_timeseries/server/validation_telemetry/validation_telemetry_service.ts +++ b/src/plugins/vis_type_timeseries/server/validation_telemetry/validation_telemetry_service.ts @@ -64,11 +64,14 @@ export class ValidationTelemetryService implements Plugin start.savedObjects.createInternalRepository()); return { logFailedValidation: async () => { try { + const internalRepository = await internalRepositoryPromise; await internalRepository.incrementCounter( 'tsvb-validation-telemetry', 'tsvb-validation-telemetry', diff --git a/tasks/config/peg.js b/tasks/config/peg.js index 0c57c6139d298..1d8667840faba 100644 --- a/tasks/config/peg.js +++ b/tasks/config/peg.js @@ -25,4 +25,8 @@ module.exports = { allowedStartRules: ['start', 'Literal'], }, }, + timelion_chain: { + src: 'src/legacy/core_plugins/vis_type_timelion/public/chain.peg', + dest: 'src/legacy/core_plugins/vis_type_timelion/public/_generated_/chain.js', + }, }; diff --git a/test/api_integration/apis/general/csp.js b/test/api_integration/apis/general/csp.js index 8c191703070d8..38893c371fb52 100644 --- a/test/api_integration/apis/general/csp.js +++ b/test/api_integration/apis/general/csp.js @@ -21,6 +21,7 @@ import expect from '@kbn/expect'; export default function({ getService }) { const supertest = getService('supertest'); + const kibanaServer = getService('kibanaServer'); describe('csp smoke test', () => { it('app response sends content security policy headers', async () => { @@ -36,11 +37,12 @@ export default function({ getService }) { }) ); + const isDist = await kibanaServer.status.isDistributable(); const entries = Array.from(parsed.entries()); expect(entries).to.eql([ ['script-src', ["'unsafe-eval'", "'self'"]], ['worker-src', ['blob:', "'self'"]], - ['style-src', ["'unsafe-inline'", "'self'"]], + ['style-src', [...(isDist ? [] : ['blob:']), "'unsafe-inline'", "'self'"]], ]); }); }); diff --git a/test/functional/apps/dashboard/create_and_add_embeddables.js b/test/functional/apps/dashboard/create_and_add_embeddables.js index 90f02c36b3b7f..0b628100a98bd 100644 --- a/test/functional/apps/dashboard/create_and_add_embeddables.js +++ b/test/functional/apps/dashboard/create_and_add_embeddables.js @@ -34,6 +34,7 @@ export default function({ getService, getPageObjects }) { await esArchiver.load('dashboard/current/kibana'); await kibanaServer.uiSettings.replace({ defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', + pageNavigation: 'individual', }); await PageObjects.common.navigateToApp('dashboard'); await PageObjects.dashboard.preserveCrossAppState(); @@ -83,7 +84,7 @@ export default function({ getService, getPageObjects }) { describe('is false', () => { before(async () => { - await PageObjects.header.clickManagement(); + await PageObjects.header.clickStackManagement(); await PageObjects.settings.clickKibanaSettings(); await PageObjects.settings.toggleAdvancedSettingCheckbox('visualize:enableLabs'); }); @@ -98,7 +99,7 @@ export default function({ getService, getPageObjects }) { }); after(async () => { - await PageObjects.header.clickManagement(); + await PageObjects.header.clickStackManagement(); await PageObjects.settings.clickKibanaSettings(); await PageObjects.settings.clearAdvancedSettings('visualize:enableLabs'); await PageObjects.header.clickDashboard(); diff --git a/test/functional/apps/dashboard/dashboard_clone.js b/test/functional/apps/dashboard/dashboard_clone.js index f5485c1db206e..8b7f6ba6a34dd 100644 --- a/test/functional/apps/dashboard/dashboard_clone.js +++ b/test/functional/apps/dashboard/dashboard_clone.js @@ -37,7 +37,7 @@ export default function({ getService, getPageObjects }) { await PageObjects.dashboard.addVisualizations( PageObjects.dashboard.getTestVisualizationNames() ); - await PageObjects.dashboard.enterDashboardTitleAndClickSave(dashboardName); + await PageObjects.dashboard.saveDashboard(dashboardName); await PageObjects.dashboard.clickClone(); await PageObjects.dashboard.confirmClone(); diff --git a/test/functional/apps/discover/_errors.js b/test/functional/apps/discover/_errors.js index 53dcd8cc9e5c1..7dbb93c884f46 100644 --- a/test/functional/apps/discover/_errors.js +++ b/test/functional/apps/discover/_errors.js @@ -22,10 +22,11 @@ import expect from '@kbn/expect'; export default function({ getService, getPageObjects }) { const esArchiver = getService('esArchiver'); const testSubjects = getService('testSubjects'); - const PageObjects = getPageObjects(['common']); + const PageObjects = getPageObjects(['common', 'discover']); describe('errors', function describeIndexTests() { before(async function() { + await esArchiver.loadIfNeeded('logstash_functional'); await esArchiver.load('invalid_scripted_field'); await PageObjects.common.navigateToApp('discover'); }); diff --git a/test/functional/apps/management/_index_pattern_filter.js b/test/functional/apps/management/_index_pattern_filter.js index a32024adb5ec7..e685c43e9ce98 100644 --- a/test/functional/apps/management/_index_pattern_filter.js +++ b/test/functional/apps/management/_index_pattern_filter.js @@ -27,7 +27,7 @@ export default function({ getService, getPageObjects }) { describe('index pattern filter', function describeIndexTests() { before(async function() { // delete .kibana index and then wait for Kibana to re-create it - await kibanaServer.uiSettings.replace({}); + await kibanaServer.uiSettings.replace({ pageNavigation: 'individual' }); await PageObjects.settings.navigateTo(); await PageObjects.settings.clickKibanaIndexPatterns(); }); diff --git a/test/functional/apps/visualize/_area_chart.js b/test/functional/apps/visualize/_area_chart.js index e52cfdf478c33..101b2d4f547dd 100644 --- a/test/functional/apps/visualize/_area_chart.js +++ b/test/functional/apps/visualize/_area_chart.js @@ -295,7 +295,6 @@ export default function({ getService, getPageObjects }) { await PageObjects.visEditor.clickMetricsAndAxes(); await PageObjects.visEditor.clickYAxisOptions(axisId); await PageObjects.visEditor.selectYAxisScaleType(axisId, 'log'); - await PageObjects.visEditor.clickYAxisAdvancedOptions(axisId); await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, false); await PageObjects.visEditor.clickGo(); const labels = await PageObjects.visChart.getYAxisLabels(); diff --git a/test/functional/apps/visualize/_lab_mode.js b/test/functional/apps/visualize/_lab_mode.js index 3ee806af8165d..b082480d95a2e 100644 --- a/test/functional/apps/visualize/_lab_mode.js +++ b/test/functional/apps/visualize/_lab_mode.js @@ -23,7 +23,6 @@ export default function({ getService, getPageObjects }) { const log = getService('log'); const PageObjects = getPageObjects(['common', 'header', 'discover', 'settings']); - // Flaky: https://github.com/elastic/kibana/issues/19743 describe('visualize lab mode', () => { it('disabling does not break loading saved searches', async () => { await PageObjects.common.navigateToUrl('discover', ''); @@ -36,7 +35,7 @@ export default function({ getService, getPageObjects }) { log.info('found saved search before toggling enableLabs mode'); // Navigate to advanced setting and disable lab mode - await PageObjects.header.clickManagement(); + await PageObjects.header.clickStackManagement(); await PageObjects.settings.clickKibanaSettings(); await PageObjects.settings.toggleAdvancedSettingCheckbox('visualize:enableLabs'); @@ -50,7 +49,7 @@ export default function({ getService, getPageObjects }) { after(async () => { await PageObjects.discover.closeLoadSaveSearchPanel(); - await PageObjects.header.clickManagement(); + await PageObjects.header.clickStackManagement(); await PageObjects.settings.clickKibanaSettings(); await PageObjects.settings.clearAdvancedSettings('visualize:enableLabs'); }); diff --git a/test/functional/apps/visualize/_line_chart.js b/test/functional/apps/visualize/_line_chart.js index becf66f0fd5b1..e8be1bf4c41c4 100644 --- a/test/functional/apps/visualize/_line_chart.js +++ b/test/functional/apps/visualize/_line_chart.js @@ -189,7 +189,6 @@ export default function({ getService, getPageObjects }) { await PageObjects.visEditor.clickMetricsAndAxes(); await PageObjects.visEditor.clickYAxisOptions(axisId); await PageObjects.visEditor.selectYAxisScaleType(axisId, 'log'); - await PageObjects.visEditor.clickYAxisAdvancedOptions(axisId); await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, false); await PageObjects.visEditor.clickGo(); const labels = await PageObjects.visChart.getYAxisLabels(); diff --git a/test/functional/apps/visualize/_vertical_bar_chart.js b/test/functional/apps/visualize/_vertical_bar_chart.js index 2efa812c7a734..9bb220a11a86a 100644 --- a/test/functional/apps/visualize/_vertical_bar_chart.js +++ b/test/functional/apps/visualize/_vertical_bar_chart.js @@ -27,509 +27,546 @@ export default function({ getService, getPageObjects }) { const filterBar = getService('filterBar'); const PageObjects = getPageObjects(['visualize', 'visEditor', 'visChart', 'timePicker']); - // FLAKY: https://github.com/elastic/kibana/issues/22322 describe('vertical bar chart', function() { - const vizName1 = 'Visualization VerticalBarChart'; - - const initBarChart = async () => { - log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); - log.debug('clickVerticalBarChart'); - await PageObjects.visualize.clickVerticalBarChart(); - await PageObjects.visualize.clickNewSearch(); - await PageObjects.timePicker.setDefaultAbsoluteRange(); - log.debug('Bucket = X-Axis'); - await PageObjects.visEditor.clickBucket('X-axis'); - log.debug('Aggregation = Date Histogram'); - await PageObjects.visEditor.selectAggregation('Date Histogram'); - log.debug('Field = @timestamp'); - await PageObjects.visEditor.selectField('@timestamp'); - // leaving Interval set to Auto - await PageObjects.visEditor.clickGo(); - }; - - before(initBarChart); - - it('should save and load', async function() { - await PageObjects.visualize.saveVisualizationExpectSuccessAndBreadcrumb(vizName1); - - await PageObjects.visualize.loadSavedVisualization(vizName1); - await PageObjects.visChart.waitForVisualization(); - }); + describe('bar charts x axis tick labels', () => { + it('should show tick labels also after rotation of the chart', async function() { + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickVerticalBarChart(); + await PageObjects.visualize.clickNewSearch(); + await PageObjects.timePicker.setDefaultAbsoluteRange(); + await PageObjects.visEditor.clickBucket('X-axis'); + log.debug('Aggregation = Date Histogram'); + await PageObjects.visEditor.selectAggregation('Date Histogram'); + log.debug('Field = @timestamp'); + await PageObjects.visEditor.selectField('@timestamp'); + // leaving Interval set to Auto + await PageObjects.visEditor.clickGo(); + const bottomLabels = await PageObjects.visChart.getXAxisLabels(); + log.debug(`${bottomLabels.length} tick labels on bottom x axis`); - it('should have inspector enabled', async function() { - await inspector.expectIsEnabled(); - }); + await PageObjects.visEditor.clickMetricsAndAxes(); + await PageObjects.visEditor.selectXAxisPosition('left'); + await PageObjects.visEditor.clickGo(); - it('should show correct chart', async function() { - const expectedChartValues = [ - 37, - 202, - 740, - 1437, - 1371, - 751, - 188, - 31, - 42, - 202, - 683, - 1361, - 1415, - 707, - 177, - 27, - 32, - 175, - 707, - 1408, - 1355, - 726, - 201, - 29, - ]; - - // Most recent failure on Jenkins usually indicates the bar chart is still being drawn? - // return arguments[0].getAttribute(arguments[1]);","args":[{"ELEMENT":"592"},"fill"]}] arguments[0].getAttribute is not a function - // try sleeping a bit before getting that data - await retry.try(async () => { - const data = await PageObjects.visChart.getBarChartData(); - log.debug('data=' + data); - log.debug('data.length=' + data.length); - expect(data).to.eql(expectedChartValues); + const leftLabels = await PageObjects.visChart.getXAxisLabels(); + log.debug(`${leftLabels.length} tick labels on left x axis`); + expect(leftLabels.length).to.be.greaterThan(bottomLabels.length * (2 / 3)); }); }); - it('should show correct data', async function() { - // this is only the first page of the tabular data. - const expectedChartData = [ - ['2015-09-20 00:00', '37'], - ['2015-09-20 03:00', '202'], - ['2015-09-20 06:00', '740'], - ['2015-09-20 09:00', '1,437'], - ['2015-09-20 12:00', '1,371'], - ['2015-09-20 15:00', '751'], - ['2015-09-20 18:00', '188'], - ['2015-09-20 21:00', '31'], - ['2015-09-21 00:00', '42'], - ['2015-09-21 03:00', '202'], - ['2015-09-21 06:00', '683'], - ['2015-09-21 09:00', '1,361'], - ['2015-09-21 12:00', '1,415'], - ['2015-09-21 15:00', '707'], - ['2015-09-21 18:00', '177'], - ['2015-09-21 21:00', '27'], - ['2015-09-22 00:00', '32'], - ['2015-09-22 03:00', '175'], - ['2015-09-22 06:00', '707'], - ['2015-09-22 09:00', '1,408'], - ]; - - await inspector.open(); - await inspector.expectTableData(expectedChartData); - await inspector.close(); - }); - - it('should have `drop partial buckets` option', async () => { - const fromTime = 'Sep 20, 2015 @ 06:31:44.000'; - const toTime = 'Sep 22, 2015 @ 18:31:44.000'; - - await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); - - let expectedChartValues = [ - 82, - 218, - 341, - 440, - 480, - 517, - 522, - 446, - 403, - 321, - 258, - 172, - 95, - 55, - 38, - 24, - 3, - 4, - 11, - 14, - 17, - 38, - 49, - 115, - 152, - 216, - 315, - 402, - 446, - 513, - 520, - 474, - 421, - 307, - 230, - 170, - 99, - 48, - 30, - 15, - 10, - 2, - 8, - 7, - 17, - 34, - 37, - 104, - 153, - 241, - 313, - 404, - 492, - 512, - 503, - 473, - 379, - 293, - 277, - 156, - 56, - ]; - - // Most recent failure on Jenkins usually indicates the bar chart is still being drawn? - // return arguments[0].getAttribute(arguments[1]);","args":[{"ELEMENT":"592"},"fill"]}] arguments[0].getAttribute is not a function - // try sleeping a bit before getting that data - await retry.try(async () => { - const data = await PageObjects.visChart.getBarChartData(); - log.debug('data=' + data); - log.debug('data.length=' + data.length); - expect(data).to.eql(expectedChartValues); - }); - - await PageObjects.visEditor.toggleOpenEditor(2); - await PageObjects.visEditor.clickDropPartialBuckets(); - await PageObjects.visEditor.clickGo(); - - expectedChartValues = [ - 218, - 341, - 440, - 480, - 517, - 522, - 446, - 403, - 321, - 258, - 172, - 95, - 55, - 38, - 24, - 3, - 4, - 11, - 14, - 17, - 38, - 49, - 115, - 152, - 216, - 315, - 402, - 446, - 513, - 520, - 474, - 421, - 307, - 230, - 170, - 99, - 48, - 30, - 15, - 10, - 2, - 8, - 7, - 17, - 34, - 37, - 104, - 153, - 241, - 313, - 404, - 492, - 512, - 503, - 473, - 379, - 293, - 277, - 156, - ]; - - // Most recent failure on Jenkins usually indicates the bar chart is still being drawn? - // return arguments[0].getAttribute(arguments[1]);","args":[{"ELEMENT":"592"},"fill"]}] arguments[0].getAttribute is not a function - // try sleeping a bit before getting that data - await retry.try(async () => { - const data = await PageObjects.visChart.getBarChartData(); - log.debug('data=' + data); - log.debug('data.length=' + data.length); - expect(data).to.eql(expectedChartValues); - }); - }); + // FLAKY: https://github.com/elastic/kibana/issues/22322 + describe.skip('vertical bar chart flaky part', function() { + const vizName1 = 'Visualization VerticalBarChart'; + + const initBarChart = async () => { + log.debug('navigateToApp visualize'); + await PageObjects.visualize.navigateToNewVisualization(); + log.debug('clickVerticalBarChart'); + await PageObjects.visualize.clickVerticalBarChart(); + await PageObjects.visualize.clickNewSearch(); + await PageObjects.timePicker.setDefaultAbsoluteRange(); + log.debug('Bucket = X-Axis'); + await PageObjects.visEditor.clickBucket('X-axis'); + log.debug('Aggregation = Date Histogram'); + await PageObjects.visEditor.selectAggregation('Date Histogram'); + log.debug('Field = @timestamp'); + await PageObjects.visEditor.selectField('@timestamp'); + // leaving Interval set to Auto + await PageObjects.visEditor.clickGo(); + }; - describe.skip('switch between Y axis scale types', () => { before(initBarChart); - const axisId = 'ValueAxis-1'; - it('should show ticks on selecting log scale', async () => { - await PageObjects.visEditor.clickMetricsAndAxes(); - await PageObjects.visEditor.clickYAxisOptions(axisId); - await PageObjects.visEditor.selectYAxisScaleType(axisId, 'log'); - await PageObjects.visEditor.clickYAxisAdvancedOptions(axisId); - await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, false); - await PageObjects.visEditor.clickGo(); - const labels = await PageObjects.visChart.getYAxisLabels(); - const expectedLabels = [ - '2', - '3', - '5', - '7', - '10', - '20', - '30', - '50', - '70', - '100', - '200', - '300', - '500', - '700', - '1,000', - '2,000', - '3,000', - '5,000', - '7,000', - ]; - expect(labels).to.eql(expectedLabels); + it('should save and load', async function() { + await PageObjects.visualize.saveVisualizationExpectSuccessAndBreadcrumb(vizName1); + + await PageObjects.visualize.loadSavedVisualization(vizName1); + await PageObjects.visChart.waitForVisualization(); }); - it('should show filtered ticks on selecting log scale', async () => { - await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, true); - await PageObjects.visEditor.clickGo(); - const labels = await PageObjects.visChart.getYAxisLabels(); - const expectedLabels = [ - '2', - '3', - '5', - '7', - '10', - '20', - '30', - '50', - '70', - '100', - '200', - '300', - '500', - '700', - '1,000', - '2,000', - '3,000', - '5,000', - '7,000', - ]; - expect(labels).to.eql(expectedLabels); + it('should have inspector enabled', async function() { + await inspector.expectIsEnabled(); }); - it('should show ticks on selecting square root scale', async () => { - await PageObjects.visEditor.selectYAxisScaleType(axisId, 'square root'); - await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, false); - await PageObjects.visEditor.clickGo(); - const labels = await PageObjects.visChart.getYAxisLabels(); - const expectedLabels = [ - '0', - '200', - '400', - '600', - '800', - '1,000', - '1,200', - '1,400', - '1,600', + it('should show correct chart', async function() { + const expectedChartValues = [ + 37, + 202, + 740, + 1437, + 1371, + 751, + 188, + 31, + 42, + 202, + 683, + 1361, + 1415, + 707, + 177, + 27, + 32, + 175, + 707, + 1408, + 1355, + 726, + 201, + 29, ]; - expect(labels).to.eql(expectedLabels); - }); - it('should show filtered ticks on selecting square root scale', async () => { - await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, true); - await PageObjects.visEditor.clickGo(); - const labels = await PageObjects.visChart.getYAxisLabels(); - const expectedLabels = ['200', '400', '600', '800', '1,000', '1,200', '1,400']; - expect(labels).to.eql(expectedLabels); + // Most recent failure on Jenkins usually indicates the bar chart is still being drawn? + // return arguments[0].getAttribute(arguments[1]);","args":[{"ELEMENT":"592"},"fill"]}] arguments[0].getAttribute is not a function + // try sleeping a bit before getting that data + await retry.try(async () => { + const data = await PageObjects.visChart.getBarChartData(); + log.debug('data=' + data); + log.debug('data.length=' + data.length); + expect(data).to.eql(expectedChartValues); + }); }); - it('should show ticks on selecting linear scale', async () => { - await PageObjects.visEditor.selectYAxisScaleType(axisId, 'linear'); - await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, false); - await PageObjects.visEditor.clickGo(); - const labels = await PageObjects.visChart.getYAxisLabels(); - log.debug(labels); - const expectedLabels = [ - '0', - '200', - '400', - '600', - '800', - '1,000', - '1,200', - '1,400', - '1,600', + it('should show correct data', async function() { + // this is only the first page of the tabular data. + const expectedChartData = [ + ['2015-09-20 00:00', '37'], + ['2015-09-20 03:00', '202'], + ['2015-09-20 06:00', '740'], + ['2015-09-20 09:00', '1,437'], + ['2015-09-20 12:00', '1,371'], + ['2015-09-20 15:00', '751'], + ['2015-09-20 18:00', '188'], + ['2015-09-20 21:00', '31'], + ['2015-09-21 00:00', '42'], + ['2015-09-21 03:00', '202'], + ['2015-09-21 06:00', '683'], + ['2015-09-21 09:00', '1,361'], + ['2015-09-21 12:00', '1,415'], + ['2015-09-21 15:00', '707'], + ['2015-09-21 18:00', '177'], + ['2015-09-21 21:00', '27'], + ['2015-09-22 00:00', '32'], + ['2015-09-22 03:00', '175'], + ['2015-09-22 06:00', '707'], + ['2015-09-22 09:00', '1,408'], ]; - expect(labels).to.eql(expectedLabels); - }); - - it('should show filtered ticks on selecting linear scale', async () => { - await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, true); - await PageObjects.visEditor.clickGo(); - const labels = await PageObjects.visChart.getYAxisLabels(); - const expectedLabels = ['200', '400', '600', '800', '1,000', '1,200', '1,400']; - expect(labels).to.eql(expectedLabels); - }); - }); - describe('vertical bar in percent mode', async () => { - it('should show ticks with percentage values', async function() { - const axisId = 'ValueAxis-1'; - await PageObjects.visEditor.clickMetricsAndAxes(); - await PageObjects.visEditor.clickYAxisOptions(axisId); - await PageObjects.visEditor.selectYAxisMode('percentage'); - await PageObjects.visEditor.clickGo(); - const labels = await PageObjects.visChart.getYAxisLabels(); - expect(labels[0]).to.eql('0%'); - expect(labels[labels.length - 1]).to.eql('100%'); + await inspector.open(); + await inspector.expectTableData(expectedChartData); + await inspector.close(); }); - }); - - describe('vertical bar with Split series', function() { - before(initBarChart); - - it('should show correct series', async function() { - await PageObjects.visEditor.toggleOpenEditor(2, 'false'); - await PageObjects.visEditor.clickBucket('Split series'); - await PageObjects.visEditor.selectAggregation('Terms'); - await PageObjects.visEditor.selectField('response.raw'); - await PageObjects.visChart.waitForVisualizationRenderingStabilized(); - await PageObjects.visEditor.clickGo(); - const expectedEntries = ['200', '404', '503']; - const legendEntries = await PageObjects.visChart.getLegendEntries(); - expect(legendEntries).to.eql(expectedEntries); - }); + it('should have `drop partial buckets` option', async () => { + const fromTime = 'Sep 20, 2015 @ 06:31:44.000'; + const toTime = 'Sep 22, 2015 @ 18:31:44.000'; + + await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); + + let expectedChartValues = [ + 82, + 218, + 341, + 440, + 480, + 517, + 522, + 446, + 403, + 321, + 258, + 172, + 95, + 55, + 38, + 24, + 3, + 4, + 11, + 14, + 17, + 38, + 49, + 115, + 152, + 216, + 315, + 402, + 446, + 513, + 520, + 474, + 421, + 307, + 230, + 170, + 99, + 48, + 30, + 15, + 10, + 2, + 8, + 7, + 17, + 34, + 37, + 104, + 153, + 241, + 313, + 404, + 492, + 512, + 503, + 473, + 379, + 293, + 277, + 156, + 56, + ]; - it('should allow custom sorting of series', async () => { - await PageObjects.visEditor.toggleOpenEditor(1, 'false'); - await PageObjects.visEditor.selectCustomSortMetric(3, 'Min', 'bytes'); + // Most recent failure on Jenkins usually indicates the bar chart is still being drawn? + // return arguments[0].getAttribute(arguments[1]);","args":[{"ELEMENT":"592"},"fill"]}] arguments[0].getAttribute is not a function + // try sleeping a bit before getting that data + await retry.try(async () => { + const data = await PageObjects.visChart.getBarChartData(); + log.debug('data=' + data); + log.debug('data.length=' + data.length); + expect(data).to.eql(expectedChartValues); + }); + + await PageObjects.visEditor.toggleOpenEditor(2); + await PageObjects.visEditor.clickDropPartialBuckets(); await PageObjects.visEditor.clickGo(); - const expectedEntries = ['404', '200', '503']; - const legendEntries = await PageObjects.visChart.getLegendEntries(); - expect(legendEntries).to.eql(expectedEntries); - }); + expectedChartValues = [ + 218, + 341, + 440, + 480, + 517, + 522, + 446, + 403, + 321, + 258, + 172, + 95, + 55, + 38, + 24, + 3, + 4, + 11, + 14, + 17, + 38, + 49, + 115, + 152, + 216, + 315, + 402, + 446, + 513, + 520, + 474, + 421, + 307, + 230, + 170, + 99, + 48, + 30, + 15, + 10, + 2, + 8, + 7, + 17, + 34, + 37, + 104, + 153, + 241, + 313, + 404, + 492, + 512, + 503, + 473, + 379, + 293, + 277, + 156, + ]; - it('should correctly filter by legend', async () => { - await PageObjects.visChart.filterLegend('200'); - await PageObjects.visChart.waitForVisualization(); - const legendEntries = await PageObjects.visChart.getLegendEntries(); - const expectedEntries = ['200']; - expect(legendEntries).to.eql(expectedEntries); - await filterBar.removeFilter('response.raw'); - await PageObjects.visChart.waitForVisualization(); + // Most recent failure on Jenkins usually indicates the bar chart is still being drawn? + // return arguments[0].getAttribute(arguments[1]);","args":[{"ELEMENT":"592"},"fill"]}] arguments[0].getAttribute is not a function + // try sleeping a bit before getting that data + await retry.try(async () => { + const data = await PageObjects.visChart.getBarChartData(); + log.debug('data=' + data); + log.debug('data.length=' + data.length); + expect(data).to.eql(expectedChartValues); + }); }); - }); - - describe('vertical bar with multiple splits', function() { - before(initBarChart); - it('should show correct series', async function() { - await PageObjects.visEditor.toggleOpenEditor(2, 'false'); - await PageObjects.visEditor.clickBucket('Split series'); - await PageObjects.visEditor.selectAggregation('Terms'); - await PageObjects.visEditor.selectField('response.raw'); - await PageObjects.visChart.waitForVisualizationRenderingStabilized(); - - await PageObjects.visEditor.toggleOpenEditor(3, 'false'); - await PageObjects.visEditor.clickBucket('Split series'); - await PageObjects.visEditor.selectAggregation('Terms'); - await PageObjects.visEditor.selectField('machine.os'); - await PageObjects.visChart.waitForVisualizationRenderingStabilized(); - await PageObjects.visEditor.clickGo(); + describe('switch between Y axis scale types', () => { + before(initBarChart); + const axisId = 'ValueAxis-1'; - const expectedEntries = [ - '200 - win 8', - '200 - win xp', - '200 - ios', - '200 - osx', - '200 - win 7', - '404 - ios', - '503 - ios', - '503 - osx', - '503 - win 7', - '503 - win 8', - '503 - win xp', - '404 - osx', - '404 - win 7', - '404 - win 8', - '404 - win xp', - ]; - const legendEntries = await PageObjects.visChart.getLegendEntries(); - expect(legendEntries).to.eql(expectedEntries); + it('should show ticks on selecting log scale', async () => { + await PageObjects.visEditor.clickMetricsAndAxes(); + await PageObjects.visEditor.clickYAxisOptions(axisId); + await PageObjects.visEditor.selectYAxisScaleType(axisId, 'log'); + await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, false); + await PageObjects.visEditor.clickGo(); + const labels = await PageObjects.visChart.getYAxisLabels(); + const expectedLabels = [ + '2', + '3', + '4', + '5', + '7', + '9', + '20', + '30', + '40', + '50', + '70', + '90', + '200', + '300', + '400', + '500', + '700', + '900', + '2,000', + '3,000', + '4,000', + '5,000', + '7,000', + ]; + expect(labels).to.eql(expectedLabels); + }); + + it('should show filtered ticks on selecting log scale', async () => { + await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, true); + await PageObjects.visEditor.clickGo(); + const labels = await PageObjects.visChart.getYAxisLabels(); + const expectedLabels = [ + '2', + '3', + '4', + '5', + '7', + '9', + '20', + '30', + '40', + '50', + '70', + '90', + '200', + '300', + '400', + '500', + '700', + '900', + '2,000', + '3,000', + '4,000', + '5,000', + '7,000', + ]; + expect(labels).to.eql(expectedLabels); + }); + + it('should show ticks on selecting square root scale', async () => { + await PageObjects.visEditor.selectYAxisScaleType(axisId, 'square root'); + await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, false); + await PageObjects.visEditor.clickGo(); + const labels = await PageObjects.visChart.getYAxisLabels(); + const expectedLabels = [ + '0', + '200', + '400', + '600', + '800', + '1,000', + '1,200', + '1,400', + '1,600', + ]; + expect(labels).to.eql(expectedLabels); + }); + + it('should show filtered ticks on selecting square root scale', async () => { + await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, true); + await PageObjects.visEditor.clickGo(); + const labels = await PageObjects.visChart.getYAxisLabels(); + const expectedLabels = ['200', '400', '600', '800', '1,000', '1,200', '1,400']; + expect(labels).to.eql(expectedLabels); + }); + + it('should show ticks on selecting linear scale', async () => { + await PageObjects.visEditor.selectYAxisScaleType(axisId, 'linear'); + await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, false); + await PageObjects.visEditor.clickGo(); + const labels = await PageObjects.visChart.getYAxisLabels(); + log.debug(labels); + const expectedLabels = [ + '0', + '200', + '400', + '600', + '800', + '1,000', + '1,200', + '1,400', + '1,600', + ]; + expect(labels).to.eql(expectedLabels); + }); + + it('should show filtered ticks on selecting linear scale', async () => { + await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, true); + await PageObjects.visEditor.clickGo(); + const labels = await PageObjects.visChart.getYAxisLabels(); + const expectedLabels = ['200', '400', '600', '800', '1,000', '1,200', '1,400']; + expect(labels).to.eql(expectedLabels); + }); }); - it('should show correct series when disabling first agg', async function() { - // this will avoid issues with the play tooltip covering the disable agg button - await testSubjects.scrollIntoView('metricsAggGroup'); - await PageObjects.visEditor.toggleDisabledAgg(3); - await PageObjects.visEditor.clickGo(); - - const expectedEntries = ['win 8', 'win xp', 'ios', 'osx', 'win 7']; - const legendEntries = await PageObjects.visChart.getLegendEntries(); - expect(legendEntries).to.eql(expectedEntries); + describe('vertical bar in percent mode', async () => { + it('should show ticks with percentage values', async function() { + const axisId = 'ValueAxis-1'; + await PageObjects.visEditor.clickMetricsAndAxes(); + await PageObjects.visEditor.clickYAxisOptions(axisId); + await PageObjects.visEditor.selectYAxisMode('percentage'); + await PageObjects.visEditor.changeYAxisShowCheckbox(axisId, true); + await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, false); + await PageObjects.visEditor.clickGo(); + const labels = await PageObjects.visChart.getYAxisLabels(); + expect(labels[0]).to.eql('0%'); + expect(labels[labels.length - 1]).to.eql('100%'); + }); }); - }); - - describe('vertical bar with derivative', function() { - before(initBarChart); - it('should show correct series', async function() { - await PageObjects.visEditor.toggleOpenEditor(2, 'false'); - await PageObjects.visEditor.toggleOpenEditor(1); - await PageObjects.visEditor.selectAggregation('Derivative', 'metrics'); - await PageObjects.visChart.waitForVisualizationRenderingStabilized(); - await PageObjects.visEditor.clickGo(); - - const expectedEntries = ['Derivative of Count']; - const legendEntries = await PageObjects.visChart.getLegendEntries(); - expect(legendEntries).to.eql(expectedEntries); + describe('vertical bar with Split series', function() { + before(initBarChart); + + it('should show correct series', async function() { + await PageObjects.visEditor.toggleOpenEditor(2, 'false'); + await PageObjects.visEditor.clickBucket('Split series'); + await PageObjects.visEditor.selectAggregation('Terms'); + await PageObjects.visEditor.selectField('response.raw'); + await PageObjects.visChart.waitForVisualizationRenderingStabilized(); + await PageObjects.visEditor.clickGo(); + + const expectedEntries = ['200', '404', '503']; + const legendEntries = await PageObjects.visChart.getLegendEntries(); + expect(legendEntries).to.eql(expectedEntries); + }); + + it('should allow custom sorting of series', async () => { + await PageObjects.visEditor.toggleOpenEditor(1, 'false'); + await PageObjects.visEditor.selectCustomSortMetric(3, 'Min', 'bytes'); + await PageObjects.visEditor.clickGo(); + + const expectedEntries = ['404', '200', '503']; + const legendEntries = await PageObjects.visChart.getLegendEntries(); + expect(legendEntries).to.eql(expectedEntries); + }); + + it('should correctly filter by legend', async () => { + await PageObjects.visChart.filterLegend('200'); + await PageObjects.visChart.waitForVisualization(); + const legendEntries = await PageObjects.visChart.getLegendEntries(); + const expectedEntries = ['200']; + expect(legendEntries).to.eql(expectedEntries); + await filterBar.removeFilter('response.raw'); + await PageObjects.visChart.waitForVisualization(); + }); }); - it('should show an error if last bucket aggregation is terms', async () => { - await PageObjects.visEditor.toggleOpenEditor(2, 'false'); - await PageObjects.visEditor.clickBucket('Split series'); - await PageObjects.visEditor.selectAggregation('Terms'); - await PageObjects.visEditor.selectField('response.raw'); + describe('vertical bar with multiple splits', function() { + before(initBarChart); + + it('should show correct series', async function() { + await PageObjects.visEditor.toggleOpenEditor(2, 'false'); + await PageObjects.visEditor.clickBucket('Split series'); + await PageObjects.visEditor.selectAggregation('Terms'); + await PageObjects.visEditor.selectField('response.raw'); + await PageObjects.visChart.waitForVisualizationRenderingStabilized(); + + await PageObjects.visEditor.toggleOpenEditor(3, 'false'); + await PageObjects.visEditor.clickBucket('Split series'); + await PageObjects.visEditor.selectAggregation('Terms'); + await PageObjects.visEditor.selectField('machine.os'); + await PageObjects.visChart.waitForVisualizationRenderingStabilized(); + await PageObjects.visEditor.clickGo(); + + const expectedEntries = [ + '200 - win 8', + '200 - win xp', + '200 - ios', + '200 - osx', + '200 - win 7', + '404 - ios', + '503 - ios', + '503 - osx', + '503 - win 7', + '503 - win 8', + '503 - win xp', + '404 - osx', + '404 - win 7', + '404 - win 8', + '404 - win xp', + ]; + const legendEntries = await PageObjects.visChart.getLegendEntries(); + expect(legendEntries).to.eql(expectedEntries); + }); + + it('should show correct series when disabling first agg', async function() { + // this will avoid issues with the play tooltip covering the disable agg button + await testSubjects.scrollIntoView('metricsAggGroup'); + await PageObjects.visEditor.toggleDisabledAgg(3); + await PageObjects.visEditor.clickGo(); + + const expectedEntries = ['win 8', 'win xp', 'ios', 'osx', 'win 7']; + const legendEntries = await PageObjects.visChart.getLegendEntries(); + expect(legendEntries).to.eql(expectedEntries); + }); + }); - const errorMessage = await PageObjects.visEditor.getBucketErrorMessage(); - expect(errorMessage).to.contain('Last bucket aggregation must be "Date Histogram"'); + describe('vertical bar with derivative', function() { + before(initBarChart); + + it('should show correct series', async function() { + await PageObjects.visEditor.toggleOpenEditor(2, 'false'); + await PageObjects.visEditor.toggleOpenEditor(1); + await PageObjects.visEditor.selectAggregation('Derivative', 'metrics'); + await PageObjects.visChart.waitForVisualizationRenderingStabilized(); + await PageObjects.visEditor.clickGo(); + + const expectedEntries = ['Derivative of Count']; + const legendEntries = await PageObjects.visChart.getLegendEntries(); + expect(legendEntries).to.eql(expectedEntries); + }); + + it('should show an error if last bucket aggregation is terms', async () => { + await PageObjects.visEditor.toggleOpenEditor(2, 'false'); + await PageObjects.visEditor.clickBucket('Split series'); + await PageObjects.visEditor.selectAggregation('Terms'); + await PageObjects.visEditor.selectField('response.raw'); + + const errorMessage = await PageObjects.visEditor.getBucketErrorMessage(); + expect(errorMessage).to.contain('Last bucket aggregation must be "Date Histogram"'); + }); }); }); }); diff --git a/test/functional/apps/visualize/_vertical_bar_chart_nontimeindex.js b/test/functional/apps/visualize/_vertical_bar_chart_nontimeindex.js index 2371df6e92476..2743f6bb38feb 100644 --- a/test/functional/apps/visualize/_vertical_bar_chart_nontimeindex.js +++ b/test/functional/apps/visualize/_vertical_bar_chart_nontimeindex.js @@ -137,7 +137,6 @@ export default function({ getService, getPageObjects }) { await PageObjects.visEditor.clickMetricsAndAxes(); await PageObjects.visEditor.clickYAxisOptions(axisId); await PageObjects.visEditor.selectYAxisScaleType(axisId, 'log'); - await PageObjects.visEditor.clickYAxisAdvancedOptions(axisId); await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, false); await PageObjects.visEditor.clickGo(); const labels = await PageObjects.visChart.getYAxisLabels(); diff --git a/test/functional/page_objects/header_page.js b/test/functional/page_objects/header_page.js index f82e4e4387e27..05edd64545a56 100644 --- a/test/functional/page_objects/header_page.js +++ b/test/functional/page_objects/header_page.js @@ -59,8 +59,8 @@ export function HeaderPageProvider({ getService, getPageObjects }) { await this.awaitGlobalLoadingIndicatorHidden(); } - async clickManagement() { - await appsMenu.clickLink('Management'); + async clickStackManagement() { + await appsMenu.clickLink('Stack Management'); await this.awaitGlobalLoadingIndicatorHidden(); } diff --git a/test/functional/page_objects/settings_page.js b/test/functional/page_objects/settings_page.ts similarity index 90% rename from test/functional/page_objects/settings_page.js rename to test/functional/page_objects/settings_page.ts index a4ae361b12ed8..e92780143f09a 100644 --- a/test/functional/page_objects/settings_page.js +++ b/test/functional/page_objects/settings_page.ts @@ -19,8 +19,10 @@ import { map as mapAsync } from 'bluebird'; import expect from '@kbn/expect'; +import { NavSetting } from '../../../src/core/public/chrome/ui/header/'; +import { FtrProviderContext } from '../ftr_provider_context'; -export function SettingsPageProvider({ getService, getPageObjects }) { +export function SettingsPageProvider({ getService, getPageObjects }: FtrProviderContext) { const log = getService('log'); const retry = getService('retry'); const browser = getService('browser'); @@ -34,7 +36,8 @@ export function SettingsPageProvider({ getService, getPageObjects }) { async clickNavigation() { find.clickDisplayedByCssSelector('.app-link:nth-child(5) a'); } - async clickLinkText(text) { + + async clickLinkText(text: string) { await find.clickByDisplayedLinkText(text); } async clickKibanaSettings() { @@ -55,6 +58,7 @@ export function SettingsPageProvider({ getService, getPageObjects }) { // check for the index pattern info flyout that covers the // create index pattern button on smaller screens + // @ts-ignore await retry.waitFor('index pattern info flyout', async () => { if (await testSubjects.exists('CreateIndexPatternPrompt')) { await testSubjects.click('CreateIndexPatternPrompt > euiFlyoutCloseButton'); @@ -62,18 +66,18 @@ export function SettingsPageProvider({ getService, getPageObjects }) { }); } - async getAdvancedSettings(propertyName) { + async getAdvancedSettings(propertyName: string) { log.debug('in getAdvancedSettings'); const setting = await testSubjects.find(`advancedSetting-editField-${propertyName}`); return await setting.getAttribute('value'); } - async expectDisabledAdvancedSetting(propertyName) { + async expectDisabledAdvancedSetting(propertyName: string) { const setting = await testSubjects.find(`advancedSetting-editField-${propertyName}`); expect(setting.getAttribute('disabled')).to.eql(''); } - async getAdvancedSettingCheckbox(propertyName) { + async getAdvancedSettingCheckbox(propertyName: string) { log.debug('in getAdvancedSettingCheckbox'); return await testSubjects.getAttribute( `advancedSetting-editField-${propertyName}`, @@ -81,12 +85,12 @@ export function SettingsPageProvider({ getService, getPageObjects }) { ); } - async clearAdvancedSettings(propertyName) { + async clearAdvancedSettings(propertyName: string) { await testSubjects.click(`advancedSetting-resetField-${propertyName}`); await PageObjects.header.waitUntilLoadingHasFinished(); } - async setAdvancedSettingsSelect(propertyName, propertyValue) { + async setAdvancedSettingsSelect(propertyName: string, propertyValue: string) { await find.clickByCssSelector( `[data-test-subj="advancedSetting-editField-${propertyName}"] option[value="${propertyValue}"]` ); @@ -95,7 +99,7 @@ export function SettingsPageProvider({ getService, getPageObjects }) { await PageObjects.header.waitUntilLoadingHasFinished(); } - async setAdvancedSettingsInput(propertyName, propertyValue) { + async setAdvancedSettingsInput(propertyName: string, propertyValue: string) { const input = await testSubjects.find(`advancedSetting-editField-${propertyName}`); await input.clearValue(); await input.type(propertyValue); @@ -103,7 +107,7 @@ export function SettingsPageProvider({ getService, getPageObjects }) { await PageObjects.header.waitUntilLoadingHasFinished(); } - async toggleAdvancedSettingCheckbox(propertyName) { + async toggleAdvancedSettingCheckbox(propertyName: string) { testSubjects.click(`advancedSetting-editField-${propertyName}`); await PageObjects.header.waitUntilLoadingHasFinished(); await testSubjects.click(`advancedSetting-saveEditField-${propertyName}`); @@ -126,7 +130,7 @@ export function SettingsPageProvider({ getService, getPageObjects }) { return await testSubjects.find('createIndexPatternTimeFieldSelect'); } - async selectTimeFieldOption(selection) { + async selectTimeFieldOption(selection: string) { // open dropdown await this.clickTimeFieldNameField(); // close dropdown, keep focus @@ -141,7 +145,7 @@ export function SettingsPageProvider({ getService, getPageObjects }) { }); } - async getTimeFieldOption(selection) { + async getTimeFieldOption(selection: string) { return await find.displayedByCssSelector('option[value="' + selection + '"]'); } @@ -174,9 +178,9 @@ export function SettingsPageProvider({ getService, getPageObjects }) { return await find.allByCssSelector('table.euiTable thead tr th'); } - async sortBy(columnName) { + async sortBy(columnName: string) { const chartTypes = await find.allByCssSelector('table.euiTable thead tr th button'); - async function getChartType(chart) { + async function getChartType(chart: Record) { const chartString = await chart.getVisibleText(); if (chartString === columnName) { await chart.click(); @@ -187,7 +191,7 @@ export function SettingsPageProvider({ getService, getPageObjects }) { return Promise.all(getChartTypesPromises); } - async getTableRow(rowNumber, colNumber) { + async getTableRow(rowNumber: number, colNumber: number) { // passing in zero-based index, but adding 1 for css 1-based indexes return await find.byCssSelector( 'table.euiTable tbody tr:nth-child(' + @@ -234,13 +238,13 @@ export function SettingsPageProvider({ getService, getPageObjects }) { }); } - async setFieldTypeFilter(type) { + async setFieldTypeFilter(type: string) { await find.clickByCssSelector( 'select[data-test-subj="indexedFieldTypeFilterDropdown"] > option[label="' + type + '"]' ); } - async setScriptedFieldLanguageFilter(language) { + async setScriptedFieldLanguageFilter(language: string) { await find.clickByCssSelector( 'select[data-test-subj="scriptedFieldLanguageFilterDropdown"] > option[label="' + language + @@ -248,13 +252,13 @@ export function SettingsPageProvider({ getService, getPageObjects }) { ); } - async filterField(name) { + async filterField(name: string) { const input = await testSubjects.find('indexPatternFieldFilter'); await input.clearValue(); await input.type(name); } - async openControlsByName(name) { + async openControlsByName(name: string) { await this.filterField(name); const tableFields = await ( await find.byCssSelector( @@ -312,7 +316,7 @@ export function SettingsPageProvider({ getService, getPageObjects }) { } async createIndexPattern( - indexPatternName, + indexPatternName: string, timefield = '@timestamp', isStandardIndexPattern = true ) { @@ -364,7 +368,7 @@ export function SettingsPageProvider({ getService, getPageObjects }) { async getIndexPatternIdFromUrl() { const currentUrl = await browser.getCurrentUrl(); - const indexPatternId = currentUrl.match(/.*\/(.*)/)[1]; + const indexPatternId = currentUrl.match(/.*\/(.*)/)![1]; log.debug('index pattern ID: ', indexPatternId); @@ -423,12 +427,19 @@ export function SettingsPageProvider({ getService, getPageObjects }) { await testSubjects.click('tab-sourceFilters'); } - async editScriptedField(name) { + async editScriptedField(name: string) { await this.filterField(name); await find.clickByCssSelector('.euiTableRowCell--hasActions button:first-child'); } - async addScriptedField(name, language, type, format, popularity, script) { + async addScriptedField( + name: string, + language: string, + type: string, + format: Record, + popularity: string, + script: string + ) { await this.clickAddScriptedField(); await this.setScriptedFieldName(name); if (language) await this.setScriptedFieldLanguage(language); @@ -469,42 +480,42 @@ export function SettingsPageProvider({ getService, getPageObjects }) { await PageObjects.header.waitUntilLoadingHasFinished(); } - async setScriptedFieldName(name) { + async setScriptedFieldName(name: string) { log.debug('set scripted field name = ' + name); const field = await testSubjects.find('editorFieldName'); await field.clearValue(); await field.type(name); } - async setScriptedFieldLanguage(language) { + async setScriptedFieldLanguage(language: string) { log.debug('set scripted field language = ' + language); await find.clickByCssSelector( 'select[data-test-subj="editorFieldLang"] > option[value="' + language + '"]' ); } - async setScriptedFieldType(type) { + async setScriptedFieldType(type: string) { log.debug('set scripted field type = ' + type); await find.clickByCssSelector( 'select[data-test-subj="editorFieldType"] > option[value="' + type + '"]' ); } - async setFieldFormat(format) { + async setFieldFormat(format: string) { log.debug('set scripted field format = ' + format); await find.clickByCssSelector( 'select[data-test-subj="editorSelectedFormatId"] > option[value="' + format + '"]' ); } - async setScriptedFieldUrlType(type) { + async setScriptedFieldUrlType(type: string) { log.debug('set scripted field Url type = ' + type); await find.clickByCssSelector( 'select[data-test-subj="urlEditorType"] > option[value="' + type + '"]' ); } - async setScriptedFieldUrlTemplate(template) { + async setScriptedFieldUrlTemplate(template: string) { log.debug('set scripted field Url Template = ' + template); const urlTemplateField = await find.byCssSelector( 'input[data-test-subj="urlEditorUrlTemplate"]' @@ -512,7 +523,7 @@ export function SettingsPageProvider({ getService, getPageObjects }) { await urlTemplateField.type(template); } - async setScriptedFieldUrlLabelTemplate(labelTemplate) { + async setScriptedFieldUrlLabelTemplate(labelTemplate: string) { log.debug('set scripted field Url Label Template = ' + labelTemplate); const urlEditorLabelTemplate = await find.byCssSelector( 'input[data-test-subj="urlEditorLabelTemplate"]' @@ -520,7 +531,7 @@ export function SettingsPageProvider({ getService, getPageObjects }) { await urlEditorLabelTemplate.type(labelTemplate); } - async setScriptedFieldDatePattern(datePattern) { + async setScriptedFieldDatePattern(datePattern: string) { log.debug('set scripted field Date Pattern = ' + datePattern); const datePatternField = await find.byCssSelector( 'input[data-test-subj="dateEditorPattern"]' @@ -531,21 +542,21 @@ export function SettingsPageProvider({ getService, getPageObjects }) { await datePatternField.type(datePattern); } - async setScriptedFieldStringTransform(stringTransform) { + async setScriptedFieldStringTransform(stringTransform: string) { log.debug('set scripted field string Transform = ' + stringTransform); await find.clickByCssSelector( 'select[data-test-subj="stringEditorTransform"] > option[value="' + stringTransform + '"]' ); } - async setScriptedFieldPopularity(popularity) { + async setScriptedFieldPopularity(popularity: string) { log.debug('set scripted field popularity = ' + popularity); const field = await testSubjects.find('editorFieldCount'); await field.clearValue(); await field.type(popularity); } - async setScriptedFieldScript(script) { + async setScriptedFieldScript(script: string) { log.debug('set scripted field script = ' + script); const aceEditorCssSelector = '[data-test-subj="editorFieldScript"] .ace_editor'; await find.clickByCssSelector(aceEditorCssSelector); @@ -555,7 +566,7 @@ export function SettingsPageProvider({ getService, getPageObjects }) { await browser.pressKeys(...script.split('')); } - async openScriptedFieldHelp(activeTab) { + async openScriptedFieldHelp(activeTab: string) { log.debug('open Scripted Fields help'); let isOpen = await testSubjects.exists('scriptedFieldsHelpFlyout'); if (!isOpen) { @@ -577,7 +588,7 @@ export function SettingsPageProvider({ getService, getPageObjects }) { await flyout.ensureClosed('scriptedFieldsHelpFlyout'); } - async executeScriptedField(script, additionalField) { + async executeScriptedField(script: string, additionalField: string) { log.debug('execute Scripted Fields help'); await this.closeScriptedFieldHelp(); // ensure script help is closed so script input is not blocked await this.setScriptedFieldScript(script); @@ -595,7 +606,7 @@ export function SettingsPageProvider({ getService, getPageObjects }) { return scriptResults; } - async importFile(path, overwriteAll = true) { + async importFile(path: string, overwriteAll = true) { log.debug(`importFile(${path})`); log.debug(`Clicking importObjects`); @@ -645,7 +656,7 @@ export function SettingsPageProvider({ getService, getPageObjects }) { await testSubjects.click('importSavedObjectsConfirmBtn'); } - async associateIndexPattern(oldIndexPatternId, newIndexPatternTitle) { + async associateIndexPattern(oldIndexPatternId: string, newIndexPatternTitle: string) { await find.clickByCssSelector( `select[data-test-subj="managementChangeIndexSelection-${oldIndexPatternId}"] > [data-test-subj="indexPatternOption-${newIndexPatternTitle}"]` @@ -710,7 +721,7 @@ export function SettingsPageProvider({ getService, getPageObjects }) { return await deleteButton.isEnabled(); } - async canSavedObjectBeDeleted(id) { + async canSavedObjectBeDeleted(id: string) { const allCheckBoxes = await testSubjects.findAll('checkboxSelectRow*'); for (const checkBox of allCheckBoxes) { if (await checkBox.isSelected()) { @@ -722,6 +733,12 @@ export function SettingsPageProvider({ getService, getPageObjects }) { await checkBox.click(); return await this.canSavedObjectsBeDeleted(); } + + async setNavType(navType: NavSetting) { + await PageObjects.common.navigateToApp('settings'); + await this.clickKibanaSettings(); + await this.setAdvancedSettingsSelect('pageNavigation', navType); + } } return new SettingsPage(); diff --git a/test/functional/page_objects/visualize_editor_page.ts b/test/functional/page_objects/visualize_editor_page.ts index 1e098e86216e3..cdc16babc4189 100644 --- a/test/functional/page_objects/visualize_editor_page.ts +++ b/test/functional/page_objects/visualize_editor_page.ts @@ -362,13 +362,22 @@ export function VisualizeEditorPageProvider({ getService, getPageObjects }: FtrP await testSubjects.click(`toggleYAxisOptions-${axisId}`); } - public async clickYAxisAdvancedOptions(axisId: string) { - await testSubjects.click(`toggleYAxisAdvancedOptions-${axisId}`); + public async changeYAxisShowCheckbox(axisId: string, enabled: boolean) { + const selector = `valueAxisShow-${axisId}`; + const button = await testSubjects.find(selector); + const isEnabled = (await button.getAttribute('aria-checked')) === 'true'; + if (enabled !== isEnabled) { + await button.click(); + } } public async changeYAxisFilterLabelsCheckbox(axisId: string, enabled: boolean) { const selector = `yAxisFilterLabelsCheckbox-${axisId}`; - await testSubjects.setCheckbox(selector, enabled ? 'check' : 'uncheck'); + const button = await testSubjects.find(selector); + const isEnabled = (await button.getAttribute('aria-checked')) === 'true'; + if (enabled !== isEnabled) { + await button.click(); + } } public async setSize(newValue: string, aggId: string) { @@ -384,11 +393,19 @@ export function VisualizeEditorPageProvider({ getService, getPageObjects }: FtrP } public async selectYAxisScaleType(axisId: string, scaleType: string) { - const selectElement = await testSubjects.find(`scaleSelectYAxis-${axisId}`); - const selector = await selectElement.findByCssSelector(`option[value="${scaleType}"]`); + const selector = await find.byCssSelector( + `#scaleSelectYAxis-${axisId} > option[value="${scaleType}"]` + ); await selector.click(); } + public async selectXAxisPosition(position: string) { + const option = await (await testSubjects.find('categoryAxisPosition')).findByCssSelector( + `option[value="${position}"]` + ); + await option.click(); + } + public async selectYAxisMode(mode: string) { const selector = await find.byCssSelector(`#valueAxisMode0 > option[value="${mode}"]`); await selector.click(); diff --git a/test/plugin_functional/test_suites/core_plugins/application_status.ts b/test/plugin_functional/test_suites/core_plugins/application_status.ts index b6d13a5604011..0cc64277efe11 100644 --- a/test/plugin_functional/test_suites/core_plugins/application_status.ts +++ b/test/plugin_functional/test_suites/core_plugins/application_status.ts @@ -28,7 +28,7 @@ import '../../plugins/core_app_status/public/types'; // eslint-disable-next-line import/no-default-export export default function({ getService, getPageObjects }: PluginFunctionalProviderContext) { - const PageObjects = getPageObjects(['common']); + const PageObjects = getPageObjects(['common', 'settings']); const browser = getService('browser'); const appsMenu = getService('appsMenu'); const testSubjects = getService('testSubjects'); @@ -48,6 +48,10 @@ export default function({ getService, getPageObjects }: PluginFunctionalProvider }; describe('application status management', () => { + before(async () => { + await PageObjects.settings.setNavType('individual'); + }); + beforeEach(async () => { await PageObjects.common.navigateToApp('app_status_start'); }); diff --git a/test/plugin_functional/test_suites/core_plugins/applications.ts b/test/plugin_functional/test_suites/core_plugins/applications.ts index f50d460532556..6567837f65309 100644 --- a/test/plugin_functional/test_suites/core_plugins/applications.ts +++ b/test/plugin_functional/test_suites/core_plugins/applications.ts @@ -122,7 +122,7 @@ export default function({ getService, getPageObjects }: PluginFunctionalProvider }); it('can navigate from NP apps to legacy apps', async () => { - await appsMenu.clickLink('Management'); + await appsMenu.clickLink('Stack Management'); await loadingScreenShown(); await testSubjects.existOrFail('managementNav'); }); diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index 747e177b2e1c8..0713c4f5cc68c 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -1,11 +1,11 @@ { "prefix": "xpack", "paths": { - "xpack.actions": "legacy/plugins/actions", + "xpack.actions": "plugins/actions", "xpack.advancedUiActions": "plugins/advanced_ui_actions", "xpack.alerting": "legacy/plugins/alerting", "xpack.triggersActionsUI": "legacy/plugins/triggers_actions_ui", - "xpack.apm": "legacy/plugins/apm", + "xpack.apm": ["legacy/plugins/apm", "plugins/apm"], "xpack.beatsManagement": "legacy/plugins/beats_management", "xpack.canvas": "legacy/plugins/canvas", "xpack.crossClusterReplication": "legacy/plugins/cross_cluster_replication", @@ -29,9 +29,9 @@ "xpack.monitoring": "legacy/plugins/monitoring", "xpack.painless_playground": "legacy/plugins/painless_playground", "xpack.remoteClusters": "legacy/plugins/remote_clusters", - "xpack.reporting": [ "plugins/reporting", "legacy/plugins/reporting" ], + "xpack.reporting": ["plugins/reporting", "legacy/plugins/reporting"], "xpack.rollupJobs": "legacy/plugins/rollup", - "xpack.searchProfiler": "legacy/plugins/searchprofiler", + "xpack.searchProfiler": "plugins/searchprofiler", "xpack.security": ["legacy/plugins/security", "plugins/security"], "xpack.server": "legacy/server", "xpack.siem": "legacy/plugins/siem", diff --git a/x-pack/legacy/plugins/actions/index.ts b/x-pack/legacy/plugins/actions/index.ts index dd91d85cd8493..276d1ea3accea 100644 --- a/x-pack/legacy/plugins/actions/index.ts +++ b/x-pack/legacy/plugins/actions/index.ts @@ -4,55 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Legacy } from 'kibana'; -import { Root } from 'joi'; -import mappings from './mappings.json'; -import { init } from './server'; -import { WhitelistedHosts, EnabledActionTypes } from './server/actions_config'; - -export { - ActionsPlugin, - ActionsClient, - ActionType, - ActionTypeExecutorOptions, - PluginSetupContract, - PluginStartContract, -} from './server'; - -export function actions(kibana: any) { - return new kibana.Plugin({ - id: 'actions', - configPrefix: 'xpack.actions', - require: ['kibana', 'elasticsearch', 'task_manager', 'encryptedSavedObjects'], - isEnabled(config: Legacy.KibanaConfig) { - return ( - config.get('xpack.encryptedSavedObjects.enabled') === true && - config.get('xpack.actions.enabled') === true && - config.get('xpack.task_manager.enabled') === true - ); - }, - config(Joi: Root) { - return Joi.object() - .keys({ - enabled: Joi.boolean().default(true), - whitelistedHosts: Joi.array() - .items( - Joi.string() - .hostname() - .allow(WhitelistedHosts.Any) - ) - .sparse(false) - .default([WhitelistedHosts.Any]), - enabledActionTypes: Joi.array() - .items(Joi.string()) - .sparse(false) - .default([EnabledActionTypes.Any]), - }) - .default(); - }, - init, - uiExports: { - mappings, - }, - }); -} +export * from './server/'; diff --git a/x-pack/legacy/plugins/actions/server/extend_route_with_license_check.test.ts b/x-pack/legacy/plugins/actions/server/extend_route_with_license_check.test.ts deleted file mode 100644 index 186c26f6100e4..0000000000000 --- a/x-pack/legacy/plugins/actions/server/extend_route_with_license_check.test.ts +++ /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 { extendRouteWithLicenseCheck } from './extend_route_with_license_check'; -import { LicenseState } from './lib/license_state'; -jest.mock('./lib/license_state', () => ({ - verifyApiAccessFactory: () => {}, -})); - -describe('extendRouteWithLicenseCheck', () => { - describe('#actionsextendRouteWithLicenseCheck', () => { - let licenseState: jest.Mocked; - - test('extends route object with license, if config property already exists', () => { - const newRoute = extendRouteWithLicenseCheck( - { config: { someTestProperty: 'test' } }, - licenseState - ); - expect(newRoute.config.pre.length > 0); - }); - test('extends route object with license check uder options.pre', () => { - const newRoute = extendRouteWithLicenseCheck( - { options: { someProperty: 'test' } }, - licenseState - ); - expect(newRoute.options.pre.length > 0); - }); - }); -}); diff --git a/x-pack/legacy/plugins/actions/server/extend_route_with_license_check.ts b/x-pack/legacy/plugins/actions/server/extend_route_with_license_check.ts deleted file mode 100644 index f39dc125071b4..0000000000000 --- a/x-pack/legacy/plugins/actions/server/extend_route_with_license_check.ts +++ /dev/null @@ -1,19 +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 { LicenseState, verifyApiAccessFactory } from './lib/license_state'; - -export function extendRouteWithLicenseCheck(route: any, licenseState: LicenseState) { - const verifyApiAccessPreRouting = verifyApiAccessFactory(licenseState); - - const key = route.options ? 'options' : 'config'; - return { - ...route, - [key]: { - ...route[key], - pre: [verifyApiAccessPreRouting], - }, - }; -} diff --git a/x-pack/legacy/plugins/actions/server/index.ts b/x-pack/legacy/plugins/actions/server/index.ts index faafb0db330cd..7235eda88149f 100644 --- a/x-pack/legacy/plugins/actions/server/index.ts +++ b/x-pack/legacy/plugins/actions/server/index.ts @@ -3,8 +3,31 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { Root } from 'joi'; +import { Legacy } from 'kibana'; +import mappings from './mappings.json'; -export { init } from './init'; -export { ActionsPlugin, ActionTypeExecutorOptions, ActionType } from './types'; -export { ActionsClient } from './actions_client'; -export { PluginSetupContract, PluginStartContract } from './plugin'; +export function actions(kibana: any) { + return new kibana.Plugin({ + id: 'actions', + configPrefix: 'xpack.actions', + config(Joi: Root) { + return Joi.object({ + enabled: Joi.boolean().default(true), + }) + .unknown(true) + .default(); + }, + require: ['kibana', 'elasticsearch'], + isEnabled(config: Legacy.KibanaConfig) { + return ( + config.get('xpack.encryptedSavedObjects.enabled') === true && + config.get('xpack.actions.enabled') === true && + config.get('xpack.task_manager.enabled') === true + ); + }, + uiExports: { + mappings, + }, + }); +} diff --git a/x-pack/legacy/plugins/actions/server/init.ts b/x-pack/legacy/plugins/actions/server/init.ts deleted file mode 100644 index 6f221b08c4bc5..0000000000000 --- a/x-pack/legacy/plugins/actions/server/init.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { Legacy } from 'kibana'; -import { Plugin } from './plugin'; -import { shim } from './shim'; -import { ActionsPlugin } from './types'; - -export async function init(server: Legacy.Server) { - const { initializerContext, coreSetup, coreStart, pluginsSetup, pluginsStart } = shim(server); - - const plugin = new Plugin(initializerContext); - - const setupContract = await plugin.setup(coreSetup, pluginsSetup); - const startContract = plugin.start(coreStart, pluginsStart); - - server.decorate('request', 'getActionsClient', function() { - return startContract.getActionsClientWithRequest(this); - }); - - const exposedFunctions: ActionsPlugin = { - setup: setupContract, - start: startContract, - }; - server.expose(exposedFunctions); -} diff --git a/x-pack/legacy/plugins/actions/mappings.json b/x-pack/legacy/plugins/actions/server/mappings.json similarity index 100% rename from x-pack/legacy/plugins/actions/mappings.json rename to x-pack/legacy/plugins/actions/server/mappings.json diff --git a/x-pack/legacy/plugins/actions/server/plugin.ts b/x-pack/legacy/plugins/actions/server/plugin.ts deleted file mode 100644 index ffc4a9cf90e54..0000000000000 --- a/x-pack/legacy/plugins/actions/server/plugin.ts +++ /dev/null @@ -1,192 +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 Hapi from 'hapi'; -import { Observable } from 'rxjs'; -import { first } from 'rxjs/operators'; -import { ActionsConfigType, Services } from './types'; -import { ActionExecutor, TaskRunnerFactory } from './lib'; -import { ActionsClient } from './actions_client'; -import { ActionTypeRegistry } from './action_type_registry'; -import { ExecuteOptions } from './create_execute_function'; -import { createExecuteFunction } from './create_execute_function'; -import { registerBuiltInActionTypes } from './builtin_action_types'; -import { IClusterClient, KibanaRequest, Logger } from '../../../../../src/core/server'; -import { getActionsConfigurationUtilities } from './actions_config'; -import { - ActionsPluginInitializerContext, - ActionsCoreSetup, - ActionsCoreStart, - ActionsPluginsSetup, - ActionsPluginsStart, - KibanaConfig, -} from './shim'; -import { - createActionRoute, - deleteActionRoute, - findActionRoute, - getActionRoute, - updateActionRoute, - listActionTypesRoute, - getExecuteActionRoute, -} from './routes'; -import { extendRouteWithLicenseCheck } from './extend_route_with_license_check'; -import { LicenseState } from './lib/license_state'; - -export interface PluginSetupContract { - registerType: ActionTypeRegistry['register']; -} - -export interface PluginStartContract { - listTypes: ActionTypeRegistry['list']; - execute(options: ExecuteOptions): Promise; - getActionsClientWithRequest(request: Hapi.Request): ActionsClient; -} - -export class Plugin { - private readonly kibana$: Observable; - private readonly config$: Observable; - private readonly logger: Logger; - private serverBasePath?: string; - private adminClient?: IClusterClient; - private taskRunnerFactory?: TaskRunnerFactory; - private actionTypeRegistry?: ActionTypeRegistry; - private actionExecutor?: ActionExecutor; - private defaultKibanaIndex?: string; - private licenseState: LicenseState | null = null; - - constructor(initializerContext: ActionsPluginInitializerContext) { - this.logger = initializerContext.logger.get('plugins', 'actions'); - this.config$ = initializerContext.config.create(); - this.kibana$ = initializerContext.config.kibana$; - } - - public async setup( - core: ActionsCoreSetup, - plugins: ActionsPluginsSetup - ): Promise { - const config = await this.config$.pipe(first()).toPromise(); - this.adminClient = core.elasticsearch.adminClient; - this.defaultKibanaIndex = (await this.kibana$.pipe(first()).toPromise()).index; - - this.licenseState = new LicenseState(plugins.licensing.license$); - - // Encrypted attributes - // - `secrets` properties will be encrypted - // - `config` will be included in AAD - // - everything else excluded from AAD - plugins.encryptedSavedObjects.registerType({ - type: 'action', - attributesToEncrypt: new Set(['secrets']), - attributesToExcludeFromAAD: new Set(['name']), - }); - plugins.encryptedSavedObjects.registerType({ - type: 'action_task_params', - attributesToEncrypt: new Set(['apiKey']), - }); - - const actionExecutor = new ActionExecutor(); - const taskRunnerFactory = new TaskRunnerFactory(actionExecutor); - const actionsConfigUtils = getActionsConfigurationUtilities(config as ActionsConfigType); - const actionTypeRegistry = new ActionTypeRegistry({ - taskRunnerFactory, - taskManager: plugins.taskManager, - actionsConfigUtils, - }); - this.taskRunnerFactory = taskRunnerFactory; - this.actionTypeRegistry = actionTypeRegistry; - this.serverBasePath = core.http.basePath.serverBasePath; - this.actionExecutor = actionExecutor; - - registerBuiltInActionTypes({ - logger: this.logger, - actionTypeRegistry, - actionsConfigUtils, - }); - - // Routes - core.http.route(extendRouteWithLicenseCheck(createActionRoute, this.licenseState)); - core.http.route(extendRouteWithLicenseCheck(deleteActionRoute, this.licenseState)); - core.http.route(extendRouteWithLicenseCheck(getActionRoute, this.licenseState)); - core.http.route(extendRouteWithLicenseCheck(findActionRoute, this.licenseState)); - core.http.route(extendRouteWithLicenseCheck(updateActionRoute, this.licenseState)); - core.http.route(extendRouteWithLicenseCheck(listActionTypesRoute, this.licenseState)); - core.http.route( - extendRouteWithLicenseCheck(getExecuteActionRoute(actionExecutor), this.licenseState) - ); - - return { - registerType: actionTypeRegistry.register.bind(actionTypeRegistry), - }; - } - - public start(core: ActionsCoreStart, plugins: ActionsPluginsStart): PluginStartContract { - const { - logger, - actionExecutor, - actionTypeRegistry, - adminClient, - serverBasePath, - taskRunnerFactory, - defaultKibanaIndex, - } = this; - - function getServices(request: any): Services { - return { - callCluster: (...args) => - adminClient!.asScoped(KibanaRequest.from(request)).callAsCurrentUser(...args), - savedObjectsClient: core.savedObjects.getScopedSavedObjectsClient(request), - }; - } - function spaceIdToNamespace(spaceId?: string): string | undefined { - const spacesPlugin = plugins.spaces(); - return spacesPlugin && spaceId ? spacesPlugin.spaceIdToNamespace(spaceId) : undefined; - } - function getBasePath(spaceId?: string): string { - const spacesPlugin = plugins.spaces(); - return spacesPlugin && spaceId ? spacesPlugin.getBasePath(spaceId) : serverBasePath!; - } - - actionExecutor!.initialize({ - logger, - spaces: plugins.spaces, - getServices, - encryptedSavedObjectsPlugin: plugins.encryptedSavedObjects, - actionTypeRegistry: actionTypeRegistry!, - }); - taskRunnerFactory!.initialize({ - encryptedSavedObjectsPlugin: plugins.encryptedSavedObjects, - getBasePath, - spaceIdToNamespace, - }); - - const executeFn = createExecuteFunction({ - taskManager: plugins.taskManager, - getScopedSavedObjectsClient: core.savedObjects.getScopedSavedObjectsClient, - getBasePath, - }); - - return { - execute: executeFn, - listTypes: actionTypeRegistry!.list.bind(actionTypeRegistry!), - getActionsClientWithRequest(request: Hapi.Request) { - const savedObjectsClient = request.getSavedObjectsClient(); - return new ActionsClient({ - savedObjectsClient, - actionTypeRegistry: actionTypeRegistry!, - defaultKibanaIndex: defaultKibanaIndex!, - scopedClusterClient: adminClient!.asScoped(request), - }); - }, - }; - } - - public stop() { - if (this.licenseState) { - this.licenseState.clean(); - } - } -} diff --git a/x-pack/legacy/plugins/actions/server/routes/_mock_server.ts b/x-pack/legacy/plugins/actions/server/routes/_mock_server.ts deleted file mode 100644 index 23356cedb3ab8..0000000000000 --- a/x-pack/legacy/plugins/actions/server/routes/_mock_server.ts +++ /dev/null @@ -1,65 +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 Hapi from 'hapi'; -import { savedObjectsClientMock } from '../../../../../../src/core/server/mocks'; -import { actionsClientMock } from '../actions_client.mock'; -import { actionTypeRegistryMock } from '../action_type_registry.mock'; -import { encryptedSavedObjectsMock } from '../../../../../plugins/encrypted_saved_objects/server/mocks'; - -const defaultConfig = { - 'kibana.index': '.kibana', -}; - -export function createMockServer(config: Record = defaultConfig) { - const server = new Hapi.Server({ - port: 0, - }); - - const actionsClient = actionsClientMock.create(); - const actionTypeRegistry = actionTypeRegistryMock.create(); - const savedObjectsClient = savedObjectsClientMock.create(); - const encryptedSavedObjectsSetup = encryptedSavedObjectsMock.createSetup(); - const encryptedSavedObjectsStart = encryptedSavedObjectsMock.createStart(); - - server.config = () => { - return { - get(key: string) { - return config[key]; - }, - has(key: string) { - return config.hasOwnProperty(key); - }, - }; - }; - - server.register({ - name: 'actions', - register(pluginServer: Hapi.Server) { - pluginServer.expose({ - setup: { - registerType: actionTypeRegistry.register.bind(actionTypeRegistry), - }, - start: { - listTypes: actionTypeRegistry.list.bind(actionTypeRegistry), - }, - }); - }, - }); - - server.decorate('request', 'getSavedObjectsClient', () => savedObjectsClient); - server.decorate('request', 'getActionsClient', () => actionsClient); - server.decorate('request', 'getBasePath', () => '/s/my-space'); - - return { - server, - savedObjectsClient, - actionsClient, - actionTypeRegistry, - encryptedSavedObjectsSetup, - encryptedSavedObjectsStart, - }; -} diff --git a/x-pack/legacy/plugins/actions/server/routes/create.test.ts b/x-pack/legacy/plugins/actions/server/routes/create.test.ts deleted file mode 100644 index 3b6446280e8d5..0000000000000 --- a/x-pack/legacy/plugins/actions/server/routes/create.test.ts +++ /dev/null @@ -1,60 +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 { createMockServer } from './_mock_server'; -import { createActionRoute } from './create'; - -const { server, actionsClient } = createMockServer(); -server.route(createActionRoute); - -beforeEach(() => { - jest.resetAllMocks(); -}); - -it('creates an action with proper parameters', async () => { - const request = { - method: 'POST', - url: '/api/action', - payload: { - name: 'My name', - actionTypeId: 'abc', - config: { foo: true }, - secrets: {}, - }, - }; - const createResult = { - id: '1', - name: 'My name', - actionTypeId: 'abc', - config: { foo: true }, - }; - - actionsClient.create.mockResolvedValueOnce(createResult); - const { payload, statusCode } = await server.inject(request); - expect(statusCode).toBe(200); - const response = JSON.parse(payload); - expect(response).toEqual({ - id: '1', - name: 'My name', - actionTypeId: 'abc', - config: { foo: true }, - }); - expect(actionsClient.create).toHaveBeenCalledTimes(1); - expect(actionsClient.create.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - Object { - "action": Object { - "actionTypeId": "abc", - "config": Object { - "foo": true, - }, - "name": "My name", - "secrets": Object {}, - }, - }, - ] - `); -}); diff --git a/x-pack/legacy/plugins/actions/server/routes/create.ts b/x-pack/legacy/plugins/actions/server/routes/create.ts deleted file mode 100644 index d3cc130f24324..0000000000000 --- a/x-pack/legacy/plugins/actions/server/routes/create.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import Joi from 'joi'; -import Hapi from 'hapi'; -import { ActionResult, WithoutQueryAndParams } from '../types'; - -interface CreateRequest extends WithoutQueryAndParams { - query: { - overwrite: boolean; - }; - params: { - id?: string; - }; - payload: { - name: string; - actionTypeId: string; - config: Record; - secrets: Record; - }; -} - -export const createActionRoute = { - method: 'POST', - path: `/api/action`, - config: { - tags: ['access:actions-all'], - validate: { - options: { - abortEarly: false, - }, - payload: Joi.object() - .keys({ - name: Joi.string().required(), - actionTypeId: Joi.string().required(), - config: Joi.object().default({}), - secrets: Joi.object().default({}), - }) - .required(), - }, - }, - async handler(request: CreateRequest): Promise { - const actionsClient = request.getActionsClient!(); - - const action = request.payload; - return await actionsClient.create({ action }); - }, -}; diff --git a/x-pack/legacy/plugins/actions/server/routes/delete.test.ts b/x-pack/legacy/plugins/actions/server/routes/delete.test.ts deleted file mode 100644 index 66241e3f3d876..0000000000000 --- a/x-pack/legacy/plugins/actions/server/routes/delete.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { createMockServer } from './_mock_server'; -import { deleteActionRoute } from './delete'; - -const { server, actionsClient } = createMockServer(); -server.route(deleteActionRoute); - -beforeEach(() => { - jest.resetAllMocks(); -}); - -it('deletes an action with proper parameters', async () => { - const request = { - method: 'DELETE', - url: '/api/action/1', - }; - - actionsClient.delete.mockResolvedValueOnce({ success: true }); - const { payload, statusCode } = await server.inject(request); - expect(statusCode).toBe(204); - expect(payload).toEqual(''); - expect(actionsClient.delete).toHaveBeenCalledTimes(1); - expect(actionsClient.delete.mock.calls[0]).toMatchInlineSnapshot(` -Array [ - Object { - "id": "1", - }, -] -`); -}); diff --git a/x-pack/legacy/plugins/actions/server/routes/delete.ts b/x-pack/legacy/plugins/actions/server/routes/delete.ts deleted file mode 100644 index 9fce689c09b36..0000000000000 --- a/x-pack/legacy/plugins/actions/server/routes/delete.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import Hapi from 'hapi'; -import Joi from 'joi'; - -interface DeleteRequest extends Hapi.Request { - params: { - id: string; - }; -} - -export const deleteActionRoute = { - method: 'DELETE', - path: `/api/action/{id}`, - config: { - tags: ['access:actions-all'], - validate: { - params: Joi.object() - .keys({ - id: Joi.string().required(), - }) - .required(), - }, - }, - async handler(request: DeleteRequest, h: Hapi.ResponseToolkit) { - const { id } = request.params; - const actionsClient = request.getActionsClient!(); - await actionsClient.delete({ id }); - return h.response().code(204); - }, -}; diff --git a/x-pack/legacy/plugins/actions/server/routes/execute.test.ts b/x-pack/legacy/plugins/actions/server/routes/execute.test.ts deleted file mode 100644 index b0ba5a8a0f566..0000000000000 --- a/x-pack/legacy/plugins/actions/server/routes/execute.test.ts +++ /dev/null @@ -1,45 +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 { createMockServer } from './_mock_server'; -import { getExecuteActionRoute } from './execute'; -import { actionExecutorMock } from '../lib/action_executor.mock'; - -const getServices = jest.fn(); - -const { server } = createMockServer(); -const mockedActionExecutor = actionExecutorMock.create(); -server.route(getExecuteActionRoute(mockedActionExecutor)); - -beforeEach(() => jest.resetAllMocks()); - -it('executes an action with proper parameters', async () => { - const request = { - method: 'POST', - url: '/api/action/1/_execute', - payload: { - params: { - foo: true, - }, - }, - }; - getServices.mockReturnValue({ - log: jest.fn(), - callCluster: jest.fn(), - savedObjectsClient: jest.fn(), - }); - mockedActionExecutor.execute.mockResolvedValueOnce({ status: 'ok', actionId: '1' }); - - const { payload, statusCode } = await server.inject(request); - expect(statusCode).toBe(200); - expect(JSON.parse(payload)).toEqual({ status: 'ok', actionId: '1' }); - - expect(mockedActionExecutor.execute).toHaveBeenCalledWith({ - actionId: '1', - params: { foo: true }, - request: expect.anything(), - }); -}); diff --git a/x-pack/legacy/plugins/actions/server/routes/execute.ts b/x-pack/legacy/plugins/actions/server/routes/execute.ts deleted file mode 100644 index 00fe7a8a24b58..0000000000000 --- a/x-pack/legacy/plugins/actions/server/routes/execute.ts +++ /dev/null @@ -1,55 +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 Joi from 'joi'; -import Hapi from 'hapi'; -import { ActionExecutorContract } from '../lib'; - -interface ExecuteRequest extends Hapi.Request { - params: { - id: string; - }; - payload: { - params: Record; - }; -} - -export function getExecuteActionRoute(actionExecutor: ActionExecutorContract) { - return { - method: 'POST', - path: '/api/action/{id}/_execute', - config: { - tags: ['access:actions-read'], - response: { - emptyStatusCode: 204, - }, - validate: { - options: { - abortEarly: false, - }, - params: Joi.object() - .keys({ - id: Joi.string().required(), - }) - .required(), - payload: Joi.object() - .keys({ - params: Joi.object().required(), - }) - .required(), - }, - }, - async handler(request: ExecuteRequest, h: Hapi.ResponseToolkit) { - const { id } = request.params; - const { params } = request.payload; - return await actionExecutor.execute({ - params, - request, - actionId: id, - }); - }, - }; -} diff --git a/x-pack/legacy/plugins/actions/server/routes/find.test.ts b/x-pack/legacy/plugins/actions/server/routes/find.test.ts deleted file mode 100644 index b4e0cb3a13a97..0000000000000 --- a/x-pack/legacy/plugins/actions/server/routes/find.test.ts +++ /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 { createMockServer } from './_mock_server'; -import { findActionRoute } from './find'; - -const { server, actionsClient } = createMockServer(); -server.route(findActionRoute); - -beforeEach(() => { - jest.resetAllMocks(); -}); - -it('sends proper arguments to action find function', async () => { - const request = { - method: 'GET', - url: - '/api/action/_find?' + - 'per_page=1&' + - 'page=1&' + - 'search=text*&' + - 'default_search_operator=AND&' + - 'search_fields=name&' + - 'sort_field=name&' + - 'fields=name', - }; - const expectedResult = { - total: 0, - perPage: 10, - page: 1, - data: [], - }; - - actionsClient.find.mockResolvedValueOnce(expectedResult); - const { payload, statusCode } = await server.inject(request); - expect(statusCode).toBe(200); - const response = JSON.parse(payload); - expect(response).toEqual(expectedResult); - expect(actionsClient.find).toHaveBeenCalledTimes(1); - expect(actionsClient.find.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - Object { - "options": Object { - "defaultSearchOperator": "AND", - "fields": Array [ - "name", - ], - "filter": undefined, - "hasReference": undefined, - "page": 1, - "perPage": 1, - "search": "text*", - "searchFields": Array [ - "name", - ], - "sortField": "name", - }, - }, - ] - `); -}); diff --git a/x-pack/legacy/plugins/actions/server/routes/find.ts b/x-pack/legacy/plugins/actions/server/routes/find.ts deleted file mode 100644 index e521ba174f287..0000000000000 --- a/x-pack/legacy/plugins/actions/server/routes/find.ts +++ /dev/null @@ -1,86 +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 Joi from 'joi'; -import Hapi from 'hapi'; - -import { WithoutQueryAndParams } from '../types'; - -interface FindRequest extends WithoutQueryAndParams { - query: { - per_page: number; - page: number; - search?: string; - default_search_operator: 'AND' | 'OR'; - search_fields?: string[]; - sort_field?: string; - has_reference?: { - type: string; - id: string; - }; - fields?: string[]; - filter?: string; - }; -} - -export const findActionRoute = { - method: 'GET', - path: `/api/action/_find`, - config: { - tags: ['access:actions-read'], - validate: { - query: Joi.object() - .keys({ - per_page: Joi.number() - .min(0) - .default(20), - page: Joi.number() - .min(1) - .default(1), - search: Joi.string() - .allow('') - .optional(), - default_search_operator: Joi.string() - .valid('OR', 'AND') - .default('OR'), - search_fields: Joi.array() - .items(Joi.string()) - .single(), - sort_field: Joi.string(), - has_reference: Joi.object() - .keys({ - type: Joi.string().required(), - id: Joi.string().required(), - }) - .optional(), - fields: Joi.array() - .items(Joi.string()) - .single(), - filter: Joi.string() - .allow('') - .optional(), - }) - .default(), - }, - }, - async handler(request: FindRequest) { - const query = request.query; - const actionsClient = request.getActionsClient!(); - return await actionsClient.find({ - options: { - perPage: query.per_page, - page: query.page, - search: query.search, - defaultSearchOperator: query.default_search_operator, - searchFields: query.search_fields, - sortField: query.sort_field, - hasReference: query.has_reference, - fields: query.fields, - filter: query.filter, - }, - }); - }, -}; diff --git a/x-pack/legacy/plugins/actions/server/routes/get.test.ts b/x-pack/legacy/plugins/actions/server/routes/get.test.ts deleted file mode 100644 index cdc38df5f6350..0000000000000 --- a/x-pack/legacy/plugins/actions/server/routes/get.test.ts +++ /dev/null @@ -1,43 +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 { createMockServer } from './_mock_server'; -import { getActionRoute } from './get'; -import { ActionResult } from '../types'; - -const { server, actionsClient } = createMockServer(); -server.route(getActionRoute); - -beforeEach(() => { - jest.resetAllMocks(); -}); - -it('calls get with proper parameters', async () => { - const request = { - method: 'GET', - url: '/api/action/1', - }; - const expectedResult: ActionResult = { - id: '1', - actionTypeId: 'my-action-type-id', - config: {}, - name: 'my action type name', - }; - - actionsClient.get.mockResolvedValueOnce(expectedResult); - const { payload, statusCode } = await server.inject(request); - expect(statusCode).toBe(200); - const response = JSON.parse(payload); - expect(response).toEqual(expectedResult); - expect(actionsClient.get).toHaveBeenCalledTimes(1); - expect(actionsClient.get.mock.calls[0]).toMatchInlineSnapshot(` -Array [ - Object { - "id": "1", - }, -] -`); -}); diff --git a/x-pack/legacy/plugins/actions/server/routes/get.ts b/x-pack/legacy/plugins/actions/server/routes/get.ts deleted file mode 100644 index d6946bcb54d7f..0000000000000 --- a/x-pack/legacy/plugins/actions/server/routes/get.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 Joi from 'joi'; -import Hapi from 'hapi'; - -interface GetRequest extends Hapi.Request { - params: { - id: string; - }; -} - -export const getActionRoute = { - method: 'GET', - path: `/api/action/{id}`, - config: { - tags: ['access:actions-read'], - validate: { - params: Joi.object() - .keys({ - id: Joi.string().required(), - }) - .required(), - }, - }, - async handler(request: GetRequest) { - const { id } = request.params; - const actionsClient = request.getActionsClient!(); - return await actionsClient.get({ id }); - }, -}; diff --git a/x-pack/legacy/plugins/actions/server/routes/list_action_types.test.ts b/x-pack/legacy/plugins/actions/server/routes/list_action_types.test.ts deleted file mode 100644 index 3bfc3d736cda6..0000000000000 --- a/x-pack/legacy/plugins/actions/server/routes/list_action_types.test.ts +++ /dev/null @@ -1,37 +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 { createMockServer } from './_mock_server'; -import { listActionTypesRoute } from './list_action_types'; - -const { server, actionTypeRegistry } = createMockServer(); -server.route(listActionTypesRoute); - -beforeEach(() => { - jest.resetAllMocks(); -}); - -it('calls the list function', async () => { - const request = { - method: 'GET', - url: '/api/action/types', - }; - const expectedResult = [ - { - id: '1', - name: 'One', - enabled: true, - }, - ]; - - actionTypeRegistry.list.mockReturnValueOnce(expectedResult); - const { payload, statusCode } = await server.inject(request); - expect(statusCode).toBe(200); - const response = JSON.parse(payload); - expect(response).toEqual(expectedResult); - expect(actionTypeRegistry.list).toHaveBeenCalledTimes(1); - expect(actionTypeRegistry.list.mock.calls[0]).toMatchInlineSnapshot(`Array []`); -}); diff --git a/x-pack/legacy/plugins/actions/server/routes/list_action_types.ts b/x-pack/legacy/plugins/actions/server/routes/list_action_types.ts deleted file mode 100644 index ff545cdf973ad..0000000000000 --- a/x-pack/legacy/plugins/actions/server/routes/list_action_types.ts +++ /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 Hapi from 'hapi'; - -export const listActionTypesRoute = { - method: 'GET', - path: `/api/action/types`, - config: { - tags: ['access:actions-read'], - }, - async handler(request: Hapi.Request) { - return request.server.plugins.actions!.start.listTypes(); - }, -}; diff --git a/x-pack/legacy/plugins/actions/server/routes/update.test.ts b/x-pack/legacy/plugins/actions/server/routes/update.test.ts deleted file mode 100644 index d6294e72474f9..0000000000000 --- a/x-pack/legacy/plugins/actions/server/routes/update.test.ts +++ /dev/null @@ -1,59 +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 { createMockServer } from './_mock_server'; -import { updateActionRoute } from './update'; -import { ActionResult } from '../types'; - -const { server, actionsClient } = createMockServer(); -server.route(updateActionRoute); - -beforeEach(() => { - jest.resetAllMocks(); -}); - -it('calls the update function with proper parameters', async () => { - const request = { - method: 'PUT', - url: '/api/action/1', - payload: { - name: 'My name', - config: { foo: true }, - }, - }; - const updateResult: ActionResult = { - id: '1', - actionTypeId: 'my-action-type-id', - name: 'My name', - config: { foo: true }, - }; - - actionsClient.update.mockResolvedValueOnce(updateResult); - const { payload, statusCode } = await server.inject(request); - expect(statusCode).toBe(200); - const response = JSON.parse(payload); - expect(response).toEqual({ - id: '1', - actionTypeId: 'my-action-type-id', - name: 'My name', - config: { foo: true }, - }); - expect(actionsClient.update).toHaveBeenCalledTimes(1); - expect(actionsClient.update.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - Object { - "action": Object { - "config": Object { - "foo": true, - }, - "name": "My name", - "secrets": Object {}, - }, - "id": "1", - }, - ] - `); -}); diff --git a/x-pack/legacy/plugins/actions/server/routes/update.ts b/x-pack/legacy/plugins/actions/server/routes/update.ts deleted file mode 100644 index a86ef8fa99c8a..0000000000000 --- a/x-pack/legacy/plugins/actions/server/routes/update.ts +++ /dev/null @@ -1,47 +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 Joi from 'joi'; -import Hapi from 'hapi'; - -interface UpdateRequest extends Hapi.Request { - payload: { - name: string; - config: Record; - secrets: Record; - }; -} - -export const updateActionRoute = { - method: 'PUT', - path: `/api/action/{id}`, - config: { - tags: ['access:actions-all'], - validate: { - options: { - abortEarly: false, - }, - params: Joi.object() - .keys({ - id: Joi.string().required(), - }) - .required(), - payload: Joi.object() - .keys({ - name: Joi.string().required(), - config: Joi.object().default({}), - secrets: Joi.object().default({}), - }) - .required(), - }, - }, - async handler(request: UpdateRequest) { - const { id } = request.params; - const { name, config, secrets } = request.payload; - const actionsClient = request.getActionsClient!(); - return await actionsClient.update({ id, action: { name, config, secrets } }); - }, -}; diff --git a/x-pack/legacy/plugins/actions/server/shim.ts b/x-pack/legacy/plugins/actions/server/shim.ts deleted file mode 100644 index 8077dc67c92c4..0000000000000 --- a/x-pack/legacy/plugins/actions/server/shim.ts +++ /dev/null @@ -1,148 +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 Hapi from 'hapi'; -import { Legacy } from 'kibana'; -import * as Rx from 'rxjs'; -import { ActionsConfigType } from './types'; -import { - TaskManagerStartContract, - TaskManagerSetupContract, -} from '../../../../plugins/task_manager/server'; -import { getTaskManagerSetup, getTaskManagerStart } from '../../task_manager/server'; -import { XPackMainPlugin } from '../../xpack_main/server/xpack_main'; -import KbnServer from '../../../../../src/legacy/server/kbn_server'; -import { LegacySpacesPlugin as SpacesPluginStartContract } from '../../spaces'; -import { - PluginSetupContract as EncryptedSavedObjectsSetupContract, - PluginStartContract as EncryptedSavedObjectsStartContract, -} from '../../../../plugins/encrypted_saved_objects/server'; -import { PluginSetupContract as SecurityPlugin } from '../../../../plugins/security/server'; -import { - CoreSetup, - LoggerFactory, - SavedObjectsLegacyService, -} from '../../../../../src/core/server'; -import { LicensingPluginSetup } from '../../../../plugins/licensing/server'; - -export interface KibanaConfig { - index: string; -} - -/** - * Shim what we're thinking setup and start contracts will look like - */ -export type XPackMainPluginSetupContract = Pick; -export type SecurityPluginSetupContract = Pick; -export type SecurityPluginStartContract = Pick; - -/** - * New platform interfaces - */ -export interface ActionsPluginInitializerContext { - logger: LoggerFactory; - config: { - kibana$: Rx.Observable; - create(): Rx.Observable; - }; -} -export interface ActionsCoreSetup { - elasticsearch: CoreSetup['elasticsearch']; - http: { - route: (route: Hapi.ServerRoute) => void; - basePath: { - serverBasePath: string; - }; - }; -} -export interface ActionsCoreStart { - savedObjects: SavedObjectsLegacyService; -} -export interface ActionsPluginsSetup { - security?: SecurityPluginSetupContract; - taskManager: TaskManagerSetupContract; - xpack_main: XPackMainPluginSetupContract; - encryptedSavedObjects: EncryptedSavedObjectsSetupContract; - licensing: LicensingPluginSetup; -} -export interface ActionsPluginsStart { - security?: SecurityPluginStartContract; - spaces: () => SpacesPluginStartContract | undefined; - encryptedSavedObjects: EncryptedSavedObjectsStartContract; - taskManager: TaskManagerStartContract; -} - -/** - * Shim - * - * @param server Hapi server instance - */ -export function shim( - server: Legacy.Server -): { - initializerContext: ActionsPluginInitializerContext; - coreSetup: ActionsCoreSetup; - coreStart: ActionsCoreStart; - pluginsSetup: ActionsPluginsSetup; - pluginsStart: ActionsPluginsStart; -} { - const newPlatform = ((server as unknown) as KbnServer).newPlatform; - - const initializerContext: ActionsPluginInitializerContext = { - logger: newPlatform.coreContext.logger, - config: { - kibana$: Rx.of({ - index: server.config().get('kibana.index'), - }), - create() { - return Rx.of({ - enabled: server.config().get('xpack.actions.enabled') as boolean, - whitelistedHosts: server.config().get('xpack.actions.whitelistedHosts') as string[], - enabledActionTypes: server.config().get('xpack.actions.enabledActionTypes') as string[], - }) as Rx.Observable; - }, - }, - }; - - const coreSetup: ActionsCoreSetup = { - elasticsearch: newPlatform.setup.core.elasticsearch, - http: { - route: server.route.bind(server), - basePath: newPlatform.setup.core.http.basePath, - }, - }; - - const coreStart: ActionsCoreStart = { - savedObjects: server.savedObjects, - }; - - const pluginsSetup: ActionsPluginsSetup = { - security: newPlatform.setup.plugins.security as SecurityPluginSetupContract | undefined, - taskManager: getTaskManagerSetup(server)!, - xpack_main: server.plugins.xpack_main, - encryptedSavedObjects: newPlatform.setup.plugins - .encryptedSavedObjects as EncryptedSavedObjectsSetupContract, - licensing: newPlatform.setup.plugins.licensing as LicensingPluginSetup, - }; - - const pluginsStart: ActionsPluginsStart = { - security: newPlatform.setup.plugins.security as SecurityPluginStartContract | undefined, - // TODO: Currently a function because it's an optional dependency that - // initializes after this function is called - spaces: () => server.plugins.spaces, - encryptedSavedObjects: newPlatform.start.plugins - .encryptedSavedObjects as EncryptedSavedObjectsStartContract, - taskManager: getTaskManagerStart(server)!, - }; - - return { - initializerContext, - coreSetup, - coreStart, - pluginsSetup, - pluginsStart, - }; -} diff --git a/x-pack/legacy/plugins/alerting/server/alert_type_registry.test.ts b/x-pack/legacy/plugins/alerting/server/alert_type_registry.test.ts index e1a05d6460e25..1087ee9109885 100644 --- a/x-pack/legacy/plugins/alerting/server/alert_type_registry.test.ts +++ b/x-pack/legacy/plugins/alerting/server/alert_type_registry.test.ts @@ -118,13 +118,16 @@ describe('list()', () => { registry.register({ id: 'test', name: 'Test', - actionGroups: [], + actionGroups: ['testActionGroup'], executor: jest.fn(), }); const result = registry.list(); expect(result).toMatchInlineSnapshot(` Array [ Object { + "actionGroups": Array [ + "testActionGroup", + ], "id": "test", "name": "Test", }, diff --git a/x-pack/legacy/plugins/alerting/server/alert_type_registry.ts b/x-pack/legacy/plugins/alerting/server/alert_type_registry.ts index 1e9007202c452..8c9844d935163 100644 --- a/x-pack/legacy/plugins/alerting/server/alert_type_registry.ts +++ b/x-pack/legacy/plugins/alerting/server/alert_type_registry.ts @@ -69,6 +69,7 @@ export class AlertTypeRegistry { return Array.from(this.alertTypes).map(([alertTypeId, alertType]) => ({ id: alertTypeId, name: alertType.name, + actionGroups: alertType.actionGroups, })); } } diff --git a/x-pack/legacy/plugins/alerting/server/plugin.ts b/x-pack/legacy/plugins/alerting/server/plugin.ts index 357db9e3df97e..3218d9eeb2ef4 100644 --- a/x-pack/legacy/plugins/alerting/server/plugin.ts +++ b/x-pack/legacy/plugins/alerting/server/plugin.ts @@ -128,11 +128,12 @@ export class Plugin { this.taskRunnerFactory.initialize({ logger: this.logger, - getServices(request: Hapi.Request): Services { + getServices(rawRequest: Hapi.Request): Services { + const request = KibanaRequest.from(rawRequest); return { - callCluster: (...args) => - adminClient!.asScoped(KibanaRequest.from(request)).callAsCurrentUser(...args), - savedObjectsClient: core.savedObjects.getScopedSavedObjectsClient(request), + callCluster: (...args) => adminClient!.asScoped(request).callAsCurrentUser(...args), + // rawRequest is actually a fake request, converting it to KibanaRequest causes issue in SO access + savedObjectsClient: core.savedObjects.getScopedSavedObjectsClient(rawRequest as any), }; }, spaceIdToNamespace, diff --git a/x-pack/legacy/plugins/alerting/server/shim.ts b/x-pack/legacy/plugins/alerting/server/shim.ts index ccc10f929e123..80d01ea722926 100644 --- a/x-pack/legacy/plugins/alerting/server/shim.ts +++ b/x-pack/legacy/plugins/alerting/server/shim.ts @@ -28,7 +28,7 @@ import { ActionsPlugin, PluginSetupContract as ActionsPluginSetupContract, PluginStartContract as ActionsPluginStartContract, -} from '../../actions'; +} from '../../../../plugins/actions/server'; import { LicensingPluginSetup } from '../../../../plugins/licensing/server'; // Extend PluginProperties to indicate which plugins are guaranteed to exist @@ -117,7 +117,7 @@ export function shim( const pluginsSetup: AlertingPluginsSetup = { security: newPlatform.setup.plugins.security as SecurityPluginSetupContract | undefined, taskManager: getTaskManagerSetup(server)!, - actions: server.plugins.actions.setup, + actions: newPlatform.setup.plugins.actions as ActionsPluginSetupContract, xpack_main: server.plugins.xpack_main, encryptedSavedObjects: newPlatform.setup.plugins .encryptedSavedObjects as EncryptedSavedObjectsSetupContract, @@ -126,7 +126,7 @@ export function shim( const pluginsStart: AlertingPluginsStart = { security: newPlatform.setup.plugins.security as SecurityPluginStartContract | undefined, - actions: server.plugins.actions.start, + actions: newPlatform.start.plugins.actions as ActionsPluginStartContract, // TODO: Currently a function because it's an optional dependency that // initializes after this function is called spaces: () => server.plugins.spaces, diff --git a/x-pack/legacy/plugins/alerting/server/task_runner/create_execution_handler.ts b/x-pack/legacy/plugins/alerting/server/task_runner/create_execution_handler.ts index 3fb197ec97f4f..6b4b47b87b300 100644 --- a/x-pack/legacy/plugins/alerting/server/task_runner/create_execution_handler.ts +++ b/x-pack/legacy/plugins/alerting/server/task_runner/create_execution_handler.ts @@ -7,7 +7,7 @@ import { AlertAction, State, Context, AlertType } from '../types'; import { Logger } from '../../../../../../src/core/server'; import { transformActionParams } from './transform_action_params'; -import { PluginStartContract as ActionsPluginStartContract } from '../../../actions'; +import { PluginStartContract as ActionsPluginStartContract } from '../../../../../plugins/actions/server'; interface CreateExecutionHandlerOptions { alertId: string; diff --git a/x-pack/legacy/plugins/alerting/server/task_runner/task_runner_factory.ts b/x-pack/legacy/plugins/alerting/server/task_runner/task_runner_factory.ts index 7178fa4f01282..67fef33b69c6d 100644 --- a/x-pack/legacy/plugins/alerting/server/task_runner/task_runner_factory.ts +++ b/x-pack/legacy/plugins/alerting/server/task_runner/task_runner_factory.ts @@ -6,7 +6,7 @@ import { Logger } from '../../../../../../src/core/server'; import { RunContext } from '../../../../../plugins/task_manager/server'; import { PluginStartContract as EncryptedSavedObjectsStartContract } from '../../../../../plugins/encrypted_saved_objects/server'; -import { PluginStartContract as ActionsPluginStartContract } from '../../../actions'; +import { PluginStartContract as ActionsPluginStartContract } from '../../../../../plugins/actions/server'; import { AlertType, GetBasePathFunction, diff --git a/x-pack/legacy/plugins/apm/index.ts b/x-pack/legacy/plugins/apm/index.ts index 0934cb0019f44..c52e6742ddae5 100644 --- a/x-pack/legacy/plugins/apm/index.ts +++ b/x-pack/legacy/plugins/apm/index.ts @@ -9,6 +9,7 @@ import { Server } from 'hapi'; import { resolve } from 'path'; import { APMPluginContract } from '../../../plugins/apm/server'; import { LegacyPluginInitializer } from '../../../../src/legacy/types'; +import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/utils'; import mappings from './mappings.json'; import { makeApmUsageCollector } from './server/lib/apm_telemetry'; @@ -18,7 +19,6 @@ export const apm: LegacyPluginInitializer = kibana => { id: 'apm', configPrefix: 'xpack.apm', publicDir: resolve(__dirname, 'public'), - uiExports: { app: { title: 'APM', @@ -28,7 +28,8 @@ export const apm: LegacyPluginInitializer = kibana => { main: 'plugins/apm/index', icon: 'plugins/apm/icon.svg', euiIconType: 'apmApp', - order: 8100 + order: 8100, + category: DEFAULT_APP_CATEGORIES.observability }, styleSheetPaths: resolve(__dirname, 'public/index.scss'), home: ['plugins/apm/legacy_register_feature'], diff --git a/x-pack/legacy/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts b/x-pack/legacy/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts index 562eb8850aa0c..fefaa30c8c36b 100644 --- a/x-pack/legacy/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts +++ b/x-pack/legacy/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ import { getInternalSavedObjectsClient } from '../helpers/saved_objects_client'; -import apmIndexPattern from '../../../../../../../src/legacy/core_plugins/kibana/server/tutorials/apm/index_pattern.json'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import apmIndexPattern from '../../../../../../plugins/apm/server/tutorial/index_pattern.json'; import { APM_STATIC_INDEX_PATTERN_ID } from '../../../common/index_pattern_constants'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { SavedObjectsErrorHelpers } from '../../../../../../../src/core/server/saved_objects'; diff --git a/x-pack/legacy/plugins/canvas/index.js b/x-pack/legacy/plugins/canvas/index.js index 8e742de6de944..ebd4f35db8175 100644 --- a/x-pack/legacy/plugins/canvas/index.js +++ b/x-pack/legacy/plugins/canvas/index.js @@ -5,6 +5,7 @@ */ import { resolve } from 'path'; +import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/utils'; import { init } from './init'; import { mappings } from './server/mappings'; import { CANVAS_APP, CANVAS_TYPE, CUSTOM_ELEMENT_TYPE } from './common/lib'; @@ -23,6 +24,7 @@ export function canvas(kibana) { icon: 'plugins/canvas/icon.svg', euiIconType: 'canvasApp', main: 'plugins/canvas/legacy_start', + category: DEFAULT_APP_CATEGORIES.analyze, }, interpreter: [ 'plugins/canvas/browser_functions', diff --git a/x-pack/legacy/plugins/dashboard_mode/index.js b/x-pack/legacy/plugins/dashboard_mode/index.js index 4a04249844322..94655adf981b4 100644 --- a/x-pack/legacy/plugins/dashboard_mode/index.js +++ b/x-pack/legacy/plugins/dashboard_mode/index.js @@ -5,15 +5,13 @@ */ import { resolve } from 'path'; - +import { i18n } from '@kbn/i18n'; +import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/utils'; import { CONFIG_DASHBOARD_ONLY_MODE_ROLES } from './common'; - import { createDashboardModeRequestInterceptor } from './server'; -import { i18n } from '@kbn/i18n'; - // Copied largely from plugins/kibana/index.js. The dashboard viewer includes just the dashboard section of -// the standard kibana plugin. We don't want to include code for the other links (visualize, dev tools, etc) +// the standard kibana plugin. We don't want to include code for the other links (visualize, dev tools, etc) // since it's view only, but we want the urls to be the same, so we are using largely the same setup. export function dashboardMode(kibana) { const kbnBaseUrl = '/app/kibana'; @@ -64,6 +62,7 @@ export function dashboardMode(kibana) { } ), icon: 'plugins/kibana/dashboard/assets/dashboard.svg', + category: DEFAULT_APP_CATEGORIES.analyze, }, ], }, diff --git a/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js b/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js index 391973f6d909b..fbf917054edbf 100644 --- a/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js +++ b/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js @@ -34,6 +34,7 @@ import 'ui/color_maps'; import 'ui/agg_response'; import 'ui/agg_types'; import 'leaflet'; +import 'plugins/kibana/dashboard/legacy'; import { npStart } from 'ui/new_platform'; import { localApplicationService } from 'plugins/kibana/local_application_service'; diff --git a/x-pack/legacy/plugins/file_upload/public/components/json_index_file_picker.js b/x-pack/legacy/plugins/file_upload/public/components/json_index_file_picker.js index f1e74919d734b..0ee4f76ebf9d0 100644 --- a/x-pack/legacy/plugins/file_upload/public/components/json_index_file_picker.js +++ b/x-pack/legacy/plugins/file_upload/public/components/json_index_file_picker.js @@ -13,6 +13,8 @@ import { MAX_FILE_SIZE } from '../../common/constants/file_import'; import _ from 'lodash'; const ACCEPTABLE_FILETYPES = ['json', 'geojson']; +const acceptedFileTypeString = ACCEPTABLE_FILETYPES.map(type => `.${type}`).join(','); +const acceptedFileTypeStringMessage = ACCEPTABLE_FILETYPES.map(type => `.${type}`).join(', '); export class JsonIndexFilePicker extends Component { state = { @@ -103,6 +105,7 @@ export class JsonIndexFilePicker extends Component { const splitNameArr = name.split('.'); const fileType = splitNameArr.pop(); if (!ACCEPTABLE_FILETYPES.includes(fileType)) { + //should only occur if browser does not accept the accept parameter throw new Error( i18n.translate('xpack.fileUpload.jsonIndexFilePicker.acceptableTypesError', { defaultMessage: 'File is not one of acceptable types: {types}', @@ -252,7 +255,10 @@ export class JsonIndexFilePicker extends Component { ) : ( {i18n.translate('xpack.fileUpload.jsonIndexFilePicker.formatsAccepted', { - defaultMessage: 'Formats accepted: .json, .geojson', + defaultMessage: 'Formats accepted: {acceptedFileTypeStringMessage}', + values: { + acceptedFileTypeStringMessage, + }, })}{' '}
} onChange={this._fileHandler} + accept={acceptedFileTypeString} /> diff --git a/x-pack/legacy/plugins/graph/index.ts b/x-pack/legacy/plugins/graph/index.ts index 601a239574e6b..f798fa5e9f39d 100644 --- a/x-pack/legacy/plugins/graph/index.ts +++ b/x-pack/legacy/plugins/graph/index.ts @@ -11,6 +11,7 @@ import { i18n } from '@kbn/i18n'; import migrations from './migrations'; import mappings from './mappings.json'; import { LegacyPluginInitializer } from '../../../../src/legacy/plugin_discovery/types'; +import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/utils'; export const graph: LegacyPluginInitializer = kibana => { return new kibana.Plugin({ @@ -25,6 +26,7 @@ export const graph: LegacyPluginInitializer = kibana => { icon: 'plugins/graph/icon.png', euiIconType: 'graphApp', main: 'plugins/graph/index', + category: DEFAULT_APP_CATEGORIES.analyze, }, styleSheetPaths: resolve(__dirname, 'public/index.scss'), mappings, diff --git a/x-pack/legacy/plugins/graph/public/components/guidance_panel/guidance_panel.tsx b/x-pack/legacy/plugins/graph/public/components/guidance_panel/guidance_panel.tsx index f34b82d6bb1a3..d1fcbea2ff5b7 100644 --- a/x-pack/legacy/plugins/graph/public/components/guidance_panel/guidance_panel.tsx +++ b/x-pack/legacy/plugins/graph/public/components/guidance_panel/guidance_panel.tsx @@ -146,7 +146,7 @@ function GuidancePanelComponent(props: GuidancePanelProps) { ); if (noIndexPatterns) { - const managementUrl = chrome.navLinks.get('kibana:management')!.url; + const managementUrl = chrome.navLinks.get('kibana:stack_management')!.url; const indexPatternUrl = `${managementUrl}/kibana/index_patterns`; const sampleDataUrl = `${application.getUrlForApp( 'kibana' diff --git a/x-pack/legacy/plugins/index_management/__jest__/client_integration/helpers/template_form.helpers.ts b/x-pack/legacy/plugins/index_management/__jest__/client_integration/helpers/template_form.helpers.ts index 48ae51b711f9c..a7c87723b33fb 100644 --- a/x-pack/legacy/plugins/index_management/__jest__/client_integration/helpers/template_form.helpers.ts +++ b/x-pack/legacy/plugins/index_management/__jest__/client_integration/helpers/template_form.helpers.ts @@ -198,7 +198,7 @@ export type TestSubjects = | 'backButton' | 'codeEditorContainer' | 'confirmModalConfirmButton' - | 'createFieldWrapper.addChildButton' + | 'createFieldWrapper.addPropertyButton' | 'createFieldWrapper.addButton' | 'createFieldWrapper.addFieldButton' | 'createFieldWrapper.addMultiFieldButton' diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/analyzers_parameter.tsx b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/analyzers_parameter.tsx index 46b0ece4b325e..0cf22946bf60a 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/analyzers_parameter.tsx +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/analyzers_parameter.tsx @@ -46,7 +46,11 @@ export const AnalyzersParameter = ({ field, withSearchQuoteAnalyzer = false }: P }); return ( - + ); }} @@ -74,7 +78,7 @@ export const AnalyzersParameter = ({ field, withSearchQuoteAnalyzer = false }: P @@ -88,7 +92,7 @@ export const AnalyzersParameter = ({ field, withSearchQuoteAnalyzer = false }: P diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/dynamic_parameter.tsx b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/dynamic_parameter.tsx new file mode 100644 index 0000000000000..975a6cd9bba4f --- /dev/null +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/dynamic_parameter.tsx @@ -0,0 +1,93 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; + +import { documentationService } from '../../../../../services/documentation'; +import { UseField, CheckBoxField } from '../../../shared_imports'; +import { getFieldConfig } from '../../../lib'; +import { Field } from '../../../types'; +import { EditFieldFormRow } from '../fields/edit_field'; + +/** + * Export custom serializer to be used when we need to serialize the form data to be sent to ES + * @param field The field to be serialized + */ +export const dynamicSerializer = (field: Field): Field => { + if (field.dynamic_toggle === undefined) { + return field; + } + + const dynamic = + field.dynamic_toggle === true ? true : field.dynamic_strict === true ? 'strict' : false; + const { dynamic_toggle, dynamic_strict, ...rest } = field; + + return { + ...rest, + dynamic, + }; +}; + +/** + * Export custom deserializer to be used when we need to deserialize the data coming from ES + * @param field The field to be serialized + */ +export const dynamicDeserializer = (field: Field): Field => { + if (field.dynamic === undefined) { + return field; + } + + const dynamicToggleValue = field.dynamic === true; + const dynamicStrictValue = field.dynamic === 'strict'; + + return { + ...field, + dynamic_toggle: dynamicToggleValue, + dynamic_strict: dynamicStrictValue, + }; +}; +interface Props { + defaultToggleValue: boolean; +} + +export const DynamicParameter = ({ defaultToggleValue }: Props) => { + return ( + + {isOn => { + return isOn === false ? ( + + ) : null; + }} + + ); +}; diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/eager_global_ordinals_parameter.tsx b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/eager_global_ordinals_parameter.tsx index ecd9715ea295d..e6acf288331b5 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/eager_global_ordinals_parameter.tsx +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/eager_global_ordinals_parameter.tsx @@ -8,21 +8,31 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; +import { ParameterName } from '../../../types'; import { EditFieldFormRow } from '../fields/edit_field'; import { documentationService } from '../../../../../services/documentation'; -export const EagerGlobalOrdinalsParameter = () => ( +interface Props { + configPath?: ParameterName; + description?: string | JSX.Element; +} + +export const EagerGlobalOrdinalsParameter = ({ + description, + configPath = 'eager_global_ordinals', +}: Props) => ( ( href: documentationService.getEagerGlobalOrdinalsLink(), }} formFieldPath="eager_global_ordinals" + configPath={configPath} /> ); diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/enabled_parameter.tsx b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/enabled_parameter.tsx new file mode 100644 index 0000000000000..719dd89d4c05c --- /dev/null +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/enabled_parameter.tsx @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiCode } from '@elastic/eui'; + +import { documentationService } from '../../../../../services/documentation'; +import { EditFieldFormRow } from '../fields/edit_field'; + +export const EnabledParameter = () => { + return ( + _source, + }} + /> + } + docLink={{ + text: i18n.translate('xpack.idxMgmt.mappingsEditor.enabledDocLinkText', { + defaultMessage: 'Enabled documentation', + }), + href: documentationService.getEnabledLink(), + }} + formFieldPath="enabled" + /> + ); +}; diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/index.ts b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/index.ts index 9622466ad795c..663017e2e47af 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/index.ts +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/index.ts @@ -4,6 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +import { relationsSerializer, relationsDeserializer } from './relations_parameter'; +import { dynamicSerializer, dynamicDeserializer } from './dynamic_parameter'; + export * from './name_parameter'; export * from './index_parameter'; @@ -51,3 +54,15 @@ export * from './fielddata_parameter'; export * from './split_queries_on_whitespace_parameter'; export * from './locale_parameter'; + +export * from './dynamic_parameter'; + +export * from './enabled_parameter'; + +export * from './max_shingle_size_parameter'; + +export * from './relations_parameter'; + +export const PARAMETER_SERIALIZERS = [relationsSerializer, dynamicSerializer]; + +export const PARAMETER_DESERIALIZERS = [relationsDeserializer, dynamicDeserializer]; diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/max_shingle_size_parameter.tsx b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/max_shingle_size_parameter.tsx new file mode 100644 index 0000000000000..bc1917b2da966 --- /dev/null +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/max_shingle_size_parameter.tsx @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { i18n } from '@kbn/i18n'; + +import { getFieldConfig } from '../../../lib'; +import { EditFieldFormRow } from '../fields/edit_field'; +import { UseField, Field } from '../../../shared_imports'; + +interface Props { + defaultToggleValue: boolean; +} + +export const MaxShingleSizeParameter = ({ defaultToggleValue }: Props) => ( + + + +); diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/relations_parameter.tsx b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/relations_parameter.tsx new file mode 100644 index 0000000000000..1fe01968b0983 --- /dev/null +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/field_parameters/relations_parameter.tsx @@ -0,0 +1,268 @@ +/* + * 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 { + EuiButtonEmpty, + EuiToolTip, + EuiButtonIcon, + EuiSpacer, + EuiCallOut, + EuiLink, + EuiBasicTable, + EuiBasicTableColumn, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { + UseField, + UseArray, + ArrayItem, + FieldConfig, + TextField, + ComboBoxField, +} from '../../../shared_imports'; +import { Field } from '../../../types'; + +import { documentationService } from '../../../../../services/documentation'; +import { EditFieldFormRow } from '../fields/edit_field'; + +// This is the Elasticsearch interface to declare relations +interface RelationsES { + [parent: string]: string | string[]; +} + +// Internally we will use this type for "relations" as it is more UI friendly +// to loop over the relations and its children +type RelationsInternal = Array<{ parent: string; children: string[] }>; + +/** + * Export custom serializer to be used when we need to serialize the form data to be sent to ES + * @param field The field to be serialized + */ +export const relationsSerializer = (field: Field): Field => { + if (field.relations === undefined) { + return field; + } + + const relations = field.relations as RelationsInternal; + const relationsSerialized = relations.reduce( + (acc, item) => ({ + ...acc, + [item.parent]: item.children.length === 1 ? item.children[0] : item.children, + }), + {} as RelationsES + ); + + return { + ...field, + relations: relationsSerialized, + }; +}; + +/** + * Export custom deserializer to be used when we need to deserialize the data coming from ES + * @param field The field to be serialized + */ +export const relationsDeserializer = (field: Field): Field => { + if (field.relations === undefined) { + return field; + } + + const relations = field.relations as RelationsES; + const relationsDeserialized = Object.entries(relations).map(([parent, children]) => ({ + parent, + children: typeof children === 'string' ? [children] : children, + })); + + return { + ...field, + relations: relationsDeserialized, + }; +}; + +const childConfig: FieldConfig = { + defaultValue: [], +}; + +export const RelationsParameter = () => { + const renderWarning = () => ( + + {i18n.translate( + 'xpack.idxMgmt.mappingsEditor.join.multiLevelsPerformanceDocumentationLink', + { + defaultMessage: 'denormalize your data.', + } + )} + + ), + }} + /> + } + /> + ); + + return ( + + + {({ items, addItem, removeItem }) => { + const columns: Array> = [ + // Parent column + { + name: i18n.translate( + 'xpack.idxMgmt.mappingsEditor.joinType.relationshipTable.parentColumnTitle', + { + defaultMessage: 'Parent', + } + ), + render: (item: ArrayItem) => { + // By adding ".parent" to the path, we are saying that we want an **object** + // to be created for each array item. + // This object will have a "parent" property with the field value. + return ( +
+ +
+ ); + }, + }, + // Children column (ComboBox) + { + name: i18n.translate( + 'xpack.idxMgmt.mappingsEditor.joinType.relationshipTable.childrenColumnTitle', + { + defaultMessage: 'Children', + } + ), + render: (item: ArrayItem) => { + return ( +
+ +
+ ); + }, + }, + // Actions column + { + width: '48px', + actions: [ + { + render: ({ id }: ArrayItem) => { + const label = i18n.translate( + 'xpack.idxMgmt.mappingsEditor.joinType.relationshipTable.removeRelationshipTooltipLabel', + { + defaultMessage: 'Remove relationship', + } + ); + return ( + + removeItem(id)} + /> + + ); + }, + }, + ], + }, + ]; + + return ( + <> + {items.length > 1 && ( + <> + {renderWarning()} + + + )} + + + + {/* Add relation button */} + + {i18n.translate( + 'xpack.idxMgmt.mappingsEditor.joinType.addRelationshipButtonLabel', + { + defaultMessage: 'Add relationship', + } + )} + + + ); + }} +
+
+ ); +}; diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/field_types/date_type.tsx b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/field_types/date_type.tsx index 3165f18aff4b3..0c067d09046d7 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/field_types/date_type.tsx +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/field_types/date_type.tsx @@ -48,7 +48,7 @@ export const DateType = ({ field }: Props) => { diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/field_types/index.ts b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/field_types/index.ts index 5b81c525804c9..6e18e29f827d4 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/field_types/index.ts +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/field_types/index.ts @@ -24,6 +24,9 @@ import { SearchAsYouType } from './search_as_you_type'; import { FlattenedType } from './flattened_type'; import { ShapeType } from './shape_type'; import { DenseVectorType } from './dense_vector_type'; +import { ObjectType } from './object_type'; +import { NestedType } from './nested_type'; +import { JoinType } from './join_type'; const typeToParametersFormMap: { [key in DataType]?: ComponentType } = { alias: AliasType, @@ -44,6 +47,9 @@ const typeToParametersFormMap: { [key in DataType]?: ComponentType } = { flattened: FlattenedType, shape: ShapeType, dense_vector: DenseVectorType, + object: ObjectType, + nested: NestedType, + join: JoinType, }; export const getParametersFormForType = ( diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/field_types/join_type.tsx b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/field_types/join_type.tsx new file mode 100644 index 0000000000000..688786ae25f8c --- /dev/null +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/field_types/join_type.tsx @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { i18n } from '@kbn/i18n'; + +import { BasicParametersSection, AdvancedParametersSection } from '../edit_field'; +import { RelationsParameter, EagerGlobalOrdinalsParameter } from '../../field_parameters'; + +const i18nTexts = { + eagerGlobalOrdinalsDescription: i18n.translate( + 'xpack.idxMgmt.mappingsEditor.join.eagerGlobalOrdinalsFieldDescription', + { + defaultMessage: + 'The join field uses global ordinals to speed up joins. By default, if the index has changed, global ordinals for the join field will be rebuilt as part of the refresh. This can add significant time to the refresh, however most of the times this is the right trade-off.', + } + ), +}; + +export const JoinType = () => { + return ( + <> + + + + + + + + + ); +}; diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/field_types/nested_type.tsx b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/field_types/nested_type.tsx new file mode 100644 index 0000000000000..72aa88ed75ffb --- /dev/null +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/field_types/nested_type.tsx @@ -0,0 +1,24 @@ +/* + * 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 { NormalizedField } from '../../../../types'; +import { DynamicParameter } from '../../field_parameters'; +import { BasicParametersSection } from '../edit_field'; + +interface Props { + field: NormalizedField; +} + +export const NestedType = ({ field }: Props) => { + return ( + + + + ); +}; diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/field_types/numeric_type.tsx b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/field_types/numeric_type.tsx index 367a700281581..89af480d79a20 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/field_types/numeric_type.tsx +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/field_types/numeric_type.tsx @@ -50,7 +50,7 @@ export const NumericType = ({ field }: Props) => { {formData => formData.subType === 'scaled_float' ? ( diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/field_types/object_type.tsx b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/field_types/object_type.tsx new file mode 100644 index 0000000000000..fef1f9860ca51 --- /dev/null +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/field_types/object_type.tsx @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; + +import { NormalizedField } from '../../../../types'; +import { DynamicParameter, EnabledParameter } from '../../field_parameters'; +import { BasicParametersSection } from '../edit_field'; + +interface Props { + field: NormalizedField; +} + +export const ObjectType = ({ field }: Props) => { + return ( + + + + + + ); +}; diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/field_types/range_type.tsx b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/field_types/range_type.tsx index 0be754bcfb966..a7d2af41c83e6 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/field_types/range_type.tsx +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/field_types/range_type.tsx @@ -36,7 +36,7 @@ export const RangeType = ({ field }: Props) => { {formData => formData.subType === 'date_range' ? ( ) : null diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/field_types/search_as_you_type.tsx b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/field_types/search_as_you_type.tsx index 83541ec982ee6..dafbebd24b3fa 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/field_types/search_as_you_type.tsx +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/field_types/search_as_you_type.tsx @@ -14,6 +14,7 @@ import { NormsParameter, SimilarityParameter, TermVectorParameter, + MaxShingleSizeParameter, } from '../../field_parameters'; import { BasicParametersSection, AdvancedParametersSection } from '../edit_field'; @@ -24,7 +25,8 @@ interface Props { const getDefaultToggleValue = (param: string, field: FieldType) => { switch (param) { case 'similarity': - case 'term_vector': { + case 'term_vector': + case 'max_shingle_size': { return field[param] !== undefined && field[param] !== getFieldConfig(param).defaultValue; } case 'analyzers': { @@ -47,6 +49,10 @@ export const SearchAsYouType = React.memo(({ field }: Props) => { + + { diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/fields_list_item.tsx b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/fields_list_item.tsx index 285598fc8c3c1..4c1c8bc1da114 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/fields_list_item.tsx +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/document_fields/fields/fields_list_item.tsx @@ -113,9 +113,12 @@ function FieldListItemComponent( } ); - const addChildButtonLabel = i18n.translate('xpack.idxMgmt.mappingsEditor.addChildButtonLabel', { - defaultMessage: 'Add child', - }); + const addPropertyButtonLabel = i18n.translate( + 'xpack.idxMgmt.mappingsEditor.addPropertyButtonLabel', + { + defaultMessage: 'Add property', + } + ); const editButtonLabel = i18n.translate('xpack.idxMgmt.mappingsEditor.editFieldButtonLabel', { defaultMessage: 'Edit', @@ -145,12 +148,12 @@ function FieldListItemComponent( {canHaveChildFields && ( - + diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/constants/parameters_definition.tsx b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/constants/parameters_definition.tsx index 64888253ecab4..732449f382f93 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/constants/parameters_definition.tsx +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/constants/parameters_definition.tsx @@ -17,7 +17,13 @@ import { fieldFormatters, FieldConfig, } from '../shared_imports'; -import { AliasOption, DataType, ComboBoxOption } from '../types'; +import { + AliasOption, + DataType, + ComboBoxOption, + ParameterName, + ParameterDefinition, +} from '../types'; import { documentationService } from '../../../services/documentation'; import { INDEX_DEFAULT } from './default_values'; import { TYPE_DEFINITION } from './data_types_definition'; @@ -124,7 +130,7 @@ const analyzerValidations = [ * * As a consequence, if a parameter is *not* declared here, we won't be able to declare it in the Json editor. */ -export const PARAMETERS_DEFINITION = { +export const PARAMETERS_DEFINITION: { [key in ParameterName]: ParameterDefinition } = { name: { fieldConfig: { label: i18n.translate('xpack.idxMgmt.mappingsEditor.nameFieldLabel', { @@ -537,20 +543,32 @@ export const PARAMETERS_DEFINITION = { }, dynamic: { fieldConfig: { - label: i18n.translate('xpack.idxMgmt.mappingsEditor.dynamicFieldLabel', { - defaultMessage: 'Dynamic', - }), - type: FIELD_TYPES.CHECKBOX, defaultValue: true, }, - schema: t.boolean, + schema: t.union([t.boolean, t.literal('strict')]), }, - enabled: { + dynamic_toggle: { fieldConfig: { - label: i18n.translate('xpack.idxMgmt.mappingsEditor.enabledFieldLabel', { - defaultMessage: 'Enabled', + defaultValue: true, + }, + }, + dynamic_strict: { + fieldConfig: { + defaultValue: false, + label: i18n.translate('xpack.idxMgmt.mappingsEditor.dynamicStrictParameter.fieldTitle', { + defaultMessage: 'Throw an exception when the object contains an unmapped property', }), - type: FIELD_TYPES.CHECKBOX, + helpText: i18n.translate( + 'xpack.idxMgmt.mappingsEditor.dynamicStrictParameter.fieldHelpText', + { + defaultMessage: + 'By default, unmapped properties will be silently ignored when dynamic mapping is disabled. Optionally, you can choose to throw an exception when an object contains an unmapped property.', + } + ), + }, + }, + enabled: { + fieldConfig: { defaultValue: true, }, schema: t.boolean, @@ -663,6 +681,11 @@ export const PARAMETERS_DEFINITION = { }, schema: t.boolean, }, + eager_global_ordinals_join: { + fieldConfig: { + defaultValue: true, + }, + }, index_phrases: { fieldConfig: { defaultValue: false, @@ -894,4 +917,21 @@ export const PARAMETERS_DEFINITION = { }, schema: t.string, }, + relations: { + fieldConfig: { + defaultValue: [] as any, // Needed for FieldParams typing + }, + schema: t.record(t.string, t.union([t.string, t.array(t.string)])), + }, + max_shingle_size: { + fieldConfig: { + type: FIELD_TYPES.SELECT, + label: i18n.translate('xpack.idxMgmt.mappingsEditor.largestShingleSizeFieldLabel', { + defaultMessage: 'Max shingle size', + }), + defaultValue: 3, + formatters: [toInt], + }, + schema: t.union([t.literal(2), t.literal(3), t.literal(4)]), + }, }; diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/lib/mappings_validator.test.ts b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/lib/mappings_validator.test.ts index bb86c6277ffe5..c70462a656e2f 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/lib/mappings_validator.test.ts +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/lib/mappings_validator.test.ts @@ -256,6 +256,7 @@ describe('Properties validator', () => { enable_position_increments: [], depth_limit: true, dims: false, + max_shingle_size: 'string_not_allowed', }, // All the parameters in "goodField" have the correct format // and should still be there after the validation ran. @@ -279,7 +280,7 @@ describe('Properties validator', () => { orientation: 'ccw', boost: 1.5, scaling_factor: 2.5, - dynamic: true, + dynamic: 'strict', // true | false | 'strict' are allowed enabled: true, format: 'strict_date_optional_time', analyzer: 'standard', @@ -307,15 +308,26 @@ describe('Properties validator', () => { enable_position_increments: true, depth_limit: 20, dims: 'abc', + max_shingle_size: 2, + }, + goodField2: { + type: 'object', + dynamic: true, + }, + goodField3: { + type: 'object', + dynamic: false, }, }; const { value, errors } = validateProperties(properties as any); - expect(Object.keys(value)).toEqual(['wrongField', 'goodField']); + expect(Object.keys(value)).toEqual(['wrongField', 'goodField', 'goodField2', 'goodField3']); expect(value.wrongField).toEqual({ type: 'text' }); // All parameters have been stripped out but the "type". expect(value.goodField).toEqual(properties.goodField); // All parameters are stil there. + expect(value.goodField2).toEqual(properties.goodField2); + expect(value.goodField3).toEqual(properties.goodField3); const allWrongParameters = Object.keys(properties.wrongField).filter(v => v !== 'type'); expect(errors).toEqual( diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/lib/serializers.ts b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/lib/serializers.ts index f57f0bb9d87de..131d886ff05d9 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/lib/serializers.ts +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/lib/serializers.ts @@ -5,6 +5,10 @@ */ import { SerializerFunc } from '../shared_imports'; +import { + PARAMETER_SERIALIZERS, + PARAMETER_DESERIALIZERS, +} from '../components/document_fields/field_parameters'; import { Field, DataType, MainType, SubType } from '../types'; import { INDEX_DEFAULT, MAIN_DATA_TYPE_DEFINITION } from '../constants'; import { getMainTypeFromSubType } from './utils'; @@ -21,6 +25,25 @@ const sanitizeField = (field: Field): Field => {} as any ); +/** + * Run custom parameter serializers on field. + * Each serializer takes the field as single argument and returns it serialized in an immutable way. + * @param field The field we are serializing + */ +const runParametersSerializers = (field: Field): Field => + PARAMETER_SERIALIZERS.reduce((fieldSerialized, serializer) => serializer(fieldSerialized), field); + +/** + * Run custom parameter deserializers on field. + * Each deserializer takes the field as single argument and returns it deserialized in an immutable way. + * @param field The field we are deserializing + */ +const runParametersDeserializers = (field: Field): Field => + PARAMETER_DESERIALIZERS.reduce( + (fieldDeserialized, serializer) => serializer(fieldDeserialized), + field + ); + export const fieldSerializer: SerializerFunc = (field: Field) => { // If a subType is present, use it as type for ES if ({}.hasOwnProperty.call(field, 'subType')) { @@ -31,7 +54,7 @@ export const fieldSerializer: SerializerFunc = (field: Field) => { // Delete temp fields delete (field as any).useSameAnalyzerForSearch; - return sanitizeField(field); + return sanitizeField(runParametersSerializers(field)); }; export const fieldDeserializer: SerializerFunc = (field: Field): Field => { @@ -50,5 +73,5 @@ export const fieldDeserializer: SerializerFunc = (field: Field): Field => (field as any).useSameAnalyzerForSearch = {}.hasOwnProperty.call(field, 'search_analyzer') === false; - return field; + return runParametersDeserializers(field); }; diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/lib/utils.ts b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/lib/utils.ts index 50e4023c8c742..337554ab5fa5a 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/lib/utils.ts +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/lib/utils.ts @@ -79,10 +79,10 @@ export const getFieldConfig = (param: ParameterName, prop?: string): FieldConfig ) { throw new Error(`No field config found for prop "${prop}" on param "${param}" `); } - return (PARAMETERS_DEFINITION[param] as any).props[prop].fieldConfig || {}; + return (PARAMETERS_DEFINITION[param] as any).props[prop]?.fieldConfig || {}; } - return (PARAMETERS_DEFINITION[param] as any).fieldConfig || {}; + return (PARAMETERS_DEFINITION[param] as any)?.fieldConfig || {}; }; /** diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/reducer.ts b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/reducer.ts index 26d5b8e1edfa5..1e6733b1632d7 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/reducer.ts +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/reducer.ts @@ -174,7 +174,7 @@ const updateAliasesReferences = ( ); } - const targetId = field.source.path!; + const targetId = field.source.path! as string; if (!updatedAliases[targetId]) { updatedAliases[targetId] = []; @@ -420,7 +420,7 @@ export const reducer = (state: State, action: Action): State => { /** * If we delete an alias field, we need to remove its id from the reference Array */ - const targetId = field.source.path; + const targetId = field.source.path as string; updatedFields.aliases = { ...updatedFields.aliases, [targetId]: updatedFields.aliases[targetId].filter(aliasId => aliasId !== id), @@ -455,7 +455,7 @@ export const reducer = (state: State, action: Action): State => { updatedFields.aliases = updateAliasesReferences( newField, updatedFields, - previousField.source.path + previousField.source.path as string ); } @@ -489,9 +489,9 @@ export const reducer = (state: State, action: Action): State => { // We need to remove its reference from our state.aliases map updatedFields.aliases = { ...updatedFields.aliases, - [previousField.source.path]: updatedFields.aliases[previousField.source.path].filter( - aliasId => aliasId !== fieldToEdit - ), + [previousField.source.path as string]: updatedFields.aliases[ + previousField.source.path as string + ].filter(aliasId => aliasId !== fieldToEdit), }; } else { const nextTypeCanHaveAlias = !PARAMETERS_DEFINITION.path.targetTypesNotAllowed.includes( diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/shared_imports.ts b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/shared_imports.ts index 8ac1c2f8c35d1..e99d8840d57df 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/shared_imports.ts +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/shared_imports.ts @@ -16,6 +16,8 @@ export { OnFormUpdateArg, SerializerFunc, UseField, + UseArray, + ArrayItem, useForm, useFormContext, UseMultiFields, @@ -34,6 +36,7 @@ export { SuperSelectField, TextAreaField, TextField, + ComboBoxField, ToggleField, JsonEditorField, } from '../../../../../../../../src/plugins/es_ui_shared/static/forms/components'; diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/types.ts b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/types.ts index 0fce3422344bc..5d9c15caf9cd9 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/types.ts +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/types.ts @@ -19,6 +19,19 @@ export interface DataTypeDefinition { description?: () => ReactNode; } +export interface ParameterDefinition { + title?: string; + description?: JSX.Element | string; + fieldConfig: FieldConfig; + schema?: any; + props?: { [key: string]: ParameterDefinition }; + documentation?: { + main: string; + [key: string]: string; + }; + [key: string]: any; +} + export type MainType = | 'text' | 'keyword' @@ -88,6 +101,8 @@ export type ParameterName = | 'null_value_ip' | 'copy_to' | 'dynamic' + | 'dynamic_toggle' + | 'dynamic_strict' | 'enabled' | 'boost' | 'locale' @@ -99,6 +114,7 @@ export type ParameterName = | 'index_options_flattened' | 'index_options_keyword' | 'eager_global_ordinals' + | 'eager_global_ordinals_join' | 'index_prefixes' | 'index_phrases' | 'norms' @@ -119,7 +135,9 @@ export type ParameterName = | 'points_only' | 'path' | 'dims' - | 'depth_limit'; + | 'depth_limit' + | 'relations' + | 'max_shingle_size'; export interface Parameter { fieldConfig: FieldConfig; @@ -141,10 +159,10 @@ interface FieldBasic { } type FieldParams = { - [K in ParameterName]: typeof PARAMETERS_DEFINITION[K]['fieldConfig']['defaultValue']; + [K in ParameterName]: typeof PARAMETERS_DEFINITION[K]['fieldConfig']['defaultValue'] | unknown; }; -export type Field = FieldBasic & FieldParams; +export type Field = FieldBasic & Partial; export interface FieldMeta { childFieldsName: ChildFieldName | undefined; diff --git a/x-pack/legacy/plugins/index_management/public/app/services/documentation.ts b/x-pack/legacy/plugins/index_management/public/app/services/documentation.ts index 036388452f876..f63dc622453fe 100644 --- a/x-pack/legacy/plugins/index_management/public/app/services/documentation.ts +++ b/x-pack/legacy/plugins/index_management/public/app/services/documentation.ts @@ -40,13 +40,17 @@ class DocumentationService { return `${this.kibanaDocsBase}/managing-indices.html`; } - public getTypeDocLink = (type: DataType, uri = 'main'): string | undefined => { + public getTypeDocLink = (type: DataType, docType = 'main'): string | undefined => { const typeDefinition = TYPE_DEFINITION[type]; - if (!typeDefinition || !typeDefinition.documentation || !typeDefinition.documentation[uri]) { + if ( + !typeDefinition || + !typeDefinition.documentation || + !typeDefinition.documentation[docType] + ) { return undefined; } - return `${this.esDocsBase}${typeDefinition.documentation[uri]}`; + return `${this.esDocsBase}${typeDefinition.documentation[docType]}`; }; public getMappingTypesLink() { @@ -177,6 +181,18 @@ class DocumentationService { return `${this.esDocsBase}/index-options.html`; } + public getJoinMultiLevelsPerformanceLink() { + return `${this.esDocsBase}/parent-join.html#_parent_join_and_performance`; + } + + public getDynamicLink() { + return `${this.esDocsBase}/dynamic.html`; + } + + public getEnabledLink() { + return `${this.esDocsBase}/enabled.html`; + } + public getWellKnownTextLink() { return 'http://docs.opengeospatial.org/is/12-063r5/12-063r5.html'; } diff --git a/x-pack/legacy/plugins/infra/index.ts b/x-pack/legacy/plugins/infra/index.ts index 196950b51be3a..d9abadcb5125c 100644 --- a/x-pack/legacy/plugins/infra/index.ts +++ b/x-pack/legacy/plugins/infra/index.ts @@ -18,6 +18,7 @@ import { PluginSetupContract as FeaturesPluginSetup } from '../../../plugins/fea import { SpacesPluginSetup } from '../../../plugins/spaces/server'; import { VisTypeTimeseriesSetup } from '../../../../src/plugins/vis_type_timeseries/server'; import { APMPluginContract } from '../../../plugins/apm/server'; +import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/utils'; export const APP_ID = 'infra'; @@ -55,6 +56,7 @@ export function infra(kibana: any) { defaultMessage: 'Metrics', }), url: `/app/${APP_ID}#/infrastructure`, + category: DEFAULT_APP_CATEGORIES.observability, }, { description: i18n.translate('xpack.infra.linkLogsDescription', { @@ -68,6 +70,7 @@ export function infra(kibana: any) { defaultMessage: 'Logs', }), url: `/app/${APP_ID}#/logs`, + category: DEFAULT_APP_CATEGORIES.observability, }, ], mappings: savedObjectMappings, diff --git a/x-pack/legacy/plugins/infra/server/features.ts b/x-pack/legacy/plugins/infra/server/features.ts index fc20813c777b6..02658002694d2 100644 --- a/x-pack/legacy/plugins/infra/server/features.ts +++ b/x-pack/legacy/plugins/infra/server/features.ts @@ -9,9 +9,9 @@ import { i18n } from '@kbn/i18n'; export const METRICS_FEATURE = { id: 'infrastructure', name: i18n.translate('xpack.infra.featureRegistry.linkInfrastructureTitle', { - defaultMessage: 'Infrastructure', + defaultMessage: 'Metrics', }), - icon: 'infraApp', + icon: 'metricsApp', navLinkId: 'infra:home', app: ['infra', 'kibana'], catalogue: ['infraops'], @@ -40,7 +40,7 @@ export const LOGS_FEATURE = { name: i18n.translate('xpack.infra.featureRegistry.linkLogsTitle', { defaultMessage: 'Logs', }), - icon: 'loggingApp', + icon: 'logsApp', navLinkId: 'infra:logs', app: ['infra', 'kibana'], catalogue: ['infralogging'], diff --git a/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx b/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx index 794128832461b..80a7ceb61c324 100644 --- a/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx +++ b/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx @@ -12,7 +12,12 @@ import { EditorFrameInstance } from '../types'; import { Storage } from '../../../../../../src/plugins/kibana_utils/public'; import { Document, SavedObjectStore } from '../persistence'; import { mount } from 'enzyme'; -import { esFilters, IFieldType, IIndexPattern } from '../../../../../../src/plugins/data/public'; +import { + esFilters, + FilterManager, + IFieldType, + IIndexPattern, +} from '../../../../../../src/plugins/data/public'; import { dataPluginMock } from '../../../../../../src/plugins/data/public/mocks'; const dataStartMock = dataPluginMock.createStartContract(); @@ -60,6 +65,10 @@ function createMockFilterManager() { subscriber(); }, getFilters: () => filters, + getGlobalFilters: () => { + // @ts-ignore + return filters.filter(esFilters.isFilterPinned); + }, removeAll: () => { filters = []; subscriber(); @@ -821,7 +830,7 @@ describe('Lens App', () => { ); }); - it('clears all existing filters when the active saved query is cleared', () => { + it('clears all existing unpinned filters when the active saved query is cleared', () => { const args = makeDefaultArgs(); args.editorFrame = frame; @@ -834,8 +843,13 @@ describe('Lens App', () => { const indexPattern = ({ id: 'index1' } as unknown) as IIndexPattern; const field = ({ name: 'myfield' } as unknown) as IFieldType; + const pinnedField = ({ name: 'pinnedField' } as unknown) as IFieldType; - args.data.query.filterManager.setFilters([esFilters.buildExistsFilter(field, indexPattern)]); + const unpinned = esFilters.buildExistsFilter(field, indexPattern); + const pinned = esFilters.buildExistsFilter(pinnedField, indexPattern); + FilterManager.setFiltersStore([pinned], esFilters.FilterStateStore.GLOBAL_STATE); + + args.data.query.filterManager.setFilters([pinned, unpinned]); instance.update(); instance.find(TopNavMenu).prop('onClearSavedQuery')!(); @@ -844,7 +858,7 @@ describe('Lens App', () => { expect(frame.mount).toHaveBeenLastCalledWith( expect.any(Element), expect.objectContaining({ - filters: [], + filters: [pinned], }) ); }); diff --git a/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx b/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx index f33cd41f46a11..35e45af6a3d68 100644 --- a/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx @@ -239,7 +239,9 @@ export function App({ setState(s => ({ ...s, savedQuery })); }} onSavedQueryUpdated={savedQuery => { - data.query.filterManager.setFilters(savedQuery.attributes.filters || state.filters); + const savedQueryFilters = savedQuery.attributes.filters || []; + const globalFilters = data.query.filterManager.getGlobalFilters(); + data.query.filterManager.setFilters([...globalFilters, ...savedQueryFilters]); setState(s => ({ ...s, savedQuery: { ...savedQuery }, // Shallow query for reference issues @@ -252,11 +254,11 @@ export function App({ })); }} onClearSavedQuery={() => { - data.query.filterManager.removeAll(); + data.query.filterManager.setFilters(data.query.filterManager.getGlobalFilters()); setState(s => ({ ...s, savedQuery: undefined, - filters: [], + filters: data.query.filterManager.getGlobalFilters(), query: { query: '', language: diff --git a/x-pack/legacy/plugins/maps/index.js b/x-pack/legacy/plugins/maps/index.js index d38a23560fa9f..4f679905fc352 100644 --- a/x-pack/legacy/plugins/maps/index.js +++ b/x-pack/legacy/plugins/maps/index.js @@ -4,15 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ +import _ from 'lodash'; +import mappings from './mappings.json'; import { i18n } from '@kbn/i18n'; import { resolve } from 'path'; -import mappings from './mappings.json'; import { migrations } from './migrations'; import { initTelemetryCollection } from './server/maps_telemetry'; import { getAppTitle } from './common/i18n_getters'; -import _ from 'lodash'; import { MapPlugin } from './server/plugin'; import { APP_ID, APP_ICON, createMapPath, MAP_SAVED_OBJECT_TYPE } from './common/constants'; +import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/utils'; export function maps(kibana) { return new kibana.Plugin({ @@ -29,6 +30,7 @@ export function maps(kibana) { main: 'plugins/maps/legacy', icon: 'plugins/maps/icon.svg', euiIconType: APP_ICON, + category: DEFAULT_APP_CATEGORIES.analyze, }, injectDefaultVars(server) { const serverConfig = server.config(); diff --git a/x-pack/legacy/plugins/maps/public/actions/map_actions.js b/x-pack/legacy/plugins/maps/public/actions/map_actions.js index f05b7eba9e7e0..59b54c2434d17 100644 --- a/x-pack/legacy/plugins/maps/public/actions/map_actions.js +++ b/x-pack/legacy/plugins/maps/public/actions/map_actions.js @@ -753,9 +753,9 @@ export function clearMissingStyleProperties(layerId) { return; } - const ordinalFields = await targetLayer.getOrdinalFields(); + const nextFields = await targetLayer.getFields(); //take into account all fields, since labels can be driven by any field (source or join) const { hasChanges, nextStyleDescriptor } = style.getDescriptorWithMissingStylePropsRemoved( - ordinalFields + nextFields ); if (hasChanges) { dispatch(updateLayerStyle(layerId, nextStyleDescriptor)); diff --git a/x-pack/legacy/plugins/maps/public/angular/map_controller.js b/x-pack/legacy/plugins/maps/public/angular/map_controller.js index 75971d5dfe2a8..ece775f5a7e25 100644 --- a/x-pack/legacy/plugins/maps/public/angular/map_controller.js +++ b/x-pack/legacy/plugins/maps/public/angular/map_controller.js @@ -153,7 +153,7 @@ app.controller( delete $scope.savedQuery; delete $state.savedQuery; onQueryChange({ - filters: [], + filters: filterManager.getGlobalFilters(), query: { query: '', language: localStorage.get('kibana.userQueryLanguage'), @@ -162,6 +162,10 @@ app.controller( }; function updateStateFromSavedQuery(savedQuery) { + const savedQueryFilters = savedQuery.attributes.filters || []; + const globalFilters = filterManager.getGlobalFilters(); + const allFilters = [...savedQueryFilters, ...globalFilters]; + if (savedQuery.attributes.timefilter) { if (savedQuery.attributes.timefilter.refreshInterval) { $scope.onRefreshChange({ @@ -170,13 +174,13 @@ app.controller( }); } onQueryChange({ - filters: savedQuery.attributes.filters || [], + filters: allFilters, query: savedQuery.attributes.query, time: savedQuery.attributes.timefilter, }); } else { onQueryChange({ - filters: savedQuery.attributes.filters || [], + filters: allFilters, query: savedQuery.attributes.query, }); } @@ -367,7 +371,9 @@ app.controller( if (prevIndexPatternIds !== nextIndexPatternIds) { return; } - $scope.indexPatterns = indexPatterns; + $scope.$evalAsync(() => { + $scope.indexPatterns = indexPatterns; + }); } $scope.isFullScreen = false; diff --git a/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/__snapshots__/view.test.js.snap b/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/__snapshots__/view.test.js.snap index eb30a5c33496e..560ebad89c50e 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/__snapshots__/view.test.js.snap +++ b/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/__snapshots__/view.test.js.snap @@ -65,6 +65,7 @@ exports[`LayerControl is rendered 1`] = ` data-test-subj="addLayerButton" fill={true} fullWidth={true} + isDisabled={true} onClick={[Function]} > { @@ -85,6 +86,7 @@ export function LayerControl({ { height: '100%', display: 'inline-block', }; - return
 
; + return ( +
+   +
+ ); }); return { value: palette.id, diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_map_select.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_map_select.js index 242b71522f9a2..fde088ab4475e 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_map_select.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_map_select.js @@ -14,9 +14,7 @@ import { ColorStopsCategorical } from './color_stops_categorical'; const CUSTOM_COLOR_MAP = 'CUSTOM_COLOR_MAP'; export class ColorMapSelect extends Component { - state = { - selected: '', - }; + state = {}; static getDerivedStateFromProps(nextProps, prevState) { if (nextProps.customColorMap === prevState.prevPropsCustomColorMap) { @@ -41,10 +39,7 @@ export class ColorMapSelect extends Component { _onCustomColorMapChange = ({ colorStops, isInvalid }) => { // Manage invalid custom color map in local state if (isInvalid) { - const newState = { - customColorMap: colorStops, - }; - this.setState(newState); + this.setState({ customColorMap: colorStops }); return; } @@ -56,35 +51,34 @@ export class ColorMapSelect extends Component { }; _renderColorStopsInput() { - let colorStopsInput; - if (this.props.useCustomColorMap) { - if (this.props.colorMapType === COLOR_MAP_TYPE.ORDINAL) { - colorStopsInput = ( - - - - - ); - } else if (this.props.colorMapType === COLOR_MAP_TYPE.CATEGORICAL) { - colorStopsInput = ( - - - - - ); - } + if (!this.props.useCustomColorMap) { + return null; } - return colorStopsInput; + + if (this.props.colorMapType === COLOR_MAP_TYPE.ORDINAL) { + return ( + + + + + ); + } + + return ( + + + + + ); } render() { - const colorStopsInput = this._renderColorStopsInput(); const colorMapOptionsWithCustom = [ { value: CUSTOM_COLOR_MAP, @@ -110,7 +104,7 @@ export class ColorMapSelect extends Component { valueOfSelected={valueOfSelected} hasDividers={true} /> - {colorStopsInput} + {this._renderColorStopsInput()}
); } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js index 7994f84386a8a..ba5621b8efadf 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js @@ -13,87 +13,56 @@ import { CATEGORICAL_DATA_TYPES, COLOR_MAP_TYPE } from '../../../../../../common import { COLOR_GRADIENTS, COLOR_PALETTES } from '../../../color_utils'; import { i18n } from '@kbn/i18n'; -export class DynamicColorForm extends React.Component { - state = { - colorMapType: COLOR_MAP_TYPE.ORDINAL, - }; - - constructor() { - super(); - this._isMounted = false; - } - - componentWillUnmount() { - this._isMounted = false; - } +export function DynamicColorForm({ + fields, + onDynamicStyleChange, + staticDynamicSelect, + styleProperty, +}) { + const styleOptions = styleProperty.getOptions(); - componentDidMount() { - this._isMounted = true; - this._loadColorMapType(); - } - - componentDidUpdate() { - this._loadColorMapType(); - } - - async _loadColorMapType() { - const field = this.props.styleProperty.getField(); - if (!field) { - return; - } - const dataType = await field.getDataType(); - const colorMapType = CATEGORICAL_DATA_TYPES.includes(dataType) - ? COLOR_MAP_TYPE.CATEGORICAL - : COLOR_MAP_TYPE.ORDINAL; - if (this._isMounted && this.state.colorMapType !== colorMapType) { - this.setState({ colorMapType }, () => { - const options = this.props.styleProperty.getOptions(); - this.props.onDynamicStyleChange(this.props.styleProperty.getStyleName(), { - ...options, - type: colorMapType, - }); - }); + const onColorMapSelect = ({ color, customColorMap, type, useCustomColorMap }) => { + const newColorOptions = { + ...styleOptions, + type, + }; + if (type === COLOR_MAP_TYPE.ORDINAL) { + newColorOptions.useCustomColorRamp = useCustomColorMap; + newColorOptions.customColorRamp = customColorMap; + newColorOptions.color = color; + } else { + newColorOptions.useCustomColorPalette = useCustomColorMap; + newColorOptions.customColorPalette = customColorMap; + newColorOptions.colorCategory = color; } - } - _getColorSelector() { - const { onDynamicStyleChange, styleProperty } = this.props; - const styleOptions = styleProperty.getOptions(); + onDynamicStyleChange(styleProperty.getStyleName(), newColorOptions); + }; + const onFieldChange = async ({ field }) => { + const { name, origin, type } = field; + onDynamicStyleChange(styleProperty.getStyleName(), { + ...styleOptions, + field: { name, origin }, + type: CATEGORICAL_DATA_TYPES.includes(type) + ? COLOR_MAP_TYPE.CATEGORICAL + : COLOR_MAP_TYPE.ORDINAL, + }); + }; + + const renderColorMapSelect = () => { if (!styleOptions.field || !styleOptions.field.name) { - return; + return null; } - let colorSelect; - const onColorChange = colorOptions => { - const newColorOptions = { - type: colorOptions.type, - }; - if (colorOptions.type === COLOR_MAP_TYPE.ORDINAL) { - newColorOptions.useCustomColorRamp = colorOptions.useCustomColorMap; - newColorOptions.customColorRamp = colorOptions.customColorMap; - newColorOptions.color = colorOptions.color; - } else { - newColorOptions.useCustomColorPalette = colorOptions.useCustomColorMap; - newColorOptions.customColorPalette = colorOptions.customColorMap; - newColorOptions.colorCategory = colorOptions.color; - } - - onDynamicStyleChange(styleProperty.getStyleName(), { - ...styleOptions, - ...newColorOptions, - }); - }; - - if (this.state.colorMapType === COLOR_MAP_TYPE.ORDINAL) { - const customOptionLabel = i18n.translate('xpack.maps.style.customColorRampLabel', { - defaultMessage: 'Custom color ramp', - }); - colorSelect = ( + if (styleOptions.type === COLOR_MAP_TYPE.ORDINAL) { + return ( onColorChange(options)} + customOptionLabel={i18n.translate('xpack.maps.style.customColorRampLabel', { + defaultMessage: 'Custom color ramp', + })} + onChange={onColorMapSelect} colorMapType={COLOR_MAP_TYPE.ORDINAL} color={styleOptions.color} customColorMap={styleOptions.customColorRamp} @@ -101,52 +70,39 @@ export class DynamicColorForm extends React.Component { compressed /> ); - } else if (this.state.colorMapType === COLOR_MAP_TYPE.CATEGORICAL) { - const customOptionLabel = i18n.translate('xpack.maps.style.customColorPaletteLabel', { - defaultMessage: 'Custom color palette', - }); - colorSelect = ( - onColorChange(options)} - colorMapType={COLOR_MAP_TYPE.CATEGORICAL} - color={styleOptions.colorCategory} - customColorMap={styleOptions.customColorPalette} - useCustomColorMap={_.get(styleOptions, 'useCustomColorPalette', false)} - compressed - /> - ); } - return colorSelect; - } - - render() { - const { fields, onDynamicStyleChange, staticDynamicSelect, styleProperty } = this.props; - const styleOptions = styleProperty.getOptions(); - const onFieldChange = options => { - const field = options.field; - onDynamicStyleChange(styleProperty.getStyleName(), { ...styleOptions, field }); - }; - - const colorSelect = this._getColorSelector(); return ( - - - {staticDynamicSelect} - - - - - - {colorSelect} - + ); - } + }; + + return ( + + + {staticDynamicSelect} + + + + + + {renderColorMapSelect()} + + ); } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_select.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_select.js index 1d8f4e13fdd1a..a32c2ce04d735 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_select.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_select.js @@ -7,48 +7,73 @@ import PropTypes from 'prop-types'; import React from 'react'; -import { EuiComboBox } from '@elastic/eui'; -import { SOURCE_DATA_ID_ORIGIN } from '../../../../../common/constants'; +import { EuiComboBox, EuiHighlight } from '@elastic/eui'; +import { FIELD_ORIGIN } from '../../../../../common/constants'; import { i18n } from '@kbn/i18n'; +import { FieldIcon } from '../../../../../../../../../src/plugins/kibana_react/public'; -export function FieldSelect({ fields, selectedFieldName, onChange, ...rest }) { - const onFieldChange = selectedFields => { - onChange({ - field: selectedFields.length > 0 ? selectedFields[0].value : null, - }); - }; +function renderOption(option, searchValue, contentClassName) { + return ( + + +   + {option.label} + + ); +} - const groupFieldsByOrigin = () => { - const fieldsByOriginMap = new Map(); - fields.forEach(field => { - if (fieldsByOriginMap.has(field.origin)) { - const fieldsList = fieldsByOriginMap.get(field.origin); - fieldsList.push(field); - fieldsByOriginMap.set(field.origin, fieldsList); - } else { - fieldsByOriginMap.set(field.origin, [field]); - } - }); +function groupFieldsByOrigin(fields) { + const fieldsByOriginMap = new Map(); + fields.forEach(field => { + if (fieldsByOriginMap.has(field.origin)) { + const fieldsList = fieldsByOriginMap.get(field.origin); + fieldsList.push(field); + fieldsByOriginMap.set(field.origin, fieldsList); + } else { + fieldsByOriginMap.set(field.origin, [field]); + } + }); - const optionGroups = []; - fieldsByOriginMap.forEach((fieldsList, fieldOrigin) => { - optionGroups.push({ - label: fieldOrigin, - options: fieldsList - .map(field => { - return { value: field, label: field.label }; - }) - .sort((a, b) => { - return a.label.toLowerCase().localeCompare(b.label.toLowerCase()); - }), + function fieldsListToOptions(fieldsList) { + return fieldsList + .map(field => { + return { value: field, label: field.label }; + }) + .sort((a, b) => { + return a.label.toLowerCase().localeCompare(b.label.toLowerCase()); }); - }); + } + + if (fieldsByOriginMap.size === 1) { + // do not show origin group if all fields are from same origin + const onlyOriginKey = fieldsByOriginMap.keys().next().value; + const fieldsList = fieldsByOriginMap.get(onlyOriginKey); + return fieldsListToOptions(fieldsList); + } - optionGroups.sort((a, b) => { - return a.label.toLowerCase().localeCompare(b.label.toLowerCase()); + const optionGroups = []; + fieldsByOriginMap.forEach((fieldsList, fieldOrigin) => { + optionGroups.push({ + label: i18n.translate('xpack.maps.style.fieldSelect.OriginLabel', { + defaultMessage: 'Fields from {fieldOrigin}', + values: { fieldOrigin }, + }), + options: fieldsListToOptions(fieldsList), }); + }); + + optionGroups.sort((a, b) => { + return a.label.toLowerCase().localeCompare(b.label.toLowerCase()); + }); + + return optionGroups; +} - return optionGroups; +export function FieldSelect({ fields, selectedFieldName, onChange, ...rest }) { + const onFieldChange = selectedFields => { + onChange({ + field: selectedFields.length > 0 ? selectedFields[0].value : null, + }); }; let selectedOption; @@ -61,7 +86,7 @@ export function FieldSelect({ fields, selectedFieldName, onChange, ...rest }) { return ( ); @@ -76,7 +102,8 @@ export function FieldSelect({ fields, selectedFieldName, onChange, ...rest }) { export const fieldShape = PropTypes.shape({ name: PropTypes.string.isRequired, - origin: PropTypes.oneOf(['join', SOURCE_DATA_ID_ORIGIN]).isRequired, + origin: PropTypes.oneOf(Object.values(FIELD_ORIGIN)).isRequired, + type: PropTypes.string.isRequired, }); FieldSelect.propTypes = { diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js index 2f3cfa1d8e4bb..0784e2a8cc355 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js @@ -61,6 +61,7 @@ export class VectorStyleEditor extends Component { label: await field.getLabel(), name: field.getName(), origin: field.getOrigin(), + type: await field.getDataType(), }; }; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_size_property.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_size_property.js index 1584dec998986..024b446369851 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_size_property.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_size_property.js @@ -26,7 +26,8 @@ export class StaticSizeProperty extends StaticStyleProperty { syncIconImageAndSizeWithMb(symbolLayerId, mbMap, symbolId) { const iconPixels = - this._size >= HALF_LARGE_MAKI_ICON_SIZE ? LARGE_MAKI_ICON_SIZE : SMALL_MAKI_ICON_SIZE; + this._options.size >= HALF_LARGE_MAKI_ICON_SIZE ? LARGE_MAKI_ICON_SIZE : SMALL_MAKI_ICON_SIZE; + mbMap.setLayoutProperty(symbolLayerId, 'icon-image', `${symbolId}-${iconPixels}`); const halfIconPixels = iconPixels / 2; mbMap.setLayoutProperty(symbolLayerId, 'icon-size', this._options.size / halfIconPixels); diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js index ec0afb6a6a37e..558df73f74595 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js @@ -175,7 +175,7 @@ export class VectorStyle extends AbstractStyle { * This method does not update its descriptor. It just returns a new descriptor that the caller * can then use to update store state via dispatch. */ - getDescriptorWithMissingStylePropsRemoved(nextOrdinalFields) { + getDescriptorWithMissingStylePropsRemoved(nextFields) { const originalProperties = this.getRawProperties(); const updatedProperties = {}; @@ -192,7 +192,7 @@ export class VectorStyle extends AbstractStyle { return; } - const matchingOrdinalField = nextOrdinalFields.find(ordinalField => { + const matchingOrdinalField = nextFields.find(ordinalField => { return fieldName === ordinalField.getName(); }); diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.test.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.test.js index b8f5c74139a63..7c35bbeba018b 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.test.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.test.js @@ -66,21 +66,19 @@ describe('getDescriptorWithMissingStylePropsRemoved', () => { it('Should return no changes when next oridinal fields contain existing style property fields', () => { const vectorStyle = new VectorStyle({ properties }, new MockSource()); - const nextOridinalFields = [new MockField({ fieldName })]; - const { hasChanges } = vectorStyle.getDescriptorWithMissingStylePropsRemoved( - nextOridinalFields - ); + const nextFields = [new MockField({ fieldName })]; + const { hasChanges } = vectorStyle.getDescriptorWithMissingStylePropsRemoved(nextFields); expect(hasChanges).toBe(false); }); it('Should clear missing fields when next oridinal fields do not contain existing style property fields', () => { const vectorStyle = new VectorStyle({ properties }, new MockSource()); - const nextOridinalFields = []; + const nextFields = []; const { hasChanges, nextStyleDescriptor, - } = vectorStyle.getDescriptorWithMissingStylePropsRemoved(nextOridinalFields); + } = vectorStyle.getDescriptorWithMissingStylePropsRemoved(nextFields); expect(hasChanges).toBe(true); expect(nextStyleDescriptor.properties).toEqual({ fillColor: { diff --git a/x-pack/legacy/plugins/maps/public/layers/vector_layer.js b/x-pack/legacy/plugins/maps/public/layers/vector_layer.js index 96223aa536170..31c3831fb612a 100644 --- a/x-pack/legacy/plugins/maps/public/layers/vector_layer.js +++ b/x-pack/legacy/plugins/maps/public/layers/vector_layer.js @@ -209,10 +209,6 @@ export class VectorLayer extends AbstractLayer { return [...numberFieldOptions, ...this._getJoinFields()]; } - async getOrdinalFields() { - return [...(await this.getDateFields()), ...(await this.getNumberFields())]; - } - async getCategoricalFields() { return await this._source.getCategoricalFields(); } diff --git a/x-pack/legacy/plugins/maps/server/plugin.js b/x-pack/legacy/plugins/maps/server/plugin.js index 6009cea330ab0..d52a9f12ba631 100644 --- a/x-pack/legacy/plugins/maps/server/plugin.js +++ b/x-pack/legacy/plugins/maps/server/plugin.js @@ -10,6 +10,7 @@ import { getFlightsSavedObjects } from './sample_data/flights_saved_objects.js'; import { getWebLogsSavedObjects } from './sample_data/web_logs_saved_objects.js'; import { LICENSE_CHECK_STATE } from '../../../../plugins/licensing/server'; import { initRoutes } from './routes'; +import { emsBoundariesSpecProvider } from './tutorials/ems'; export class MapPlugin { setup(core, plugins, __LEGACY) { @@ -115,6 +116,13 @@ export class MapPlugin { isLayerTOCOpen: false, }, }); + + home.tutorials.registerTutorial( + emsBoundariesSpecProvider({ + prependBasePath: core.http.basePath.prepend, + emsLandingPageUrl: __LEGACY.mapConfig().emsLandingPageUrl, + }) + ); } __LEGACY.injectUiAppVars(APP_ID, async () => { diff --git a/src/legacy/core_plugins/kibana/server/tutorials/ems/index.js b/x-pack/legacy/plugins/maps/server/tutorials/ems/index.ts similarity index 51% rename from src/legacy/core_plugins/kibana/server/tutorials/ems/index.js rename to x-pack/legacy/plugins/maps/server/tutorials/ems/index.ts index 24293590299b1..88c22d01a527a 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/ems/index.js +++ b/x-pack/legacy/plugins/maps/server/tutorials/ems/index.ts @@ -1,41 +1,29 @@ /* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. + * 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 { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { TutorialsCategory } from '../../../../../../../src/plugins/home/server'; -export function emsBoundariesSpecProvider(server) { - function addBasePath(url) { - const basePath = server.config().get('server.basePath'); - return `${basePath.length > 0 ? `${basePath}` : ''}${url}`; - } - - return { +export function emsBoundariesSpecProvider({ + emsLandingPageUrl, + prependBasePath, +}: { + emsLandingPageUrl: string; + prependBasePath: (path: string) => string; +}) { + return () => ({ id: 'emsBoundaries', - name: i18n.translate('kbn.server.tutorials.ems.nameTitle', { + name: i18n.translate('xpack.maps.tutorials.ems.nameTitle', { defaultMessage: 'EMS Boundaries', }), - category: TUTORIAL_CATEGORY.OTHER, - shortDescription: i18n.translate('kbn.server.tutorials.ems.shortDescription', { + category: TutorialsCategory.OTHER, + shortDescription: i18n.translate('xpack.maps.tutorials.ems.shortDescription', { defaultMessage: 'Administrative boundaries from Elastic Maps Service.', }), - longDescription: i18n.translate('kbn.server.tutorials.ems.longDescription', { + longDescription: i18n.translate('xpack.maps.tutorials.ems.longDescription', { defaultMessage: '[Elastic Maps Service (EMS)](https://www.elastic.co/elastic-maps-service) \ hosts tile layers and vector shapes of administrative boundaries. \ @@ -52,30 +40,30 @@ Indexing EMS administrative boundaries in Elasticsearch allows for search on bou id: 'EMS', instructions: [ { - title: i18n.translate('kbn.server.tutorials.ems.downloadStepTitle', { + title: i18n.translate('xpack.maps.tutorials.ems.downloadStepTitle', { defaultMessage: 'Download Elastic Maps Service boundaries', }), - textPre: i18n.translate('kbn.server.tutorials.ems.downloadStepText', { + textPre: i18n.translate('xpack.maps.tutorials.ems.downloadStepText', { defaultMessage: '1. Navigate to Elastic Maps Service [landing page]({emsLandingPageUrl}).\n\ 2. In the left sidebar, select an administrative boundary.\n\ 3. Click `Download GeoJSON` button.', values: { - emsLandingPageUrl: server.config().get('map.emsLandingPageUrl'), + emsLandingPageUrl, }, }), }, { - title: i18n.translate('kbn.server.tutorials.ems.uploadStepTitle', { + title: i18n.translate('xpack.maps.tutorials.ems.uploadStepTitle', { defaultMessage: 'Index Elastic Maps Service boundaries', }), - textPre: i18n.translate('kbn.server.tutorials.ems.uploadStepText', { + textPre: i18n.translate('xpack.maps.tutorials.ems.uploadStepText', { defaultMessage: '1. Open [Elastic Maps]({newMapUrl}).\n\ 2. Click `Add layer`, then select `Upload GeoJSON`.\n\ 3. Upload the GeoJSON file and click `Import file`.', values: { - newMapUrl: addBasePath('/app/maps#/map'), + newMapUrl: prependBasePath('/app/maps#/map'), }, }), }, @@ -85,5 +73,5 @@ Indexing EMS administrative boundaries in Elasticsearch allows for search on bou }, ], }, - }; + }); } diff --git a/x-pack/legacy/plugins/ml/common/constants/new_job.ts b/x-pack/legacy/plugins/ml/common/constants/new_job.ts index 3c98b372afdf7..862fa72d11fdb 100644 --- a/x-pack/legacy/plugins/ml/common/constants/new_job.ts +++ b/x-pack/legacy/plugins/ml/common/constants/new_job.ts @@ -26,7 +26,14 @@ export const DEFAULT_QUERY_DELAY = '60s'; export const SHARED_RESULTS_INDEX_NAME = 'shared'; +// Categorization export const NUMBER_OF_CATEGORY_EXAMPLES = 5; export const CATEGORY_EXAMPLES_SAMPLE_SIZE = 1000; export const CATEGORY_EXAMPLES_WARNING_LIMIT = 0.75; export const CATEGORY_EXAMPLES_ERROR_LIMIT = 0.02; + +export enum CATEGORY_EXAMPLES_VALIDATION_STATUS { + VALID = 'valid', + PARTIALLY_VALID = 'partially_valid', + INVALID = 'invalid', +} diff --git a/x-pack/legacy/plugins/ml/common/types/categories.ts b/x-pack/legacy/plugins/ml/common/types/categories.ts index 6ccd13ed9a39e..862ad8e194a0b 100644 --- a/x-pack/legacy/plugins/ml/common/types/categories.ts +++ b/x-pack/legacy/plugins/ml/common/types/categories.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { CATEGORY_EXAMPLES_VALIDATION_STATUS } from '../constants/new_job'; + export type CategoryId = number; export interface Category { @@ -23,3 +25,31 @@ export interface Token { type: string; position: number; } + +export interface CategorizationAnalyzer { + char_filter?: any[]; + tokenizer?: string; + filter?: any[]; + analyzer?: string; +} + +export interface CategoryFieldExample { + text: string; + tokens: Token[]; +} + +export enum VALIDATION_RESULT { + TOKEN_COUNT, + MEDIAN_LINE_LENGTH, + NULL_VALUES, + NO_EXAMPLES, + TOO_MANY_TOKENS, + FAILED_TO_TOKENIZE, + INSUFFICIENT_PRIVILEGES, +} + +export interface FieldExampleCheck { + id: VALIDATION_RESULT; + valid: CATEGORY_EXAMPLES_VALIDATION_STATUS; + message: string; +} diff --git a/x-pack/legacy/plugins/ml/common/util/string_utils.test.ts b/x-pack/legacy/plugins/ml/common/util/string_utils.test.ts index aba2dbd230ada..026c8e6110c99 100644 --- a/x-pack/legacy/plugins/ml/common/util/string_utils.test.ts +++ b/x-pack/legacy/plugins/ml/common/util/string_utils.test.ts @@ -4,7 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ -import { renderTemplate } from './string_utils'; +import { renderTemplate, getMedianStringLength } from './string_utils'; + +const strings: string[] = [ + 'foo', + 'foofoofoofoofoo', + 'foofoofoo', + 'f', + 'f', + 'foofoofoofoofoofoofoo', +]; +const noStrings: string[] = []; describe('ML - string utils', () => { describe('renderTemplate', () => { @@ -24,4 +34,16 @@ describe('ML - string utils', () => { expect(result).toBe('string with 1 replacement, and a 2nd one.'); }); }); + + describe('getMedianStringLength', () => { + test('test median for string array', () => { + const result = getMedianStringLength(strings); + expect(result).toBe(9); + }); + + test('test median for no strings', () => { + const result = getMedianStringLength(noStrings); + expect(result).toBe(0); + }); + }); }); diff --git a/x-pack/legacy/plugins/ml/common/util/string_utils.ts b/x-pack/legacy/plugins/ml/common/util/string_utils.ts index 432baabe773cc..9dd2ce3d74cd5 100644 --- a/x-pack/legacy/plugins/ml/common/util/string_utils.ts +++ b/x-pack/legacy/plugins/ml/common/util/string_utils.ts @@ -17,3 +17,8 @@ export function renderTemplate(str: string, data?: Record): stri return str; } + +export function getMedianStringLength(strings: string[]) { + const sortedStringLengths = strings.map(s => s.length).sort((a, b) => a - b); + return sortedStringLengths[Math.floor(sortedStringLengths.length / 2)] || 0; +} diff --git a/x-pack/legacy/plugins/ml/index.ts b/x-pack/legacy/plugins/ml/index.ts index c4289389b0d56..fc1cec7c16208 100755 --- a/x-pack/legacy/plugins/ml/index.ts +++ b/x-pack/legacy/plugins/ml/index.ts @@ -10,7 +10,7 @@ import KbnServer, { Server } from 'src/legacy/server/kbn_server'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { plugin } from './server/new_platform'; import { CloudSetup } from '../../../plugins/cloud/server'; - +import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/utils'; import { MlInitializerContext, MlCoreSetup, @@ -42,6 +42,7 @@ export const ml = (kibana: any) => { icon: 'plugins/ml/application/ml.svg', euiIconType: 'machineLearningApp', main: 'plugins/ml/legacy', + category: DEFAULT_APP_CATEGORIES.analyze, }, styleSheetPaths: resolve(__dirname, 'public/application/index.scss'), hacks: ['plugins/ml/application/hacks/toggle_app_link_in_nav'], diff --git a/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/page.tsx b/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/page.tsx index 68856eb42973b..721b8a458efc1 100644 --- a/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/page.tsx +++ b/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/page.tsx @@ -33,6 +33,8 @@ import { NavigationMenu } from '../../components/navigation_menu'; import { ML_JOB_FIELD_TYPES } from '../../../../common/constants/field_types'; import { SEARCH_QUERY_LANGUAGE } from '../../../../common/constants/search'; import { isFullLicense } from '../../license/check_license'; +import { checkPermission } from '../../privilege/check_privilege'; +import { mlNodesAvailable } from '../../ml_nodes_check/check_ml_nodes'; import { FullTimeRangeSelector } from '../../components/full_time_range_selector'; import { mlTimefilterRefresh$ } from '../../services/timefilter_refresh_service'; import { useKibanaContext, SavedSearchQuery } from '../../contexts/kibana'; @@ -130,9 +132,11 @@ export const Page: FC = () => { const defaults = getDefaultPageState(); - const [showActionsPanel] = useState( - isFullLicense() && currentIndexPattern.timeFieldName !== undefined - ); + const showActionsPanel = + isFullLicense() && + checkPermission('canCreateJob') && + mlNodesAvailable() && + currentIndexPattern.timeFieldName !== undefined; const [searchString, setSearchString] = useState(defaults.searchString); const [searchQuery, setSearchQuery] = useState(defaults.searchQuery); @@ -613,7 +617,9 @@ export const Page: FC = () => { )} - + {showActionsPanel === true && ( + + )} diff --git a/x-pack/legacy/plugins/ml/public/application/formatters/round_to_decimal_place.test.ts b/x-pack/legacy/plugins/ml/public/application/formatters/round_to_decimal_place.test.ts index 99b9aceab3696..663a3f3d8f955 100644 --- a/x-pack/legacy/plugins/ml/public/application/formatters/round_to_decimal_place.test.ts +++ b/x-pack/legacy/plugins/ml/public/application/formatters/round_to_decimal_place.test.ts @@ -20,6 +20,7 @@ describe('ML - roundToDecimalPlace formatter', () => { expect(roundToDecimalPlace(0.0005)).toBe('5.00e-4'); expect(roundToDecimalPlace(-0.0005)).toBe('-5.00e-4'); expect(roundToDecimalPlace(-12.045)).toBe(-12.04); + expect(roundToDecimalPlace(0)).toBe(0); }); it('returns the correct format using specified decimal place', () => { @@ -31,5 +32,6 @@ describe('ML - roundToDecimalPlace formatter', () => { expect(roundToDecimalPlace(0.0005, 4)).toBe(0.0005); expect(roundToDecimalPlace(0.00005, 4)).toBe('5.00e-5'); expect(roundToDecimalPlace(-0.00005, 4)).toBe('-5.00e-5'); + expect(roundToDecimalPlace(0, 4)).toBe(0); }); }); diff --git a/x-pack/legacy/plugins/ml/public/application/formatters/round_to_decimal_place.ts b/x-pack/legacy/plugins/ml/public/application/formatters/round_to_decimal_place.ts index f863fe6d76e57..5a030d7619e98 100644 --- a/x-pack/legacy/plugins/ml/public/application/formatters/round_to_decimal_place.ts +++ b/x-pack/legacy/plugins/ml/public/application/formatters/round_to_decimal_place.ts @@ -4,7 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -export function roundToDecimalPlace(num: number, dp: number = 2) { +export function roundToDecimalPlace(num: number, dp: number = 2): number | string { + if (num % 1 === 0) { + // no decimal place + return num; + } + if (Math.abs(num) < Math.pow(10, -dp)) { return Number.parseFloat(String(num)).toExponential(2); } diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/create_watch_view.js b/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/create_watch_view.js index 0a823726a2d68..7a855301885a9 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/create_watch_view.js +++ b/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/create_watch_view.js @@ -21,7 +21,7 @@ import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; import { parseInterval } from '../../../../../../common/util/parse_interval'; import { ml } from '../../../../services/ml_api_service'; -import { SelectSeverity } from '../../../../components/controls/select_severity/select_severity'; +import { SelectSeverity } from './select_severity'; import { mlCreateWatchService } from './create_watch_service'; const STATUS = mlCreateWatchService.STATUS; @@ -111,19 +111,6 @@ export const CreateWatch = injectI18n( render() { const { intl } = this.props; - const mlSelectSeverityService = { - state: { - set: (name, threshold) => { - this.onThresholdChange(threshold); - return { - changed: () => {}, - }; - }, - get: () => { - return this.config.threshold; - }, - }, - }; const { status } = this.state; if (status === null || status === STATUS.SAVING || status === STATUS.SAVE_FAILED) { @@ -164,10 +151,7 @@ export const CreateWatch = injectI18n(
- +
diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/select_severity.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/select_severity.tsx new file mode 100644 index 0000000000000..8b0e7da2a5637 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/select_severity.tsx @@ -0,0 +1,133 @@ +/* + * 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. + */ + +/* + * React component for rendering a select element with threshold levels. + * This is basically a copy of SelectSeverity in public/application/components/controls/select_severity + * but which stores its state internally rather than in the appState + */ +import React, { Fragment, FC, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { EuiHealth, EuiSpacer, EuiSuperSelect, EuiText } from '@elastic/eui'; + +import { getSeverityColor } from '../../../../../../common/util/anomaly_utils'; + +const warningLabel = i18n.translate('xpack.ml.controls.selectSeverity.warningLabel', { + defaultMessage: 'warning', +}); +const minorLabel = i18n.translate('xpack.ml.controls.selectSeverity.minorLabel', { + defaultMessage: 'minor', +}); +const majorLabel = i18n.translate('xpack.ml.controls.selectSeverity.majorLabel', { + defaultMessage: 'major', +}); +const criticalLabel = i18n.translate('xpack.ml.controls.selectSeverity.criticalLabel', { + defaultMessage: 'critical', +}); + +const optionsMap = { + [warningLabel]: 0, + [minorLabel]: 25, + [majorLabel]: 50, + [criticalLabel]: 75, +}; + +interface TableSeverity { + val: number; + display: string; + color: string; +} + +export const SEVERITY_OPTIONS: TableSeverity[] = [ + { + val: 0, + display: warningLabel, + color: getSeverityColor(0), + }, + { + val: 25, + display: minorLabel, + color: getSeverityColor(25), + }, + { + val: 50, + display: majorLabel, + color: getSeverityColor(50), + }, + { + val: 75, + display: criticalLabel, + color: getSeverityColor(75), + }, +]; + +function optionValueToThreshold(value: number) { + // Get corresponding threshold object with required display and val properties from the specified value. + let threshold = SEVERITY_OPTIONS.find(opt => opt.val === value); + + // Default to warning if supplied value doesn't map to one of the options. + if (threshold === undefined) { + threshold = SEVERITY_OPTIONS[0]; + } + + return threshold; +} + +const TABLE_SEVERITY_DEFAULT = SEVERITY_OPTIONS[0]; + +const getSeverityOptions = () => + SEVERITY_OPTIONS.map(({ color, display, val }) => ({ + value: display, + inputDisplay: ( + + + {display} + + + ), + dropdownDisplay: ( + + + {display} + + + +

+ +

+
+
+ ), + })); + +interface Props { + onChange: (sev: TableSeverity) => void; +} + +export const SelectSeverity: FC = ({ onChange }) => { + const [severity, setSeverity] = useState(TABLE_SEVERITY_DEFAULT); + + const onSeverityChange = (valueDisplay: string) => { + const option = optionValueToThreshold(optionsMap[valueDisplay]); + setSeverity(option); + onChange(option); + }; + + return ( + + ); +}; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/job_details/format_values.js b/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/job_details/format_values.js index eb0a905725d75..9984f3be299ae 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/job_details/format_values.js +++ b/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/job_details/format_values.js @@ -6,6 +6,7 @@ import numeral from '@elastic/numeral'; import { formatDate } from '@elastic/eui/lib/services/format'; +import { roundToDecimalPlace } from '../../../../formatters/round_to_decimal_place'; import { toLocaleString } from '../../../../util/string_utils'; const TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss'; @@ -35,6 +36,8 @@ export function formatValues([key, value]) { case 'established_model_memory': case 'input_bytes': case 'model_bytes': + case 'model_bytes_exceeded': + case 'model_bytes_memory_limit': value = formatData(value); break; @@ -53,9 +56,16 @@ export function formatValues([key, value]) { case 'total_over_field_count': case 'total_partition_field_count': case 'bucket_allocation_failures_count': + case 'search_count': value = toLocaleString(value); break; + // numbers rounded to 3 decimal places + case 'average_search_time_per_bucket_ms': + case 'exponential_average_search_time_per_hour_ms': + value = typeof value === 'number' ? roundToDecimalPlace(value, 3).toLocaleString() : value; + break; + default: break; } diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/categorization_job_creator.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/categorization_job_creator.ts index 71619311c4361..0ff0ffb6f3bb3 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/categorization_job_creator.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/categorization_job_creator.ts @@ -16,25 +16,31 @@ import { CREATED_BY_LABEL, DEFAULT_BUCKET_SPAN, DEFAULT_RARE_BUCKET_SPAN, + CATEGORY_EXAMPLES_VALIDATION_STATUS, } from '../../../../../../common/constants/new_job'; import { ML_JOB_AGGREGATION } from '../../../../../../common/constants/aggregation_types'; +import { + CategorizationAnalyzer, + CategoryFieldExample, + FieldExampleCheck, +} from '../../../../../../common/types/categories'; import { getRichDetectors } from './util/general'; -import { CategorizationExamplesLoader, CategoryExample } from '../results_loader'; -import { CategorizationAnalyzer, getNewJobDefaults } from '../../../../services/ml_server_info'; - -type CategorizationAnalyzerType = CategorizationAnalyzer | null; +import { CategorizationExamplesLoader } from '../results_loader'; +import { getNewJobDefaults } from '../../../../services/ml_server_info'; export class CategorizationJobCreator extends JobCreator { protected _type: JOB_TYPE = JOB_TYPE.CATEGORIZATION; private _createCountDetector: () => void = () => {}; private _createRareDetector: () => void = () => {}; private _examplesLoader: CategorizationExamplesLoader; - private _categoryFieldExamples: CategoryExample[] = []; - private _categoryFieldValid: number = 0; + private _categoryFieldExamples: CategoryFieldExample[] = []; + private _validationChecks: FieldExampleCheck[] = []; + private _overallValidStatus: CATEGORY_EXAMPLES_VALIDATION_STATUS = + CATEGORY_EXAMPLES_VALIDATION_STATUS.INVALID; private _detectorType: ML_JOB_AGGREGATION.COUNT | ML_JOB_AGGREGATION.RARE = ML_JOB_AGGREGATION.COUNT; - private _categorizationAnalyzer: CategorizationAnalyzerType = null; - private _defaultCategorizationAnalyzer: CategorizationAnalyzerType; + private _categorizationAnalyzer: CategorizationAnalyzer = {}; + private _defaultCategorizationAnalyzer: CategorizationAnalyzer; constructor( indexPattern: IndexPattern, @@ -46,7 +52,7 @@ export class CategorizationJobCreator extends JobCreator { this._examplesLoader = new CategorizationExamplesLoader(this, indexPattern, query); const { anomaly_detectors: anomalyDetectors } = getNewJobDefaults(); - this._defaultCategorizationAnalyzer = anomalyDetectors.categorization_analyzer || null; + this._defaultCategorizationAnalyzer = anomalyDetectors.categorization_analyzer || {}; } public setDefaultDetectorProperties( @@ -93,7 +99,7 @@ export class CategorizationJobCreator extends JobCreator { } else { delete this._job_config.analysis_config.categorization_field_name; this._categoryFieldExamples = []; - this._categoryFieldValid = 0; + this._validationChecks = []; } } @@ -102,31 +108,38 @@ export class CategorizationJobCreator extends JobCreator { } public async loadCategorizationFieldExamples() { - const { valid, examples, sampleSize } = await this._examplesLoader.loadExamples(); + const { + examples, + sampleSize, + overallValidStatus, + validationChecks, + } = await this._examplesLoader.loadExamples(); this._categoryFieldExamples = examples; - this._categoryFieldValid = valid; - return { valid, examples, sampleSize }; + this._validationChecks = validationChecks; + this._overallValidStatus = overallValidStatus; + return { examples, sampleSize, overallValidStatus, validationChecks }; } public get categoryFieldExamples() { return this._categoryFieldExamples; } - public get categoryFieldValid() { - return this._categoryFieldValid; + public get validationChecks() { + return this._validationChecks; + } + + public get overallValidStatus() { + return this._overallValidStatus; } public get selectedDetectorType() { return this._detectorType; } - public set categorizationAnalyzer(analyzer: CategorizationAnalyzerType) { + public set categorizationAnalyzer(analyzer: CategorizationAnalyzer) { this._categorizationAnalyzer = analyzer; - if ( - analyzer === null || - isEqual(this._categorizationAnalyzer, this._defaultCategorizationAnalyzer) - ) { + if (isEqual(this._categorizationAnalyzer, this._defaultCategorizationAnalyzer)) { delete this._job_config.analysis_config.categorization_analyzer; } else { this._job_config.analysis_config.categorization_analyzer = analyzer; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_validator/job_validator.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_validator/job_validator.ts index 976e94b377ae8..8f6b16c407fb6 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_validator/job_validator.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_validator/job_validator.ts @@ -16,7 +16,7 @@ import { JobCreator, JobCreatorType, isCategorizationJobCreator } from '../job_c import { populateValidationMessages, checkForExistingJobAndGroupIds } from './util'; import { ExistingJobsAndGroups } from '../../../../services/job_service'; import { cardinalityValidator, CardinalityValidatorResult } from './validators'; -import { CATEGORY_EXAMPLES_ERROR_LIMIT } from '../../../../../../common/constants/new_job'; +import { CATEGORY_EXAMPLES_VALIDATION_STATUS } from '../../../../../../common/constants/new_job'; // delay start of validation to allow the user to make changes // e.g. if they are typing in a new value, try not to validate @@ -207,7 +207,7 @@ export class JobValidator { private _runAdvancedValidation() { if (isCategorizationJobCreator(this._jobCreator)) { this._advancedValidations.categorizationFieldValid.valid = - this._jobCreator.categoryFieldValid > CATEGORY_EXAMPLES_ERROR_LIMIT; + this._jobCreator.overallValidStatus !== CATEGORY_EXAMPLES_VALIDATION_STATUS.INVALID; } } diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/results_loader/categorization_examples_loader.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/results_loader/categorization_examples_loader.ts index ce1ea0bdaf181..62a4d070fec32 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/results_loader/categorization_examples_loader.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/results_loader/categorization_examples_loader.ts @@ -6,15 +6,12 @@ import { IndexPattern } from '../../../../../../../../../../src/plugins/data/public'; import { IndexPatternTitle } from '../../../../../../common/types/kibana'; -import { Token } from '../../../../../../common/types/categories'; import { CategorizationJobCreator } from '../job_creator'; import { ml } from '../../../../services/ml_api_service'; -import { NUMBER_OF_CATEGORY_EXAMPLES } from '../../../../../../common/constants/new_job'; - -export interface CategoryExample { - text: string; - tokens: Token[]; -} +import { + NUMBER_OF_CATEGORY_EXAMPLES, + CATEGORY_EXAMPLES_VALIDATION_STATUS, +} from '../../../../../../common/constants/new_job'; export class CategorizationExamplesLoader { private _jobCreator: CategorizationJobCreator; @@ -36,20 +33,22 @@ export class CategorizationExamplesLoader { const analyzer = this._jobCreator.categorizationAnalyzer; const categorizationFieldName = this._jobCreator.categorizationFieldName; if (categorizationFieldName === null) { - return { valid: 0, examples: [], sampleSize: 0 }; + return { + examples: [], + sampleSize: 0, + overallValidStatus: CATEGORY_EXAMPLES_VALIDATION_STATUS.INVALID, + validationChecks: [], + }; } - const start = Math.floor( - this._jobCreator.start + (this._jobCreator.end - this._jobCreator.start) / 2 - ); const resp = await ml.jobs.categorizationFieldExamples( this._indexPatternTitle, this._query, NUMBER_OF_CATEGORY_EXAMPLES, categorizationFieldName, this._timeFieldName, - start, - 0, + this._jobCreator.start, + this._jobCreator.end, analyzer ); return resp; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/results_loader/index.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/results_loader/index.ts index 724c62f22e469..e15d859f8e6c3 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/results_loader/index.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/results_loader/index.ts @@ -5,4 +5,4 @@ */ export { ResultsLoader, Results, ModelItem, Anomaly } from './results_loader'; -export { CategorizationExamplesLoader, CategoryExample } from './categorization_examples_loader'; +export { CategorizationExamplesLoader } from './categorization_examples_loader'; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/examples_valid_callout.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/examples_valid_callout.tsx index 270ba99d938cd..ac886a3aea61a 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/examples_valid_callout.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/examples_valid_callout.tsx @@ -9,27 +9,24 @@ import { EuiCallOut, EuiSpacer, EuiCallOutProps } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { CategorizationAnalyzer } from '../../../../../../../services/ml_server_info'; -import { EditCategorizationAnalyzerFlyout } from '../../../common/edit_categorization_analyzer_flyout'; import { - CATEGORY_EXAMPLES_ERROR_LIMIT, - CATEGORY_EXAMPLES_WARNING_LIMIT, -} from '../../../../../../../../../common/constants/new_job'; - -type CategorizationAnalyzerType = CategorizationAnalyzer | null; + CategorizationAnalyzer, + FieldExampleCheck, +} from '../../../../../../../../../common/types/categories'; +import { EditCategorizationAnalyzerFlyout } from '../../../common/edit_categorization_analyzer_flyout'; +import { CATEGORY_EXAMPLES_VALIDATION_STATUS } from '../../../../../../../../../common/constants/new_job'; interface Props { - examplesValid: number; - sampleSize: number; - categorizationAnalyzer: CategorizationAnalyzerType; + validationChecks: FieldExampleCheck[]; + overallValidStatus: CATEGORY_EXAMPLES_VALIDATION_STATUS; + categorizationAnalyzer: CategorizationAnalyzer; } export const ExamplesValidCallout: FC = ({ - examplesValid, + overallValidStatus, + validationChecks, categorizationAnalyzer, - sampleSize, }) => { - const percentageText = ; const analyzerUsed = ; let color: EuiCallOutProps['color'] = 'success'; @@ -40,7 +37,7 @@ export const ExamplesValidCallout: FC = ({ } ); - if (examplesValid < CATEGORY_EXAMPLES_ERROR_LIMIT) { + if (overallValidStatus === CATEGORY_EXAMPLES_VALIDATION_STATUS.INVALID) { color = 'danger'; title = i18n.translate( 'xpack.ml.newJob.wizard.pickFieldsStep.categorizationFieldCalloutTitle.invalid', @@ -48,7 +45,7 @@ export const ExamplesValidCallout: FC = ({ defaultMessage: 'Selected category field is invalid', } ); - } else if (examplesValid < CATEGORY_EXAMPLES_WARNING_LIMIT) { + } else if (overallValidStatus === CATEGORY_EXAMPLES_VALIDATION_STATUS.PARTIALLY_VALID) { color = 'warning'; title = i18n.translate( 'xpack.ml.newJob.wizard.pickFieldsStep.categorizationFieldCalloutTitle.possiblyInvalid', @@ -60,45 +57,24 @@ export const ExamplesValidCallout: FC = ({ return ( - {percentageText} + {validationChecks.map((v, i) => ( +
{v.message}
+ ))} {analyzerUsed}
); }; -const PercentageText: FC<{ examplesValid: number; sampleSize: number }> = ({ - examplesValid, - sampleSize, -}) => ( -
- -
-); - -const AnalyzerUsed: FC<{ categorizationAnalyzer: CategorizationAnalyzerType }> = ({ +const AnalyzerUsed: FC<{ categorizationAnalyzer: CategorizationAnalyzer }> = ({ categorizationAnalyzer, }) => { let analyzer = ''; - if (typeof categorizationAnalyzer === null) { - return null; - } - if (typeof categorizationAnalyzer === 'string') { - analyzer = categorizationAnalyzer; - } else { - if (categorizationAnalyzer?.tokenizer !== undefined) { - analyzer = categorizationAnalyzer?.tokenizer!; - } else if (categorizationAnalyzer?.analyzer !== undefined) { - analyzer = categorizationAnalyzer?.analyzer!; - } + if (categorizationAnalyzer?.tokenizer !== undefined) { + analyzer = categorizationAnalyzer.tokenizer; + } else if (categorizationAnalyzer?.analyzer !== undefined) { + analyzer = categorizationAnalyzer.analyzer; } return ( diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/field_examples.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/field_examples.tsx index 7f9b2e43b9005..51cea179a6c0d 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/field_examples.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/field_examples.tsx @@ -7,10 +7,10 @@ import React, { FC } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiBasicTable, EuiText } from '@elastic/eui'; -import { CategoryExample } from '../../../../../common/results_loader'; +import { CategoryFieldExample } from '../../../../../../../../../common/types/categories'; interface Props { - fieldExamples: CategoryExample[] | null; + fieldExamples: CategoryFieldExample[] | null; } const TOKEN_HIGHLIGHT_COLOR = '#b0ccf7'; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/metric_selection.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/metric_selection.tsx index 52b5c61e70fe5..411f6e898bd48 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/metric_selection.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/metric_selection.tsx @@ -14,7 +14,11 @@ import { CategorizationField } from '../categorization_field'; import { CategorizationDetector } from '../categorization_detector'; import { FieldExamples } from './field_examples'; import { ExamplesValidCallout } from './examples_valid_callout'; -import { CategoryExample } from '../../../../../common/results_loader'; +import { + CategoryFieldExample, + FieldExampleCheck, +} from '../../../../../../../../../common/types/categories'; +import { CATEGORY_EXAMPLES_VALIDATION_STATUS } from '../../../../../../../../../common/constants/new_job'; import { LoadingWrapper } from '../../../charts/loading_wrapper'; interface Props { @@ -31,9 +35,11 @@ export const CategorizationDetectors: FC = ({ setIsValid }) => { const [categorizationAnalyzerString, setCategorizationAnalyzerString] = useState( JSON.stringify(jobCreator.categorizationAnalyzer) ); - const [fieldExamples, setFieldExamples] = useState(null); - const [examplesValid, setExamplesValid] = useState(0); - const [sampleSize, setSampleSize] = useState(0); + const [fieldExamples, setFieldExamples] = useState(null); + const [overallValidStatus, setOverallValidStatus] = useState( + CATEGORY_EXAMPLES_VALIDATION_STATUS.INVALID + ); + const [validationChecks, setValidationChecks] = useState([]); const [categorizationFieldName, setCategorizationFieldName] = useState( jobCreator.categorizationFieldName @@ -73,28 +79,32 @@ export const CategorizationDetectors: FC = ({ setIsValid }) => { setLoadingData(true); try { const { - valid, examples, - sampleSize: tempSampleSize, + overallValidStatus: tempOverallValidStatus, + validationChecks: tempValidationChecks, } = await jobCreator.loadCategorizationFieldExamples(); setFieldExamples(examples); - setExamplesValid(valid); + setOverallValidStatus(tempOverallValidStatus); + setValidationChecks(tempValidationChecks); setLoadingData(false); - setSampleSize(tempSampleSize); } catch (error) { setLoadingData(false); + setFieldExamples(null); + setValidationChecks([]); + setOverallValidStatus(CATEGORY_EXAMPLES_VALIDATION_STATUS.INVALID); mlMessageBarService.notify.error(error); } } else { setFieldExamples(null); - setExamplesValid(0); + setValidationChecks([]); + setOverallValidStatus(CATEGORY_EXAMPLES_VALIDATION_STATUS.INVALID); } setIsValid(categorizationFieldName !== null); } useEffect(() => { jobCreatorUpdate(); - }, [examplesValid]); + }, [overallValidStatus]); return ( <> @@ -109,8 +119,8 @@ export const CategorizationDetectors: FC = ({ setIsValid }) => { {fieldExamples !== null && loadingData === false && ( <> diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/recognize/page.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/recognize/page.tsx index 141ed5d1bbb8f..c4a96d9e373c8 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/recognize/page.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/recognize/page.tsx @@ -77,6 +77,7 @@ export const Page: FC = ({ moduleId, existingGroupIds }) => { const [kibanaObjects, setKibanaObjects] = useState({}); const [saveState, setSaveState] = useState(SAVE_STATE.NOT_SAVED); const [resultsUrl, setResultsUrl] = useState(''); + const [existingGroups, setExistingGroups] = useState(existingGroupIds); // #endregion const { @@ -109,6 +110,10 @@ export const Page: FC = ({ moduleId, existingGroupIds }) => { setKibanaObjects(kibanaObjectsResult); setSaveState(SAVE_STATE.NOT_SAVED); + + // mix existing groups from the server with the groups used across all jobs in the module. + const moduleGroups = [...response.jobs.map(j => j.config.groups || [])].flat(); + setExistingGroups([...new Set([...existingGroups, ...moduleGroups])]); } catch (e) { // eslint-disable-next-line no-console console.error(e); @@ -222,6 +227,12 @@ export const Page: FC = ({ moduleId, existingGroupIds }) => { ...jobOverrides, [job.job_id as string]: job, }); + if (job.groups !== undefined) { + // add newly added jobs to the list of existing groups + // for use when editing other jobs in the module + const groups = [...new Set([...existingGroups, ...job.groups])]; + setExistingGroups(groups); + } }; const isFormVisible = [SAVE_STATE.NOT_SAVED, SAVE_STATE.SAVING].includes(saveState); @@ -304,7 +315,7 @@ export const Page: FC = ({ moduleId, existingGroupIds }) => { jobs={jobs} jobPrefix={jobPrefix} saveState={saveState} - existingGroupIds={existingGroupIds} + existingGroupIds={existingGroups} jobOverrides={jobOverrides} onJobOverridesChange={onJobOverridesChange} /> diff --git a/x-pack/legacy/plugins/ml/public/application/services/http_service.ts b/x-pack/legacy/plugins/ml/public/application/services/http_service.ts index 1d68ec5b886eb..41200759b7c8a 100644 --- a/x-pack/legacy/plugins/ml/public/application/services/http_service.ts +++ b/x-pack/legacy/plugins/ml/public/application/services/http_service.ts @@ -53,7 +53,10 @@ export function http(options: any) { fetch(url, payload) .then(resp => { - resp.json().then(resp.ok === true ? resolve : reject); + resp + .json() + .then(resp.ok === true ? resolve : reject) + .catch(resp.ok === true ? resolve : reject); }) .catch(resp => { reject(resp); diff --git a/x-pack/legacy/plugins/ml/public/application/services/ml_api_service/index.d.ts b/x-pack/legacy/plugins/ml/public/application/services/ml_api_service/index.d.ts index db9d158c0ead9..6420b60e4c838 100644 --- a/x-pack/legacy/plugins/ml/public/application/services/ml_api_service/index.d.ts +++ b/x-pack/legacy/plugins/ml/public/application/services/ml_api_service/index.d.ts @@ -22,6 +22,12 @@ import { PartitionFieldsDefinition } from '../results_service/result_service_rx' import { annotations } from './annotations'; import { Calendar, CalendarId, UpdateCalendar } from '../../../../common/types/calendars'; import { CombinedJob, JobId } from '../../jobs/new_job/common/job_creator/configs'; +import { + CategorizationAnalyzer, + CategoryFieldExample, + FieldExampleCheck, +} from '../../../../common/types/categories'; +import { CATEGORY_EXAMPLES_VALIDATION_STATUS } from '../../../../common/constants/new_job'; // TODO This is not a complete representation of all methods of `ml.*`. // It just satisfies needs for other parts of the code area which use @@ -184,8 +190,13 @@ declare interface Ml { timeField: string | undefined, start: number, end: number, - analyzer: any - ): Promise<{ valid: number; examples: any[]; sampleSize: number }>; + analyzer: CategorizationAnalyzer + ): Promise<{ + examples: CategoryFieldExample[]; + sampleSize: number; + overallValidStatus: CATEGORY_EXAMPLES_VALIDATION_STATUS; + validationChecks: FieldExampleCheck[]; + }>; topCategories( jobId: string, count: number diff --git a/x-pack/legacy/plugins/ml/public/application/services/ml_server_info.ts b/x-pack/legacy/plugins/ml/public/application/services/ml_server_info.ts index 6bf5a7b0c9743..304778281c2f2 100644 --- a/x-pack/legacy/plugins/ml/public/application/services/ml_server_info.ts +++ b/x-pack/legacy/plugins/ml/public/application/services/ml_server_info.ts @@ -5,6 +5,7 @@ */ import { ml } from './ml_api_service'; +import { CategorizationAnalyzer } from '../../../common/types/categories'; export interface MlServerDefaults { anomaly_detectors: { @@ -16,13 +17,6 @@ export interface MlServerDefaults { datafeeds: { scroll_size?: number }; } -export interface CategorizationAnalyzer { - char_filter?: any[]; - tokenizer?: string; - filter?: any[]; - analyzer?: string; -} - export interface MlServerLimits { max_model_memory_limit?: string; } diff --git a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/components/entity_control/entity_control.tsx b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/components/entity_control/entity_control.tsx index df5412e609a9c..6727102f55a52 100644 --- a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/components/entity_control/entity_control.tsx +++ b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/components/entity_control/entity_control.tsx @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { isEqual } from 'lodash'; import React, { Component } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; @@ -47,8 +48,8 @@ export class EntityControl extends Component { return { label: value }; }); @@ -163,7 +165,6 @@ export class TimeSeriesExplorer extends React.Component { selectedDetectorIndex: PropTypes.number, selectedEntities: PropTypes.object, selectedForecastId: PropTypes.string, - setGlobalState: PropTypes.func.isRequired, tableInterval: PropTypes.string, tableSeverity: PropTypes.number, zoom: PropTypes.object, diff --git a/x-pack/legacy/plugins/ml/server/models/data_visualizer/data_visualizer.js b/x-pack/legacy/plugins/ml/server/models/data_visualizer/data_visualizer.js index 7c2e3eaf07bcc..f4ee032ee2dbb 100644 --- a/x-pack/legacy/plugins/ml/server/models/data_visualizer/data_visualizer.js +++ b/x-pack/legacy/plugins/ml/server/models/data_visualizer/data_visualizer.js @@ -261,7 +261,7 @@ export class DataVisualizer { aggregatableFields.forEach((field, i) => { const safeFieldName = getSafeAggregationName(field, i); aggs[`${safeFieldName}_count`] = { - value_count: { field }, + filter: { exists: { field } }, }; aggs[`${safeFieldName}_cardinality`] = { cardinality: { field }, @@ -296,7 +296,7 @@ export class DataVisualizer { samplerShardSize > 0 ? _.get(aggregations, ['sample', 'doc_count'], 0) : totalCount; aggregatableFields.forEach((field, i) => { const safeFieldName = getSafeAggregationName(field, i); - const count = _.get(aggregations, [...aggsPath, `${safeFieldName}_count`, 'value'], 0); + const count = _.get(aggregations, [...aggsPath, `${safeFieldName}_count`, 'doc_count'], 0); if (count > 0) { const cardinality = _.get( aggregations, @@ -433,7 +433,12 @@ export class DataVisualizer { fields.forEach((field, i) => { const safeFieldName = getSafeAggregationName(field.fieldName, i); aggs[`${safeFieldName}_field_stats`] = { - stats: { field: field.fieldName }, + filter: { exists: { field: field.fieldName } }, + aggs: { + actual_stats: { + stats: { field: field.fieldName }, + }, + }, }; aggs[`${safeFieldName}_percentiles`] = { percentiles: { @@ -484,10 +489,19 @@ export class DataVisualizer { const batchStats = []; fields.forEach((field, i) => { const safeFieldName = getSafeAggregationName(field.fieldName, i); - const fieldStatsResp = _.get(aggregations, [...aggsPath, `${safeFieldName}_field_stats`], {}); + const docCount = _.get( + aggregations, + [...aggsPath, `${safeFieldName}_field_stats`, 'doc_count'], + 0 + ); + const fieldStatsResp = _.get( + aggregations, + [...aggsPath, `${safeFieldName}_field_stats`, 'actual_stats'], + {} + ); const stats = { fieldName: field.fieldName, - count: _.get(fieldStatsResp, 'count', 0), + count: docCount, min: _.get(fieldStatsResp, 'min', 0), max: _.get(fieldStatsResp, 'max', 0), avg: _.get(fieldStatsResp, 'avg', 0), @@ -632,7 +646,12 @@ export class DataVisualizer { fields.forEach((field, i) => { const safeFieldName = getSafeAggregationName(field.fieldName, i); aggs[`${safeFieldName}_field_stats`] = { - stats: { field: field.fieldName }, + filter: { exists: { field: field.fieldName } }, + aggs: { + actual_stats: { + stats: { field: field.fieldName }, + }, + }, }; }); @@ -651,10 +670,19 @@ export class DataVisualizer { const batchStats = []; fields.forEach((field, i) => { const safeFieldName = getSafeAggregationName(field.fieldName, i); - const fieldStatsResp = _.get(aggregations, [...aggsPath, `${safeFieldName}_field_stats`], {}); + const docCount = _.get( + aggregations, + [...aggsPath, `${safeFieldName}_field_stats`, 'doc_count'], + 0 + ); + const fieldStatsResp = _.get( + aggregations, + [...aggsPath, `${safeFieldName}_field_stats`, 'actual_stats'], + {} + ); batchStats.push({ fieldName: field.fieldName, - count: _.get(fieldStatsResp, 'count', 0), + count: docCount, earliest: _.get(fieldStatsResp, 'min', 0), latest: _.get(fieldStatsResp, 'max', 0), }); @@ -680,7 +708,7 @@ export class DataVisualizer { fields.forEach((field, i) => { const safeFieldName = getSafeAggregationName(field.fieldName, i); aggs[`${safeFieldName}_value_count`] = { - value_count: { field: field.fieldName }, + filter: { exists: { field: field.fieldName } }, }; aggs[`${safeFieldName}_values`] = { terms: { @@ -707,7 +735,7 @@ export class DataVisualizer { const safeFieldName = getSafeAggregationName(field.fieldName, i); const stats = { fieldName: field.fieldName, - count: _.get(aggregations, [...aggsPath, `${safeFieldName}_value_count`, 'value'], 0), + count: _.get(aggregations, [...aggsPath, `${safeFieldName}_value_count`, 'doc_count'], 0), trueCount: 0, falseCount: 0, }; diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/index.js b/x-pack/legacy/plugins/ml/server/models/job_service/index.js index 186bcbae84546..5c0eff3112a53 100644 --- a/x-pack/legacy/plugins/ml/server/models/job_service/index.js +++ b/x-pack/legacy/plugins/ml/server/models/job_service/index.js @@ -8,7 +8,11 @@ import { datafeedsProvider } from './datafeeds'; import { jobsProvider } from './jobs'; import { groupsProvider } from './groups'; import { newJobCapsProvider } from './new_job_caps'; -import { newJobChartsProvider, categorizationExamplesProvider } from './new_job'; +import { + newJobChartsProvider, + categorizationExamplesProvider, + topCategoriesProvider, +} from './new_job'; export function jobServiceProvider(callWithRequest, request) { return { @@ -18,5 +22,6 @@ export function jobServiceProvider(callWithRequest, request) { ...newJobCapsProvider(callWithRequest, request), ...newJobChartsProvider(callWithRequest, request), ...categorizationExamplesProvider(callWithRequest, request), + ...topCategoriesProvider(callWithRequest, request), }; } diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization.ts b/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization.ts deleted file mode 100644 index b3c70bf589cd0..0000000000000 --- a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization.ts +++ /dev/null @@ -1,314 +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 { chunk } from 'lodash'; -import { ML_RESULTS_INDEX_PATTERN } from '../../../../common/constants/index_patterns'; -import { CATEGORY_EXAMPLES_SAMPLE_SIZE } from '../../../../common/constants/new_job'; -import { CategoryId, Category, Token } from '../../../../common/types/categories'; -import { callWithRequestType } from '../../../../common/types/kibana'; - -const VALID_TOKEN_COUNT = 3; -const CHUNK_SIZE = 100; - -export function categorizationExamplesProvider(callWithRequest: callWithRequestType) { - async function categorizationExamples( - indexPatternTitle: string, - query: any, - size: number, - categorizationFieldName: string, - timeField: string | undefined, - start: number, - end: number, - analyzer?: any - ) { - if (timeField !== undefined) { - const range = { - range: { - [timeField]: { - gte: start, - format: 'epoch_millis', - }, - }, - }; - - if (query.bool === undefined) { - query.bool = {}; - } - if (query.bool.filter === undefined) { - query.bool.filter = range; - } else { - if (Array.isArray(query.bool.filter)) { - query.bool.filter.push(range); - } else { - query.bool.filter.range = range; - } - } - } - - const results = await callWithRequest('search', { - index: indexPatternTitle, - size, - body: { - _source: categorizationFieldName, - query, - }, - }); - const examples: string[] = results.hits?.hits - ?.map((doc: any) => doc._source[categorizationFieldName]) - .filter((example: string | null | undefined) => example !== undefined && example !== null); - - async function loadTokens(chunkSize: number) { - const exampleChunks = chunk(examples, chunkSize); - const tokensPerChunks = await Promise.all(exampleChunks.map(c => getTokens(c, analyzer))); - const tokensPerExample = tokensPerChunks.flat(); - return examples.map((e, i) => ({ text: e, tokens: tokensPerExample[i] })); - } - try { - return loadTokens(CHUNK_SIZE); - } catch (error) { - // if an error is thrown when loading the tokens, lower the chunk size by half and try again - // the error may have been caused by too many tokens being found. - // the _analyze endpoint has a maximum of 10000 tokens. - return loadTokens(CHUNK_SIZE / 2); - } - } - - async function getTokens(examples: string[], analyzer?: any) { - const { tokens }: { tokens: Token[] } = await callWithRequest('indices.analyze', { - body: { - ...getAnalyzer(analyzer), - text: examples, - }, - }); - - const lengths = examples.map(e => e.length); - const sumLengths = lengths.map((s => (a: number) => (s += a))(0)); - - const tokensPerExample: Token[][] = examples.map(e => []); - - tokens.forEach((t, i) => { - for (let g = 0; g < sumLengths.length; g++) { - if (t.start_offset <= sumLengths[g] + g) { - const offset = g > 0 ? sumLengths[g - 1] + g : 0; - tokensPerExample[g].push({ - ...t, - start_offset: t.start_offset - offset, - end_offset: t.end_offset - offset, - }); - break; - } - } - }); - return tokensPerExample; - } - - function getAnalyzer(analyzer: any) { - if (typeof analyzer === 'object' && analyzer.tokenizer !== undefined) { - return analyzer; - } else { - return { analyzer: 'standard' }; - } - } - - async function validateCategoryExamples( - indexPatternTitle: string, - query: any, - size: number, - categorizationFieldName: string, - timeField: string | undefined, - start: number, - end: number, - analyzer?: any - ) { - const resp = await categorizationExamples( - indexPatternTitle, - query, - CATEGORY_EXAMPLES_SAMPLE_SIZE, - categorizationFieldName, - timeField, - start, - end, - analyzer - ); - - const sortedExamples = resp - .map((e, i) => ({ ...e, origIndex: i })) - .sort((a, b) => b.tokens.length - a.tokens.length); - const validExamples = sortedExamples.filter(e => e.tokens.length >= VALID_TOKEN_COUNT); - const sampleSize = sortedExamples.length; - - const multiple = Math.floor(sampleSize / size) || sampleSize; - const filteredExamples = []; - let i = 0; - while (filteredExamples.length < size && i < sortedExamples.length) { - filteredExamples.push(sortedExamples[i]); - i += multiple; - } - const examples = filteredExamples - .sort((a, b) => a.origIndex - b.origIndex) - .map(e => ({ text: e.text, tokens: e.tokens })); - - return { - sampleSize, - valid: sortedExamples.length === 0 ? 0 : validExamples.length / sortedExamples.length, - examples, - }; - } - - async function getTotalCategories(jobId: string): Promise<{ total: number }> { - const totalResp = await callWithRequest('search', { - index: ML_RESULTS_INDEX_PATTERN, - size: 0, - body: { - query: { - bool: { - filter: [ - { - term: { - job_id: jobId, - }, - }, - { - exists: { - field: 'category_id', - }, - }, - ], - }, - }, - }, - }); - return totalResp?.hits?.total?.value ?? 0; - } - - async function getTopCategoryCounts(jobId: string, numberOfCategories: number) { - const top = await callWithRequest('search', { - index: ML_RESULTS_INDEX_PATTERN, - size: 0, - body: { - query: { - bool: { - filter: [ - { - term: { - job_id: jobId, - }, - }, - { - term: { - result_type: 'model_plot', - }, - }, - { - term: { - by_field_name: 'mlcategory', - }, - }, - ], - }, - }, - aggs: { - cat_count: { - terms: { - field: 'by_field_value', - size: numberOfCategories, - }, - }, - }, - }, - }); - - const catCounts: Array<{ - id: CategoryId; - count: number; - }> = top.aggregations?.cat_count?.buckets.map((c: any) => ({ - id: c.key, - count: c.doc_count, - })); - return catCounts || []; - } - - async function getCategories( - jobId: string, - catIds: CategoryId[], - size: number - ): Promise { - const categoryFilter = catIds.length - ? { - terms: { - category_id: catIds, - }, - } - : { - exists: { - field: 'category_id', - }, - }; - const result = await callWithRequest('search', { - index: ML_RESULTS_INDEX_PATTERN, - size, - body: { - query: { - bool: { - filter: [ - { - term: { - job_id: jobId, - }, - }, - categoryFilter, - ], - }, - }, - }, - }); - - return result.hits.hits?.map((c: { _source: Category }) => c._source) || []; - } - - async function topCategories(jobId: string, numberOfCategories: number) { - const catCounts = await getTopCategoryCounts(jobId, numberOfCategories); - const categories = await getCategories( - jobId, - catCounts.map(c => c.id), - catCounts.length || numberOfCategories - ); - - const catsById = categories.reduce((p, c) => { - p[c.category_id] = c; - return p; - }, {} as { [id: number]: Category }); - - const total = await getTotalCategories(jobId); - - if (catCounts.length) { - return { - total, - categories: catCounts.map(({ id, count }) => { - return { - count, - category: catsById[id] ?? null, - }; - }), - }; - } else { - return { - total, - categories: categories.map(category => { - return { - category, - }; - }), - }; - } - } - - return { - categorizationExamples, - validateCategoryExamples, - topCategories, - }; -} diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/examples.ts b/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/examples.ts new file mode 100644 index 0000000000000..76473bd55db7f --- /dev/null +++ b/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/examples.ts @@ -0,0 +1,206 @@ +/* + * 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 { chunk } from 'lodash'; +import { SearchResponse } from 'elasticsearch'; +import { CATEGORY_EXAMPLES_SAMPLE_SIZE } from '../../../../../common/constants/new_job'; +import { + Token, + CategorizationAnalyzer, + CategoryFieldExample, +} from '../../../../../common/types/categories'; +import { callWithRequestType } from '../../../../../common/types/kibana'; +import { ValidationResults } from './validation_results'; + +const CHUNK_SIZE = 100; + +export function categorizationExamplesProvider(callWithRequest: callWithRequestType) { + const validationResults = new ValidationResults(); + + async function categorizationExamples( + indexPatternTitle: string, + query: any, + size: number, + categorizationFieldName: string, + timeField: string | undefined, + start: number, + end: number, + analyzer: CategorizationAnalyzer + ): Promise<{ examples: CategoryFieldExample[]; error?: any }> { + if (timeField !== undefined) { + const range = { + range: { + [timeField]: { + gte: start, + lt: end, + format: 'epoch_millis', + }, + }, + }; + if (query.bool === undefined) { + query.bool = {}; + } + if (query.bool.filter === undefined) { + query.bool.filter = range; + } else { + if (Array.isArray(query.bool.filter)) { + query.bool.filter.push(range); + } else { + query.bool.filter.range = range; + } + } + } + + const results: SearchResponse<{ [id: string]: string }> = await callWithRequest('search', { + index: indexPatternTitle, + size, + body: { + _source: categorizationFieldName, + query, + sort: ['_doc'], + }, + }); + + const tempExamples = results.hits.hits.map(({ _source }) => _source[categorizationFieldName]); + + validationResults.createNullValueResult(tempExamples); + + const allExamples = tempExamples.filter( + (example: string | null | undefined) => example !== undefined && example !== null + ); + + validationResults.createMedianMessageLengthResult(allExamples); + + try { + const examplesWithTokens = await getTokens(CHUNK_SIZE, allExamples, analyzer); + return { examples: examplesWithTokens }; + } catch (err) { + // console.log('dropping to 50 chunk size'); + // if an error is thrown when loading the tokens, lower the chunk size by half and try again + // the error may have been caused by too many tokens being found. + // the _analyze endpoint has a maximum of 10000 tokens. + const halfExamples = allExamples.splice(0, Math.ceil(allExamples.length / 2)); + const halfChunkSize = CHUNK_SIZE / 2; + try { + const examplesWithTokens = await getTokens(halfChunkSize, halfExamples, analyzer); + return { examples: examplesWithTokens }; + } catch (error) { + validationResults.createTooManyTokensResult(error, halfChunkSize); + return { examples: halfExamples.map(e => ({ text: e, tokens: [] })) }; + } + } + } + + async function getTokens( + chunkSize: number, + examples: string[], + analyzer: CategorizationAnalyzer + ): Promise { + const exampleChunks = chunk(examples, chunkSize); + const tokensPerExampleChunks: Token[][][] = []; + for (const c of exampleChunks) { + tokensPerExampleChunks.push(await loadTokens(c, analyzer)); + } + const tokensPerExample = tokensPerExampleChunks.flat(); + return examples.map((e, i) => ({ text: e, tokens: tokensPerExample[i] })); + } + + async function loadTokens(examples: string[], analyzer: CategorizationAnalyzer) { + const { tokens }: { tokens: Token[] } = await callWithRequest('indices.analyze', { + body: { + ...getAnalyzer(analyzer), + text: examples, + }, + }); + + const lengths = examples.map(e => e.length); + const sumLengths = lengths.map((s => (a: number) => (s += a))(0)); + + const tokensPerExample: Token[][] = examples.map(e => []); + + tokens.forEach((t, i) => { + for (let g = 0; g < sumLengths.length; g++) { + if (t.start_offset <= sumLengths[g] + g) { + const offset = g > 0 ? sumLengths[g - 1] + g : 0; + tokensPerExample[g].push({ + ...t, + start_offset: t.start_offset - offset, + end_offset: t.end_offset - offset, + }); + break; + } + } + }); + return tokensPerExample; + } + + function getAnalyzer(analyzer: CategorizationAnalyzer) { + if (typeof analyzer === 'object' && analyzer.tokenizer !== undefined) { + return analyzer; + } else { + return { analyzer: 'standard' }; + } + } + + async function validateCategoryExamples( + indexPatternTitle: string, + query: any, + size: number, + categorizationFieldName: string, + timeField: string | undefined, + start: number, + end: number, + analyzer: CategorizationAnalyzer + ) { + const resp = await categorizationExamples( + indexPatternTitle, + query, + CATEGORY_EXAMPLES_SAMPLE_SIZE, + categorizationFieldName, + timeField, + start, + end, + analyzer + ); + + const { examples } = resp; + const sampleSize = examples.length; + validationResults.createTokenCountResult(examples, sampleSize); + + // sort examples by number of tokens, keeping track of their original order + // with an origIndex property + const sortedExamples = examples + .map((e, i) => ({ ...e, origIndex: i })) + .sort((a, b) => b.tokens.length - a.tokens.length); + + // we only want 'size' (e.g. 5) number of examples, + // so loop through the sorted examples, taking 5 at evenly + // spread intervals + const multiple = Math.floor(sampleSize / size) || sampleSize; + const filteredExamples = []; + let i = 0; + while (filteredExamples.length < size && i < sampleSize) { + filteredExamples.push(sortedExamples[i]); + i += multiple; + } + + // sort back into original order and remove origIndex property + const processedExamples = filteredExamples + .sort((a, b) => a.origIndex - b.origIndex) + .map(e => ({ text: e.text, tokens: e.tokens })); + + return { + overallValidStatus: validationResults.overallResult, + validationChecks: validationResults.results, + sampleSize, + examples: processedExamples, + }; + } + + return { + validateCategoryExamples, + }; +} diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/index.ts b/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/index.ts new file mode 100644 index 0000000000000..be32b99b5e527 --- /dev/null +++ b/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/index.ts @@ -0,0 +1,8 @@ +/* + * 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 { categorizationExamplesProvider } from './examples'; +export { topCategoriesProvider } from './top_categories'; diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/top_categories.ts b/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/top_categories.ts new file mode 100644 index 0000000000000..3361cc454e2b7 --- /dev/null +++ b/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/top_categories.ts @@ -0,0 +1,164 @@ +/* + * 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 { SearchResponse } from 'elasticsearch'; +import { ML_RESULTS_INDEX_PATTERN } from '../../../../../common/constants/index_patterns'; +import { CategoryId, Category } from '../../../../../common/types/categories'; +import { callWithRequestType } from '../../../../../common/types/kibana'; + +export function topCategoriesProvider(callWithRequest: callWithRequestType) { + async function getTotalCategories(jobId: string): Promise<{ total: number }> { + const totalResp = await callWithRequest('search', { + index: ML_RESULTS_INDEX_PATTERN, + size: 0, + body: { + query: { + bool: { + filter: [ + { + term: { + job_id: jobId, + }, + }, + { + exists: { + field: 'category_id', + }, + }, + ], + }, + }, + }, + }); + return totalResp?.hits?.total?.value ?? 0; + } + + async function getTopCategoryCounts(jobId: string, numberOfCategories: number) { + const top: SearchResponse = await callWithRequest('search', { + index: ML_RESULTS_INDEX_PATTERN, + size: 0, + body: { + query: { + bool: { + filter: [ + { + term: { + job_id: jobId, + }, + }, + { + term: { + result_type: 'model_plot', + }, + }, + { + term: { + by_field_name: 'mlcategory', + }, + }, + ], + }, + }, + aggs: { + cat_count: { + terms: { + field: 'by_field_value', + size: numberOfCategories, + }, + }, + }, + }, + }); + + const catCounts: Array<{ + id: CategoryId; + count: number; + }> = top.aggregations?.cat_count?.buckets.map((c: any) => ({ + id: c.key, + count: c.doc_count, + })); + return catCounts || []; + } + + async function getCategories( + jobId: string, + catIds: CategoryId[], + size: number + ): Promise { + const categoryFilter = catIds.length + ? { + terms: { + category_id: catIds, + }, + } + : { + exists: { + field: 'category_id', + }, + }; + const result: SearchResponse = await callWithRequest('search', { + index: ML_RESULTS_INDEX_PATTERN, + size, + body: { + query: { + bool: { + filter: [ + { + term: { + job_id: jobId, + }, + }, + categoryFilter, + ], + }, + }, + }, + }); + + return result.hits.hits?.map((c: { _source: Category }) => c._source) || []; + } + + async function topCategories(jobId: string, numberOfCategories: number) { + const catCounts = await getTopCategoryCounts(jobId, numberOfCategories); + const categories = await getCategories( + jobId, + catCounts.map(c => c.id), + catCounts.length || numberOfCategories + ); + + const catsById = categories.reduce((p, c) => { + p[c.category_id] = c; + return p; + }, {} as { [id: number]: Category }); + + const total = await getTotalCategories(jobId); + + if (catCounts.length) { + return { + total, + categories: catCounts.map(({ id, count }) => { + return { + count, + category: catsById[id] ?? null, + }; + }), + }; + } else { + return { + total, + categories: categories.map(category => { + return { + category, + }; + }), + }; + } + } + + return { + topCategories, + }; +} diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/validation_results.ts b/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/validation_results.ts new file mode 100644 index 0000000000000..34e63eabb405e --- /dev/null +++ b/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/validation_results.ts @@ -0,0 +1,208 @@ +/* + * 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 { + CATEGORY_EXAMPLES_VALIDATION_STATUS, + CATEGORY_EXAMPLES_ERROR_LIMIT, + CATEGORY_EXAMPLES_WARNING_LIMIT, +} from '../../../../../common/constants/new_job'; +import { + FieldExampleCheck, + CategoryFieldExample, + VALIDATION_RESULT, +} from '../../../../../common/types/categories'; +import { getMedianStringLength } from '../../../../../common/util/string_utils'; + +const VALID_TOKEN_COUNT = 3; +const MEDIAN_LINE_LENGTH_LIMIT = 400; +const NULL_COUNT_PERCENT_LIMIT = 0.75; + +export class ValidationResults { + private _results: FieldExampleCheck[] = []; + + public get results() { + return this._results; + } + + public get overallResult() { + if (this._results.some(c => c.valid === CATEGORY_EXAMPLES_VALIDATION_STATUS.INVALID)) { + return CATEGORY_EXAMPLES_VALIDATION_STATUS.INVALID; + } + if (this._results.some(c => c.valid === CATEGORY_EXAMPLES_VALIDATION_STATUS.PARTIALLY_VALID)) { + return CATEGORY_EXAMPLES_VALIDATION_STATUS.PARTIALLY_VALID; + } + return CATEGORY_EXAMPLES_VALIDATION_STATUS.VALID; + } + + private _resultExists(id: VALIDATION_RESULT) { + return this._results.some(r => r.id === id); + } + + public createTokenCountResult(examples: CategoryFieldExample[], sampleSize: number) { + if (examples.length === 0) { + this.createNoExamplesResult(); + return; + } + + if (this._resultExists(VALIDATION_RESULT.INSUFFICIENT_PRIVILEGES) === true) { + // if tokenizing has failed due to insufficient privileges, don't show + // the message about token count + return; + } + + const validExamplesSize = examples.filter(e => e.tokens.length >= VALID_TOKEN_COUNT).length; + const percentValid = sampleSize === 0 ? 0 : validExamplesSize / sampleSize; + + let valid = CATEGORY_EXAMPLES_VALIDATION_STATUS.VALID; + if (percentValid < CATEGORY_EXAMPLES_ERROR_LIMIT) { + valid = CATEGORY_EXAMPLES_VALIDATION_STATUS.INVALID; + } else if (percentValid < CATEGORY_EXAMPLES_WARNING_LIMIT) { + valid = CATEGORY_EXAMPLES_VALIDATION_STATUS.PARTIALLY_VALID; + } + + const message = i18n.translate( + 'xpack.ml.models.jobService.categorization.messages.tokenLengthValidation', + { + defaultMessage: + '{number} field {number, plural, zero {value} one {value} other {values}} analyzed, {percentage}% contain {validTokenCount} or more tokens.', + values: { + number: sampleSize, + percentage: Math.floor(percentValid * 100), + validTokenCount: VALID_TOKEN_COUNT, + }, + } + ); + + if ( + this._resultExists(VALIDATION_RESULT.TOO_MANY_TOKENS) === false && + this._resultExists(VALIDATION_RESULT.FAILED_TO_TOKENIZE) === false + ) { + this._results.unshift({ + id: VALIDATION_RESULT.TOKEN_COUNT, + valid, + message, + }); + } + } + + public createMedianMessageLengthResult(examples: string[]) { + const median = getMedianStringLength(examples); + + if (median > MEDIAN_LINE_LENGTH_LIMIT) { + this._results.push({ + id: VALIDATION_RESULT.MEDIAN_LINE_LENGTH, + valid: CATEGORY_EXAMPLES_VALIDATION_STATUS.PARTIALLY_VALID, + message: i18n.translate( + 'xpack.ml.models.jobService.categorization.messages.medianLineLength', + { + defaultMessage: + 'The median length for the field values analyzed is over {medianLimit} characters.', + values: { medianLimit: MEDIAN_LINE_LENGTH_LIMIT }, + } + ), + }); + } + } + + public createNoExamplesResult() { + this._results.push({ + id: VALIDATION_RESULT.NO_EXAMPLES, + valid: CATEGORY_EXAMPLES_VALIDATION_STATUS.INVALID, + message: i18n.translate('xpack.ml.models.jobService.categorization.messages.noDataFound', { + defaultMessage: + 'No examples for this field could be found. Please ensure the selected date range contains data.', + }), + }); + } + + public createNullValueResult(examples: Array) { + const nullCount = examples.filter(e => e === null).length; + + if (nullCount / examples.length >= NULL_COUNT_PERCENT_LIMIT) { + this._results.push({ + id: VALIDATION_RESULT.NULL_VALUES, + valid: CATEGORY_EXAMPLES_VALIDATION_STATUS.PARTIALLY_VALID, + message: i18n.translate('xpack.ml.models.jobService.categorization.messages.nullValues', { + defaultMessage: 'More than {percent}% of field values are null.', + values: { percent: NULL_COUNT_PERCENT_LIMIT * 100 }, + }), + }); + } + } + + public createTooManyTokensResult(error: any, sampleSize: number) { + // expecting error message: + // The number of tokens produced by calling _analyze has exceeded the allowed maximum of [10000]. + // This limit can be set by changing the [index.analyze.max_token_count] index level setting. + + if (error.statusCode === 403) { + this.createPrivilegesErrorResult(error); + return; + } + const message: string = error.message; + if (message) { + const rxp = /exceeded the allowed maximum of \[(\d+?)\]/; + const match = rxp.exec(message); + if (match?.length === 2) { + const tokenLimit = match[1]; + this._results.push({ + id: VALIDATION_RESULT.TOO_MANY_TOKENS, + valid: CATEGORY_EXAMPLES_VALIDATION_STATUS.INVALID, + message: i18n.translate( + 'xpack.ml.models.jobService.categorization.messages.tooManyTokens', + { + defaultMessage: + 'Tokenization of field value examples has failed due to more than {tokenLimit} tokens being found in a sample of {sampleSize} values.', + values: { sampleSize, tokenLimit }, + } + ), + }); + return; + } + return; + } + this.createFailureToTokenize(message); + } + + public createPrivilegesErrorResult(error: any) { + const message: string = error.message; + if (message) { + this._results.push({ + id: VALIDATION_RESULT.INSUFFICIENT_PRIVILEGES, + valid: CATEGORY_EXAMPLES_VALIDATION_STATUS.PARTIALLY_VALID, + message: i18n.translate( + 'xpack.ml.models.jobService.categorization.messages.insufficientPrivileges', + { + defaultMessage: + 'Tokenization of field value examples could not be performed due to insufficient privileges. Field values cannot therefore be checked to see if they are appropriate for use in a categorization job.', + } + ), + }); + this._results.push({ + id: VALIDATION_RESULT.INSUFFICIENT_PRIVILEGES, + valid: CATEGORY_EXAMPLES_VALIDATION_STATUS.PARTIALLY_VALID, + message, + }); + return; + } + } + + public createFailureToTokenize(message: string | undefined) { + this._results.push({ + id: VALIDATION_RESULT.FAILED_TO_TOKENIZE, + valid: CATEGORY_EXAMPLES_VALIDATION_STATUS.INVALID, + message: i18n.translate( + 'xpack.ml.models.jobService.categorization.messages.failureToGetTokens', + { + defaultMessage: + 'It was not possible to tokenize a sample of example field values. {message}', + values: { message: message || '' }, + } + ), + }); + } +} diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/index.ts b/x-pack/legacy/plugins/ml/server/models/job_service/new_job/index.ts index da23efa67d0b5..da60a90f4bfbc 100644 --- a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/index.ts +++ b/x-pack/legacy/plugins/ml/server/models/job_service/new_job/index.ts @@ -5,4 +5,4 @@ */ export { newJobChartsProvider } from './charts'; -export { categorizationExamplesProvider } from './categorization'; +export { categorizationExamplesProvider, topCategoriesProvider } from './categorization'; diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/mock_farequote_cardinality.json b/x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/mock_farequote_cardinality.json index 9dcfc11575abb..8d408ff0310c9 100644 --- a/x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/mock_farequote_cardinality.json +++ b/x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/mock_farequote_cardinality.json @@ -1 +1,7 @@ -{"took":0,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":86274,"max_score":0,"hits":[]},"aggregations":{"airline_cardinality":{"value":19},"airline_count":{"value":86274}}} +{ + "took": 0, + "timed_out": false, + "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }, + "hits": { "total": 86274, "max_score": 0, "hits": [] }, + "aggregations": { "airline_cardinality": { "value": 19 }, "airline_count": { "doc_count": 86274 } } +} diff --git a/x-pack/legacy/plugins/monitoring/__tests__/deprecations.js b/x-pack/legacy/plugins/monitoring/__tests__/deprecations.js index aaaf3d6ad40cf..3df93bdb24f32 100644 --- a/x-pack/legacy/plugins/monitoring/__tests__/deprecations.js +++ b/x-pack/legacy/plugins/monitoring/__tests__/deprecations.js @@ -80,4 +80,54 @@ describe('monitoring plugin deprecations', function() { expect(log.called).to.be(true); }); }); + + describe('elasticsearch.username', function() { + it('logs a warning if elasticsearch.username is set to "elastic"', () => { + const settings = { elasticsearch: { username: 'elastic' } }; + + const log = sinon.spy(); + transformDeprecations(settings, log); + expect(log.called).to.be(true); + }); + + it('does not log a warning if elasticsearch.username is set to something besides "elastic"', () => { + const settings = { elasticsearch: { username: 'otheruser' } }; + + const log = sinon.spy(); + transformDeprecations(settings, log); + expect(log.called).to.be(false); + }); + + it('does not log a warning if elasticsearch.username is unset', () => { + const settings = { elasticsearch: { username: undefined } }; + + const log = sinon.spy(); + transformDeprecations(settings, log); + expect(log.called).to.be(false); + }); + + it('logs a warning if ssl.key is set and ssl.certificate is not', () => { + const settings = { elasticsearch: { ssl: { key: '' } } }; + + const log = sinon.spy(); + transformDeprecations(settings, log); + expect(log.called).to.be(true); + }); + + it('logs a warning if ssl.certificate is set and ssl.key is not', () => { + const settings = { elasticsearch: { ssl: { certificate: '' } } }; + + const log = sinon.spy(); + transformDeprecations(settings, log); + expect(log.called).to.be(true); + }); + + it('does not log a warning if both ssl.key and ssl.certificate are set', () => { + const settings = { elasticsearch: { ssl: { key: '', certificate: '' } } }; + + const log = sinon.spy(); + transformDeprecations(settings, log); + expect(log.called).to.be(false); + }); + }); }); diff --git a/x-pack/legacy/plugins/monitoring/config.js b/x-pack/legacy/plugins/monitoring/config.js index c33b0b28e830a..91c1ee99a0b2e 100644 --- a/x-pack/legacy/plugins/monitoring/config.js +++ b/x-pack/legacy/plugins/monitoring/config.js @@ -83,6 +83,14 @@ export const config = Joi => { certificate: Joi.string(), key: Joi.string(), keyPassphrase: Joi.string(), + keystore: Joi.object({ + path: Joi.string(), + password: Joi.string(), + }).default(), + truststore: Joi.object({ + path: Joi.string(), + password: Joi.string(), + }).default(), alwaysPresentCertificate: Joi.boolean().default(false), }).default(), apiVersion: Joi.string().default('master'), diff --git a/x-pack/legacy/plugins/monitoring/deprecations.js b/x-pack/legacy/plugins/monitoring/deprecations.js index 13a6a58fa8752..c3b2b70690f33 100644 --- a/x-pack/legacy/plugins/monitoring/deprecations.js +++ b/x-pack/legacy/plugins/monitoring/deprecations.js @@ -27,5 +27,31 @@ export const deprecations = () => { ); } }, + (settings, log) => { + const fromPath = 'xpack.monitoring.elasticsearch'; + const es = get(settings, 'elasticsearch'); + if (es) { + if (es.username === 'elastic') { + log( + `Setting [${fromPath}.username] to "elastic" is deprecated. You should use the "kibana" user instead.` + ); + } + } + }, + (settings, log) => { + const fromPath = 'xpack.monitoring.elasticsearch.ssl'; + const ssl = get(settings, 'elasticsearch.ssl'); + if (ssl) { + if (ssl.key !== undefined && ssl.certificate === undefined) { + log( + `Setting [${fromPath}.key] without [${fromPath}.certificate] is deprecated. This has no effect, you should use both settings to enable TLS client authentication to Elasticsearch.` + ); + } else if (ssl.certificate !== undefined && ssl.key === undefined) { + log( + `Setting [${fromPath}.certificate] without [${fromPath}.key] is deprecated. This has no effect, you should use both settings to enable TLS client authentication to Elasticsearch.` + ); + } + } + }, ]; }; diff --git a/x-pack/legacy/plugins/monitoring/server/es_client/__tests__/instantiate_client.js b/x-pack/legacy/plugins/monitoring/server/es_client/__tests__/instantiate_client.js index 8797f92e489bb..6844bd5febf8e 100644 --- a/x-pack/legacy/plugins/monitoring/server/es_client/__tests__/instantiate_client.js +++ b/x-pack/legacy/plugins/monitoring/server/es_client/__tests__/instantiate_client.js @@ -6,7 +6,7 @@ import expect from '@kbn/expect'; import sinon from 'sinon'; -import { get, noop } from 'lodash'; +import { noop } from 'lodash'; import { exposeClient, hasMonitoringCluster } from '../instantiate_client'; function getMockServerFromConnectionUrl(monitoringClusterUrl) { @@ -26,15 +26,8 @@ function getMockServerFromConnectionUrl(monitoringClusterUrl) { }, }; - const config = { - get: path => { - return get(server, path); - }, - set: noop, - }; - return { - config, + elasticsearchConfig: server.xpack.monitoring.elasticsearch, elasticsearchPlugin: { getCluster: sinon .stub() @@ -141,12 +134,12 @@ describe('Instantiate Client', () => { describe('hasMonitoringCluster', () => { it('returns true if monitoring is configured', () => { const server = getMockServerFromConnectionUrl('http://monitoring-cluster.test:9200'); // pass null for URL to create the client using prod config - expect(hasMonitoringCluster(server.config)).to.be(true); + expect(hasMonitoringCluster(server.elasticsearchConfig)).to.be(true); }); it('returns false if monitoring is not configured', () => { const server = getMockServerFromConnectionUrl(null); - expect(hasMonitoringCluster(server.config)).to.be(false); + expect(hasMonitoringCluster(server.elasticsearchConfig)).to.be(false); }); }); }); diff --git a/x-pack/legacy/plugins/monitoring/server/es_client/instantiate_client.js b/x-pack/legacy/plugins/monitoring/server/es_client/instantiate_client.js index 87a2e5349cf1b..9aed1ac145617 100644 --- a/x-pack/legacy/plugins/monitoring/server/es_client/instantiate_client.js +++ b/x-pack/legacy/plugins/monitoring/server/es_client/instantiate_client.js @@ -14,24 +14,21 @@ import { LOGGING_TAG } from '../../common/constants'; * Kibana itself is connected to a production cluster. */ -export function exposeClient({ config, events, log, elasticsearchPlugin }) { - const elasticsearchConfig = hasMonitoringCluster(config) - ? config.get('xpack.monitoring.elasticsearch') - : {}; +export function exposeClient({ elasticsearchConfig, events, log, elasticsearchPlugin }) { + const isMonitoringCluster = hasMonitoringCluster(elasticsearchConfig); const cluster = elasticsearchPlugin.createCluster('monitoring', { - ...elasticsearchConfig, + ...(isMonitoringCluster ? elasticsearchConfig : {}), plugins: [monitoringBulk], logQueries: Boolean(elasticsearchConfig.logQueries), }); events.on('stop', bindKey(cluster, 'close')); - const configSource = hasMonitoringCluster(config) ? 'monitoring' : 'production'; + const configSource = isMonitoringCluster ? 'monitoring' : 'production'; log([LOGGING_TAG, 'es-client'], `config sourced from: ${configSource} cluster`); } export function hasMonitoringCluster(config) { - const hosts = config.get('xpack.monitoring.elasticsearch.hosts'); - return Boolean(hosts && hosts.length); + return Boolean(config.hosts && config.hosts.length); } export const instantiateClient = once(exposeClient); diff --git a/x-pack/legacy/plugins/monitoring/server/es_client/parse_elasticsearch_config.test.mocks.ts b/x-pack/legacy/plugins/monitoring/server/es_client/parse_elasticsearch_config.test.mocks.ts new file mode 100644 index 0000000000000..42141313ceea2 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/server/es_client/parse_elasticsearch_config.test.mocks.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const mockReadFileSync = jest.fn(); +jest.mock('fs', () => ({ readFileSync: mockReadFileSync })); + +export const mockReadPkcs12Keystore = jest.fn(); +export const mockReadPkcs12Truststore = jest.fn(); +jest.mock('../../../../../../src/core/utils', () => ({ + readPkcs12Keystore: mockReadPkcs12Keystore, + readPkcs12Truststore: mockReadPkcs12Truststore, +})); diff --git a/x-pack/legacy/plugins/monitoring/server/es_client/parse_elasticsearch_config.test.ts b/x-pack/legacy/plugins/monitoring/server/es_client/parse_elasticsearch_config.test.ts new file mode 100644 index 0000000000000..c6f4e0fa68504 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/server/es_client/parse_elasticsearch_config.test.ts @@ -0,0 +1,181 @@ +/* + * 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 { + mockReadFileSync, + mockReadPkcs12Keystore, + mockReadPkcs12Truststore, +} from './parse_elasticsearch_config.test.mocks'; + +import { parseElasticsearchConfig } from './parse_elasticsearch_config'; + +const parse = (config: any) => { + return parseElasticsearchConfig({ + get: () => config, + }); +}; + +describe('reads files', () => { + beforeEach(() => { + mockReadFileSync.mockReset(); + mockReadFileSync.mockImplementation((path: string) => `content-of-${path}`); + mockReadPkcs12Keystore.mockReset(); + mockReadPkcs12Keystore.mockImplementation((path: string) => ({ + key: `content-of-${path}.key`, + cert: `content-of-${path}.cert`, + ca: [`content-of-${path}.ca`], + })); + mockReadPkcs12Truststore.mockReset(); + mockReadPkcs12Truststore.mockImplementation((path: string) => [`content-of-${path}`]); + }); + + it('reads certificate authorities when ssl.keystore.path is specified', () => { + const configValue = parse({ ssl: { keystore: { path: 'some-path' } } }); + expect(mockReadPkcs12Keystore).toHaveBeenCalledTimes(1); + expect(configValue.ssl.certificateAuthorities).toEqual(['content-of-some-path.ca']); + }); + + it('reads certificate authorities when ssl.truststore.path is specified', () => { + const configValue = parse({ ssl: { truststore: { path: 'some-path' } } }); + expect(mockReadPkcs12Truststore).toHaveBeenCalledTimes(1); + expect(configValue.ssl.certificateAuthorities).toEqual(['content-of-some-path']); + }); + + it('reads certificate authorities when ssl.certificateAuthorities is specified', () => { + let configValue = parse({ ssl: { certificateAuthorities: 'some-path' } }); + expect(mockReadFileSync).toHaveBeenCalledTimes(1); + expect(configValue.ssl.certificateAuthorities).toEqual(['content-of-some-path']); + + mockReadFileSync.mockClear(); + configValue = parse({ ssl: { certificateAuthorities: ['some-path'] } }); + expect(mockReadFileSync).toHaveBeenCalledTimes(1); + expect(configValue.ssl.certificateAuthorities).toEqual(['content-of-some-path']); + + mockReadFileSync.mockClear(); + configValue = parse({ ssl: { certificateAuthorities: ['some-path', 'another-path'] } }); + expect(mockReadFileSync).toHaveBeenCalledTimes(2); + expect(configValue.ssl.certificateAuthorities).toEqual([ + 'content-of-some-path', + 'content-of-another-path', + ]); + }); + + it('reads certificate authorities when ssl.keystore.path, ssl.truststore.path, and ssl.certificateAuthorities are specified', () => { + const configValue = parse({ + ssl: { + keystore: { path: 'some-path' }, + truststore: { path: 'another-path' }, + certificateAuthorities: 'yet-another-path', + }, + }); + expect(mockReadPkcs12Keystore).toHaveBeenCalledTimes(1); + expect(mockReadPkcs12Truststore).toHaveBeenCalledTimes(1); + expect(mockReadFileSync).toHaveBeenCalledTimes(1); + expect(configValue.ssl.certificateAuthorities).toEqual([ + 'content-of-some-path.ca', + 'content-of-another-path', + 'content-of-yet-another-path', + ]); + }); + + it('reads a private key and certificate when ssl.keystore.path is specified', () => { + const configValue = parse({ ssl: { keystore: { path: 'some-path' } } }); + expect(mockReadPkcs12Keystore).toHaveBeenCalledTimes(1); + expect(configValue.ssl.key).toEqual('content-of-some-path.key'); + expect(configValue.ssl.certificate).toEqual('content-of-some-path.cert'); + }); + + it('reads a private key when ssl.key is specified', () => { + const configValue = parse({ ssl: { key: 'some-path' } }); + expect(mockReadFileSync).toHaveBeenCalledTimes(1); + expect(configValue.ssl.key).toEqual('content-of-some-path'); + }); + + it('reads a certificate when ssl.certificate is specified', () => { + const configValue = parse({ ssl: { certificate: 'some-path' } }); + expect(mockReadFileSync).toHaveBeenCalledTimes(1); + expect(configValue.ssl.certificate).toEqual('content-of-some-path'); + }); +}); + +describe('throws when config is invalid', () => { + beforeAll(() => { + const realFs = jest.requireActual('fs'); + mockReadFileSync.mockImplementation((path: string) => realFs.readFileSync(path)); + const utils = jest.requireActual('../../../../../../src/core/utils'); + mockReadPkcs12Keystore.mockImplementation((path: string, password?: string) => + utils.readPkcs12Keystore(path, password) + ); + mockReadPkcs12Truststore.mockImplementation((path: string, password?: string) => + utils.readPkcs12Truststore(path, password) + ); + }); + + it('throws if key is invalid', () => { + const value = { ssl: { key: '/invalid/key' } }; + expect(() => parse(value)).toThrowErrorMatchingInlineSnapshot( + `"ENOENT: no such file or directory, open '/invalid/key'"` + ); + }); + + it('throws if certificate is invalid', () => { + const value = { ssl: { certificate: '/invalid/cert' } }; + expect(() => parse(value)).toThrowErrorMatchingInlineSnapshot( + `"ENOENT: no such file or directory, open '/invalid/cert'"` + ); + }); + + it('throws if certificateAuthorities is invalid', () => { + const value = { ssl: { certificateAuthorities: '/invalid/ca' } }; + expect(() => parse(value)).toThrowErrorMatchingInlineSnapshot( + `"ENOENT: no such file or directory, open '/invalid/ca'"` + ); + }); + + it('throws if keystore path is invalid', () => { + const value = { ssl: { keystore: { path: '/invalid/keystore' } } }; + expect(() => parse(value)).toThrowErrorMatchingInlineSnapshot( + `"ENOENT: no such file or directory, open '/invalid/keystore'"` + ); + }); + + it('throws if keystore does not contain a key', () => { + mockReadPkcs12Keystore.mockReturnValueOnce({}); + const value = { ssl: { keystore: { path: 'some-path' } } }; + expect(() => parse(value)).toThrowErrorMatchingInlineSnapshot( + `"Did not find key in Elasticsearch keystore."` + ); + }); + + it('throws if keystore does not contain a certificate', () => { + mockReadPkcs12Keystore.mockReturnValueOnce({ key: 'foo' }); + const value = { ssl: { keystore: { path: 'some-path' } } }; + expect(() => parse(value)).toThrowErrorMatchingInlineSnapshot( + `"Did not find certificate in Elasticsearch keystore."` + ); + }); + + it('throws if truststore path is invalid', () => { + const value = { ssl: { keystore: { path: '/invalid/truststore' } } }; + expect(() => parse(value)).toThrowErrorMatchingInlineSnapshot( + `"ENOENT: no such file or directory, open '/invalid/truststore'"` + ); + }); + + it('throws if key and keystore.path are both specified', () => { + const value = { ssl: { key: 'foo', keystore: { path: 'bar' } } }; + expect(() => parse(value)).toThrowErrorMatchingInlineSnapshot( + `"[config validation of [xpack.monitoring.elasticsearch].ssl]: cannot use [key] when [keystore.path] is specified"` + ); + }); + + it('throws if certificate and keystore.path are both specified', () => { + const value = { ssl: { certificate: 'foo', keystore: { path: 'bar' } } }; + expect(() => parse(value)).toThrowErrorMatchingInlineSnapshot( + `"[config validation of [xpack.monitoring.elasticsearch].ssl]: cannot use [certificate] when [keystore.path] is specified"` + ); + }); +}); diff --git a/x-pack/legacy/plugins/monitoring/server/es_client/parse_elasticsearch_config.ts b/x-pack/legacy/plugins/monitoring/server/es_client/parse_elasticsearch_config.ts new file mode 100644 index 0000000000000..70e6235602b5b --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/server/es_client/parse_elasticsearch_config.ts @@ -0,0 +1,111 @@ +/* + * 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 { readFileSync } from 'fs'; +import { readPkcs12Truststore, readPkcs12Keystore } from '../../../../../../src/core/utils'; + +const KEY = 'xpack.monitoring.elasticsearch'; + +/* + * Parse a config object's Elasticsearch configuration, reading any + * certificates/keys from the filesystem + * + * TODO: this code can be removed when this plugin is migrated to the Kibana Platform, + * at that point the ElasticsearchClient and ElasticsearchConfig should be used instead + */ +export const parseElasticsearchConfig = (config: any) => { + const es = config.get(KEY); + + const errorPrefix = `[config validation of [${KEY}].ssl]`; + if (es.ssl?.key && es.ssl?.keystore?.path) { + throw new Error(`${errorPrefix}: cannot use [key] when [keystore.path] is specified`); + } + if (es.ssl?.certificate && es.ssl?.keystore?.path) { + throw new Error(`${errorPrefix}: cannot use [certificate] when [keystore.path] is specified`); + } + + const { alwaysPresentCertificate, verificationMode } = es.ssl; + const { key, keyPassphrase, certificate, certificateAuthorities } = readKeyAndCerts(es); + + return { + ...es, + ssl: { + alwaysPresentCertificate, + key, + keyPassphrase, + certificate, + certificateAuthorities, + verificationMode, + }, + }; +}; + +const readKeyAndCerts = (rawConfig: any) => { + let key: string | undefined; + let keyPassphrase: string | undefined; + let certificate: string | undefined; + let certificateAuthorities: string[] | undefined; + + const addCAs = (ca: string[] | undefined) => { + if (ca && ca.length) { + certificateAuthorities = [...(certificateAuthorities || []), ...ca]; + } + }; + + if (rawConfig.ssl.keystore?.path) { + const keystore = readPkcs12Keystore( + rawConfig.ssl.keystore.path, + rawConfig.ssl.keystore.password + ); + if (!keystore.key) { + throw new Error(`Did not find key in Elasticsearch keystore.`); + } else if (!keystore.cert) { + throw new Error(`Did not find certificate in Elasticsearch keystore.`); + } + key = keystore.key; + certificate = keystore.cert; + addCAs(keystore.ca); + } else { + if (rawConfig.ssl.key) { + key = readFile(rawConfig.ssl.key); + keyPassphrase = rawConfig.ssl.keyPassphrase; + } + if (rawConfig.ssl.certificate) { + certificate = readFile(rawConfig.ssl.certificate); + } + } + + if (rawConfig.ssl.truststore?.path) { + const ca = readPkcs12Truststore( + rawConfig.ssl.truststore.path, + rawConfig.ssl.truststore.password + ); + addCAs(ca); + } + + const ca = rawConfig.ssl.certificateAuthorities; + if (ca) { + const parsed: string[] = []; + const paths = Array.isArray(ca) ? ca : [ca]; + if (paths.length > 0) { + for (const path of paths) { + parsed.push(readFile(path)); + } + addCAs(parsed); + } + } + + return { + key, + keyPassphrase, + certificate, + certificateAuthorities, + }; +}; + +const readFile = (file: string) => { + return readFileSync(file, 'utf8'); +}; diff --git a/x-pack/legacy/plugins/monitoring/server/plugin.js b/x-pack/legacy/plugins/monitoring/server/plugin.js index e26dd96dde1bf..163bc43945be1 100644 --- a/x-pack/legacy/plugins/monitoring/server/plugin.js +++ b/x-pack/legacy/plugins/monitoring/server/plugin.js @@ -11,6 +11,7 @@ import { instantiateClient } from './es_client/instantiate_client'; import { initMonitoringXpackInfo } from './init_monitoring_xpack_info'; import { initBulkUploader, registerCollectors } from './kibana_monitoring'; import { registerMonitoringCollection } from './telemetry_collection'; +import { parseElasticsearchConfig } from './es_client/parse_elasticsearch_config'; export class Plugin { setup(core, plugins) { @@ -36,6 +37,12 @@ export class Plugin { * fetch methods and uploads to the ES monitoring bulk endpoint */ const xpackMainPlugin = plugins.xpack_main; + + /* + * Parse the Elasticsearch config and read any certificates/keys if necessary + */ + const elasticsearchConfig = parseElasticsearchConfig(config); + xpackMainPlugin.status.once('green', async () => { // first time xpack_main turns green /* @@ -47,7 +54,7 @@ export class Plugin { await instantiateClient({ log: core.log, events: core.events, - config, + elasticsearchConfig, elasticsearchPlugin: plugins.elasticsearch, }); // Instantiate the dedicated ES client await initMonitoringXpackInfo({ diff --git a/x-pack/legacy/plugins/monitoring/ui_exports.js b/x-pack/legacy/plugins/monitoring/ui_exports.js index ba659aa74f10c..2b5ea21a2bb45 100644 --- a/x-pack/legacy/plugins/monitoring/ui_exports.js +++ b/x-pack/legacy/plugins/monitoring/ui_exports.js @@ -6,6 +6,7 @@ import { i18n } from '@kbn/i18n'; import { resolve } from 'path'; +import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/utils'; /** * Configuration of dependency objects for the UI, which are needed for the @@ -26,6 +27,7 @@ export const getUiExports = () => ({ euiIconType: 'monitoringApp', linkToLastSubUrl: false, main: 'plugins/monitoring/monitoring', + category: DEFAULT_APP_CATEGORIES.management, }, injectDefaultVars(server) { const config = server.config(); diff --git a/x-pack/legacy/plugins/reporting/common/cancellation_token.test.ts b/x-pack/legacy/plugins/reporting/common/cancellation_token.test.ts index eac9b6a1a7f35..5f820f460753e 100644 --- a/x-pack/legacy/plugins/reporting/common/cancellation_token.test.ts +++ b/x-pack/legacy/plugins/reporting/common/cancellation_token.test.ts @@ -46,13 +46,4 @@ describe('Cancellation Token', () => { expect(onCancelled).toBeCalled(); }); - - it('throws an error when the callback is not a function', () => { - const cancellationToken = new CancellationToken(); - - expect(() => { - // @ts-ignore - cancellationToken.on('cool!'); - }).toThrowError('Expected callback to be a function'); - }); }); diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.ts b/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.ts index ae603d93245a3..83bcc90ba92ed 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.ts @@ -5,13 +5,18 @@ */ import { i18n } from '@kbn/i18n'; -import { ExecuteJobFactory, ESQueueWorkerExecuteFn, ServerFacade } from '../../../types'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { KibanaRequest } from '../../../../../../../src/core/server'; +import { + ExecuteJobFactory, + ESQueueWorkerExecuteFn, + FieldFormats, + ServerFacade, +} from '../../../types'; import { CSV_JOB_TYPE, PLUGIN_ID } from '../../../common/constants'; import { cryptoFactory, LevelLogger } from '../../../server/lib'; import { JobDocPayloadDiscoverCsv } from '../types'; -// @ts-ignore untyped module TODO import { createGenerateCsv } from './lib/generate_csv'; -// @ts-ignore untyped module TODO import { fieldFormatMapFactory } from './lib/field_format_map'; export const executeJobFactory: ExecuteJobFactory { - const fieldFormats = await server.fieldFormatServiceFactory(uiConfig); + const fieldFormats = (await server.fieldFormatServiceFactory(uiConfig)) as FieldFormats; return fieldFormatMapFactory(indexPatternSavedObject, fieldFormats); })(), (async () => { diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/check_cells_for_formulas.test.ts b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/check_cells_for_formulas.test.ts index 31f5a91e5a57b..972ca1777bd73 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/check_cells_for_formulas.test.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/check_cells_for_formulas.test.ts @@ -3,6 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ + import { checkIfRowsHaveFormulas } from './check_cells_for_formulas'; const formulaValues = ['=', '+', '-', '@']; diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/escape_value.js b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/escape_value.js deleted file mode 100644 index 4031191c2f812..0000000000000 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/escape_value.js +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -const nonAlphaNumRE = /[^a-zA-Z0-9]/; -const allDoubleQuoteRE = /"/g; - -export function createEscapeValue(quoteValues) { - return function escapeValue(val) { - if (quoteValues && nonAlphaNumRE.test(val)) { - return `"${val.replace(allDoubleQuoteRE, '""')}"`; - } - return val; - }; -} diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/escape_value.js b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/escape_value.test.ts similarity index 91% rename from x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/escape_value.js rename to x-pack/legacy/plugins/reporting/export_types/csv/server/lib/escape_value.test.ts index 02dc694e1555b..64b021a2aeea8 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/escape_value.js +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/escape_value.test.ts @@ -5,11 +5,11 @@ */ import expect from '@kbn/expect'; -import { createEscapeValue } from '../escape_value'; +import { createEscapeValue } from './escape_value'; describe('escapeValue', function() { describe('quoteValues is true', function() { - let escapeValue; + let escapeValue: (val: string) => string; beforeEach(function() { escapeValue = createEscapeValue(true); }); @@ -44,7 +44,7 @@ describe('escapeValue', function() { }); describe('quoteValues is false', function() { - let escapeValue; + let escapeValue: (val: string) => string; beforeEach(function() { escapeValue = createEscapeValue(false); }); diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/escape_value.ts b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/escape_value.ts new file mode 100644 index 0000000000000..563de563350e9 --- /dev/null +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/escape_value.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { RawValue } from './types'; + +const nonAlphaNumRE = /[^a-zA-Z0-9]/; +const allDoubleQuoteRE = /"/g; + +export function createEscapeValue(quoteValues: boolean): (val: RawValue) => string { + return function escapeValue(val: RawValue) { + if (val && typeof val === 'string') { + if (quoteValues && nonAlphaNumRE.test(val)) { + return `"${val.replace(allDoubleQuoteRE, '""')}"`; + } + } + + return val == null ? '' : val.toString(); + }; +} diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/field_format_map.js b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/field_format_map.test.ts similarity index 83% rename from x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/field_format_map.js rename to x-pack/legacy/plugins/reporting/export_types/csv/server/lib/field_format_map.test.ts index 5f6bc32f10d8b..b3384dd6ca51d 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/field_format_map.js +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/field_format_map.test.ts @@ -5,13 +5,13 @@ */ import expect from '@kbn/expect'; - -import { FieldFormatsService } from '../../../../../../../../../src/legacy/ui/field_formats/mixin/field_formats_service'; +import { FieldFormatsService } from '../../../../../../../../src/legacy/ui/field_formats/mixin/field_formats_service'; // Reporting uses an unconventional directory structure so the linter marks this as a violation // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { BytesFormat, NumberFormat } from '../../../../../../../../../src/plugins/data/server'; +import { BytesFormat, NumberFormat } from '../../../../../../../../src/plugins/data/server'; +import { fieldFormatMapFactory } from './field_format_map'; -import { fieldFormatMapFactory } from '../field_format_map'; +type ConfigValue = { number: { id: string; params: {} } } | string; describe('field format map', function() { const indexPatternSavedObject = { @@ -26,12 +26,12 @@ describe('field format map', function() { fieldFormatMap: '{"field1":{"id":"bytes","params":{"pattern":"0,0.[0]b"}}}', }, }; - const configMock = {}; + const configMock: Record = {}; configMock['format:defaultTypeMap'] = { number: { id: 'number', params: {} }, }; configMock['format:number:defaultPattern'] = '0,0.[000]'; - const getConfig = key => configMock[key]; + const getConfig = (key: string) => configMock[key]; const testValue = '4000'; const fieldFormats = new FieldFormatsService([BytesFormat, NumberFormat], getConfig); diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/field_format_map.js b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/field_format_map.ts similarity index 71% rename from x-pack/legacy/plugins/reporting/export_types/csv/server/lib/field_format_map.js rename to x-pack/legacy/plugins/reporting/export_types/csv/server/lib/field_format_map.ts index 17b7361ca07b0..d013b5e75ee4d 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/field_format_map.js +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/field_format_map.ts @@ -5,6 +5,16 @@ */ import _ from 'lodash'; +import { FieldFormats } from '../../../../types'; + +interface IndexPatternSavedObject { + attributes: { + fieldFormatMap: string; + }; + id: string; + type: string; + version: string; +} /** * Create a map of FieldFormat instances for index pattern fields @@ -13,10 +23,13 @@ import _ from 'lodash'; * @param {FieldFormatsService} fieldFormats * @return {Map} key: field name, value: FieldFormat instance */ -export function fieldFormatMapFactory(indexPatternSavedObject, fieldFormats) { +export function fieldFormatMapFactory( + indexPatternSavedObject: IndexPatternSavedObject, + fieldFormats: FieldFormats +) { const formatsMap = new Map(); - //Add FieldFormat instances for fields with custom formatters + // Add FieldFormat instances for fields with custom formatters if (_.has(indexPatternSavedObject, 'attributes.fieldFormatMap')) { const fieldFormatMap = JSON.parse(indexPatternSavedObject.attributes.fieldFormatMap); Object.keys(fieldFormatMap).forEach(fieldName => { @@ -28,9 +41,9 @@ export function fieldFormatMapFactory(indexPatternSavedObject, fieldFormats) { }); } - //Add default FieldFormat instances for all other fields + // Add default FieldFormat instances for all other fields const indexFields = JSON.parse(_.get(indexPatternSavedObject, 'attributes.fields', '[]')); - indexFields.forEach(field => { + indexFields.forEach((field: any) => { if (!formatsMap.has(field.name)) { formatsMap.set(field.name, fieldFormats.getDefaultInstance(field.type)); } diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/flatten_hit.js b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/flatten_hit.test.ts similarity index 95% rename from x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/flatten_hit.js rename to x-pack/legacy/plugins/reporting/export_types/csv/server/lib/flatten_hit.test.ts index 2169c7cab1236..1e06e78357399 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/flatten_hit.js +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/flatten_hit.test.ts @@ -5,12 +5,14 @@ */ import expect from '@kbn/expect'; -import { createFlattenHit } from '../flatten_hit'; +import { createFlattenHit } from './flatten_hit'; + +type Hit = Record; describe('flattenHit', function() { - let flattenHit; - let hit; - let metaFields; + let flattenHit: (hit: Hit) => Record; + let hit: Hit; + let metaFields: string[]; beforeEach(function() { const fields = [ diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/flatten_hit.js b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/flatten_hit.ts similarity index 66% rename from x-pack/legacy/plugins/reporting/export_types/csv/server/lib/flatten_hit.js rename to x-pack/legacy/plugins/reporting/export_types/csv/server/lib/flatten_hit.ts index b1387a3ef310c..328d49a27911c 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/flatten_hit.js +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/flatten_hit.ts @@ -6,9 +6,17 @@ import _ from 'lodash'; +type Hit = Record; +type FlattenHitFn = (hit: Hit) => Record; +type FlatHits = Record; + // TODO this logic should be re-used with Discover -export function createFlattenHit(fields, metaFields, conflictedTypesFields) { - const flattenSource = (flat, obj, keyPrefix) => { +export function createFlattenHit( + fields: string[], + metaFields: string[], + conflictedTypesFields: string[] +): FlattenHitFn { + const flattenSource = (flat: FlatHits, obj: object, keyPrefix = '') => { keyPrefix = keyPrefix ? keyPrefix + '.' : ''; _.forOwn(obj, (val, key) => { key = keyPrefix + key; @@ -31,17 +39,19 @@ export function createFlattenHit(fields, metaFields, conflictedTypesFields) { }); }; - const flattenMetaFields = (flat, hit) => { + const flattenMetaFields = (flat: Hit, hit: Hit) => { _.each(metaFields, meta => { if (meta === '_source') return; flat[meta] = hit[meta]; }); }; - const flattenFields = (flat, hitFields) => { + const flattenFields = (flat: FlatHits, hitFields: string[]) => { _.forOwn(hitFields, (val, key) => { - if (key[0] === '_' && !_.contains(metaFields, key)) return; - flat[key] = _.isArray(val) && val.length === 1 ? val[0] : val; + if (key) { + if (key[0] === '_' && !_.contains(metaFields, key)) return; + flat[key] = _.isArray(val) && val.length === 1 ? val[0] : val; + } }); }; diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/format_csv_values.js b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/format_csv_values.test.ts similarity index 92% rename from x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/format_csv_values.js rename to x-pack/legacy/plugins/reporting/export_types/csv/server/lib/format_csv_values.test.ts index 7401109ae8030..b38bad1179471 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/format_csv_values.js +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/format_csv_values.test.ts @@ -5,12 +5,12 @@ */ import expect from '@kbn/expect'; -import { createFormatCsvValues } from '../format_csv_values'; +import { createFormatCsvValues } from './format_csv_values'; describe('formatCsvValues', function() { const separator = ','; const fields = ['foo', 'bar']; - const mockEscapeValue = val => val; + const mockEscapeValue = (value: any, index: number, array: any[]) => value || ''; describe('with _source as one of the fields', function() { const formatsMap = new Map(); const formatCsvValues = createFormatCsvValues( @@ -62,7 +62,7 @@ describe('formatCsvValues', function() { describe('with field formats', function() { const mockFieldFormat = { - convert: val => String(val).toUpperCase(), + convert: (val: string) => String(val).toUpperCase(), }; const formatsMap = new Map(); formatsMap.set('bar', mockFieldFormat); diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/format_csv_values.js b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/format_csv_values.ts similarity index 72% rename from x-pack/legacy/plugins/reporting/export_types/csv/server/lib/format_csv_values.js rename to x-pack/legacy/plugins/reporting/export_types/csv/server/lib/format_csv_values.ts index 9083e8ce04f88..0bcf0fc31beae 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/format_csv_values.js +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/format_csv_values.ts @@ -5,9 +5,15 @@ */ import { isObject, isNull, isUndefined } from 'lodash'; +import { RawValue } from './types'; -export function createFormatCsvValues(escapeValue, separator, fields, formatsMap) { - return function formatCsvValues(values) { +export function createFormatCsvValues( + escapeValue: (value: RawValue, index: number, array: RawValue[]) => string, + separator: string, + fields: string[], + formatsMap: any +) { + return function formatCsvValues(values: Record) { return fields .map(field => { let value; @@ -29,7 +35,7 @@ export function createFormatCsvValues(escapeValue, separator, fields, formatsMap return formattedValue; }) .map(value => (isObject(value) ? JSON.stringify(value) : value)) - .map(value => value.toString()) + .map(value => (value ? value.toString() : value)) .map(escapeValue) .join(separator); }; diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/generate_csv.js b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/generate_csv.ts similarity index 89% rename from x-pack/legacy/plugins/reporting/export_types/csv/server/lib/generate_csv.js rename to x-pack/legacy/plugins/reporting/export_types/csv/server/lib/generate_csv.ts index c13d24022c40c..1986e68917ba8 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/generate_csv.js +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/generate_csv.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Logger } from '../../../../types'; +import { GenerateCsvParams, SavedSearchGeneratorResult } from '../../types'; import { createFlattenHit } from './flatten_hit'; import { createFormatCsvValues } from './format_csv_values'; import { createEscapeValue } from './escape_value'; @@ -11,7 +13,7 @@ import { createHitIterator } from './hit_iterator'; import { MaxSizeStringBuilder } from './max_size_string_builder'; import { checkIfRowsHaveFormulas } from './check_cells_for_formulas'; -export function createGenerateCsv(logger) { +export function createGenerateCsv(logger: Logger) { const hitIterator = createHitIterator(logger); return async function generateCsv({ @@ -23,12 +25,13 @@ export function createGenerateCsv(logger) { callEndpoint, cancellationToken, settings, - }) { + }: GenerateCsvParams): Promise { const escapeValue = createEscapeValue(settings.quoteValues); const builder = new MaxSizeStringBuilder(settings.maxSizeBytes); const header = `${fields.map(escapeValue).join(settings.separator)}\n`; if (!builder.tryAppend(header)) { return { + size: 0, content: '', maxSizeReached: true, }; @@ -49,6 +52,10 @@ export function createGenerateCsv(logger) { while (true) { const { done, value: hit } = await iterator.next(); + if (!hit) { + break; + } + if (done) { break; } diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/hit_iterator.ts b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/hit_iterator.test.ts similarity index 95% rename from x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/hit_iterator.ts rename to x-pack/legacy/plugins/reporting/export_types/csv/server/lib/hit_iterator.test.ts index 20e373a4168af..3765217de9285 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/hit_iterator.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/hit_iterator.test.ts @@ -6,9 +6,9 @@ import expect from '@kbn/expect'; import sinon from 'sinon'; -import { CancellationToken } from '../../../../../common/cancellation_token'; -import { Logger, ScrollConfig } from '../../../../../types'; -import { createHitIterator } from '../hit_iterator'; +import { CancellationToken } from '../../../../common/cancellation_token'; +import { Logger, ScrollConfig } from '../../../../types'; +import { createHitIterator } from './hit_iterator'; const mockLogger = { error: new Function(), diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/hit_iterator.ts b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/hit_iterator.ts index 68836c01369e3..90690b62ff4a4 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/hit_iterator.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/hit_iterator.ts @@ -3,8 +3,8 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { SearchParams, SearchResponse } from 'elasticsearch'; +import { SearchParams, SearchResponse } from 'elasticsearch'; import { i18n } from '@kbn/i18n'; import { CancellationToken, ScrollConfig, Logger } from '../../../../types'; diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/max_size_string_builder.js b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/max_size_string_builder.test.ts similarity index 97% rename from x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/max_size_string_builder.js rename to x-pack/legacy/plugins/reporting/export_types/csv/server/lib/max_size_string_builder.test.ts index ddc4299ebcb45..843ff82e7c4bc 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/max_size_string_builder.js +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/max_size_string_builder.test.ts @@ -5,7 +5,7 @@ */ import expect from '@kbn/expect'; -import { MaxSizeStringBuilder } from '../max_size_string_builder'; +import { MaxSizeStringBuilder } from './max_size_string_builder'; describe('MaxSizeStringBuilder', function() { describe('tryAppend', function() { diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/max_size_string_builder.js b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/max_size_string_builder.ts similarity index 83% rename from x-pack/legacy/plugins/reporting/export_types/csv/server/lib/max_size_string_builder.js rename to x-pack/legacy/plugins/reporting/export_types/csv/server/lib/max_size_string_builder.ts index b4bdfcc13b3f6..70bc2030d290c 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/max_size_string_builder.js +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/max_size_string_builder.ts @@ -5,13 +5,17 @@ */ export class MaxSizeStringBuilder { - constructor(maxSizeBytes) { + private _buffer: Buffer; + private _size: number; + private _maxSize: number; + + constructor(maxSizeBytes: number) { this._buffer = Buffer.alloc(maxSizeBytes); this._size = 0; this._maxSize = maxSizeBytes; } - tryAppend(str) { + tryAppend(str: string) { const byteLength = Buffer.byteLength(str); if (this._size + byteLength <= this._maxSize) { this._buffer.write(str, this._size); diff --git a/x-pack/legacy/plugins/searchprofiler/server/np_ready/lib/index.ts b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/types.d.ts similarity index 80% rename from x-pack/legacy/plugins/searchprofiler/server/np_ready/lib/index.ts rename to x-pack/legacy/plugins/reporting/export_types/csv/server/lib/types.d.ts index f2c070fd44b6e..b4dc743664995 100644 --- a/x-pack/legacy/plugins/searchprofiler/server/np_ready/lib/index.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/types.d.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { checkLicense } from './check_license'; +export type RawValue = string | object | null | undefined; diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/types.d.ts b/x-pack/legacy/plugins/reporting/export_types/csv/types.d.ts index 9d1eb9cc1bd4d..842330fa7c93f 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/types.d.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv/types.d.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { CancellationToken } from '../../common/cancellation_token'; import { JobDocPayload, JobParamPostPayload, ConditionalHeaders, RequestFacade } from '../../types'; interface DocValueField { @@ -37,3 +38,75 @@ export interface JobDocPayloadDiscoverCsv extends JobDocPayload; + stored_fields: string[]; + } + | any; +} + +type EndpointCaller = (method: string, params: any) => Promise; + +type FormatsMap = Map< + string, + { + id: string; + params: { + pattern: string; + }; + } +>; + +export interface SavedSearchGeneratorResult { + content: string; + size: number; + maxSizeReached: boolean; + csvContainsFormulas?: boolean; +} + +export interface CsvResultFromSearch { + type: string; + result: SavedSearchGeneratorResult; +} + +export interface GenerateCsvParams { + searchRequest: SearchRequest; + callEndpoint: EndpointCaller; + fields: string[]; + formatsMap: FormatsMap; + metaFields: string[]; + conflictedTypesFields: string[]; + cancellationToken: CancellationToken; + settings: { + separator: string; + quoteValues: boolean; + timezone: string | null; + maxSizeBytes: number; + scroll: { duration: string; size: number }; + checkForFormulas?: boolean; + }; +} diff --git a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/execute_job.ts b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/execute_job.ts index e161d7afc84e2..69d9a173d40b3 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/execute_job.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/execute_job.ts @@ -18,13 +18,8 @@ import { CSV_FROM_SAVEDOBJECT_JOB_TYPE, PLUGIN_ID, } from '../../../common/constants'; -import { - CsvResultFromSearch, - JobParamsPanelCsv, - SearchPanel, - JobDocPayloadPanelCsv, - FakeRequest, -} from '../types'; +import { CsvResultFromSearch } from '../../csv/types'; +import { JobParamsPanelCsv, SearchPanel, JobDocPayloadPanelCsv, FakeRequest } from '../types'; import { createGenerateCsv } from './lib'; export const executeJobFactory: ExecuteJobFactory { const { savedObjects, uiSettingsServiceFactory } = server; - const savedObjectsClient = savedObjects.getScopedSavedObjectsClient(req.getRawRequest()); + const savedObjectsClient = savedObjects.getScopedSavedObjectsClient( + KibanaRequest.from(req.getRawRequest()) + ); const { indexPatternSavedObjectId, timerange } = searchPanel; const savedSearchObjectAttr = searchPanel.attributes as SavedSearchObjectAttributes; const { indexPatternSavedObject } = await getDataSource( diff --git a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/types.d.ts b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/types.d.ts index b6dd174410c11..6a7d5f336e238 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/types.d.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/types.d.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CancellationToken } from '../../common/cancellation_token'; import { JobParamPostPayload, JobDocPayload, ServerFacade } from '../../types'; export interface FakeRequest { @@ -144,75 +143,6 @@ export interface SearchSource { filter: any[]; } -export interface SearchRequest { - index: string; - body: - | { - _source: { - excludes: string[]; - includes: string[]; - }; - docvalue_fields: string[]; - query: - | { - bool: { - filter: any[]; - must_not: any[]; - should: any[]; - must: any[]; - }; - } - | any; - script_fields: any; - sort: Array<{ - [key: string]: { - order: string; - }; - }>; - stored_fields: string[]; - } - | any; -} - -export interface SavedSearchGeneratorResult { - content: string; - maxSizeReached: boolean; - size: number; -} - -export interface CsvResultFromSearch { - type: string; - result: SavedSearchGeneratorResult; -} - -type EndpointCaller = (method: string, params: any) => Promise; -type FormatsMap = Map< - string, - { - id: string; - params: { - pattern: string; - }; - } ->; - -export interface GenerateCsvParams { - searchRequest: SearchRequest; - callEndpoint: EndpointCaller; - fields: string[]; - formatsMap: FormatsMap; - metaFields: string[]; // FIXME not sure what this is for - conflictedTypesFields: string[]; // FIXME not sure what this is for - cancellationToken: CancellationToken; - settings: { - separator: string; - quoteValues: boolean; - timezone: string | null; - maxSizeBytes: number; - scroll: { duration: string; size: number }; - }; -} - /* * These filter types are stub types to help ensure things get passed to * non-Typescript functions in the right order. An actual structure is not diff --git a/x-pack/legacy/plugins/reporting/server/lib/create_queue.ts b/x-pack/legacy/plugins/reporting/server/lib/create_queue.ts index 5cf760250ec0e..4a9b0c7cd2ebb 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/create_queue.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/create_queue.ts @@ -15,7 +15,6 @@ import { import { Esqueue } from './esqueue'; import { createWorkerFactory } from './create_worker'; import { LevelLogger } from './level_logger'; -// @ts-ignore import { createTaggedLogger } from './create_tagged_logger'; // TODO remove createTaggedLogger once esqueue is removed interface CreateQueueFactoryOpts { diff --git a/x-pack/legacy/plugins/reporting/server/lib/create_tagged_logger.js b/x-pack/legacy/plugins/reporting/server/lib/create_tagged_logger.ts similarity index 79% rename from x-pack/legacy/plugins/reporting/server/lib/create_tagged_logger.js rename to x-pack/legacy/plugins/reporting/server/lib/create_tagged_logger.ts index 838024f801b67..40a1cd8203d2f 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/create_tagged_logger.js +++ b/x-pack/legacy/plugins/reporting/server/lib/create_tagged_logger.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { ServerFacade } from '../../types'; + /** * @function taggedLogger * @param {string} message @@ -17,8 +19,8 @@ * @param {string[]} tags - tags to always be passed into the `logger` function * @returns taggedLogger */ -export function createTaggedLogger(server, tags) { - return (msg, additionalTags = []) => { +export function createTaggedLogger(server: ServerFacade, tags: string[]) { + return (msg: string, additionalTags = []) => { server.log([...tags, ...additionalTags], msg); }; } diff --git a/x-pack/legacy/plugins/reporting/server/lib/jobs_query.js b/x-pack/legacy/plugins/reporting/server/lib/jobs_query.ts similarity index 70% rename from x-pack/legacy/plugins/reporting/server/lib/jobs_query.js rename to x-pack/legacy/plugins/reporting/server/lib/jobs_query.ts index 2162ecedb8371..0c16f780c34ac 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/jobs_query.js +++ b/x-pack/legacy/plugins/reporting/server/lib/jobs_query.ts @@ -5,21 +5,48 @@ */ import { get } from 'lodash'; +import { ServerFacade, JobSource } from '../../types'; const defaultSize = 10; -export function jobsQueryFactory(server) { +interface QueryBody { + size?: number; + from?: number; + _source?: { + excludes: string[]; + }; + query: { + constant_score: { + filter: { + bool: { + must: Array>; + }; + }; + }; + }; +} + +interface GetOpts { + includeContent?: boolean; +} + +interface CountAggResult { + count: number; +} + +export function jobsQueryFactory(server: ServerFacade) { const index = server.config().get('xpack.reporting.index'); + // @ts-ignore `errors` does not exist on type Cluster const { callWithInternalUser, errors: esErrors } = server.plugins.elasticsearch.getCluster( 'admin' ); - function getUsername(user) { + function getUsername(user: any) { return get(user, 'username', false); } - function execQuery(queryType, body) { - const defaultBody = { + function execQuery(queryType: string, body: QueryBody) { + const defaultBody: Record = { search: { _source: { excludes: ['output.content'], @@ -42,15 +69,17 @@ export function jobsQueryFactory(server) { }); } - function getHits(query) { + type Result = number; + + function getHits(query: Promise) { return query.then(res => get(res, 'hits.hits', [])); } return { - list(jobTypes, user, page = 0, size = defaultSize, jobIds) { + list(jobTypes: string[], user: any, page = 0, size = defaultSize, jobIds: string[] | null) { const username = getUsername(user); - const body = { + const body: QueryBody = { size, from: size * page, query: { @@ -73,10 +102,10 @@ export function jobsQueryFactory(server) { return getHits(execQuery('search', body)); }, - count(jobTypes, user) { + count(jobTypes: string[], user: any) { const username = getUsername(user); - const body = { + const body: QueryBody = { query: { constant_score: { filter: { @@ -88,18 +117,18 @@ export function jobsQueryFactory(server) { }, }; - return execQuery('count', body).then(doc => { + return execQuery('count', body).then((doc: CountAggResult) => { if (!doc) return 0; return doc.count; }); }, - get(user, id, opts = {}) { + get(user: any, id: string, opts: GetOpts = {}): Promise | void> { if (!id) return Promise.resolve(); const username = getUsername(user); - const body = { + const body: QueryBody = { query: { constant_score: { filter: { diff --git a/x-pack/legacy/plugins/reporting/server/routes/jobs.ts b/x-pack/legacy/plugins/reporting/server/routes/jobs.ts index a0be15d60f316..6084ca613d10e 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/jobs.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/jobs.ts @@ -82,17 +82,19 @@ export function registerJobInfoRoutes( const { docId } = request.params; return jobsQuery.get(request.pre.user, docId, { includeContent: true }).then( - ({ _source: job }: JobSource): JobDocOutput => { - if (!job) { + (result): JobDocOutput => { + if (!result) { throw boom.notFound(); } + const { + _source: { jobtype: jobType, output: jobOutput }, + } = result; - const { jobtype: jobType } = job; if (!request.pre.management.jobTypes.includes(jobType)) { throw boom.unauthorized(`Sorry, you are not authorized to download ${jobType} reports`); } - return job.output; + return jobOutput; } ); }, @@ -107,26 +109,25 @@ export function registerJobInfoRoutes( const request = makeRequestFacade(legacyRequest); const { docId } = request.params; - return jobsQuery - .get(request.pre.user, docId) - .then(({ _source: job }: JobSource): JobSource['_source'] => { - if (!job) { - throw boom.notFound(); - } + return jobsQuery.get(request.pre.user, docId).then((result): JobSource['_source'] => { + if (!result) { + throw boom.notFound(); + } - const { jobtype: jobType, payload } = job; - if (!request.pre.management.jobTypes.includes(jobType)) { - throw boom.unauthorized(`Sorry, you are not authorized to view ${jobType} info`); - } + const { _source: job } = result; + const { jobtype: jobType, payload: jobPayload } = job; + if (!request.pre.management.jobTypes.includes(jobType)) { + throw boom.unauthorized(`Sorry, you are not authorized to view ${jobType} info`); + } - return { - ...job, - payload: { - ...payload, - headers: undefined, - }, - }; - }); + return { + ...job, + payload: { + ...jobPayload, + headers: undefined, + }, + }; + }); }, }); diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.js b/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts similarity index 60% rename from x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.js rename to x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts index 79a5f1a4f6f5f..eb473e0bc76d4 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.js +++ b/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.ts @@ -4,16 +4,25 @@ * you may not use this file except in compliance with the Elastic License. */ -import boom from 'boom'; +import Boom from 'boom'; +import { Legacy } from 'kibana'; +import { AuthenticatedUser } from '../../../../../../plugins/security/server'; import { getUserFactory } from '../../lib/get_user'; +import { ServerFacade } from '../../../types'; const superuserRole = 'superuser'; -export const authorizedUserPreRoutingFactory = function authorizedUserPreRoutingFn(server) { +export type PreRoutingFunction = ( + request: Legacy.Request +) => Promise | AuthenticatedUser | null>; + +export const authorizedUserPreRoutingFactory = function authorizedUserPreRoutingFn( + server: ServerFacade +) { const getUser = getUserFactory(server); const config = server.config(); - return async function authorizedUserPreRouting(request) { + return async function authorizedUserPreRouting(request: Legacy.Request) { const xpackInfo = server.plugins.xpack_main.info; if (!xpackInfo || !xpackInfo.isAvailable()) { @@ -21,7 +30,7 @@ export const authorizedUserPreRoutingFactory = function authorizedUserPreRouting ['reporting', 'authorizedUserPreRouting', 'debug'], 'Unable to authorize user before xpack info is available.' ); - return boom.notFound(); + return Boom.notFound(); } const security = xpackInfo.feature('security'); @@ -32,12 +41,15 @@ export const authorizedUserPreRoutingFactory = function authorizedUserPreRouting const user = await getUser(request); if (!user) { - return boom.unauthorized(`Sorry, you aren't authenticated`); + return Boom.unauthorized(`Sorry, you aren't authenticated`); } - const authorizedRoles = [superuserRole, ...config.get('xpack.reporting.roles.allow')]; + const authorizedRoles = [ + superuserRole, + ...(config.get('xpack.reporting.roles.allow') as string[]), + ]; if (!user.roles.find(role => authorizedRoles.includes(role))) { - return boom.forbidden(`Sorry, you don't have access to Reporting`); + return Boom.forbidden(`Sorry, you don't have access to Reporting`); } return user; diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/reporting_feature_pre_routing.js b/x-pack/legacy/plugins/reporting/server/routes/lib/reporting_feature_pre_routing.ts similarity index 76% rename from x-pack/legacy/plugins/reporting/server/routes/lib/reporting_feature_pre_routing.js rename to x-pack/legacy/plugins/reporting/server/routes/lib/reporting_feature_pre_routing.ts index 92973e3d0b422..6efac818981ef 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/lib/reporting_feature_pre_routing.js +++ b/x-pack/legacy/plugins/reporting/server/routes/lib/reporting_feature_pre_routing.ts @@ -5,14 +5,20 @@ */ import Boom from 'boom'; +import { Legacy } from 'kibana'; +import { ServerFacade } from '../../../types'; -export const reportingFeaturePreRoutingFactory = function reportingFeaturePreRoutingFn(server) { +export type GetReportingFeatureIdFn = (request: Legacy.Request) => string; + +export const reportingFeaturePreRoutingFactory = function reportingFeaturePreRoutingFn( + server: ServerFacade +) { const xpackMainPlugin = server.plugins.xpack_main; const pluginId = 'reporting'; // License checking and enable/disable logic - return function reportingFeaturePreRouting(getReportingFeatureId) { - return function licensePreRouting(request) { + return function reportingFeaturePreRouting(getReportingFeatureId: GetReportingFeatureIdFn) { + return function licensePreRouting(request: Legacy.Request) { const licenseCheckResults = xpackMainPlugin.info.feature(pluginId).getLicenseCheckResults(); const reportingFeatureId = getReportingFeatureId(request); const reportingFeature = licenseCheckResults[reportingFeatureId]; diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/route_config_factories.ts b/x-pack/legacy/plugins/reporting/server/routes/lib/route_config_factories.ts index 29ded68d403c5..caf24bf64f602 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/lib/route_config_factories.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/lib/route_config_factories.ts @@ -6,11 +6,10 @@ import Joi from 'joi'; import { CSV_FROM_SAVEDOBJECT_JOB_TYPE } from '../../../common/constants'; -import { ServerFacade, RequestFacade } from '../../../types'; -// @ts-ignore +import { ServerFacade } from '../../../types'; import { authorizedUserPreRoutingFactory } from './authorized_user_pre_routing'; -// @ts-ignore import { reportingFeaturePreRoutingFactory } from './reporting_feature_pre_routing'; +import { GetReportingFeatureIdFn } from './reporting_feature_pre_routing'; const API_TAG = 'api'; @@ -22,19 +21,16 @@ export interface RouteConfigFactory { }; } -type GetFeatureFunction = (request: RequestFacade) => any; -type PreRoutingFunction = (getFeatureId?: GetFeatureFunction) => any; - export type GetRouteConfigFactoryFn = ( - getFeatureId?: GetFeatureFunction | undefined + getFeatureId?: GetReportingFeatureIdFn ) => RouteConfigFactory; export function getRouteConfigFactoryReportingPre(server: ServerFacade): GetRouteConfigFactoryFn { - const authorizedUserPreRouting: PreRoutingFunction = authorizedUserPreRoutingFactory(server); - const reportingFeaturePreRouting: PreRoutingFunction = reportingFeaturePreRoutingFactory(server); + const authorizedUserPreRouting = authorizedUserPreRoutingFactory(server); + const reportingFeaturePreRouting = reportingFeaturePreRoutingFactory(server); - return (getFeatureId?: GetFeatureFunction): RouteConfigFactory => { - const preRouting = [{ method: authorizedUserPreRouting, assign: 'user' }]; + return (getFeatureId?: GetReportingFeatureIdFn): RouteConfigFactory => { + const preRouting: any[] = [{ method: authorizedUserPreRouting, assign: 'user' }]; if (getFeatureId) { preRouting.push(reportingFeaturePreRouting(getFeatureId)); } diff --git a/x-pack/legacy/plugins/reporting/types.d.ts b/x-pack/legacy/plugins/reporting/types.d.ts index 9fae60afee4e8..47f384250dd53 100644 --- a/x-pack/legacy/plugins/reporting/types.d.ts +++ b/x-pack/legacy/plugins/reporting/types.d.ts @@ -355,3 +355,9 @@ export interface InterceptedRequest { frameId: string; resourceType: string; } + +export interface FieldFormats { + getConfig: number; + getInstance: (config: any) => any; + getDefaultInstance: (key: string) => any; +} diff --git a/x-pack/legacy/plugins/searchprofiler/README.md b/x-pack/legacy/plugins/searchprofiler/README.md deleted file mode 100644 index 1dec1bb4e5450..0000000000000 --- a/x-pack/legacy/plugins/searchprofiler/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# Profiler - -A UI for the query and aggregation profiler in Elasticsearch - -## Development - -Assuming you've checked out x-plugins next to kibana... - -- Run `yarn kbn bootstrap` -- Run `yarn start` to watch for and sync files on change -- Open a new terminal to run Kibana - use `yarn start` to launch it in dev mode - - Kibana will automatically restart as files are synced - - If you need debugging output, run `DEBUG=reporting yarn start` instead - -If you have installed this somewhere other than via x-plugins, and next to the kibana repo, you'll need to change the `pathToKibana` setting in `gulpfile.js` - -## Testing - -To run the server tests, change into `x-plugins/kibana` and run: - -```bash -mocha --debug --compilers js:@babel/register plugins/profiler/**/__tests__/**/*.js -``` - - ---kbnServer.tests_bundle.pluginId diff --git a/x-pack/legacy/plugins/searchprofiler/index.ts b/x-pack/legacy/plugins/searchprofiler/index.ts index 834f331cd7bf4..fab2e43847348 100644 --- a/x-pack/legacy/plugins/searchprofiler/index.ts +++ b/x-pack/legacy/plugins/searchprofiler/index.ts @@ -5,12 +5,10 @@ */ import { resolve } from 'path'; -import Boom from 'boom'; -import { CoreSetup } from 'src/core/server'; -import { Server } from 'src/legacy/server/kbn_server'; -import { LegacySetup } from './server/np_ready/types'; -import { plugin } from './server/np_ready'; +// TODO: +// Until we can process SCSS in new platform, this part of Searchprofiler +// legacy must remain here. export const searchprofiler = (kibana: any) => { const publicSrc = resolve(__dirname, 'public'); @@ -22,43 +20,8 @@ export const searchprofiler = (kibana: any) => { publicDir: publicSrc, uiExports: { - // NP Ready - devTools: [`${publicSrc}/legacy`], - styleSheetPaths: `${publicSrc}/np_ready/application/index.scss`, - // Legacy - home: ['plugins/searchprofiler/register_feature'], - }, - init(server: Server) { - const serverPlugin = plugin(); - const thisPlugin = this; - - const commonRouteConfig = { - pre: [ - function forbidApiAccess() { - const licenseCheckResults = server.plugins.xpack_main.info - .feature(thisPlugin.id) - .getLicenseCheckResults(); - if (licenseCheckResults.showAppLink && licenseCheckResults.enableAppLink) { - return null; - } else { - throw Boom.forbidden(licenseCheckResults.message); - } - }, - ], - }; - - const legacySetup: LegacySetup = { - route: (args: Parameters[0]) => server.route(args), - plugins: { - __LEGACY: { - thisPlugin, - xpackMain: server.plugins.xpack_main, - elasticsearch: server.plugins.elasticsearch, - commonRouteConfig, - }, - }, - }; - serverPlugin.setup({} as CoreSetup, legacySetup); + styleSheetPaths: `${publicSrc}/index.scss`, }, + init() {}, }); }; diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/index.scss b/x-pack/legacy/plugins/searchprofiler/public/index.scss similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/index.scss rename to x-pack/legacy/plugins/searchprofiler/public/index.scss diff --git a/x-pack/legacy/plugins/searchprofiler/public/legacy.ts b/x-pack/legacy/plugins/searchprofiler/public/legacy.ts deleted file mode 100644 index b767705e025a0..0000000000000 --- a/x-pack/legacy/plugins/searchprofiler/public/legacy.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -/* eslint-disable @kbn/eslint/no-restricted-paths */ -import { npSetup } from 'ui/new_platform'; -import { I18nContext } from 'ui/i18n'; -// @ts-ignore -import { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; -// @ts-ignore -import { formatAngularHttpError } from 'ui/notify/lib'; -import 'ui/autoload/all'; - -import { plugin } from './np_ready'; - -const pluginInstance = plugin({} as any); - -pluginInstance.setup(npSetup.core, { - ...npSetup.plugins, - __LEGACY: { - I18nContext, - licenseEnabled: xpackInfo.get('features.searchprofiler.enableAppLink'), - notifications: npSetup.core.notifications.toasts, - formatAngularHttpError, - }, -}); diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/plugin.ts b/x-pack/legacy/plugins/searchprofiler/public/np_ready/plugin.ts deleted file mode 100644 index f2acc97e55a70..0000000000000 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/plugin.ts +++ /dev/null @@ -1,62 +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 { - Plugin, - CoreStart, - CoreSetup, - PluginInitializerContext, - ToastsStart, -} from 'src/core/public'; - -import { DevToolsSetup } from '../../../../../../src/plugins/dev_tools/public'; - -export class SearchProfilerUIPlugin implements Plugin { - constructor(ctx: PluginInitializerContext) {} - - async setup( - core: CoreSetup, - plugins: { - __LEGACY: { - I18nContext: any; - licenseEnabled: boolean; - notifications: ToastsStart; - formatAngularHttpError: any; - }; - dev_tools: DevToolsSetup; - } - ) { - const { http } = core; - const { - __LEGACY: { I18nContext, licenseEnabled, notifications, formatAngularHttpError }, - dev_tools, - } = plugins; - dev_tools.register({ - id: 'searchprofiler', - title: i18n.translate('xpack.searchProfiler.pageDisplayName', { - defaultMessage: 'Search Profiler', - }), - order: 5, - enableRouting: false, - async mount(ctx, params) { - const { boot } = await import('./application/boot'); - return boot({ - http, - licenseEnabled, - el: params.element, - I18nContext, - notifications, - formatAngularHttpError, - }); - }, - }); - } - - async start(core: CoreStart, plugins: any) {} - - async stop() {} -} diff --git a/x-pack/legacy/plugins/searchprofiler/public/register_feature.js b/x-pack/legacy/plugins/searchprofiler/public/register_feature.js deleted file mode 100644 index 2a4218e2527c7..0000000000000 --- a/x-pack/legacy/plugins/searchprofiler/public/register_feature.js +++ /dev/null @@ -1,28 +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 { - FeatureCatalogueRegistryProvider, - FeatureCatalogueCategory, -} from 'ui/registry/feature_catalogue'; - -import { i18n } from '@kbn/i18n'; - -FeatureCatalogueRegistryProvider.register(() => { - return { - id: 'searchprofiler', - title: i18n.translate('xpack.searchProfiler.registryProviderTitle', { - defaultMessage: 'Search Profiler', - }), - description: i18n.translate('xpack.searchProfiler.registryProviderDescription', { - defaultMessage: 'Quickly check the performance of any Elasticsearch query.', - }), - icon: 'searchProfilerApp', - path: '/app/kibana#/dev_tools/searchprofiler', - showOnHomePage: false, - category: FeatureCatalogueCategory.ADMIN, - }; -}); diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/_index.scss b/x-pack/legacy/plugins/searchprofiler/public/styles/_index.scss similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/_index.scss rename to x-pack/legacy/plugins/searchprofiler/public/styles/_index.scss diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/_mixins.scss b/x-pack/legacy/plugins/searchprofiler/public/styles/_mixins.scss similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/_mixins.scss rename to x-pack/legacy/plugins/searchprofiler/public/styles/_mixins.scss diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/components/_highlight_details_flyout.scss b/x-pack/legacy/plugins/searchprofiler/public/styles/components/_highlight_details_flyout.scss similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/components/_highlight_details_flyout.scss rename to x-pack/legacy/plugins/searchprofiler/public/styles/components/_highlight_details_flyout.scss diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/components/_percentage_badge.scss b/x-pack/legacy/plugins/searchprofiler/public/styles/components/_percentage_badge.scss similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/components/_percentage_badge.scss rename to x-pack/legacy/plugins/searchprofiler/public/styles/components/_percentage_badge.scss diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/components/_profile_tree.scss b/x-pack/legacy/plugins/searchprofiler/public/styles/components/_profile_tree.scss similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/components/_profile_tree.scss rename to x-pack/legacy/plugins/searchprofiler/public/styles/components/_profile_tree.scss diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/containers/_main.scss b/x-pack/legacy/plugins/searchprofiler/public/styles/containers/_main.scss similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/containers/_main.scss rename to x-pack/legacy/plugins/searchprofiler/public/styles/containers/_main.scss diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/containers/_profile_query_editor.scss b/x-pack/legacy/plugins/searchprofiler/public/styles/containers/_profile_query_editor.scss similarity index 100% rename from x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/containers/_profile_query_editor.scss rename to x-pack/legacy/plugins/searchprofiler/public/styles/containers/_profile_query_editor.scss diff --git a/x-pack/legacy/plugins/searchprofiler/server/np_ready/lib/check_license.test.ts b/x-pack/legacy/plugins/searchprofiler/server/np_ready/lib/check_license.test.ts deleted file mode 100644 index 1b8155221cb9d..0000000000000 --- a/x-pack/legacy/plugins/searchprofiler/server/np_ready/lib/check_license.test.ts +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { set } from 'lodash'; -import { checkLicense } from './check_license'; - -describe('check_license', () => { - let mockLicenseInfo: any; - beforeEach(() => (mockLicenseInfo = {})); - - describe('license information is not available', () => { - beforeEach(() => (mockLicenseInfo.isAvailable = () => false)); - - it('should set showLinks to true', () => { - expect(checkLicense(mockLicenseInfo).showAppLink).to.be(true); - }); - - it('should set enableLinks to false', () => { - expect(checkLicense(mockLicenseInfo).enableAppLink).to.be(false); - }); - }); - - describe('license information is available', () => { - beforeEach(() => { - mockLicenseInfo.isAvailable = () => true; - set(mockLicenseInfo, 'license.getType', () => 'basic'); - }); - - describe('& license is > basic', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isOneOf', () => true)); - - describe('& license is active', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => true)); - - it('should set showLinks to true', () => { - expect(checkLicense(mockLicenseInfo).showAppLink).to.be(true); - }); - - it('should set enableLinks to true', () => { - expect(checkLicense(mockLicenseInfo).enableAppLink).to.be(true); - }); - }); - - describe('& license is expired', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => false)); - - it('should set showLinks to true', () => { - expect(checkLicense(mockLicenseInfo).showAppLink).to.be(true); - }); - - it('should set enableLinks to false', () => { - expect(checkLicense(mockLicenseInfo).enableAppLink).to.be(false); - }); - }); - }); - - describe('& license is basic', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isOneOf', () => false)); - - describe('& license is active', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => true)); - - it('should set showLinks to false', () => { - expect(checkLicense(mockLicenseInfo).showAppLink).to.be(false); - }); - }); - - describe('& license is expired', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => false)); - - it('should set showLinks to false', () => { - expect(checkLicense(mockLicenseInfo).showAppLink).to.be(false); - }); - }); - }); - }); -}); diff --git a/x-pack/legacy/plugins/searchprofiler/server/np_ready/lib/check_license.ts b/x-pack/legacy/plugins/searchprofiler/server/np_ready/lib/check_license.ts deleted file mode 100644 index 2a22d615ca6e5..0000000000000 --- a/x-pack/legacy/plugins/searchprofiler/server/np_ready/lib/check_license.ts +++ /dev/null @@ -1,59 +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 '../../../../xpack_main/server/lib/xpack_info'; - -export function checkLicense( - xpackLicenseInfo: XPackInfo -): { showAppLink: boolean; enableAppLink: boolean; message: string | undefined } { - if (!xpackLicenseInfo || !xpackLicenseInfo.isAvailable()) { - return { - showAppLink: true, - enableAppLink: false, - message: i18n.translate('xpack.searchProfiler.unavailableLicenseInformationMessage', { - defaultMessage: - 'Search Profiler is unavailable - license information is not available at this time.', - }), - }; - } - - const isLicenseActive = xpackLicenseInfo.license.isActive(); - let message: string | undefined; - if (!isLicenseActive) { - message = i18n.translate('xpack.searchProfiler.licenseHasExpiredMessage', { - defaultMessage: 'Search Profiler is unavailable - license has expired.', - }); - } - - if ( - xpackLicenseInfo.license.isOneOf([ - 'trial', - 'basic', - 'standard', - 'gold', - 'platinum', - 'enterprise', - ]) - ) { - return { - showAppLink: true, - enableAppLink: isLicenseActive, - message, - }; - } - - message = i18n.translate('xpack.searchProfiler.upgradeLicenseMessage', { - defaultMessage: - 'Search Profiler is unavailable for the current {licenseInfo} license. Please upgrade your license.', - values: { licenseInfo: xpackLicenseInfo.license.getType() }, - }); - return { - showAppLink: false, - enableAppLink: false, - message, - }; -} diff --git a/x-pack/legacy/plugins/searchprofiler/server/np_ready/plugin.ts b/x-pack/legacy/plugins/searchprofiler/server/np_ready/plugin.ts deleted file mode 100644 index e00e2829f980d..0000000000000 --- a/x-pack/legacy/plugins/searchprofiler/server/np_ready/plugin.ts +++ /dev/null @@ -1,38 +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 { CoreSetup, Plugin } from 'src/core/server'; -import { LegacySetup } from './types'; -import { checkLicense } from './lib'; -// @ts-ignore -import { mirrorPluginStatus } from '../../../../server/lib/mirror_plugin_status'; - -import * as profileRoute from './routes/profile'; - -export class SearchProfilerServerPlugin implements Plugin { - async setup( - core: CoreSetup, - { - route, - plugins: { - __LEGACY: { thisPlugin, elasticsearch, xpackMain, commonRouteConfig }, - }, - }: LegacySetup - ) { - mirrorPluginStatus(xpackMain, thisPlugin); - (xpackMain as any).status.once('green', () => { - // Register a function that is called whenever the xpack info changes, - // to re-compute the license check results for this plugin - xpackMain.info.feature(thisPlugin.id).registerLicenseCheckResultsGenerator(checkLicense); - }); - - profileRoute.register({ elasticsearch }, route, commonRouteConfig); - } - - async start() {} - - stop(): void {} -} diff --git a/x-pack/legacy/plugins/searchprofiler/server/np_ready/routes/profile.ts b/x-pack/legacy/plugins/searchprofiler/server/np_ready/routes/profile.ts deleted file mode 100644 index 082307b5a7a2b..0000000000000 --- a/x-pack/legacy/plugins/searchprofiler/server/np_ready/routes/profile.ts +++ /dev/null @@ -1,53 +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 Joi from 'joi'; -import { RequestShim, ServerShim, RegisterRoute } from '../types'; - -export const handler = async (server: ServerShim, request: RequestShim) => { - const { callWithRequest } = server.elasticsearch.getCluster('data'); - let parsed = request.payload.query; - parsed.profile = true; - parsed = JSON.stringify(parsed, null, 2); - - const body = { - index: request.payload.index, - body: parsed, - }; - try { - const resp = await callWithRequest(request, 'search', body); - return { - ok: true, - resp, - }; - } catch (err) { - return { - ok: false, - err, - }; - } -}; - -export const register = (server: ServerShim, route: RegisterRoute, commonConfig: any) => { - route({ - path: '/api/searchprofiler/profile', - method: 'POST', - config: { - ...commonConfig, - validate: { - payload: Joi.object() - .keys({ - query: Joi.object().required(), - index: Joi.string().required(), - type: Joi.string().optional(), - }) - .required(), - }, - }, - handler: req => { - return handler(server, { headers: req.headers, payload: req.payload as any }); - }, - }); -}; diff --git a/x-pack/legacy/plugins/searchprofiler/server/np_ready/types.ts b/x-pack/legacy/plugins/searchprofiler/server/np_ready/types.ts deleted file mode 100644 index 9b25f8bb36b0c..0000000000000 --- a/x-pack/legacy/plugins/searchprofiler/server/np_ready/types.ts +++ /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 { ServerRoute } from 'hapi'; -import { ElasticsearchPlugin, Request } from 'src/legacy/core_plugins/elasticsearch'; -import { XPackMainPlugin } from '../../../xpack_main/server/xpack_main'; - -export type RegisterRoute = (args: ServerRoute & { config: any }) => void; - -export interface LegacyPlugins { - __LEGACY: { - thisPlugin: any; - elasticsearch: ElasticsearchPlugin; - xpackMain: XPackMainPlugin; - commonRouteConfig: any; - }; -} - -export interface LegacySetup { - route: RegisterRoute; - plugins: LegacyPlugins; -} - -export interface ServerShim { - elasticsearch: ElasticsearchPlugin; -} - -export interface RequestShim extends Request { - payload: any; -} diff --git a/x-pack/legacy/plugins/security/common/model.ts b/x-pack/legacy/plugins/security/common/model.ts deleted file mode 100644 index 733e89f774db8..0000000000000 --- a/x-pack/legacy/plugins/security/common/model.ts +++ /dev/null @@ -1,28 +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 { - ApiKey, - ApiKeyToInvalidate, - AuthenticatedUser, - BuiltinESPrivileges, - EditUser, - FeaturesPrivileges, - InlineRoleTemplate, - InvalidRoleTemplate, - KibanaPrivileges, - RawKibanaFeaturePrivileges, - RawKibanaPrivileges, - Role, - RoleIndexPrivilege, - RoleKibanaPrivilege, - RoleMapping, - RoleTemplate, - StoredRoleTemplate, - User, - canUserChangePassword, - getUserDisplayName, -} from '../../../../plugins/security/common/model'; diff --git a/x-pack/legacy/plugins/security/index.d.ts b/x-pack/legacy/plugins/security/index.d.ts index 18284c8be689a..d453415f73376 100644 --- a/x-pack/legacy/plugins/security/index.d.ts +++ b/x-pack/legacy/plugins/security/index.d.ts @@ -5,7 +5,7 @@ */ import { Legacy } from 'kibana'; -import { AuthenticatedUser } from './common/model'; +import { AuthenticatedUser } from '../../../plugins/security/public'; /** * Public interface of the security plugin. diff --git a/x-pack/legacy/plugins/security/index.js b/x-pack/legacy/plugins/security/index.js index bc403b803b8df..4988c30a1398b 100644 --- a/x-pack/legacy/plugins/security/index.js +++ b/x-pack/legacy/plugins/security/index.js @@ -40,8 +40,6 @@ export const security = kibana => }, uiExports: { - chromeNavControls: [], - managementSections: ['plugins/security/views/management'], styleSheetPaths: resolve(__dirname, 'public/index.scss'), apps: [ { @@ -76,7 +74,6 @@ export const security = kibana => 'plugins/security/hacks/on_unauthorized_response', 'plugins/security/hacks/register_account_management_app', ], - home: ['plugins/security/register_feature'], injectDefaultVars: server => { const securityPlugin = server.newPlatform.setup.plugins.security; if (!securityPlugin) { diff --git a/x-pack/legacy/plugins/security/public/documentation_links.js b/x-pack/legacy/plugins/security/public/documentation_links.js deleted file mode 100644 index 8050289b95e9d..0000000000000 --- a/x-pack/legacy/plugins/security/public/documentation_links.js +++ /dev/null @@ -1,16 +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 { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links'; - -const ES_REF_URL = `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${DOC_LINK_VERSION}`; - -export const documentationLinks = { - dashboardViewMode: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/xpack-view-modes.html`, - esClusterPrivileges: `${ES_REF_URL}/security-privileges.html#privileges-list-cluster`, - esIndicesPrivileges: `${ES_REF_URL}/security-privileges.html#privileges-list-indices`, - esRunAsPrivileges: `${ES_REF_URL}/security-privileges.html#_run_as_privilege`, -}; diff --git a/x-pack/legacy/plugins/security/public/images/logout.svg b/x-pack/legacy/plugins/security/public/images/logout.svg deleted file mode 100644 index d6533c0719904..0000000000000 --- a/x-pack/legacy/plugins/security/public/images/logout.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/x-pack/legacy/plugins/security/public/images/person.svg b/x-pack/legacy/plugins/security/public/images/person.svg deleted file mode 100644 index 988ddac8859d7..0000000000000 --- a/x-pack/legacy/plugins/security/public/images/person.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/x-pack/legacy/plugins/security/public/index.scss b/x-pack/legacy/plugins/security/public/index.scss index 2d7696bed3989..187ad5231534d 100644 --- a/x-pack/legacy/plugins/security/public/index.scss +++ b/x-pack/legacy/plugins/security/public/index.scss @@ -15,3 +15,6 @@ $secFormWidth: 460px; // Public views @import './views/index'; +// Styles of Kibana Platform plugin +@import '../../../../plugins/security/public/index'; + diff --git a/x-pack/legacy/plugins/security/public/lib/__tests__/util.js b/x-pack/legacy/plugins/security/public/lib/__tests__/util.js deleted file mode 100644 index 3f7d8aea53a85..0000000000000 --- a/x-pack/legacy/plugins/security/public/lib/__tests__/util.js +++ /dev/null @@ -1,49 +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 { toggle, toggleSort } from '../../../public/lib/util'; - -describe('util', () => { - describe('toggle', () => { - it('should add an item to a collection if not already included', () => { - const collection = [1, 2, 3, 4, 5]; - toggle(collection, 6); - expect(collection.indexOf(6)).to.be.above(0); - }); - - it('should remove an item from a collection if already included', () => { - const collection = [1, 2, 3, 4, 5]; - toggle(collection, 3); - expect(collection.indexOf(3)).to.be.below(0); - }); - }); - - describe('toggleSort', () => { - it('should toggle reverse if called with the same orderBy', () => { - const sort = { orderBy: 'foo', reverse: false }; - - toggleSort(sort, 'foo'); - expect(sort.reverse).to.be.true; - - toggleSort(sort, 'foo'); - expect(sort.reverse).to.be.false; - }); - - it('should change orderBy and set reverse to false when called with a different orderBy', () => { - const sort = { orderBy: 'foo', reverse: false }; - - toggleSort(sort, 'bar'); - expect(sort.orderBy).to.equal('bar'); - expect(sort.reverse).to.be.false; - - sort.reverse = true; - toggleSort(sort, 'foo'); - expect(sort.orderBy).to.equal('foo'); - expect(sort.reverse).to.be.false; - }); - }); -}); diff --git a/x-pack/legacy/plugins/security/public/lib/api.ts b/x-pack/legacy/plugins/security/public/lib/api.ts deleted file mode 100644 index c5c6994bf4be3..0000000000000 --- a/x-pack/legacy/plugins/security/public/lib/api.ts +++ /dev/null @@ -1,56 +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 { kfetch } from 'ui/kfetch'; -import { Role, User, EditUser } from '../../common/model'; - -const usersUrl = '/internal/security/users'; -const rolesUrl = '/api/security/role'; - -export class UserAPIClient { - public async getUsers(): Promise { - return await kfetch({ pathname: usersUrl }); - } - - public async getUser(username: string): Promise { - const url = `${usersUrl}/${encodeURIComponent(username)}`; - return await kfetch({ pathname: url }); - } - - public async deleteUser(username: string) { - const url = `${usersUrl}/${encodeURIComponent(username)}`; - await kfetch({ pathname: url, method: 'DELETE' }, {}); - } - - public async saveUser(user: EditUser) { - const url = `${usersUrl}/${encodeURIComponent(user.username)}`; - - await kfetch({ pathname: url, body: JSON.stringify(user), method: 'POST' }); - } - - public async getRoles(): Promise { - return await kfetch({ pathname: rolesUrl }); - } - - public async getRole(name: string): Promise { - const url = `${rolesUrl}/${encodeURIComponent(name)}`; - return await kfetch({ pathname: url }); - } - - public async changePassword(username: string, password: string, currentPassword: string) { - const data: Record = { - newPassword: password, - }; - if (currentPassword) { - data.password = currentPassword; - } - await kfetch({ - pathname: `${usersUrl}/${encodeURIComponent(username)}/password`, - method: 'POST', - body: JSON.stringify(data), - }); - } -} diff --git a/x-pack/legacy/plugins/security/public/lib/api_keys_api.ts b/x-pack/legacy/plugins/security/public/lib/api_keys_api.ts deleted file mode 100644 index fbc0460c5908a..0000000000000 --- a/x-pack/legacy/plugins/security/public/lib/api_keys_api.ts +++ /dev/null @@ -1,47 +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 { kfetch } from 'ui/kfetch'; -import { ApiKey, ApiKeyToInvalidate } from '../../common/model'; - -interface CheckPrivilegesResponse { - areApiKeysEnabled: boolean; - isAdmin: boolean; -} - -interface InvalidateApiKeysResponse { - itemsInvalidated: ApiKeyToInvalidate[]; - errors: any[]; -} - -interface GetApiKeysResponse { - apiKeys: ApiKey[]; -} - -const apiKeysUrl = `/internal/security/api_key`; - -export class ApiKeysApi { - public static async checkPrivileges(): Promise { - return kfetch({ pathname: `${apiKeysUrl}/privileges` }); - } - - public static async getApiKeys(isAdmin: boolean = false): Promise { - const query = { - isAdmin, - }; - - return kfetch({ pathname: apiKeysUrl, query }); - } - - public static async invalidateApiKeys( - apiKeys: ApiKeyToInvalidate[], - isAdmin: boolean = false - ): Promise { - const pathname = `${apiKeysUrl}/invalidate`; - const body = JSON.stringify({ apiKeys, isAdmin }); - return kfetch({ pathname, method: 'POST', body }); - } -} diff --git a/x-pack/legacy/plugins/security/public/lib/role_utils.ts b/x-pack/legacy/plugins/security/public/lib/role_utils.ts deleted file mode 100644 index c33b7385306fb..0000000000000 --- a/x-pack/legacy/plugins/security/public/lib/role_utils.ts +++ /dev/null @@ -1,58 +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 { cloneDeep, get } from 'lodash'; -import { Role } from '../../common/model'; - -/** - * Returns whether given role is enabled or not - * - * @param role Object Role JSON, as returned by roles API - * @return Boolean true if role is enabled; false otherwise - */ -export function isRoleEnabled(role: Partial) { - return get(role, 'transient_metadata.enabled', true); -} - -/** - * Returns whether given role is reserved or not. - * - * @param {role} the Role as returned by roles API - */ -export function isReservedRole(role: Partial) { - return get(role, 'metadata._reserved', false); -} - -/** - * Returns whether given role is editable through the UI or not. - * - * @param role the Role as returned by roles API - */ -export function isReadOnlyRole(role: Partial): boolean { - return isReservedRole(role) || !!(role._transform_error && role._transform_error.length > 0); -} - -/** - * Returns a deep copy of the role. - * - * @param role the Role to copy. - */ -export function copyRole(role: Role) { - return cloneDeep(role); -} - -/** - * Creates a deep copy of the role suitable for cloning. - * - * @param role the Role to clone. - */ -export function prepareRoleClone(role: Role): Role { - const clone = copyRole(role); - - clone.name = ''; - - return clone; -} diff --git a/x-pack/legacy/plugins/security/public/lib/roles_api.ts b/x-pack/legacy/plugins/security/public/lib/roles_api.ts deleted file mode 100644 index 20c1491ccaac6..0000000000000 --- a/x-pack/legacy/plugins/security/public/lib/roles_api.ts +++ /dev/null @@ -1,25 +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 { kfetch } from 'ui/kfetch'; -import { Role } from '../../common/model'; - -export class RolesApi { - public static async getRoles(): Promise { - return kfetch({ pathname: '/api/security/role' }); - } - - public static async getRole(roleName: string): Promise { - return kfetch({ pathname: `/api/security/role/${encodeURIComponent(roleName)}` }); - } - - public static async deleteRole(roleName: string) { - return kfetch({ - pathname: `/api/security/role/${encodeURIComponent(roleName)}`, - method: 'DELETE', - }); - } -} diff --git a/x-pack/legacy/plugins/security/public/lib/transform_role_for_save.ts b/x-pack/legacy/plugins/security/public/lib/transform_role_for_save.ts deleted file mode 100644 index 861ba530050a1..0000000000000 --- a/x-pack/legacy/plugins/security/public/lib/transform_role_for_save.ts +++ /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 { Role, RoleIndexPrivilege } from '../../common/model'; -import { isGlobalPrivilegeDefinition } from './privilege_utils'; - -export function transformRoleForSave(role: Role, spacesEnabled: boolean) { - // Remove any placeholder index privileges - role.elasticsearch.indices = role.elasticsearch.indices.filter( - indexPrivilege => !isPlaceholderPrivilege(indexPrivilege) - ); - - // Remove any placeholder query entries - role.elasticsearch.indices.forEach(index => index.query || delete index.query); - - // If spaces are disabled, then do not persist any space privileges - if (!spacesEnabled) { - role.kibana = role.kibana.filter(isGlobalPrivilegeDefinition); - } - - role.kibana.forEach(kibanaPrivilege => { - // If a base privilege is defined, then do not persist feature privileges - if (kibanaPrivilege.base.length > 0) { - kibanaPrivilege.feature = {}; - } - }); - - delete role.name; - delete role.transient_metadata; - delete role._unrecognized_applications; - delete role._transform_error; - - return role; -} - -function isPlaceholderPrivilege(indexPrivilege: RoleIndexPrivilege) { - return indexPrivilege.names.length === 0; -} diff --git a/x-pack/legacy/plugins/security/public/lib/util.js b/x-pack/legacy/plugins/security/public/lib/util.js deleted file mode 100644 index bdf44aa3f10bb..0000000000000 --- a/x-pack/legacy/plugins/security/public/lib/util.js +++ /dev/null @@ -1,19 +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 function toggle(collection, item) { - const i = collection.indexOf(item); - if (i >= 0) collection.splice(i, 1); - else collection.push(item); -} - -export function toggleSort(sort, orderBy) { - if (sort.orderBy === orderBy) sort.reverse = !sort.reverse; - else { - sort.orderBy = orderBy; - sort.reverse = false; - } -} diff --git a/x-pack/legacy/plugins/security/public/objects/lib/get_fields.ts b/x-pack/legacy/plugins/security/public/objects/lib/get_fields.ts deleted file mode 100644 index 91d98782dab42..0000000000000 --- a/x-pack/legacy/plugins/security/public/objects/lib/get_fields.ts +++ /dev/null @@ -1,15 +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 { IHttpResponse } from 'angular'; -import chrome from 'ui/chrome'; - -const apiBase = chrome.addBasePath(`/internal/security/fields`); - -export async function getFields($http: any, query: string): Promise { - return await $http - .get(`${apiBase}/${query}`) - .then((response: IHttpResponse) => response.data || []); -} diff --git a/x-pack/legacy/plugins/security/public/objects/lib/roles.ts b/x-pack/legacy/plugins/security/public/objects/lib/roles.ts deleted file mode 100644 index e33cbe4c6c031..0000000000000 --- a/x-pack/legacy/plugins/security/public/objects/lib/roles.ts +++ /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 chrome from 'ui/chrome'; -import { Role } from '../../../common/model'; -import { copyRole } from '../../lib/role_utils'; -import { transformRoleForSave } from '../../lib/transform_role_for_save'; - -const apiBase = chrome.addBasePath(`/api/security/role`); - -export async function saveRole($http: any, role: Role, spacesEnabled: boolean) { - const data = transformRoleForSave(copyRole(role), spacesEnabled); - - return await $http.put(`${apiBase}/${role.name}`, data); -} - -export async function deleteRole($http: any, name: string) { - return await $http.delete(`${apiBase}/${name}`); -} diff --git a/x-pack/legacy/plugins/security/public/register_feature.js b/x-pack/legacy/plugins/security/public/register_feature.js deleted file mode 100644 index c0bd42690b6fd..0000000000000 --- a/x-pack/legacy/plugins/security/public/register_feature.js +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { - FeatureCatalogueRegistryProvider, - FeatureCatalogueCategory, -} from 'ui/registry/feature_catalogue'; - -import { i18n } from '@kbn/i18n'; - -FeatureCatalogueRegistryProvider.register(() => { - return { - id: 'security', - title: i18n.translate('xpack.security.registerFeature.securitySettingsTitle', { - defaultMessage: 'Security Settings', - }), - description: i18n.translate('xpack.security.registerFeature.securitySettingsDescription', { - defaultMessage: - 'Protect your data and easily manage who has access to what with users and roles.', - }), - icon: 'securityApp', - path: '/app/kibana#/management/security', - showOnHomePage: true, - category: FeatureCatalogueCategory.ADMIN, - }; -}); diff --git a/x-pack/legacy/plugins/security/public/services/shield_indices.js b/x-pack/legacy/plugins/security/public/services/shield_indices.js deleted file mode 100644 index 791fa6cb59648..0000000000000 --- a/x-pack/legacy/plugins/security/public/services/shield_indices.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 { uiModules } from 'ui/modules'; - -const module = uiModules.get('security', []); -module.service('shieldIndices', ($http, chrome) => { - return { - getFields: query => { - return $http - .get(chrome.addBasePath(`/internal/security/fields/${query}`)) - .then(response => response.data); - }, - }; -}); diff --git a/x-pack/legacy/plugins/security/public/services/shield_role.js b/x-pack/legacy/plugins/security/public/services/shield_role.js deleted file mode 100644 index 261d3449a7a2d..0000000000000 --- a/x-pack/legacy/plugins/security/public/services/shield_role.js +++ /dev/null @@ -1,30 +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 'angular-resource'; -import { omit } from 'lodash'; -import angular from 'angular'; -import { uiModules } from 'ui/modules'; - -const module = uiModules.get('security', ['ngResource']); -module.service('ShieldRole', ($resource, chrome) => { - return $resource( - chrome.addBasePath('/api/security/role/:name'), - { - name: '@name', - }, - { - save: { - method: 'PUT', - transformRequest(data) { - return angular.toJson( - omit(data, 'name', 'transient_metadata', '_unrecognized_applications') - ); - }, - }, - } - ); -}); diff --git a/x-pack/legacy/plugins/security/public/views/_index.scss b/x-pack/legacy/plugins/security/public/views/_index.scss index b85a7e1997390..6c2a091adf536 100644 --- a/x-pack/legacy/plugins/security/public/views/_index.scss +++ b/x-pack/legacy/plugins/security/public/views/_index.scss @@ -1,5 +1,2 @@ // Login styles @import './login/index'; - -// Management styles -@import './management/index'; diff --git a/x-pack/legacy/plugins/security/public/views/account/account.html b/x-pack/legacy/plugins/security/public/views/account/account.html deleted file mode 100644 index 0935c415b1829..0000000000000 --- a/x-pack/legacy/plugins/security/public/views/account/account.html +++ /dev/null @@ -1 +0,0 @@ -
diff --git a/x-pack/legacy/plugins/security/public/views/account/account.js b/x-pack/legacy/plugins/security/public/views/account/account.js index 70a7b8dce727e..13abc44e08f96 100644 --- a/x-pack/legacy/plugins/security/public/views/account/account.js +++ b/x-pack/legacy/plugins/security/public/views/account/account.js @@ -4,17 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import routes from 'ui/routes'; -import template from './account.html'; -import { i18n } from '@kbn/i18n'; -import { I18nContext } from 'ui/i18n'; -import { npSetup } from 'ui/new_platform'; -import { AccountManagementPage } from './components'; import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; +import { i18n } from '@kbn/i18n'; +import { npStart } from 'ui/new_platform'; +import routes from 'ui/routes'; routes.when('/account', { - template, + template: '
', k7Breadcrumbs: () => [ { text: i18n.translate('xpack.security.account.breadcrumb', { @@ -24,19 +21,15 @@ routes.when('/account', { ], controllerAs: 'accountController', controller($scope) { - $scope.$on('$destroy', () => { - const elem = document.getElementById('userProfileReactRoot'); - if (elem) { - unmountComponentAtNode(elem); - } - }); $scope.$$postDigest(() => { + const domNode = document.getElementById('userProfileReactRoot'); + render( - - - , - document.getElementById('userProfileReactRoot') + , + domNode ); + + $scope.$on('$destroy', () => unmountComponentAtNode(domNode)); }); }, }); diff --git a/x-pack/legacy/plugins/security/public/views/login/_index.scss b/x-pack/legacy/plugins/security/public/views/login/_index.scss index 9f133940f7977..9083c8dc3b775 100644 --- a/x-pack/legacy/plugins/security/public/views/login/_index.scss +++ b/x-pack/legacy/plugins/security/public/views/login/_index.scss @@ -5,5 +5,4 @@ // loginChart__legend--small // loginChart__legend-isLoading -@import 'login'; - +@import './components/index'; diff --git a/x-pack/legacy/plugins/security/public/views/login/components/_index.scss b/x-pack/legacy/plugins/security/public/views/login/components/_index.scss new file mode 100644 index 0000000000000..a6f9598b9cc04 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/login/components/_index.scss @@ -0,0 +1 @@ +@import './login_page/index'; diff --git a/x-pack/legacy/plugins/security/public/views/login/components/basic_login_form/basic_login_form.test.tsx b/x-pack/legacy/plugins/security/public/views/login/components/basic_login_form/basic_login_form.test.tsx index 93451453a523a..3a970d582bdc8 100644 --- a/x-pack/legacy/plugins/security/public/views/login/components/basic_login_form/basic_login_form.test.tsx +++ b/x-pack/legacy/plugins/security/public/views/login/components/basic_login_form/basic_login_form.test.tsx @@ -7,7 +7,7 @@ import { EuiButton, EuiCallOut } from '@elastic/eui'; import React from 'react'; import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { LoginState } from '../../../../../common/login_state'; +import { LoginState } from '../../login_state'; import { BasicLoginForm } from './basic_login_form'; const createMockHttp = ({ simulateError = false } = {}) => { diff --git a/x-pack/legacy/plugins/security/public/views/login/components/basic_login_form/basic_login_form.tsx b/x-pack/legacy/plugins/security/public/views/login/components/basic_login_form/basic_login_form.tsx index e6d3b5b7536b6..c263381fbdb56 100644 --- a/x-pack/legacy/plugins/security/public/views/login/components/basic_login_form/basic_login_form.tsx +++ b/x-pack/legacy/plugins/security/public/views/login/components/basic_login_form/basic_login_form.tsx @@ -9,7 +9,7 @@ import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; import React, { ChangeEvent, Component, FormEvent, Fragment, MouseEvent } from 'react'; import ReactMarkdown from 'react-markdown'; import { EuiText } from '@elastic/eui'; -import { LoginState } from '../../../../../common/login_state'; +import { LoginState } from '../../login_state'; interface Props { http: any; diff --git a/x-pack/legacy/plugins/security/public/views/login/components/login_page/_index.scss b/x-pack/legacy/plugins/security/public/views/login/components/login_page/_index.scss new file mode 100644 index 0000000000000..4dd2c0cabfb5e --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/login/components/login_page/_index.scss @@ -0,0 +1 @@ +@import './login_page'; diff --git a/x-pack/legacy/plugins/security/public/views/login/_login.scss b/x-pack/legacy/plugins/security/public/views/login/components/login_page/_login_page.scss similarity index 88% rename from x-pack/legacy/plugins/security/public/views/login/_login.scss rename to x-pack/legacy/plugins/security/public/views/login/components/login_page/_login_page.scss index 607e9e6ec5e3f..cdfad55ee064a 100644 --- a/x-pack/legacy/plugins/security/public/views/login/_login.scss +++ b/x-pack/legacy/plugins/security/public/views/login/components/login_page/_login_page.scss @@ -1,4 +1,3 @@ - .loginWelcome { @include kibanaFullScreenGraphics; } @@ -16,10 +15,6 @@ margin-bottom: $euiSizeXL; } -.loginWelcome__footerAction { - margin-right: $euiSizeS; -} - .loginWelcome__content { position: relative; margin: auto; diff --git a/x-pack/legacy/plugins/security/public/views/login/components/login_page/login_page.test.tsx b/x-pack/legacy/plugins/security/public/views/login/components/login_page/login_page.test.tsx index c16db007bdbdc..a0318d50a45e5 100644 --- a/x-pack/legacy/plugins/security/public/views/login/components/login_page/login_page.test.tsx +++ b/x-pack/legacy/plugins/security/public/views/login/components/login_page/login_page.test.tsx @@ -6,7 +6,7 @@ import { shallow } from 'enzyme'; import React from 'react'; -import { LoginLayout, LoginState } from '../../../../../common/login_state'; +import { LoginLayout, LoginState } from '../../login_state'; import { LoginPage } from './login_page'; const createMockHttp = ({ simulateError = false } = {}) => { diff --git a/x-pack/legacy/plugins/security/public/views/login/components/login_page/login_page.tsx b/x-pack/legacy/plugins/security/public/views/login/components/login_page/login_page.tsx index e7e56947ca58f..8035789a30e9d 100644 --- a/x-pack/legacy/plugins/security/public/views/login/components/login_page/login_page.tsx +++ b/x-pack/legacy/plugins/security/public/views/login/components/login_page/login_page.tsx @@ -19,7 +19,7 @@ import { EuiTitle, } from '@elastic/eui'; import classNames from 'classnames'; -import { LoginState } from '../../../../../common/login_state'; +import { LoginState } from '../../login_state'; import { BasicLoginForm } from '../basic_login_form'; import { DisabledLoginForm } from '../disabled_login_form'; diff --git a/x-pack/legacy/plugins/security/public/views/login/login.html b/x-pack/legacy/plugins/security/public/views/login/login.html deleted file mode 100644 index 2695fabdd6367..0000000000000 --- a/x-pack/legacy/plugins/security/public/views/login/login.html +++ /dev/null @@ -1 +0,0 @@ -
\ No newline at end of file diff --git a/x-pack/legacy/plugins/security/public/views/login/login.tsx b/x-pack/legacy/plugins/security/public/views/login/login.tsx index d9daf2d1f4d0d..0b89ac553c9a8 100644 --- a/x-pack/legacy/plugins/security/public/views/login/login.tsx +++ b/x-pack/legacy/plugins/security/public/views/login/login.tsx @@ -6,16 +6,14 @@ import { i18n } from '@kbn/i18n'; import { get } from 'lodash'; -import { parseNext } from 'plugins/security/lib/parse_next'; import { LoginPage } from 'plugins/security/views/login/components'; -// @ts-ignore -import template from 'plugins/security/views/login/login.html'; import React from 'react'; import { render } from 'react-dom'; import chrome from 'ui/chrome'; import { I18nContext } from 'ui/i18n'; import { parse } from 'url'; -import { LoginState } from '../../../common/login_state'; +import { parseNext } from './parse_next'; +import { LoginState } from './login_state'; const messageMap = { SESSION_EXPIRED: i18n.translate('xpack.security.login.sessionExpiredDescription', { defaultMessage: 'Your session has timed out. Please log in again.', @@ -31,7 +29,7 @@ interface AnyObject { (chrome as AnyObject) .setVisible(false) - .setRootTemplate(template) + .setRootTemplate('
') .setRootController( 'login', ( diff --git a/x-pack/legacy/plugins/security/common/login_state.ts b/x-pack/legacy/plugins/security/public/views/login/login_state.ts similarity index 100% rename from x-pack/legacy/plugins/security/common/login_state.ts rename to x-pack/legacy/plugins/security/public/views/login/login_state.ts diff --git a/x-pack/legacy/plugins/security/public/lib/__tests__/parse_next.js b/x-pack/legacy/plugins/security/public/views/login/parse_next.test.ts similarity index 80% rename from x-pack/legacy/plugins/security/public/lib/__tests__/parse_next.js rename to x-pack/legacy/plugins/security/public/views/login/parse_next.test.ts index 7516433c77f83..b5e6c7dca41d8 100644 --- a/x-pack/legacy/plugins/security/public/lib/__tests__/parse_next.js +++ b/x-pack/legacy/plugins/security/public/views/login/parse_next.test.ts @@ -4,12 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import expect from '@kbn/expect'; -import { parseNext } from '../parse_next'; +import { parseNext } from './parse_next'; describe('parseNext', () => { it('should return a function', () => { - expect(parseNext).to.be.a('function'); + expect(parseNext).toBeInstanceOf(Function); }); describe('with basePath defined', () => { @@ -17,14 +16,14 @@ describe('parseNext', () => { it('should return basePath with a trailing slash when next is not specified', () => { const basePath = '/iqf'; const href = `${basePath}/login`; - expect(parseNext(href, basePath)).to.equal(`${basePath}/`); + expect(parseNext(href, basePath)).toEqual(`${basePath}/`); }); it('should properly handle next without hash', () => { const basePath = '/iqf'; const next = `${basePath}/app/kibana`; const href = `${basePath}/login?next=${next}`; - expect(parseNext(href, basePath)).to.equal(next); + expect(parseNext(href, basePath)).toEqual(next); }); it('should properly handle next with hash', () => { @@ -32,7 +31,7 @@ describe('parseNext', () => { const next = `${basePath}/app/kibana`; const hash = '/discover/New-Saved-Search'; const href = `${basePath}/login?next=${next}#${hash}`; - expect(parseNext(href, basePath)).to.equal(`${next}#${hash}`); + expect(parseNext(href, basePath)).toEqual(`${next}#${hash}`); }); it('should properly decode special characters', () => { @@ -40,7 +39,7 @@ describe('parseNext', () => { const next = `${encodeURIComponent(basePath)}%2Fapp%2Fkibana`; const hash = '/discover/New-Saved-Search'; const href = `${basePath}/login?next=${next}#${hash}`; - expect(parseNext(href, basePath)).to.equal(decodeURIComponent(`${next}#${hash}`)); + expect(parseNext(href, basePath)).toEqual(decodeURIComponent(`${next}#${hash}`)); }); // to help prevent open redirect to a different url @@ -48,7 +47,7 @@ describe('parseNext', () => { const basePath = '/iqf'; const next = `https://example.com${basePath}/app/kibana`; const href = `${basePath}/login?next=${next}`; - expect(parseNext(href, basePath)).to.equal(`${basePath}/`); + expect(parseNext(href, basePath)).toEqual(`${basePath}/`); }); // to help prevent open redirect to a different url by abusing encodings @@ -58,7 +57,7 @@ describe('parseNext', () => { const next = `${encodeURIComponent(baseUrl)}%2Fapp%2Fkibana`; const hash = '/discover/New-Saved-Search'; const href = `${basePath}/login?next=${next}#${hash}`; - expect(parseNext(href, basePath)).to.equal(`${basePath}/`); + expect(parseNext(href, basePath)).toEqual(`${basePath}/`); }); // to help prevent open redirect to a different port @@ -66,7 +65,7 @@ describe('parseNext', () => { const basePath = '/iqf'; const next = `http://localhost:5601${basePath}/app/kibana`; const href = `${basePath}/login?next=${next}`; - expect(parseNext(href, basePath)).to.equal(`${basePath}/`); + expect(parseNext(href, basePath)).toEqual(`${basePath}/`); }); // to help prevent open redirect to a different port by abusing encodings @@ -76,7 +75,7 @@ describe('parseNext', () => { const next = `${encodeURIComponent(baseUrl)}%2Fapp%2Fkibana`; const hash = '/discover/New-Saved-Search'; const href = `${basePath}/login?next=${next}#${hash}`; - expect(parseNext(href, basePath)).to.equal(`${basePath}/`); + expect(parseNext(href, basePath)).toEqual(`${basePath}/`); }); // to help prevent open redirect to a different base path @@ -84,18 +83,18 @@ describe('parseNext', () => { const basePath = '/iqf'; const next = '/notbasepath/app/kibana'; const href = `${basePath}/login?next=${next}`; - expect(parseNext(href, basePath)).to.equal(`${basePath}/`); + expect(parseNext(href, basePath)).toEqual(`${basePath}/`); }); // disallow network-path references it('should return / if next is url without protocol', () => { const nextWithTwoSlashes = '//example.com'; const hrefWithTwoSlashes = `/login?next=${nextWithTwoSlashes}`; - expect(parseNext(hrefWithTwoSlashes)).to.equal('/'); + expect(parseNext(hrefWithTwoSlashes)).toEqual('/'); const nextWithThreeSlashes = '///example.com'; const hrefWithThreeSlashes = `/login?next=${nextWithThreeSlashes}`; - expect(parseNext(hrefWithThreeSlashes)).to.equal('/'); + expect(parseNext(hrefWithThreeSlashes)).toEqual('/'); }); }); @@ -103,34 +102,34 @@ describe('parseNext', () => { // trailing slash is important since it must match the cookie path exactly it('should return / with a trailing slash when next is not specified', () => { const href = '/login'; - expect(parseNext(href)).to.equal('/'); + expect(parseNext(href)).toEqual('/'); }); it('should properly handle next without hash', () => { const next = '/app/kibana'; const href = `/login?next=${next}`; - expect(parseNext(href)).to.equal(next); + expect(parseNext(href)).toEqual(next); }); it('should properly handle next with hash', () => { const next = '/app/kibana'; const hash = '/discover/New-Saved-Search'; const href = `/login?next=${next}#${hash}`; - expect(parseNext(href)).to.equal(`${next}#${hash}`); + expect(parseNext(href)).toEqual(`${next}#${hash}`); }); it('should properly decode special characters', () => { const next = '%2Fapp%2Fkibana'; const hash = '/discover/New-Saved-Search'; const href = `/login?next=${next}#${hash}`; - expect(parseNext(href)).to.equal(decodeURIComponent(`${next}#${hash}`)); + expect(parseNext(href)).toEqual(decodeURIComponent(`${next}#${hash}`)); }); // to help prevent open redirect to a different url it('should return / if next includes a protocol/hostname', () => { const next = 'https://example.com/app/kibana'; const href = `/login?next=${next}`; - expect(parseNext(href)).to.equal('/'); + expect(parseNext(href)).toEqual('/'); }); // to help prevent open redirect to a different url by abusing encodings @@ -139,14 +138,14 @@ describe('parseNext', () => { const next = `${encodeURIComponent(baseUrl)}%2Fapp%2Fkibana`; const hash = '/discover/New-Saved-Search'; const href = `/login?next=${next}#${hash}`; - expect(parseNext(href)).to.equal('/'); + expect(parseNext(href)).toEqual('/'); }); // to help prevent open redirect to a different port it('should return / if next includes a port', () => { const next = 'http://localhost:5601/app/kibana'; const href = `/login?next=${next}`; - expect(parseNext(href)).to.equal('/'); + expect(parseNext(href)).toEqual('/'); }); // to help prevent open redirect to a different port by abusing encodings @@ -155,18 +154,18 @@ describe('parseNext', () => { const next = `${encodeURIComponent(baseUrl)}%2Fapp%2Fkibana`; const hash = '/discover/New-Saved-Search'; const href = `/login?next=${next}#${hash}`; - expect(parseNext(href)).to.equal('/'); + expect(parseNext(href)).toEqual('/'); }); // disallow network-path references it('should return / if next is url without protocol', () => { const nextWithTwoSlashes = '//example.com'; const hrefWithTwoSlashes = `/login?next=${nextWithTwoSlashes}`; - expect(parseNext(hrefWithTwoSlashes)).to.equal('/'); + expect(parseNext(hrefWithTwoSlashes)).toEqual('/'); const nextWithThreeSlashes = '///example.com'; const hrefWithThreeSlashes = `/login?next=${nextWithThreeSlashes}`; - expect(parseNext(hrefWithThreeSlashes)).to.equal('/'); + expect(parseNext(hrefWithThreeSlashes)).toEqual('/'); }); }); }); diff --git a/x-pack/legacy/plugins/security/public/lib/parse_next.ts b/x-pack/legacy/plugins/security/public/views/login/parse_next.ts similarity index 100% rename from x-pack/legacy/plugins/security/public/lib/parse_next.ts rename to x-pack/legacy/plugins/security/public/views/login/parse_next.ts diff --git a/x-pack/legacy/plugins/security/public/views/management/_index.scss b/x-pack/legacy/plugins/security/public/views/management/_index.scss deleted file mode 100644 index 78b53845071e4..0000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/_index.scss +++ /dev/null @@ -1,4 +0,0 @@ -@import './change_password_form/index'; -@import './edit_role/index'; -@import './edit_user/index'; -@import './role_mappings/edit_role_mapping/index'; \ No newline at end of file diff --git a/x-pack/legacy/plugins/security/public/views/management/api_keys_grid/api_keys.html b/x-pack/legacy/plugins/security/public/views/management/api_keys_grid/api_keys.html deleted file mode 100644 index e46c6f72b5d20..0000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/api_keys_grid/api_keys.html +++ /dev/null @@ -1,3 +0,0 @@ - -
- diff --git a/x-pack/legacy/plugins/security/public/views/management/api_keys_grid/api_keys.js b/x-pack/legacy/plugins/security/public/views/management/api_keys_grid/api_keys.js deleted file mode 100644 index e7143b1020814..0000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/api_keys_grid/api_keys.js +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; -import routes from 'ui/routes'; -import template from './api_keys.html'; -import { API_KEYS_PATH } from '../management_urls'; -import { getApiKeysBreadcrumbs } from '../breadcrumbs'; -import { I18nContext } from 'ui/i18n'; -import { ApiKeysGridPage } from './components'; - -routes.when(API_KEYS_PATH, { - template, - k7Breadcrumbs: getApiKeysBreadcrumbs, - controller($scope) { - $scope.$$postDigest(() => { - const domNode = document.getElementById('apiKeysGridReactRoot'); - - render( - - - , - domNode - ); - - // unmount react on controller destroy - $scope.$on('$destroy', () => { - unmountComponentAtNode(domNode); - }); - }); - }, -}); diff --git a/x-pack/legacy/plugins/security/public/views/management/breadcrumbs.ts b/x-pack/legacy/plugins/security/public/views/management/breadcrumbs.ts deleted file mode 100644 index 4ab7e45e84849..0000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/breadcrumbs.ts +++ /dev/null @@ -1,115 +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 { MANAGEMENT_BREADCRUMB } from 'ui/management/breadcrumbs'; - -export function getUsersBreadcrumbs() { - return [ - MANAGEMENT_BREADCRUMB, - { - text: i18n.translate('xpack.security.users.breadcrumb', { - defaultMessage: 'Users', - }), - href: '#/management/security/users', - }, - ]; -} - -export function getEditUserBreadcrumbs($route: Record) { - const { username } = $route.current.params; - return [ - ...getUsersBreadcrumbs(), - { - text: username, - href: `#/management/security/users/edit/${username}`, - }, - ]; -} - -export function getCreateUserBreadcrumbs() { - return [ - ...getUsersBreadcrumbs(), - { - text: i18n.translate('xpack.security.users.createBreadcrumb', { - defaultMessage: 'Create', - }), - }, - ]; -} - -export function getRolesBreadcrumbs() { - return [ - MANAGEMENT_BREADCRUMB, - { - text: i18n.translate('xpack.security.roles.breadcrumb', { - defaultMessage: 'Roles', - }), - href: '#/management/security/roles', - }, - ]; -} - -export function getEditRoleBreadcrumbs($route: Record) { - const { name } = $route.current.params; - return [ - ...getRolesBreadcrumbs(), - { - text: name, - href: `#/management/security/roles/edit/${name}`, - }, - ]; -} - -export function getCreateRoleBreadcrumbs() { - return [ - ...getUsersBreadcrumbs(), - { - text: i18n.translate('xpack.security.roles.createBreadcrumb', { - defaultMessage: 'Create', - }), - }, - ]; -} - -export function getApiKeysBreadcrumbs() { - return [ - MANAGEMENT_BREADCRUMB, - { - text: i18n.translate('xpack.security.apiKeys.breadcrumb', { - defaultMessage: 'API Keys', - }), - href: '#/management/security/api_keys', - }, - ]; -} - -export function getRoleMappingBreadcrumbs() { - return [ - MANAGEMENT_BREADCRUMB, - { - text: i18n.translate('xpack.security.roleMapping.breadcrumb', { - defaultMessage: 'Role Mappings', - }), - href: '#/management/security/role_mappings', - }, - ]; -} - -export function getEditRoleMappingBreadcrumbs($route: Record) { - const { name } = $route.current.params; - return [ - ...getRoleMappingBreadcrumbs(), - { - text: - name || - i18n.translate('xpack.security.roleMappings.createBreadcrumb', { - defaultMessage: 'Create', - }), - href: `#/management/security/role_mappings/edit/${name}`, - }, - ]; -} diff --git a/x-pack/legacy/plugins/security/public/views/management/change_password_form/_change_password_form.scss b/x-pack/legacy/plugins/security/public/views/management/change_password_form/_change_password_form.scss deleted file mode 100644 index 98331c2070a31..0000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/change_password_form/_change_password_form.scss +++ /dev/null @@ -1,17 +0,0 @@ -.secChangePasswordForm__panel { - max-width: $secFormWidth; -} - -.secChangePasswordForm__subLabel { - margin-bottom: $euiSizeS; -} - -.secChangePasswordForm__footer { - display: flex; - justify-content: flex-start; - align-items: center; - - .kuiButton + .kuiButton { - margin-left: $euiSizeS; - } -} diff --git a/x-pack/legacy/plugins/security/public/views/management/change_password_form/_index.scss b/x-pack/legacy/plugins/security/public/views/management/change_password_form/_index.scss deleted file mode 100644 index a6058b5ddebbf..0000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/change_password_form/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './change_password_form'; diff --git a/x-pack/legacy/plugins/security/public/views/management/change_password_form/change_password_form.html b/x-pack/legacy/plugins/security/public/views/management/change_password_form/change_password_form.html deleted file mode 100644 index 92fb95861a6f8..0000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/change_password_form/change_password_form.html +++ /dev/null @@ -1,141 +0,0 @@ - - -
- - - - - - -
- -
- - - - -
-
- - -
- - - - -
-
- - -
- - - - -
- - -
- - -
-
- - - -
-
-
diff --git a/x-pack/legacy/plugins/security/public/views/management/change_password_form/change_password_form.js b/x-pack/legacy/plugins/security/public/views/management/change_password_form/change_password_form.js deleted file mode 100644 index d9aa59f6df142..0000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/change_password_form/change_password_form.js +++ /dev/null @@ -1,48 +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 { uiModules } from 'ui/modules'; -import template from './change_password_form.html'; - -const module = uiModules.get('security', ['kibana']); -module.directive('kbnChangePasswordForm', function() { - return { - template, - scope: { - requireCurrentPassword: '=', - showKibanaWarning: '=', - onChangePassword: '&', - }, - restrict: 'E', - replace: true, - controllerAs: 'changePasswordController', - controller: function($scope) { - this.currentPassword = null; - this.newPassword = null; - this.newPasswordConfirmation = null; - this.isFormVisible = false; - this.isIncorrectPassword = false; - - this.showForm = () => { - this.isFormVisible = true; - }; - - this.hideForm = () => { - $scope.changePasswordForm.$setPristine(); - $scope.changePasswordForm.$setUntouched(); - this.currentPassword = null; - this.newPassword = null; - this.newPasswordConfirmation = null; - this.isFormVisible = false; - this.isIncorrectPassword = false; - }; - - this.onIncorrectPassword = () => { - this.isIncorrectPassword = true; - }; - }, - }; -}); diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/_index.scss b/x-pack/legacy/plugins/security/public/views/management/edit_role/_index.scss deleted file mode 100644 index 192091fb04e3c..0000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './components/index'; diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/_index.scss b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/_index.scss deleted file mode 100644 index 32b3832e7a9fa..0000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/_index.scss +++ /dev/null @@ -1,9 +0,0 @@ -@import './collapsible_panel/collapsible_panel'; -@import './privileges/kibana/space_aware_privilege_section/index'; -@import './privileges/kibana/feature_table/index'; -@import './spaces_popover_list/spaces_popover_list'; - -.secPrivilegeFeatureIcon { - flex-shrink: 0; - margin-right: $euiSizeS; -} diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.test.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.test.tsx deleted file mode 100644 index 67c32c8393171..0000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.test.tsx +++ /dev/null @@ -1,716 +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 { ReactWrapper } from 'enzyme'; -import React from 'react'; -import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import { UICapabilities } from 'ui/capabilities'; -import { Space } from '../../../../../../spaces/common/model/space'; -import { Feature } from '../../../../../../../../plugins/features/public'; -// These modules should be moved into a common directory -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { Actions } from '../../../../../../../../plugins/security/server/authorization/actions'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { privilegesFactory } from '../../../../../../../../plugins/security/server/authorization/privileges'; -import { RawKibanaPrivileges, Role } from '../../../../../common/model'; -import { EditRolePage } from './edit_role_page'; -import { SimplePrivilegeSection } from './privileges/kibana/simple_privilege_section'; -import { SpaceAwarePrivilegeSection } from './privileges/kibana/space_aware_privilege_section'; -import { TransformErrorSection } from './privileges/kibana/transform_error_section'; - -const buildFeatures = () => { - return [ - { - id: 'feature1', - name: 'Feature 1', - icon: 'addDataApp', - app: ['feature1App'], - privileges: { - all: { - app: ['feature1App'], - ui: ['feature1-ui'], - savedObject: { - all: [], - read: [], - }, - }, - }, - }, - { - id: 'feature2', - name: 'Feature 2', - icon: 'addDataApp', - app: ['feature2App'], - privileges: { - all: { - app: ['feature2App'], - ui: ['feature2-ui'], - savedObject: { - all: ['feature2'], - read: ['config'], - }, - }, - }, - }, - ] as Feature[]; -}; - -const buildRawKibanaPrivileges = () => { - return privilegesFactory(new Actions('unit_test_version'), { - getFeatures: () => buildFeatures(), - }).get(); -}; - -const buildBuiltinESPrivileges = () => { - return { - cluster: ['all', 'manage', 'monitor'], - index: ['all', 'read', 'write', 'index'], - }; -}; - -const buildUICapabilities = (canManageSpaces = true) => { - return { - catalogue: {}, - management: {}, - navLinks: {}, - spaces: { - manage: canManageSpaces, - }, - } as UICapabilities; -}; - -const buildSpaces = () => { - return [ - { - id: 'default', - name: 'Default', - disabledFeatures: [], - _reserved: true, - }, - { - id: 'space_1', - name: 'Space 1', - disabledFeatures: [], - }, - { - id: 'space_2', - name: 'Space 2', - disabledFeatures: ['feature2'], - }, - ] as Space[]; -}; - -const expectReadOnlyFormButtons = (wrapper: ReactWrapper) => { - expect(wrapper.find('button[data-test-subj="roleFormReturnButton"]')).toHaveLength(1); - expect(wrapper.find('button[data-test-subj="roleFormSaveButton"]')).toHaveLength(0); -}; - -const expectSaveFormButtons = (wrapper: ReactWrapper) => { - expect(wrapper.find('button[data-test-subj="roleFormReturnButton"]')).toHaveLength(0); - expect(wrapper.find('button[data-test-subj="roleFormSaveButton"]')).toHaveLength(1); -}; - -describe('', () => { - describe('with spaces enabled', () => { - it('can render a reserved role', () => { - const role: Role = { - name: 'superuser', - metadata: { - _reserved: true, - }, - elasticsearch: { - cluster: ['all'], - indices: [], - run_as: ['*'], - }, - kibana: [ - { - spaces: ['*'], - base: ['all'], - feature: {}, - }, - ], - }; - - const features: Feature[] = buildFeatures(); - const mockHttpClient = jest.fn(); - const indexPatterns: string[] = ['foo*', 'bar*']; - const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges(); - const builtinESPrivileges = buildBuiltinESPrivileges(); - const spaces: Space[] = buildSpaces(); - const uiCapabilities: UICapabilities = buildUICapabilities(); - - const wrapper = mountWithIntl( - - ); - - expect(wrapper.find('[data-test-subj="reservedRoleBadgeTooltip"]')).toHaveLength(1); - expect(wrapper.find(SpaceAwarePrivilegeSection)).toHaveLength(1); - expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0); - expectReadOnlyFormButtons(wrapper); - }); - - it('can render a user defined role', () => { - const role: Role = { - name: 'my custom role', - metadata: {}, - elasticsearch: { - cluster: ['all'], - indices: [], - run_as: ['*'], - }, - kibana: [ - { - spaces: ['*'], - base: ['all'], - feature: {}, - }, - ], - }; - - const features: Feature[] = buildFeatures(); - const mockHttpClient = jest.fn(); - const indexPatterns: string[] = ['foo*', 'bar*']; - const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges(); - const builtinESPrivileges = buildBuiltinESPrivileges(); - const spaces: Space[] = buildSpaces(); - const uiCapabilities: UICapabilities = buildUICapabilities(); - - const wrapper = mountWithIntl( - - ); - - expect(wrapper.find('[data-test-subj="reservedRoleBadgeTooltip"]')).toHaveLength(0); - expect(wrapper.find(SpaceAwarePrivilegeSection)).toHaveLength(1); - expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0); - expectSaveFormButtons(wrapper); - }); - - it('can render when creating a new role', () => { - // @ts-ignore - const role: Role = { - metadata: {}, - elasticsearch: { - cluster: [], - indices: [], - run_as: [], - }, - kibana: [], - }; - - const features: Feature[] = buildFeatures(); - const mockHttpClient = jest.fn(); - const indexPatterns: string[] = ['foo*', 'bar*']; - const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges(); - const builtinESPrivileges = buildBuiltinESPrivileges(); - const spaces: Space[] = buildSpaces(); - const uiCapabilities: UICapabilities = buildUICapabilities(); - - const wrapper = mountWithIntl( - - ); - - expect(wrapper.find(SpaceAwarePrivilegeSection)).toHaveLength(1); - expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0); - expectSaveFormButtons(wrapper); - }); - - it('can render when cloning an existing role', () => { - const role: Role = { - metadata: { - _reserved: false, - }, - name: '', - elasticsearch: { - cluster: ['all', 'manage'], - indices: [ - { - names: ['foo*'], - privileges: ['all'], - field_security: { - except: ['f'], - grant: ['b*'], - }, - }, - ], - run_as: ['elastic'], - }, - kibana: [ - { - spaces: ['*'], - base: ['all'], - feature: {}, - }, - ], - }; - - const features: Feature[] = buildFeatures(); - const mockHttpClient = jest.fn(); - const indexPatterns: string[] = ['foo*', 'bar*']; - const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges(); - const builtinESPrivileges = buildBuiltinESPrivileges(); - const spaces: Space[] = buildSpaces(); - const uiCapabilities: UICapabilities = buildUICapabilities(); - - const wrapper = mountWithIntl( - - ); - - expect(wrapper.find(SpaceAwarePrivilegeSection)).toHaveLength(1); - expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0); - expectSaveFormButtons(wrapper); - }); - - it('renders an auth error when not authorized to manage spaces', () => { - const role: Role = { - name: 'my custom role', - metadata: {}, - elasticsearch: { - cluster: ['all'], - indices: [], - run_as: ['*'], - }, - kibana: [ - { - spaces: ['*'], - base: ['all'], - feature: {}, - }, - ], - }; - - const features: Feature[] = buildFeatures(); - const mockHttpClient = jest.fn(); - const indexPatterns: string[] = ['foo*', 'bar*']; - const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges(); - const builtinESPrivileges = buildBuiltinESPrivileges(); - const spaces: Space[] = buildSpaces(); - const uiCapabilities: UICapabilities = buildUICapabilities(false); - - const wrapper = mountWithIntl( - - ); - - expect(wrapper.find('[data-test-subj="reservedRoleBadgeTooltip"]')).toHaveLength(0); - - expect( - wrapper.find('EuiCallOut[data-test-subj="userCannotManageSpacesCallout"]') - ).toHaveLength(1); - - expect(wrapper.find(SpaceAwarePrivilegeSection)).toHaveLength(1); - expectSaveFormButtons(wrapper); - }); - - it('renders a partial read-only view when there is a transform error', () => { - const role: Role = { - name: 'my custom role', - metadata: {}, - elasticsearch: { - cluster: ['all'], - indices: [], - run_as: ['*'], - }, - kibana: [], - _transform_error: ['kibana'], - }; - - const features: Feature[] = buildFeatures(); - const mockHttpClient = jest.fn(); - const indexPatterns: string[] = ['foo*', 'bar*']; - const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges(); - const builtinESPrivileges = buildBuiltinESPrivileges(); - const spaces: Space[] = buildSpaces(); - const uiCapabilities: UICapabilities = buildUICapabilities(false); - - const wrapper = mountWithIntl( - - ); - - expect(wrapper.find(TransformErrorSection)).toHaveLength(1); - expectReadOnlyFormButtons(wrapper); - }); - }); - - describe('with spaces disabled', () => { - it('can render a reserved role', () => { - const role: Role = { - name: 'superuser', - metadata: { - _reserved: true, - }, - elasticsearch: { - cluster: ['all'], - indices: [], - run_as: ['*'], - }, - kibana: [ - { - spaces: ['*'], - base: ['all'], - feature: {}, - }, - ], - }; - - const features: Feature[] = buildFeatures(); - const mockHttpClient = jest.fn(); - const indexPatterns: string[] = ['foo*', 'bar*']; - const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges(); - const builtinESPrivileges = buildBuiltinESPrivileges(); - const uiCapabilities: UICapabilities = buildUICapabilities(); - - const wrapper = mountWithIntl( - - ); - - expect(wrapper.find('[data-test-subj="reservedRoleBadgeTooltip"]')).toHaveLength(1); - expect(wrapper.find(SimplePrivilegeSection)).toHaveLength(1); - expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0); - expectReadOnlyFormButtons(wrapper); - }); - - it('can render a user defined role', () => { - const role: Role = { - name: 'my custom role', - metadata: {}, - elasticsearch: { - cluster: ['all'], - indices: [], - run_as: ['*'], - }, - kibana: [ - { - spaces: ['*'], - base: ['all'], - feature: {}, - }, - ], - }; - - const features: Feature[] = buildFeatures(); - const mockHttpClient = jest.fn(); - const indexPatterns: string[] = ['foo*', 'bar*']; - const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges(); - const builtinESPrivileges = buildBuiltinESPrivileges(); - const uiCapabilities: UICapabilities = buildUICapabilities(); - - const wrapper = mountWithIntl( - - ); - - expect(wrapper.find('[data-test-subj="reservedRoleBadgeTooltip"]')).toHaveLength(0); - expect(wrapper.find(SimplePrivilegeSection)).toHaveLength(1); - expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0); - expectSaveFormButtons(wrapper); - }); - - it('can render when creating a new role', () => { - // @ts-ignore - const role: Role = { - metadata: {}, - elasticsearch: { - cluster: [], - indices: [], - run_as: [], - }, - kibana: [], - }; - - const features: Feature[] = buildFeatures(); - const mockHttpClient = jest.fn(); - const indexPatterns: string[] = ['foo*', 'bar*']; - const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges(); - const builtinESPrivileges = buildBuiltinESPrivileges(); - const uiCapabilities: UICapabilities = buildUICapabilities(); - - const wrapper = mountWithIntl( - - ); - - expect(wrapper.find(SimplePrivilegeSection)).toHaveLength(1); - expectSaveFormButtons(wrapper); - }); - - it('can render when cloning an existing role', () => { - const role: Role = { - metadata: { - _reserved: false, - }, - name: '', - elasticsearch: { - cluster: ['all', 'manage'], - indices: [ - { - names: ['foo*'], - privileges: ['all'], - field_security: { - except: ['f'], - grant: ['b*'], - }, - }, - ], - run_as: ['elastic'], - }, - kibana: [ - { - spaces: ['*'], - base: ['all'], - feature: {}, - }, - ], - }; - - const features: Feature[] = buildFeatures(); - const mockHttpClient = jest.fn(); - const indexPatterns: string[] = ['foo*', 'bar*']; - const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges(); - const builtinESPrivileges = buildBuiltinESPrivileges(); - const uiCapabilities: UICapabilities = buildUICapabilities(); - - const wrapper = mountWithIntl( - - ); - - expect(wrapper.find(SimplePrivilegeSection)).toHaveLength(1); - expectSaveFormButtons(wrapper); - }); - - it('does not care if user cannot manage spaces', () => { - const role: Role = { - name: 'my custom role', - metadata: {}, - elasticsearch: { - cluster: ['all'], - indices: [], - run_as: ['*'], - }, - kibana: [ - { - spaces: ['*'], - base: ['all'], - feature: {}, - }, - ], - }; - - const features: Feature[] = buildFeatures(); - const mockHttpClient = jest.fn(); - const indexPatterns: string[] = ['foo*', 'bar*']; - const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges(); - const builtinESPrivileges = buildBuiltinESPrivileges(); - const uiCapabilities: UICapabilities = buildUICapabilities(false); - - const wrapper = mountWithIntl( - - ); - - expect(wrapper.find('[data-test-subj="reservedRoleBadgeTooltip"]')).toHaveLength(0); - - expect( - wrapper.find('EuiCallOut[data-test-subj="userCannotManageSpacesCallout"]') - ).toHaveLength(0); - - expect(wrapper.find(SimplePrivilegeSection)).toHaveLength(1); - expectSaveFormButtons(wrapper); - }); - - it('renders a partial read-only view when there is a transform error', () => { - const role: Role = { - name: 'my custom role', - metadata: {}, - elasticsearch: { - cluster: ['all'], - indices: [], - run_as: ['*'], - }, - kibana: [], - _transform_error: ['kibana'], - }; - - const features: Feature[] = buildFeatures(); - const mockHttpClient = jest.fn(); - const indexPatterns: string[] = ['foo*', 'bar*']; - const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges(); - const builtinESPrivileges = buildBuiltinESPrivileges(); - const uiCapabilities: UICapabilities = buildUICapabilities(false); - - const wrapper = mountWithIntl( - - ); - - expect(wrapper.find(TransformErrorSection)).toHaveLength(1); - expectReadOnlyFormButtons(wrapper); - }); - }); -}); diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.tsx deleted file mode 100644 index 2ba012afa689d..0000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.tsx +++ /dev/null @@ -1,409 +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 { - EuiButton, - EuiButtonEmpty, - EuiFieldText, - EuiFlexGroup, - EuiFlexItem, - EuiForm, - EuiFormRow, - EuiPanel, - EuiSpacer, - EuiText, - EuiTitle, -} from '@elastic/eui'; -import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; -import { get } from 'lodash'; -import React, { ChangeEvent, Component, Fragment, HTMLProps } from 'react'; -import { UICapabilities } from 'ui/capabilities'; -import { toastNotifications } from 'ui/notify'; -import { Space } from '../../../../../../spaces/common/model/space'; -import { Feature } from '../../../../../../../../plugins/features/public'; -import { - KibanaPrivileges, - RawKibanaPrivileges, - Role, - BuiltinESPrivileges, -} from '../../../../../common/model'; -import { - isReadOnlyRole, - isReservedRole, - copyRole, - prepareRoleClone, -} from '../../../../lib/role_utils'; -import { deleteRole, saveRole } from '../../../../objects'; -import { ROLES_PATH } from '../../management_urls'; -import { RoleValidationResult, RoleValidator } from '../lib/validate_role'; -import { DeleteRoleButton } from './delete_role_button'; -import { ElasticsearchPrivileges, KibanaPrivilegesRegion } from './privileges'; -import { ReservedRoleBadge } from './reserved_role_badge'; - -interface Props { - action: 'edit' | 'clone'; - role: Role; - runAsUsers: string[]; - indexPatterns: string[]; - httpClient: any; - allowDocumentLevelSecurity: boolean; - allowFieldLevelSecurity: boolean; - kibanaPrivileges: RawKibanaPrivileges; - builtinESPrivileges: BuiltinESPrivileges; - spaces?: Space[]; - spacesEnabled: boolean; - intl: InjectedIntl; - uiCapabilities: UICapabilities; - features: Feature[]; -} - -interface State { - role: Role; - formError: RoleValidationResult | null; -} - -class EditRolePageUI extends Component { - private validator: RoleValidator; - - constructor(props: Props) { - super(props); - - this.validator = new RoleValidator({ shouldValidate: false }); - - let role: Role; - if (props.action === 'clone') { - role = prepareRoleClone(props.role); - } else { - role = copyRole(props.role); - } - - this.state = { - role, - formError: null, - }; - } - - public componentDidMount() { - if (this.props.action === 'clone' && isReservedRole(this.props.role)) { - this.backToRoleList(); - } - } - - public render() { - const description = this.props.spacesEnabled ? ( - - ) : ( - - ); - - return ( -
- - {this.getFormTitle()} - - - - {description} - - {isReservedRole(this.state.role) && ( - - - -

- -

-
-
- )} - - - - {this.getRoleName()} - - {this.getElasticsearchPrivileges()} - - {this.getKibanaPrivileges()} - - - - {this.getFormButtons()} -
-
- ); - } - - private getFormTitle = () => { - let titleText; - const props: HTMLProps = { - tabIndex: 0, - }; - if (isReservedRole(this.state.role)) { - titleText = ( - - ); - props['aria-describedby'] = 'reservedRoleDescription'; - } else if (this.editingExistingRole()) { - titleText = ( - - ); - } else { - titleText = ( - - ); - } - - return ( - -

- {titleText} -

-
- ); - }; - - private getActionButton = () => { - if (this.editingExistingRole() && !isReadOnlyRole(this.state.role)) { - return ( - - - - ); - } - - return null; - }; - - private getRoleName = () => { - return ( - - - } - helpText={ - !isReservedRole(this.state.role) && this.editingExistingRole() ? ( - - ) : ( - undefined - ) - } - {...this.validator.validateRoleName(this.state.role)} - > - - - - ); - }; - - private onNameChange = (e: ChangeEvent) => { - const rawValue = e.target.value; - const name = rawValue.replace(/\s/g, '_'); - - this.setState({ - role: { - ...this.state.role, - name, - }, - }); - }; - - private getElasticsearchPrivileges() { - return ( -
- - -
- ); - } - - private onRoleChange = (role: Role) => { - this.setState({ - role, - }); - }; - - private getKibanaPrivileges = () => { - return ( -
- - -
- ); - }; - - private getFormButtons = () => { - if (isReadOnlyRole(this.state.role)) { - return this.getReturnToRoleListButton(); - } - - return ( - - {this.getSaveButton()} - {this.getCancelButton()} - - {this.getActionButton()} - - ); - }; - - private getReturnToRoleListButton = () => { - return ( - - - - ); - }; - - private getSaveButton = () => { - const saveText = this.editingExistingRole() ? ( - - ) : ( - - ); - - return ( - - {saveText} - - ); - }; - - private getCancelButton = () => { - return ( - - - - ); - }; - - private editingExistingRole = () => { - return !!this.props.role.name && this.props.action === 'edit'; - }; - - private saveRole = () => { - this.validator.enableValidation(); - - const result = this.validator.validateForSave(this.state.role); - if (result.isInvalid) { - this.setState({ - formError: result, - }); - } else { - this.setState({ - formError: null, - }); - - const { httpClient, intl, spacesEnabled } = this.props; - - saveRole(httpClient, this.state.role, spacesEnabled) - .then(() => { - toastNotifications.addSuccess( - intl.formatMessage({ - id: 'xpack.security.management.editRole.roleSuccessfullySavedNotificationMessage', - defaultMessage: 'Saved role', - }) - ); - this.backToRoleList(); - }) - .catch((error: any) => { - toastNotifications.addDanger(get(error, 'data.message')); - }); - } - }; - - private handleDeleteRole = () => { - const { httpClient, role, intl } = this.props; - - deleteRole(httpClient, role.name) - .then(() => { - toastNotifications.addSuccess( - intl.formatMessage({ - id: 'xpack.security.management.editRole.roleSuccessfullyDeletedNotificationMessage', - defaultMessage: 'Deleted role', - }) - ); - this.backToRoleList(); - }) - .catch((error: any) => { - toastNotifications.addDanger(get(error, 'data.message')); - }); - }; - - private backToRoleList = () => { - window.location.hash = ROLES_PATH; - }; -} - -export const EditRolePage = injectI18n(EditRolePageUI); diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/elasticsearch_privileges.test.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/elasticsearch_privileges.test.tsx deleted file mode 100644 index 5ba3d1daf61ac..0000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/elasticsearch_privileges.test.tsx +++ /dev/null @@ -1,96 +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 React from 'react'; -import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { RoleValidator } from '../../../lib/validate_role'; -import { ClusterPrivileges } from './cluster_privileges'; -import { ElasticsearchPrivileges } from './elasticsearch_privileges'; -import { IndexPrivileges } from './index_privileges'; - -test('it renders without crashing', () => { - const props = { - role: { - name: '', - elasticsearch: { - cluster: [], - indices: [], - run_as: [], - }, - kibana: [], - }, - editable: true, - httpClient: jest.fn(), - onChange: jest.fn(), - runAsUsers: [], - indexPatterns: [], - allowDocumentLevelSecurity: true, - allowFieldLevelSecurity: true, - validator: new RoleValidator(), - builtinESPrivileges: { - cluster: ['all', 'manage', 'monitor'], - index: ['all', 'read', 'write', 'index'], - }, - }; - const wrapper = shallowWithIntl(); - expect(wrapper).toMatchSnapshot(); -}); - -test('it renders ClusterPrivileges', () => { - const props = { - role: { - name: '', - elasticsearch: { - cluster: [], - indices: [], - run_as: [], - }, - kibana: [], - }, - editable: true, - httpClient: jest.fn(), - onChange: jest.fn(), - runAsUsers: [], - indexPatterns: [], - allowDocumentLevelSecurity: true, - allowFieldLevelSecurity: true, - validator: new RoleValidator(), - builtinESPrivileges: { - cluster: ['all', 'manage', 'monitor'], - index: ['all', 'read', 'write', 'index'], - }, - }; - const wrapper = mountWithIntl(); - expect(wrapper.find(ClusterPrivileges)).toHaveLength(1); -}); - -test('it renders IndexPrivileges', () => { - const props = { - role: { - name: '', - elasticsearch: { - cluster: [], - indices: [], - run_as: [], - }, - kibana: [], - }, - editable: true, - httpClient: jest.fn(), - onChange: jest.fn(), - runAsUsers: [], - indexPatterns: [], - allowDocumentLevelSecurity: true, - allowFieldLevelSecurity: true, - validator: new RoleValidator(), - builtinESPrivileges: { - cluster: ['all', 'manage', 'monitor'], - index: ['all', 'read', 'write', 'index'], - }, - }; - const wrapper = mountWithIntl(); - expect(wrapper.find(IndexPrivileges)).toHaveLength(1); -}); diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/edit_role.html b/x-pack/legacy/plugins/security/public/views/management/edit_role/edit_role.html deleted file mode 100644 index ca4073dcad6f5..0000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/edit_role.html +++ /dev/null @@ -1,3 +0,0 @@ - -
- diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/index.js b/x-pack/legacy/plugins/security/public/views/management/edit_role/index.js deleted file mode 100644 index 27c9beb4ba828..0000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/index.js +++ /dev/null @@ -1,176 +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 _ from 'lodash'; -import routes from 'ui/routes'; -import { capabilities } from 'ui/capabilities'; -import { kfetch } from 'ui/kfetch'; -import { fatalError, toastNotifications } from 'ui/notify'; -import { npStart } from 'ui/new_platform'; -import template from 'plugins/security/views/management/edit_role/edit_role.html'; -import 'plugins/security/services/shield_role'; -import 'plugins/security/services/shield_indices'; -import { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; -import { UserAPIClient } from '../../../lib/api'; -import { ROLES_PATH, CLONE_ROLES_PATH, EDIT_ROLES_PATH } from '../management_urls'; -import { getEditRoleBreadcrumbs, getCreateRoleBreadcrumbs } from '../breadcrumbs'; - -import { EditRolePage } from './components'; - -import React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; -import { I18nContext } from 'ui/i18n'; -import { i18n } from '@kbn/i18n'; - -const routeDefinition = action => ({ - template, - k7Breadcrumbs: ($injector, $route) => - $injector.invoke( - action === 'edit' && $route.current.params.name - ? getEditRoleBreadcrumbs - : getCreateRoleBreadcrumbs - ), - resolve: { - role($route, ShieldRole, Promise, kbnUrl) { - const name = $route.current.params.name; - - let role; - - if (name != null) { - role = ShieldRole.get({ name }).$promise.catch(response => { - if (response.status === 404) { - toastNotifications.addDanger({ - title: i18n.translate('xpack.security.management.roles.roleNotFound', { - defaultMessage: 'No "{roleName}" role found.', - values: { roleName: name }, - }), - }); - kbnUrl.redirect(ROLES_PATH); - } else { - return fatalError(response); - } - }); - } else { - role = Promise.resolve( - new ShieldRole({ - elasticsearch: { - cluster: [], - indices: [], - run_as: [], - }, - kibana: [], - _unrecognized_applications: [], - }) - ); - } - - return role.then(res => res.toJSON()); - }, - users() { - return new UserAPIClient().getUsers().then(users => _.map(users, 'username')); - }, - indexPatterns() { - return npStart.plugins.data.indexPatterns.getTitles(); - }, - spaces(spacesEnabled) { - if (spacesEnabled) { - return kfetch({ method: 'get', pathname: '/api/spaces/space' }); - } - return []; - }, - kibanaPrivileges() { - return kfetch({ - method: 'get', - pathname: '/api/security/privileges', - query: { includeActions: true }, - }); - }, - builtinESPrivileges() { - return kfetch({ method: 'get', pathname: '/internal/security/esPrivileges/builtin' }); - }, - features() { - return kfetch({ method: 'get', pathname: '/api/features' }).catch(e => { - // TODO: This check can be removed once all of these `resolve` entries are moved out of Angular and into the React app. - const unauthorizedForFeatures = _.get(e, 'body.statusCode') === 404; - if (unauthorizedForFeatures) { - return []; - } - throw e; - }); - }, - }, - controllerAs: 'editRole', - controller($injector, $scope, $http, enableSpaceAwarePrivileges) { - const $route = $injector.get('$route'); - const role = $route.current.locals.role; - - const allowDocumentLevelSecurity = xpackInfo.get( - 'features.security.allowRoleDocumentLevelSecurity' - ); - const allowFieldLevelSecurity = xpackInfo.get('features.security.allowRoleFieldLevelSecurity'); - if (role.elasticsearch.indices.length === 0) { - const emptyOption = { - names: [], - privileges: [], - }; - - if (allowFieldLevelSecurity) { - emptyOption.field_security = { - grant: ['*'], - except: [], - }; - } - - if (allowDocumentLevelSecurity) { - emptyOption.query = ''; - } - - role.elasticsearch.indices.push(emptyOption); - } - - const { - users, - indexPatterns, - spaces, - kibanaPrivileges, - builtinESPrivileges, - features, - } = $route.current.locals; - - $scope.$$postDigest(async () => { - const domNode = document.getElementById('editRoleReactRoot'); - - render( - - - , - domNode - ); - - // unmount react on controller destroy - $scope.$on('$destroy', () => { - unmountComponentAtNode(domNode); - }); - }); - }, -}); - -routes.when(`${CLONE_ROLES_PATH}/:name`, routeDefinition('clone')); -routes.when(`${EDIT_ROLES_PATH}/:name?`, routeDefinition('edit')); diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_user/_index.scss b/x-pack/legacy/plugins/security/public/views/management/edit_user/_index.scss deleted file mode 100644 index c5da74aa3f785..0000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/edit_user/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './users'; diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_user/edit_user.html b/x-pack/legacy/plugins/security/public/views/management/edit_user/edit_user.html deleted file mode 100644 index 4fa2768480874..0000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/edit_user/edit_user.html +++ /dev/null @@ -1,3 +0,0 @@ - -
- diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_user/edit_user.js b/x-pack/legacy/plugins/security/public/views/management/edit_user/edit_user.js deleted file mode 100644 index ab218022c6ee6..0000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/edit_user/edit_user.js +++ /dev/null @@ -1,58 +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 routes from 'ui/routes'; -import template from 'plugins/security/views/management/edit_user/edit_user.html'; -import 'angular-resource'; -import 'ui/angular_ui_select'; -import 'plugins/security/services/shield_role'; -import { EDIT_USERS_PATH } from '../management_urls'; -import { EditUserPage } from './components'; -import { UserAPIClient } from '../../../lib/api'; -import React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; -import { I18nContext } from 'ui/i18n'; -import { npSetup } from 'ui/new_platform'; -import { getEditUserBreadcrumbs, getCreateUserBreadcrumbs } from '../breadcrumbs'; - -const renderReact = (elem, changeUrl, username) => { - render( - - - , - elem - ); -}; - -routes.when(`${EDIT_USERS_PATH}/:username?`, { - template, - k7Breadcrumbs: ($injector, $route) => - $injector.invoke( - $route.current.params.username ? getEditUserBreadcrumbs : getCreateUserBreadcrumbs - ), - controllerAs: 'editUser', - controller($scope, $route, kbnUrl) { - $scope.$on('$destroy', () => { - const elem = document.getElementById('editUserReactRoot'); - if (elem) { - unmountComponentAtNode(elem); - } - }); - $scope.$$postDigest(() => { - const elem = document.getElementById('editUserReactRoot'); - const username = $route.current.params.username; - const changeUrl = url => { - kbnUrl.change(url); - $scope.$apply(); - }; - renderReact(elem, changeUrl, username); - }); - }, -}); diff --git a/x-pack/legacy/plugins/security/public/views/management/management.js b/x-pack/legacy/plugins/security/public/views/management/management.js deleted file mode 100644 index f0369f232aeba..0000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/management.js +++ /dev/null @@ -1,134 +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 'plugins/security/views/management/change_password_form/change_password_form'; -import 'plugins/security/views/management/password_form/password_form'; -import 'plugins/security/views/management/users_grid/users'; -import 'plugins/security/views/management/roles_grid/roles'; -import 'plugins/security/views/management/api_keys_grid/api_keys'; -import 'plugins/security/views/management/edit_user/edit_user'; -import 'plugins/security/views/management/edit_role/index'; -import 'plugins/security/views/management/role_mappings/role_mappings_grid'; -import 'plugins/security/views/management/role_mappings/edit_role_mapping'; -import routes from 'ui/routes'; -import { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; -import { ROLES_PATH, USERS_PATH, API_KEYS_PATH, ROLE_MAPPINGS_PATH } from './management_urls'; - -import { management } from 'ui/management'; -import { npSetup } from 'ui/new_platform'; -import { i18n } from '@kbn/i18n'; -import { toastNotifications } from 'ui/notify'; - -routes - .defaults(/^\/management\/security(\/|$)/, { - resolve: { - showLinks(kbnUrl, Promise) { - if (!xpackInfo.get('features.security.showLinks')) { - toastNotifications.addDanger({ - title: xpackInfo.get('features.security.linksMessage'), - }); - kbnUrl.redirect('/management'); - return Promise.halt(); - } - }, - }, - }) - .defaults(/\/management/, { - resolve: { - securityManagementSection: function() { - const showSecurityLinks = xpackInfo.get('features.security.showLinks'); - const showRoleMappingsManagementLink = xpackInfo.get( - 'features.security.showRoleMappingsManagement' - ); - - function deregisterSecurity() { - management.deregister('security'); - } - - function deregisterRoleMappingsManagement() { - if (management.hasItem('security')) { - const security = management.getSection('security'); - if (security.hasItem('roleMappings')) { - security.deregister('roleMappings'); - } - } - } - - function ensureSecurityRegistered() { - const registerSecurity = () => - management.register('security', { - display: i18n.translate('xpack.security.management.securityTitle', { - defaultMessage: 'Security', - }), - order: 100, - icon: 'securityApp', - }); - const getSecurity = () => management.getSection('security'); - - const security = management.hasItem('security') ? getSecurity() : registerSecurity(); - - if (!security.hasItem('users')) { - security.register('users', { - name: 'securityUsersLink', - order: 10, - display: i18n.translate('xpack.security.management.usersTitle', { - defaultMessage: 'Users', - }), - url: `#${USERS_PATH}`, - }); - } - - if (!security.hasItem('roles')) { - security.register('roles', { - name: 'securityRolesLink', - order: 20, - display: i18n.translate('xpack.security.management.rolesTitle', { - defaultMessage: 'Roles', - }), - url: `#${ROLES_PATH}`, - }); - } - - if (!security.hasItem('apiKeys')) { - security.register('apiKeys', { - name: 'securityApiKeysLink', - order: 30, - display: i18n.translate('xpack.security.management.apiKeysTitle', { - defaultMessage: 'API Keys', - }), - url: `#${API_KEYS_PATH}`, - }); - } - - if (showRoleMappingsManagementLink && !security.hasItem('roleMappings')) { - security.register('roleMappings', { - name: 'securityRoleMappingLink', - order: 30, - display: i18n.translate('xpack.security.management.roleMappingsTitle', { - defaultMessage: 'Role Mappings', - }), - url: `#${ROLE_MAPPINGS_PATH}`, - }); - } - } - - if (!showSecurityLinks) { - deregisterSecurity(); - } else { - if (!showRoleMappingsManagementLink) { - deregisterRoleMappingsManagement(); - } - - // getCurrentUser will reject if there is no authenticated user, so we prevent them from - // seeing the security management screens. - return npSetup.plugins.security.authc - .getCurrentUser() - .then(ensureSecurityRegistered) - .catch(deregisterSecurity); - } - }, - }, - }); diff --git a/x-pack/legacy/plugins/security/public/views/management/password_form/password_form.html b/x-pack/legacy/plugins/security/public/views/management/password_form/password_form.html deleted file mode 100644 index 72956992100f5..0000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/password_form/password_form.html +++ /dev/null @@ -1,53 +0,0 @@ - - -
- - - - -
-
- - -
- - - - -
-
-
diff --git a/x-pack/legacy/plugins/security/public/views/management/password_form/password_form.js b/x-pack/legacy/plugins/security/public/views/management/password_form/password_form.js deleted file mode 100644 index edcccdb5e6e69..0000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/password_form/password_form.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 { uiModules } from 'ui/modules'; -import template from './password_form.html'; - -const module = uiModules.get('security', ['kibana']); -module.directive('kbnPasswordForm', function() { - return { - template, - scope: { - password: '=', - }, - restrict: 'E', - replace: true, - controllerAs: 'passwordController', - controller: function() { - this.confirmation = null; - }, - }; -}); diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/_index.scss b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/_index.scss deleted file mode 100644 index 80e08ebcf1226..0000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './components/rule_editor_panel/index'; \ No newline at end of file diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/edit_role_mapping_page.test.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/edit_role_mapping_page.test.tsx deleted file mode 100644 index 375a8d9f374a8..0000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/edit_role_mapping_page.test.tsx +++ /dev/null @@ -1,341 +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 React from 'react'; -import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; -import { findTestSubject } from 'test_utils/find_test_subject'; - -// brace/ace uses the Worker class, which is not currently provided by JSDOM. -// This is not required for the tests to pass, but it rather suppresses lengthy -// warnings in the console which adds unnecessary noise to the test output. -import 'test_utils/stub_web_worker'; - -import { RoleMappingsAPI } from '../../../../../lib/role_mappings_api'; -import { EditRoleMappingPage } from '.'; -import { NoCompatibleRealms, SectionLoading, PermissionDenied } from '../../components'; -import { VisualRuleEditor } from './rule_editor_panel/visual_rule_editor'; -import { JSONRuleEditor } from './rule_editor_panel/json_rule_editor'; -import { EuiComboBox } from '@elastic/eui'; - -jest.mock('../../../../../lib/roles_api', () => { - return { - RolesApi: { - getRoles: () => Promise.resolve([{ name: 'foo_role' }, { name: 'bar role' }]), - }, - }; -}); - -describe('EditRoleMappingPage', () => { - it('allows a role mapping to be created', async () => { - const roleMappingsAPI = ({ - saveRoleMapping: jest.fn().mockResolvedValue(null), - checkRoleMappingFeatures: jest.fn().mockResolvedValue({ - canManageRoleMappings: true, - hasCompatibleRealms: true, - canUseInlineScripts: true, - canUseStoredScripts: true, - }), - } as unknown) as RoleMappingsAPI; - - const wrapper = mountWithIntl(); - - await nextTick(); - wrapper.update(); - - findTestSubject(wrapper, 'roleMappingFormNameInput').simulate('change', { - target: { value: 'my-role-mapping' }, - }); - - (wrapper - .find(EuiComboBox) - .filter('[data-test-subj="roleMappingFormRoleComboBox"]') - .props() as any).onChange([{ label: 'foo_role' }]); - - findTestSubject(wrapper, 'roleMappingsAddRuleButton').simulate('click'); - - findTestSubject(wrapper, 'saveRoleMappingButton').simulate('click'); - - expect(roleMappingsAPI.saveRoleMapping).toHaveBeenCalledWith({ - name: 'my-role-mapping', - enabled: true, - roles: ['foo_role'], - role_templates: [], - rules: { - all: [{ field: { username: '*' } }], - }, - metadata: {}, - }); - }); - - it('allows a role mapping to be updated', async () => { - const roleMappingsAPI = ({ - saveRoleMapping: jest.fn().mockResolvedValue(null), - getRoleMapping: jest.fn().mockResolvedValue({ - name: 'foo', - role_templates: [ - { - template: { id: 'foo' }, - }, - ], - enabled: true, - rules: { - any: [{ field: { 'metadata.someCustomOption': [false, true, 'asdf'] } }], - }, - metadata: { - foo: 'bar', - bar: 'baz', - }, - }), - checkRoleMappingFeatures: jest.fn().mockResolvedValue({ - canManageRoleMappings: true, - hasCompatibleRealms: true, - canUseInlineScripts: true, - canUseStoredScripts: true, - }), - } as unknown) as RoleMappingsAPI; - - const wrapper = mountWithIntl( - - ); - - await nextTick(); - wrapper.update(); - - findTestSubject(wrapper, 'switchToRolesButton').simulate('click'); - - (wrapper - .find(EuiComboBox) - .filter('[data-test-subj="roleMappingFormRoleComboBox"]') - .props() as any).onChange([{ label: 'foo_role' }]); - - findTestSubject(wrapper, 'roleMappingsAddRuleButton').simulate('click'); - wrapper.find('button[id="addRuleOption"]').simulate('click'); - - findTestSubject(wrapper, 'saveRoleMappingButton').simulate('click'); - - expect(roleMappingsAPI.saveRoleMapping).toHaveBeenCalledWith({ - name: 'foo', - enabled: true, - roles: ['foo_role'], - role_templates: [], - rules: { - any: [ - { field: { 'metadata.someCustomOption': [false, true, 'asdf'] } }, - { field: { username: '*' } }, - ], - }, - metadata: { - foo: 'bar', - bar: 'baz', - }, - }); - }); - - it('renders a permission denied message when unauthorized to manage role mappings', async () => { - const roleMappingsAPI = ({ - checkRoleMappingFeatures: jest.fn().mockResolvedValue({ - canManageRoleMappings: false, - hasCompatibleRealms: true, - }), - } as unknown) as RoleMappingsAPI; - - const wrapper = mountWithIntl(); - expect(wrapper.find(SectionLoading)).toHaveLength(1); - expect(wrapper.find(PermissionDenied)).toHaveLength(0); - - await nextTick(); - wrapper.update(); - - expect(wrapper.find(SectionLoading)).toHaveLength(0); - expect(wrapper.find(NoCompatibleRealms)).toHaveLength(0); - expect(wrapper.find(PermissionDenied)).toHaveLength(1); - }); - - it('renders a warning when there are no compatible realms enabled', async () => { - const roleMappingsAPI = ({ - checkRoleMappingFeatures: jest.fn().mockResolvedValue({ - canManageRoleMappings: true, - hasCompatibleRealms: false, - }), - } as unknown) as RoleMappingsAPI; - - const wrapper = mountWithIntl(); - expect(wrapper.find(SectionLoading)).toHaveLength(1); - expect(wrapper.find(NoCompatibleRealms)).toHaveLength(0); - - await nextTick(); - wrapper.update(); - - expect(wrapper.find(SectionLoading)).toHaveLength(0); - expect(wrapper.find(NoCompatibleRealms)).toHaveLength(1); - }); - - it('renders a warning when editing a mapping with a stored role template, when stored scripts are disabled', async () => { - const roleMappingsAPI = ({ - getRoleMapping: jest.fn().mockResolvedValue({ - name: 'foo', - role_templates: [ - { - template: { id: 'foo' }, - }, - ], - enabled: true, - rules: { - field: { username: '*' }, - }, - }), - checkRoleMappingFeatures: jest.fn().mockResolvedValue({ - canManageRoleMappings: true, - hasCompatibleRealms: true, - canUseInlineScripts: true, - canUseStoredScripts: false, - }), - } as unknown) as RoleMappingsAPI; - - const wrapper = mountWithIntl( - - ); - - expect(findTestSubject(wrapper, 'roleMappingInlineScriptsDisabled')).toHaveLength(0); - expect(findTestSubject(wrapper, 'roleMappingStoredScriptsDisabled')).toHaveLength(0); - - await nextTick(); - wrapper.update(); - - expect(findTestSubject(wrapper, 'roleMappingInlineScriptsDisabled')).toHaveLength(0); - expect(findTestSubject(wrapper, 'roleMappingStoredScriptsDisabled')).toHaveLength(1); - }); - - it('renders a warning when editing a mapping with an inline role template, when inline scripts are disabled', async () => { - const roleMappingsAPI = ({ - getRoleMapping: jest.fn().mockResolvedValue({ - name: 'foo', - role_templates: [ - { - template: { source: 'foo' }, - }, - ], - enabled: true, - rules: { - field: { username: '*' }, - }, - }), - checkRoleMappingFeatures: jest.fn().mockResolvedValue({ - canManageRoleMappings: true, - hasCompatibleRealms: true, - canUseInlineScripts: false, - canUseStoredScripts: true, - }), - } as unknown) as RoleMappingsAPI; - - const wrapper = mountWithIntl( - - ); - - expect(findTestSubject(wrapper, 'roleMappingInlineScriptsDisabled')).toHaveLength(0); - expect(findTestSubject(wrapper, 'roleMappingStoredScriptsDisabled')).toHaveLength(0); - - await nextTick(); - wrapper.update(); - - expect(findTestSubject(wrapper, 'roleMappingInlineScriptsDisabled')).toHaveLength(1); - expect(findTestSubject(wrapper, 'roleMappingStoredScriptsDisabled')).toHaveLength(0); - }); - - it('renders the visual editor by default for simple rule sets', async () => { - const roleMappingsAPI = ({ - getRoleMapping: jest.fn().mockResolvedValue({ - name: 'foo', - roles: ['superuser'], - enabled: true, - rules: { - all: [ - { - field: { - username: '*', - }, - }, - { - field: { - dn: null, - }, - }, - { - field: { - realm: ['ldap', 'pki', null, 12], - }, - }, - ], - }, - }), - checkRoleMappingFeatures: jest.fn().mockResolvedValue({ - canManageRoleMappings: true, - hasCompatibleRealms: true, - canUseInlineScripts: true, - canUseStoredScripts: true, - }), - } as unknown) as RoleMappingsAPI; - - const wrapper = mountWithIntl( - - ); - - await nextTick(); - wrapper.update(); - - expect(wrapper.find(VisualRuleEditor)).toHaveLength(1); - expect(wrapper.find(JSONRuleEditor)).toHaveLength(0); - }); - - it('renders the JSON editor by default for complex rule sets', async () => { - const createRule = (depth: number): Record => { - if (depth > 0) { - const rule = { - all: [ - { - field: { - username: '*', - }, - }, - ], - } as Record; - - const subRule = createRule(depth - 1); - if (subRule) { - rule.all.push(subRule); - } - - return rule; - } - return null as any; - }; - - const roleMappingsAPI = ({ - getRoleMapping: jest.fn().mockResolvedValue({ - name: 'foo', - roles: ['superuser'], - enabled: true, - rules: createRule(10), - }), - checkRoleMappingFeatures: jest.fn().mockResolvedValue({ - canManageRoleMappings: true, - hasCompatibleRealms: true, - canUseInlineScripts: true, - canUseStoredScripts: true, - }), - } as unknown) as RoleMappingsAPI; - - const wrapper = mountWithIntl( - - ); - - await nextTick(); - wrapper.update(); - - expect(wrapper.find(VisualRuleEditor)).toHaveLength(0); - expect(wrapper.find(JSONRuleEditor)).toHaveLength(1); - }); -}); diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/edit_role_mapping.html b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/edit_role_mapping.html deleted file mode 100644 index ca8ab9c35c49b..0000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/edit_role_mapping.html +++ /dev/null @@ -1,3 +0,0 @@ - -
- diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/index.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/index.tsx deleted file mode 100644 index b064a4dc50a22..0000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/index.tsx +++ /dev/null @@ -1,45 +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 React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; -import routes from 'ui/routes'; -import { I18nContext } from 'ui/i18n'; -import { npSetup } from 'ui/new_platform'; -import { RoleMappingsAPI } from '../../../../lib/role_mappings_api'; -// @ts-ignore -import template from './edit_role_mapping.html'; -import { CREATE_ROLE_MAPPING_PATH } from '../../management_urls'; -import { getEditRoleMappingBreadcrumbs } from '../../breadcrumbs'; -import { EditRoleMappingPage } from './components'; - -routes.when(`${CREATE_ROLE_MAPPING_PATH}/:name?`, { - template, - k7Breadcrumbs: getEditRoleMappingBreadcrumbs, - controller($scope, $route) { - $scope.$$postDigest(() => { - const domNode = document.getElementById('editRoleMappingReactRoot'); - - const { name } = $route.current.params; - - render( - - - , - domNode - ); - - // unmount react on controller destroy - $scope.$on('$destroy', () => { - if (domNode) { - unmountComponentAtNode(domNode); - } - }); - }); - }, -}); diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components/role_mappings_grid_page.test.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components/role_mappings_grid_page.test.tsx deleted file mode 100644 index 259cdc71e25a2..0000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components/role_mappings_grid_page.test.tsx +++ /dev/null @@ -1,182 +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 React from 'react'; -import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; -import { RoleMappingsGridPage } from '.'; -import { SectionLoading, PermissionDenied, NoCompatibleRealms } from '../../components'; -import { EmptyPrompt } from './empty_prompt'; -import { findTestSubject } from 'test_utils/find_test_subject'; -import { EuiLink } from '@elastic/eui'; -import { RoleMappingsAPI } from '../../../../../lib/role_mappings_api'; -import { act } from '@testing-library/react'; - -describe('RoleMappingsGridPage', () => { - it('renders an empty prompt when no role mappings exist', async () => { - const roleMappingsAPI = ({ - getRoleMappings: jest.fn().mockResolvedValue([]), - checkRoleMappingFeatures: jest.fn().mockResolvedValue({ - canManageRoleMappings: true, - hasCompatibleRealms: true, - }), - } as unknown) as RoleMappingsAPI; - - const wrapper = mountWithIntl(); - expect(wrapper.find(SectionLoading)).toHaveLength(1); - expect(wrapper.find(EmptyPrompt)).toHaveLength(0); - - await nextTick(); - wrapper.update(); - - expect(wrapper.find(SectionLoading)).toHaveLength(0); - expect(wrapper.find(NoCompatibleRealms)).toHaveLength(0); - expect(wrapper.find(EmptyPrompt)).toHaveLength(1); - }); - - it('renders a permission denied message when unauthorized to manage role mappings', async () => { - const roleMappingsAPI = ({ - checkRoleMappingFeatures: jest.fn().mockResolvedValue({ - canManageRoleMappings: false, - hasCompatibleRealms: true, - }), - } as unknown) as RoleMappingsAPI; - - const wrapper = mountWithIntl(); - expect(wrapper.find(SectionLoading)).toHaveLength(1); - expect(wrapper.find(PermissionDenied)).toHaveLength(0); - - await nextTick(); - wrapper.update(); - - expect(wrapper.find(SectionLoading)).toHaveLength(0); - expect(wrapper.find(NoCompatibleRealms)).toHaveLength(0); - expect(wrapper.find(PermissionDenied)).toHaveLength(1); - }); - - it('renders a warning when there are no compatible realms enabled', async () => { - const roleMappingsAPI = ({ - getRoleMappings: jest.fn().mockResolvedValue([ - { - name: 'some realm', - enabled: true, - roles: [], - rules: { field: { username: '*' } }, - }, - ]), - checkRoleMappingFeatures: jest.fn().mockResolvedValue({ - canManageRoleMappings: true, - hasCompatibleRealms: false, - }), - } as unknown) as RoleMappingsAPI; - - const wrapper = mountWithIntl(); - expect(wrapper.find(SectionLoading)).toHaveLength(1); - expect(wrapper.find(NoCompatibleRealms)).toHaveLength(0); - - await nextTick(); - wrapper.update(); - - expect(wrapper.find(SectionLoading)).toHaveLength(0); - expect(wrapper.find(NoCompatibleRealms)).toHaveLength(1); - }); - - it('renders links to mapped roles', async () => { - const roleMappingsAPI = ({ - getRoleMappings: jest.fn().mockResolvedValue([ - { - name: 'some realm', - enabled: true, - roles: ['superuser'], - rules: { field: { username: '*' } }, - }, - ]), - checkRoleMappingFeatures: jest.fn().mockResolvedValue({ - canManageRoleMappings: true, - hasCompatibleRealms: true, - }), - } as unknown) as RoleMappingsAPI; - - const wrapper = mountWithIntl(); - await nextTick(); - wrapper.update(); - - const links = findTestSubject(wrapper, 'roleMappingRoles').find(EuiLink); - expect(links).toHaveLength(1); - expect(links.at(0).props()).toMatchObject({ - href: '#/management/security/roles/edit/superuser', - }); - }); - - it('describes the number of mapped role templates', async () => { - const roleMappingsAPI = ({ - getRoleMappings: jest.fn().mockResolvedValue([ - { - name: 'some realm', - enabled: true, - role_templates: [{}, {}], - rules: { field: { username: '*' } }, - }, - ]), - checkRoleMappingFeatures: jest.fn().mockResolvedValue({ - canManageRoleMappings: true, - hasCompatibleRealms: true, - }), - } as unknown) as RoleMappingsAPI; - - const wrapper = mountWithIntl(); - await nextTick(); - wrapper.update(); - - const templates = findTestSubject(wrapper, 'roleMappingRoles'); - expect(templates).toHaveLength(1); - expect(templates.text()).toEqual(`2 role templates defined`); - }); - - it('allows role mappings to be deleted, refreshing the grid after', async () => { - const roleMappingsAPI = ({ - getRoleMappings: jest.fn().mockResolvedValue([ - { - name: 'some-realm', - enabled: true, - roles: ['superuser'], - rules: { field: { username: '*' } }, - }, - ]), - checkRoleMappingFeatures: jest.fn().mockResolvedValue({ - canManageRoleMappings: true, - hasCompatibleRealms: true, - }), - deleteRoleMappings: jest.fn().mockReturnValue( - Promise.resolve([ - { - name: 'some-realm', - success: true, - }, - ]) - ), - } as unknown) as RoleMappingsAPI; - - const wrapper = mountWithIntl(); - await nextTick(); - wrapper.update(); - - expect(roleMappingsAPI.getRoleMappings).toHaveBeenCalledTimes(1); - expect(roleMappingsAPI.deleteRoleMappings).not.toHaveBeenCalled(); - - findTestSubject(wrapper, `deleteRoleMappingButton-some-realm`).simulate('click'); - expect(findTestSubject(wrapper, 'deleteRoleMappingConfirmationModal')).toHaveLength(1); - - await act(async () => { - findTestSubject(wrapper, 'confirmModalConfirmButton').simulate('click'); - await nextTick(); - wrapper.update(); - }); - - expect(roleMappingsAPI.deleteRoleMappings).toHaveBeenCalledWith(['some-realm']); - // Expect an additional API call to refresh the grid - expect(roleMappingsAPI.getRoleMappings).toHaveBeenCalledTimes(2); - }); -}); diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/index.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/index.tsx deleted file mode 100644 index 9e925d0fa6dc0..0000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/index.tsx +++ /dev/null @@ -1,40 +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 React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; -import routes from 'ui/routes'; -import { I18nContext } from 'ui/i18n'; -import { npSetup } from 'ui/new_platform'; -import { RoleMappingsAPI } from '../../../../lib/role_mappings_api'; -// @ts-ignore -import template from './role_mappings.html'; -import { ROLE_MAPPINGS_PATH } from '../../management_urls'; -import { getRoleMappingBreadcrumbs } from '../../breadcrumbs'; -import { RoleMappingsGridPage } from './components'; - -routes.when(ROLE_MAPPINGS_PATH, { - template, - k7Breadcrumbs: getRoleMappingBreadcrumbs, - controller($scope) { - $scope.$$postDigest(() => { - const domNode = document.getElementById('roleMappingsGridReactRoot'); - - render( - - - , - domNode - ); - - // unmount react on controller destroy - $scope.$on('$destroy', () => { - if (domNode) { - unmountComponentAtNode(domNode); - } - }); - }); - }, -}); diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/role_mappings.html b/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/role_mappings.html deleted file mode 100644 index cff3b821d132c..0000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/role_mappings.html +++ /dev/null @@ -1,3 +0,0 @@ - -
- diff --git a/x-pack/legacy/plugins/security/public/views/management/roles_grid/roles.html b/x-pack/legacy/plugins/security/public/views/management/roles_grid/roles.html deleted file mode 100644 index 0552b655afafd..0000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/roles_grid/roles.html +++ /dev/null @@ -1,3 +0,0 @@ - -
- diff --git a/x-pack/legacy/plugins/security/public/views/management/roles_grid/roles.js b/x-pack/legacy/plugins/security/public/views/management/roles_grid/roles.js deleted file mode 100644 index e9c42824711b3..0000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/roles_grid/roles.js +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; -import routes from 'ui/routes'; -import template from 'plugins/security/views/management/roles_grid/roles.html'; -import { ROLES_PATH } from '../management_urls'; -import { getRolesBreadcrumbs } from '../breadcrumbs'; -import { I18nContext } from 'ui/i18n'; -import { RolesGridPage } from './components'; - -routes.when(ROLES_PATH, { - template, - k7Breadcrumbs: getRolesBreadcrumbs, - controller($scope) { - $scope.$$postDigest(() => { - const domNode = document.getElementById('rolesGridReactRoot'); - - render( - - - , - domNode - ); - - // unmount react on controller destroy - $scope.$on('$destroy', () => { - unmountComponentAtNode(domNode); - }); - }); - }, -}); diff --git a/x-pack/legacy/plugins/security/public/views/management/users_grid/users.html b/x-pack/legacy/plugins/security/public/views/management/users_grid/users.html deleted file mode 100644 index 3dce7326d001a..0000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/users_grid/users.html +++ /dev/null @@ -1,3 +0,0 @@ - -
- diff --git a/x-pack/legacy/plugins/security/public/views/management/users_grid/users.js b/x-pack/legacy/plugins/security/public/views/management/users_grid/users.js deleted file mode 100644 index 8d4e0526251d7..0000000000000 --- a/x-pack/legacy/plugins/security/public/views/management/users_grid/users.js +++ /dev/null @@ -1,47 +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 React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; -import routes from 'ui/routes'; -import template from 'plugins/security/views/management/users_grid/users.html'; -import { SECURITY_PATH, USERS_PATH } from '../management_urls'; -import { UsersListPage } from './components'; -import { UserAPIClient } from '../../../lib/api'; -import { I18nContext } from 'ui/i18n'; -import { getUsersBreadcrumbs } from '../breadcrumbs'; - -routes.when(SECURITY_PATH, { - redirectTo: USERS_PATH, -}); - -const renderReact = (elem, changeUrl) => { - render( - - - , - elem - ); -}; - -routes.when(USERS_PATH, { - template, - k7Breadcrumbs: getUsersBreadcrumbs, - controller($scope, $http, kbnUrl) { - $scope.$on('$destroy', () => { - const elem = document.getElementById('usersReactRoot'); - if (elem) unmountComponentAtNode(elem); - }); - $scope.$$postDigest(() => { - const elem = document.getElementById('usersReactRoot'); - const changeUrl = url => { - kbnUrl.change(url); - $scope.$apply(); - }; - renderReact(elem, $http, changeUrl); - }); - }, -}); diff --git a/x-pack/legacy/plugins/security/public/views/overwritten_session/overwritten_session.tsx b/x-pack/legacy/plugins/security/public/views/overwritten_session/overwritten_session.tsx index fb39c517e1c2c..4c79c499cc0e6 100644 --- a/x-pack/legacy/plugins/security/public/views/overwritten_session/overwritten_session.tsx +++ b/x-pack/legacy/plugins/security/public/views/overwritten_session/overwritten_session.tsx @@ -11,8 +11,7 @@ import { render } from 'react-dom'; import chrome from 'ui/chrome'; import { I18nContext } from 'ui/i18n'; import { npSetup } from 'ui/new_platform'; -import { SecurityPluginSetup } from '../../../../../../plugins/security/public'; -import { AuthenticatedUser } from '../../../common/model'; +import { AuthenticatedUser, SecurityPluginSetup } from '../../../../../../plugins/security/public'; import { AuthenticationStatePage } from '../../components/authentication_state_page'; chrome diff --git a/x-pack/legacy/plugins/siem/common/constants.ts b/x-pack/legacy/plugins/siem/common/constants.ts index e67b533e46bce..03824dd4e3960 100644 --- a/x-pack/legacy/plugins/siem/common/constants.ts +++ b/x-pack/legacy/plugins/siem/common/constants.ts @@ -60,7 +60,8 @@ export const DETECTION_ENGINE_PREPACKAGED_URL = `${DETECTION_ENGINE_RULES_URL}/p export const DETECTION_ENGINE_PRIVILEGES_URL = `${DETECTION_ENGINE_URL}/privileges`; export const DETECTION_ENGINE_INDEX_URL = `${DETECTION_ENGINE_URL}/index`; export const DETECTION_ENGINE_TAGS_URL = `${DETECTION_ENGINE_URL}/tags`; -export const DETECTION_ENGINE_RULES_STATUS = `${DETECTION_ENGINE_URL}/rules/_find_statuses`; +export const DETECTION_ENGINE_RULES_STATUS_URL = `${DETECTION_ENGINE_RULES_URL}/_find_statuses`; +export const DETECTION_ENGINE_PREPACKAGED_RULES_STATUS_URL = `${DETECTION_ENGINE_RULES_URL}/prepackaged/_status`; /** * Default signals index key for kibana.dev.yml diff --git a/x-pack/legacy/plugins/siem/cypress/README.md b/x-pack/legacy/plugins/siem/cypress/README.md index c9e0d4e18f78f..a57fe0d361b8f 100644 --- a/x-pack/legacy/plugins/siem/cypress/README.md +++ b/x-pack/legacy/plugins/siem/cypress/README.md @@ -22,29 +22,29 @@ automatically when you submit a PR. Smoke Tests are located in `siem/cypress/integration/smoke_tests` -## Test Helpers +## Structure -_Test helpers_ are functions that may be re-used across tests. +### Tasks -- Reusable code and CSS selectors should be added to -`siem/cypress/integration/lib`, as described below. +_Tasks_ are functions that my be re-used across tests. Inside the _tasks_ folder there are some other folders that represents +the page to which we will perform the actions. For each folder we are going to create a file for each one of the sections that + has the page. -### Reusable Test Helper Functions and CSS Selectors +i.e. +- tasks + - hosts + - events.ts -The `cypress/integration/lib` directory contains code intended to be re-used -across many different tests. Add reusable test helper functions and CSS -selectors to directories under `cypress/integration/lib`. +### Screens -- Files named `helpers.ts` (e.g. `siem/cypress/integration/lib/login/helpers.ts`) -contain functions (e.g. `login`) that may be imported and invoked from multiple tests. +In _screens_ folder we are going to find all the elements we want to interact in our tests. Inside _screens_ fonder there +are some other folders that represents the page that contains the elements the tests are going to interact with. For each +folder we are going to create a file for each one of the sections that the page has. -- Files named `selectors.ts` export CSS selectors for re-use. For example, -`siem/cypress/integration/lib/login/selectors.ts` exports the following selector -that matches the Username text area in the Kibana login page: - -```sh -export const USERNAME = '[data-test-subj="loginUsername"]'; -``` +i.e. +- tasks + - hosts + - events.ts ## Mock Data diff --git a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/events_viewer/events_viewer.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/events_viewer/events_viewer.spec.ts index 1450ee8dc8abf..bf141a9f0a0bf 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/events_viewer/events_viewer.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/events_viewer/events_viewer.spec.ts @@ -9,25 +9,31 @@ import { FIELDS_BROWSER_CONTAINER, FIELDS_BROWSER_SELECTED_CATEGORY_TITLE, FIELDS_BROWSER_TITLE, -} from '../../lib/fields_browser/selectors'; + FIELDS_BROWSER_CHECKBOX, +} from '../../../screens/hosts/fields_browser'; import { HOSTS_PAGE } from '../../lib/urls'; -import { loginAndWaitForPage, DEFAULT_TIMEOUT } from '../../lib/util/helpers'; +import { loginAndWaitForPage } from '../../../tasks/login'; +import { openEventsViewerFieldsBrowser, filterSearchBar } from '../../lib/events_viewer/helpers'; +import { closeFieldsBrowser } from '../../../tasks/hosts/fields_browsers'; +import { openEvents } from '../../../tasks/hosts/main'; import { - clickOutsideFieldsBrowser, - openEventsViewerFieldsBrowser, - filterSearchBar, -} from '../../lib/events_viewer/helpers'; + closeModal, + opensInspectQueryModal, + waitsForEventsToBeLoaded, + addsHostGeoCityNameToHeader, + addsHostGeoCountryNameToHeader, + resetFields, +} from '../../../tasks/hosts/events'; + import { - CLOSE_MODAL, - EVENTS_VIEWER_PANEL, HEADER_SUBTITLE, INSPECT_MODAL, - INSPECT_QUERY, LOAD_MORE, LOCAL_EVENTS_COUNT, -} from '../../lib/events_viewer/selectors'; -import { SERVER_SIDE_EVENT_COUNT } from '../../lib/timeline/selectors'; -import { clickEventsTab } from '../../lib/hosts/helpers'; + HOST_GEO_CITY_NAME_HEADER, + HOST_GEO_COUNTRY_NAME_HEADER, +} from '../../../screens/hosts/events'; +import { DEFAULT_TIMEOUT } from '../../lib/util/helpers'; const defaultHeadersInDefaultEcsCategory = [ { id: '@timestamp' }, @@ -43,7 +49,7 @@ describe('Events Viewer', () => { context('Fields rendering', () => { before(() => { loginAndWaitForPage(HOSTS_PAGE); - clickEventsTab(); + openEvents(); }); beforeEach(() => { @@ -51,7 +57,7 @@ describe('Events Viewer', () => { }); afterEach(() => { - clickOutsideFieldsBrowser(); + closeFieldsBrowser(); cy.get(FIELDS_BROWSER_CONTAINER).should('not.exist'); }); @@ -69,7 +75,7 @@ describe('Events Viewer', () => { it('displays a checked checkbox for all of the default events viewer columns that are also in the default ECS category', () => { defaultHeadersInDefaultEcsCategory.forEach(header => - cy.get(`[data-test-subj="field-${header.id}-checkbox"]`).should('be.checked') + cy.get(FIELDS_BROWSER_CHECKBOX(header.id)).should('be.checked') ); }); }); @@ -77,26 +83,17 @@ describe('Events Viewer', () => { context('Events viewer query modal', () => { before(() => { loginAndWaitForPage(HOSTS_PAGE); - clickEventsTab(); + openEvents(); }); after(() => { - cy.get(CLOSE_MODAL).click(); + closeModal(); cy.get(INSPECT_MODAL, { timeout: DEFAULT_TIMEOUT }).should('not.exist'); }); it('launches the inspect query modal when the inspect button is clicked', () => { - // wait for data to load - cy.get(SERVER_SIDE_EVENT_COUNT, { timeout: DEFAULT_TIMEOUT }) - .should('exist') - .invoke('text', { timeout: DEFAULT_TIMEOUT }) - .should('not.equal', '0'); - - cy.get(INSPECT_QUERY, { timeout: DEFAULT_TIMEOUT }) - .should('exist') - .trigger('mousemove', { force: true }) - .click({ force: true }); - + waitsForEventsToBeLoaded(); + opensInspectQueryModal(); cy.get(INSPECT_MODAL, { timeout: DEFAULT_TIMEOUT }).should('exist'); }); }); @@ -104,7 +101,7 @@ describe('Events Viewer', () => { context('Events viewer fields behaviour', () => { before(() => { loginAndWaitForPage(HOSTS_PAGE); - clickEventsTab(); + openEvents(); }); beforeEach(() => { @@ -113,89 +110,55 @@ describe('Events Viewer', () => { it('adds a field to the events viewer when the user clicks the checkbox', () => { const filterInput = 'host.geo.c'; - const toggleField = 'host.geo.city_name'; filterFieldsBrowser(filterInput); - - cy.get(`${EVENTS_VIEWER_PANEL} [data-test-subj="header-text-${toggleField}"]`).should( - 'not.exist' - ); - - cy.get(`${EVENTS_VIEWER_PANEL} [data-test-subj="field-${toggleField}-checkbox"]`).check({ - force: true, - }); - - clickOutsideFieldsBrowser(); - - cy.get(`${EVENTS_VIEWER_PANEL} [data-test-subj="header-text-${toggleField}"]`).should( - 'exist' - ); + cy.get(HOST_GEO_CITY_NAME_HEADER).should('not.exist'); + addsHostGeoCityNameToHeader(); + closeFieldsBrowser(); + cy.get(HOST_GEO_CITY_NAME_HEADER).should('exist'); }); it('resets all fields in the events viewer when `Reset Fields` is clicked', () => { const filterInput = 'host.geo.c'; - const toggleField = 'host.geo.country_name'; filterFieldsBrowser(filterInput); - - cy.get(`${EVENTS_VIEWER_PANEL} [data-test-subj="header-text-${toggleField}"]`).should( - 'not.exist' - ); - - cy.get(`${EVENTS_VIEWER_PANEL} [data-test-subj="field-${toggleField}-checkbox"]`).check({ - force: true, - }); - - cy.get(`${EVENTS_VIEWER_PANEL} [data-test-subj="reset-fields"]`).click({ force: true }); - - cy.get(`${EVENTS_VIEWER_PANEL} [data-test-subj="header-text-${toggleField}"]`).should( - 'not.exist' - ); + cy.get(HOST_GEO_COUNTRY_NAME_HEADER).should('not.exist'); + addsHostGeoCountryNameToHeader(); + resetFields(); + cy.get(HOST_GEO_COUNTRY_NAME_HEADER).should('not.exist'); }); }); context('Events behaviour', () => { before(() => { loginAndWaitForPage(HOSTS_PAGE); - clickEventsTab(); + openEvents(); }); it('filters the events by applying filter criteria from the search bar at the top of the page', () => { const filterInput = '4bf34c1c-eaa9-46de-8921-67a4ccc49829'; // this will never match real data - + waitsForEventsToBeLoaded(); cy.get(HEADER_SUBTITLE) .invoke('text') - .then(text1 => { - cy.get(HEADER_SUBTITLE) - .invoke('text', { timeout: DEFAULT_TIMEOUT }) - .should('not.equal', 'Showing: 0 events'); - + .then(initialNumberOfEvents => { filterSearchBar(filterInput); - cy.get(HEADER_SUBTITLE) .invoke('text') - .should(text2 => { - expect(text1).not.to.eq(text2); - }); + .should('not.equal', initialNumberOfEvents); }); }); it('loads more events when the load more button is clicked', () => { - cy.get(LOCAL_EVENTS_COUNT, { timeout: DEFAULT_TIMEOUT }) + const defaultNumberOfLoadedEvents = '25'; + cy.get(LOCAL_EVENTS_COUNT) .invoke('text') - .then(text1 => { - cy.get(LOCAL_EVENTS_COUNT) - .invoke('text') - .should('equal', '25'); + .should('equal', defaultNumberOfLoadedEvents); - cy.get(LOAD_MORE).click({ force: true }); + cy.get(LOAD_MORE).click({ force: true }); - cy.get(LOCAL_EVENTS_COUNT) - .invoke('text') - .should(text2 => { - expect(text1).not.to.eq(text2); - }); - }); + cy.get(LOCAL_EVENTS_COUNT) + .invoke('text') + .should('not.equal', defaultNumberOfLoadedEvents); }); }); }); diff --git a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/ml_conditional_links/ml_conditional_links.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/ml_conditional_links/ml_conditional_links.spec.ts index afeb8c3c13a4f..142729189e49b 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/ml_conditional_links/ml_conditional_links.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/ml_conditional_links/ml_conditional_links.spec.ts @@ -99,7 +99,7 @@ describe('ml conditional links', () => { loginAndWaitForPage(mlNetworkSingleIpNullKqlQuery); cy.url().should( 'include', - '/app/siem#/network/ip/127.0.0.1?timerange=(global:(linkTo:!(timeline),timerange:(from:1566990000000,kind:absolute,to:1567000799999)),timeline:(linkTo:!(global),timerange:(from:1566990000000,kind:absolute,to:1567000799999)))' + '/app/siem#/network/ip/127.0.0.1/source?timerange=(global:(linkTo:!(timeline),timerange:(from:1566990000000,kind:absolute,to:1567000799999)),timeline:(linkTo:!(global),timerange:(from:1566990000000,kind:absolute,to:1567000799999)))' ); }); @@ -107,7 +107,7 @@ describe('ml conditional links', () => { loginAndWaitForPage(mlNetworkSingleIpKqlQuery); cy.url().should( 'include', - "/app/siem#/network/ip/127.0.0.1?query=(language:kuery,query:'(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)')&timerange=(global:(linkTo:!(timeline),timerange:(from:1566990000000,kind:absolute,to:1567000799999)),timeline:(linkTo:!(global),timerange:(from:1566990000000,kind:absolute,to:1567000799999)))" + "/app/siem#/network/ip/127.0.0.1/source?query=(language:kuery,query:'(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)')&timerange=(global:(linkTo:!(timeline),timerange:(from:1566990000000,kind:absolute,to:1567000799999)),timeline:(linkTo:!(global),timerange:(from:1566990000000,kind:absolute,to:1567000799999)))" ); }); diff --git a/x-pack/legacy/plugins/siem/cypress/screens/hosts/events.ts b/x-pack/legacy/plugins/siem/cypress/screens/hosts/events.ts new file mode 100644 index 0000000000000..034c1453fc979 --- /dev/null +++ b/x-pack/legacy/plugins/siem/cypress/screens/hosts/events.ts @@ -0,0 +1,36 @@ +/* + * 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 const EVENTS_VIEWER_PANEL = '[data-test-subj="events-viewer-panel"]'; + +export const CLOSE_MODAL = '[data-test-subj="modal-inspect-close"]'; + +export const HEADER_SUBTITLE = `${EVENTS_VIEWER_PANEL} [data-test-subj="header-panel-subtitle"]`; + +export const INSPECT_MODAL = '[data-test-subj="modal-inspect-euiModal"]'; + +export const INSPECT_QUERY = `${EVENTS_VIEWER_PANEL} [data-test-subj="inspect-icon-button"]`; + +export const LOAD_MORE = `${EVENTS_VIEWER_PANEL} [data-test-subj="TimelineMoreButton"]`; + +export const LOCAL_EVENTS_COUNT = `${EVENTS_VIEWER_PANEL} [data-test-subj="local-events-count"]`; + +export const SERVER_SIDE_EVENT_COUNT = '[data-test-subj="server-side-event-count"]'; + +export const HOST_GEO_CITY_NAME_HEADER = '[data-test-subj="header-text-host.geo.city_name"]'; + +export const HOST_GEO_CITY_NAME_CHECKBOX = '[data-test-subj="field-host.geo.city_name-checkbox"]'; + +export const HOST_GEO_COUNTRY_NAME_HEADER = '[data-test-subj="header-text-host.geo.country_name"]'; + +export const HOST_GEO_COUNTRY_NAME_CHECKBOX = + '[data-test-subj="field-host.geo.country_name-checkbox"]'; + +export const FIELDS_BROWSER_CONTAINER = '[data-test-subj="fields-browser-container"]'; + +export const EVENTS_VIEWER_FIELDS_BUTTON = `${EVENTS_VIEWER_PANEL} [data-test-subj="show-field-browser-gear"]`; + +export const RESET_FIELDS = `${EVENTS_VIEWER_PANEL} [data-test-subj="reset-fields"]`; diff --git a/x-pack/legacy/plugins/siem/cypress/screens/hosts/fields_browser.ts b/x-pack/legacy/plugins/siem/cypress/screens/hosts/fields_browser.ts new file mode 100644 index 0000000000000..f4da73ba5e5f9 --- /dev/null +++ b/x-pack/legacy/plugins/siem/cypress/screens/hosts/fields_browser.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/** Clicking this button in the timeline opens the Fields browser */ +export const TIMELINE_FIELDS_BUTTON = + '[data-test-subj="timeline"] [data-test-subj="show-field-browser"]'; + +/** The title displayed in the fields browser (i.e. Customize Columns) */ +export const FIELDS_BROWSER_TITLE = '[data-test-subj="field-browser-title"]'; + +/** Contains the body of the fields browser */ +export const FIELDS_BROWSER_CONTAINER = '[data-test-subj="fields-browser-container"]'; + +/** The title of the selected category in the right-hand side of the fields browser */ +export const FIELDS_BROWSER_SELECTED_CATEGORY_TITLE = '[data-test-subj="selected-category-title"]'; + +export const FIELDS_BROWSER_CHECKBOX = (id: string) => { + return `[data-test-subj="field-${id}-checkbox`; +}; diff --git a/x-pack/legacy/plugins/siem/cypress/screens/hosts/main.ts b/x-pack/legacy/plugins/siem/cypress/screens/hosts/main.ts new file mode 100644 index 0000000000000..e80ecdac272cd --- /dev/null +++ b/x-pack/legacy/plugins/siem/cypress/screens/hosts/main.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const EVENTS_TAB = '[data-test-subj="navigation-events"]'; + +export const KQL_SEARCH_BAR = '[data-test-subj="queryInput"]'; diff --git a/x-pack/legacy/plugins/siem/cypress/tasks/hosts/events.ts b/x-pack/legacy/plugins/siem/cypress/tasks/hosts/events.ts new file mode 100644 index 0000000000000..d84d62f82d2e6 --- /dev/null +++ b/x-pack/legacy/plugins/siem/cypress/tasks/hosts/events.ts @@ -0,0 +1,66 @@ +/* + * 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 { DEFAULT_TIMEOUT } from '../../integration/lib/util/helpers'; +import { + EVENTS_VIEWER_FIELDS_BUTTON, + CLOSE_MODAL, + INSPECT_QUERY, + SERVER_SIDE_EVENT_COUNT, + HOST_GEO_CITY_NAME_CHECKBOX, + HOST_GEO_COUNTRY_NAME_CHECKBOX, + FIELDS_BROWSER_CONTAINER, + RESET_FIELDS, + LOAD_MORE, +} from '../../screens/hosts/events'; + +export const closeModal = () => { + cy.get(CLOSE_MODAL, { timeout: DEFAULT_TIMEOUT }).click(); +}; + +export const opensInspectQueryModal = () => { + cy.get(INSPECT_QUERY, { timeout: DEFAULT_TIMEOUT }) + .should('exist') + .trigger('mousemove', { force: true }) + .click({ force: true }); +}; + +export const waitsForEventsToBeLoaded = () => { + cy.get(SERVER_SIDE_EVENT_COUNT, { timeout: DEFAULT_TIMEOUT }) + .should('exist') + .invoke('text', { timeout: DEFAULT_TIMEOUT }) + .should('not.equal', '0'); +}; + +export const addsHostGeoCityNameToHeader = () => { + cy.get(HOST_GEO_CITY_NAME_CHECKBOX).check({ + force: true, + }); +}; + +export const addsHostGeoCountryNameToHeader = () => { + cy.get(HOST_GEO_COUNTRY_NAME_CHECKBOX).check({ + force: true, + }); +}; + +export const resetFields = () => { + cy.get(RESET_FIELDS).click({ force: true }); +}; + +export const openEventsViewerFieldsBrowser = () => { + cy.get(EVENTS_VIEWER_FIELDS_BUTTON, { timeout: DEFAULT_TIMEOUT }).click({ force: true }); + + cy.get(SERVER_SIDE_EVENT_COUNT, { timeout: DEFAULT_TIMEOUT }) + .invoke('text') + .should('not.equal', '0'); + + cy.get(FIELDS_BROWSER_CONTAINER).should('exist'); +}; + +export const loadMoreEvents = () => { + cy.get(LOAD_MORE).click({ force: true }); +}; diff --git a/x-pack/legacy/plugins/siem/cypress/tasks/hosts/fields_browsers.ts b/x-pack/legacy/plugins/siem/cypress/tasks/hosts/fields_browsers.ts new file mode 100644 index 0000000000000..ae3ed2010a0ae --- /dev/null +++ b/x-pack/legacy/plugins/siem/cypress/tasks/hosts/fields_browsers.ts @@ -0,0 +1,12 @@ +/* + * 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 { DEFAULT_TIMEOUT } from '../../integration/lib/util/helpers'; +import { KQL_SEARCH_BAR } from '../../screens/hosts/main'; + +export const closeFieldsBrowser = () => { + cy.get(KQL_SEARCH_BAR, { timeout: DEFAULT_TIMEOUT }).click(); +}; diff --git a/x-pack/legacy/plugins/siem/cypress/tasks/hosts/main.ts b/x-pack/legacy/plugins/siem/cypress/tasks/hosts/main.ts new file mode 100644 index 0000000000000..d95ae837a3de6 --- /dev/null +++ b/x-pack/legacy/plugins/siem/cypress/tasks/hosts/main.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { DEFAULT_TIMEOUT } from '../../integration/lib/util/helpers'; + +import { EVENTS_TAB } from '../../screens/hosts/main'; + +/** Clicks the Events tab on the hosts page */ +export const openEvents = () => + cy.get(EVENTS_TAB, { timeout: DEFAULT_TIMEOUT }).click({ force: true }); diff --git a/x-pack/legacy/plugins/siem/cypress/tasks/login.ts b/x-pack/legacy/plugins/siem/cypress/tasks/login.ts new file mode 100644 index 0000000000000..d5bf1178d5bdd --- /dev/null +++ b/x-pack/legacy/plugins/siem/cypress/tasks/login.ts @@ -0,0 +1,24 @@ +/* + * 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 { login } from '../integration/lib/login/helpers'; + +/** The default time in ms to wait for a Cypress command to complete */ +export const DEFAULT_TIMEOUT = 30 * 1000; + +/** + * Authenticates with Kibana, visits the specified `url`, and waits for the + * Kibana logo to be displayed before continuing + */ +export const loginAndWaitForPage = (url: string) => { + login(); + + cy.visit(`${Cypress.config().baseUrl}${url}`); + + cy.viewport('macbook-15'); + + cy.contains('a', 'SIEM', { timeout: DEFAULT_TIMEOUT }); +}; diff --git a/x-pack/legacy/plugins/siem/index.ts b/x-pack/legacy/plugins/siem/index.ts index edbb62feb580f..cd9b7f59226b0 100644 --- a/x-pack/legacy/plugins/siem/index.ts +++ b/x-pack/legacy/plugins/siem/index.ts @@ -32,6 +32,7 @@ import { } from './common/constants'; import { defaultIndexPattern } from './default_index_pattern'; import { initServerWithKibana } from './server/kibana.index'; +import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/utils'; // eslint-disable-next-line @typescript-eslint/no-explicit-any export const siem = (kibana: any) => { @@ -62,6 +63,7 @@ export const siem = (kibana: any) => { order: 9000, title: APP_NAME, url: `/app/${APP_ID}`, + category: DEFAULT_APP_CATEGORIES.security, }, ], uiSettingDefaults: { @@ -151,11 +153,11 @@ export const siem = (kibana: any) => { const { config, newPlatform, plugins, route } = server; const { coreContext, env, setup } = newPlatform; const initializerContext = { ...coreContext, env } as PluginInitializerContext; - const serverFacade = { config, plugins: { alerting: plugins.alerting, + actions: newPlatform.start.plugins.actions, elasticsearch: plugins.elasticsearch, spaces: plugins.spaces, savedObjects: server.savedObjects.SavedObjectsClient, diff --git a/x-pack/legacy/plugins/siem/public/components/alerts_viewer/alerts_table.tsx b/x-pack/legacy/plugins/siem/public/components/alerts_viewer/alerts_table.tsx index 1901101957307..65ade52ef7d3c 100644 --- a/x-pack/legacy/plugins/siem/public/components/alerts_viewer/alerts_table.tsx +++ b/x-pack/legacy/plugins/siem/public/components/alerts_viewer/alerts_table.tsx @@ -64,6 +64,7 @@ const AlertsTableComponent: React.FC = ({ endDate, startDate, pageFilters documentType: i18n.ALERTS_DOCUMENT_TYPE, footerText: i18n.TOTAL_COUNT_OF_ALERTS, title: i18n.ALERTS_TABLE_TITLE, + unit: i18n.UNIT, }), [] ); diff --git a/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.tsx b/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.tsx index 05a33eeba1434..163b345da40bd 100644 --- a/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.tsx +++ b/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.tsx @@ -154,20 +154,18 @@ const EventsViewerComponent: React.FC = ({ const totalCountMinusDeleted = totalCount > 0 ? totalCount - deletedEventIds.length : 0; + const subtitle = `${ + i18n.SHOWING + }: ${totalCountMinusDeleted.toLocaleString()} ${timelineTypeContext.unit?.( + totalCountMinusDeleted + ) ?? i18n.UNIT(totalCountMinusDeleted)}`; + // TODO: Reset eventDeletedIds/eventLoadingIds on refresh/loadmore (getUpdatedAt) return ( <> {headerFilterGroup} diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/header.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/header.tsx index 45b331f133e85..ccf6ec67521b0 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/header.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/header.tsx @@ -12,13 +12,16 @@ import { EuiText, EuiTitle, } from '@elastic/eui'; -import React from 'react'; +import React, { useCallback } from 'react'; import styled from 'styled-components'; import { BrowserFields } from '../../containers/source'; +import { signalsHeaders } from '../../pages/detection_engine/components/signals/default_config'; +import { alertsHeaders } from '../alerts_viewer/default_headers'; import { defaultHeaders as eventsDefaultHeaders } from '../events_viewer/default_headers'; import { defaultHeaders } from '../timeline/body/column_headers/default_headers'; import { OnUpdateColumns } from '../timeline/events'; +import { useTimelineTypeContext } from '../timeline/timeline_context'; import { getFieldBrowserSearchInputClassName, getFieldCount, SEARCH_INPUT_WIDTH } from './helpers'; @@ -96,27 +99,44 @@ const TitleRow = React.memo<{ isEventViewer?: boolean; onOutsideClick: () => void; onUpdateColumns: OnUpdateColumns; -}>(({ isEventViewer, onOutsideClick, onUpdateColumns }) => ( - - - -

{i18n.CUSTOMIZE_COLUMNS}

-
-
- - - { - onUpdateColumns(isEventViewer ? eventsDefaultHeaders : defaultHeaders); - onOutsideClick(); - }} - > - {i18n.RESET_FIELDS} - - -
-)); +}>(({ isEventViewer, onOutsideClick, onUpdateColumns }) => { + const timelineTypeContext = useTimelineTypeContext(); + const handleResetColumns = useCallback(() => { + let resetDefaultHeaders = defaultHeaders; + if (isEventViewer) { + if (timelineTypeContext.documentType?.toLocaleLowerCase() === 'alerts') { + resetDefaultHeaders = alertsHeaders; + } else if (timelineTypeContext.documentType?.toLocaleLowerCase() === 'signals') { + resetDefaultHeaders = signalsHeaders; + } else { + resetDefaultHeaders = eventsDefaultHeaders; + } + } + onUpdateColumns(resetDefaultHeaders); + onOutsideClick(); + }, [isEventViewer, onOutsideClick, onUpdateColumns, timelineTypeContext]); + + return ( + + + +

{i18n.CUSTOMIZE_COLUMNS}

+
+
+ + + + {i18n.RESET_FIELDS} + + +
+ ); +}); TitleRow.displayName = 'TitleRow'; diff --git a/x-pack/legacy/plugins/siem/public/components/header_page/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/header_page/__snapshots__/index.test.tsx.snap index a91d8fce87dac..a4bcf20ea1bc2 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_page/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/header_page/__snapshots__/index.test.tsx.snap @@ -16,7 +16,7 @@ exports[`HeaderPage it renders 1`] = ` > Test title - ( - ({ - backOptions, - badgeOptions, - border, - children, - draggableArguments, - isLoading, - subtitle, - subtitle2, - title, - ...rest - }) => ( -
- - - {backOptions && ( - - - {backOptions.text} - - - )} - - -

- {!draggableArguments ? ( - title - ) : ( - - )} - {badgeOptions && ( - <> - {' '} - {badgeOptions.beta ? ( - - ) : ( - {badgeOptions.text} - )} - - )} -

-
- - {subtitle && } - {subtitle2 && } - {border && isLoading && } -
- - {children && ( - - {children} - +const HeaderPageComponent: React.FC = ({ + backOptions, + badgeOptions, + border, + children, + draggableArguments, + isLoading, + subtitle, + subtitle2, + title, + ...rest +}) => ( +
+ + + {backOptions && ( + + + {backOptions.text} + + )} - -
- ) + + +

+ {!draggableArguments ? ( + title + ) : ( + + )} + {badgeOptions && ( + <> + {' '} + {badgeOptions.beta ? ( + + ) : ( + {badgeOptions.text} + )} + + )} +

+
+ + {subtitle && } + {subtitle2 && } + {border && isLoading && } + + + {children && ( + + {children} + + )} +
+
); -HeaderPage.displayName = 'HeaderPage'; + +export const HeaderPage = React.memo(HeaderPageComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/link_to/link_to.tsx b/x-pack/legacy/plugins/siem/public/components/link_to/link_to.tsx index 3180fc955c690..3eda945c9224e 100644 --- a/x-pack/legacy/plugins/siem/public/components/link_to/link_to.tsx +++ b/x-pack/legacy/plugins/siem/public/components/link_to/link_to.tsx @@ -77,16 +77,16 @@ export const LinkToPage = React.memo(({ match }) => ( /> ; @@ -41,22 +42,36 @@ export const RedirectToCreateRulePage = ({ }; export const RedirectToRuleDetailsPage = ({ + match: { + params: { detailName }, + }, location: { search }, }: DetectionEngineComponentProps) => { - return ; + return ; }; -export const RedirectToEditRulePage = ({ location: { search } }: DetectionEngineComponentProps) => { +export const RedirectToEditRulePage = ({ + match: { + params: { detailName }, + }, + location: { search }, +}: DetectionEngineComponentProps) => { return ( - + ); }; -export const getDetectionEngineUrl = () => `#/link-to/${DETECTION_ENGINE_PAGE_NAME}`; +const baseDetectionEngineUrl = `#/link-to/${DETECTION_ENGINE_PAGE_NAME}`; + +export const getDetectionEngineUrl = () => `${baseDetectionEngineUrl}`; export const getDetectionEngineAlertUrl = () => - `#/link-to/${DETECTION_ENGINE_PAGE_NAME}/${DetectionEngineTab.alerts}`; -export const getRulesUrl = () => `#/link-to/${DETECTION_ENGINE_PAGE_NAME}/rules`; -export const getCreateRuleUrl = () => `#/link-to/${DETECTION_ENGINE_PAGE_NAME}/rules/create-rule`; -export const getRuleDetailsUrl = () => `#/link-to/${DETECTION_ENGINE_PAGE_NAME}/rules/rule-details`; -export const getEditRuleUrl = () => - `#/link-to/${DETECTION_ENGINE_PAGE_NAME}/rules/rule-details/edit-rule`; + `${baseDetectionEngineUrl}/${DetectionEngineTab.alerts}`; +export const getDetectionEngineTabUrl = (tabPath: string) => `${baseDetectionEngineUrl}/${tabPath}`; +export const getRulesUrl = () => `${baseDetectionEngineUrl}/rules`; +export const getCreateRuleUrl = () => `${baseDetectionEngineUrl}/rules/create`; +export const getRuleDetailsUrl = (detailName: string) => + `${baseDetectionEngineUrl}/rules/id/${detailName}`; +export const getEditRuleUrl = (detailName: string) => + `${baseDetectionEngineUrl}/rules/id/${detailName}/edit`; diff --git a/x-pack/legacy/plugins/siem/public/components/navigation/breadcrumbs/index.ts b/x-pack/legacy/plugins/siem/public/components/navigation/breadcrumbs/index.ts index 9eee5b21e83f3..91055bca066c4 100644 --- a/x-pack/legacy/plugins/siem/public/components/navigation/breadcrumbs/index.ts +++ b/x-pack/legacy/plugins/siem/public/components/navigation/breadcrumbs/index.ts @@ -10,6 +10,7 @@ import { getOr, omit } from 'lodash/fp'; import { APP_NAME } from '../../../../common/constants'; import { getBreadcrumbs as getHostDetailsBreadcrumbs } from '../../../pages/hosts/details/utils'; import { getBreadcrumbs as getIPDetailsBreadcrumbs } from '../../../pages/network/ip_details'; +import { getBreadcrumbs as getDetectionRulesBreadcrumbs } from '../../../pages/detection_engine/rules/utils'; import { SiemPageName } from '../../../pages/home/types'; import { RouteSpyState, HostRouteSpyState, NetworkRouteSpyState } from '../../../utils/route/types'; import { getOverviewUrl } from '../../link_to'; @@ -38,6 +39,9 @@ const isNetworkRoutes = (spyState: RouteSpyState): spyState is NetworkRouteSpySt const isHostsRoutes = (spyState: RouteSpyState): spyState is HostRouteSpyState => spyState != null && spyState.pageName === SiemPageName.hosts; +const isDetectionsRoutes = (spyState: RouteSpyState) => + spyState != null && spyState.pageName === SiemPageName.detections; + export const getBreadcrumbsForRoute = ( object: RouteSpyState & TabNavigationProps ): Breadcrumb[] | null => { @@ -76,6 +80,24 @@ export const getBreadcrumbsForRoute = ( ), ]; } + if (isDetectionsRoutes(spyState) && object.navTabs) { + const tempNav: SearchNavTab = { urlKey: 'detections', isDetailPage: false }; + let urlStateKeys = [getOr(tempNav, spyState.pageName, object.navTabs)]; + if (spyState.tabName != null) { + urlStateKeys = [...urlStateKeys, getOr(tempNav, spyState.tabName, object.navTabs)]; + } + + return [ + ...siemRootBreadcrumb, + ...getDetectionRulesBreadcrumbs( + spyState, + urlStateKeys.reduce( + (acc: string[], item: SearchNavTab) => [...acc, getSearch(item, object)], + [] + ) + ), + ]; + } if ( spyState != null && object.navTabs && diff --git a/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx index b6efc07ad8fe3..56be39f67b1bd 100644 --- a/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx @@ -187,6 +187,7 @@ describe('SIEM Navigation', () => { query: { language: 'kuery', query: '' }, savedQuery: undefined, search: '', + state: undefined, tabName: 'authentications', timeline: { id: '', isOpen: false }, timerange: { diff --git a/x-pack/legacy/plugins/siem/public/components/navigation/index.tsx b/x-pack/legacy/plugins/siem/public/components/navigation/index.tsx index 61ac84667d80f..040a6e7847b77 100644 --- a/x-pack/legacy/plugins/siem/public/components/navigation/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/navigation/index.tsx @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { isEqual } from 'lodash/fp'; +import isEqual from 'lodash/fp/isEqual'; +import deepEqual from 'fast-deep-equal'; import React, { useEffect } from 'react'; import { connect } from 'react-redux'; import { compose } from 'redux'; @@ -16,67 +17,78 @@ import { setBreadcrumbs } from './breadcrumbs'; import { TabNavigation } from './tab_navigation'; import { SiemNavigationProps, SiemNavigationComponentProps } from './types'; -export const SiemNavigationComponent = React.memo< - SiemNavigationComponentProps & SiemNavigationProps & RouteSpyState ->( - ({ detailName, display, navTabs, pageName, pathName, search, tabName, urlState, flowTarget }) => { - useEffect(() => { - if (pathName) { - setBreadcrumbs({ - query: urlState.query, - detailName, - filters: urlState.filters, - navTabs, - pageName, - pathName, - savedQuery: urlState.savedQuery, - search, - tabName, - flowTarget, - timerange: urlState.timerange, - timeline: urlState.timeline, - }); - } - }, [pathName, search, navTabs, urlState]); +export const SiemNavigationComponent: React.FC = ({ + detailName, + display, + navTabs, + pageName, + pathName, + search, + tabName, + urlState, + flowTarget, + state, +}) => { + useEffect(() => { + if (pathName) { + setBreadcrumbs({ + query: urlState.query, + detailName, + filters: urlState.filters, + navTabs, + pageName, + pathName, + savedQuery: urlState.savedQuery, + search, + tabName, + flowTarget, + timerange: urlState.timerange, + timeline: urlState.timeline, + state, + }); + } + }, [pathName, search, navTabs, urlState, state]); - return ( - - ); - }, - (prevProps, nextProps) => { - return ( + return ( + + ); +}; + +export const SiemNavigationRedux = compose< + React.ComponentClass +>(connect(makeMapStateToProps))( + React.memo( + SiemNavigationComponent, + (prevProps, nextProps) => prevProps.pathName === nextProps.pathName && prevProps.search === nextProps.search && isEqual(prevProps.navTabs, nextProps.navTabs) && - isEqual(prevProps.urlState, nextProps.urlState) - ); - } + isEqual(prevProps.urlState, nextProps.urlState) && + deepEqual(prevProps.state, nextProps.state) + ) ); -SiemNavigationComponent.displayName = 'SiemNavigationComponent'; - -export const SiemNavigationRedux = compose< - React.ComponentClass ->(connect(makeMapStateToProps))(SiemNavigationComponent); - -export const SiemNavigation = React.memo(props => { +const SiemNavigationContainer: React.FC = props => { const [routeProps] = useRouteSpy(); const stateNavReduxProps: RouteSpyState & SiemNavigationProps = { ...routeProps, ...props, }; + return ; -}); +}; -SiemNavigation.displayName = 'SiemNavigation'; +export const SiemNavigation = SiemNavigationContainer; diff --git a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host/index.tsx index e069e64c66a92..31d8467025f96 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host/index.tsx @@ -42,76 +42,78 @@ export interface OwnProps { const OverviewHostStatsManage = manageQuery(OverviewHostStats); type OverviewHostProps = OwnProps; -export const OverviewHost = React.memo( - ({ endDate, filterQuery, startDate, setQuery }) => { - const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT); - return ( - - - - - {({ overviewHost, loading, id, inspect, refetch }) => { - const hostEventsCount = getOverviewHostStats(overviewHost).reduce( - (total, stat) => total + stat.count, - 0 - ); - const formattedHostEventsCount = numeral(hostEventsCount).format( - defaultNumberFormat - ); +const OverviewHostComponent: React.FC = ({ + endDate, + filterQuery, + startDate, + setQuery, +}) => { + const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT); - return ( - <> - - } - title={ - - } - > - - - - + return ( + + + + + {({ overviewHost, loading, id, inspect, refetch }) => { + const hostEventsCount = getOverviewHostStats(overviewHost).reduce( + (total, stat) => total + stat.count, + 0 + ); + const formattedHostEventsCount = numeral(hostEventsCount).format(defaultNumberFormat); - - - ); - }} - - - - - ); - } -); + return ( + <> + + } + title={ + + } + > + + + + -OverviewHost.displayName = 'OverviewHost'; + + + ); + }} + + + + + ); +}; + +export const OverviewHost = React.memo(OverviewHostComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host_stats/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host_stats/__snapshots__/index.test.tsx.snap index 21a4568e24133..ac4d3036046b3 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host_stats/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host_stats/__snapshots__/index.test.tsx.snap @@ -245,8 +245,8 @@ exports[`Overview Host Stat Data rendering it renders the default OverviewHostSt > diff --git a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host_stats/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host_stats/index.tsx index 4134cc5ef4e4e..d50fb45ba4393 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host_stats/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host_stats/index.tsx @@ -152,7 +152,10 @@ const hostStatGroups: StatGroup[] = [ { groupId: 'endgame', name: ( - + ), statIds: [ 'endgameDns', diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/column_header.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/column_header.tsx index ccaeeff972a81..9df805bddcc82 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/column_header.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/column_header.tsx @@ -30,6 +30,7 @@ export interface ColumnHeader { format?: string; id: ColumnId; label?: string; + linkField?: string; placeholder?: string; type?: string; width: number; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/index.test.tsx index 36427015260a7..098bd3108dba1 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/index.test.tsx @@ -23,6 +23,7 @@ describe('Columns', () => { columnHeaders={headersSansTimestamp} columnRenderers={columnRenderers} data={mockTimelineData[0].data} + ecsData={mockTimelineData[0].ecs} onColumnResized={jest.fn()} timelineId="test" /> diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/index.tsx index 37b6e30215056..416c72cfbc255 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/index.tsx @@ -6,7 +6,8 @@ import React from 'react'; -import { TimelineNonEcsData } from '../../../../graphql/types'; +import { getOr } from 'lodash/fp'; +import { Ecs, TimelineNonEcsData } from '../../../../graphql/types'; import { OnColumnResized } from '../../events'; import { EventsTd, EventsTdContent, EventsTdGroupData } from '../../styles'; import { ColumnHeader } from '../column_headers/column_header'; @@ -18,12 +19,13 @@ interface Props { columnHeaders: ColumnHeader[]; columnRenderers: ColumnRenderer[]; data: TimelineNonEcsData[]; + ecsData: Ecs; onColumnResized: OnColumnResized; timelineId: string; } export const DataDrivenColumns = React.memo( - ({ _id, columnHeaders, columnRenderers, data, timelineId }) => { + ({ _id, columnHeaders, columnRenderers, data, ecsData, timelineId }) => { // Passing the styles directly to the component because the width is // being calculated and is recommended by Styled Components for performance // https://github.com/styled-components/styled-components/issues/134#issuecomment-312415291 @@ -36,6 +38,7 @@ export const DataDrivenColumns = React.memo( columnName: header.id, eventId: _id, field: header, + linkValues: getOr([], header.linkField ?? '', ecsData), timelineId, truncate: true, values: getMappedNonEcsValue({ diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/events/event_column_view.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/events/event_column_view.tsx index 1036c6b53b4c1..74476c3dc1d61 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/events/event_column_view.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/events/event_column_view.tsx @@ -127,6 +127,7 @@ export const EventColumnView = React.memo( columnHeaders={columnHeaders} columnRenderers={columnRenderers} data={data} + ecsData={ecsData} onColumnResized={onColumnResized} timelineId={timelineId} /> diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/column_renderer.ts b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/column_renderer.ts index f64d0f39e3b29..d1807c82d188e 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/column_renderer.ts +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/column_renderer.ts @@ -16,6 +16,7 @@ export interface ColumnRenderer { timelineId, truncate, values, + linkValues, }: { columnName: string; eventId: string; @@ -23,5 +24,6 @@ export interface ColumnRenderer { timelineId: string; truncate?: boolean; values: string[] | null | undefined; + linkValues?: string[] | null | undefined; }) => React.ReactNode; } diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/constants.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/constants.tsx index f22c338f2d879..0330fb458e364 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/constants.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/constants.tsx @@ -8,3 +8,4 @@ export const DATE_FIELD_TYPE = 'date'; export const HOST_NAME_FIELD_NAME = 'host.name'; export const IP_FIELD_TYPE = 'ip'; export const MESSAGE_FIELD_NAME = 'message'; +export const SIGNAL_RULE_NAME_FIELD_NAME = 'signal.rule.name'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/formatted_field.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/formatted_field.tsx index 888029600d860..010a328d2993d 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/formatted_field.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/formatted_field.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiToolTip, EuiLink } from '@elastic/eui'; import { isNumber, isString, isEmpty } from 'lodash/fp'; import React from 'react'; @@ -15,6 +15,7 @@ import { getOrEmptyTagFromValue, getEmptyTagValue } from '../../../empty_value'; import { FormattedDate } from '../../../formatted_date'; import { FormattedIp } from '../../../formatted_ip'; import { HostDetailsLink } from '../../../links'; +import { getRuleDetailsUrl } from '../../../link_to/redirect_to_detection_engine'; import { Port, PORT_NAMES } from '../../../port'; import { TruncatableText } from '../../../truncatable_text'; import { @@ -22,6 +23,7 @@ import { HOST_NAME_FIELD_NAME, IP_FIELD_TYPE, MESSAGE_FIELD_NAME, + SIGNAL_RULE_NAME_FIELD_NAME, } from './constants'; // simple black-list to prevent dragging and dropping fields such as message name @@ -35,7 +37,8 @@ const FormattedFieldValueComponent: React.FC<{ fieldType: string; truncate?: boolean; value: string | number | undefined | null; -}> = ({ contextId, eventId, fieldFormat, fieldName, fieldType, truncate, value }) => { + linkValue?: string | null | undefined; +}> = ({ contextId, eventId, fieldFormat, fieldName, fieldType, truncate, value, linkValue }) => { if (fieldType === IP_FIELD_TYPE) { return ( {value} ); + } else if (fieldName === SIGNAL_RULE_NAME_FIELD_NAME) { + const ruleName = `${value}`; + const ruleId = linkValue; + + return isString(value) && ruleName.length > 0 && ruleId != null ? ( + + + {value} + + + ) : ( + getEmptyTagValue() + ); } else { const contentValue = getOrEmptyTagFromValue(value); const content = truncate ? {contentValue} : contentValue; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/plain_column_renderer.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/plain_column_renderer.tsx index 70485c41f3b88..deeec05bc0707 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/plain_column_renderer.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/plain_column_renderer.tsx @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { head } from 'lodash/fp'; import React from 'react'; import { TimelineNonEcsData } from '../../../../graphql/types'; @@ -27,6 +28,7 @@ export const plainColumnRenderer: ColumnRenderer = { timelineId, truncate, values, + linkValues, }: { columnName: string; eventId: string; @@ -34,6 +36,7 @@ export const plainColumnRenderer: ColumnRenderer = { timelineId: string; truncate?: boolean; values: string[] | undefined | null; + linkValues?: string[] | null | undefined; }) => values != null ? values.map(value => ( @@ -46,6 +49,7 @@ export const plainColumnRenderer: ColumnRenderer = { fieldType={field.type || ''} value={parseValue(value)} truncate={truncate} + linkValue={head(linkValues)} /> )) : getEmptyTagValue(), diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/index.tsx index bb8b04f6e304e..ff556a1a9bdfc 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/index.tsx @@ -268,6 +268,7 @@ const StatefulTimelineComponent = React.memo( columns={columns} dataProviders={dataProviders!} end={end} + eventType={eventType} filters={filters} flyoutHeaderHeight={flyoutHeaderHeight} flyoutHeight={flyoutHeight} diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/timeline.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/timeline.test.tsx index 0be5e69abea38..f7c0d0b475734 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/timeline.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/timeline.test.tsx @@ -54,6 +54,7 @@ describe('Timeline', () => { id="foo" dataProviders={mockDataProviders} end={endDate} + eventType="raw" filters={[]} flyoutHeight={testFlyoutHeight} flyoutHeaderHeight={flyoutHeaderHeight} @@ -92,6 +93,7 @@ describe('Timeline', () => { id="foo" dataProviders={mockDataProviders} end={endDate} + eventType="raw" filters={[]} flyoutHeight={testFlyoutHeight} flyoutHeaderHeight={flyoutHeaderHeight} @@ -133,6 +135,7 @@ describe('Timeline', () => { id="foo" dataProviders={mockDataProviders} end={endDate} + eventType="raw" filters={[]} flyoutHeight={testFlyoutHeight} flyoutHeaderHeight={flyoutHeaderHeight} @@ -174,6 +177,7 @@ describe('Timeline', () => { id="foo" dataProviders={mockDataProviders} end={endDate} + eventType="raw" filters={[]} flyoutHeight={testFlyoutHeight} flyoutHeaderHeight={flyoutHeaderHeight} @@ -220,6 +224,7 @@ describe('Timeline', () => { id="foo" dataProviders={mockDataProviders} end={endDate} + eventType="raw" filters={[]} flyoutHeight={testFlyoutHeight} flyoutHeaderHeight={flyoutHeaderHeight} @@ -268,6 +273,7 @@ describe('Timeline', () => { id="foo" dataProviders={mockDataProviders} end={endDate} + eventType="raw" filters={[]} flyoutHeight={testFlyoutHeight} flyoutHeaderHeight={flyoutHeaderHeight} @@ -324,6 +330,7 @@ describe('Timeline', () => { id="foo" dataProviders={mockDataProviders} end={endDate} + eventType="raw" filters={[]} flyoutHeight={testFlyoutHeight} flyoutHeaderHeight={flyoutHeaderHeight} @@ -384,6 +391,7 @@ describe('Timeline', () => { id="foo" dataProviders={mockDataProviders} end={endDate} + eventType="raw" filters={[]} flyoutHeight={testFlyoutHeight} flyoutHeaderHeight={flyoutHeaderHeight} @@ -447,6 +455,7 @@ describe('Timeline', () => { id="foo" dataProviders={dataProviders} end={endDate} + eventType="raw" filters={[]} flyoutHeight={testFlyoutHeight} flyoutHeaderHeight={flyoutHeaderHeight} @@ -500,6 +509,7 @@ describe('Timeline', () => { id="foo" dataProviders={dataProviders} end={endDate} + eventType="raw" filters={[]} flyoutHeight={testFlyoutHeight} flyoutHeaderHeight={flyoutHeaderHeight} @@ -559,6 +569,7 @@ describe('Timeline', () => { id="foo" dataProviders={dataProviders} end={endDate} + eventType="raw" filters={[]} flyoutHeight={testFlyoutHeight} flyoutHeaderHeight={flyoutHeaderHeight} @@ -622,6 +633,7 @@ describe('Timeline', () => { id="foo" dataProviders={dataProviders} end={endDate} + eventType="raw" filters={[]} flyoutHeight={testFlyoutHeight} flyoutHeaderHeight={flyoutHeaderHeight} diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/timeline.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/timeline.tsx index ece5b4fa18d1c..11886b45b0bec 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/timeline.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/timeline.tsx @@ -13,7 +13,7 @@ import { BrowserFields } from '../../containers/source'; import { TimelineQuery } from '../../containers/timeline'; import { Direction } from '../../graphql/types'; import { useKibana } from '../../lib/kibana'; -import { KqlMode } from '../../store/timeline/model'; +import { KqlMode, EventType } from '../../store/timeline/model'; import { AutoSizer } from '../auto_sizer'; import { ColumnHeader } from './body/column_headers/column_header'; import { defaultHeaders } from './body/column_headers/default_headers'; @@ -60,6 +60,7 @@ interface Props { columns: ColumnHeader[]; dataProviders: DataProvider[]; end: number; + eventType: EventType; filters: esFilters.Filter[]; flyoutHeaderHeight: number; flyoutHeight: number; @@ -92,6 +93,7 @@ export const TimelineComponent = ({ columns, dataProviders, end, + eventType, filters, flyoutHeaderHeight, flyoutHeight, @@ -159,6 +161,7 @@ export const TimelineComponent = ({ {combinedQueries != null ? ( c.id)} diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/timeline_context.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/timeline_context.tsx index 611452cc7ccd1..15759c2efff0b 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/timeline_context.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/timeline_context.tsx @@ -23,6 +23,7 @@ export interface TimelineTypeContextProps { selectAll?: boolean; timelineActions?: TimelineAction[]; title?: string; + unit?: (totalCount: number) => string; } const initTimelineType: TimelineTypeContextProps = { documentType: undefined, @@ -32,6 +33,7 @@ const initTimelineType: TimelineTypeContextProps = { selectAll: false, timelineActions: [], title: undefined, + unit: undefined, }; export const TimelineTypeContext = createContext(initTimelineType); export const useTimelineTypeContext = () => useContext(TimelineTypeContext); diff --git a/x-pack/legacy/plugins/siem/public/components/wrapper_page/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/wrapper_page/__snapshots__/index.test.tsx.snap index 1eef27bcc0a87..5ade447994a1e 100644 --- a/x-pack/legacy/plugins/siem/public/components/wrapper_page/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/wrapper_page/__snapshots__/index.test.tsx.snap @@ -1,39 +1,39 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`WrapperPage it renders 1`] = ` - +

Test page

-
+ `; exports[`WrapperPage restrict width custom max width when restrictWidth is number 1`] = ` -

Test page

-
+ `; exports[`WrapperPage restrict width custom max width when restrictWidth is string 1`] = ` -

Test page

-
+ `; exports[`WrapperPage restrict width default max width when restrictWidth is true 1`] = ` -

Test page

-
+ `; diff --git a/x-pack/legacy/plugins/siem/public/components/wrapper_page/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/wrapper_page/index.test.tsx index 788ea14f4bd22..23cae15004e39 100644 --- a/x-pack/legacy/plugins/siem/public/components/wrapper_page/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/wrapper_page/index.test.tsx @@ -20,7 +20,7 @@ describe('WrapperPage', () => { ); - expect(wrapper.find('WrapperPage')).toMatchSnapshot(); + expect(wrapper.find('Memo(WrapperPageComponent)')).toMatchSnapshot(); }); describe('restrict width', () => { @@ -33,7 +33,7 @@ describe('WrapperPage', () => { ); - expect(wrapper.find('WrapperPage')).toMatchSnapshot(); + expect(wrapper.find('Memo(WrapperPageComponent)')).toMatchSnapshot(); }); test('custom max width when restrictWidth is number', () => { @@ -45,7 +45,7 @@ describe('WrapperPage', () => { ); - expect(wrapper.find('WrapperPage')).toMatchSnapshot(); + expect(wrapper.find('Memo(WrapperPageComponent)')).toMatchSnapshot(); }); test('custom max width when restrictWidth is string', () => { @@ -57,7 +57,7 @@ describe('WrapperPage', () => { ); - expect(wrapper.find('WrapperPage')).toMatchSnapshot(); + expect(wrapper.find('Memo(WrapperPageComponent)')).toMatchSnapshot(); }); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/wrapper_page/index.tsx b/x-pack/legacy/plugins/siem/public/components/wrapper_page/index.tsx index d777c8d6c9eab..bac0357def942 100644 --- a/x-pack/legacy/plugins/siem/public/components/wrapper_page/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/wrapper_page/index.tsx @@ -37,29 +37,32 @@ interface WrapperPageProps { style?: Record; } -export const WrapperPage = React.memo( - ({ children, className, restrictWidth, style }) => { - const classes = classNames(className, { - siemWrapperPage: true, - 'siemWrapperPage--restrictWidthDefault': - restrictWidth && typeof restrictWidth === 'boolean' && restrictWidth === true, - 'siemWrapperPage--restrictWidthCustom': restrictWidth && typeof restrictWidth !== 'boolean', - }); +const WrapperPageComponent: React.FC = ({ + children, + className, + restrictWidth, + style, +}) => { + const classes = classNames(className, { + siemWrapperPage: true, + 'siemWrapperPage--restrictWidthDefault': + restrictWidth && typeof restrictWidth === 'boolean' && restrictWidth === true, + 'siemWrapperPage--restrictWidthCustom': restrictWidth && typeof restrictWidth !== 'boolean', + }); - let customStyle: WrapperPageProps['style']; + let customStyle: WrapperPageProps['style']; - if (restrictWidth && typeof restrictWidth !== 'boolean') { - const value = typeof restrictWidth === 'number' ? `${restrictWidth}px` : restrictWidth; - customStyle = { ...style, maxWidth: value }; - } - - return ( - - {children} - - - ); + if (restrictWidth && typeof restrictWidth !== 'boolean') { + const value = typeof restrictWidth === 'number' ? `${restrictWidth}px` : restrictWidth; + customStyle = { ...style, maxWidth: value }; } -); -WrapperPage.displayName = 'WrapperPage'; + return ( + + {children} + + + ); +}; + +export const WrapperPage = React.memo(WrapperPageComponent); diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts index a83e874437c10..22fb837ffb801 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts @@ -26,7 +26,9 @@ import { throwIfNotOk } from '../../../hooks/api/api'; import { DETECTION_ENGINE_RULES_URL, DETECTION_ENGINE_PREPACKAGED_URL, - DETECTION_ENGINE_RULES_STATUS, + DETECTION_ENGINE_RULES_STATUS_URL, + DETECTION_ENGINE_PREPACKAGED_RULES_STATUS_URL, + DETECTION_ENGINE_TAGS_URL, } from '../../../../common/constants'; import * as i18n from '../../../pages/detection_engine/rules/translations'; @@ -53,61 +55,72 @@ export const addRule = async ({ rule, signal }: AddRulesProps): Promise }; /** - * Fetches all rules or single specified rule from the Detection Engine API + * Fetches all rules from the Detection Engine API * * @param filterOptions desired filters (e.g. filter/sortField/sortOrder) * @param pagination desired pagination options (e.g. page/perPage) - * @param id if specified, will return specific rule if exists * @param signal to cancel request + * */ export const fetchRules = async ({ filterOptions = { filter: '', sortField: 'enabled', sortOrder: 'desc', + showCustomRules: false, + showElasticRules: false, + tags: [], }, pagination = { page: 1, perPage: 20, total: 0, }, - id, signal, }: FetchRulesProps): Promise => { + const filters = [ + ...(filterOptions.filter.length !== 0 + ? [`alert.attributes.name:%20${encodeURIComponent(filterOptions.filter)}`] + : []), + ...(filterOptions.showCustomRules + ? ['alert.attributes.tags:%20%22__internal_immutable:false%22'] + : []), + ...(filterOptions.showElasticRules + ? ['alert.attributes.tags:%20%22__internal_immutable:true%22'] + : []), + ...(filterOptions.tags?.map(t => `alert.attributes.tags:${encodeURIComponent(t)}`) ?? []), + ]; + const queryParams = [ `page=${pagination.page}`, `per_page=${pagination.perPage}`, `sort_field=${filterOptions.sortField}`, `sort_order=${filterOptions.sortOrder}`, - ...(filterOptions.filter.length !== 0 - ? [`filter=alert.attributes.name:%20${encodeURIComponent(filterOptions.filter)}`] - : []), + ...(filters.length > 0 ? [`filter=${filters.join('%20AND%20')}`] : []), ]; - const endpoint = - id != null - ? `${chrome.getBasePath()}${DETECTION_ENGINE_RULES_URL}?id="${id}"` - : `${chrome.getBasePath()}${DETECTION_ENGINE_RULES_URL}/_find?${queryParams.join('&')}`; - - const response = await fetch(endpoint, { - method: 'GET', - signal, - }); + const response = await fetch( + `${chrome.getBasePath()}${DETECTION_ENGINE_RULES_URL}/_find?${queryParams.join('&')}`, + { + method: 'GET', + credentials: 'same-origin', + headers: { + 'content-type': 'application/json', + 'kbn-xsrf': 'true', + }, + signal, + } + ); await throwIfNotOk(response); - return id != null - ? { - page: 0, - perPage: 1, - total: 1, - data: response.json(), - } - : response.json(); + return response.json(); }; /** * Fetch a Rule by providing a Rule ID * * @param id Rule ID's (not rule_id) + * @param signal to cancel request + * */ export const fetchRuleById = async ({ id, signal }: FetchRuleProps): Promise => { const response = await fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_RULES_URL}?id=${id}`, { @@ -313,6 +326,7 @@ export const exportRules = async ({ * Get Rule Status provided Rule ID * * @param id string of Rule ID's (not rule_id) + * @param signal AbortSignal for cancelling request * * @throws An error if response is not OK */ @@ -324,7 +338,7 @@ export const getRuleStatusById = async ({ signal: AbortSignal; }): Promise> => { const response = await fetch( - `${chrome.getBasePath()}${DETECTION_ENGINE_RULES_STATUS}?ids=${encodeURIComponent( + `${chrome.getBasePath()}${DETECTION_ENGINE_RULES_STATUS_URL}?ids=${encodeURIComponent( JSON.stringify([id]) )}`, { @@ -341,3 +355,57 @@ export const getRuleStatusById = async ({ await throwIfNotOk(response); return response.json(); }; + +/** + * Fetch all unique Tags used by Rules + * + * @param signal to cancel request + * + */ +export const fetchTags = async ({ signal }: { signal: AbortSignal }): Promise => { + const response = await fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_TAGS_URL}`, { + method: 'GET', + credentials: 'same-origin', + headers: { + 'content-type': 'application/json', + 'kbn-xsrf': 'true', + }, + signal, + }); + + await throwIfNotOk(response); + return response.json(); +}; + +/** + * Get pre packaged rules Status + * + * @param signal AbortSignal for cancelling request + * + * @throws An error if response is not OK + */ +export const getPrePackagedRulesStatus = async ({ + signal, +}: { + signal: AbortSignal; +}): Promise<{ + rules_installed: number; + rules_not_installed: number; + rules_not_updated: number; +}> => { + const response = await fetch( + `${chrome.getBasePath()}${DETECTION_ENGINE_PREPACKAGED_RULES_STATUS_URL}`, + { + method: 'GET', + credentials: 'same-origin', + headers: { + 'content-type': 'application/json', + 'kbn-xsrf': 'true', + }, + signal, + } + ); + + await throwIfNotOk(response); + return response.json(); +}; diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/index.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/index.ts index e9a0f27b34696..c7ecfb33cd905 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/index.ts +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/index.ts @@ -10,4 +10,5 @@ export * from './persist_rule'; export * from './types'; export * from './use_rule'; export * from './use_rules'; +export * from './use_pre_packaged_rules'; export * from './use_rule_status'; diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/translations.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/translations.ts index 39efbde2ad5c2..84e008312fefe 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/translations.ts +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/translations.ts @@ -16,3 +16,24 @@ export const RULE_ADD_FAILURE = i18n.translate( defaultMessage: 'Failed to add Rule', } ); + +export const RULE_PREPACKAGED_FAILURE = i18n.translate( + 'xpack.siem.containers.detectionEngine.createPrePackagedRuleFailDescription', + { + defaultMessage: 'Failed to installed pre-packaged rules from elastic', + } +); + +export const RULE_PREPACKAGED_SUCCESS = i18n.translate( + 'xpack.siem.containers.detectionEngine.createPrePackagedRuleSuccesDescription', + { + defaultMessage: 'Installed pre-packaged rules from elastic', + } +); + +export const TAG_FETCH_FAILURE = i18n.translate( + 'xpack.siem.containers.detectionEngine.tagFetchFailDescription', + { + defaultMessage: 'Failed to fetch Tags', + } +); diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts index 0dcd0da5be8f6..2e776738547df 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts @@ -114,7 +114,6 @@ export interface PaginationOptions { export interface FetchRulesProps { pagination?: PaginationOptions; filterOptions?: FilterOptions; - id?: string; signal: AbortSignal; } @@ -122,6 +121,9 @@ export interface FilterOptions { filter: string; sortField: string; sortOrder: 'asc' | 'desc'; + showCustomRules?: boolean; + showElasticRules?: boolean; + tags?: string[]; } export interface FetchRulesResponse { diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_create_packaged_rules.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_create_packaged_rules.tsx deleted file mode 100644 index 592419f879011..0000000000000 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_create_packaged_rules.tsx +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { useEffect, useState } from 'react'; - -import { createPrepackagedRules } from './api'; - -type Return = [boolean, boolean | null]; - -interface UseCreatePackagedRules { - canUserCRUD: boolean | null; - hasIndexManage: boolean | null; - hasManageApiKey: boolean | null; - isAuthenticated: boolean | null; - isSignalIndexExists: boolean | null; -} - -/** - * Hook for creating the packages rules - * - * @param canUserCRUD boolean - * @param hasIndexManage boolean - * @param hasManageApiKey boolean - * @param isAuthenticated boolean - * @param isSignalIndexExists boolean - * - * @returns [loading, hasCreatedPackageRules] - */ -export const useCreatePackagedRules = ({ - canUserCRUD, - hasIndexManage, - hasManageApiKey, - isAuthenticated, - isSignalIndexExists, -}: UseCreatePackagedRules): Return => { - const [hasCreatedPackageRules, setHasCreatedPackageRules] = useState(null); - const [loading, setLoading] = useState(true); - - useEffect(() => { - let isSubscribed = true; - const abortCtrl = new AbortController(); - setLoading(true); - - async function createRules() { - try { - await createPrepackagedRules({ - signal: abortCtrl.signal, - }); - - if (isSubscribed) { - setHasCreatedPackageRules(true); - } - } catch (error) { - if (isSubscribed) { - setHasCreatedPackageRules(false); - } - } - if (isSubscribed) { - setLoading(false); - } - } - if ( - canUserCRUD && - hasIndexManage && - hasManageApiKey && - isAuthenticated && - isSignalIndexExists - ) { - createRules(); - } - return () => { - isSubscribed = false; - abortCtrl.abort(); - }; - }, [canUserCRUD, hasIndexManage, hasManageApiKey, isAuthenticated, isSignalIndexExists]); - - return [loading, hasCreatedPackageRules]; -}; diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_pre_packaged_rules.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_pre_packaged_rules.tsx new file mode 100644 index 0000000000000..ee34cad873021 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_pre_packaged_rules.tsx @@ -0,0 +1,166 @@ +/* + * 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 { useEffect, useState, useRef } from 'react'; + +import { useStateToaster, displaySuccessToast } from '../../../components/toasters'; +import { errorToToaster } from '../../../components/ml/api/error_to_toaster'; +import { getPrePackagedRulesStatus, createPrepackagedRules } from './api'; +import * as i18n from './translations'; + +type Func = () => void; +export type CreatePreBuiltRules = () => Promise; +interface Return { + createPrePackagedRules: null | CreatePreBuiltRules; + loading: boolean; + loadingCreatePrePackagedRules: boolean; + refetchPrePackagedRulesStatus: Func | null; + rulesInstalled: number | null; + rulesNotInstalled: number | null; + rulesNotUpdated: number | null; +} + +interface UsePrePackagedRuleProps { + canUserCRUD: boolean | null; + hasIndexWrite: boolean | null; + hasManageApiKey: boolean | null; + isAuthenticated: boolean | null; + isSignalIndexExists: boolean | null; +} + +/** + * Hook for using to get status about pre-packaged Rules from the Detection Engine API + * + * @param hasIndexWrite boolean + * @param hasManageApiKey boolean + * @param isAuthenticated boolean + * @param isSignalIndexExists boolean + * + */ +export const usePrePackagedRules = ({ + canUserCRUD, + hasIndexWrite, + hasManageApiKey, + isAuthenticated, + isSignalIndexExists, +}: UsePrePackagedRuleProps): Return => { + const [rulesInstalled, setRulesInstalled] = useState(null); + const [rulesNotInstalled, setRulesNotInstalled] = useState(null); + const [rulesNotUpdated, setRulesNotUpdated] = useState(null); + const [loadingCreatePrePackagedRules, setLoadingCreatePrePackagedRules] = useState(false); + const [loading, setLoading] = useState(true); + const createPrePackagedRules = useRef(null); + const refetchPrePackagedRules = useRef(null); + const [, dispatchToaster] = useStateToaster(); + + useEffect(() => { + let isSubscribed = true; + const abortCtrl = new AbortController(); + + const fetchPrePackagedRules = async () => { + try { + setLoading(true); + const prePackagedRuleStatusResponse = await getPrePackagedRulesStatus({ + signal: abortCtrl.signal, + }); + + if (isSubscribed) { + setRulesInstalled(prePackagedRuleStatusResponse.rules_installed); + setRulesNotInstalled(prePackagedRuleStatusResponse.rules_not_installed); + setRulesNotUpdated(prePackagedRuleStatusResponse.rules_not_updated); + } + } catch (error) { + if (isSubscribed) { + setRulesInstalled(null); + setRulesNotInstalled(null); + setRulesNotUpdated(null); + errorToToaster({ title: i18n.RULE_FETCH_FAILURE, error, dispatchToaster }); + } + } + if (isSubscribed) { + setLoading(false); + } + }; + + const createElasticRules = async (): Promise => { + return new Promise(async resolve => { + try { + if ( + canUserCRUD && + hasIndexWrite && + hasManageApiKey && + isAuthenticated && + isSignalIndexExists + ) { + setLoadingCreatePrePackagedRules(true); + await createPrepackagedRules({ + signal: abortCtrl.signal, + }); + + if (isSubscribed) { + let iterationTryOfFetchingPrePackagedCount = 0; + let timeoutId = -1; + const stopTimeOut = () => { + if (timeoutId !== -1) { + window.clearTimeout(timeoutId); + } + }; + const reFetch = () => + window.setTimeout(async () => { + iterationTryOfFetchingPrePackagedCount = + iterationTryOfFetchingPrePackagedCount + 1; + const prePackagedRuleStatusResponse = await getPrePackagedRulesStatus({ + signal: abortCtrl.signal, + }); + if ( + isSubscribed && + ((prePackagedRuleStatusResponse.rules_not_installed === 0 && + prePackagedRuleStatusResponse.rules_not_updated === 0) || + iterationTryOfFetchingPrePackagedCount > 100) + ) { + setLoadingCreatePrePackagedRules(false); + setRulesInstalled(prePackagedRuleStatusResponse.rules_installed); + setRulesNotInstalled(prePackagedRuleStatusResponse.rules_not_installed); + setRulesNotUpdated(prePackagedRuleStatusResponse.rules_not_updated); + displaySuccessToast(i18n.RULE_PREPACKAGED_SUCCESS, dispatchToaster); + stopTimeOut(); + resolve(true); + } else { + timeoutId = reFetch(); + } + }, 300); + timeoutId = reFetch(); + } + } + } catch (error) { + if (isSubscribed) { + setLoadingCreatePrePackagedRules(false); + errorToToaster({ title: i18n.RULE_PREPACKAGED_FAILURE, error, dispatchToaster }); + resolve(false); + } + } + }); + }; + + fetchPrePackagedRules(); + createPrePackagedRules.current = createElasticRules; + refetchPrePackagedRules.current = fetchPrePackagedRules; + return () => { + isSubscribed = false; + abortCtrl.abort(); + }; + }, [canUserCRUD, hasIndexWrite, hasManageApiKey, isAuthenticated, isSignalIndexExists]); + + return { + loading, + loadingCreatePrePackagedRules, + refetchPrePackagedRulesStatus: refetchPrePackagedRules.current, + rulesInstalled, + rulesNotInstalled, + rulesNotUpdated, + createPrePackagedRules: createPrePackagedRules.current, + }; +}; diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rules.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rules.tsx index b49dd8d51d4f7..254e8cbdc9a88 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rules.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rules.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useEffect, useState } from 'react'; +import { useEffect, useState, useRef } from 'react'; import { FetchRulesResponse, FilterOptions, PaginationOptions } from './types'; import { useStateToaster } from '../../../components/toasters'; @@ -12,36 +12,33 @@ import { fetchRules } from './api'; import { errorToToaster } from '../../../components/ml/api/error_to_toaster'; import * as i18n from './translations'; -type Return = [boolean, FetchRulesResponse]; +type Func = () => void; +type Return = [boolean, FetchRulesResponse, Func | null]; /** * Hook for using the list of Rules from the Detection Engine API * * @param pagination desired pagination options (e.g. page/perPage) * @param filterOptions desired filters (e.g. filter/sortField/sortOrder) - * @param refetchToggle toggle for refetching data */ -export const useRules = ( - pagination: PaginationOptions, - filterOptions: FilterOptions, - refetchToggle: boolean -): Return => { +export const useRules = (pagination: PaginationOptions, filterOptions: FilterOptions): Return => { const [rules, setRules] = useState({ page: 1, perPage: 20, total: 0, data: [], }); + const reFetchRules = useRef(null); const [loading, setLoading] = useState(true); const [, dispatchToaster] = useStateToaster(); useEffect(() => { let isSubscribed = true; const abortCtrl = new AbortController(); - setLoading(true); async function fetchData() { try { + setLoading(true); const fetchRulesResult = await fetchRules({ filterOptions, pagination, @@ -62,18 +59,21 @@ export const useRules = ( } fetchData(); + reFetchRules.current = fetchData; return () => { isSubscribed = false; abortCtrl.abort(); }; }, [ - refetchToggle, pagination.page, pagination.perPage, filterOptions.filter, filterOptions.sortField, filterOptions.sortOrder, + filterOptions.tags?.sort().join(), + filterOptions.showCustomRules, + filterOptions.showElasticRules, ]); - return [loading, rules]; + return [loading, rules, reFetchRules.current]; }; diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_tags.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_tags.tsx new file mode 100644 index 0000000000000..1c961d530422a --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_tags.tsx @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useEffect, useState } from 'react'; +import { useStateToaster } from '../../../components/toasters'; +import { fetchTags } from './api'; +import { errorToToaster } from '../../../components/ml/api/error_to_toaster'; +import * as i18n from './translations'; + +type Return = [boolean, string[]]; + +/** + * Hook for using the list of Tags from the Detection Engine API + * + */ +export const useTags = (): Return => { + const [tags, setTags] = useState([]); + const [loading, setLoading] = useState(true); + const [, dispatchToaster] = useStateToaster(); + + useEffect(() => { + let isSubscribed = true; + const abortCtrl = new AbortController(); + + async function fetchData() { + setLoading(true); + try { + const fetchTagsResult = await fetchTags({ + signal: abortCtrl.signal, + }); + + if (isSubscribed) { + setTags(fetchTagsResult); + } + } catch (error) { + if (isSubscribed) { + errorToToaster({ title: i18n.TAG_FETCH_FAILURE, error, dispatchToaster }); + } + } + if (isSubscribed) { + setLoading(false); + } + } + + fetchData(); + + return () => { + isSubscribed = false; + abortCtrl.abort(); + }; + }, []); + + return [loading, tags]; +}; diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_privilege_user.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_privilege_user.tsx index 7d0e331200d55..d225241875d4b 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_privilege_user.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_privilege_user.tsx @@ -46,7 +46,12 @@ export const usePrivilegeUser = (): Return => { if (privilege.index != null && Object.keys(privilege.index).length > 0) { const indexName = Object.keys(privilege.index)[0]; setHasIndexManage(privilege.index[indexName].manage); - setHasIndexWrite(privilege.index[indexName].write); + setHasIndexWrite( + privilege.index[indexName].create || + privilege.index[indexName].create_doc || + privilege.index[indexName].index || + privilege.index[indexName].write + ); setHasManageApiKey( privilege.cluster.manage_security || privilege.cluster.manage_api_key || @@ -59,6 +64,7 @@ export const usePrivilegeUser = (): Return => { setAuthenticated(false); setHasIndexManage(false); setHasIndexWrite(false); + setHasManageApiKey(false); errorToToaster({ title: i18n.PRIVILEGE_FETCH_FAILURE, error, dispatchToaster }); } } diff --git a/x-pack/legacy/plugins/siem/public/containers/timeline/index.tsx b/x-pack/legacy/plugins/siem/public/containers/timeline/index.tsx index 97f007452854c..03fe68ca1398d 100644 --- a/x-pack/legacy/plugins/siem/public/containers/timeline/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/timeline/index.tsx @@ -11,6 +11,7 @@ import { Query } from 'react-apollo'; import { compose } from 'redux'; import { connect } from 'react-redux'; +import { IIndexPattern } from '../../../../../../../src/plugins/data/common/index_patterns'; import { DEFAULT_INDEX_KEY } from '../../../common/constants'; import { GetTimelineQuery, @@ -23,9 +24,8 @@ import { inputsModel, inputsSelectors, State } from '../../store'; import { withKibana, WithKibanaProps } from '../../lib/kibana'; import { createFilter } from '../helpers'; import { QueryTemplate, QueryTemplateProps } from '../query_template'; - +import { EventType } from '../../store/timeline/model'; import { timelineQuery } from './index.gql_query'; -import { IIndexPattern } from '../../../../../../../src/plugins/data/common/index_patterns'; export interface TimelineArgs { events: TimelineItem[]; @@ -45,6 +45,7 @@ export interface TimelineQueryReduxProps { export interface OwnProps extends QueryTemplateProps { children?: (args: TimelineArgs) => React.ReactNode; + eventType?: EventType; id: string; indexPattern?: IIndexPattern; indexToAdd?: string[]; @@ -70,6 +71,7 @@ class TimelineQueryComponent extends QueryTemplate< public render() { const { children, + eventType = 'raw', id, indexPattern, indexToAdd = [], @@ -83,7 +85,10 @@ class TimelineQueryComponent extends QueryTemplate< } = this.props; const defaultKibanaIndex = kibana.services.uiSettings.get(DEFAULT_INDEX_KEY); const defaultIndex = isEmpty(indexPattern) - ? [...defaultKibanaIndex, ...indexToAdd] + ? [ + ...(['all', 'raw'].includes(eventType) ? defaultKibanaIndex : []), + ...(['all', 'signal'].includes(eventType) ? indexToAdd : []), + ] : indexPattern?.title.split(',') ?? []; const variables: GetTimelineQuery.Variables = { fieldRequested: fields, diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/detection_engine_header_page/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/detection_engine_header_page/index.tsx new file mode 100644 index 0000000000000..c945073919013 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/detection_engine_header_page/index.tsx @@ -0,0 +1,24 @@ +/* + * 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 { HeaderPage, HeaderPageProps } from '../../../../components/header_page'; +import * as i18n from './translations'; + +const DetectionEngineHeaderPageComponent: React.FC = props => ( + +); + +DetectionEngineHeaderPageComponent.defaultProps = { + badgeOptions: { + beta: true, + text: i18n.PAGE_BADGE_LABEL, + tooltip: i18n.PAGE_BADGE_TOOLTIP, + }, +}; + +export const DetectionEngineHeaderPage = React.memo(DetectionEngineHeaderPageComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/detection_engine_header_page/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/detection_engine_header_page/translations.ts new file mode 100644 index 0000000000000..303431a559e8f --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/detection_engine_header_page/translations.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const PAGE_BADGE_LABEL = i18n.translate( + 'xpack.siem.detectionEngine.headerPage.pageBadgeLabel', + { + defaultMessage: 'Beta', + } +); + +export const PAGE_BADGE_TOOLTIP = i18n.translate( + 'xpack.siem.detectionEngine.headerPage.pageBadgeTooltip', + { + defaultMessage: + 'Detections is still in beta. Please help us improve by reporting issues or bugs in the Kibana repo.', + } +); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/actions.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/actions.tsx index 4701ed93dc4f0..a33efeda2196b 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/actions.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/actions.tsx @@ -151,11 +151,25 @@ export const sendSignalToTimelineAction = async ({ updateTimelineIsLoading({ id: 'timeline-1', isLoading: false }); } } else { - const query = `_id: ${ecsData._id}`; createTimeline({ from, timeline: { ...timelineDefaults, + dataProviders: [ + { + and: [], + id: `send-signal-to-timeline-action-default-draggable-event-details-value-formatted-field-value-timeline-1-signal-id-${ecsData._id}`, + name: ecsData._id, + enabled: true, + excluded: false, + kqlQuery: '', + queryMatch: { + field: '_id', + value: ecsData._id, + operator: ':', + }, + }, + ], id: 'timeline-1', dateRange: { start: from, @@ -166,13 +180,13 @@ export const sendSignalToTimelineAction = async ({ filterQuery: { kuery: { kind: 'kuery', - expression: query, + expression: '', }, - serializedQuery: convertKueryToElasticSearchQuery(query), + serializedQuery: '', }, filterQueryDraft: { kind: 'kuery', - expression: query, + expression: '', }, }, }, diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/default_config.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/default_config.tsx index e00dfa5b84473..f5d138a3afcb8 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/default_config.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/default_config.tsx @@ -90,19 +90,26 @@ export const signalsHeaders: ColumnHeader[] = [ columnHeaderType: defaultColumnHeaderType, id: 'signal.rule.name', label: i18n.SIGNALS_HEADERS_RULE, + linkField: 'signal.rule.id', width: DEFAULT_COLUMN_MIN_WIDTH, }, + { + columnHeaderType: defaultColumnHeaderType, + id: 'signal.rule.version', + label: i18n.SIGNALS_HEADERS_VERSION, + width: 100, + }, { columnHeaderType: defaultColumnHeaderType, id: 'signal.rule.type', label: i18n.SIGNALS_HEADERS_METHOD, - width: 80, + width: 100, }, { columnHeaderType: defaultColumnHeaderType, id: 'signal.rule.severity', label: i18n.SIGNALS_HEADERS_SEVERITY, - width: 80, + width: 100, }, { columnHeaderType: defaultColumnHeaderType, @@ -141,7 +148,7 @@ export const signalsHeaders: ColumnHeader[] = [ { columnHeaderType: defaultColumnHeaderType, id: 'destination.ip', - width: 120, + width: 140, }, { columnHeaderType: defaultColumnHeaderType, diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/index.tsx index 6fd37215b9259..51d8e2630459c 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/index.tsx @@ -92,247 +92,243 @@ interface OwnProps { type SignalsTableComponentProps = OwnProps & ReduxProps & DispatchProps; -export const SignalsTableComponent = React.memo( - ({ - canUserCRUD, - clearEventsDeleted, - clearEventsLoading, - clearSelected, - defaultFilters = [], - from, - globalFilters, - globalQuery, - hasIndexWrite, - isSelectAllChecked, - loading, - loadingEventIds, - selectedEventIds, - setEventsDeleted, - setEventsLoading, - signalsIndex, - to, - updateTimeline, - updateTimelineIsLoading, - }) => { - const [selectAll, setSelectAll] = useState(false); - const apolloClient = useApolloClient(); +const SignalsTableComponent: React.FC = ({ + canUserCRUD, + clearEventsDeleted, + clearEventsLoading, + clearSelected, + defaultFilters = [], + from, + globalFilters, + globalQuery, + hasIndexWrite, + isSelectAllChecked, + loading, + loadingEventIds, + selectedEventIds, + setEventsDeleted, + setEventsLoading, + signalsIndex, + to, + updateTimeline, + updateTimelineIsLoading, +}) => { + const [selectAll, setSelectAll] = useState(false); + const apolloClient = useApolloClient(); - const [showClearSelectionAction, setShowClearSelectionAction] = useState(false); - const [filterGroup, setFilterGroup] = useState(FILTER_OPEN); - const [{ browserFields, indexPatterns }] = useFetchIndexPatterns([signalsIndex]); - const kibana = useKibana(); + const [showClearSelectionAction, setShowClearSelectionAction] = useState(false); + const [filterGroup, setFilterGroup] = useState(FILTER_OPEN); + const [{ browserFields, indexPatterns }] = useFetchIndexPatterns([signalsIndex]); + const kibana = useKibana(); - const getGlobalQuery = useCallback(() => { - if (browserFields != null && indexPatterns != null) { - return combineQueries({ - config: esQuery.getEsQueryConfig(kibana.services.uiSettings), - dataProviders: [], - indexPattern: indexPatterns, - browserFields, - filters: globalFilters, - kqlQuery: globalQuery, - kqlMode: globalQuery.language, - start: from, - end: to, - isEventViewer: true, - }); - } - return null; - }, [browserFields, globalFilters, globalQuery, indexPatterns, kibana, to, from]); + const getGlobalQuery = useCallback(() => { + if (browserFields != null && indexPatterns != null) { + return combineQueries({ + config: esQuery.getEsQueryConfig(kibana.services.uiSettings), + dataProviders: [], + indexPattern: indexPatterns, + browserFields, + filters: globalFilters, + kqlQuery: globalQuery, + kqlMode: globalQuery.language, + start: from, + end: to, + isEventViewer: true, + }); + } + return null; + }, [browserFields, globalFilters, globalQuery, indexPatterns, kibana, to, from]); - // Callback for creating a new timeline -- utilized by row/batch actions - const createTimelineCallback = useCallback( - ({ from: fromTimeline, timeline, to: toTimeline }: CreateTimelineProps) => { - updateTimelineIsLoading({ id: 'timeline-1', isLoading: false }); - updateTimeline({ - duplicate: true, - from: fromTimeline, - id: 'timeline-1', - notes: [], - timeline: { - ...timeline, - show: true, - }, - to: toTimeline, - })(); - }, - [updateTimeline, updateTimelineIsLoading] - ); + // Callback for creating a new timeline -- utilized by row/batch actions + const createTimelineCallback = useCallback( + ({ from: fromTimeline, timeline, to: toTimeline }: CreateTimelineProps) => { + updateTimelineIsLoading({ id: 'timeline-1', isLoading: false }); + updateTimeline({ + duplicate: true, + from: fromTimeline, + id: 'timeline-1', + notes: [], + timeline: { + ...timeline, + show: true, + }, + to: toTimeline, + })(); + }, + [updateTimeline, updateTimelineIsLoading] + ); - const setEventsLoadingCallback = useCallback( - ({ eventIds, isLoading }: SetEventsLoadingProps) => { - setEventsLoading!({ id: SIGNALS_PAGE_TIMELINE_ID, eventIds, isLoading }); - }, - [setEventsLoading, SIGNALS_PAGE_TIMELINE_ID] - ); + const setEventsLoadingCallback = useCallback( + ({ eventIds, isLoading }: SetEventsLoadingProps) => { + setEventsLoading!({ id: SIGNALS_PAGE_TIMELINE_ID, eventIds, isLoading }); + }, + [setEventsLoading, SIGNALS_PAGE_TIMELINE_ID] + ); - const setEventsDeletedCallback = useCallback( - ({ eventIds, isDeleted }: SetEventsDeletedProps) => { - setEventsDeleted!({ id: SIGNALS_PAGE_TIMELINE_ID, eventIds, isDeleted }); - }, - [setEventsDeleted, SIGNALS_PAGE_TIMELINE_ID] - ); + const setEventsDeletedCallback = useCallback( + ({ eventIds, isDeleted }: SetEventsDeletedProps) => { + setEventsDeleted!({ id: SIGNALS_PAGE_TIMELINE_ID, eventIds, isDeleted }); + }, + [setEventsDeleted, SIGNALS_PAGE_TIMELINE_ID] + ); - // Catches state change isSelectAllChecked->false upon user selection change to reset utility bar - useEffect(() => { - if (!isSelectAllChecked) { - setShowClearSelectionAction(false); - } else { - setSelectAll(false); - } - }, [isSelectAllChecked]); - - // Callback for when open/closed filter changes - const onFilterGroupChangedCallback = useCallback( - (newFilterGroup: SignalFilterOption) => { - clearEventsLoading!({ id: SIGNALS_PAGE_TIMELINE_ID }); - clearEventsDeleted!({ id: SIGNALS_PAGE_TIMELINE_ID }); - clearSelected!({ id: SIGNALS_PAGE_TIMELINE_ID }); - setFilterGroup(newFilterGroup); - }, - [clearEventsLoading, clearEventsDeleted, clearSelected, setFilterGroup] - ); + // Catches state change isSelectAllChecked->false upon user selection change to reset utility bar + useEffect(() => { + if (!isSelectAllChecked) { + setShowClearSelectionAction(false); + } else { + setSelectAll(false); + } + }, [isSelectAllChecked]); - // Callback for clearing entire selection from utility bar - const clearSelectionCallback = useCallback(() => { + // Callback for when open/closed filter changes + const onFilterGroupChangedCallback = useCallback( + (newFilterGroup: SignalFilterOption) => { + clearEventsLoading!({ id: SIGNALS_PAGE_TIMELINE_ID }); + clearEventsDeleted!({ id: SIGNALS_PAGE_TIMELINE_ID }); clearSelected!({ id: SIGNALS_PAGE_TIMELINE_ID }); - setSelectAll(false); - setShowClearSelectionAction(false); - }, [clearSelected, setSelectAll, setShowClearSelectionAction]); + setFilterGroup(newFilterGroup); + }, + [clearEventsLoading, clearEventsDeleted, clearSelected, setFilterGroup] + ); - // Callback for selecting all events on all pages from utility bar - // Dispatches to stateful_body's selectAll via TimelineTypeContext props - // as scope of response data required to actually set selectedEvents - const selectAllCallback = useCallback(() => { - setSelectAll(true); - setShowClearSelectionAction(true); - }, [setSelectAll, setShowClearSelectionAction]); + // Callback for clearing entire selection from utility bar + const clearSelectionCallback = useCallback(() => { + clearSelected!({ id: SIGNALS_PAGE_TIMELINE_ID }); + setSelectAll(false); + setShowClearSelectionAction(false); + }, [clearSelected, setSelectAll, setShowClearSelectionAction]); - const updateSignalsStatusCallback: UpdateSignalsStatus = useCallback( - async ({ signalIds, status }: UpdateSignalsStatusProps) => { - await updateSignalStatusAction({ - query: showClearSelectionAction ? getGlobalQuery()?.filterQuery : undefined, - signalIds: Object.keys(selectedEventIds), - status, - setEventsDeleted: setEventsDeletedCallback, - setEventsLoading: setEventsLoadingCallback, - }); - }, - [ - getGlobalQuery, - selectedEventIds, - setEventsDeletedCallback, - setEventsLoadingCallback, - showClearSelectionAction, - ] - ); + // Callback for selecting all events on all pages from utility bar + // Dispatches to stateful_body's selectAll via TimelineTypeContext props + // as scope of response data required to actually set selectedEvents + const selectAllCallback = useCallback(() => { + setSelectAll(true); + setShowClearSelectionAction(true); + }, [setSelectAll, setShowClearSelectionAction]); - // Callback for creating the SignalUtilityBar which receives totalCount from EventsViewer component - const utilityBarCallback = useCallback( - (totalCount: number) => { - return ( - 0} - clearSelection={clearSelectionCallback} - hasIndexWrite={hasIndexWrite} - isFilteredToOpen={filterGroup === FILTER_OPEN} - selectAll={selectAllCallback} - selectedEventIds={selectedEventIds} - showClearSelection={showClearSelectionAction} - totalCount={totalCount} - updateSignalsStatus={updateSignalsStatusCallback} - /> - ); - }, - [ - canUserCRUD, - hasIndexWrite, - clearSelectionCallback, - filterGroup, - loadingEventIds.length, - selectAllCallback, - selectedEventIds, - showClearSelectionAction, - updateSignalsStatusCallback, - ] - ); + const updateSignalsStatusCallback: UpdateSignalsStatus = useCallback( + async ({ signalIds, status }: UpdateSignalsStatusProps) => { + await updateSignalStatusAction({ + query: showClearSelectionAction ? getGlobalQuery()?.filterQuery : undefined, + signalIds: Object.keys(selectedEventIds), + status, + setEventsDeleted: setEventsDeletedCallback, + setEventsLoading: setEventsLoadingCallback, + }); + }, + [ + getGlobalQuery, + selectedEventIds, + setEventsDeletedCallback, + setEventsLoadingCallback, + showClearSelectionAction, + ] + ); + + // Callback for creating the SignalUtilityBar which receives totalCount from EventsViewer component + const utilityBarCallback = useCallback( + (totalCount: number) => { + return ( + 0} + clearSelection={clearSelectionCallback} + hasIndexWrite={hasIndexWrite} + isFilteredToOpen={filterGroup === FILTER_OPEN} + selectAll={selectAllCallback} + selectedEventIds={selectedEventIds} + showClearSelection={showClearSelectionAction} + totalCount={totalCount} + updateSignalsStatus={updateSignalsStatusCallback} + /> + ); + }, + [ + canUserCRUD, + hasIndexWrite, + clearSelectionCallback, + filterGroup, + loadingEventIds.length, + selectAllCallback, + selectedEventIds, + showClearSelectionAction, + updateSignalsStatusCallback, + ] + ); - // Send to Timeline / Update Signal Status Actions for each table row - const additionalActions = useMemo( - () => - getSignalsActions({ - apolloClient, - canUserCRUD, - hasIndexWrite, - createTimeline: createTimelineCallback, - setEventsLoading: setEventsLoadingCallback, - setEventsDeleted: setEventsDeletedCallback, - status: filterGroup === FILTER_OPEN ? FILTER_CLOSED : FILTER_OPEN, - updateTimelineIsLoading, - }), - [ + // Send to Timeline / Update Signal Status Actions for each table row + const additionalActions = useMemo( + () => + getSignalsActions({ apolloClient, canUserCRUD, - createTimelineCallback, hasIndexWrite, - filterGroup, - setEventsLoadingCallback, - setEventsDeletedCallback, + createTimeline: createTimelineCallback, + setEventsLoading: setEventsLoadingCallback, + setEventsDeleted: setEventsDeletedCallback, + status: filterGroup === FILTER_OPEN ? FILTER_CLOSED : FILTER_OPEN, updateTimelineIsLoading, - ] - ); - - const defaultIndices = useMemo(() => [signalsIndex], [signalsIndex]); - const defaultFiltersMemo = useMemo( - () => [ - ...defaultFilters, - ...(filterGroup === FILTER_OPEN ? signalsOpenFilters : signalsClosedFilters), - ], - [defaultFilters, filterGroup] - ); - - const timelineTypeContext = useMemo( - () => ({ - documentType: i18n.SIGNALS_DOCUMENT_TYPE, - footerText: i18n.TOTAL_COUNT_OF_SIGNALS, - loadingText: i18n.LOADING_SIGNALS, - queryFields: requiredFieldsForActions, - timelineActions: additionalActions, - title: i18n.SIGNALS_TABLE_TITLE, - selectAll: canUserCRUD ? selectAll : false, }), - [additionalActions, canUserCRUD, selectAll] - ); + [ + apolloClient, + canUserCRUD, + createTimelineCallback, + hasIndexWrite, + filterGroup, + setEventsLoadingCallback, + setEventsDeletedCallback, + updateTimelineIsLoading, + ] + ); - if (loading || isEmpty(signalsIndex)) { - return ( - - - - - ); - } + const defaultIndices = useMemo(() => [signalsIndex], [signalsIndex]); + const defaultFiltersMemo = useMemo( + () => [ + ...defaultFilters, + ...(filterGroup === FILTER_OPEN ? signalsOpenFilters : signalsClosedFilters), + ], + [defaultFilters, filterGroup] + ); + + const timelineTypeContext = useMemo( + () => ({ + documentType: i18n.SIGNALS_DOCUMENT_TYPE, + footerText: i18n.TOTAL_COUNT_OF_SIGNALS, + loadingText: i18n.LOADING_SIGNALS, + queryFields: requiredFieldsForActions, + timelineActions: additionalActions, + title: i18n.SIGNALS_TABLE_TITLE, + selectAll: canUserCRUD ? selectAll : false, + }), + [additionalActions, canUserCRUD, selectAll] + ); + if (loading || isEmpty(signalsIndex)) { return ( - - } - id={SIGNALS_PAGE_TIMELINE_ID} - start={from} - timelineTypeContext={timelineTypeContext} - utilityBar={utilityBarCallback} - /> + + + + ); } -); -SignalsTableComponent.displayName = 'SignalsTableComponent'; + return ( + + } + id={SIGNALS_PAGE_TIMELINE_ID} + start={from} + timelineTypeContext={timelineTypeContext} + utilityBar={utilityBarCallback} + /> + ); +}; const makeMapStateToProps = () => { const getTimeline = timelineSelectors.getTimelineByIdSelector(); @@ -386,4 +382,7 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ updateTimeline: dispatchUpdateTimeline(dispatch), }); -export const SignalsTable = connect(makeMapStateToProps, mapDispatchToProps)(SignalsTableComponent); +export const SignalsTable = connect( + makeMapStateToProps, + mapDispatchToProps +)(React.memo(SignalsTableComponent)); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/translations.ts index c262f907c9876..c2807db179780 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/translations.ts @@ -53,6 +53,13 @@ export const SIGNALS_HEADERS_RULE = i18n.translate( } ); +export const SIGNALS_HEADERS_VERSION = i18n.translate( + 'xpack.siem.eventsViewer.signals.defaultHeaders.versionTitle', + { + defaultMessage: 'Version', + } +); + export const SIGNALS_HEADERS_METHOD = i18n.translate( 'xpack.siem.eventsViewer.signals.defaultHeaders.methodTitle', { diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.tsx index 24e14473d40e9..bbaccb7882484 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.tsx @@ -10,7 +10,6 @@ import React, { useEffect, useReducer, Dispatch, createContext, useContext } fro import { usePrivilegeUser } from '../../../../containers/detection_engine/signals/use_privilege_user'; import { useSignalIndex } from '../../../../containers/detection_engine/signals/use_signal_index'; import { useKibana } from '../../../../lib/kibana'; -import { useCreatePackagedRules } from '../../../../containers/detection_engine/rules/use_create_packaged_rules'; export interface State { canUserCRUD: boolean | null; @@ -162,14 +161,6 @@ export const useUserInfo = (): State => { createSignalIndex, ] = useSignalIndex(); - useCreatePackagedRules({ - canUserCRUD, - hasIndexManage, - hasManageApiKey, - isAuthenticated, - isSignalIndexExists, - }); - const uiCapabilities = useKibana().services.application.capabilities; const capabilitiesCanUserCRUD: boolean = typeof uiCapabilities.siem.crud === 'boolean' ? uiCapabilities.siem.crud : false; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx index d9e0377b34060..9e292fa69b2c4 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx @@ -18,7 +18,6 @@ import { GlobalTime } from '../../containers/global_time'; import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../containers/source'; import { AlertsTable } from '../../components/alerts_viewer/alerts_table'; import { FiltersGlobal } from '../../components/filters_global'; -import { HeaderPage } from '../../components/header_page'; import { DETECTION_ENGINE_PAGE_NAME } from '../../components/link_to/redirect_to_detection_engine'; import { SiemSearchBar } from '../../components/search_bar'; import { WrapperPage } from '../../components/wrapper_page'; @@ -37,6 +36,7 @@ import { signalsHistogramOptions } from './components/signals_histogram_panel/co import { useUserInfo } from './components/user_info'; import { DetectionEngineEmptyPage } from './detection_engine_empty_page'; import { DetectionEngineNoIndex } from './detection_engine_no_signal_index'; +import { DetectionEngineHeaderPage } from './components/detection_engine_header_page'; import { DetectionEngineUserUnauthenticated } from './detection_engine_user_unauthenticated'; import * as i18n from './translations'; import { DetectionEngineTab } from './types'; @@ -54,7 +54,7 @@ export interface DispatchProps { }>; } -type DetectionEngineComponentProps = ReduxProps & DispatchProps; +type DetectionEnginePageComponentProps = ReduxProps & DispatchProps; const detectionsTabs = [ { @@ -69,153 +69,154 @@ const detectionsTabs = [ }, ]; -const DetectionEngineComponent = React.memo( - ({ filters, query, setAbsoluteRangeDatePicker }) => { - const { tabName = DetectionEngineTab.signals } = useParams(); - const { - loading, - isSignalIndexExists, - isAuthenticated: isUserAuthenticated, - canUserCRUD, - signalIndexName, - hasIndexWrite, - } = useUserInfo(); - - const [lastSignals] = useSignalInfo({}); - - const updateDateRangeCallback = useCallback( - (min: number, max: number) => { - setAbsoluteRangeDatePicker({ id: 'global', from: min, to: max }); - }, - [setAbsoluteRangeDatePicker] +const DetectionEnginePageComponent: React.FC = ({ + filters, + query, + setAbsoluteRangeDatePicker, +}) => { + const { tabName = DetectionEngineTab.signals } = useParams(); + const { + loading, + isSignalIndexExists, + isAuthenticated: isUserAuthenticated, + canUserCRUD, + signalIndexName, + hasIndexWrite, + } = useUserInfo(); + + const [lastSignals] = useSignalInfo({}); + + const updateDateRangeCallback = useCallback( + (min: number, max: number) => { + setAbsoluteRangeDatePicker({ id: 'global', from: min, to: max }); + }, + [setAbsoluteRangeDatePicker] + ); + + const tabs = useMemo( + () => ( + + {detectionsTabs.map(tab => ( + + {tab.name} + + ))} + + ), + [detectionsTabs, tabName] + ); + + if (isUserAuthenticated != null && !isUserAuthenticated && !loading) { + return ( + + + + ); - - if (isUserAuthenticated != null && !isUserAuthenticated && !loading) { - return ( - - - - - ); - } - if (isSignalIndexExists != null && !isSignalIndexExists && !loading) { - return ( - - - - - ); - } - - const tabs = useMemo( - () => ( - - {detectionsTabs.map(tab => ( - - {tab.name} - - ))} - - ), - [detectionsTabs, tabName] + } + if (isSignalIndexExists != null && !isSignalIndexExists && !loading) { + return ( + + + + ); + } - return ( - <> - {hasIndexWrite != null && !hasIndexWrite && } - - {({ indicesExist, indexPattern }) => { - return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( - - - - - - - {i18n.LAST_SIGNAL} - {': '} - {lastSignals} - - ) - } - title={i18n.PAGE_TITLE} - > - - {i18n.BUTTON_MANAGE_RULES} - - - - - {({ to, from, deleteQuery, setQuery }) => ( + return ( + <> + {hasIndexWrite != null && !hasIndexWrite && } + + {({ indicesExist, indexPattern }) => { + return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( + + + + + + - {tabs} - - {tabName === DetectionEngineTab.signals && ( - <> - - - - - )} - {tabName === DetectionEngineTab.alerts && ( - <> - - - - - )} + {i18n.LAST_SIGNAL} + {': '} + {lastSignals} - )} - - - - ) : ( - - - + ) + } + title={i18n.PAGE_TITLE} + > + + {i18n.BUTTON_MANAGE_RULES} + + + + + {({ to, from, deleteQuery, setQuery }) => ( + <> + {tabs} + + {tabName === DetectionEngineTab.signals && ( + <> + + + + + )} + {tabName === DetectionEngineTab.alerts && ( + <> + + + + + )} + + )} + - ); - }} - - - - ); - } -); -DetectionEngineComponent.displayName = 'DetectionEngineComponent'; + + ) : ( + + + + + ); + }} + + + + ); +}; const makeMapStateToProps = () => { const getGlobalInputs = inputsSelectors.globalSelector(); @@ -230,8 +231,11 @@ const makeMapStateToProps = () => { }; }; -export const DetectionEngine = connect(makeMapStateToProps, { +const mapDispatchToProps = { setAbsoluteRangeDatePicker: dispatchSetAbsoluteRangeDatePicker, -})(DetectionEngineComponent); +}; -DetectionEngine.displayName = 'DetectionEngine'; +export const DetectionEnginePage = connect( + makeMapStateToProps, + mapDispatchToProps +)(React.memo(DetectionEnginePageComponent)); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx index 33186d2787d8a..1509348819510 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx @@ -8,18 +8,18 @@ import React from 'react'; import { Redirect, Route, Switch, RouteComponentProps } from 'react-router-dom'; import { ManageUserInfo } from './components/user_info'; -import { CreateRuleComponent } from './rules/create'; -import { DetectionEngine } from './detection_engine'; -import { EditRuleComponent } from './rules/edit'; -import { RuleDetails } from './rules/details'; -import { RulesComponent } from './rules'; +import { CreateRulePage } from './rules/create'; +import { DetectionEnginePage } from './detection_engine'; +import { EditRulePage } from './rules/edit'; +import { RuleDetailsPage } from './rules/details'; +import { RulesPage } from './rules'; import { DetectionEngineTab } from './types'; const detectionEnginePath = `/:pageName(detections)`; type Props = Partial> & { url: string }; -export const DetectionEngineContainer = React.memo(() => ( +const DetectionEngineContainerComponent: React.FC = () => ( (() => ( path={`${detectionEnginePath}/:tabName(${DetectionEngineTab.signals}|${DetectionEngineTab.alerts})`} strict > - + - + - + - - + + - - + + (() => ( /> -)); -DetectionEngineContainer.displayName = 'DetectionEngineContainer'; +); + +export const DetectionEngineContainer = React.memo(DetectionEngineContainerComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/__mocks__/mock.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/__mocks__/mock.ts index b79b3ed091f16..b62247e8f9a4f 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/__mocks__/mock.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/__mocks__/mock.ts @@ -56,13 +56,10 @@ export const mockTableData: TableData[] = [ id: 'abe6c564-050d-45a5-aaf0-386c37dd1f61', immutable: false, isLoading: false, - lastCompletedRun: undefined, - lastResponse: { type: '—' }, - method: 'saved_query', + risk_score: 21, rule: { href: '#/detections/rules/id/abe6c564-050d-45a5-aaf0-386c37dd1f61', name: 'Home Grown!', - status: 'Status Placeholder', }, rule_id: 'b5ba41ab-aaf3-4f43-971b-bdf9434ce0ea', severity: 'low', @@ -108,13 +105,10 @@ export const mockTableData: TableData[] = [ id: '63f06f34-c181-4b2d-af35-f2ace572a1ee', immutable: false, isLoading: false, - lastCompletedRun: undefined, - lastResponse: { type: '—' }, - method: 'saved_query', + risk_score: 21, rule: { href: '#/detections/rules/id/63f06f34-c181-4b2d-af35-f2ace572a1ee', name: 'Home Grown!', - status: 'Status Placeholder', }, rule_id: 'b5ba41ab-aaf3-4f43-971b-bdf9434ce0ea', severity: 'low', diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/actions.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/actions.tsx index 435edcab433b6..d6bf8643fff1c 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/actions.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/actions.tsx @@ -64,13 +64,12 @@ export const deleteRulesAction = async ( onRuleDeleted?: () => void ) => { try { - dispatch({ type: 'updateLoading', ids, isLoading: true }); + dispatch({ type: 'loading', isLoading: true }); const response = await deleteRules({ ids }); - const { rules, errors } = bucketRulesResponse(response); - - dispatch({ type: 'deleteRules', rules }); + const { errors } = bucketRulesResponse(response); + dispatch({ type: 'refresh' }); if (errors.length > 0) { displayErrorToast( i18n.BATCH_ACTION_DELETE_SELECTED_ERROR(ids.length), diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx index d546c4edb55d3..d648854368c28 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx @@ -31,6 +31,7 @@ import { RuleSwitch } from '../components/rule_switch'; import { SeverityBadge } from '../components/severity_badge'; import { ActionToaster } from '../../../../components/toasters'; import { getStatusColor } from '../components/rule_status/helpers'; +import { TruncatableText } from '../../../../components/truncatable_text'; const getActions = ( dispatch: React.Dispatch, @@ -84,8 +85,8 @@ export const getColumns = ( width: '24%', }, { - field: 'method', - name: i18n.COLUMN_METHOD, + field: 'risk_score', + name: i18n.COLUMN_RISK_SCORE, truncateText: true, width: '14%', }, @@ -129,13 +130,13 @@ export const getColumns = ( field: 'tags', name: i18n.COLUMN_TAGS, render: (value: TableData['tags']) => ( - <> + {value.map((tag, i) => ( {tag} ))} - + ), truncateText: true, width: '20%', diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/helpers.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/helpers.ts index 07a2f2f278987..3616d4dbaad24 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/helpers.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/helpers.ts @@ -10,7 +10,6 @@ import { RuleResponseBuckets, } from '../../../../containers/detection_engine/rules'; import { TableData } from '../types'; -import { getEmptyValue } from '../../../../components/empty_value'; /** * Formats rules into the correct format for the AllRulesTable @@ -26,14 +25,9 @@ export const formatRules = (rules: Rule[], selectedIds?: string[]): TableData[] rule: { href: `#/detections/rules/id/${encodeURIComponent(rule.id)}`, name: rule.name, - status: 'Status Placeholder', }, - method: rule.type, // TODO: Map to i18n? + risk_score: rule.risk_score, severity: rule.severity, - lastCompletedRun: undefined, // TODO: Not available yet - lastResponse: { - type: getEmptyValue(), // TODO: Not available yet - }, tags: rule.tags ?? [], activate: rule.enabled, status: rule.status ?? null, diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx index 4aa6b778582f9..a4e7d7a3615cc 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx @@ -6,15 +6,22 @@ import { EuiBasicTable, + EuiButton, EuiContextMenuPanel, - EuiFieldSearch, + EuiEmptyPrompt, EuiLoadingContent, EuiSpacer, } from '@elastic/eui'; +import { isEmpty } from 'lodash/fp'; import React, { useCallback, useEffect, useMemo, useReducer, useState } from 'react'; import { useHistory } from 'react-router-dom'; - import uuid from 'uuid'; + +import { + useRules, + CreatePreBuiltRules, + FilterOptions, +} from '../../../../containers/detection_engine/rules'; import { HeaderSection } from '../../../../components/header_section'; import { UtilityBar, @@ -23,16 +30,19 @@ import { UtilityBarSection, UtilityBarText, } from '../../../../components/detection_engine/utility_bar'; -import { getColumns } from './columns'; -import { useRules } from '../../../../containers/detection_engine/rules'; +import { useStateToaster } from '../../../../components/toasters'; import { Loader } from '../../../../components/loader'; import { Panel } from '../../../../components/panel'; -import { getBatchItems } from './batch_actions'; +import { PrePackagedRulesPrompt } from '../components/pre_packaged_rules/load_empty_prompt'; +import { RuleDownloader } from '../components/rule_downloader'; +import { getPrePackagedRuleStatus } from '../helpers'; +import * as i18n from '../translations'; import { EuiBasicTableOnChange, TableData } from '../types'; +import { getBatchItems } from './batch_actions'; +import { getColumns } from './columns'; import { allRulesReducer, State } from './reducer'; -import * as i18n from '../translations'; -import { RuleDownloader } from '../components/rule_downloader'; -import { useStateToaster } from '../../../../components/toasters'; +import { RulesTableFilters } from './rules_table_filters/rules_table_filters'; +import { DETECTION_ENGINE_PAGE_NAME } from '../../../../components/link_to/redirect_to_detection_engine'; const initialState: State = { isLoading: true, @@ -52,6 +62,19 @@ const initialState: State = { }, }; +interface AllRulesProps { + createPrePackagedRules: CreatePreBuiltRules | null; + hasNoPermissions: boolean; + importCompleteToggle: boolean; + loading: boolean; + loadingCreatePrePackagedRules: boolean; + refetchPrePackagedRulesStatus: () => void; + rulesInstalled: number | null; + rulesNotInstalled: number | null; + rulesNotUpdated: number | null; + setRefreshRulesData: (refreshRule: () => void) => void; +} + /** * Table Component for displaying all Rules for a given cluster. Provides the ability to filter * by name, sort by enabled, and perform the following actions: @@ -60,191 +83,262 @@ const initialState: State = { * * Delete * * Import/Export */ -export const AllRules = React.memo<{ - hasNoPermissions: boolean; - importCompleteToggle: boolean; - loading: boolean; -}>(({ hasNoPermissions, importCompleteToggle, loading }) => { - const [ - { - exportPayload, - filterOptions, - isLoading, - refreshToggle, - selectedItems, - tableData, - pagination, - }, - dispatch, - ] = useReducer(allRulesReducer, initialState); - const history = useHistory(); - const [isInitialLoad, setIsInitialLoad] = useState(true); - const [isLoadingRules, rulesData] = useRules(pagination, filterOptions, refreshToggle); - const [, dispatchToaster] = useStateToaster(); - - const getBatchItemsPopoverContent = useCallback( - (closePopover: () => void) => ( - - ), - [selectedItems, dispatch, dispatchToaster, history] - ); - - const tableOnChangeCallback = useCallback( - ({ page, sort }: EuiBasicTableOnChange) => { +export const AllRules = React.memo( + ({ + createPrePackagedRules, + hasNoPermissions, + importCompleteToggle, + loading, + loadingCreatePrePackagedRules, + refetchPrePackagedRulesStatus, + rulesInstalled, + rulesNotInstalled, + rulesNotUpdated, + setRefreshRulesData, + }) => { + const [ + { + exportPayload, + filterOptions, + isLoading, + refreshToggle, + selectedItems, + tableData, + pagination, + }, + dispatch, + ] = useReducer(allRulesReducer, initialState); + const history = useHistory(); + const [isInitialLoad, setIsInitialLoad] = useState(true); + const [isGlobalLoading, setIsGlobalLoad] = useState(false); + const [, dispatchToaster] = useStateToaster(); + const [isLoadingRules, rulesData, reFetchRulesData] = useRules(pagination, filterOptions); + + const prePackagedRuleStatus = getPrePackagedRuleStatus( + rulesInstalled, + rulesNotInstalled, + rulesNotUpdated + ); + + const getBatchItemsPopoverContent = useCallback( + (closePopover: () => void) => ( + + ), + [selectedItems, dispatch, dispatchToaster, history] + ); + + const tableOnChangeCallback = useCallback( + ({ page, sort }: EuiBasicTableOnChange) => { + dispatch({ + type: 'updatePagination', + pagination: { ...pagination, page: page.index + 1, perPage: page.size }, + }); + dispatch({ + type: 'updateFilterOptions', + filterOptions: { + ...filterOptions, + sortField: 'enabled', // Only enabled is supported for sorting currently + sortOrder: sort?.direction ?? 'desc', + }, + }); + }, + [dispatch, filterOptions, pagination] + ); + + const columns = useMemo(() => { + return getColumns(dispatch, dispatchToaster, history, hasNoPermissions); + }, [dispatch, dispatchToaster, history]); + + useEffect(() => { + dispatch({ type: 'loading', isLoading: isLoadingRules }); + }, [isLoadingRules]); + + useEffect(() => { + if (!isLoadingRules && !loading && isInitialLoad) { + setIsInitialLoad(false); + } + }, [isInitialLoad, isLoadingRules, loading]); + + useEffect(() => { + if (!isGlobalLoading && (isLoadingRules || isLoading)) { + setIsGlobalLoad(true); + } else if (isGlobalLoading && !isLoadingRules && !isLoading) { + setIsGlobalLoad(false); + } + }, [setIsGlobalLoad, isGlobalLoading, isLoadingRules, isLoading]); + + useEffect(() => { + if (!isInitialLoad) { + dispatch({ type: 'refresh' }); + } + }, [importCompleteToggle]); + + useEffect(() => { + if (reFetchRulesData != null) { + reFetchRulesData(); + } + refetchPrePackagedRulesStatus(); + }, [refreshToggle, reFetchRulesData, refetchPrePackagedRulesStatus]); + + useEffect(() => { + if (reFetchRulesData != null) { + setRefreshRulesData(reFetchRulesData); + } + }, [reFetchRulesData, setRefreshRulesData]); + + useEffect(() => { dispatch({ - type: 'updatePagination', - pagination: { ...pagination, page: page.index + 1, perPage: page.size }, + type: 'updateRules', + rules: rulesData.data, + pagination: { + page: rulesData.page, + perPage: rulesData.perPage, + total: rulesData.total, + }, }); + }, [rulesData]); + + const handleCreatePrePackagedRules = useCallback(async () => { + if (createPrePackagedRules != null) { + await createPrePackagedRules(); + dispatch({ type: 'refresh' }); + } + }, [createPrePackagedRules]); + + const euiBasicTableSelectionProps = useMemo( + () => ({ + selectable: (item: TableData) => !item.isLoading, + onSelectionChange: (selected: TableData[]) => + dispatch({ type: 'setSelected', selectedItems: selected }), + }), + [] + ); + + const onFilterChangedCallback = useCallback((newFilterOptions: Partial) => { dispatch({ type: 'updateFilterOptions', filterOptions: { ...filterOptions, - sortField: 'enabled', // Only enabled is supported for sorting currently - sortOrder: sort?.direction ?? 'desc', + ...newFilterOptions, }, }); - }, - [dispatch, filterOptions, pagination] - ); - - const columns = useMemo(() => { - return getColumns(dispatch, dispatchToaster, history, hasNoPermissions); - }, [dispatch, dispatchToaster, history]); - - useEffect(() => { - dispatch({ type: 'loading', isLoading: isLoadingRules }); - - if (!isLoadingRules) { - setIsInitialLoad(false); - } - }, [isLoadingRules]); - - useEffect(() => { - if (!isInitialLoad) { - dispatch({ type: 'refresh' }); - } - }, [importCompleteToggle]); - - useEffect(() => { - dispatch({ - type: 'updateRules', - rules: rulesData.data, - pagination: { - page: rulesData.page, - perPage: rulesData.perPage, - total: rulesData.total, - }, - }); - }, [rulesData]); - - const euiBasicTableSelectionProps = useMemo( - () => ({ - selectable: (item: TableData) => !item.isLoading, - onSelectionChange: (selected: TableData[]) => - dispatch({ type: 'setSelected', selectedItems: selected }), - }), - [] - ); - - return ( - <> - { - dispatchToaster({ - type: 'addToaster', - toast: { - id: uuid.v4(), - title: i18n.SUCCESSFULLY_EXPORTED_RULES(exportCount), - color: 'success', - iconType: 'check', - }, - }); - }} - /> - - - - {isInitialLoad ? ( - - ) : ( + dispatch({ + type: 'updatePagination', + pagination: { ...pagination, page: 1 }, + }); + }, []); + + return ( + <> + { + dispatchToaster({ + type: 'addToaster', + toast: { + id: uuid.v4(), + title: i18n.SUCCESSFULLY_EXPORTED_RULES(exportCount), + color: 'success', + iconType: 'check', + }, + }); + }} + /> + + + <> - - { - dispatch({ - type: 'updateFilterOptions', - filterOptions: { - ...filterOptions, - filter: filterString, - }, - }); - dispatch({ - type: 'updatePagination', - pagination: { ...pagination, page: 1 }, - }); - }} - /> - - - - - - {i18n.SHOWING_RULES(pagination.total ?? 0)} - - - - {i18n.SELECTED_RULES(selectedItems.length)} - {!hasNoPermissions && ( - - {i18n.BATCH_ACTIONS} - - )} - dispatch({ type: 'refresh' })} - > - {i18n.REFRESH} - - - - - - - {(isLoading || loading) && ( + {rulesInstalled != null && rulesInstalled > 0 && ( + + + + )} + {isInitialLoad && isEmpty(tableData) && ( + + )} + {isGlobalLoading && !isEmpty(tableData) && ( )} + {isEmpty(tableData) && prePackagedRuleStatus === 'ruleNotInstalled' && ( + + )} + {!isEmpty(tableData) && ( + <> + + + + {i18n.SHOWING_RULES(pagination.total ?? 0)} + + + + {i18n.SELECTED_RULES(selectedItems.length)} + {!hasNoPermissions && ( + + {i18n.BATCH_ACTIONS} + + )} + dispatch({ type: 'refresh' })} + > + {i18n.REFRESH} + + + + + + {i18n.NO_RULES}} + titleSize="xs" + body={i18n.NO_RULES_BODY} + actions={ + + {i18n.ADD_NEW_RULE} + + } + /> + } + onChange={tableOnChangeCallback} + pagination={{ + pageIndex: pagination.page - 1, + pageSize: pagination.perPage, + totalItemCount: pagination.total, + pageSizeOptions: [5, 10, 20, 50, 100, 200, 300], + }} + sorting={{ sort: { field: 'activate', direction: filterOptions.sortOrder } }} + selection={hasNoPermissions ? undefined : euiBasicTableSelectionProps} + /> + + )} - )} - - - ); -}); + + + ); + } +); AllRules.displayName = 'AllRules'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/reducer.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/reducer.ts index 22d6ca2195fe6..74ce8f2847faa 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/reducer.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/reducer.ts @@ -58,19 +58,21 @@ export const allRulesReducer = (state: State, action: Action): State => { const ruleIds = state.rules.map(r => r.rule_id); const appendIdx = action.appendRuleId != null ? state.rules.findIndex(r => r.id === action.appendRuleId) : -1; - const updatedRules = action.rules.reduce( - (rules, updatedRule) => - ruleIds.includes(updatedRule.rule_id) - ? rules.map(r => (updatedRule.rule_id === r.rule_id ? updatedRule : r)) - : appendIdx !== -1 - ? [ - ...rules.slice(0, appendIdx + 1), - updatedRule, - ...rules.slice(appendIdx + 1, rules.length - 1), - ] - : [...rules, updatedRule], - [...state.rules] - ); + const updatedRules = action.rules.reverse().reduce((rules, updatedRule) => { + let newRules = rules; + if (ruleIds.includes(updatedRule.rule_id)) { + newRules = newRules.map(r => (updatedRule.rule_id === r.rule_id ? updatedRule : r)); + } else if (appendIdx !== -1) { + newRules = [ + ...newRules.slice(0, appendIdx + 1), + updatedRule, + ...newRules.slice(appendIdx + 1, newRules.length), + ]; + } else { + newRules = [...newRules, updatedRule]; + } + return newRules; + }, state.rules); // Update enabled on selectedItems so that batch actions show correct available actions const updatedRuleIdToState = action.rules.reduce>( @@ -88,6 +90,13 @@ export const allRulesReducer = (state: State, action: Action): State => { rules: updatedRules, tableData: formatRules(updatedRules), selectedItems: updatedSelectedItems, + pagination: { + ...state.pagination, + total: + action.appendRuleId != null + ? state.pagination.total + action.rules.length + : state.pagination.total, + }, }; } case 'updatePagination': { @@ -112,6 +121,7 @@ export const allRulesReducer = (state: State, action: Action): State => { ...state, rules: updatedRules, tableData: formatRules(updatedRules), + refreshToggle: !state.refreshToggle, }; } case 'setSelected': { diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.tsx new file mode 100644 index 0000000000000..daf519f5af695 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.tsx @@ -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. + */ + +import React, { useCallback, useEffect, useState } from 'react'; + +import { + EuiFieldSearch, + EuiFilterButton, + EuiFilterGroup, + EuiFlexGroup, + EuiFlexItem, +} from '@elastic/eui'; +import * as i18n from '../../translations'; + +import { FilterOptions } from '../../../../../containers/detection_engine/rules'; +import { useTags } from '../../../../../containers/detection_engine/rules/use_tags'; +import { TagsFilterPopover } from './tags_filter_popover'; + +interface RulesTableFiltersProps { + onFilterChanged: (filterOptions: Partial) => void; +} + +/** + * Collection of filters for filtering data within the RulesTable. Contains search bar, Elastic/Custom + * Rules filter button toggle, and tag selection + * + * @param onFilterChanged change listener to be notified on filter changes + */ +const RulesTableFiltersComponent = ({ onFilterChanged }: RulesTableFiltersProps) => { + const [filter, setFilter] = useState(''); + const [selectedTags, setSelectedTags] = useState([]); + const [showCustomRules, setShowCustomRules] = useState(false); + const [showElasticRules, setShowElasticRules] = useState(false); + const [isLoadingTags, tags] = useTags(); + + // Propagate filter changes to parent + useEffect(() => { + onFilterChanged({ filter, showCustomRules, showElasticRules, tags: selectedTags }); + }, [filter, selectedTags, showCustomRules, showElasticRules, onFilterChanged]); + + const handleOnSearch = useCallback(filterString => setFilter(filterString.trim()), [setFilter]); + + const handleElasticRulesClick = useCallback(() => { + setShowElasticRules(!showElasticRules); + setShowCustomRules(false); + }, [setShowElasticRules, showElasticRules, setShowCustomRules]); + + const handleCustomRulesClick = useCallback(() => { + setShowCustomRules(!showCustomRules); + setShowElasticRules(false); + }, [setShowElasticRules, showCustomRules, setShowCustomRules]); + + return ( + + + + + + + + + + + + + + + {i18n.ELASTIC_RULES} + + + {i18n.CUSTOM_RULES} + + + + + ); +}; + +RulesTableFiltersComponent.displayName = 'RulesTableFiltersComponent'; + +export const RulesTableFilters = React.memo(RulesTableFiltersComponent); + +RulesTableFilters.displayName = 'RulesTableFilters'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/rules_table_filters/tags_filter_popover.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/rules_table_filters/tags_filter_popover.tsx new file mode 100644 index 0000000000000..b9d2c97f063b1 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/rules_table_filters/tags_filter_popover.tsx @@ -0,0 +1,96 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { Dispatch, SetStateAction, useEffect, useState } from 'react'; +import { + EuiFilterButton, + EuiFilterSelectItem, + EuiFlexGroup, + EuiFlexItem, + EuiPanel, + EuiPopover, + EuiText, +} from '@elastic/eui'; +import styled from 'styled-components'; +import * as i18n from '../../translations'; +import { toggleSelectedGroup } from '../../../../../components/ml_popover/jobs_table/filters/toggle_selected_group'; + +interface TagsFilterPopoverProps { + tags: string[]; + onSelectedTagsChanged: Dispatch>; + isLoading: boolean; +} + +const ScrollableDiv = styled.div` + max-height: 250px; + overflow: auto; +`; + +/** + * Popover for selecting tags to filter on + * + * @param tags to display for filtering + * @param onSelectedTagsChanged change listener to be notified when tag selection changes + */ +export const TagsFilterPopoverComponent = ({ + tags, + onSelectedTagsChanged, +}: TagsFilterPopoverProps) => { + const [isTagPopoverOpen, setIsTagPopoverOpen] = useState(false); + const [selectedTags, setSelectedTags] = useState([]); + + useEffect(() => { + onSelectedTagsChanged(selectedTags); + }, [selectedTags.sort().join()]); + + return ( + setIsTagPopoverOpen(!isTagPopoverOpen)} + isSelected={isTagPopoverOpen} + hasActiveFilters={selectedTags.length > 0} + numActiveFilters={selectedTags.length} + > + {i18n.TAGS} + + } + isOpen={isTagPopoverOpen} + closePopover={() => setIsTagPopoverOpen(!isTagPopoverOpen)} + panelPaddingSize="none" + > + + {tags.map((tag, index) => ( + toggleSelectedGroup(tag, selectedTags, setSelectedTags)} + > + {`${tag}`} + + ))} + + {tags.length === 0 && ( + + + + {i18n.NO_TAGS_AVAILABLE} + + + + )} + + ); +}; + +TagsFilterPopoverComponent.displayName = 'TagsFilterPopoverComponent'; + +export const TagsFilterPopover = React.memo(TagsFilterPopoverComponent); + +TagsFilterPopover.displayName = 'TagsFilterPopover'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/load_empty_prompt.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/load_empty_prompt.tsx new file mode 100644 index 0000000000000..41b7fafd6becd --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/load_empty_prompt.tsx @@ -0,0 +1,65 @@ +/* + * 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 { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, EuiButton } from '@elastic/eui'; +import React, { memo, useCallback } from 'react'; +import styled from 'styled-components'; + +import { DETECTION_ENGINE_PAGE_NAME } from '../../../../../components/link_to/redirect_to_detection_engine'; +import * as i18n from './translations'; + +const EmptyPrompt = styled(EuiEmptyPrompt)` + align-self: center; /* Corrects horizontal centering in IE11 */ +`; + +interface PrePackagedRulesPromptProps { + createPrePackagedRules: () => void; + loading: boolean; + userHasNoPermissions: boolean; +} + +const PrePackagedRulesPromptComponent: React.FC = ({ + createPrePackagedRules, + loading = false, + userHasNoPermissions = true, +}) => { + const handlePreBuiltCreation = useCallback(() => { + createPrePackagedRules(); + }, [createPrePackagedRules]); + return ( + {i18n.PRE_BUILT_TITLE}} + body={

{i18n.PRE_BUILT_MSG}

} + actions={ + + + + {i18n.PRE_BUILT_ACTION} + + + + + {i18n.CREATE_RULE_ACTION} + + + + } + /> + ); +}; + +export const PrePackagedRulesPrompt = memo(PrePackagedRulesPromptComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/translations.ts new file mode 100644 index 0000000000000..e70eadda57085 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/translations.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const PRE_BUILT_TITLE = i18n.translate( + 'xpack.siem.detectionEngine.rules.prePackagedRules.emptyPromptTitle', + { + defaultMessage: 'Load Elastic prebuilt detection rules', + } +); + +export const PRE_BUILT_MSG = i18n.translate( + 'xpack.siem.detectionEngine.rules.prePackagedRules.emptyPromptMessage', + { + defaultMessage: + 'Elastic SIEM comes with prebuilt detection rules that run in the background and create signals when their conditions are met. By default, all prebuilt rules are disabled and you select which rules you want to activate.', + } +); + +export const PRE_BUILT_ACTION = i18n.translate( + 'xpack.siem.detectionEngine.rules.prePackagedRules.loadPreBuiltButton', + { + defaultMessage: 'Load prebuilt detection rules', + } +); + +export const CREATE_RULE_ACTION = i18n.translate( + 'xpack.siem.detectionEngine.rules.prePackagedRules.createOwnRuletButton', + { + defaultMessage: 'Create your own rules', + } +); + +export const UPDATE_PREPACKAGED_RULES_TITLE = i18n.translate( + 'xpack.siem.detectionEngine.rules.updatePrePackagedRulesTitle', + { + defaultMessage: 'Update available for Elastic prebuilt rules', + } +); + +export const UPDATE_PREPACKAGED_RULES_MSG = (updateRules: number) => + i18n.translate('xpack.siem.detectionEngine.rules.updatePrePackagedRulesMsg', { + values: { updateRules }, + defaultMessage: + 'You can update {updateRules} Elastic prebuilt {updateRules, plural, =1 {rule} other {rules}}. Note that this will reload deleted Elastic prebuilt rules.', + }); + +export const UPDATE_PREPACKAGED_RULES = (updateRules: number) => + i18n.translate('xpack.siem.detectionEngine.rules.updatePrePackagedRulesButton', { + values: { updateRules }, + defaultMessage: + 'Update {updateRules} Elastic prebuilt {updateRules, plural, =1 {rule} other {rules}} ', + }); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/update_callout.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/update_callout.tsx new file mode 100644 index 0000000000000..80a120ebc63ef --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pre_packaged_rules/update_callout.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { memo } from 'react'; + +import { EuiCallOut, EuiButton } from '@elastic/eui'; +import * as i18n from './translations'; + +interface UpdatePrePackagedRulesCallOutProps { + loading: boolean; + numberOfUpdatedRules: number; + updateRules: () => void; +} + +const UpdatePrePackagedRulesCallOutComponent: React.FC = ({ + loading, + numberOfUpdatedRules, + updateRules, +}) => ( + +

{i18n.UPDATE_PREPACKAGED_RULES_MSG(numberOfUpdatedRules)}

+ + {i18n.UPDATE_PREPACKAGED_RULES(numberOfUpdatedRules)} + +
+); + +export const UpdatePrePackagedRulesCallOut = memo(UpdatePrePackagedRulesCallOutComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/index.tsx index 73c07673a82f4..8370af397bfec 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/index.tsx @@ -4,7 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiButton, EuiHorizontalRule, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { + EuiAccordion, + EuiButton, + EuiHorizontalRule, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiButtonEmpty, +} from '@elastic/eui'; import { isEqual } from 'lodash/fp'; import React, { FC, memo, useCallback, useEffect, useState } from 'react'; import styled from 'styled-components'; @@ -35,6 +43,28 @@ const TagContainer = styled.div` margin-top: 16px; `; +TagContainer.displayName = 'TagContainer'; + +const AdvancedSettingsAccordion = styled(EuiAccordion)` + .euiAccordion__iconWrapper { + display: none; + } + + .euiAccordion__childWrapper { + transition-duration: 1ms; /* hack to fire Step accordion to set proper content's height */ + } + + &.euiAccordion-isOpen .euiButtonEmpty__content > svg { + transform: rotate(90deg); + } +`; + +const AdvancedSettingsAccordionButton = ( + + {I18n.ADVANCED_SETTINGS} + +); + const StepAboutRuleComponent: FC = ({ addPadding = false, defaultValues, @@ -111,73 +141,41 @@ const StepAboutRuleComponent: FC = ({ }, }} /> - - - - - - + + + + + + + + + = ({ }} /> + + + + + + + + {({ severity }) => { const newRiskScore = defaultRiskScoreBySeverity[severity as SeverityValue]; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/schema.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/schema.tsx index 3de0e7605f3d9..15b793a502840 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/schema.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/schema.tsx @@ -95,7 +95,7 @@ export const schema: FormSchema = { label: i18n.translate( 'xpack.siem.detectionEngine.createRule.stepAboutRule.fieldTimelineTemplateLabel', { - defaultMessage: 'Timeline template', + defaultMessage: 'Investigate detections using this timeline template', } ), }, diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/translations.ts index 9323769765739..3b6680fd4e687 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/translations.ts @@ -6,6 +6,13 @@ import { i18n } from '@kbn/i18n'; +export const ADVANCED_SETTINGS = i18n.translate( + 'xpack.siem.detectionEngine.createRule.stepAboutRuleForm.advancedSettingsButton', + { + defaultMessage: 'Advanced settings', + } +); + export const ADD_REFERENCE = i18n.translate( 'xpack.siem.detectionEngine.createRule.stepAboutRuleForm.addReferenceDescription', { diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx index cbc60015d9c87..55b838077988c 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx @@ -7,10 +7,10 @@ import { EuiButtonEmpty, EuiAccordion, EuiHorizontalRule, EuiPanel, EuiSpacer } from '@elastic/eui'; import React, { useCallback, useRef, useState } from 'react'; import { Redirect } from 'react-router-dom'; -import styled from 'styled-components'; +import styled, { StyledComponent } from 'styled-components'; import { usePersistRule } from '../../../../containers/detection_engine/rules'; -import { HeaderPage } from '../../../../components/header_page'; + import { DETECTION_ENGINE_PAGE_NAME } from '../../../../components/link_to/redirect_to_detection_engine'; import { WrapperPage } from '../../../../components/wrapper_page'; import { displaySuccessToast, useStateToaster } from '../../../../components/toasters'; @@ -21,6 +21,7 @@ import { FormData, FormHook } from '../components/shared_imports'; import { StepAboutRule } from '../components/step_about_rule'; import { StepDefineRule } from '../components/step_define_rule'; import { StepScheduleRule } from '../components/step_schedule_rule'; +import { DetectionEngineHeaderPage } from '../../components/detection_engine_header_page'; import * as RuleI18n from '../translations'; import { AboutStepRule, DefineStepRule, RuleStep, RuleStepData, ScheduleStepRule } from '../types'; import { formatRule } from './helpers'; @@ -29,26 +30,41 @@ import * as i18n from './translations'; const stepsRuleOrder = [RuleStep.defineRule, RuleStep.aboutRule, RuleStep.scheduleRule]; const MyEuiPanel = styled(EuiPanel)<{ - zIndex?: number; + zindex?: number; }>` position: relative; - z-index: ${props => props.zIndex}; /* ugly fix to allow searchBar to overflow the EuiPanel */ + z-index: ${props => props.zindex}; /* ugly fix to allow searchBar to overflow the EuiPanel */ - .euiAccordion__iconWrapper { - display: none; + > .euiAccordion > .euiAccordion__triggerWrapper { + .euiAccordion__button { + cursor: default !important; + &:hover { + text-decoration: none !important; + } + } + + .euiAccordion__iconWrapper { + display: none; + } } +`; + +MyEuiPanel.displayName = 'MyEuiPanel'; + +const StepDefineRuleAccordion: StyledComponent< + typeof EuiAccordion, + any, // eslint-disable-line + { ref: React.MutableRefObject }, + never +> = styled(EuiAccordion)` .euiAccordion__childWrapper { overflow: visible; } - .euiAccordion__button { - cursor: default !important; - &:hover { - text-decoration: none !important; - } - } `; -export const CreateRuleComponent = React.memo(() => { +StepDefineRuleAccordion.displayName = 'StepDefineRuleAccordion'; + +const CreateRulePageComponent: React.FC = () => { const { loading, isSignalIndexExists, @@ -80,16 +96,7 @@ export const CreateRuleComponent = React.memo(() => { const userHasNoPermissions = canUserCRUD != null && hasManageApiKey != null ? !canUserCRUD || !hasManageApiKey : false; - if ( - isSignalIndexExists != null && - isAuthenticated != null && - (!isSignalIndexExists || !isAuthenticated) - ) { - return ; - } else if (userHasNoPermissions) { - return ; - } - + // eslint-disable-next-line react-hooks/rules-of-hooks const setStepData = useCallback( (step: RuleStep, data: unknown, isValid: boolean) => { stepsData.current[step] = { ...stepsData.current[step], data, isValid }; @@ -129,10 +136,12 @@ export const CreateRuleComponent = React.memo(() => { [isStepRuleInReadOnlyView, openAccordionId, stepsData.current, setRule] ); + // eslint-disable-next-line react-hooks/rules-of-hooks const setStepsForm = useCallback((step: RuleStep, form: FormHook) => { stepsForm.current[step] = form; }, []); + // eslint-disable-next-line react-hooks/rules-of-hooks const getAccordionType = useCallback( (accordionId: RuleStep) => { if (accordionId === openAccordionId) { @@ -181,6 +190,7 @@ export const CreateRuleComponent = React.memo(() => { } }; + // eslint-disable-next-line react-hooks/rules-of-hooks const manageAccordions = useCallback( (id: RuleStep, isOpen: boolean) => { const activeRuleIdx = stepsRuleOrder.findIndex(step => step === openAccordionId); @@ -202,6 +212,7 @@ export const CreateRuleComponent = React.memo(() => { [isStepRuleInReadOnlyView, openAccordionId, stepsData] ); + // eslint-disable-next-line react-hooks/rules-of-hooks const manageIsEditable = useCallback( async (id: RuleStep) => { const activeForm = await stepsForm.current[openAccordionId]?.submit(); @@ -228,17 +239,27 @@ export const CreateRuleComponent = React.memo(() => { return ; } + if ( + isSignalIndexExists != null && + isAuthenticated != null && + (!isSignalIndexExists || !isAuthenticated) + ) { + return ; + } else if (userHasNoPermissions) { + return ; + } + return ( <> - - - + { setStepData={setStepData} descriptionDirection="row" /> - + - + { - + { ); -}); -CreateRuleComponent.displayName = 'CreateRuleComponent'; +}; + +export const CreateRulePage = React.memo(CreateRulePageComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx index a23c681a5aab2..3406d5bcd6950 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +/* eslint-disable react-hooks/rules-of-hooks */ + import { EuiButton, EuiLoadingSpinner, @@ -14,7 +16,7 @@ import { EuiTabs, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import React, { memo, useCallback, useMemo, useState } from 'react'; +import React, { FC, memo, useCallback, useMemo, useState } from 'react'; import { Redirect, useParams } from 'react-router-dom'; import { StickyContainer } from 'react-sticky'; @@ -22,7 +24,6 @@ import { ActionCreator } from 'typescript-fsa'; import { connect } from 'react-redux'; import { FiltersGlobal } from '../../../../components/filters_global'; import { FormattedDate } from '../../../../components/formatted_date'; -import { HeaderPage } from '../../../../components/header_page'; import { DETECTION_ENGINE_PAGE_NAME } from '../../../../components/link_to/redirect_to_detection_engine'; import { SiemSearchBar } from '../../../../components/search_bar'; import { WrapperPage } from '../../../../components/wrapper_page'; @@ -34,6 +35,7 @@ import { } from '../../../../containers/source'; import { SpyRoute } from '../../../../utils/route/spy_routes'; +import { DetectionEngineHeaderPage } from '../../components/detection_engine_header_page'; import { SignalsHistogramPanel } from '../../components/signals_histogram_panel'; import { SignalsTable } from '../../components/signals'; import { useUserInfo } from '../../components/user_info'; @@ -98,296 +100,294 @@ const ruleDetailTabs = [ type RuleDetailsComponentProps = ReduxProps & DispatchProps; -const RuleDetailsComponent = memo( - ({ filters, query, setAbsoluteRangeDatePicker }) => { - const { - loading, - isSignalIndexExists, - isAuthenticated, - canUserCRUD, - hasManageApiKey, - hasIndexWrite, - signalIndexName, - } = useUserInfo(); - const { ruleId } = useParams(); - const [isLoading, rule] = useRule(ruleId); - // This is used to re-trigger api rule status when user de/activate rule - const [ruleEnabled, setRuleEnabled] = useState(null); - const [ruleDetailTab, setRuleDetailTab] = useState(RuleDetailTabs.signals); - const { aboutRuleData, defineRuleData, scheduleRuleData } = getStepsData({ - rule, - detailsView: true, - }); - const [lastSignals] = useSignalInfo({ ruleId }); - const userHasNoPermissions = - canUserCRUD != null && hasManageApiKey != null ? !canUserCRUD || !hasManageApiKey : false; +const RuleDetailsPageComponent: FC = ({ + filters, + query, + setAbsoluteRangeDatePicker, +}) => { + const { + loading, + isSignalIndexExists, + isAuthenticated, + canUserCRUD, + hasManageApiKey, + hasIndexWrite, + signalIndexName, + } = useUserInfo(); + const { detailName: ruleId } = useParams(); + const [isLoading, rule] = useRule(ruleId); + // This is used to re-trigger api rule status when user de/activate rule + const [ruleEnabled, setRuleEnabled] = useState(null); + const [ruleDetailTab, setRuleDetailTab] = useState(RuleDetailTabs.signals); + const { aboutRuleData, defineRuleData, scheduleRuleData } = getStepsData({ + rule, + detailsView: true, + }); + const [lastSignals] = useSignalInfo({ ruleId }); + const userHasNoPermissions = + canUserCRUD != null && hasManageApiKey != null ? !canUserCRUD || !hasManageApiKey : false; - if ( - isSignalIndexExists != null && - isAuthenticated != null && - (!isSignalIndexExists || !isAuthenticated) - ) { - return ; - } + if ( + isSignalIndexExists != null && + isAuthenticated != null && + (!isSignalIndexExists || !isAuthenticated) + ) { + return ; + } - const title = isLoading === true || rule === null ? : rule.name; - const subTitle = useMemo( - () => - isLoading === true || rule === null ? ( - - ) : ( - [ + const title = isLoading === true || rule === null ? : rule.name; + const subTitle = useMemo( + () => + isLoading === true || rule === null ? ( + + ) : ( + [ + + ), + }} + />, + rule?.updated_by != null ? ( ), }} - />, - rule?.updated_by != null ? ( - - ), - }} - /> - ) : ( - '' - ), - ] - ), - [isLoading, rule] - ); + /> + ) : ( + '' + ), + ] + ), + [isLoading, rule] + ); - const signalDefaultFilters = useMemo( - () => (ruleId != null ? buildSignalsRuleIdFilter(ruleId) : []), - [ruleId] - ); + const signalDefaultFilters = useMemo( + () => (ruleId != null ? buildSignalsRuleIdFilter(ruleId) : []), + [ruleId] + ); - const signalMergedFilters = useMemo(() => [...signalDefaultFilters, ...filters], [ - signalDefaultFilters, - filters, - ]); + const signalMergedFilters = useMemo(() => [...signalDefaultFilters, ...filters], [ + signalDefaultFilters, + filters, + ]); - const tabs = useMemo( - () => ( - - {ruleDetailTabs.map(tab => ( - setRuleDetailTab(tab.id)} - isSelected={tab.id === ruleDetailTab} - disabled={tab.disabled} - key={tab.id} - > - {tab.name} - - ))} - - ), - [ruleDetailTabs, ruleDetailTab, setRuleDetailTab] - ); - const ruleError = useMemo( - () => - rule?.status === 'failed' && - ruleDetailTab === RuleDetailTabs.signals && - rule?.last_failure_at != null ? ( - - ) : null, - [rule, ruleDetailTab] - ); + const tabs = useMemo( + () => ( + + {ruleDetailTabs.map(tab => ( + setRuleDetailTab(tab.id)} + isSelected={tab.id === ruleDetailTab} + disabled={tab.disabled} + key={tab.id} + > + {tab.name} + + ))} + + ), + [ruleDetailTabs, ruleDetailTab, setRuleDetailTab] + ); + const ruleError = useMemo( + () => + rule?.status === 'failed' && + ruleDetailTab === RuleDetailTabs.signals && + rule?.last_failure_at != null ? ( + + ) : null, + [rule, ruleDetailTab] + ); - const updateDateRangeCallback = useCallback( - (min: number, max: number) => { - setAbsoluteRangeDatePicker({ id: 'global', from: min, to: max }); - }, - [setAbsoluteRangeDatePicker] - ); + const updateDateRangeCallback = useCallback( + (min: number, max: number) => { + setAbsoluteRangeDatePicker({ id: 'global', from: min, to: max }); + }, + [setAbsoluteRangeDatePicker] + ); - const handleOnChangeEnabledRule = useCallback( - (enabled: boolean) => { - if (ruleEnabled == null || enabled !== ruleEnabled) { - setRuleEnabled(enabled); - } - }, - [ruleEnabled, setRuleEnabled] - ); + const handleOnChangeEnabledRule = useCallback( + (enabled: boolean) => { + if (ruleEnabled == null || enabled !== ruleEnabled) { + setRuleEnabled(enabled); + } + }, + [ruleEnabled, setRuleEnabled] + ); - return ( - <> - {hasIndexWrite != null && !hasIndexWrite && } - {userHasNoPermissions && } - - {({ indicesExist, indexPattern }) => { - return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( - - {({ to, from }) => ( - - - - + return ( + <> + {hasIndexWrite != null && !hasIndexWrite && } + {userHasNoPermissions && } + + {({ indicesExist, indexPattern }) => { + return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( + + {({ to, from }) => ( + + + + - - - {detectionI18n.LAST_SIGNAL} - {': '} - {lastSignals} - , - ] - : []), - , - ]} - title={title} - > - - - - + + + {detectionI18n.LAST_SIGNAL} + {': '} + {lastSignals} + , + ] + : []), + , + ]} + title={title} + > + + + + - - - - - {ruleI18n.EDIT_RULE_SETTINGS} - - - - + + + + {ruleI18n.EDIT_RULE_SETTINGS} + + + + + + + + + + {ruleError} + {tabs} + + {ruleDetailTab === RuleDetailTabs.signals && ( + <> + + + + {defineRuleData != null && ( + - - + )} + - - - {ruleError} - {tabs} - - {ruleDetailTab === RuleDetailTabs.signals && ( - <> - - - - {defineRuleData != null && ( - - )} - - - - - {aboutRuleData != null && ( - - )} - - + + + {aboutRuleData != null && ( + + )} + + - - - {scheduleRuleData != null && ( - - )} - - - - - + + {scheduleRuleData != null && ( + + )} + + + + + + + {ruleId != null && ( + - - {ruleId != null && ( - - )} - - )} - {ruleDetailTab === RuleDetailTabs.failures && ( - - )} - - - )} - - ) : ( - - - - - - ); - }} - + )} + + )} + {ruleDetailTab === RuleDetailTabs.failures && } + + + )} + + ) : ( + + - - - ); - } -); + + + ); + }} + -RuleDetailsComponent.displayName = 'RuleDetailsComponent'; + + + ); +}; const makeMapStateToProps = () => { const getGlobalInputs = inputsSelectors.globalSelector(); @@ -402,8 +402,11 @@ const makeMapStateToProps = () => { }; }; -export const RuleDetails = connect(makeMapStateToProps, { +const mapDispatchToProps = { setAbsoluteRangeDatePicker: dispatchSetAbsoluteRangeDatePicker, -})(RuleDetailsComponent); +}; -RuleDetails.displayName = 'RuleDetails'; +export const RuleDetailsPage = connect( + makeMapStateToProps, + mapDispatchToProps +)(memo(RuleDetailsPageComponent)); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx index 9b7833afd7f4d..65f4bd2edf7cd 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +/* eslint-disable react-hooks/rules-of-hooks */ + import { EuiButton, EuiCallOut, @@ -14,16 +16,16 @@ import { EuiTabbedContentTab, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import React, { FC, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { Redirect, useParams } from 'react-router-dom'; import { useRule, usePersistRule } from '../../../../containers/detection_engine/rules'; -import { HeaderPage } from '../../../../components/header_page'; import { WrapperPage } from '../../../../components/wrapper_page'; import { DETECTION_ENGINE_PAGE_NAME } from '../../../../components/link_to/redirect_to_detection_engine'; import { displaySuccessToast, useStateToaster } from '../../../../components/toasters'; import { SpyRoute } from '../../../../utils/route/spy_routes'; import { useUserInfo } from '../../components/user_info'; +import { DetectionEngineHeaderPage } from '../../components/detection_engine_header_page'; import { FormHook, FormData } from '../components/shared_imports'; import { StepPanel } from '../components/step_panel'; import { StepAboutRule } from '../components/step_about_rule'; @@ -48,7 +50,7 @@ interface ScheduleStepRuleForm extends StepRuleForm { data: ScheduleStepRule | null; } -export const EditRuleComponent = memo(() => { +const EditRulePageComponent: FC = () => { const [, dispatchToaster] = useStateToaster(); const { loading: initLoading, @@ -57,20 +59,11 @@ export const EditRuleComponent = memo(() => { canUserCRUD, hasManageApiKey, } = useUserInfo(); - const { ruleId } = useParams(); + const { detailName: ruleId } = useParams(); const [loading, rule] = useRule(ruleId); const userHasNoPermissions = canUserCRUD != null && hasManageApiKey != null ? !canUserCRUD || !hasManageApiKey : false; - if ( - isSignalIndexExists != null && - isAuthenticated != null && - (!isSignalIndexExists || !isAuthenticated) - ) { - return ; - } else if (userHasNoPermissions) { - return ; - } const [initForm, setInitForm] = useState(false); const [myAboutRuleForm, setMyAboutRuleForm] = useState({ @@ -277,10 +270,20 @@ export const EditRuleComponent = memo(() => { return ; } + if ( + isSignalIndexExists != null && + isAuthenticated != null && + (!isSignalIndexExists || !isAuthenticated) + ) { + return ; + } else if (userHasNoPermissions) { + return ; + } + return ( <> - { - + ); -}); -EditRuleComponent.displayName = 'EditRuleComponent'; +}; + +export const EditRulePage = memo(EditRulePageComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx index cfe6cb8da1cb0..4cbaa38e1febc 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx @@ -69,6 +69,52 @@ export const getStepsData = ({ export const useQuery = () => new URLSearchParams(useLocation().search); +export type PrePackagedRuleStatus = + | 'ruleInstalled' + | 'ruleNotInstalled' + | 'ruleNeedUpdate' + | 'someRuleUninstall' + | 'unknown'; + +export const getPrePackagedRuleStatus = ( + rulesInstalled: number | null, + rulesNotInstalled: number | null, + rulesNotUpdated: number | null +): PrePackagedRuleStatus => { + if ( + rulesNotInstalled != null && + rulesInstalled === 0 && + rulesNotInstalled > 0 && + rulesNotUpdated === 0 + ) { + return 'ruleNotInstalled'; + } else if ( + rulesInstalled != null && + rulesInstalled > 0 && + rulesNotInstalled === 0 && + rulesNotUpdated === 0 + ) { + return 'ruleInstalled'; + } else if ( + rulesInstalled != null && + rulesNotInstalled != null && + rulesInstalled > 0 && + rulesNotInstalled > 0 && + rulesNotUpdated === 0 + ) { + return 'someRuleUninstall'; + } else if ( + rulesInstalled != null && + rulesNotInstalled != null && + rulesNotUpdated != null && + rulesInstalled > 0 && + rulesNotInstalled >= 0 && + rulesNotUpdated > 0 + ) { + return 'ruleNeedUpdate'; + } + return 'unknown'; +}; export const setFieldValue = ( form: FormHook, schema: FormSchema, diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx index dd46b33ca7257..75b1ce71efbb6 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx @@ -6,32 +6,82 @@ import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import React, { useState } from 'react'; +import React, { useCallback, useRef, useState } from 'react'; import { Redirect } from 'react-router-dom'; +import { usePrePackagedRules } from '../../../containers/detection_engine/rules'; import { DETECTION_ENGINE_PAGE_NAME } from '../../../components/link_to/redirect_to_detection_engine'; import { FormattedRelativePreferenceDate } from '../../../components/formatted_date'; import { getEmptyTagValue } from '../../../components/empty_value'; -import { HeaderPage } from '../../../components/header_page'; +import { DetectionEngineHeaderPage } from '../components/detection_engine_header_page'; import { WrapperPage } from '../../../components/wrapper_page'; import { SpyRoute } from '../../../utils/route/spy_routes'; +import { useUserInfo } from '../components/user_info'; import { AllRules } from './all'; import { ImportRuleModal } from './components/import_rule_modal'; import { ReadOnlyCallOut } from './components/read_only_callout'; -import { useUserInfo } from '../components/user_info'; +import { UpdatePrePackagedRulesCallOut } from './components/pre_packaged_rules/update_callout'; +import { getPrePackagedRuleStatus } from './helpers'; import * as i18n from './translations'; -export const RulesComponent = React.memo(() => { +type Func = () => void; + +const RulesPageComponent: React.FC = () => { const [showImportModal, setShowImportModal] = useState(false); const [importCompleteToggle, setImportCompleteToggle] = useState(false); + const refreshRulesData = useRef(null); const { loading, isSignalIndexExists, isAuthenticated, canUserCRUD, + hasIndexWrite, hasManageApiKey, } = useUserInfo(); + const { + createPrePackagedRules, + loading: prePackagedRuleLoading, + loadingCreatePrePackagedRules, + refetchPrePackagedRulesStatus, + rulesInstalled, + rulesNotInstalled, + rulesNotUpdated, + } = usePrePackagedRules({ + canUserCRUD, + hasIndexWrite, + hasManageApiKey, + isSignalIndexExists, + isAuthenticated, + }); + const prePackagedRuleStatus = getPrePackagedRuleStatus( + rulesInstalled, + rulesNotInstalled, + rulesNotUpdated + ); + + const userHasNoPermissions = + canUserCRUD != null && hasManageApiKey != null ? !canUserCRUD || !hasManageApiKey : false; + const lastCompletedRun = undefined; + + const handleCreatePrePackagedRules = useCallback(async () => { + if (createPrePackagedRules != null) { + await createPrePackagedRules(); + if (refreshRulesData.current != null) { + refreshRulesData.current(); + } + } + }, [createPrePackagedRules, refreshRulesData]); + + const handleRefetchPrePackagedRulesStatus = useCallback(() => { + if (refetchPrePackagedRulesStatus != null) { + refetchPrePackagedRulesStatus(); + } + }, [refetchPrePackagedRulesStatus]); + + const handleSetRefreshRulesData = useCallback((refreshRule: Func) => { + refreshRulesData.current = refreshRule; + }, []); if ( isSignalIndexExists != null && @@ -40,9 +90,7 @@ export const RulesComponent = React.memo(() => { ) { return ; } - const userHasNoPermissions = - canUserCRUD != null && hasManageApiKey != null ? !canUserCRUD || !hasManageApiKey : false; - const lastCompletedRun = undefined; + return ( <> {userHasNoPermissions && } @@ -52,7 +100,7 @@ export const RulesComponent = React.memo(() => { importComplete={() => setImportCompleteToggle(!importCompleteToggle)} /> - { title={i18n.PAGE_TITLE} > + {prePackagedRuleStatus === 'ruleNotInstalled' && ( + + + {i18n.LOAD_PREPACKAGED_RULES} + + + )} + {prePackagedRuleStatus === 'someRuleUninstall' && ( + + + {i18n.RELOAD_MISSING_PREPACKAGED_RULES(rulesNotInstalled ?? 0)} + + + )} { - + + {prePackagedRuleStatus === 'ruleNeedUpdate' && ( + + )} ); -}); +}; -RulesComponent.displayName = 'RulesComponent'; +export const RulesPage = React.memo(RulesPageComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts index e1257007d44a3..5b1ee049371d2 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts @@ -25,6 +25,14 @@ export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.rules.pageT defaultMessage: 'Signal detection rules', }); +export const ADD_PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.rules.addPageTitle', { + defaultMessage: 'Create', +}); + +export const EDIT_PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.rules.editPageTitle', { + defaultMessage: 'Edit', +}); + export const REFRESH = i18n.translate('xpack.siem.detectionEngine.rules.allRules.refreshTitle', { defaultMessage: 'Refresh', }); @@ -205,10 +213,10 @@ export const COLUMN_RULE = i18n.translate( } ); -export const COLUMN_METHOD = i18n.translate( - 'xpack.siem.detectionEngine.rules.allRules.columns.methodTitle', +export const COLUMN_RISK_SCORE = i18n.translate( + 'xpack.siem.detectionEngine.rules.allRules.columns.riskScoreTitle', { - defaultMessage: 'Method', + defaultMessage: 'Risk score', } ); @@ -247,16 +255,42 @@ export const COLUMN_ACTIVATE = i18n.translate( } ); -export const COLUMN_STATUS = i18n.translate( - 'xpack.siem.detectionEngine.rules.allRules.columns.currentStatusTitle', +export const CUSTOM_RULES = i18n.translate( + 'xpack.siem.detectionEngine.rules.allRules.filters.customRulesTitle', + { + defaultMessage: 'Custom rules', + } +); + +export const ELASTIC_RULES = i18n.translate( + 'xpack.siem.detectionEngine.rules.allRules.filters.elasticRulesTitle', + { + defaultMessage: 'Elastic rules', + } +); + +export const TAGS = i18n.translate('xpack.siem.detectionEngine.rules.allRules.filters.tagsLabel', { + defaultMessage: 'Tags', +}); + +export const NO_TAGS_AVAILABLE = i18n.translate( + 'xpack.siem.detectionEngine.rules.allRules.filters.noTagsAvailableDescription', + { + defaultMessage: 'No tags available', + } +); + +export const NO_RULES = i18n.translate( + 'xpack.siem.detectionEngine.rules.allRules.filters.noRulesTitle', { - defaultMessage: 'Current status', + defaultMessage: 'No rules found', } ); -export const NO_STATUS = i18n.translate( - 'xpack.siem.detectionEngine.rules.allRules.columns.unknownStatusDescription', + +export const NO_RULES_BODY = i18n.translate( + 'xpack.siem.detectionEngine.rules.allRules.filters.noRulesBodyTitle', { - defaultMessage: 'Unknown', + defaultMessage: "We weren't able to find any rules with the above filters.", } ); @@ -302,3 +336,17 @@ export const UPDATE = i18n.translate('xpack.siem.detectionEngine.rules.updateBut export const DELETE = i18n.translate('xpack.siem.detectionEngine.rules.deleteDescription', { defaultMessage: 'Delete', }); + +export const LOAD_PREPACKAGED_RULES = i18n.translate( + 'xpack.siem.detectionEngine.rules.loadPrePackagedRulesButton', + { + defaultMessage: 'Load Elastic prebuilt rules', + } +); + +export const RELOAD_MISSING_PREPACKAGED_RULES = (missingRules: number) => + i18n.translate('xpack.siem.detectionEngine.rules.reloadMissingPrePackagedRulesButton', { + values: { missingRules }, + defaultMessage: + 'Reload {missingRules} deleted Elastic prebuilt {missingRules, plural, =1 {rule} other {rules}} ', + }); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/types.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/types.ts index 5ae516dda5b38..2b50e32a367ec 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/types.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/types.ts @@ -30,15 +30,9 @@ export interface TableData { rule: { href: string; name: string; - status: string; }; - method: string; + risk_score: number; severity: string; - lastCompletedRun: string | undefined; - lastResponse: { - type: string; - message?: string; - }; tags: string[]; activate: boolean; isLoading: boolean; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/utils.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/utils.ts new file mode 100644 index 0000000000000..55772aa73ecf3 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/utils.ts @@ -0,0 +1,98 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Breadcrumb } from 'ui/chrome'; +import { isEmpty } from 'lodash/fp'; + +import { + getDetectionEngineUrl, + getDetectionEngineTabUrl, + getRulesUrl, + getRuleDetailsUrl, + getCreateRuleUrl, + getEditRuleUrl, +} from '../../../components/link_to/redirect_to_detection_engine'; +import * as i18nDetections from '../translations'; +import * as i18nRules from './translations'; +import { RouteSpyState } from '../../../utils/route/types'; + +const getTabBreadcrumb = (pathname: string, search: string[]) => { + const tabPath = pathname.split('/')[2]; + + if (tabPath === 'alerts') { + return { + text: i18nDetections.ALERT, + href: `${getDetectionEngineTabUrl(tabPath)}${!isEmpty(search[0]) ? search[0] : ''}`, + }; + } + + if (tabPath === 'signals') { + return { + text: i18nDetections.SIGNAL, + href: `${getDetectionEngineTabUrl(tabPath)}${!isEmpty(search[0]) ? search[0] : ''}`, + }; + } + + if (tabPath === 'rules') { + return { + text: i18nRules.PAGE_TITLE, + href: `${getRulesUrl()}${!isEmpty(search[0]) ? search[0] : ''}`, + }; + } +}; + +const isRuleCreatePage = (pathname: string) => + pathname.includes('/rules') && pathname.includes('/create'); + +const isRuleEditPage = (pathname: string) => + pathname.includes('/rules') && pathname.includes('/edit'); + +export const getBreadcrumbs = (params: RouteSpyState, search: string[]): Breadcrumb[] => { + let breadcrumb = [ + { + text: i18nDetections.PAGE_TITLE, + href: `${getDetectionEngineUrl()}${!isEmpty(search[0]) ? search[0] : ''}`, + }, + ]; + + const tabBreadcrumb = getTabBreadcrumb(params.pathName, search); + + if (tabBreadcrumb) { + breadcrumb = [...breadcrumb, tabBreadcrumb]; + } + + if (params.detailName && params.state?.ruleName) { + breadcrumb = [ + ...breadcrumb, + { + text: params.state.ruleName, + href: `${getRuleDetailsUrl(params.detailName)}${!isEmpty(search[1]) ? search[1] : ''}`, + }, + ]; + } + + if (isRuleCreatePage(params.pathName)) { + breadcrumb = [ + ...breadcrumb, + { + text: i18nRules.ADD_PAGE_TITLE, + href: `${getCreateRuleUrl()}${!isEmpty(search[1]) ? search[1] : ''}`, + }, + ]; + } + + if (isRuleEditPage(params.pathName) && params.detailName && params.state?.ruleName) { + breadcrumb = [ + ...breadcrumb, + { + text: i18nRules.EDIT_PAGE_TITLE, + href: `${getEditRuleUrl(params.detailName)}${!isEmpty(search[1]) ? search[1] : ''}`, + }, + ]; + } + + return breadcrumb; +}; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts index ab785a8ad2c6d..dd4acaeaf5a02 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts @@ -19,11 +19,11 @@ export const TOTAL_SIGNAL = i18n.translate('xpack.siem.detectionEngine.totalSign }); export const SIGNAL = i18n.translate('xpack.siem.detectionEngine.signalTitle', { - defaultMessage: 'Signals', + defaultMessage: 'Signals (SIEM Detections)', }); export const ALERT = i18n.translate('xpack.siem.detectionEngine.alertTitle', { - defaultMessage: 'Third-party alerts', + defaultMessage: 'External alerts', }); export const BUTTON_MANAGE_RULES = i18n.translate('xpack.siem.detectionEngine.buttonManageRules', { diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/details/utils.ts b/x-pack/legacy/plugins/siem/public/pages/hosts/details/utils.ts index 52e016502940b..c321478f10174 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/details/utils.ts +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/details/utils.ts @@ -5,8 +5,8 @@ */ import { Breadcrumb } from 'ui/chrome'; +import { get, isEmpty } from 'lodash/fp'; -import { get } from 'lodash/fp'; import { hostsModel } from '../../../store'; import { HostsTableType } from '../../../store/hosts/model'; import { getHostsUrl, getHostDetailsUrl } from '../../../components/link_to/redirect_to_hosts'; @@ -29,7 +29,7 @@ export const getBreadcrumbs = (params: HostRouteSpyState, search: string[]): Bre let breadcrumb = [ { text: i18n.PAGE_TITLE, - href: `${getHostsUrl()}${search && search[0] ? search[0] : ''}`, + href: `${getHostsUrl()}${!isEmpty(search[0]) ? search[0] : ''}`, }, ]; @@ -38,7 +38,7 @@ export const getBreadcrumbs = (params: HostRouteSpyState, search: string[]): Bre ...breadcrumb, { text: params.detailName, - href: `${getHostDetailsUrl(params.detailName)}${search && search[1] ? search[1] : ''}`, + href: `${getHostDetailsUrl(params.detailName)}${!isEmpty(search[1]) ? search[1] : ''}`, }, ]; } diff --git a/x-pack/legacy/plugins/siem/public/pages/network/ip_details/utils.ts b/x-pack/legacy/plugins/siem/public/pages/network/ip_details/utils.ts index fed832167a60e..a468812e2718d 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/ip_details/utils.ts +++ b/x-pack/legacy/plugins/siem/public/pages/network/ip_details/utils.ts @@ -5,8 +5,8 @@ */ import { Breadcrumb } from 'ui/chrome'; +import { get, isEmpty } from 'lodash/fp'; -import { get } from 'lodash/fp'; import { decodeIpv6 } from '../../../lib/helpers'; import { getNetworkUrl, getIPDetailsUrl } from '../../../components/link_to/redirect_to_network'; import { networkModel } from '../../../store/network'; @@ -28,7 +28,7 @@ export const getBreadcrumbs = (params: NetworkRouteSpyState, search: string[]): let breadcrumb = [ { text: i18n.PAGE_TITLE, - href: `${getNetworkUrl()}${search && search[0] ? search[0] : ''}`, + href: `${getNetworkUrl()}${!isEmpty(search[0]) ? search[0] : ''}`, }, ]; if (params.detailName != null) { @@ -37,7 +37,7 @@ export const getBreadcrumbs = (params: NetworkRouteSpyState, search: string[]): { text: decodeIpv6(params.detailName), href: `${getIPDetailsUrl(params.detailName, params.flowTarget)}${ - search && search[1] ? search[1] : '' + !isEmpty(search[1]) ? search[1] : '' }`, }, ]; diff --git a/x-pack/legacy/plugins/siem/public/pages/overview/event_counts/index.tsx b/x-pack/legacy/plugins/siem/public/pages/overview/event_counts/index.tsx index d0bb46a1965dc..2a35dbf96d6d7 100644 --- a/x-pack/legacy/plugins/siem/public/pages/overview/event_counts/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/overview/event_counts/index.tsx @@ -37,44 +37,49 @@ interface Props { to: number; } -export const EventCounts = React.memo( - ({ filters = NO_FILTERS, from, indexPattern, query = DEFAULT_QUERY, setQuery, to }) => { - const kibana = useKibana(); +const EventCountsComponent: React.FC = ({ + filters = NO_FILTERS, + from, + indexPattern, + query = DEFAULT_QUERY, + setQuery, + to, +}) => { + const kibana = useKibana(); - return ( - - - - + return ( + + + + - + - - - - - ); - } -); + + + + + ); +}; -EventCounts.displayName = 'EventCounts'; +export const EventCounts = React.memo(EventCountsComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/overview/overview.tsx b/x-pack/legacy/plugins/siem/public/pages/overview/overview.tsx index 572a19ca67b8a..d3c9df8ca0980 100644 --- a/x-pack/legacy/plugins/siem/public/pages/overview/overview.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/overview/overview.tsx @@ -14,7 +14,6 @@ import styled from 'styled-components'; import { AlertsByCategory } from './alerts_by_category'; import { FiltersGlobal } from '../../components/filters_global'; -import { HeaderPage } from '../../components/header_page'; import { SiemSearchBar } from '../../components/search_bar'; import { WrapperPage } from '../../components/wrapper_page'; import { GlobalTime } from '../../containers/global_time'; @@ -29,10 +28,9 @@ import { inputsSelectors, State } from '../../store'; import { setAbsoluteRangeDatePicker as dispatchSetAbsoluteRangeDatePicker } from '../../store/inputs/actions'; import { SpyRoute } from '../../utils/route/spy_routes'; -import * as i18n from './translations'; - const DEFAULT_QUERY: Query = { query: '', language: 'kuery' }; const NO_FILTERS: esFilters.Filter[] = []; + const SidebarFlexItem = styled(EuiFlexItem)` margin-right: 24px; `; @@ -43,95 +41,93 @@ interface OverviewComponentReduxProps { setAbsoluteRangeDatePicker?: SetAbsoluteRangeDatePicker; } -const OverviewComponent = React.memo( - ({ filters = NO_FILTERS, query = DEFAULT_QUERY, setAbsoluteRangeDatePicker }) => ( - <> - - {({ indicesExist, indexPattern }) => - indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( - - - - - - - - - - - - - - - - {({ from, deleteQuery, setQuery, to }) => ( - <> - - - - - - - - - - - - - - - )} - - - - - - ) : ( - - ) - } - - - - - ) +const OverviewComponent: React.FC = ({ + filters = NO_FILTERS, + query = DEFAULT_QUERY, + setAbsoluteRangeDatePicker, +}) => ( + <> + + {({ indicesExist, indexPattern }) => + indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( + + + + + + + + + + + + + + {({ from, deleteQuery, setQuery, to }) => ( + <> + + + + + + + + + + + + + + + )} + + + + + + ) : ( + + ) + } + + + + ); -OverviewComponent.displayName = 'OverviewComponent'; - const makeMapStateToProps = () => { const getGlobalFiltersQuerySelector = inputsSelectors.globalFiltersQuerySelector(); const getGlobalQuerySelector = inputsSelectors.globalQuerySelector(); @@ -144,6 +140,8 @@ const makeMapStateToProps = () => { return mapStateToProps; }; +const mapDispatchToProps = { setAbsoluteRangeDatePicker: dispatchSetAbsoluteRangeDatePicker }; + export const StatefulOverview = compose>( - connect(makeMapStateToProps, { setAbsoluteRangeDatePicker: dispatchSetAbsoluteRangeDatePicker }) -)(OverviewComponent); + connect(makeMapStateToProps, mapDispatchToProps) +)(React.memo(OverviewComponent)); diff --git a/x-pack/legacy/plugins/siem/public/pages/overview/translations.ts b/x-pack/legacy/plugins/siem/public/pages/overview/translations.ts index d449a6cd9d7a8..662fc721111ed 100644 --- a/x-pack/legacy/plugins/siem/public/pages/overview/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/overview/translations.ts @@ -34,15 +34,6 @@ export const PAGE_SUBTITLE = i18n.translate('xpack.siem.overview.pageSubtitle', defaultMessage: 'Security Information & Event Management with the Elastic Stack', }); -export const PAGE_BADGE_LABEL = i18n.translate('xpack.siem.overview.pageBadgeLabel', { - defaultMessage: 'Beta', -}); - -export const PAGE_BADGE_TOOLTIP = i18n.translate('xpack.siem.overview.pageBadgeTooltip', { - defaultMessage: - 'SIEM is still in beta. Please help us improve by reporting issues or bugs in the Kibana repo.', -}); - export const RECENT_TIMELINES = i18n.translate('xpack.siem.overview.recentTimelinesSidebarTitle', { defaultMessage: 'Recent timelines', }); diff --git a/x-pack/legacy/plugins/siem/public/utils/route/helpers.ts b/x-pack/legacy/plugins/siem/public/utils/route/helpers.ts index 188ae9c6c1866..39efccc9f45b8 100644 --- a/x-pack/legacy/plugins/siem/public/utils/route/helpers.ts +++ b/x-pack/legacy/plugins/siem/public/utils/route/helpers.ts @@ -15,6 +15,7 @@ export const initRouteSpy: RouteSpyState = { tabName: undefined, search: '', pathName: '/', + state: undefined, }; export const RouterSpyStateContext = createContext<[RouteSpyState, Dispatch]>([ diff --git a/x-pack/legacy/plugins/siem/public/utils/route/spy_routes.tsx b/x-pack/legacy/plugins/siem/public/utils/route/spy_routes.tsx index 5c24b2f48488d..c88562abef6ae 100644 --- a/x-pack/legacy/plugins/siem/public/utils/route/spy_routes.tsx +++ b/x-pack/legacy/plugins/siem/public/utils/route/spy_routes.tsx @@ -8,6 +8,7 @@ import * as H from 'history'; import { isEqual } from 'lodash/fp'; import { memo, useEffect, useState } from 'react'; import { withRouter } from 'react-router-dom'; +import deepEqual from 'fast-deep-equal'; import { SpyRouteProps } from './types'; import { useRouteSpy } from './use_route_spy'; @@ -19,6 +20,7 @@ export const SpyRouteComponent = memo( match: { params: { pageName, detailName, tabName, flowTarget }, }, + state, }) => { const [isInitializing, setIsInitializing] = useState(true); const [route, dispatch] = useRouteSpy(); @@ -61,8 +63,24 @@ export const SpyRouteComponent = memo( }, }); } + } else { + if (pageName && !deepEqual(state, route.state)) { + dispatch({ + type: 'updateRoute', + route: { + pageName, + detailName, + tabName, + search, + pathName: pathname, + history, + flowTarget, + state, + }, + }); + } } - }, [pathname, search, pageName, detailName, tabName, flowTarget]); + }, [pathname, search, pageName, detailName, tabName, flowTarget, state]); return null; } ); diff --git a/x-pack/legacy/plugins/siem/public/utils/route/types.ts b/x-pack/legacy/plugins/siem/public/utils/route/types.ts index 79d2677eff06f..d3eca36bd0d96 100644 --- a/x-pack/legacy/plugins/siem/public/utils/route/types.ts +++ b/x-pack/legacy/plugins/siem/public/utils/route/types.ts @@ -21,6 +21,7 @@ export interface RouteSpyState { pathName: string; history?: H.History; flowTarget?: FlowTarget; + state?: Record; } export interface HostRouteSpyState extends RouteSpyState { @@ -38,7 +39,10 @@ export type RouteSpyAction = } | { type: 'updateRouteWithOutSearch'; - route: Pick; + route: Pick< + RouteSpyState, + 'pageName' & 'detailName' & 'tabName' & 'pathName' & 'history' & 'state' + >; } | { type: 'updateRoute'; @@ -55,4 +59,6 @@ export type SpyRouteProps = RouteComponentProps<{ tabName: HostsTableType | undefined; search: string; flowTarget: FlowTarget | undefined; -}>; +}> & { + state?: Record; +}; diff --git a/x-pack/legacy/plugins/siem/scripts/convert_saved_search_to_rules.js b/x-pack/legacy/plugins/siem/scripts/convert_saved_search_to_rules.js index 4243e67ca1320..233d4dd7de721 100644 --- a/x-pack/legacy/plugins/siem/scripts/convert_saved_search_to_rules.js +++ b/x-pack/legacy/plugins/siem/scripts/convert_saved_search_to_rules.js @@ -114,7 +114,6 @@ async function main() { ); return [...accum, parsedLine]; } catch (err) { - console.log('error parsing a line in this file:', json, line); return accum; } }, []); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/README.md b/x-pack/legacy/plugins/siem/server/lib/detection_engine/README.md index 7c22d6334a2d1..1d33466a458d2 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/README.md +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/README.md @@ -94,7 +94,7 @@ You should see the new rules created like so: "interval": "5m", "rule_id": "rule-1", "language": "kuery", - "output_index": ".siem-signals-frank-hassanabad", + "output_index": ".siem-signals-some-name", "max_signals": 100, "risk_score": 1, "name": "Detect Root/Admin Users", diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/create_bootstrap_index.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/create_bootstrap_index.ts index d7cb922b5b6c3..dff6e7136bff2 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/create_bootstrap_index.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/create_bootstrap_index.ts @@ -19,7 +19,7 @@ export const createBootstrapIndex = async ( index: string ): Promise => { return callWithRequest('transport.request', { - path: `${index}-000001`, + path: `/${index}-000001`, method: 'PUT', body: { aliases: { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/delete_policy.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/delete_policy.ts index 92003f165d996..aa31c427ec84f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/delete_policy.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/delete_policy.ts @@ -11,7 +11,7 @@ export const deletePolicy = async ( policy: string ): Promise => { return callWithRequest('transport.request', { - path: `_ilm/policy/${policy}`, + path: `/_ilm/policy/${policy}`, method: 'DELETE', }); }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/get_policy_exists.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/get_policy_exists.ts index 7541c4217b387..d5ab1a10180c0 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/get_policy_exists.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/get_policy_exists.ts @@ -12,7 +12,7 @@ export const getPolicyExists = async ( ): Promise => { try { await callWithRequest('transport.request', { - path: `_ilm/policy/${policy}`, + path: `/_ilm/policy/${policy}`, method: 'GET', }); // Return true that there exists a policy which is not 404 or some error diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/set_policy.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/set_policy.ts index 115f0af75898c..fae28bab749ca 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/set_policy.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/set_policy.ts @@ -12,7 +12,7 @@ export const setPolicy = async ( body: unknown ): Promise => { return callWithRequest('transport.request', { - path: `_ilm/policy/${policy}`, + path: `/_ilm/policy/${policy}`, method: 'PUT', body, }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/privileges/read_privileges.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/privileges/read_privileges.ts index 3b84075b9e435..a93be40738e57 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/privileges/read_privileges.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/privileges/read_privileges.ts @@ -11,7 +11,7 @@ export const readPrivileges = async ( index: string ): Promise => { return callWithRequest('transport.request', { - path: `_security/user/_has_privileges`, + path: '/_security/user/_has_privileges', method: 'POST', body: { cluster: [ diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/_mock_server.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/_mock_server.ts index 8bddd4a1ef456..4bf7b3279374b 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/_mock_server.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/_mock_server.ts @@ -9,7 +9,7 @@ import { KibanaConfig } from 'src/legacy/server/kbn_server'; import { ElasticsearchPlugin } from 'src/legacy/core_plugins/elasticsearch'; import { savedObjectsClientMock } from '../../../../../../../../../src/core/server/mocks'; import { alertsClientMock } from '../../../../../../alerting/server/alerts_client.mock'; -import { actionsClientMock } from '../../../../../../actions/server/actions_client.mock'; +import { actionsClientMock } from '../../../../../../../../plugins/actions/server/mocks'; import { APP_ID, SIGNALS_INDEX_KEY } from '../../../../../common/constants'; import { ServerFacade } from '../../../../types'; @@ -59,9 +59,12 @@ export const createMockServer = (config: Record = defaultConfig) }; server.decorate('request', 'getAlertsClient', () => alertsClient); server.decorate('request', 'getBasePath', () => '/s/default'); - server.decorate('request', 'getActionsClient', () => actionsClient); server.plugins.elasticsearch = (elasticsearch as unknown) as ElasticsearchPlugin; server.plugins.spaces = { getSpaceId: () => 'default' }; + server.plugins.actions = { + getActionsClientWithRequest: () => actionsClient, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any; // The types have really bad conflicts at the moment so I have to use any server.decorate('request', 'getSavedObjectsClient', () => savedObjectsClient); return { server: server as ServerFacade & Hapi.Server, @@ -79,11 +82,16 @@ export const createMockServerWithoutAlertClientDecoration = ( port: 0, }); + const savedObjectsClient = savedObjectsClientMock.create(); serverWithoutAlertClient.config = () => createMockKibanaConfig(config); + serverWithoutAlertClient.decorate('request', 'getSavedObjectsClient', () => savedObjectsClient); + serverWithoutAlertClient.plugins.actions = { + getActionsClientWithRequest: () => actionsClient, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any; // The types have really bad conflicts at the moment so I have to use any const actionsClient = actionsClientMock.create(); serverWithoutAlertClient.decorate('request', 'getBasePath', () => '/s/default'); - serverWithoutAlertClient.decorate('request', 'getActionsClient', () => actionsClient); return { serverWithoutAlertClient: serverWithoutAlertClient as ServerFacade & Hapi.Server, @@ -91,40 +99,6 @@ export const createMockServerWithoutAlertClientDecoration = ( }; }; -export const createMockServerWithoutActionClientDecoration = ( - config: Record = defaultConfig -) => { - const serverWithoutActionClient = new Hapi.Server({ - port: 0, - }); - - serverWithoutActionClient.config = () => createMockKibanaConfig(config); - - const alertsClient = alertsClientMock.create(); - serverWithoutActionClient.decorate('request', 'getBasePath', () => '/s/default'); - serverWithoutActionClient.decorate('request', 'getAlertsClient', () => alertsClient); - - return { - serverWithoutActionClient: serverWithoutActionClient as ServerFacade & Hapi.Server, - alertsClient, - }; -}; - -export const createMockServerWithoutActionOrAlertClientDecoration = ( - config: Record = defaultConfig -) => { - const serverWithoutActionOrAlertClient = new Hapi.Server({ - port: 0, - }); - - serverWithoutActionOrAlertClient.config = () => createMockKibanaConfig(config); - - return { - serverWithoutActionOrAlertClient: serverWithoutActionOrAlertClient as ServerFacade & - Hapi.Server, - }; -}; - export const getMockIndexName = () => jest.fn().mockImplementation(() => ({ callWithRequest: jest.fn().mockImplementationOnce(() => 'index-name'), diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts index a84fcb64d9ff7..4a48301ee4b9c 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -6,7 +6,7 @@ import { ServerInjectOptions } from 'hapi'; import { SavedObjectsFindResponse } from 'kibana/server'; -import { ActionResult } from '../../../../../../actions/server/types'; +import { ActionResult } from '../../../../../../../../plugins/actions/server'; import { SignalsStatusRestParams, SignalsQueryRestParams } from '../../signals/types'; import { DETECTION_ENGINE_RULES_URL, @@ -371,7 +371,7 @@ export const getMockPrivileges = () => ({ create_snapshot: true, }, index: { - '.siem-signals-frank-hassanabad-test-space': { + '.siem-signals-test-space': { all: false, manage_ilm: true, read: false, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/signals_mapping.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/signals_mapping.json index 00ae5b1f7426b..4f3ba768b17b0 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/signals_mapping.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/signals_mapping.json @@ -5,6 +5,9 @@ "properties": { "parent": { "properties": { + "rule": { + "type": "keyword" + }, "index": { "type": "keyword" }, @@ -19,6 +22,9 @@ } } }, + "ancestors": { + "type": "object" + }, "rule": { "properties": { "id": { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts index ed193b6473a9e..4b04cb257d4ac 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts @@ -6,9 +6,7 @@ import { createMockServer, - createMockServerWithoutActionClientDecoration, createMockServerWithoutAlertClientDecoration, - createMockServerWithoutActionOrAlertClientDecoration, getMockEmptyIndex, getMockNonEmptyIndex, } from '../__mocks__/_mock_server'; @@ -34,7 +32,7 @@ jest.mock('../../rules/get_prepackaged_rules', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', version: 2, // set one higher than the mocks which is set to 1 to trigger updates @@ -67,30 +65,12 @@ describe('add_prepackaged_rules_route', () => { expect(statusCode).toBe(200); }); - test('returns 404 if actionClient is not available on the route', async () => { - const { serverWithoutActionClient } = createMockServerWithoutActionClientDecoration(); - createRulesRoute(serverWithoutActionClient); - const { statusCode } = await serverWithoutActionClient.inject(addPrepackagedRulesRequest()); - expect(statusCode).toBe(404); - }); - test('returns 404 if alertClient is not available on the route', async () => { const { serverWithoutAlertClient } = createMockServerWithoutAlertClientDecoration(); createRulesRoute(serverWithoutAlertClient); const { statusCode } = await serverWithoutAlertClient.inject(addPrepackagedRulesRequest()); expect(statusCode).toBe(404); }); - - test('returns 404 if alertClient and actionClient are both not available on the route', async () => { - const { - serverWithoutActionOrAlertClient, - } = createMockServerWithoutActionOrAlertClientDecoration(); - createRulesRoute(serverWithoutActionOrAlertClient); - const { statusCode } = await serverWithoutActionOrAlertClient.inject( - addPrepackagedRulesRequest() - ); - expect(statusCode).toBe(404); - }); }); describe('validation', () => { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts index 3c9cad8dc4d4b..28af530272bc7 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts @@ -18,6 +18,7 @@ import { updatePrepackagedRules } from '../../rules/update_prepacked_rules'; import { getRulesToInstall } from '../../rules/get_rules_to_install'; import { getRulesToUpdate } from '../../rules/get_rules_to_update'; import { getExistingPrepackagedRules } from '../../rules/get_existing_prepackaged_rules'; +import { KibanaRequest } from '../../../../../../../../../src/core/server'; export const createAddPrepackedRulesRoute = (server: ServerFacade): Hapi.ServerRoute => { return { @@ -33,13 +34,13 @@ export const createAddPrepackedRulesRoute = (server: ServerFacade): Hapi.ServerR }, async handler(request: RequestFacade, headers) { const alertsClient = isFunction(request.getAlertsClient) ? request.getAlertsClient() : null; - const actionsClient = isFunction(request.getActionsClient) - ? request.getActionsClient() - : null; + const actionsClient = await server.plugins.actions.getActionsClientWithRequest( + KibanaRequest.from((request as unknown) as Hapi.Request) + ); const savedObjectsClient = isFunction(request.getSavedObjectsClient) ? request.getSavedObjectsClient() : null; - if (!alertsClient || !actionsClient || !savedObjectsClient) { + if (!alertsClient || !savedObjectsClient) { return headers.response().code(404); } @@ -60,7 +61,9 @@ export const createAddPrepackedRulesRoute = (server: ServerFacade): Hapi.ServerR ); } } - await installPrepackagedRules(alertsClient, actionsClient, rulesToInstall, spaceIndex); + await Promise.all( + installPrepackagedRules(alertsClient, actionsClient, rulesToInstall, spaceIndex) + ); await updatePrepackagedRules( alertsClient, actionsClient, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts index 0931e941f8e46..5cf6d8955d8b2 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts @@ -6,9 +6,7 @@ import { createMockServer, - createMockServerWithoutActionClientDecoration, createMockServerWithoutAlertClientDecoration, - createMockServerWithoutActionOrAlertClientDecoration, getMockEmptyIndex, } from '../__mocks__/_mock_server'; import { createRulesRoute } from './create_rules_route'; @@ -42,28 +40,12 @@ describe('create_rules_bulk', () => { expect(statusCode).toBe(200); }); - test('returns 404 if actionClient is not available on the route', async () => { - const { serverWithoutActionClient } = createMockServerWithoutActionClientDecoration(); - createRulesRoute(serverWithoutActionClient); - const { statusCode } = await serverWithoutActionClient.inject(getReadBulkRequest()); - expect(statusCode).toBe(404); - }); - test('returns 404 if alertClient is not available on the route', async () => { const { serverWithoutAlertClient } = createMockServerWithoutAlertClientDecoration(); createRulesRoute(serverWithoutAlertClient); const { statusCode } = await serverWithoutAlertClient.inject(getReadBulkRequest()); expect(statusCode).toBe(404); }); - - test('returns 404 if alertClient and actionClient are both not available on the route', async () => { - const { - serverWithoutActionOrAlertClient, - } = createMockServerWithoutActionOrAlertClientDecoration(); - createRulesRoute(serverWithoutActionOrAlertClient); - const { statusCode } = await serverWithoutActionOrAlertClient.inject(getReadBulkRequest()); - expect(statusCode).toBe(404); - }); }); describe('validation', () => { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts index 00a1d2eb980ec..0c98507bc6fa8 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts @@ -21,6 +21,7 @@ import { createBulkErrorObject, } from '../utils'; import { createRulesBulkSchema } from '../schemas/create_rules_bulk_schema'; +import { KibanaRequest } from '../../../../../../../../../src/core/server'; export const createCreateRulesBulkRoute = (server: ServerFacade): Hapi.ServerRoute => { return { @@ -37,17 +38,17 @@ export const createCreateRulesBulkRoute = (server: ServerFacade): Hapi.ServerRou }, async handler(request: BulkRulesRequest, headers) { const alertsClient = isFunction(request.getAlertsClient) ? request.getAlertsClient() : null; - const actionsClient = isFunction(request.getActionsClient) - ? request.getActionsClient() - : null; + const actionsClient = await server.plugins.actions.getActionsClientWithRequest( + KibanaRequest.from((request as unknown) as Hapi.Request) + ); const savedObjectsClient = isFunction(request.getSavedObjectsClient) ? request.getSavedObjectsClient() : null; - if (!alertsClient || !actionsClient || !savedObjectsClient) { + if (!alertsClient || !savedObjectsClient) { return headers.response().code(404); } - const rules = Promise.all( + const rules = await Promise.all( request.payload.map(async payloadRule => { const { created_at: createdAt, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.test.ts index 77c6f6f3b4840..27575fb264f7b 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.test.ts @@ -6,9 +6,7 @@ import { createMockServer, - createMockServerWithoutActionClientDecoration, createMockServerWithoutAlertClientDecoration, - createMockServerWithoutActionOrAlertClientDecoration, getMockNonEmptyIndex, getMockEmptyIndex, } from '../__mocks__/_mock_server'; @@ -58,28 +56,12 @@ describe('create_rules', () => { expect(statusCode).toBe(200); }); - test('returns 404 if actionClient is not available on the route', async () => { - const { serverWithoutActionClient } = createMockServerWithoutActionClientDecoration(); - createRulesRoute(serverWithoutActionClient); - const { statusCode } = await serverWithoutActionClient.inject(getCreateRequest()); - expect(statusCode).toBe(404); - }); - test('returns 404 if alertClient is not available on the route', async () => { const { serverWithoutAlertClient } = createMockServerWithoutAlertClientDecoration(); createRulesRoute(serverWithoutAlertClient); const { statusCode } = await serverWithoutAlertClient.inject(getCreateRequest()); expect(statusCode).toBe(404); }); - - test('returns 404 if alertClient and actionClient are both not available on the route', async () => { - const { - serverWithoutActionOrAlertClient, - } = createMockServerWithoutActionOrAlertClientDecoration(); - createRulesRoute(serverWithoutActionOrAlertClient); - const { statusCode } = await serverWithoutActionOrAlertClient.inject(getCreateRequest()); - expect(statusCode).toBe(404); - }); }); describe('validation', () => { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts index 23acd12d341ed..4480186d9a7a6 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts @@ -18,6 +18,7 @@ import { ruleStatusSavedObjectType } from '../../rules/saved_object_mappings'; import { transformOrError } from './utils'; import { getIndexExists } from '../../index/get_index_exists'; import { callWithRequestFactory, getIndex, transformError } from '../utils'; +import { KibanaRequest } from '../../../../../../../../../src/core/server'; export const createCreateRulesRoute = (server: ServerFacade): Hapi.ServerRoute => { return { @@ -62,13 +63,13 @@ export const createCreateRulesRoute = (server: ServerFacade): Hapi.ServerRoute = references, } = request.payload; const alertsClient = isFunction(request.getAlertsClient) ? request.getAlertsClient() : null; - const actionsClient = isFunction(request.getActionsClient) - ? request.getActionsClient() - : null; + const actionsClient = await server.plugins.actions.getActionsClientWithRequest( + KibanaRequest.from((request as unknown) as Hapi.Request) + ); const savedObjectsClient = isFunction(request.getSavedObjectsClient) ? request.getSavedObjectsClient() : null; - if (!alertsClient || !actionsClient || !savedObjectsClient) { + if (!alertsClient || !savedObjectsClient) { return headers.response().code(404); } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.test.ts index 7b8496b2fe725..e66fc765c08bf 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.test.ts @@ -6,9 +6,7 @@ import { createMockServer, - createMockServerWithoutActionClientDecoration, createMockServerWithoutAlertClientDecoration, - createMockServerWithoutActionOrAlertClientDecoration, } from '../__mocks__/_mock_server'; import { ServerInjectOptions } from 'hapi'; @@ -97,28 +95,12 @@ describe('delete_rules', () => { expect(parsed).toEqual(expected); }); - test('returns 404 if actionClient is not available on the route', async () => { - const { serverWithoutActionClient } = createMockServerWithoutActionClientDecoration(); - deleteRulesBulkRoute(serverWithoutActionClient); - const { statusCode } = await serverWithoutActionClient.inject(getDeleteBulkRequest()); - expect(statusCode).toBe(404); - }); - test('returns 404 if alertClient is not available on the route', async () => { const { serverWithoutAlertClient } = createMockServerWithoutAlertClientDecoration(); deleteRulesBulkRoute(serverWithoutAlertClient); const { statusCode } = await serverWithoutAlertClient.inject(getDeleteBulkRequest()); expect(statusCode).toBe(404); }); - - test('returns 404 if alertClient and actionClient are both not available on the route', async () => { - const { - serverWithoutActionOrAlertClient, - } = createMockServerWithoutActionOrAlertClientDecoration(); - deleteRulesBulkRoute(serverWithoutActionOrAlertClient); - const { statusCode } = await serverWithoutActionOrAlertClient.inject(getDeleteBulkRequest()); - expect(statusCode).toBe(404); - }); }); describe('validation', () => { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts index b77ae56a486c8..c2b5576c09183 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts @@ -15,64 +15,69 @@ import { transformOrBulkError, getIdBulkError } from './utils'; import { transformBulkError } from '../utils'; import { QueryBulkRequest, IRuleSavedAttributesSavedObjectAttributes } from '../../rules/types'; import { ruleStatusSavedObjectType } from '../../rules/saved_object_mappings'; +import { KibanaRequest } from '../../../../../../../../../src/core/server'; -export const createDeleteRulesBulkRoute: Hapi.ServerRoute = { - method: ['POST', 'DELETE'], // allow both POST and DELETE in case their client does not support bodies in DELETE - path: `${DETECTION_ENGINE_RULES_URL}/_bulk_delete`, - options: { - tags: ['access:siem'], - validate: { - options: { - abortEarly: false, +export const createDeleteRulesBulkRoute = (server: ServerFacade): Hapi.ServerRoute => { + return { + method: ['POST', 'DELETE'], // allow both POST and DELETE in case their client does not support bodies in DELETE + path: `${DETECTION_ENGINE_RULES_URL}/_bulk_delete`, + options: { + tags: ['access:siem'], + validate: { + options: { + abortEarly: false, + }, + payload: queryRulesBulkSchema, }, - payload: queryRulesBulkSchema, }, - }, - async handler(request: QueryBulkRequest, headers) { - const alertsClient = isFunction(request.getAlertsClient) ? request.getAlertsClient() : null; - const actionsClient = isFunction(request.getActionsClient) ? request.getActionsClient() : null; - const savedObjectsClient = isFunction(request.getSavedObjectsClient) - ? request.getSavedObjectsClient() - : null; - if (!alertsClient || !actionsClient || !savedObjectsClient) { - return headers.response().code(404); - } - const rules = Promise.all( - request.payload.map(async payloadRule => { - const { id, rule_id: ruleId } = payloadRule; - const idOrRuleIdOrUnknown = id ?? ruleId ?? '(unknown id)'; - try { - const rule = await deleteRules({ - actionsClient, - alertsClient, - id, - ruleId, - }); - if (rule != null) { - const ruleStatuses = await savedObjectsClient.find< - IRuleSavedAttributesSavedObjectAttributes - >({ - type: ruleStatusSavedObjectType, - perPage: 6, - search: rule.id, - searchFields: ['alertId'], + async handler(request: QueryBulkRequest, headers) { + const alertsClient = isFunction(request.getAlertsClient) ? request.getAlertsClient() : null; + const actionsClient = await server.plugins.actions.getActionsClientWithRequest( + KibanaRequest.from((request as unknown) as Hapi.Request) + ); + const savedObjectsClient = isFunction(request.getSavedObjectsClient) + ? request.getSavedObjectsClient() + : null; + if (!alertsClient || !savedObjectsClient) { + return headers.response().code(404); + } + const rules = Promise.all( + request.payload.map(async payloadRule => { + const { id, rule_id: ruleId } = payloadRule; + const idOrRuleIdOrUnknown = id ?? ruleId ?? '(unknown id)'; + try { + const rule = await deleteRules({ + actionsClient, + alertsClient, + id, + ruleId, }); - ruleStatuses.saved_objects.forEach(async obj => - savedObjectsClient.delete(ruleStatusSavedObjectType, obj.id) - ); - return transformOrBulkError(idOrRuleIdOrUnknown, rule); - } else { - return getIdBulkError({ id, ruleId }); + if (rule != null) { + const ruleStatuses = await savedObjectsClient.find< + IRuleSavedAttributesSavedObjectAttributes + >({ + type: ruleStatusSavedObjectType, + perPage: 6, + search: rule.id, + searchFields: ['alertId'], + }); + ruleStatuses.saved_objects.forEach(async obj => + savedObjectsClient.delete(ruleStatusSavedObjectType, obj.id) + ); + return transformOrBulkError(idOrRuleIdOrUnknown, rule); + } else { + return getIdBulkError({ id, ruleId }); + } + } catch (err) { + return transformBulkError(idOrRuleIdOrUnknown, err); } - } catch (err) { - return transformBulkError(idOrRuleIdOrUnknown, err); - } - }) - ); - return rules; - }, + }) + ); + return rules; + }, + }; }; export const deleteRulesBulkRoute = (server: ServerFacade): void => { - server.route(createDeleteRulesBulkRoute); + server.route(createDeleteRulesBulkRoute(server)); }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_route.test.ts index 2854312246c5f..0aa60d3bbd922 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_route.test.ts @@ -6,9 +6,7 @@ import { createMockServer, - createMockServerWithoutActionClientDecoration, createMockServerWithoutAlertClientDecoration, - createMockServerWithoutActionOrAlertClientDecoration, } from '../__mocks__/_mock_server'; import { deleteRulesRoute } from './delete_rules_route'; @@ -67,28 +65,12 @@ describe('delete_rules', () => { expect(statusCode).toBe(404); }); - test('returns 404 if actionClient is not available on the route', async () => { - const { serverWithoutActionClient } = createMockServerWithoutActionClientDecoration(); - deleteRulesRoute(serverWithoutActionClient); - const { statusCode } = await serverWithoutActionClient.inject(getDeleteRequest()); - expect(statusCode).toBe(404); - }); - test('returns 404 if alertClient is not available on the route', async () => { const { serverWithoutAlertClient } = createMockServerWithoutAlertClientDecoration(); deleteRulesRoute(serverWithoutAlertClient); const { statusCode } = await serverWithoutAlertClient.inject(getDeleteRequest()); expect(statusCode).toBe(404); }); - - test('returns 404 if alertClient and actionClient are both not available on the route', async () => { - const { - serverWithoutActionOrAlertClient, - } = createMockServerWithoutActionOrAlertClientDecoration(); - deleteRulesRoute(serverWithoutActionOrAlertClient); - const { statusCode } = await serverWithoutActionOrAlertClient.inject(getDeleteRequest()); - expect(statusCode).toBe(404); - }); }); describe('validation', () => { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_route.ts index ea8b54a79a5e3..33f181cfbb5a5 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_route.ts @@ -15,59 +15,64 @@ import { getIdError, transformOrError } from './utils'; import { transformError } from '../utils'; import { QueryRequest, IRuleSavedAttributesSavedObjectAttributes } from '../../rules/types'; import { ruleStatusSavedObjectType } from '../../rules/saved_object_mappings'; +import { KibanaRequest } from '../../../../../../../../../src/core/server'; -export const createDeleteRulesRoute: Hapi.ServerRoute = { - method: 'DELETE', - path: DETECTION_ENGINE_RULES_URL, - options: { - tags: ['access:siem'], - validate: { - options: { - abortEarly: false, +export const createDeleteRulesRoute = (server: ServerFacade): Hapi.ServerRoute => { + return { + method: 'DELETE', + path: DETECTION_ENGINE_RULES_URL, + options: { + tags: ['access:siem'], + validate: { + options: { + abortEarly: false, + }, + query: queryRulesSchema, }, - query: queryRulesSchema, }, - }, - async handler(request: QueryRequest, headers) { - const { id, rule_id: ruleId } = request.query; - const alertsClient = isFunction(request.getAlertsClient) ? request.getAlertsClient() : null; - const actionsClient = isFunction(request.getActionsClient) ? request.getActionsClient() : null; - const savedObjectsClient = isFunction(request.getSavedObjectsClient) - ? request.getSavedObjectsClient() - : null; - if (!alertsClient || !actionsClient || !savedObjectsClient) { - return headers.response().code(404); - } + async handler(request: QueryRequest, headers) { + const { id, rule_id: ruleId } = request.query; + const alertsClient = isFunction(request.getAlertsClient) ? request.getAlertsClient() : null; + const actionsClient = await server.plugins.actions.getActionsClientWithRequest( + KibanaRequest.from((request as unknown) as Hapi.Request) + ); + const savedObjectsClient = isFunction(request.getSavedObjectsClient) + ? request.getSavedObjectsClient() + : null; + if (!alertsClient || !savedObjectsClient) { + return headers.response().code(404); + } - try { - const rule = await deleteRules({ - actionsClient, - alertsClient, - id, - ruleId, - }); - if (rule != null) { - const ruleStatuses = await savedObjectsClient.find< - IRuleSavedAttributesSavedObjectAttributes - >({ - type: ruleStatusSavedObjectType, - perPage: 6, - search: rule.id, - searchFields: ['alertId'], + try { + const rule = await deleteRules({ + actionsClient, + alertsClient, + id, + ruleId, }); - ruleStatuses.saved_objects.forEach(async obj => - savedObjectsClient.delete(ruleStatusSavedObjectType, obj.id) - ); - return transformOrError(rule, ruleStatuses.saved_objects[0]); - } else { - return getIdError({ id, ruleId }); + if (rule != null) { + const ruleStatuses = await savedObjectsClient.find< + IRuleSavedAttributesSavedObjectAttributes + >({ + type: ruleStatusSavedObjectType, + perPage: 6, + search: rule.id, + searchFields: ['alertId'], + }); + ruleStatuses.saved_objects.forEach(async obj => + savedObjectsClient.delete(ruleStatusSavedObjectType, obj.id) + ); + return transformOrError(rule, ruleStatuses.saved_objects[0]); + } else { + return getIdError({ id, ruleId }); + } + } catch (err) { + return transformError(err); } - } catch (err) { - return transformError(err); - } - }, + }, + }; }; export const deleteRulesRoute = (server: ServerFacade): void => { - server.route(createDeleteRulesRoute); + server.route(createDeleteRulesRoute(server)); }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/export_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/export_rules_route.ts index aa17946849027..b9ff2e6018624 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/export_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/export_rules_route.ts @@ -31,11 +31,8 @@ export const createExportRulesRoute = (server: ServerFacade): Hapi.ServerRoute = }, async handler(request: ExportRulesRequest, headers) { const alertsClient = isFunction(request.getAlertsClient) ? request.getAlertsClient() : null; - const actionsClient = isFunction(request.getActionsClient) - ? request.getActionsClient() - : null; - if (!alertsClient || !actionsClient) { + if (!alertsClient) { return headers.response().code(404); } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_route.test.ts index 0aab02281a536..62c9f44da1e33 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_route.test.ts @@ -6,9 +6,7 @@ import { createMockServer, - createMockServerWithoutActionClientDecoration, createMockServerWithoutAlertClientDecoration, - createMockServerWithoutActionOrAlertClientDecoration, } from '../__mocks__/_mock_server'; import { findRulesRoute } from './find_rules_route'; @@ -43,28 +41,12 @@ describe('find_rules', () => { expect(statusCode).toBe(200); }); - test('returns 404 if actionClient is not available on the route', async () => { - const { serverWithoutActionClient } = createMockServerWithoutActionClientDecoration(); - findRulesRoute(serverWithoutActionClient); - const { statusCode } = await serverWithoutActionClient.inject(getFindRequest()); - expect(statusCode).toBe(404); - }); - test('returns 404 if alertClient is not available on the route', async () => { const { serverWithoutAlertClient } = createMockServerWithoutAlertClientDecoration(); findRulesRoute(serverWithoutAlertClient); const { statusCode } = await serverWithoutAlertClient.inject(getFindRequest()); expect(statusCode).toBe(404); }); - - test('returns 404 if alertClient and actionClient are both not available on the route', async () => { - const { - serverWithoutActionOrAlertClient, - } = createMockServerWithoutActionOrAlertClientDecoration(); - findRulesRoute(serverWithoutActionOrAlertClient); - const { statusCode } = await serverWithoutActionOrAlertClient.inject(getFindRequest()); - expect(statusCode).toBe(404); - }); }); describe('validation', () => { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_route.ts index 6cbc794751f9f..5b12703590407 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_route.ts @@ -15,58 +15,61 @@ import { transformFindAlertsOrError } from './utils'; import { transformError } from '../utils'; import { ruleStatusSavedObjectType } from '../../rules/saved_object_mappings'; -export const createFindRulesRoute: Hapi.ServerRoute = { - method: 'GET', - path: `${DETECTION_ENGINE_RULES_URL}/_find`, - options: { - tags: ['access:siem'], - validate: { - options: { - abortEarly: false, +export const createFindRulesRoute = (): Hapi.ServerRoute => { + return { + method: 'GET', + path: `${DETECTION_ENGINE_RULES_URL}/_find`, + options: { + tags: ['access:siem'], + validate: { + options: { + abortEarly: false, + }, + query: findRulesSchema, }, - query: findRulesSchema, }, - }, - async handler(request: FindRulesRequest, headers) { - const { query } = request; - const alertsClient = isFunction(request.getAlertsClient) ? request.getAlertsClient() : null; - const actionsClient = isFunction(request.getActionsClient) ? request.getActionsClient() : null; - const savedObjectsClient = isFunction(request.getSavedObjectsClient) - ? request.getSavedObjectsClient() - : null; - if (!alertsClient || !actionsClient || !savedObjectsClient) { - return headers.response().code(404); - } + async handler(request: FindRulesRequest, headers) { + const { query } = request; + const alertsClient = isFunction(request.getAlertsClient) ? request.getAlertsClient() : null; + const savedObjectsClient = isFunction(request.getSavedObjectsClient) + ? request.getSavedObjectsClient() + : null; + if (!alertsClient || !savedObjectsClient) { + return headers.response().code(404); + } - try { - const rules = await findRules({ - alertsClient, - perPage: query.per_page, - page: query.page, - sortField: query.sort_field, - sortOrder: query.sort_order, - filter: query.filter, - }); - const ruleStatuses = await Promise.all( - rules.data.map(async rule => { - const results = await savedObjectsClient.find({ - type: ruleStatusSavedObjectType, - perPage: 1, - sortField: 'statusDate', - sortOrder: 'desc', - search: rule.id, - searchFields: ['alertId'], - }); - return results; - }) - ); - return transformFindAlertsOrError(rules, ruleStatuses); - } catch (err) { - return transformError(err); - } - }, + try { + const rules = await findRules({ + alertsClient, + perPage: query.per_page, + page: query.page, + sortField: query.sort_field, + sortOrder: query.sort_order, + filter: query.filter, + }); + const ruleStatuses = await Promise.all( + rules.data.map(async rule => { + const results = await savedObjectsClient.find< + IRuleSavedAttributesSavedObjectAttributes + >({ + type: ruleStatusSavedObjectType, + perPage: 1, + sortField: 'statusDate', + sortOrder: 'desc', + search: rule.id, + searchFields: ['alertId'], + }); + return results; + }) + ); + return transformFindAlertsOrError(rules, ruleStatuses); + } catch (err) { + return transformError(err); + } + }, + }; }; export const findRulesRoute = (server: ServerFacade) => { - server.route(createFindRulesRoute); + server.route(createFindRulesRoute()); }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_status_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_status_route.ts index 545c2e488b1c8..8b3113a044b5a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_status_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_status_route.ts @@ -44,11 +44,10 @@ export const createFindRulesStatusRoute: Hapi.ServerRoute = { async handler(request: FindRulesStatusesRequest, headers) { const { query } = request; const alertsClient = isFunction(request.getAlertsClient) ? request.getAlertsClient() : null; - const actionsClient = isFunction(request.getActionsClient) ? request.getActionsClient() : null; const savedObjectsClient = isFunction(request.getSavedObjectsClient) ? request.getSavedObjectsClient() : null; - if (!alertsClient || !actionsClient || !savedObjectsClient) { + if (!alertsClient || !savedObjectsClient) { return headers.response().code(404); } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/get_prepackaged_rule_status_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/get_prepackaged_rule_status_route.test.ts index 1ae9e87b8eefe..67680a8f86eec 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/get_prepackaged_rule_status_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/get_prepackaged_rule_status_route.test.ts @@ -6,9 +6,7 @@ import { createMockServer, - createMockServerWithoutActionClientDecoration, createMockServerWithoutAlertClientDecoration, - createMockServerWithoutActionOrAlertClientDecoration, getMockNonEmptyIndex, } from '../__mocks__/_mock_server'; import { createRulesRoute } from './create_rules_route'; @@ -33,7 +31,7 @@ jest.mock('../../rules/get_prepackaged_rules', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', version: 2, // set one higher than the mocks which is set to 1 to trigger updates @@ -65,15 +63,6 @@ describe('get_prepackaged_rule_status_route', () => { expect(statusCode).toBe(200); }); - test('returns 404 if actionClient is not available on the route', async () => { - const { serverWithoutActionClient } = createMockServerWithoutActionClientDecoration(); - createRulesRoute(serverWithoutActionClient); - const { statusCode } = await serverWithoutActionClient.inject( - getPrepackagedRulesStatusRequest() - ); - expect(statusCode).toBe(404); - }); - test('returns 404 if alertClient is not available on the route', async () => { const { serverWithoutAlertClient } = createMockServerWithoutAlertClientDecoration(); createRulesRoute(serverWithoutAlertClient); @@ -82,17 +71,6 @@ describe('get_prepackaged_rule_status_route', () => { ); expect(statusCode).toBe(404); }); - - test('returns 404 if alertClient and actionClient are both not available on the route', async () => { - const { - serverWithoutActionOrAlertClient, - } = createMockServerWithoutActionOrAlertClientDecoration(); - createRulesRoute(serverWithoutActionOrAlertClient); - const { statusCode } = await serverWithoutActionOrAlertClient.inject( - getPrepackagedRulesStatusRequest() - ); - expect(statusCode).toBe(404); - }); }); describe('payload', () => { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.ts index 99e29242bced0..0208a209c5eae 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.ts @@ -29,11 +29,8 @@ export const createGetPrepackagedRulesStatusRoute = (): Hapi.ServerRoute => { }, async handler(request: RequestFacade, headers) { const alertsClient = isFunction(request.getAlertsClient) ? request.getAlertsClient() : null; - const actionsClient = isFunction(request.getActionsClient) - ? request.getActionsClient() - : null; - if (!alertsClient || !actionsClient) { + if (!alertsClient) { return headers.response().code(404); } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts index 6efaa1fea60d0..0dfdee2d71375 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts @@ -27,6 +27,7 @@ import { ImportRuleAlertRest } from '../../types'; import { transformOrImportError } from './utils'; import { updateRules } from '../../rules/update_rules'; import { importRulesQuerySchema, importRulesPayloadSchema } from '../schemas/import_rules_schema'; +import { KibanaRequest } from '../../../../../../../../../src/core/server'; export const createImportRulesRoute = (server: ServerFacade): Hapi.ServerRoute => { return { @@ -49,13 +50,13 @@ export const createImportRulesRoute = (server: ServerFacade): Hapi.ServerRoute = }, async handler(request: ImportRulesRequest, headers) { const alertsClient = isFunction(request.getAlertsClient) ? request.getAlertsClient() : null; - const actionsClient = isFunction(request.getActionsClient) - ? request.getActionsClient() - : null; + const actionsClient = await server.plugins.actions.getActionsClientWithRequest( + KibanaRequest.from((request as unknown) as Hapi.Request) + ); const savedObjectsClient = isFunction(request.getSavedObjectsClient) ? request.getSavedObjectsClient() : null; - if (!alertsClient || !actionsClient || !savedObjectsClient) { + if (!alertsClient || !savedObjectsClient) { return headers.response().code(404); } const { filename } = request.payload.file.hapi; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/read_rules_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/read_rules_route.test.ts index 4190225bea1f1..000cd29af8ba9 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/read_rules_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/read_rules_route.test.ts @@ -6,9 +6,7 @@ import { createMockServer, - createMockServerWithoutActionClientDecoration, createMockServerWithoutAlertClientDecoration, - createMockServerWithoutActionOrAlertClientDecoration, } from '../__mocks__/_mock_server'; import { readRulesRoute } from './read_rules_route'; @@ -44,28 +42,12 @@ describe('read_signals', () => { expect(statusCode).toBe(200); }); - test('returns 404 if actionClient is not available on the route', async () => { - const { serverWithoutActionClient } = createMockServerWithoutActionClientDecoration(); - readRulesRoute(serverWithoutActionClient); - const { statusCode } = await serverWithoutActionClient.inject(getReadRequest()); - expect(statusCode).toBe(404); - }); - test('returns 404 if alertClient is not available on the route', async () => { const { serverWithoutAlertClient } = createMockServerWithoutAlertClientDecoration(); readRulesRoute(serverWithoutAlertClient); const { statusCode } = await serverWithoutAlertClient.inject(getReadRequest()); expect(statusCode).toBe(404); }); - - test('returns 404 if alertClient and actionClient are both not available on the route', async () => { - const { - serverWithoutActionOrAlertClient, - } = createMockServerWithoutActionOrAlertClientDecoration(); - readRulesRoute(serverWithoutActionOrAlertClient); - const { statusCode } = await serverWithoutActionOrAlertClient.inject(getReadRequest()); - expect(statusCode).toBe(404); - }); }); describe('validation', () => { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/read_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/read_rules_route.ts index 25d8e6770f699..55fecdc14f755 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/read_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/read_rules_route.ts @@ -31,11 +31,10 @@ export const createReadRulesRoute: Hapi.ServerRoute = { async handler(request: QueryRequest, headers) { const { id, rule_id: ruleId } = request.query; const alertsClient = isFunction(request.getAlertsClient) ? request.getAlertsClient() : null; - const actionsClient = isFunction(request.getActionsClient) ? request.getActionsClient() : null; const savedObjectsClient = isFunction(request.getSavedObjectsClient) ? request.getSavedObjectsClient() : null; - if (!alertsClient || !actionsClient || !savedObjectsClient) { + if (!alertsClient || !savedObjectsClient) { return headers.response().code(404); } try { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk.test.ts index cc41800671d7d..81b6444f38603 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk.test.ts @@ -6,9 +6,7 @@ import { createMockServer, - createMockServerWithoutActionClientDecoration, createMockServerWithoutAlertClientDecoration, - createMockServerWithoutActionOrAlertClientDecoration, } from '../__mocks__/_mock_server'; import { updateRulesRoute } from './update_rules_route'; @@ -70,28 +68,12 @@ describe('update_rules_bulk', () => { expect(parsed).toEqual(expected); }); - test('returns 404 if actionClient is not available on the route', async () => { - const { serverWithoutActionClient } = createMockServerWithoutActionClientDecoration(); - updateRulesRoute(serverWithoutActionClient); - const { statusCode } = await serverWithoutActionClient.inject(getUpdateBulkRequest()); - expect(statusCode).toBe(404); - }); - test('returns 404 if alertClient is not available on the route', async () => { const { serverWithoutAlertClient } = createMockServerWithoutAlertClientDecoration(); updateRulesRoute(serverWithoutAlertClient); const { statusCode } = await serverWithoutAlertClient.inject(getUpdateBulkRequest()); expect(statusCode).toBe(404); }); - - test('returns 404 if alertClient and actionClient are both not available on the route', async () => { - const { - serverWithoutActionOrAlertClient, - } = createMockServerWithoutActionOrAlertClientDecoration(); - updateRulesRoute(serverWithoutActionOrAlertClient); - const { statusCode } = await serverWithoutActionOrAlertClient.inject(getUpdateBulkRequest()); - expect(statusCode).toBe(404); - }); }); describe('validation', () => { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts index e0d2672cf356a..cf98043529bc0 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts @@ -17,6 +17,7 @@ import { transformBulkError } from '../utils'; import { updateRulesBulkSchema } from '../schemas/update_rules_bulk_schema'; import { updateRules } from '../../rules/update_rules'; import { ruleStatusSavedObjectType } from '../../rules/saved_object_mappings'; +import { KibanaRequest } from '../../../../../../../../../src/core/server'; export const createUpdateRulesBulkRoute = (server: ServerFacade): Hapi.ServerRoute => { return { @@ -33,13 +34,13 @@ export const createUpdateRulesBulkRoute = (server: ServerFacade): Hapi.ServerRou }, async handler(request: BulkUpdateRulesRequest, headers) { const alertsClient = isFunction(request.getAlertsClient) ? request.getAlertsClient() : null; - const actionsClient = isFunction(request.getActionsClient) - ? request.getActionsClient() - : null; + const actionsClient = await server.plugins.actions.getActionsClientWithRequest( + KibanaRequest.from((request as unknown) as Hapi.Request) + ); const savedObjectsClient = isFunction(request.getSavedObjectsClient) ? request.getSavedObjectsClient() : null; - if (!alertsClient || !actionsClient || !savedObjectsClient) { + if (!alertsClient || !savedObjectsClient) { return headers.response().code(404); } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.test.ts index a7e8f1b1c0a7e..c4f10d7a20327 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.test.ts @@ -6,9 +6,7 @@ import { createMockServer, - createMockServerWithoutActionClientDecoration, createMockServerWithoutAlertClientDecoration, - createMockServerWithoutActionOrAlertClientDecoration, } from '../__mocks__/_mock_server'; import { updateRulesRoute } from './update_rules_route'; @@ -55,28 +53,12 @@ describe('update_rules', () => { expect(statusCode).toBe(404); }); - test('returns 404 if actionClient is not available on the route', async () => { - const { serverWithoutActionClient } = createMockServerWithoutActionClientDecoration(); - updateRulesRoute(serverWithoutActionClient); - const { statusCode } = await serverWithoutActionClient.inject(getUpdateRequest()); - expect(statusCode).toBe(404); - }); - test('returns 404 if alertClient is not available on the route', async () => { const { serverWithoutAlertClient } = createMockServerWithoutAlertClientDecoration(); updateRulesRoute(serverWithoutAlertClient); const { statusCode } = await serverWithoutAlertClient.inject(getUpdateRequest()); expect(statusCode).toBe(404); }); - - test('returns 404 if alertClient and actionClient are both not available on the route', async () => { - const { - serverWithoutActionOrAlertClient, - } = createMockServerWithoutActionOrAlertClientDecoration(); - updateRulesRoute(serverWithoutActionOrAlertClient); - const { statusCode } = await serverWithoutActionOrAlertClient.inject(getUpdateRequest()); - expect(statusCode).toBe(404); - }); }); describe('validation', () => { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts index 49c9304ae2d25..cbb66317186a1 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts @@ -14,81 +14,41 @@ import { ServerFacade } from '../../../../types'; import { getIdError, transformOrError } from './utils'; import { transformError } from '../utils'; import { ruleStatusSavedObjectType } from '../../rules/saved_object_mappings'; +import { KibanaRequest } from '../../../../../../../../../src/core/server'; -export const createUpdateRulesRoute: Hapi.ServerRoute = { - method: 'PUT', - path: DETECTION_ENGINE_RULES_URL, - options: { - tags: ['access:siem'], - validate: { - options: { - abortEarly: false, +export const createUpdateRulesRoute = (server: ServerFacade): Hapi.ServerRoute => { + return { + method: 'PUT', + path: DETECTION_ENGINE_RULES_URL, + options: { + tags: ['access:siem'], + validate: { + options: { + abortEarly: false, + }, + payload: updateRulesSchema, }, - payload: updateRulesSchema, }, - }, - async handler(request: UpdateRulesRequest, headers) { - const { - description, - enabled, - false_positives: falsePositives, - from, - query, - language, - output_index: outputIndex, - saved_id: savedId, - timeline_id: timelineId, - timeline_title: timelineTitle, - meta, - filters, - rule_id: ruleId, - id, - index, - interval, - max_signals: maxSignals, - risk_score: riskScore, - name, - severity, - tags, - to, - type, - threats, - references, - version, - } = request.payload; - - const alertsClient = isFunction(request.getAlertsClient) ? request.getAlertsClient() : null; - const actionsClient = isFunction(request.getActionsClient) ? request.getActionsClient() : null; - const savedObjectsClient = isFunction(request.getSavedObjectsClient) - ? request.getSavedObjectsClient() - : null; - if (!alertsClient || !actionsClient || !savedObjectsClient) { - return headers.response().code(404); - } - - try { - const rule = await updateRules({ - alertsClient, - actionsClient, + async handler(request: UpdateRulesRequest, headers) { + const { description, enabled, - falsePositives, + false_positives: falsePositives, from, query, language, - outputIndex, - savedId, - savedObjectsClient, - timelineId, - timelineTitle, + output_index: outputIndex, + saved_id: savedId, + timeline_id: timelineId, + timeline_title: timelineTitle, meta, filters, + rule_id: ruleId, id, - ruleId, index, interval, - maxSignals, - riskScore, + max_signals: maxSignals, + risk_score: riskScore, name, severity, tags, @@ -97,28 +57,73 @@ export const createUpdateRulesRoute: Hapi.ServerRoute = { threats, references, version, - }); - if (rule != null) { - const ruleStatuses = await savedObjectsClient.find< - IRuleSavedAttributesSavedObjectAttributes - >({ - type: ruleStatusSavedObjectType, - perPage: 1, - sortField: 'statusDate', - sortOrder: 'desc', - search: rule.id, - searchFields: ['alertId'], + } = request.payload; + + const alertsClient = isFunction(request.getAlertsClient) ? request.getAlertsClient() : null; + const actionsClient = await server.plugins.actions.getActionsClientWithRequest( + KibanaRequest.from((request as unknown) as Hapi.Request) + ); + const savedObjectsClient = isFunction(request.getSavedObjectsClient) + ? request.getSavedObjectsClient() + : null; + if (!alertsClient || !savedObjectsClient) { + return headers.response().code(404); + } + + try { + const rule = await updateRules({ + alertsClient, + actionsClient, + description, + enabled, + falsePositives, + from, + query, + language, + outputIndex, + savedId, + savedObjectsClient, + timelineId, + timelineTitle, + meta, + filters, + id, + ruleId, + index, + interval, + maxSignals, + riskScore, + name, + severity, + tags, + to, + type, + threats, + references, + version, }); - return transformOrError(rule, ruleStatuses.saved_objects[0]); - } else { - return getIdError({ id, ruleId }); + if (rule != null) { + const ruleStatuses = await savedObjectsClient.find< + IRuleSavedAttributesSavedObjectAttributes + >({ + type: ruleStatusSavedObjectType, + perPage: 1, + sortField: 'statusDate', + sortOrder: 'desc', + search: rule.id, + searchFields: ['alertId'], + }); + return transformOrError(rule, ruleStatuses.saved_objects[0]); + } else { + return getIdError({ id, ruleId }); + } + } catch (err) { + return transformError(err); } - } catch (err) { - return transformError(err); - } - }, + }, + }; }; export const updateRulesRoute = (server: ServerFacade) => { - server.route(createUpdateRulesRoute); + server.route(createUpdateRulesRoute(server)); }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.test.ts index abdd5a0c7b508..2a04c15b8cd9f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.test.ts @@ -78,7 +78,7 @@ describe('add prepackaged rules schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', }).error ).toBeTruthy(); }); @@ -91,7 +91,7 @@ describe('add prepackaged rules schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', type: 'query', }).error ).toBeTruthy(); @@ -105,7 +105,7 @@ describe('add prepackaged rules schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', }).error @@ -120,7 +120,7 @@ describe('add prepackaged rules schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', type: 'query', interval: '5m', index: ['index-1'], @@ -137,7 +137,7 @@ describe('add prepackaged rules schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', type: 'query', query: 'some query', index: ['index-1'], @@ -156,7 +156,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some query', @@ -175,7 +175,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some query', @@ -196,7 +196,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some query', @@ -215,7 +215,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', risk_score: 50, @@ -234,7 +234,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -256,7 +256,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', threats: [ @@ -291,7 +291,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -312,7 +312,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some-query', @@ -332,7 +332,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some-query', @@ -353,7 +353,7 @@ describe('add prepackaged rules schema', () => { index: ['index-1'], immutable: false, name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some-query', @@ -374,7 +374,7 @@ describe('add prepackaged rules schema', () => { index: ['index-1'], immutable: true, name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some-query', @@ -394,7 +394,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some-query', @@ -413,7 +413,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some-query', @@ -435,7 +435,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some-query', @@ -460,7 +460,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: [5], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some-query', @@ -482,7 +482,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', type: 'query', version: 1, }).value.interval @@ -499,7 +499,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', version: 1, @@ -517,7 +517,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'saved_query', version: 1, @@ -535,7 +535,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'saved_query', saved_id: 'some id', @@ -554,7 +554,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'saved_query', saved_id: 'some id', @@ -576,7 +576,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'saved_query', saved_id: 'some id', @@ -596,7 +596,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -617,7 +617,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -638,7 +638,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -659,7 +659,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -681,7 +681,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -703,7 +703,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -725,7 +725,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -750,7 +750,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -779,7 +779,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -823,7 +823,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -863,7 +863,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -898,7 +898,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -923,7 +923,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -948,7 +948,7 @@ describe('add prepackaged rules schema', () => { immutable: true, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -973,7 +973,7 @@ describe('add prepackaged rules schema', () => { immutable: 5, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -996,7 +996,7 @@ describe('add prepackaged rules schema', () => { immutable: true, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1019,7 +1019,7 @@ describe('add prepackaged rules schema', () => { immutable: true, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1042,7 +1042,7 @@ describe('add prepackaged rules schema', () => { immutable: true, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1065,7 +1065,7 @@ describe('add prepackaged rules schema', () => { immutable: true, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1088,7 +1088,7 @@ describe('add prepackaged rules schema', () => { immutable: true, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1116,7 +1116,7 @@ describe('add prepackaged rules schema', () => { immutable: true, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1140,7 +1140,7 @@ describe('add prepackaged rules schema', () => { immutable: true, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1162,7 +1162,7 @@ describe('add prepackaged rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1186,7 +1186,7 @@ describe('add prepackaged rules schema', () => { immutable: true, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1210,7 +1210,7 @@ describe('add prepackaged rules schema', () => { immutable: true, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1235,7 +1235,7 @@ describe('add prepackaged rules schema', () => { immutable: true, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1260,7 +1260,7 @@ describe('add prepackaged rules schema', () => { immutable: true, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1285,7 +1285,7 @@ describe('add prepackaged rules schema', () => { immutable: true, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1297,4 +1297,60 @@ describe('add prepackaged rules schema', () => { }).error.message ).toEqual('child "timeline_title" fails because ["timeline_title" is not allowed]'); }); + + test('The default for "from" will be "now-6m"', () => { + expect( + addPrepackagedRulesSchema.validate>({ + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + name: 'some-name', + severity: 'low', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + version: 1, + }).value.from + ).toEqual('now-6m'); + }); + + test('The default for "to" will be "now"', () => { + expect( + addPrepackagedRulesSchema.validate>({ + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + name: 'some-name', + severity: 'low', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + version: 1, + }).value.to + ).toEqual('now'); + }); + + test('You cannot set the severity to a value other than low, medium, high, or critical', () => { + expect( + addPrepackagedRulesSchema.validate>({ + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + name: 'some-name', + severity: 'junk', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + version: 1, + }).error.message + ).toEqual( + 'child "severity" fails because ["severity" must be one of [low, medium, high, critical]]' + ); + }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.ts index 9311371d630f7..d254f83243491 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.ts @@ -51,7 +51,7 @@ export const addPrepackagedRulesSchema = Joi.object({ enabled: enabled.default(false), false_positives: false_positives.default([]), filters, - from: from.required(), + from: from.default('now-6m'), rule_id: rule_id.required(), immutable: immutable.default(true).valid(true), index, @@ -71,7 +71,7 @@ export const addPrepackagedRulesSchema = Joi.object({ name: name.required(), severity: severity.required(), tags: tags.default([]), - to: to.required(), + to: to.default('now'), type: type.required(), threats: threats.default([]), references: references.default([]), diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_bulk_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_bulk_schema.test.ts index 17fb5320daa01..1eab50848b822 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_bulk_schema.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_bulk_schema.test.ts @@ -37,7 +37,7 @@ describe('create_rules_bulk_schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', type: 'query', query: 'some query', index: ['index-1'], @@ -57,7 +57,7 @@ describe('create_rules_bulk_schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', type: 'query', query: 'some query', index: ['index-1'], @@ -70,7 +70,7 @@ describe('create_rules_bulk_schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', type: 'query', query: 'some query', index: ['index-1'], @@ -79,4 +79,66 @@ describe('create_rules_bulk_schema', () => { ]).error ).toBeFalsy(); }); + + test('The default for "from" will be "now-6m"', () => { + expect( + createRulesBulkSchema.validate>([ + { + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + name: 'some-name', + severity: 'low', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + version: 1, + }, + ]).value[0].from + ).toEqual('now-6m'); + }); + + test('The default for "to" will be "now"', () => { + expect( + createRulesBulkSchema.validate>([ + { + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + name: 'some-name', + severity: 'low', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + version: 1, + }, + ]).value[0].to + ).toEqual('now'); + }); + + test('You cannot set the severity to a value other than low, medium, high, or critical', () => { + expect( + createRulesBulkSchema.validate>([ + { + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + name: 'some-name', + severity: 'junk', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + version: 1, + }, + ]).error.message + ).toEqual( + '"value" at position 0 fails because [child "severity" fails because ["severity" must be one of [low, medium, high, critical]]]' + ); + }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts index c76071047434c..f765f01300c58 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts @@ -79,7 +79,7 @@ describe('create rules schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', }).error ).toBeTruthy(); }); @@ -92,7 +92,7 @@ describe('create rules schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', type: 'query', }).error ).toBeTruthy(); @@ -106,7 +106,7 @@ describe('create rules schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', }).error @@ -121,7 +121,7 @@ describe('create rules schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', type: 'query', interval: '5m', index: ['index-1'], @@ -138,7 +138,7 @@ describe('create rules schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', type: 'query', query: 'some query', index: ['index-1'], @@ -156,7 +156,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some query', @@ -175,7 +175,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some query', @@ -195,7 +195,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some query', @@ -213,7 +213,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', risk_score: 50, @@ -232,7 +232,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', }).error @@ -250,7 +250,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -273,7 +273,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', threats: [ @@ -308,7 +308,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -329,7 +329,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some-query', @@ -351,7 +351,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some-query', @@ -375,7 +375,7 @@ describe('create rules schema', () => { to: 'now', index: [5], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some-query', @@ -398,7 +398,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', type: 'query', }).value.interval ).toEqual('5m'); @@ -415,7 +415,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', }).value.max_signals @@ -433,7 +433,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'saved_query', }).error.message @@ -451,7 +451,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'saved_query', saved_id: 'some id', @@ -470,7 +470,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'saved_query', saved_id: 'some id', @@ -492,7 +492,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'saved_query', saved_id: 'some id', @@ -512,7 +512,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -533,7 +533,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -554,7 +554,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -575,7 +575,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -597,7 +597,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -619,7 +619,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -641,7 +641,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -664,7 +664,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -693,7 +693,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -737,7 +737,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -777,7 +777,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -812,7 +812,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -837,7 +837,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -864,7 +864,7 @@ describe('create rules schema', () => { immutable: 5, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -886,7 +886,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -908,7 +908,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -930,7 +930,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -952,7 +952,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -974,7 +974,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -999,7 +999,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1022,7 +1022,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1044,7 +1044,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1067,7 +1067,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1089,7 +1089,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1112,7 +1112,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1135,7 +1135,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1158,7 +1158,7 @@ describe('create rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1168,4 +1168,60 @@ describe('create rules schema', () => { }).error.message ).toEqual('child "timeline_title" fails because ["timeline_title" is not allowed]'); }); + + test('The default for "from" will be "now-6m"', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + name: 'some-name', + severity: 'low', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + version: 1, + }).value.from + ).toEqual('now-6m'); + }); + + test('The default for "to" will be "now"', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + name: 'some-name', + severity: 'low', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + version: 1, + }).value.to + ).toEqual('now'); + }); + + test('You cannot set the severity to a value other than low, medium, high, or critical', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + name: 'some-name', + severity: 'junk', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + version: 1, + }).error.message + ).toEqual( + 'child "severity" fails because ["severity" must be one of [low, medium, high, critical]]' + ); + }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.ts index 5d9972453fb1a..06dbb0cbb48f3 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.ts @@ -43,7 +43,7 @@ export const createRulesSchema = Joi.object({ enabled: enabled.default(true), false_positives: false_positives.default([]), filters, - from: from.required(), + from: from.default('now-6m'), rule_id, index, interval: interval.default('5m'), @@ -63,7 +63,7 @@ export const createRulesSchema = Joi.object({ name: name.required(), severity: severity.required(), tags: tags.default([]), - to: to.required(), + to: to.default('now'), type: type.required(), threats: threats.default([]), references: references.default([]), diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.test.ts index 20f418c57b5db..b19a91d18c3ff 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.test.ts @@ -84,7 +84,7 @@ describe('import rules schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', }).error ).toBeTruthy(); }); @@ -97,7 +97,7 @@ describe('import rules schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', type: 'query', }).error ).toBeTruthy(); @@ -111,7 +111,7 @@ describe('import rules schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', }).error @@ -126,7 +126,7 @@ describe('import rules schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', type: 'query', interval: '5m', index: ['index-1'], @@ -143,7 +143,7 @@ describe('import rules schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', type: 'query', query: 'some query', index: ['index-1'], @@ -161,7 +161,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some query', @@ -180,7 +180,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some query', @@ -200,7 +200,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some query', @@ -218,7 +218,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', risk_score: 50, @@ -237,7 +237,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', }).error @@ -255,7 +255,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -278,7 +278,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', threats: [ @@ -313,7 +313,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -334,7 +334,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some-query', @@ -356,7 +356,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some-query', @@ -381,7 +381,7 @@ describe('import rules schema', () => { to: 'now', index: [5], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some-query', @@ -403,7 +403,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', type: 'query', }).value.interval ).toEqual('5m'); @@ -420,7 +420,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', }).value.max_signals @@ -438,7 +438,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'saved_query', }).error.message @@ -456,7 +456,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'saved_query', saved_id: 'some id', @@ -475,7 +475,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'saved_query', saved_id: 'some id', @@ -497,7 +497,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'saved_query', saved_id: 'some id', @@ -517,7 +517,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -538,7 +538,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -559,7 +559,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -580,7 +580,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -602,7 +602,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -624,7 +624,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -646,7 +646,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -670,7 +670,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -700,7 +700,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -744,7 +744,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -784,7 +784,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -819,7 +819,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -844,7 +844,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -869,7 +869,7 @@ describe('import rules schema', () => { immutable: true, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -894,7 +894,7 @@ describe('import rules schema', () => { immutable: 5, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -917,7 +917,7 @@ describe('import rules schema', () => { immutable: true, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -940,7 +940,7 @@ describe('import rules schema', () => { immutable: true, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -963,7 +963,7 @@ describe('import rules schema', () => { immutable: true, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -986,7 +986,7 @@ describe('import rules schema', () => { immutable: true, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1009,7 +1009,7 @@ describe('import rules schema', () => { immutable: true, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1035,7 +1035,7 @@ describe('import rules schema', () => { immutable: true, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1059,7 +1059,7 @@ describe('import rules schema', () => { immutable: true, index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1081,7 +1081,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1104,7 +1104,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1126,7 +1126,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1149,7 +1149,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1174,7 +1174,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1197,7 +1197,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1219,7 +1219,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1240,7 +1240,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1265,7 +1265,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1290,7 +1290,7 @@ describe('import rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -1344,4 +1344,59 @@ describe('import rules schema', () => { expect(importRulesPayloadSchema.validate({ file: {} }).error).toBeFalsy(); }); }); + + test('The default for "from" will be "now-6m"', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + name: 'some-name', + severity: 'low', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + version: 1, + }).value.from + ).toEqual('now-6m'); + }); + + test('The default for "to" will be "now"', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + name: 'some-name', + severity: 'low', + type: 'query', + references: ['index-1'], + language: 'kuery', + max_signals: 1, + version: 1, + }).value.to + ).toEqual('now'); + }); + + test('You cannot set the severity to a value other than low, medium, high, or critical', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + name: 'some-name', + severity: 'junk', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + version: 1, + }).error.message + ).toEqual( + 'child "severity" fails because ["severity" must be one of [low, medium, high, critical]]' + ); + }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.ts index df825c442fff6..8516585a2c055 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.ts @@ -59,7 +59,7 @@ export const importRulesSchema = Joi.object({ enabled: enabled.default(true), false_positives: false_positives.default([]), filters, - from: from.required(), + from: from.default('now-6m'), rule_id: rule_id.required(), immutable: immutable.default(false), index, @@ -80,7 +80,7 @@ export const importRulesSchema = Joi.object({ name: name.required(), severity: severity.required(), tags: tags.default([]), - to: to.required(), + to: to.default('now'), type: type.required(), threats: threats.default([]), references: references.default([]), diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_signals_index_schema.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_signals_index_schema.ts index 0a6fceb44f845..26a32d2e4980b 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_signals_index_schema.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_signals_index_schema.ts @@ -9,7 +9,7 @@ import Joi from 'joi'; export const querySignalsSchema = Joi.object({ query: Joi.object(), aggs: Joi.object(), - size: Joi.number(), + size: Joi.number().integer(), track_total_hits: Joi.boolean(), _source: Joi.array().items(Joi.string()), }).min(1); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/schemas.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/schemas.ts index ecca661d2b856..a027fcb96b599 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/schemas.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/schemas.ts @@ -37,12 +37,15 @@ export const timeline_title = Joi.string().when('timeline_id', { otherwise: Joi.forbidden(), }); export const meta = Joi.object(); -export const max_signals = Joi.number().greater(0); +export const max_signals = Joi.number() + .integer() + .greater(0); export const name = Joi.string(); export const risk_score = Joi.number() + .integer() .greater(-1) .less(101); -export const severity = Joi.string(); +export const severity = Joi.string().valid('low', 'medium', 'high', 'critical'); export const status = Joi.string().valid('open', 'closed'); export const to = Joi.string(); export const type = Joi.string().valid('query', 'saved_query'); @@ -51,9 +54,11 @@ export const references = Joi.array() .items(Joi.string()) .single(); export const per_page = Joi.number() + .integer() .min(0) .default(20); export const page = Joi.number() + .integer() .min(1) .default(1); export const signal_ids = Joi.array().items(Joi.string()); @@ -97,4 +102,6 @@ export const updated_at = Joi.string() .strict(); export const created_by = Joi.string(); export const updated_by = Joi.string(); -export const version = Joi.number().min(1); +export const version = Joi.number() + .integer() + .min(1); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.test.ts index 823ebb90a3b3c..44b3b5b927be2 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.test.ts @@ -147,7 +147,7 @@ describe('update rules schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', }).error ).toBeFalsy(); }); @@ -160,7 +160,7 @@ describe('update rules schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', }).error ).toBeFalsy(); }); @@ -173,7 +173,7 @@ describe('update rules schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', type: 'query', }).error ).toBeFalsy(); @@ -187,7 +187,7 @@ describe('update rules schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', type: 'query', }).error ).toBeFalsy(); @@ -201,7 +201,7 @@ describe('update rules schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', }).error @@ -216,7 +216,7 @@ describe('update rules schema', () => { from: 'now-5m', to: 'now', name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', }).error @@ -232,7 +232,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', }).error @@ -248,7 +248,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', }).error @@ -264,7 +264,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some query', @@ -281,7 +281,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some query', @@ -298,7 +298,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some query', @@ -316,7 +316,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some query', @@ -334,7 +334,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', }).error @@ -350,7 +350,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', }).error @@ -366,7 +366,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -385,7 +385,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some-query', @@ -403,7 +403,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', type: 'query', }).value.interval ).toEqual(undefined); @@ -418,7 +418,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', }).value.max_signals @@ -436,7 +436,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some-query', @@ -459,7 +459,7 @@ describe('update rules schema', () => { to: 'now', index: [5], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', query: 'some-query', @@ -479,7 +479,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'saved_query', }).error @@ -495,7 +495,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'saved_query', saved_id: 'some id', @@ -512,7 +512,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'saved_query', saved_id: 'some id', @@ -530,7 +530,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -549,7 +549,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -568,7 +568,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -587,7 +587,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -607,7 +607,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -627,7 +627,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -679,7 +679,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -699,7 +699,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -737,7 +737,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -778,7 +778,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -820,7 +820,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -858,7 +858,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', references: ['index-1'], @@ -890,7 +890,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'saved_query', saved_id: 'some id', @@ -909,7 +909,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'saved_query', saved_id: 'some id', @@ -927,7 +927,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'saved_query', saved_id: 'some id', @@ -946,7 +946,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'saved_query', saved_id: 'some id', @@ -965,7 +965,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'saved_query', saved_id: 'some id', @@ -984,7 +984,7 @@ describe('update rules schema', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'saved_query', saved_id: 'some id', @@ -992,4 +992,24 @@ describe('update rules schema', () => { }).error.message ).toEqual('child "timeline_title" fails because ["timeline_title" is not allowed]'); }); + + test('You cannot set the severity to a value other than low, medium, high, or critical', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + risk_score: 50, + description: 'some description', + name: 'some-name', + severity: 'junk', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + version: 1, + }).error.message + ).toEqual( + 'child "severity" fails because ["severity" must be one of [low, medium, high, critical]]' + ); + }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/tags/read_tags_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/tags/read_tags_route.ts index beef8b4199c15..c598e22ff596c 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/tags/read_tags_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/tags/read_tags_route.ts @@ -24,9 +24,7 @@ export const createReadTagsRoute: Hapi.ServerRoute = { }, async handler(request: RequestFacade, headers) { const alertsClient = isFunction(request.getAlertsClient) ? request.getAlertsClient() : null; - const actionsClient = isFunction(request.getActionsClient) ? request.getActionsClient() : null; - - if (!alertsClient || !actionsClient) { + if (!alertsClient) { return headers.response().code(404); } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules.ts index d2f76907d7aa3..1d3801d80de11 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules.ts @@ -8,7 +8,7 @@ import { APP_ID, SIGNALS_ID } from '../../../../common/constants'; import { RuleParams } from './types'; import { addTags } from './add_tags'; -export const createRules = async ({ +export const createRules = ({ alertsClient, actionsClient, // TODO: Use this actionsClient once we have actions such as email, etc... description, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.test.ts index fce3c90ef18e7..48b7195c3b0bc 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.test.ts @@ -21,7 +21,7 @@ export const getOutputSample = (): Partial => ({ to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', }); @@ -55,7 +55,7 @@ describe('create_rules_stream_from_ndjson', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', enabled: true, @@ -78,7 +78,7 @@ describe('create_rules_stream_from_ndjson', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', enabled: true, @@ -120,7 +120,7 @@ describe('create_rules_stream_from_ndjson', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', enabled: true, @@ -143,7 +143,7 @@ describe('create_rules_stream_from_ndjson', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', enabled: true, @@ -184,7 +184,7 @@ describe('create_rules_stream_from_ndjson', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', enabled: true, @@ -207,7 +207,7 @@ describe('create_rules_stream_from_ndjson', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', enabled: true, @@ -248,7 +248,7 @@ describe('create_rules_stream_from_ndjson', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', enabled: true, @@ -272,7 +272,7 @@ describe('create_rules_stream_from_ndjson', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', enabled: true, @@ -312,7 +312,7 @@ describe('create_rules_stream_from_ndjson', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', enabled: true, @@ -338,7 +338,7 @@ describe('create_rules_stream_from_ndjson', () => { to: 'now', index: ['index-1'], name: 'some-name', - severity: 'severity', + severity: 'low', interval: '5m', type: 'query', enabled: true, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/install_prepacked_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/install_prepacked_rules.ts index 98c04f95387f4..555063854dc60 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/install_prepacked_rules.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/install_prepacked_rules.ts @@ -4,18 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ActionsClient } from '../../../../../actions'; +import { ActionsClient } from '../../../../../../../plugins/actions/server'; import { AlertsClient } from '../../../../../alerting'; +import { Alert } from '../../../../../alerting/server/types'; import { createRules } from './create_rules'; import { PrepackagedRules } from '../types'; -export const installPrepackagedRules = async ( +export const installPrepackagedRules = ( alertsClient: AlertsClient, actionsClient: ActionsClient, rules: PrepackagedRules[], outputIndex: string -): Promise => { - await rules.forEach(async rule => { +): Array> => + rules.reduce>>((acc, rule) => { const { description, enabled, @@ -43,37 +44,39 @@ export const installPrepackagedRules = async ( references, version, } = rule; - createRules({ - alertsClient, - actionsClient, - description, - enabled, - falsePositives, - from, - immutable, - query, - language, - outputIndex, - savedId, - timelineId, - timelineTitle, - meta, - filters, - ruleId, - index, - interval, - maxSignals, - riskScore, - name, - severity, - tags, - to, - type, - threats, - references, - version, - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - }); - }); -}; + return [ + ...acc, + createRules({ + alertsClient, + actionsClient, + description, + enabled, + falsePositives, + from, + immutable, + query, + language, + outputIndex, + savedId, + timelineId, + timelineTitle, + meta, + filters, + ruleId, + index, + interval, + maxSignals, + riskScore, + name, + severity, + tags, + to, + type, + threats, + references, + version, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }), + ]; + }, []); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_adding_the_hidden_file_attribute_with_via_attribexe.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_adding_the_hidden_file_attribute_with_via_attribexe.json index a65a386cb827e..e5280d19f8e4a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_adding_the_hidden_file_attribute_with_via_attribexe.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_adding_the_hidden_file_attribute_with_via_attribexe.json @@ -1,16 +1,51 @@ { - "description": "EQL - Adding the Hidden File Attribute with via attrib.exe", + "description": "Adversaries can add the 'hidden' attribute to files to hide them from the user in an attempt to evade detection", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Adding the Hidden File Attribute with via attrib.exe", + "name": "Adding the Hidden File Attribute with via attrib.exe", "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"attrib.exe\" and process.args:\"+h\"", - "risk_score": 50, + "risk_score": 25, "rule_id": "4630d948-40d4-4cef-ac69-4002e29bc3db", "severity": "low", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "techniques": [ + { + "id": "T1158", + "name": "Hidden Files and Directories", + "reference": "https://attack.mitre.org/techniques/T1158/" + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "techniques": [ + { + "id": "T1158", + "name": "Hidden Files and Directories", + "reference": "https://attack.mitre.org/techniques/T1158/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_adobe_hijack_persistence.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_adobe_hijack_persistence.json index e5d797f3fc131..0fac9b17160e2 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_adobe_hijack_persistence.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_adobe_hijack_persistence.json @@ -1,16 +1,36 @@ { - "description": "EQL - Adobe Hijack Persistence", + "description": "Detects writing executable files that will be automatically launched by Adobe on launch.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Adobe Hijack Persistence", + "name": "Adobe Hijack Persistence", "query": "file.path:(\"C:\\Program Files (x86)\\Adobe\\Acrobat Reader DC\\Reader\\AcroCEF\\RdrCEF.exe\" or \"C:\\Program Files\\Adobe\\Acrobat Reader DC\\Reader\\AcroCEF\\RdrCEF.exe\") and event.action:\"File created (rule: FileCreate)\" and not process.name:msiexeec.exe", - "risk_score": 50, + "risk_score": 25, "rule_id": "2bf78aa2-9c56-48de-b139-f169bf99cf86", "severity": "low", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "techniques": [ + { + "id": "T1044", + "name": "File System Permissions Weakness", + "reference": "https://attack.mitre.org/techniques/T1044/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_audio_capture_via_powershell.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_audio_capture_via_powershell.json index ef65bd3ecef35..0506d03348913 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_audio_capture_via_powershell.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_audio_capture_via_powershell.json @@ -1,16 +1,36 @@ { - "description": "EQL - Audio Capture via PowerShell", + "description": "An adversary can leverage a computer's peripheral devices or applications to capture audio recordings for the purpose of listening into sensitive conversations to gather information.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Audio Capture via PowerShell", + "name": "Audio Capture via PowerShell", "query": "event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"powershell.exe\" and process.args:\"WindowsAudioDevice-Powershell-Cmdlet\"", - "risk_score": 50, + "risk_score": 25, "rule_id": "b27b9f47-0a20-4807-8377-7f899b4fbada", "severity": "low", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Collection", + "reference": "https://attack.mitre.org/tactics/TA0009/" + }, + "techniques": [ + { + "id": "T1123", + "name": "Audio Capture", + "reference": "https://attack.mitre.org/techniques/T1123/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_audio_capture_via_soundrecorder.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_audio_capture_via_soundrecorder.json index 89eec55d827d6..392eeb3980c9f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_audio_capture_via_soundrecorder.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_audio_capture_via_soundrecorder.json @@ -1,16 +1,36 @@ { - "description": "EQL - Audio Capture via SoundRecorder", + "description": "An adversary can leverage a computer's peripheral devices or applications to capture audio recordings for the purpose of listening into sensitive conversations to gather information.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Audio Capture via SoundRecorder", + "name": "Audio Capture via SoundRecorder", "query": "event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"SoundRecorder.exe\" and process.args:\"/FILE\"", - "risk_score": 50, + "risk_score": 25, "rule_id": "f8e06892-ed10-4452-892e-2c5a38d552f1", "severity": "low", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Collection", + "reference": "https://attack.mitre.org/tactics/TA0009/" + }, + "techniques": [ + { + "id": "T1123", + "name": "Audio Capture", + "reference": "https://attack.mitre.org/techniques/T1123/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_bypass_uac_event_viewer.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_bypass_uac_event_viewer.json index 80f83991516a6..ecbc9a2dd46c4 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_bypass_uac_event_viewer.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_bypass_uac_event_viewer.json @@ -1,16 +1,36 @@ { - "description": "EQL -Bypass UAC Event Viewer", + "description": "Identifies User Account Control (UAC) bypass via eventvwr. Attackers bypass UAC to stealthily execute code with elevated permissions.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL -Bypass UAC Event Viewer", + "name": "Bypass UAC via Event Viewer", "query": "process.parent.name:eventvwr.exe and event.action:\"Process Create (rule: ProcessCreate)\" and not process.executable:(\"C:\\Windows\\System32\\mmc.exe\" or \"C:\\Windows\\SysWOW64\\mmc.exe\")", - "risk_score": 50, + "risk_score": 25, "rule_id": "59547add-a400-4baa-aa0c-66c72efdb77f", "severity": "low", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0004", + "name": "Privilege Escalation", + "reference": "https://attack.mitre.org/tactics/TA0004/" + }, + "techniques": [ + { + "id": "T1088", + "name": "Bypass User Account Control", + "reference": "https://attack.mitre.org/techniques/T1088/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_bypass_uac_via_cmstp.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_bypass_uac_via_cmstp.json index 0850632c95899..2518fda68ee0f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_bypass_uac_via_cmstp.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_bypass_uac_via_cmstp.json @@ -1,16 +1,36 @@ { - "description": "EQL - Bypass UAC via CMSTP", + "description": "Identifies User Account Control (UAC) bypass via cmstp. Attackers bypass UAC to stealthily execute code with elevated permissions.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Bypass UAC via CMSTP", + "name": "Bypass UAC via CMSTP", "query": "event.action:\"Process Create (rule: ProcessCreate)\" and process.parent.name:\"cmstp.exe\" and process.parent.args:(\"/s\" and \"/au\")", - "risk_score": 50, + "risk_score": 25, "rule_id": "2f7403da-1a4c-46bb-8ecc-c1a596e10cd0", "severity": "low", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0004", + "name": "Privilege Escalation", + "reference": "https://attack.mitre.org/tactics/TA0004/" + }, + "techniques": [ + { + "id": "T1088", + "name": "Bypass User Account Control", + "reference": "https://attack.mitre.org/techniques/T1088/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_bypass_uac_via_sdclt.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_bypass_uac_via_sdclt.json index 85ba24fd572c3..c419dc080ec3c 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_bypass_uac_via_sdclt.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_bypass_uac_via_sdclt.json @@ -1,16 +1,36 @@ { - "description": "EQL -Bypass UAC Via sdclt", + "description": "Identifies User Account Control (UAC) bypass via cmstp. Attackers bypass UAC to stealthily execute code with elevated permissions.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL -Bypass UAC Via sdclt", + "name": "Bypass UAC via SDCLT", "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"sdclt.exe\" and process.args:\"/kickoffelev\" and not process.executable:(\"C:\\Windows\\System32\\sdclt.exe\" or \"C:\\Windows\\System32\\control.exe\" or \"C:\\Windows\\SysWOW64\\sdclt.exe\" or \"C:\\Windows\\SysWOW64\\control.exe\")", - "risk_score": 50, + "risk_score": 25, "rule_id": "f68d83a1-24cb-4b8d-825b-e8af400b9670", "severity": "low", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0004", + "name": "Privilege Escalation", + "reference": "https://attack.mitre.org/tactics/TA0004/" + }, + "techniques": [ + { + "id": "T1088", + "name": "Bypass User Account Control", + "reference": "https://attack.mitre.org/techniques/T1088/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_clearing_windows_event_logs.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_clearing_windows_event_logs.json index 28f45b94049e7..bcf9b02a0210f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_clearing_windows_event_logs.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_clearing_windows_event_logs.json @@ -1,16 +1,36 @@ { - "description": "EQL - Clearing Windows Event Logs", + "description": "Identifies attempts to clear Windows event log stores. This is often done by attackers in an attempt evade detection or destroy forensic evidence on a system.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Clearing Windows Event Logs", + "name": "Clearing Windows Event Logs", "query": "event.action:\"Process Create (rule: ProcessCreate)\" and (process.name:\"wevtutil.exe\" and process.args:\"cl\") or (process.name:\"powershell.exe\" and process.args:\"Clear-EventLog\")", - "risk_score": 50, + "risk_score": 25, "rule_id": "d331bbe2-6db4-4941-80a5-8270db72eb61", "severity": "low", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "techniques": [ + { + "id": "T1070", + "name": "Indicator Removal on Host", + "reference": "https://attack.mitre.org/techniques/T1070/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_delete_volume_usn_journal_with_fsutil.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_delete_volume_usn_journal_with_fsutil.json index 6f00427656af6..5a9ba60597534 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_delete_volume_usn_journal_with_fsutil.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_delete_volume_usn_journal_with_fsutil.json @@ -1,16 +1,36 @@ { - "description": "EQL - Delete Volume USN Journal with fsutil", + "description": "Identifies use of the fsutil command to delete the volume USNJRNL. This technique is used by attackers to eliminate evidence of files created during post-exploitation activities.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Delete Volume USN Journal with fsutil", + "name": "Delete Volume USN Journal with fsutil", "query": "event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"fsutil.exe\" and process.args:(\"usn\" and \"deletejournal\")", - "risk_score": 50, + "risk_score": 25, "rule_id": "f675872f-6d85-40a3-b502-c0d2ef101e92", "severity": "low", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "techniques": [ + { + "id": "T1107", + "name": "File Deletion", + "reference": "https://attack.mitre.org/techniques/T1107/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_deleting_backup_catalogs_with_wbadmin.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_deleting_backup_catalogs_with_wbadmin.json index 8f5b21b74ee6a..240678d45238c 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_deleting_backup_catalogs_with_wbadmin.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_deleting_backup_catalogs_with_wbadmin.json @@ -1,16 +1,36 @@ { - "description": "EQL - Deleting Backup Catalogs with wbadmin", + "description": "Identifies use of the wbadmin command to delete the backup catalog. Ransomware and other malware may do this to prevent system recovery.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Deleting Backup Catalogs with wbadmin", + "name": "Deleting Backup Catalogs with wbadmin", "query": "event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"wbadmin.exe\" and process.args:(\"delete\" and \"catalog\")", - "risk_score": 50, + "risk_score": 25, "rule_id": "581add16-df76-42bb-af8e-c979bfb39a59", "severity": "low", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "techniques": [ + { + "id": "T1107", + "name": "File Deletion", + "reference": "https://attack.mitre.org/techniques/T1107/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_direct_outbound_smb_connection.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_direct_outbound_smb_connection.json index 56f0b2efec620..9e5ccc73dc05e 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_direct_outbound_smb_connection.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_direct_outbound_smb_connection.json @@ -1,16 +1,36 @@ { - "description": "EQL - Direct Outbound SMB Connection", + "description": "Identifies unexpected processes making network connections over port 445. Windows File Sharing is typically implemented over Server Message Block (SMB), which communicates between hosts using port 445. When legitimate, these network connections are established by the kernel. Processes making 445/tcp connections may be port scanners, exploits, or suspicious user-level processes moving laterally.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Direct Outbound SMB Connection", + "name": "Direct Outbound SMB Connection", "query": " event.action:\"Network connection detected (rule: NetworkConnect)\" and destination.port:445 and not process.pid:4 and not destination.ip:(\"127.0.0.1\" or \"::1\")", "risk_score": 50, "rule_id": "c82c7d8f-fb9e-4874-a4bd-fd9e3f9becf1", - "severity": "low", + "severity": "medium", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0008", + "name": "Lateral Movement", + "reference": "https://attack.mitre.org/tactics/TA0008/" + }, + "techniques": [ + { + "id": "T1210", + "name": "Exploitation of Remote Services", + "reference": "https://attack.mitre.org/techniques/T1210/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_disable_windows_firewall_rules_with_netsh.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_disable_windows_firewall_rules_with_netsh.json index 4d1e32eb29897..40a8298561dbd 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_disable_windows_firewall_rules_with_netsh.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_disable_windows_firewall_rules_with_netsh.json @@ -1,16 +1,36 @@ { - "description": "EQL - Disable Windows Firewall Rules with Netsh", + "description": "Identifies use of the netsh command to disable or weaken the local firewall. Attackers will use this command line tool to disable the firewall during troubleshooting or to enable network mobility.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Disable Windows Firewall Rules with Netsh", + "name": "Disable Windows Firewall Rules with Netsh", "query": "event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"netsh.exe\" and process.args:(\"firewall\" and \"set\" and \"disable\") or process.args:(\"advfirewall\" and \"state\" and \"off\")", "risk_score": 50, "rule_id": "4b438734-3793-4fda-bd42-ceeada0be8f9", - "severity": "low", + "severity": "medium", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "techniques": [ + { + "id": "T1089", + "name": "Disabling Security Tools", + "reference": "https://attack.mitre.org/techniques/T1089/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_dll_search_order_hijack.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_dll_search_order_hijack.json index b9bf463a8e5f2..0ee8674e3304b 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_dll_search_order_hijack.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_dll_search_order_hijack.json @@ -1,16 +1,51 @@ { - "description": "EQL - DLL Search Order Hijack", + "description": "Detects writing DLL files to known locations associated with Windows files vulnerable to DLL search order hijacking.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - DLL Search Order Hijack", + "name": "DLL Search Order Hijack", "query": " event.action:\"File created (rule: FileCreate)\" and not winlog.user.identifier:(\"S-1-5-18\" or \"S-1-5-19\" or \"S-1-5-20\") and file.path:(\"C\\Windows\\ehome\\cryptbase.dll\" or \"C\\Windows\\System32\\Sysprep\\cryptbase.dll\" or \"C\\Windows\\System32\\Sysprep\\cryptsp.dll\" or \"C\\Windows\\System32\\Sysprep\\rpcrtremote.dll\" or \"C\\Windows\\System32\\Sysprep\\uxtheme.dll\" or \"C\\Windows\\System32\\Sysprep\\dwmapi.dll\" or \"C\\Windows\\System32\\Sysprep\\shcore.dll\" or \"C\\Windows\\System32\\Sysprep\\oleacc.dll\" or \"C\\Windows\\System32\\ntwdblib.dll\") ", "risk_score": 50, "rule_id": "73fbc44c-c3cd-48a8-a473-f4eb2065c716", - "severity": "low", + "severity": "medium", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0004", + "name": "Privilege Escalation", + "reference": "https://attack.mitre.org/tactics/TA0004/" + }, + "techniques": [ + { + "id": "T1088", + "name": "Bypass User Account Control", + "reference": "https://attack.mitre.org/techniques/T1088/" + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "techniques": [ + { + "id": "T1088", + "name": "Bypass User Account Control", + "reference": "https://attack.mitre.org/techniques/T1088/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_encoding_or_decoding_files_via_certutil.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_encoding_or_decoding_files_via_certutil.json index 6b4ffd9cb21e3..3e912e076adec 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_encoding_or_decoding_files_via_certutil.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_encoding_or_decoding_files_via_certutil.json @@ -1,16 +1,36 @@ { - "description": "EQL - Encoding or Decoding Files via CertUtil", + "description": "Identifies the use of certutil.exe to encode or decode data. CertUtil is a native Windows component which is part of Certificate Services. CertUtil is often abused by attackers to encode or decode base64 data for stealthier command and control or exfiltration.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Encoding or Decoding Files via CertUtil", + "name": "Encoding or Decoding Files via CertUtil", "query": "event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"certutil.exe\" and process.args:(\"-encode\" or \"/encode\" or \"-decode\" or \"/decode\")", "risk_score": 50, "rule_id": "fd70c98a-c410-42dc-a2e3-761c71848acf", - "severity": "low", + "severity": "medium", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "techniques": [ + { + "id": "T1140", + "name": "Deobfuscate/Decode Files or Information", + "reference": "https://attack.mitre.org/techniques/T1140/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_scheduled_task_commands.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_scheduled_task_commands.json index f09983d26aff5..304fea1cfbb76 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_scheduled_task_commands.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_scheduled_task_commands.json @@ -1,16 +1,36 @@ { - "description": "EQL - Local Scheduled Task Commands", + "description": "A scheduled task can be used by an adversary to establish persistence, move laterally, and/or escalate privileges.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Local Scheduled Task Commands", + "name": "Local Scheduled Task Commands", "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.name:schtasks.exe and process.args:(\"/create\" or \"-create\" or \"/S\" or \"-s\" or \"/run\" or \"-run\" or \"/change\" or \"-change\")", - "risk_score": 50, + "risk_score": 25, "rule_id": "afcce5ad-65de-4ed2-8516-5e093d3ac99a", "severity": "low", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "techniques": [ + { + "id": "T1053", + "name": "Scheduled Task", + "reference": "https://attack.mitre.org/techniques/T1053/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_service_commands.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_service_commands.json index d33a3dbe6de81..7454b0fd452c6 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_service_commands.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_service_commands.json @@ -1,16 +1,36 @@ { - "description": "EQL - Local Service Commands", + "description": "Identifies use of sc.exe to create, modify, or start services on remote hosts. This could be indicative of adversary lateral movement but will be noisy if commonly done by admins.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Local Service Commands", + "name": "Local Service Commands", "query": "event.action:\"Process Create (rule: ProcessCreate)\" and process.name:sc.exe and process.args:(\"create\" or \"config\" or \"failure\" or \"start\")", - "risk_score": 50, + "risk_score": 25, "rule_id": "e8571d5f-bea1-46c2-9f56-998de2d3ed95", "severity": "low", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "techniques": [ + { + "id": "T1021", + "name": "Remote Services", + "reference": "https://attack.mitre.org/techniques/T1021/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_modification_of_boot_configuration.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_modification_of_boot_configuration.json index 39dc254752073..d4ac29a78c77d 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_modification_of_boot_configuration.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_modification_of_boot_configuration.json @@ -1,16 +1,36 @@ { - "description": "EQL - Modification of Boot Configuration", + "description": "Identifies use of the bcdedit command to delete boot configuration data. This tactic is sometimes used as by malware or an attacker as a destructive technique.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Modification of Boot Configuration", + "name": "Modification of Boot Configuration", "query": "event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"bcdedit.exe\" and process.args:\"set\" and process.args:( (\"bootstatuspolicy\" and \"ignoreallfailures\") or (\"recoveryenabled\" and \"no\") ) ", - "risk_score": 50, + "risk_score": 75, "rule_id": "b9ab2f7f-f719-4417-9599-e0252fffe2d8", - "severity": "low", + "severity": "high", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "techniques": [ + { + "id": "T1107", + "name": "File Deletion", + "reference": "https://attack.mitre.org/techniques/T1107/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_msbuild_making_network_connections.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_msbuild_making_network_connections.json index dd8fab2d8ad70..61049bba92cce 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_msbuild_making_network_connections.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_msbuild_making_network_connections.json @@ -1,16 +1,36 @@ { - "description": "EQL - MsBuild Making Network Connections", + "description": "Identifies MsBuild.exe making outbound network connections. This may indicate adversarial activity as MsBuild is often leveraged by adversaries to execute code and evade detection.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - MsBuild Making Network Connections", + "name": "MsBuild Making Network Connections", "query": " event.action:\"Network connection detected (rule: NetworkConnect)\" and process.name:msbuild.exe and not destination.ip:(\"127.0.0.1\" or \"::1\")", "risk_score": 50, "rule_id": "0e79980b-4250-4a50-a509-69294c14e84b", - "severity": "low", + "severity": "medium", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "techniques": [ + { + "id": "T1127", + "name": "Trusted Developer Utilities", + "reference": "https://attack.mitre.org/techniques/T1127/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_mshta_making_network_connections.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_mshta_making_network_connections.json index 8037cc9bcba7f..f2ed8449b9aaf 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_mshta_making_network_connections.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_mshta_making_network_connections.json @@ -1,16 +1,39 @@ { - "description": "EQL - Mshta Making Network Connections", + "description": "Identifies Mshta.exe making outbound network connections. This may indicate adversarial activity as Mshta is often leveraged by adversaries to execute malicious scripts and evade detection.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Mshta Making Network Connections", + "name": "Mshta Making Network Connections", "query": "event.action:\"Network connection detected (rule: NetworkConnect)\" and process.name:\"mshta.exe\" and not process.name:\"mshta.exe\"", + "references": [ + "https://www.fireeye.com/blog/threat-research/2017/05/cyber-espionage-apt32.html" + ], "risk_score": 50, "rule_id": "a4ec1382-4557-452b-89ba-e413b22ed4b8", - "severity": "low", + "severity": "medium", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "techniques": [ + { + "id": "T1170", + "name": "Mshta", + "reference": "https://attack.mitre.org/techniques/T1170/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_msxsl_making_network_connections.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_msxsl_making_network_connections.json index 5dd6d5d3042c6..c86b7515173dc 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_msxsl_making_network_connections.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_msxsl_making_network_connections.json @@ -1,16 +1,36 @@ { - "description": "EQL - MsXsl Making Network Connections", + "description": "Identifies MsXsl.exe making outbound network connections. This may indicate adversarial activity as MsXsl is often leveraged by adversaries to execute malicious scripts and evade detection.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - MsXsl Making Network Connections", + "name": "MsXsl Making Network Connections", "query": "process.name:msxsl.exe and event.action:\"Network connection detected (rule: NetworkConnect)\" and not destination.ip:10.0.0.0/8 and not destination.ip:172.16.0.0/12 and not destination.ip:192.168.0.0/16", "risk_score": 50, "rule_id": "d7351b03-135d-43ba-8b36-cc9b07854525", - "severity": "low", + "severity": "medium", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "techniques": [ + { + "id": "T1220", + "name": "XSL Script Processing", + "reference": "https://attack.mitre.org/techniques/T1220/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_psexec_lateral_movement_command.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_psexec_lateral_movement_command.json index d83f7796cd4d1..e35843bc9b413 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_psexec_lateral_movement_command.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_psexec_lateral_movement_command.json @@ -1,16 +1,19 @@ { - "description": "EQL - PsExec Lateral Movement Command", + "description": "Identifies use of the SysInternals tool PsExec to execute commands on a remote host. This is an indication of lateral movement and may detect adversaries.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - PsExec Lateral Movement Command", + "name": "PsExec Lateral Movement Command", "query": "process.name:psexec.exe and event.action:\"Network connection detected (rule: NetworkConnect)\" ", "risk_score": 50, "rule_id": "55d551c6-333b-4665-ab7e-5d14a59715ce", "severity": "low", + "tags": [ + "EIA" + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_suspicious_ms_office_child_process.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_suspicious_ms_office_child_process.json index 5746541dd879c..9d3b0361c9d29 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_suspicious_ms_office_child_process.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_suspicious_ms_office_child_process.json @@ -1,16 +1,36 @@ { - "description": "EQL - Suspicious MS Office Child Process", + "description": "Identifies suspicious child processes of frequently targeted Microsoft Office applications (Word, PowerPoint, Excel). These child processes are often launched during exploitation of Office applications or from documents with malicious macros.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Suspicious MS Office Child Process", + "name": "Suspicious MS Office Child Process", "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.parent.name:(\"winword.exe\" or \"excel.exe\" or \"powerpnt.exe\" or \"eqnedt32.exe\" or \"fltldr.exe\" or \"mspub.exe\" or \"msaccess.exe\") and process.name:(\"arp.exe\" or \"dsquery.exe\" or \"dsget.exe\" or \"gpresult.exe\" or \"hostname.exe\" or \"ipconfig.exe\" or \"nbtstat.exe\" or \"net.exe\" or \"net1.exe\" or \"netsh.exe\" or \"netstat.exe\" or \"nltest.exe\" or \"ping.exe\" or \"qprocess.exe\" or \"quser.exe\" or \"qwinsta.exe\" or \"reg.exe\" or \"sc.exe\" or \"systeminfo.exe\" or \"tasklist.exe\" or \"tracert.exe\" or \"whoami.exe\" or \"bginfo.exe\" or \"cdb.exe\" or \"cmstp.exe\" or \"csi.exe\" or \"dnx.exe\" or \"fsi.exe\" or \"ieexec.exe\" or \"iexpress.exe\" or \"installutil.exe\" or \"Microsoft.Workflow.Compiler.exe\" or \"msbuild.exe\" or \"mshta.exe\" or \"msxsl.exe\" or \"odbcconf.exe\" or \"rcsi.exe\" or \"regsvr32.exe\" or \"xwizard.exe\" or \"atbroker.exe\" or \"forfiles.exe\" or \"schtasks.exe\" or \"regasm.exe\" or \"regsvcs.exe\" or \"cmd.exe\" or \"cscript.exe\" or \"powershell.exe\" or \"pwsh.exe\" or \"wmic.exe\" or \"wscript.exe\" or \"bitsadmin.exe\" or \"certutil.exe\" or \"ftp.exe\") ", - "risk_score": 50, + "risk_score": 25, "rule_id": "a624863f-a70d-417f-a7d2-7a404638d47f", "severity": "low", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "techniques": [ + { + "id": "T1193", + "name": "Spearphishing Attachment", + "reference": "https://attack.mitre.org/techniques/T1193/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_suspicious_ms_outlook_child_process.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_suspicious_ms_outlook_child_process.json index 88ce75eeef34e..f445cb187c428 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_suspicious_ms_outlook_child_process.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_suspicious_ms_outlook_child_process.json @@ -1,16 +1,36 @@ { - "description": "EQL - Suspicious MS Outlook Child Process", + "description": "Identifies suspicious child processes of Microsoft Outlook. These child processes are often associated with spear phishing activity.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Suspicious MS Outlook Child Process", + "name": "Suspicious MS Outlook Child Process", "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.parent.name:\"outlook.exe\" and process.name:(\"arp.exe\" or \"dsquery.exe\" or \"dsget.exe\" or \"gpresult.exe\" or \"hostname.exe\" or \"ipconfig.exe\" or \"nbtstat.exe\" or \"net.exe\" or \"net1.exe\" or \"netsh.exe\" or \"netstat.exe\" or \"nltest.exe\" or \"ping.exe\" or \"qprocess.exe\" or \"quser.exe\" or \"qwinsta.exe\" or \"reg.exe\" or \"sc.exe\" or \"systeminfo.exe\" or \"tasklist.exe\" or \"tracert.exe\" or \"whoami.exe\" or \"bginfo.exe\" or \"cdb.exe\" or \"cmstp.exe\" or \"csi.exe\" or \"dnx.exe\" or \"fsi.exe\" or \"ieexec.exe\" or \"iexpress.exe\" or \"installutil.exe\" or \"Microsoft.Workflow.Compiler.exe\" or \"msbuild.exe\" or \"mshta.exe\" or \"msxsl.exe\" or \"odbcconf.exe\" or \"rcsi.exe\" or \"regsvr32.exe\" or \"xwizard.exe\" or \"atbroker.exe\" or \"forfiles.exe\" or \"schtasks.exe\" or \"regasm.exe\" or \"regsvcs.exe\" or \"cmd.exe\" or \"cscript.exe\" or \"powershell.exe\" or \"pwsh.exe\" or \"wmic.exe\" or \"wscript.exe\" or \"bitsadmin.exe\" or \"certutil.exe\" or \"ftp.exe\") ", - "risk_score": 50, + "risk_score": 25, "rule_id": "32f4675e-6c49-4ace-80f9-97c9259dca2e", "severity": "low", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "techniques": [ + { + "id": "T1193", + "name": "Spearphishing Attachment", + "reference": "https://attack.mitre.org/techniques/T1193/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_suspicious_pdf_reader_child_process.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_suspicious_pdf_reader_child_process.json index 2e3a654127b53..0b44ebd922c02 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_suspicious_pdf_reader_child_process.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_suspicious_pdf_reader_child_process.json @@ -1,5 +1,5 @@ { - "description": "EQL - Suspicious PDF Reader Child Process", + "description": "Identifies suspicious child processes of PDF reader applications. These child processes are often launched via exploitation of PDF applications or social engineering.", "enabled": false, "filters": [], "from": "now-6m", @@ -8,9 +8,29 @@ "language": "kuery", "name": "EQL - Suspicious PDF Reader Child Process", "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.parent.name:(\"acrord32.exe\" or \"rdrcef.exe\" or \"foxitphantomPDF.exe\" or \"foxitreader.exe\") and process.name:(\"arp.exe\" or \"dsquery.exe\" or \"dsget.exe\" or \"gpresult.exe\" or \"hostname.exe\" or \"ipconfig.exe\" or \"nbtstat.exe\" or \"net.exe\" or \"net1.exe\" or \"netsh.exe\" or \"netstat.exe\" or \"nltest.exe\" or \"ping.exe\" or \"qprocess.exe\" or \"quser.exe\" or \"qwinsta.exe\" or \"reg.exe\" or \"sc.exe\" or \"systeminfo.exe\" or \"tasklist.exe\" or \"tracert.exe\" or \"whoami.exe\" or \"bginfo.exe\" or \"cdb.exe\" or \"cmstp.exe\" or \"csi.exe\" or \"dnx.exe\" or \"fsi.exe\" or \"ieexec.exe\" or \"iexpress.exe\" or \"installutil.exe\" or \"Microsoft.Workflow.Compiler.exe\" or \"msbuild.exe\" or \"mshta.exe\" or \"msxsl.exe\" or \"odbcconf.exe\" or \"rcsi.exe\" or \"regsvr32.exe\" or \"xwizard.exe\" or \"atbroker.exe\" or \"forfiles.exe\" or \"schtasks.exe\" or \"regasm.exe\" or \"regsvcs.exe\" or \"cmd.exe\" or \"cscript.exe\" or \"powershell.exe\" or \"pwsh.exe\" or \"wmic.exe\" or \"wscript.exe\" or \"bitsadmin.exe\" or \"certutil.exe\" or \"ftp.exe\") ", - "risk_score": 50, + "risk_score": 75, "rule_id": "afcac7b1-d092-43ff-a136-aa7accbda38f", - "severity": "low", + "severity": "high", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "techniques": [ + { + "id": "T1193", + "name": "Spearphishing Attachment", + "reference": "https://attack.mitre.org/techniques/T1193/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_system_shells_via_services.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_system_shells_via_services.json index 20080719f3ed3..687f5c0db2dab 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_system_shells_via_services.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_system_shells_via_services.json @@ -1,16 +1,36 @@ { - "description": "EQL - System Shells via Services", + "description": "Windows services typically run as SYSTEM and can be used as a privilege escalation opportunity. Malware or penetration testers may run a shell as a service to gain SYSTEM permissions.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - System Shells via Services", + "name": "System Shells via Services", "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.parent.name:\"services.exe\" and process.name:(\"cmd.exe\" or \"powershell.exe\")", "risk_score": 50, "rule_id": "0022d47d-39c7-4f69-a232-4fe9dc7a3acd", - "severity": "low", + "severity": "medium", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "techniques": [ + { + "id": "T1050", + "name": "New Service", + "reference": "https://attack.mitre.org/techniques/T1050/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_network_connection_via_rundll32.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_network_connection_via_rundll32.json index 79f8f8e1f606c..4893f80e8b56c 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_network_connection_via_rundll32.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_network_connection_via_rundll32.json @@ -1,16 +1,36 @@ { - "description": "EQL - Unusual Network Connection via RunDLL32", + "description": "Identifies unusual instances of Rundll32.exe making outbound network connections. This may indicate adversarial activity and may identify malicious DLLs.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Unusual Network Connection via RunDLL32", + "name": "Unusual Network Connection via RunDLL32", "query": "process.name:rundll32.exe and event.action:\"Network connection detected (rule: NetworkConnect)\" and not destination.ip:10.0.0.0/8 and not destination.ip:172.16.0.0/12 and not destination.ip:192.168.0.0/16", - "risk_score": 50, + "risk_score": 25, "rule_id": "52aaab7b-b51c-441a-89ce-4387b3aea886", "severity": "low", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "techniques": [ + { + "id": "T1085", + "name": "Rundll32", + "reference": "https://attack.mitre.org/techniques/T1085/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_parentchild_relationship.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_parentchild_relationship.json index 28cce6ed89f8b..29e3c998ebe02 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_parentchild_relationship.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_parentchild_relationship.json @@ -1,16 +1,36 @@ { - "description": "EQL - Unusual Parent-Child Relationship ", + "description": "Identifies Windows programs run from unexpected parent processes. This could indicate masquerading or other strange activity on a system.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Unusual Parent-Child Relationship ", + "name": "Unusual Parent-Child Relationship ", "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.parent.executable:* and ( (process.name:\"smss.exe\" and not process.parent.name:(\"System\" or \"smss.exe\")) or (process.name:\"csrss.exe\" and not process.parent.name:(\"smss.exe\" or \"svchost.exe\")) or (process.name:\"wininit.exe\" and not process.parent.name:\"smss.exe\") or (process.name:\"winlogon.exe\" and not process.parent.name:\"smss.exe\") or (process.name:\"lsass.exe\" and not process.parent.name:\"wininit.exe\") or (process.name:\"LogonUI.exe\" and not process.parent.name:(\"winlogon.exe\" or \"wininit.exe\")) or (process.name:\"services.exe\" and not process.parent.name:\"wininit.exe\") or (process.name:\"svchost.exe\" and not process.parent.name:(\"services.exe\" or \"MsMpEng.exe\")) or (process.name:\"spoolsv.exe\" and not process.parent.name:\"services.exe\") or (process.name:\"taskhost.exe\" and not process.parent.name:(\"services.exe\" or \"svchost.exe\")) or (process.name:\"taskhostw.exe\" and not process.parent.name:(\"services.exe\" or \"svchost.exe\")) or (process.name:\"userinit.exe\" and not process.parent.name:(\"dwm.exe\" or \"winlogon.exe\")) )", "risk_score": 50, "rule_id": "35df0dd8-092d-4a83-88c1-5151a804f31b", - "severity": "low", + "severity": "medium", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0004", + "name": "Privilege Escalation", + "reference": "https://attack.mitre.org/tactics/TA0004/" + }, + "techniques": [ + { + "id": "T1093", + "name": "Process Hollowing", + "reference": "https://attack.mitre.org/techniques/T1093/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_process_network_connection.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_process_network_connection.json index 8b84ec4ff34f4..ce34e4a352c88 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_process_network_connection.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_process_network_connection.json @@ -1,16 +1,36 @@ { - "description": "EQL - Unusual Process Network Connection", + "description": "Identifies network activity from unexpected system applications. This may indicate adversarial activity as these applications are often leveraged by adversaries to execute code and evade detection.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Unusual Process Network Connection", + "name": "Unusual Process Network Connection", "query": " event.action:\"Network connection detected (rule: NetworkConnect)\" and process.name:(bginfo.exe or cdb.exe or cmstp.exe or csi.exe or dnx.exe or fsi.exe or ieexec.exe or iexpress.exe or Microsoft.Workflow.Compiler.exe or odbcconf.exe or rcsi.exe or xwizard.exe)", - "risk_score": 50, + "risk_score": 25, "rule_id": "610949a1-312f-4e04-bb55-3a79b8c95267", "severity": "low", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "techniques": [ + { + "id": "T1127", + "name": "Trusted Developer Utilities", + "reference": "https://attack.mitre.org/techniques/T1127/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_user_account_creation.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_user_account_creation.json index 3af9d9c427751..5b94babaf8add 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_user_account_creation.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_user_account_creation.json @@ -1,16 +1,36 @@ { - "description": "EQL - User Account Creation", + "description": "Identifies attempts to create new local users. This is sometimes done by attackers to increase access to a system or domain.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - User Account Creation", + "name": "User Account Creation", "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.name:(\"net.exe\" or \"net1.exe\") and not process.parent.name:\"net.exe\" and process.args:(\"user\" and (\"/add\" or \"/ad\")) ", "risk_score": 50, "rule_id": "1aa9181a-492b-4c01-8b16-fa0735786b2b", "severity": "low", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "techniques": [ + { + "id": "T1136", + "name": "Create Account", + "reference": "https://attack.mitre.org/techniques/T1136/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_user_added_to_administrator_group.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_user_added_to_administrator_group.json index 226f2dd1e3934..f0b770985c716 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_user_added_to_administrator_group.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_user_added_to_administrator_group.json @@ -1,16 +1,36 @@ { - "description": "EQL - User Added to Administrator Group", + "description": "Identifies attempts to add a user to an administrative group with the \"net.exe\" command. This is sometimes done by attackers to increase access of a compromised account or create new account.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - User Added to Administrator Group", + "name": "User Added to Administrator Group", "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.name:(\"net.exe\" or \"net1.exe\") and not process.parent.name:\"net.exe\" and process.args:(\"group\" and \"admin\" and \"/add\") ", "risk_score": 50, "rule_id": "4426de6f-6103-44aa-a77e-49d672836c27", - "severity": "low", + "severity": "medium", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/tactics/TA0003/" + }, + "techniques": [ + { + "id": "T1098", + "name": "Account Manipulation", + "reference": "https://attack.mitre.org/techniques/T1098/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_vssadmin.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_vssadmin.json index 2b27bce457aff..8f23d398a48a7 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_vssadmin.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_vssadmin.json @@ -1,16 +1,36 @@ { - "description": "EQL - Volume Shadow Copy Deletion via VssAdmin", + "description": "Identifies use of vssadmin.exe for shadow copy deletion on endpoints. This commonly occurs in tandem with ransomware or other destructive attacks.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Volume Shadow Copy Deletion via VssAdmin", + "name": "Volume Shadow Copy Deletion via VssAdmin", "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"vssadmin.exe\" and process.args:(\"delete\" and \"shadows\") ", - "risk_score": 50, + "risk_score": 75, "rule_id": "b5ea4bfe-a1b2-421f-9d47-22a75a6f2921", - "severity": "low", + "severity": "high", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "techniques": [ + { + "id": "T1490", + "name": "Inhibit System Recovery", + "reference": "https://attack.mitre.org/techniques/T1490/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_wmic.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_wmic.json index 4ec4530cc967f..fc18b2c0f5d70 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_wmic.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_wmic.json @@ -1,16 +1,36 @@ { - "description": "EQL - Volume Shadow Copy Deletion via WMIC", + "description": "Identifies use of wmic for shadow copy deletion on endpoints. This commonly occurs in tandem with ransomware or other destructive attacks.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Volume Shadow Copy Deletion via WMIC", + "name": "Volume Shadow Copy Deletion via WMIC", "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"wmic.exe\" and process.args:(\"shadowcopy\" and \"delete\")", - "risk_score": 50, + "risk_score": 75, "rule_id": "dc9c1f74-dac3-48e3-b47f-eb79db358f57", - "severity": "low", + "severity": "high", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "techniques": [ + { + "id": "T1107", + "name": "File Deletion", + "reference": "https://attack.mitre.org/techniques/T1107/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_windows_script_executing_powershell.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_windows_script_executing_powershell.json index da96eb39e4d96..ff3d660704eeb 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_windows_script_executing_powershell.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_windows_script_executing_powershell.json @@ -1,16 +1,36 @@ { - "description": "EQL - Windows Script Executing PowerShell", + "description": "Identifies a PowerShell process launched by either CScript or WScript. Observing Windows scripting processes executing a PowerShell script, may be indicative of malicious activity.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - Windows Script Executing PowerShell", + "name": "Windows Script Executing PowerShell", "query": "event.action:\"Process Create (rule: ProcessCreate)\" and process.parent.name:(\"wscript.exe\" or \"cscript.exe\") and process.name:\"powershell.exe\"", "risk_score": 50, "rule_id": "f545ff26-3c94-4fd0-bd33-3c7f95a3a0fc", "severity": "low", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "techniques": [ + { + "id": "T1193", + "name": "Spearphishing Attachment", + "reference": "https://attack.mitre.org/techniques/T1193/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_wmic_command_lateral_movement.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_wmic_command_lateral_movement.json index 3f1c22e2a55d9..5a9bda9e8ddfa 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_wmic_command_lateral_movement.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_wmic_command_lateral_movement.json @@ -1,16 +1,36 @@ { - "description": "EQL - WMIC Command Lateral Movement", + "description": "Identifies use of wmic.exe to run commands on remote hosts. This could be indicative of adversary lateral movement but will be noisy if commonly done by admins.", "enabled": false, "filters": [], "from": "now-6m", "immutable": true, "interval": "5m", "language": "kuery", - "name": "EQL - WMIC Command Lateral Movement", + "name": "WMIC Command Lateral Movement", "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"wmic.exe\" and process.args:(\"/node\" or \"-node\") and process.args:(\"call\" or \"set\")", - "risk_score": 50, + "risk_score": 25, "rule_id": "9616587f-6396-42d0-bd31-ef8dbd806210", "severity": "low", + "tags": [ + "EIA" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0008", + "name": "Lateral Movement", + "reference": "https://attack.mitre.org/tactics/TA0008/" + }, + "techniques": [ + { + "id": "T1047", + "name": "Windows Management Instrumentation", + "reference": "https://attack.mitre.org/techniques/T1047/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/index.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/index.ts index 6ef81addd846e..a70ff7d13f0ee 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/index.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/index.ts @@ -88,228 +88,68 @@ import rule78 from './network_vnc_virtual_network_computing_from_the_internet.js import rule79 from './network_vnc_virtual_network_computing_to_the_internet.json'; import rule80 from './null_user_agent.json'; import rule81 from './sqlmap_user_agent.json'; -import rule82 from './suricata_base64_encoded_invokecommand_powershell_execution.json'; -import rule83 from './suricata_base64_encoded_newobject_powershell_execution.json'; -import rule84 from './suricata_base64_encoded_startprocess_powershell_execution.json'; -import rule85 from './suricata_category_a_suspicious_string_was_detected.json'; -import rule86 from './suricata_category_attempted_administrator_privilege_gain.json'; -import rule87 from './suricata_category_attempted_denial_of_service.json'; -import rule88 from './suricata_category_attempted_information_leak.json'; -import rule89 from './suricata_category_attempted_login_with_suspicious_username.json'; -import rule90 from './suricata_category_attempted_user_privilege_gain.json'; -import rule91 from './suricata_category_client_using_unusual_port.json'; -import rule92 from './suricata_category_crypto_currency_mining_activity.json'; -import rule93 from './suricata_category_decode_of_an_rpc_query.json'; -import rule94 from './suricata_category_default_username_and_password_login_attempt.json'; -import rule95 from './suricata_category_denial_of_service.json'; -import rule96 from './suricata_category_denial_of_service_attack.json'; -import rule97 from './suricata_category_executable_code_was_detected.json'; -import rule98 from './suricata_category_exploit_kit_activity.json'; -import rule99 from './suricata_category_external_ip_address_retrieval.json'; -import rule100 from './suricata_category_generic_icmp_event.json'; -import rule101 from './suricata_category_generic_protocol_command_decode.json'; -import rule102 from './suricata_category_information_leak.json'; -import rule103 from './suricata_category_large_scale_information_leak.json'; -import rule104 from './suricata_category_malware_command_and_control_activity.json'; -import rule105 from './suricata_category_misc_activity.json'; -import rule106 from './suricata_category_misc_attack.json'; -import rule107 from './suricata_category_network_scan_detected.json'; -import rule108 from './suricata_category_network_trojan_detected.json'; -import rule109 from './suricata_category_nonstandard_protocol_or_event.json'; -import rule110 from './suricata_category_not_suspicious_traffic.json'; -import rule111 from './suricata_category_observed_c2_domain.json'; -import rule112 from './suricata_category_possible_social_engineering_attempted.json'; -import rule113 from './suricata_category_possibly_unwanted_program.json'; -import rule114 from './suricata_category_potential_corporate_privacy_violation.json'; -import rule115 from './suricata_category_potentially_bad_traffic.json'; -import rule116 from './suricata_category_potentially_vulnerable_web_application_access.json'; -import rule117 from './suricata_category_successful_administrator_privilege_gain.json'; -import rule118 from './suricata_category_successful_credential_theft.json'; -import rule119 from './suricata_category_successful_user_privilege_gain.json'; -import rule120 from './suricata_category_suspicious_filename_detected.json'; -import rule121 from './suricata_category_system_call_detected.json'; -import rule122 from './suricata_category_targeted_malicious_activity.json'; -import rule123 from './suricata_category_tcp_connection_detected.json'; -import rule124 from './suricata_category_unknown_traffic.json'; -import rule125 from './suricata_category_unsuccessful_user_privilege_gain.json'; -import rule126 from './suricata_category_web_application_attack.json'; -import rule127 from './suricata_cobaltstrike_artifact_in_an_dns_request.json'; -import rule128 from './suricata_commonly_abused_dns_domain_detected.json'; -import rule129 from './suricata_directory_reversal_characters_in_an_http_request.json'; -import rule130 from './suricata_directory_traversal_characters_in_an_http_request.json'; -import rule131 from './suricata_directory_traversal_characters_in_http_response.json'; -import rule132 from './suricata_directory_traversal_in_downloaded_zip_file.json'; -import rule133 from './suricata_dns_traffic_on_unusual_tcp_port.json'; -import rule134 from './suricata_dns_traffic_on_unusual_udp_port.json'; -import rule135 from './suricata_double_encoded_characters_in_a_uri.json'; -import rule136 from './suricata_double_encoded_characters_in_an_http_post.json'; -import rule137 from './suricata_double_encoded_characters_in_http_request.json'; -import rule138 from './suricata_eval_php_function_in_an_http_request.json'; -import rule139 from './suricata_exploit_cve_2018_1000861.json'; -import rule140 from './suricata_exploit_cve_2019_0227.json'; -import rule141 from './suricata_exploit_cve_2019_0232.json'; -import rule142 from './suricata_exploit_cve_2019_0604.json'; -import rule143 from './suricata_exploit_cve_2019_0708.json'; -import rule144 from './suricata_exploit_cve_2019_0752.json'; -import rule145 from './suricata_exploit_cve_2019_1003000.json'; -import rule146 from './suricata_exploit_cve_2019_10149.json'; -import rule147 from './suricata_exploit_cve_2019_11043.json'; -import rule148 from './suricata_exploit_cve_2019_11510.json'; -import rule149 from './suricata_exploit_cve_2019_11580.json'; -import rule150 from './suricata_exploit_cve_2019_11581.json'; -import rule151 from './suricata_exploit_cve_2019_13450.json'; -import rule152 from './suricata_exploit_cve_2019_13505.json'; -import rule153 from './suricata_exploit_cve_2019_15107.json'; -import rule154 from './suricata_exploit_cve_2019_15846.json'; -import rule155 from './suricata_exploit_cve_2019_16072.json'; -import rule156 from './suricata_exploit_cve_2019_1652.json'; -import rule157 from './suricata_exploit_cve_2019_16662.json'; -import rule158 from './suricata_exploit_cve_2019_16759.json'; -import rule159 from './suricata_exploit_cve_2019_16928.json'; -import rule160 from './suricata_exploit_cve_2019_17270.json'; -import rule161 from './suricata_exploit_cve_2019_1821.json'; -import rule162 from './suricata_exploit_cve_2019_19781.json'; -import rule163 from './suricata_exploit_cve_2019_2618.json'; -import rule164 from './suricata_exploit_cve_2019_2725.json'; -import rule165 from './suricata_exploit_cve_2019_3396.json'; -import rule166 from './suricata_exploit_cve_2019_3929.json'; -import rule167 from './suricata_exploit_cve_2019_5533.json'; -import rule168 from './suricata_exploit_cve_2019_6340.json'; -import rule169 from './suricata_exploit_cve_2019_7256.json'; -import rule170 from './suricata_exploit_cve_2019_9978.json'; -import rule171 from './suricata_ftp_traffic_on_unusual_port_internet_destination.json'; -import rule172 from './suricata_http_traffic_on_unusual_port_internet_destination.json'; -import rule173 from './suricata_imap_traffic_on_unusual_port_internet_destination.json'; -import rule174 from './suricata_lazagne_artifact_in_an_http_post.json'; -import rule175 from './suricata_mimikatz_artifacts_in_an_http_post.json'; -import rule176 from './suricata_mimikatz_string_detected_in_http_response.json'; -import rule177 from './suricata_nondns_traffic_on_tcp_port_53.json'; -import rule178 from './suricata_nondns_traffic_on_udp_port_53.json'; -import rule179 from './suricata_nonftp_traffic_on_port_21.json'; -import rule180 from './suricata_nonhttp_traffic_on_tcp_port_80.json'; -import rule181 from './suricata_nonimap_traffic_on_port_1443_imap.json'; -import rule182 from './suricata_nonsmb_traffic_on_tcp_port_139_smb.json'; -import rule183 from './suricata_nonssh_traffic_on_port_22.json'; -import rule184 from './suricata_nontls_on_tls_port.json'; -import rule185 from './suricata_possible_cobalt_strike_malleable_c2_null_response.json'; -import rule186 from './suricata_possible_sql_injection_sql_commands_in_http_transactions.json'; -import rule187 from './suricata_rpc_traffic_on_http_ports.json'; -import rule188 from './suricata_serialized_php_detected.json'; -import rule189 from './suricata_shell_exec_php_function_in_an_http_post.json'; -import rule190 from './suricata_ssh_traffic_not_on_port_22_internet_destination.json'; -import rule191 from './suricata_tls_traffic_on_unusual_port_internet_destination.json'; -import rule192 from './suricata_windows_executable_served_by_jpeg_web_content.json'; -import rule193 from './windows_background_intelligent_transfer_service_bits_connecting_to_the_internet.json'; -import rule194 from './windows_burp_ce_activity.json'; -import rule195 from './windows_certutil_connecting_to_the_internet.json'; -import rule196 from './windows_command_prompt_connecting_to_the_internet.json'; -import rule197 from './windows_command_shell_started_by_internet_explorer.json'; -import rule198 from './windows_command_shell_started_by_powershell.json'; -import rule199 from './windows_command_shell_started_by_svchost.json'; -import rule200 from './windows_credential_dumping_commands.json'; -import rule201 from './windows_credential_dumping_via_imageload.json'; -import rule202 from './windows_credential_dumping_via_registry_save.json'; -import rule203 from './windows_data_compression_using_powershell.json'; -import rule204 from './windows_defense_evasion_decoding_using_certutil.json'; -import rule205 from './windows_defense_evasion_or_persistence_via_hidden_files.json'; -import rule206 from './windows_defense_evasion_via_filter_manager.json'; -import rule207 from './windows_defense_evasion_via_windows_event_log_tools.json'; -import rule208 from './windows_execution_via_compiled_html_file.json'; -import rule209 from './windows_execution_via_connection_manager.json'; -import rule210 from './windows_execution_via_microsoft_html_application_hta.json'; -import rule211 from './windows_execution_via_net_com_assemblies.json'; -import rule212 from './windows_execution_via_regsvr32.json'; -import rule213 from './windows_execution_via_trusted_developer_utilities.json'; -import rule214 from './windows_html_help_executable_program_connecting_to_the_internet.json'; -import rule215 from './windows_image_load_from_a_temp_directory.json'; -import rule216 from './windows_indirect_command_execution.json'; -import rule217 from './windows_iodine_activity.json'; -import rule218 from './windows_management_instrumentation_wmi_execution.json'; -import rule219 from './windows_microsoft_html_application_hta_connecting_to_the_internet.json'; -import rule220 from './windows_mimikatz_activity.json'; -import rule221 from './windows_misc_lolbin_connecting_to_the_internet.json'; -import rule222 from './windows_net_command_activity_by_the_system_account.json'; -import rule223 from './windows_net_user_command_activity.json'; -import rule224 from './windows_netcat_activity.json'; -import rule225 from './windows_netcat_network_activity.json'; -import rule226 from './windows_network_anomalous_windows_process_using_https_ports.json'; -import rule227 from './windows_nmap_activity.json'; -import rule228 from './windows_nmap_scan_activity.json'; -import rule229 from './windows_payload_obfuscation_via_certutil.json'; -import rule230 from './windows_persistence_or_priv_escalation_via_hooking.json'; -import rule231 from './windows_persistence_via_application_shimming.json'; -import rule232 from './windows_persistence_via_bits_jobs.json'; -import rule233 from './windows_persistence_via_modification_of_existing_service.json'; -import rule234 from './windows_persistence_via_netshell_helper_dll.json'; -import rule235 from './windows_powershell_connecting_to_the_internet.json'; -import rule236 from './windows_priv_escalation_via_accessibility_features.json'; -import rule237 from './windows_process_discovery_via_tasklist_command.json'; -import rule238 from './windows_process_execution_via_wmi.json'; -import rule239 from './windows_process_started_by_acrobat_reader_possible_payload.json'; -import rule240 from './windows_process_started_by_ms_office_program_possible_payload.json'; -import rule241 from './windows_process_started_by_the_java_runtime.json'; -import rule242 from './windows_psexec_activity.json'; -import rule243 from './windows_register_server_program_connecting_to_the_internet.json'; -import rule244 from './windows_registry_query_local.json'; -import rule245 from './windows_registry_query_network.json'; -import rule246 from './windows_remote_management_execution.json'; -import rule247 from './windows_scheduled_task_activity.json'; -import rule248 from './windows_script_interpreter_connecting_to_the_internet.json'; -import rule249 from './windows_signed_binary_proxy_execution.json'; -import rule250 from './windows_signed_binary_proxy_execution_download.json'; -import rule251 from './windows_suspicious_process_started_by_a_script.json'; -import rule252 from './windows_whoami_command_activity.json'; -import rule253 from './windows_windump_activity.json'; -import rule254 from './windows_wireshark_activity.json'; -import rule255 from './zeek_notice_capturelosstoo_much_loss.json'; -import rule256 from './zeek_notice_conncontent_gap.json'; -import rule257 from './zeek_notice_connretransmission_inconsistency.json'; -import rule258 from './zeek_notice_dnsexternal_name.json'; -import rule259 from './zeek_notice_ftpbruteforcing.json'; -import rule260 from './zeek_notice_ftpsite_exec_success.json'; -import rule261 from './zeek_notice_heartbleedssl_heartbeat_attack.json'; -import rule262 from './zeek_notice_heartbleedssl_heartbeat_attack_success.json'; -import rule263 from './zeek_notice_heartbleedssl_heartbeat_many_requests.json'; -import rule264 from './zeek_notice_heartbleedssl_heartbeat_odd_length.json'; -import rule265 from './zeek_notice_httpsql_injection_attacker.json'; -import rule266 from './zeek_notice_httpsql_injection_victim.json'; -import rule267 from './zeek_notice_intelnotice.json'; -import rule268 from './zeek_notice_noticetally.json'; -import rule269 from './zeek_notice_packetfiltercannot_bpf_shunt_conn.json'; -import rule270 from './zeek_notice_packetfiltercompile_failure.json'; -import rule271 from './zeek_notice_packetfilterdropped_packets.json'; -import rule272 from './zeek_notice_packetfilterinstall_failure.json'; -import rule273 from './zeek_notice_packetfilterno_more_conn_shunts_available.json'; -import rule274 from './zeek_notice_packetfiltertoo_long_to_compile_filter.json'; -import rule275 from './zeek_notice_protocoldetectorprotocol_found.json'; -import rule276 from './zeek_notice_protocoldetectorserver_found.json'; -import rule277 from './zeek_notice_scanaddress_scan.json'; -import rule278 from './zeek_notice_scanport_scan.json'; -import rule279 from './zeek_notice_signaturescount_signature.json'; -import rule280 from './zeek_notice_signaturesmultiple_sig_responders.json'; -import rule281 from './zeek_notice_signaturesmultiple_signatures.json'; -import rule282 from './zeek_notice_signaturessensitive_signature.json'; -import rule283 from './zeek_notice_signaturessignature_summary.json'; -import rule284 from './zeek_notice_smtpblocklist_blocked_host.json'; -import rule285 from './zeek_notice_smtpblocklist_error_message.json'; -import rule286 from './zeek_notice_smtpsuspicious_origination.json'; -import rule287 from './zeek_notice_softwaresoftware_version_change.json'; -import rule288 from './zeek_notice_softwarevulnerable_version.json'; -import rule289 from './zeek_notice_sshinteresting_hostname_login.json'; -import rule290 from './zeek_notice_sshlogin_by_password_guesser.json'; -import rule291 from './zeek_notice_sshpassword_guessing.json'; -import rule292 from './zeek_notice_sshwatched_country_login.json'; -import rule293 from './zeek_notice_sslcertificate_expired.json'; -import rule294 from './zeek_notice_sslcertificate_expires_soon.json'; -import rule295 from './zeek_notice_sslcertificate_not_valid_yet.json'; -import rule296 from './zeek_notice_sslinvalid_ocsp_response.json'; -import rule297 from './zeek_notice_sslinvalid_server_cert.json'; -import rule298 from './zeek_notice_sslold_version.json'; -import rule299 from './zeek_notice_sslweak_cipher.json'; -import rule300 from './zeek_notice_sslweak_key.json'; -import rule301 from './zeek_notice_teamcymrumalwarehashregistrymatch.json'; -import rule302 from './zeek_notice_traceroutedetected.json'; -import rule303 from './zeek_notice_weirdactivity.json'; +import rule82 from './windows_background_intelligent_transfer_service_bits_connecting_to_the_internet.json'; +import rule83 from './windows_burp_ce_activity.json'; +import rule84 from './windows_certutil_connecting_to_the_internet.json'; +import rule85 from './windows_command_prompt_connecting_to_the_internet.json'; +import rule86 from './windows_command_shell_started_by_internet_explorer.json'; +import rule87 from './windows_command_shell_started_by_powershell.json'; +import rule88 from './windows_command_shell_started_by_svchost.json'; +import rule89 from './windows_credential_dumping_commands.json'; +import rule90 from './windows_credential_dumping_via_imageload.json'; +import rule91 from './windows_credential_dumping_via_registry_save.json'; +import rule92 from './windows_data_compression_using_powershell.json'; +import rule93 from './windows_defense_evasion_decoding_using_certutil.json'; +import rule94 from './windows_defense_evasion_or_persistence_via_hidden_files.json'; +import rule95 from './windows_defense_evasion_via_filter_manager.json'; +import rule96 from './windows_defense_evasion_via_windows_event_log_tools.json'; +import rule97 from './windows_execution_via_compiled_html_file.json'; +import rule98 from './windows_execution_via_connection_manager.json'; +import rule99 from './windows_execution_via_microsoft_html_application_hta.json'; +import rule100 from './windows_execution_via_net_com_assemblies.json'; +import rule101 from './windows_execution_via_regsvr32.json'; +import rule102 from './windows_execution_via_trusted_developer_utilities.json'; +import rule103 from './windows_html_help_executable_program_connecting_to_the_internet.json'; +import rule104 from './windows_image_load_from_a_temp_directory.json'; +import rule105 from './windows_indirect_command_execution.json'; +import rule106 from './windows_iodine_activity.json'; +import rule107 from './windows_management_instrumentation_wmi_execution.json'; +import rule108 from './windows_microsoft_html_application_hta_connecting_to_the_internet.json'; +import rule109 from './windows_mimikatz_activity.json'; +import rule110 from './windows_misc_lolbin_connecting_to_the_internet.json'; +import rule111 from './windows_net_command_activity_by_the_system_account.json'; +import rule112 from './windows_net_user_command_activity.json'; +import rule113 from './windows_netcat_activity.json'; +import rule114 from './windows_netcat_network_activity.json'; +import rule115 from './windows_network_anomalous_windows_process_using_https_ports.json'; +import rule116 from './windows_nmap_activity.json'; +import rule117 from './windows_nmap_scan_activity.json'; +import rule118 from './windows_payload_obfuscation_via_certutil.json'; +import rule119 from './windows_persistence_or_priv_escalation_via_hooking.json'; +import rule120 from './windows_persistence_via_application_shimming.json'; +import rule121 from './windows_persistence_via_bits_jobs.json'; +import rule122 from './windows_persistence_via_modification_of_existing_service.json'; +import rule123 from './windows_persistence_via_netshell_helper_dll.json'; +import rule124 from './windows_powershell_connecting_to_the_internet.json'; +import rule125 from './windows_priv_escalation_via_accessibility_features.json'; +import rule126 from './windows_process_discovery_via_tasklist_command.json'; +import rule127 from './windows_process_execution_via_wmi.json'; +import rule128 from './windows_process_started_by_acrobat_reader_possible_payload.json'; +import rule129 from './windows_process_started_by_ms_office_program_possible_payload.json'; +import rule130 from './windows_process_started_by_the_java_runtime.json'; +import rule131 from './windows_psexec_activity.json'; +import rule132 from './windows_register_server_program_connecting_to_the_internet.json'; +import rule133 from './windows_registry_query_local.json'; +import rule134 from './windows_registry_query_network.json'; +import rule135 from './windows_remote_management_execution.json'; +import rule136 from './windows_scheduled_task_activity.json'; +import rule137 from './windows_script_interpreter_connecting_to_the_internet.json'; +import rule138 from './windows_signed_binary_proxy_execution.json'; +import rule139 from './windows_signed_binary_proxy_execution_download.json'; +import rule140 from './windows_suspicious_process_started_by_a_script.json'; +import rule141 from './windows_whoami_command_activity.json'; +import rule142 from './windows_windump_activity.json'; +import rule143 from './windows_wireshark_activity.json'; export const rawRules = [ rule1, rule2, @@ -454,164 +294,4 @@ export const rawRules = [ rule141, rule142, rule143, - rule144, - rule145, - rule146, - rule147, - rule148, - rule149, - rule150, - rule151, - rule152, - rule153, - rule154, - rule155, - rule156, - rule157, - rule158, - rule159, - rule160, - rule161, - rule162, - rule163, - rule164, - rule165, - rule166, - rule167, - rule168, - rule169, - rule170, - rule171, - rule172, - rule173, - rule174, - rule175, - rule176, - rule177, - rule178, - rule179, - rule180, - rule181, - rule182, - rule183, - rule184, - rule185, - rule186, - rule187, - rule188, - rule189, - rule190, - rule191, - rule192, - rule193, - rule194, - rule195, - rule196, - rule197, - rule198, - rule199, - rule200, - rule201, - rule202, - rule203, - rule204, - rule205, - rule206, - rule207, - rule208, - rule209, - rule210, - rule211, - rule212, - rule213, - rule214, - rule215, - rule216, - rule217, - rule218, - rule219, - rule220, - rule221, - rule222, - rule223, - rule224, - rule225, - rule226, - rule227, - rule228, - rule229, - rule230, - rule231, - rule232, - rule233, - rule234, - rule235, - rule236, - rule237, - rule238, - rule239, - rule240, - rule241, - rule242, - rule243, - rule244, - rule245, - rule246, - rule247, - rule248, - rule249, - rule250, - rule251, - rule252, - rule253, - rule254, - rule255, - rule256, - rule257, - rule258, - rule259, - rule260, - rule261, - rule262, - rule263, - rule264, - rule265, - rule266, - rule267, - rule268, - rule269, - rule270, - rule271, - rule272, - rule273, - rule274, - rule275, - rule276, - rule277, - rule278, - rule279, - rule280, - rule281, - rule282, - rule283, - rule284, - rule285, - rule286, - rule287, - rule288, - rule289, - rule290, - rule291, - rule292, - rule293, - rule294, - rule295, - rule296, - rule297, - rule298, - rule299, - rule300, - rule301, - rule302, - rule303, ]; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_kernel_module_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_kernel_module_activity.json index 90864f1ab8ab9..d6887f7928dd8 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_kernel_module_activity.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_kernel_module_activity.json @@ -1,15 +1,46 @@ { - "description": "Linux: Kernel Module Activity", + "description": "Identifies loadable kernel module errors, often indicative of potential persistence attempts.", "enabled": false, + "false_positives": [ + "Security tools and device drivers may load legitimate kernel modules." + ], "from": "now-6m", "immutable": true, + "index": [ + "auditbeat-*" + ], "interval": "5m", "language": "kuery", - "name": "Linux: Kernel Module Activity", + "max_signals": 33, + "name": "Persistence via Kernel Module Modification", "query": "process.name: (insmod or kmod or modprobe or rmod) and event.action:executed", - "risk_score": 50, + "references": [ + "https://www.hackers-arise.com/single-post/2017/11/03/Linux-for-Hackers-Part-10-Loadable-Kernel-Modules-LKM" + ], + "risk_score": 25, "rule_id": "81cc58f5-8062-49a2-ba84-5cc4b4d31c40", "severity": "low", + "tags": [ + "EIA", + "auditbeat" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/techniques/TA0003/" + }, + "techniques": [ + { + "id": "T1215", + "name": "Kernel Modules and Extensions", + "reference": "https://attack.mitre.org/techniques/T1215/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_process_started_in_temp_directory.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_process_started_in_temp_directory.json index d9d409feae473..945c8acfe00e4 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_process_started_in_temp_directory.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_process_started_in_temp_directory.json @@ -1,15 +1,26 @@ { - "description": "Linux: Process Started in Temp Directory", + "description": "Identifies processes running in a temporary folder. This is sometimes done by adversaries to hide malware.", "enabled": false, + "false_positives": [ + "Build systems like Jenkins may start processes in the /tmp directory." + ], "from": "now-6m", "immutable": true, + "index": [ + "auditbeat-*" + ], "interval": "5m", "language": "kuery", - "name": "Linux: Process Started in Temp Directory", + "max_signals": 33, + "name": "Unusual Process Execution - Temp", "query": "process.working_directory: /tmp and event.action:executed", - "risk_score": 50, + "risk_score": 25, "rule_id": "df959768-b0c9-4d45-988c-5606a2be8e5a", "severity": "low", + "tags": [ + "EIA", + "auditbeat" + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_shell_activity_by_web_server.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_shell_activity_by_web_server.json index d533f5d4ec3f6..e8c5942ec5100 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_shell_activity_by_web_server.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_shell_activity_by_web_server.json @@ -1,16 +1,47 @@ { - "description": "Linux: Shell Activity By Web Server", + "description": "Identifies suspicious commands executed via a web server, which may suggest a vulnerability and remote shell access.", "enabled": false, + "false_positives": [ + "Network monitoring or management products may have a web server component that runs shell commands as part of normal behavior." + ], "filters": [], "from": "now-6m", "immutable": true, + "index": [ + "auditbeat-*" + ], "interval": "5m", "language": "kuery", + "max_signals": 33, "name": "Linux: Shell Activity By Web Server", "query": "process.name: bash and (user.name: apache or www) and event.action:executed", + "references": [ + "https://pentestlab.blog/tag/web-shell/" + ], "risk_score": 50, "rule_id": "231876e7-4d1f-4d63-a47c-47dd1acdc1cb", "severity": "low", + "tags": [ + "EIA", + "auditbeat" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0003", + "name": "Persistence", + "reference": "https://attack.mitre.org/techniques/TA0003/" + }, + "techniques": [ + { + "id": "T1100", + "name": "Web Shell", + "reference": "https://attack.mitre.org/techniques/T1215/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_whoami_commmand.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_whoami_commmand.json index 56a2782eb0cca..c57e21334b4f7 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_whoami_commmand.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_whoami_commmand.json @@ -1,15 +1,43 @@ { - "description": "Linux: Whoami Commmand", + "description": "The 'whoami' command was executed on a Linux host. This is often used by tools and persistence mechanisms to test for privlieged access.", "enabled": false, + "false_positives": [ + "Security testing tools and frameworks may run this command. Some normal use of this command may originate from automation tools and frameworks." + ], "from": "now-6m", "immutable": true, + "index": [ + "auditbeat-*" + ], "interval": "5m", "language": "kuery", - "name": "Linux: Whoami Commmand", + "max_signals": 33, + "name": "Linux: User Discovery Via The Whoami Commmand", "query": "process.name: whoami and event.action:executed", "risk_score": 50, "rule_id": "120559c6-5e24-49f4-9e30-8ffe697df6b9", "severity": "low", + "tags": [ + "EIA", + "auditbeat" + ], + "threats": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0007", + "name": "Discovery", + "reference": "https://attack.mitre.org/tactics/TA0007/" + }, + "techniques": [ + { + "id": "T1033", + "name": "System Owner/User Discovery", + "reference": "https://attack.mitre.org/techniques/T1033/" + } + ] + } + ], "to": "now", "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_base64_encoded_invokecommand_powershell_execution.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_base64_encoded_invokecommand_powershell_execution.json deleted file mode 100644 index 05d54f6bdb4c6..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_base64_encoded_invokecommand_powershell_execution.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "description": "Suricata Base64 Encoded Invoke-Command Powershell Execution", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Base64 Encoded Invoke-Command Powershell Execution", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2610182 or 2610183 or 2610184 or 2610185 or 2610186 or 2610187) or rule.id: (2610182 or 2610183 or 2610184 or 2610185 or 2610186 or 2610187))", - "references": [ - "https://github.com/travisbgreen/hunting-rules/blob/master/hunting.rules#L179-L184", - "This group of signatures detect base-64 encoded variations of the 'Invoke-Command' Powershell cmdlet. This is not something you should see on a typical network and could indicate a possible command and control channel." - ], - "risk_score": 50, - "rule_id": "6ff01a30-95dd-471c-b61d-0fd9ee2d0a20", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0011", - "name": "command and control", - "reference": "https://attack.mitre.org/tactics/TA0011/" - }, - "techniques": [ - { - "id": "T1001", - "name": "data obfuscation", - "reference": "https://attack.mitre.org/techniques/T1001/" - }, - { - "id": "T1132", - "name": "data encoding", - "reference": "https://attack.mitre.org/techniques/T1132/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_base64_encoded_newobject_powershell_execution.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_base64_encoded_newobject_powershell_execution.json deleted file mode 100644 index ac47a6877c525..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_base64_encoded_newobject_powershell_execution.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "description": "Suricata Base64 Encoded New-Object Powershell Execution", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Base64 Encoded New-Object Powershell Execution", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2610188 or 2610189 or 2610190 or 2610191 or 2610192 or 2610193) or rule.id: (2610188 or 2610189 or 2610190 or 2610191 or 2610192 or 2610193))", - "references": [ - "https://github.com/travisbgreen/hunting-rules/blob/master/hunting.rules#L191-L196", - "This group of signatures detect base-64 encoded variations of the 'New-Object' Powershell cmdlet. This is not something you should see on a typical network and could indicate a possible command and control channel." - ], - "risk_score": 50, - "rule_id": "d14d5401-0f7a-4933-b816-1b8f823e3d84", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0011", - "name": "command and control", - "reference": "https://attack.mitre.org/tactics/TA0011/" - }, - "techniques": [ - { - "id": "T1001", - "name": "data obfuscation", - "reference": "https://attack.mitre.org/techniques/T1001/" - }, - { - "id": "T1132", - "name": "data encoding", - "reference": "https://attack.mitre.org/techniques/T1132/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_base64_encoded_startprocess_powershell_execution.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_base64_encoded_startprocess_powershell_execution.json deleted file mode 100644 index 972299bbd74b0..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_base64_encoded_startprocess_powershell_execution.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "description": "Suricata Base64 Encoded Start-Process Powershell Execution", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Base64 Encoded Start-Process Powershell Execution", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2610194 or 2610195 or 2610196 or 2610197 or 2610198 or 2610199) or rule.id: (2610194 or 2610195 or 2610196 or 2610197 or 2610198 or 2610199))", - "references": [ - "https://github.com/travisbgreen/hunting-rules/blob/master/hunting.rules#L191-L196", - "This group of signatures detect base-64 encoded variations of the 'Start-Process' Powershell cmdlet. This is not something you should see on a typical network and could indicate a possible command and control channel." - ], - "risk_score": 50, - "rule_id": "372dce88-003d-4bcf-8c95-34ea8be180a1", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0011", - "name": "command and control", - "reference": "https://attack.mitre.org/tactics/TA0011/" - }, - "techniques": [ - { - "id": "T1001", - "name": "data obfuscation", - "reference": "https://attack.mitre.org/techniques/T1001/" - }, - { - "id": "T1132", - "name": "data encoding", - "reference": "https://attack.mitre.org/techniques/T1132/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_a_suspicious_string_was_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_a_suspicious_string_was_detected.json deleted file mode 100644 index bb6a57f905bf7..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_a_suspicious_string_was_detected.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - A suspicious string was detected", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - A suspicious string was detected", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"A suspicious string was detected\" or rule.category: \"A suspicious string was detected\")", - "risk_score": 50, - "rule_id": "2a3d91c1-5065-46ab-bed0-93f80835b1d5", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_administrator_privilege_gain.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_administrator_privilege_gain.json deleted file mode 100644 index 9de1f5ad33712..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_administrator_privilege_gain.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Attempted Administrator Privilege Gain", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Attempted Administrator Privilege Gain", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Attempted Administrator Privilege Gain\" or rule.category: \"Attempted Administrator Privilege Gain\")", - "risk_score": 50, - "rule_id": "f840129e-9089-4f46-8af1-0745e8f54713", - "severity": "high", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_denial_of_service.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_denial_of_service.json deleted file mode 100644 index d0c3eb9ba2331..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_denial_of_service.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Attempted Denial of Service", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Attempted Denial of Service", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Attempted Denial of Service\" or rule.category: \"Attempted Denial of Service\")", - "risk_score": 50, - "rule_id": "a62927f4-2488-4679-b56f-cda1a7f4c9e1", - "severity": "high", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_information_leak.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_information_leak.json deleted file mode 100644 index 75995d657b464..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_information_leak.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Attempted Information Leak", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Attempted Information Leak", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Attempted Information Leak\" or rule.category: \"Attempted Information Leak\")", - "risk_score": 50, - "rule_id": "88d69362-f496-41d6-8e6b-a2dbaed3513f", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_login_with_suspicious_username.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_login_with_suspicious_username.json deleted file mode 100644 index 31d14a3b68708..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_login_with_suspicious_username.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Attempted Login with Suspicious Username", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Attempted Login with Suspicious Username", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"An attempted login using a suspicious username was detected\" or rule.category: \"An attempted login using a suspicious username was detected\")", - "risk_score": 50, - "rule_id": "a84cd36c-dd5a-4e86-a2ce-44556c21cef0", - "severity": "medium", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_user_privilege_gain.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_user_privilege_gain.json deleted file mode 100644 index 13300e8a17694..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_attempted_user_privilege_gain.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Attempted User Privilege Gain", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Attempted User Privilege Gain", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Attempted User Privilege Gain\" or rule.category: \"Attempted User Privilege Gain\")", - "risk_score": 50, - "rule_id": "eabce895-4602-4d20-8bf9-11c903bb3e08", - "severity": "medium", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_client_using_unusual_port.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_client_using_unusual_port.json deleted file mode 100644 index 9c1e3ef1b39f8..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_client_using_unusual_port.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Client Using Unusual Port", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Client Using Unusual Port", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"A client was using an unusual port\" or rule.category: \"A client was using an unusual port\")", - "risk_score": 50, - "rule_id": "00503a3c-304c-421c-bfea-e5d8fdfd9726", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_crypto_currency_mining_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_crypto_currency_mining_activity.json deleted file mode 100644 index a4ef732c2e1bd..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_crypto_currency_mining_activity.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Crypto Currency Mining Activity", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Crypto Currency Mining Activity", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Crypto Currency Mining Activity Detected\" or rule.category: \"Crypto Currency Mining Activity Detected\")", - "risk_score": 50, - "rule_id": "74cd4920-a441-41d2-8a23-5bee70626e60", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_decode_of_an_rpc_query.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_decode_of_an_rpc_query.json deleted file mode 100644 index 43f767f14b7e6..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_decode_of_an_rpc_query.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Decode of an RPC Query", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Decode of an RPC Query", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Decode of an RPC Query\" or rule.category: \"Decode of an RPC Query\")", - "risk_score": 50, - "rule_id": "e9fc5bd3-c8a1-442c-be6d-032da07c508b", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_default_username_and_password_login_attempt.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_default_username_and_password_login_attempt.json deleted file mode 100644 index 74a566563f15a..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_default_username_and_password_login_attempt.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Default Username and Password Login Attempt", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Default Username and Password Login Attempt", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Attempt to login by a default username and password\" or rule.category: \"Attempt to login by a default username and password\")", - "risk_score": 50, - "rule_id": "190bd112-f831-4813-98b2-e45a934277c2", - "severity": "medium", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_denial_of_service.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_denial_of_service.json deleted file mode 100644 index d7a615807593e..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_denial_of_service.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Denial of Service", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Denial of Service", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Denial of Service\" or rule.category: \"Denial of Service\")", - "risk_score": 75, - "rule_id": "0e97e390-84db-4725-965a-a8b0b600f7be", - "severity": "medium", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_denial_of_service_attack.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_denial_of_service_attack.json deleted file mode 100644 index e0bf4220d4467..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_denial_of_service_attack.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Denial of Service Attack", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Denial of Service Attack", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Detection of a Denial of Service Attack\" or rule.category: \"Detection of a Denial of Service Attack\")", - "risk_score": 100, - "rule_id": "42a60eaa-fd20-479b-b6ca-bdb88d47b34b", - "severity": "high", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_executable_code_was_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_executable_code_was_detected.json deleted file mode 100644 index 09a72e761cb40..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_executable_code_was_detected.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Executable code was detected", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Executable code was detected", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Executable code was detected\" or rule.category: \"Executable code was detected\")", - "risk_score": 50, - "rule_id": "4699296b-5127-475a-9d83-8434fcd18136", - "severity": "high", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_exploit_kit_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_exploit_kit_activity.json deleted file mode 100644 index 8c8f5565da4e6..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_exploit_kit_activity.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Exploit Kit Activity", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Exploit Kit Activity", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Exploit Kit Activity Detected\" or rule.category: \"Exploit Kit Activity Detected\")", - "risk_score": 50, - "rule_id": "b3111af8-79bf-4ec3-97ae-28d9ed9fbd38", - "severity": "high", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_external_ip_address_retrieval.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_external_ip_address_retrieval.json deleted file mode 100644 index 39c42d81ee59d..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_external_ip_address_retrieval.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - External IP Address Retrieval", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - External IP Address Retrieval", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Device Retrieving External IP Address Detected\" or rule.category: \"Device Retrieving External IP Address Detected\")", - "risk_score": 50, - "rule_id": "c7df9ecf-d6be-4ef8-9871-cb317dfff0b4", - "severity": "medium", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_generic_icmp_event.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_generic_icmp_event.json deleted file mode 100644 index e4d15f667371f..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_generic_icmp_event.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Generic ICMP event", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Generic ICMP event", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Generic ICMP event\" or rule.category: \"Generic ICMP event\")", - "risk_score": 25, - "rule_id": "3309bffa-7c43-409a-acea-6631c1b077e5", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_generic_protocol_command_decode.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_generic_protocol_command_decode.json deleted file mode 100644 index faaccc5eee992..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_generic_protocol_command_decode.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Generic Protocol Command Decode", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Generic Protocol Command Decode", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Generic Protocol Command Decode\" or rule.category: \"Generic Protocol Command Decode\")", - "risk_score": 25, - "rule_id": "6fd2deb4-a7a9-4221-8b7b-8d26836a8c30", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_information_leak.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_information_leak.json deleted file mode 100644 index c58b4a5f4b13a..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_information_leak.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Information Leak", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Information Leak", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Information Leak\" or rule.category: \"Information Leak\")", - "risk_score": 25, - "rule_id": "95df8ff4-7169-4c84-ae50-3561b1d1bc91", - "severity": "medium", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_large_scale_information_leak.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_large_scale_information_leak.json deleted file mode 100644 index b1916165c6e90..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_large_scale_information_leak.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Large Scale Information Leak", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Large Scale Information Leak", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Large Scale Information Leak\" or rule.category: \"Large Scale Information Leak\")", - "risk_score": 75, - "rule_id": "ca98de30-c703-4170-97ae-ab2b340f6080", - "severity": "medium", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_malware_command_and_control_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_malware_command_and_control_activity.json deleted file mode 100644 index 4682f973bdfc9..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_malware_command_and_control_activity.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Malware Command and Control Activity", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Malware Command and Control Activity", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Malware Command and Control Activity Detected\" or rule.category: \"Malware Command and Control Activity Detected\")", - "risk_score": 100, - "rule_id": "56656341-2940-4a69-b8fe-acf3c734f540", - "severity": "high", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_misc_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_misc_activity.json deleted file mode 100644 index 49928bd4caaa5..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_misc_activity.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Misc Activity", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Misc Activity", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Misc activity\" or rule.category: \"Misc activity\")", - "risk_score": 25, - "rule_id": "403ddbde-a486-4dd7-b932-cee4ebef88b6", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_misc_attack.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_misc_attack.json deleted file mode 100644 index 34c9059d26498..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_misc_attack.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Misc Attack", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Misc Attack", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Misc Attack\" or rule.category: \"Misc Attack\")", - "risk_score": 50, - "rule_id": "83277123-749f-49da-ad3d-d59f35490db1", - "severity": "medium", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_network_scan_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_network_scan_detected.json deleted file mode 100644 index 9bc0572e25779..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_network_scan_detected.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Network Scan Detected", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Network Scan Detected", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Detection of a Network Scan\" or rule.category: \"Detection of a Network Scan\")", - "risk_score": 25, - "rule_id": "7e969b45-d005-4173-aee7-a7aaa79bc372", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_network_trojan_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_network_trojan_detected.json deleted file mode 100644 index b319d5d2be079..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_network_trojan_detected.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Network Trojan Detected", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Network Trojan Detected", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"A Network Trojan was detected\" or rule.category: \"A Network Trojan was detected\")", - "risk_score": 100, - "rule_id": "76ffa464-ec03-42e1-87ee-87760c331061", - "severity": "high", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_nonstandard_protocol_or_event.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_nonstandard_protocol_or_event.json deleted file mode 100644 index c104b1d2acc45..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_nonstandard_protocol_or_event.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Non-Standard Protocol or Event", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Non-Standard Protocol or Event", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Detection of a non-standard protocol or event\" or rule.category: \"Detection of a non-standard protocol or event\")", - "risk_score": 50, - "rule_id": "82f9f485-873b-4eeb-b231-052ab81e05b8", - "severity": "medium", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_not_suspicious_traffic.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_not_suspicious_traffic.json deleted file mode 100644 index 4ff46e429c4c3..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_not_suspicious_traffic.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Not Suspicious Traffic", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Not Suspicious Traffic", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Not Suspicious Traffic\" or rule.category: \"Not Suspicious Traffic\")", - "risk_score": 25, - "rule_id": "c0f684ff-4f15-44e7-912d-aa8b8f08a910", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_observed_c2_domain.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_observed_c2_domain.json deleted file mode 100644 index 6b06e23648cbd..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_observed_c2_domain.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Observed C2 Domain", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Observed C2 Domain", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Domain Observed Used for C2 Detected\" or rule.category: \"Domain Observed Used for C2 Detected\")", - "risk_score": 75, - "rule_id": "8adfa89f-aa90-4d26-9d7a-7da652cae902", - "severity": "high", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_possible_social_engineering_attempted.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_possible_social_engineering_attempted.json deleted file mode 100644 index 7c4f096280ed4..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_possible_social_engineering_attempted.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Possible Social Engineering Attempted", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Possible Social Engineering Attempted", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Possible Social Engineering Attempted\" or rule.category: \"Possible Social Engineering Attempted\")", - "risk_score": 50, - "rule_id": "7d2d5a5f-f590-407d-933a-42adb1a7bcef", - "severity": "medium", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_possibly_unwanted_program.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_possibly_unwanted_program.json deleted file mode 100644 index 7e5f92c15e414..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_possibly_unwanted_program.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Possibly Unwanted Program", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Possibly Unwanted Program", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Possibly Unwanted Program Detected\" or rule.category: \"Possibly Unwanted Program Detected\")", - "risk_score": 25, - "rule_id": "1b9a31e8-fdfa-400e-aa4e-79a7f1a1da18", - "severity": "medium", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_potential_corporate_privacy_violation.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_potential_corporate_privacy_violation.json deleted file mode 100644 index 221cfaab48e00..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_potential_corporate_privacy_violation.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Potential Corporate Privacy Violation", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Potential Corporate Privacy Violation", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Potential Corporate Privacy Violation\" or rule.category: \"Potential Corporate Privacy Violation\")", - "risk_score": 25, - "rule_id": "1c70f5d5-eae0-4d00-b35a-d34ca607094e", - "severity": "high", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_potentially_bad_traffic.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_potentially_bad_traffic.json deleted file mode 100644 index fc1baf2014757..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_potentially_bad_traffic.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Potentially Bad Traffic", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Potentially Bad Traffic", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Potentially Bad Traffic\" or rule.category: \"Potentially Bad Traffic\")", - "risk_score": 25, - "rule_id": "197cdd5a-9880-4780-a87c-594d0ed2b7b4", - "severity": "medium", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_potentially_vulnerable_web_application_access.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_potentially_vulnerable_web_application_access.json deleted file mode 100644 index cfcb246d44f4d..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_potentially_vulnerable_web_application_access.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Potentially Vulnerable Web Application Access", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Potentially Vulnerable Web Application Access", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"access to a potentially vulnerable web application\" or rule.category: \"access to a potentially vulnerable web application\")", - "risk_score": 75, - "rule_id": "0993e926-1a01-4c28-918a-cdd5741a19a8", - "severity": "medium", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_successful_administrator_privilege_gain.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_successful_administrator_privilege_gain.json deleted file mode 100644 index 919083650682c..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_successful_administrator_privilege_gain.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Successful Administrator Privilege Gain", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Successful Administrator Privilege Gain", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Successful Administrator Privilege Gain\" or rule.category: \"Successful Administrator Privilege Gain\")", - "risk_score": 75, - "rule_id": "f068e655-1f52-4d81-839a-9c08c6543ceb", - "severity": "high", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_successful_credential_theft.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_successful_credential_theft.json deleted file mode 100644 index feb708316fbd8..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_successful_credential_theft.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Successful Credential Theft", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Successful Credential Theft", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Successful Credential Theft Detected\" or rule.category: \"Successful Credential Theft Detected\")", - "risk_score": 75, - "rule_id": "90f3e735-2187-4e8e-8d28-6e3249964851", - "severity": "high", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_successful_user_privilege_gain.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_successful_user_privilege_gain.json deleted file mode 100644 index 8a7e366d25e58..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_successful_user_privilege_gain.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Successful User Privilege Gain", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Successful User Privilege Gain", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Successful User Privilege Gain\" or rule.category: \"Successful User Privilege Gain\")", - "risk_score": 50, - "rule_id": "f8ebd022-6e92-4b80-ac49-7ee011ba2ce0", - "severity": "high", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_suspicious_filename_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_suspicious_filename_detected.json deleted file mode 100644 index 356c0d23dd4e9..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_suspicious_filename_detected.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Suspicious Filename Detected", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Suspicious Filename Detected", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"A suspicious filename was detected\" or rule.category: \"A suspicious filename was detected\")", - "risk_score": 25, - "rule_id": "d0489b07-8140-4e3d-a2b7-52f2c06fdc7c", - "severity": "medium", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_system_call_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_system_call_detected.json deleted file mode 100644 index f41692fb21841..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_system_call_detected.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - System Call Detected", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - System Call Detected", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"A system call was detected\" or rule.category: \"A system call was detected\")", - "risk_score": 50, - "rule_id": "44a5c55a-a34f-43c3-8f21-df502862aa9b", - "severity": "medium", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_targeted_malicious_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_targeted_malicious_activity.json deleted file mode 100644 index 9c13b53f43263..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_targeted_malicious_activity.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Targeted Malicious Activity", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Targeted Malicious Activity", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Targeted Malicious Activity was Detected\" or rule.category: \"Targeted Malicious Activity was Detected\")", - "risk_score": 75, - "rule_id": "d299379d-41de-4640-96b6-77aaa9adfa6f", - "severity": "high", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_tcp_connection_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_tcp_connection_detected.json deleted file mode 100644 index eb41269d58ffa..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_tcp_connection_detected.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - TCP Connection Detected", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - TCP Connection Detected", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"A TCP connection was detected\" or rule.category: \"A TCP connection was detected\")", - "risk_score": 0, - "rule_id": "ddf402cf-307d-4f46-a25d-dce3aee1ad13", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_unknown_traffic.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_unknown_traffic.json deleted file mode 100644 index a260d049633b9..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_unknown_traffic.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Unknown Traffic", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Unknown Traffic", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Unknown Traffic\" or rule.category: \"Unknown Traffic\")", - "risk_score": 25, - "rule_id": "827ea90c-00c2-45f7-b873-dd060297b2d2", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_unsuccessful_user_privilege_gain.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_unsuccessful_user_privilege_gain.json deleted file mode 100644 index c57cc857cef67..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_unsuccessful_user_privilege_gain.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Unsuccessful User Privilege Gain", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Unsuccessful User Privilege Gain", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Unsuccessful User Privilege Gain\" or rule.category: \"Unsuccessful User Privilege Gain\")", - "risk_score": 50, - "rule_id": "85471d30-78c9-48f6-b2db-ab5b2547e450", - "severity": "high", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_web_application_attack.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_web_application_attack.json deleted file mode 100644 index 4014473971b8e..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_category_web_application_attack.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Category - Web Application Attack", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Category - Web Application Attack", - "query": "event.module: suricata and event.kind: alert and (suricata.eve.alert.category: \"Web Application Attack\" or rule.category: \"Web Application Attack\")", - "risk_score": 75, - "rule_id": "e856918b-f26e-4893-84b9-3deb65046fb7", - "severity": "high", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_cobaltstrike_artifact_in_an_dns_request.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_cobaltstrike_artifact_in_an_dns_request.json deleted file mode 100644 index e77e977d780d5..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_cobaltstrike_artifact_in_an_dns_request.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata CobaltStrike Artifact in an DNS Request", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata CobaltStrike Artifact in an DNS Request", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2610166 or 2610167 or 2610168) or rule.id: (2610166 or 2610167 or 2610168))", - "risk_score": 100, - "rule_id": "481ef0f5-beda-4fa2-8bfb-039c95500deb", - "severity": "high", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_commonly_abused_dns_domain_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_commonly_abused_dns_domain_detected.json deleted file mode 100644 index a866c79a85822..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_commonly_abused_dns_domain_detected.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Commonly Abused DNS Domain Detected", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Commonly Abused DNS Domain Detected", - "query": " event.module:suricata and event.kind:alert and (suricata.eve.alert.signature:(TGI* and *HUNT* and *Abused* and *TLD*) or rule.description:(TGI* and *HUNT* and *Abused* and *TLD*))", - "risk_score": 25, - "rule_id": "1844dfe1-b05e-4ca6-b367-6b9e3a1fe227", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_directory_reversal_characters_in_an_http_request.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_directory_reversal_characters_in_an_http_request.json deleted file mode 100644 index 862d5417fadcc..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_directory_reversal_characters_in_an_http_request.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Directory Reversal Characters in an HTTP Request", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Directory Reversal Characters in an HTTP Request", - "query": "(event.module:suricata and event.kind:alert) and suricata.eve.alert.signature_id: (2610161 or 2610162)", - "risk_score": 50, - "rule_id": "c0ca8090-60f8-4458-befe-c43687b648a3", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_directory_traversal_characters_in_an_http_request.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_directory_traversal_characters_in_an_http_request.json deleted file mode 100644 index 73cb913e271a1..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_directory_traversal_characters_in_an_http_request.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "description": "Suricata Directory Traversal Characters in an HTTP Request Header", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Directory Traversal Characters in an HTTP Request Header", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2610161 or 2610162) or rule.id: (2610161 or 2610162))", - "references": [ - "https://github.com/travisbgreen/hunting-rules/blob/master/hunting.rules#L179-L184", - "This group of signatures detects directory traversal characters in a header of an HTTP request. This is not something you should see on a typical network and could indicate an attempt to exploit the web application." - ], - "risk_score": 50, - "rule_id": "7c663c8d-cdfd-4605-9dd6-d682fa4ade8c", - "severity": "medium", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_directory_traversal_characters_in_http_response.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_directory_traversal_characters_in_http_response.json deleted file mode 100644 index c9d0db8ed300e..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_directory_traversal_characters_in_http_response.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "description": "Suricata Directory Traversal Characters in HTTP Response", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Directory Traversal Characters in HTTP Response", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id:2610086 or rule.id:2610086)", - "references": [ - "https://github.com/travisbgreen/hunting-rules/blob/master/hunting.rules#L89", - "This group of signatures detects directory traversal characters in a header of an HTTP response. This is not something you should see on a typical network and could indicate an attempt to exploit the web application." - ], - "risk_score": 75, - "rule_id": "a6406974-ea70-45b5-b5d8-ca17695adbde", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_directory_traversal_in_downloaded_zip_file.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_directory_traversal_in_downloaded_zip_file.json deleted file mode 100644 index 65f8195751fc5..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_directory_traversal_in_downloaded_zip_file.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "description": "Suricata Directory Traversal in Downloaded Zip File", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Directory Traversal in Downloaded Zip File", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id:2610085 or rule.id:2610085)", - "references": [ - "https://github.com/travisbgreen/hunting-rules/blob/master/hunting.rules#L88", - "This group of signatures detects directory traversal characters in a zip archive downloaded over the network. This is not something you should see on a typical network and could indicate an attempt to trick a user to overwrite system files." - ], - "risk_score": 75, - "rule_id": "d5d990bc-303c-4241-8138-6ba3cf2ee93e", - "severity": "medium", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0002", - "name": "execution", - "reference": "https://attack.mitre.org/tactics/TA0002/" - }, - "techniques": [ - { - "id": "T1204", - "name": "user execution", - "reference": "https://attack.mitre.org/techniques/T1204/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_dns_traffic_on_unusual_tcp_port.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_dns_traffic_on_unusual_tcp_port.json deleted file mode 100644 index bd73b822f9f49..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_dns_traffic_on_unusual_tcp_port.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "description": "Suricata DNS Traffic on Unusual Port (TCP or UDP)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata DNS Traffic on Unusual Port", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2610015 or 2610013) or rule.id: (2610015 or 2610013))", - "references": [ - "https://github.com/travisbgreen/hunting-rules/blob/master/hunting.rules#L14-L16", - "This detects DNS traffic running on an unusual port. This could indicate an application that is misconfigured or attempting to bypass security controls." - ], - "risk_score": 50, - "rule_id": "deeae336-4ff7-4cf8-ae5b-18bce05da02e", - "severity": "low", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0011", - "name": "command and control", - "reference": "https://attack.mitre.org/tactics/TA0011/" - }, - "techniques": [ - { - "id": "T1065", - "name": "uncommonly used port", - "reference": "https://attack.mitre.org/techniques/T1065/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_dns_traffic_on_unusual_udp_port.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_dns_traffic_on_unusual_udp_port.json deleted file mode 100644 index eb9b06f3cab14..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_dns_traffic_on_unusual_udp_port.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata DNS Traffic on Unusual UDP Port", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata DNS Traffic on Unusual UDP Port", - "query": "suricata.eve.alert.signature_id:2610015 and (event.module:suricata and event.kind:alert)", - "risk_score": 50, - "rule_id": "2343d9a4-365b-45b2-acb0-76934d43c75b", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_double_encoded_characters_in_a_uri.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_double_encoded_characters_in_a_uri.json deleted file mode 100644 index eaed3aabed8f2..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_double_encoded_characters_in_a_uri.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Double Encoded Characters in a URI", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Double Encoded Characters in a URI", - "query": "(event.module:suricata and event.kind:alert) and suricata.eve.alert.signature_id: (2610092 or 2610093 or 2610094 or 2610095)", - "risk_score": 50, - "rule_id": "1ed4d2d1-330c-4c7d-b32d-2d8805437946", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_double_encoded_characters_in_an_http_post.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_double_encoded_characters_in_an_http_post.json deleted file mode 100644 index 136ea957be766..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_double_encoded_characters_in_an_http_post.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Double Encoded Characters in an HTTP POST", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Double Encoded Characters in an HTTP POST", - "query": "suricata.eve.alert.signature_id:2610090 and (event.module:suricata and event.kind:alert)", - "risk_score": 50, - "rule_id": "a839a360-94ae-4219-b1cc-458d836333a7", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_double_encoded_characters_in_http_request.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_double_encoded_characters_in_http_request.json deleted file mode 100644 index 3cbdb6da3c141..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_double_encoded_characters_in_http_request.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "description": "Suricata Double Encoded Characters in a URI", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Double Encoded Characters in a URI", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2610090 or 2610092 or 2610093 or 2610094 or 2610095) or rule.id: (2610090 or 2610092 or 2610093 or 2610094 or 2610095))", - "references": [ - "https://github.com/travisbgreen/hunting-rules/blob/master/hunting.rules", - "This group of signatures detects double encoding of characters in an HTTP request. This is not something you should see on a typical network and could indicate an attempt to exploit the web application or bypass detections." - ], - "risk_score": 25, - "rule_id": "8aedfe6f-9219-463b-808b-91e7ea8ea5e8", - "severity": "low", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_eval_php_function_in_an_http_request.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_eval_php_function_in_an_http_request.json deleted file mode 100644 index 986ac161d70df..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_eval_php_function_in_an_http_request.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata eval PHP Function in an HTTP Request", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata eval PHP Function in an HTTP Request", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id:2610088 or rule.id: 2610088)", - "risk_score": 50, - "rule_id": "8c77b4ed-4e98-438b-adb0-d645d4a4ea26", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2018_1000861.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2018_1000861.json deleted file mode 100644 index 54b881428aa34..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2018_1000861.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET WEB_SPECIFIC_APPS Jenkins Chained Exploits CVE-2018-1000861 and CVE-2019-1003000", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET WEB_SPECIFIC_APPS Jenkins Chained Exploits CVE-2018-1000861 and CVE-2019-1003000", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2027349 or 2027350) or rule.id: (2027349 or 2027350))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2018-1000861"], - "risk_score": 100, - "rule_id": "ada41f8a-92b1-49d0-80ac-c4bc28824ab5", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_0227.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_0227.json deleted file mode 100644 index c050b73114bf5..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_0227.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ATTACK [PTsecurity] Possible Apache Axis RCE via SSRF (CVE-2019-0227)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ATTACK [PTsecurity] Possible Apache Axis RCE via SSRF (CVE-2019-0227)", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (10004698) or rule.id: (10004698))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-0227"], - "risk_score": 100, - "rule_id": "2c8f321c-ba84-4c16-80dd-f20ea06e0c6d", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_0232.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_0232.json deleted file mode 100644 index 9522a286f7898..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_0232.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ATTACK [PTsecurity] Apache Tomcat RCE on Windows (CVE-2019-0232)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ATTACK [PTsecurity] Apache Tomcat RCE on Windows (CVE-2019-0232)", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (10004953) or rule.id: (10004953))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-0232"], - "risk_score": 100, - "rule_id": "fd7ef9a2-f010-49c1-8e08-31d84a9607dd", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_0604.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_0604.json deleted file mode 100644 index 95940a5396b94..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_0604.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET WEB_SPECIFIC_APPS Rails Arbitrary File Disclosure Attempt", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET WEB_SPECIFIC_APPS Rails Arbitrary File Disclosure Attempt", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2027096) or rule.id: (2027096))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-0604"], - "risk_score": 100, - "rule_id": "ec50104d-26b1-45a6-b80e-768bd13cc34c", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_0708.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_0708.json deleted file mode 100644 index 401e1e815ea52..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_0708.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET EXPLOIT [NCC GROUP] Possible Bluekeep Inbound RDP Exploitation Attempt (CVE-2019-0708)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Possible Bluekeep Inbound RDP Exploitation Attempt (CVE-2019-0708)", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (10004867 or 2027369) or rule.id: (10004867 or 2027369))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-0708"], - "risk_score": 100, - "rule_id": "1589bff6-ec82-4acf-8f67-68ef0f3676d0", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_0752.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_0752.json deleted file mode 100644 index 5f256681aedd9..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_0752.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET EXPLOIT IE Scripting Engine Memory Corruption Vulnerability (CVE-2019-0752)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET EXPLOIT IE Scripting Engine Memory Corruption Vulnerability (CVE-2019-0752)", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2027721) or rule.id: (2027721))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-0752"], - "risk_score": 100, - "rule_id": "5aa5f6db-2cc7-43de-ac8b-c7daa52ba9c3", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_1003000.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_1003000.json deleted file mode 100644 index c470783b0266d..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_1003000.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET WEB_SPECIFIC_APPS Jenkins RCE CVE-2019-1003000", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET WEB_SPECIFIC_APPS Jenkins RCE CVE-2019-1003000", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2027349 or 2027350 or 2027346) or rule.id: (2027349 or 2027350 or 2027346))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-1003000"], - "risk_score": 100, - "rule_id": "6deba829-00ac-4298-bc80-976e4ef215d2", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_10149.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_10149.json deleted file mode 100644 index 2c18ecc3104fd..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_10149.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET EXPLOIT Possible Exim 4.87-4.91 RCE Attempt Inbound (CVE-2019-10149", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET EXPLOIT Possible Exim 4.87-4.91 RCE Attempt Inbound (CVE-2019-10149", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2027442) or rule.id: (2027442))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-10149"], - "risk_score": 100, - "rule_id": "e52d833a-0642-4076-89e9-6b7263361cee", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_11043.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_11043.json deleted file mode 100644 index 0e2c8cfa7339d..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_11043.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET WEB_SERVER Possible PHP Remote Code Execution CVE-2019-11043 PoC (Inbound)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET WEB_SERVER Possible PHP Remote Code Execution CVE-2019-11043 PoC (Inbound)", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2028895) or rule.id: (2028895))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-11043"], - "risk_score": 100, - "rule_id": "7955c692-1259-4f77-aa9e-95a98b69d4aa", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_11510.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_11510.json deleted file mode 100644 index 65a6874f09932..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_11510.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET EXPLOIT Pulse Secure SSL VPN - Arbitrary File Read (CVE-2019-11510)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET EXPLOIT Pulse Secure SSL VPN - Arbitrary File Read (CVE-2019-11510)", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2027904) or rule.id: (2027904))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-11510"], - "risk_score": 100, - "rule_id": "d2dbbfee-2104-4d20-b562-d466b0b2c5ef", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_11580.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_11580.json deleted file mode 100644 index 6e3e8bc8cdbb7..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_11580.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET WEB_SPECIFIC_APPS Atlassian Crowd Plugin Upload Attempt (CVE-2019-11580)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET WEB_SPECIFIC_APPS Atlassian Crowd Plugin Upload Attempt (CVE-2019-11580)", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2027712) or rule.id: (2027712))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-11580"], - "risk_score": 100, - "rule_id": "f6e6c803-b44c-44b1-acbb-cd3e5bca10f8", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_11581.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_11581.json deleted file mode 100644 index 34b93871fa10b..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_11581.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET WEB_SPECIFIC_APPS Atlassian JIRA Template Injection RCE (CVE-2019-11581", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET WEB_SPECIFIC_APPS Atlassian JIRA Template Injection RCE (CVE-2019-11581", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2027711) or rule.id: (2027711))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-11581"], - "risk_score": 100, - "rule_id": "720663fb-23da-43a5-bf4f-907265e5426d", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_13450.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_13450.json deleted file mode 100644 index ae014db82194e..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_13450.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET EXPLOIT Possible Zoom Client Auto-Join (CVE-2019-13450", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET EXPLOIT Possible Zoom Client Auto-Join (CVE-2019-13450", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2027696) or rule.id: (2027696))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-13450"], - "risk_score": 100, - "rule_id": "04a9d926-51bb-4981-8116-04ee63f1ad75", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_13505.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_13505.json deleted file mode 100644 index 5a70886a84469..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_13505.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET WEB_SPECIFIC_APPS Appointment Hour Booking - WordPress Plugin - Stored XSS (CVE-2019-13505)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET WEB_SPECIFIC_APPS Appointment Hour Booking - WordPress Plugin - Stored XSS (CVE-2019-13505)", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2027706) or rule.id: (2027706))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-13505"], - "risk_score": 100, - "rule_id": "7b47f6a7-ae2a-46a1-a718-641649dfbfd6", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_15107.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_15107.json deleted file mode 100644 index cbede3be1782b..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_15107.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET WEB_SERVER Webmin RCE CVE-2019-15107", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET WEB_SERVER Webmin RCE CVE-2019-15107", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2027896) or rule.id: (2027896))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-15107"], - "risk_score": 100, - "rule_id": "37f923c4-048d-4a17-b804-b4f895477962", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_15846.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_15846.json deleted file mode 100644 index 99ac06aa715aa..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_15846.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET EXPLOIT Possible EXIM RCE Inbound (CVE-2019-15846)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET EXPLOIT Possible EXIM RCE Inbound (CVE-2019-15846)", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2027959 or 2027960) or rule.id: (2027959 or 2027960))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-15846"], - "risk_score": 100, - "rule_id": "1d625e03-a21b-40c8-82c0-edb497a48254", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_16072.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_16072.json deleted file mode 100644 index 0fe9cde7307e8..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_16072.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET EXPLOIT Enigma Network Management Systems v65.0.0 CVE-2019-16072 (Outbound)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET EXPLOIT Enigma Network Management Systems v65.0.0 CVE-2019-16072 (Outbound)", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2029159 or 2029158) or rule.id: (2029159 or 2029158))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-16072"], - "risk_score": 100, - "rule_id": "5cf97dad-2327-4010-8498-64e5d53fd317", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_1652.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_1652.json deleted file mode 100644 index 254c6019a039d..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_1652.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET EXPLOIT Possible Cisco RV320 RCE Attempt (CVE-2019-1652)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET EXPLOIT Possible Cisco RV320 RCE Attempt (CVE-2019-1652)", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2026860) or rule.id: (2026860))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-1652"], - "risk_score": 100, - "rule_id": "ed220bf3-6617-41c3-8a03-8726d17e3dfc", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_16662.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_16662.json deleted file mode 100644 index d804e7dc18173..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_16662.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET EXPLOIT Possible rConfig 3.9.2 Remote Code Execution PoC (CVE-2019-16662)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET EXPLOIT Possible rConfig 3.9.2 Remote Code Execution PoC (CVE-2019-16662)", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2028933) or rule.id: (2028933))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-16662"], - "risk_score": 100, - "rule_id": "777097d9-059e-409f-9509-67d7f90aea8c", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_16759.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_16759.json deleted file mode 100644 index 7ceebbe31c0ea..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_16759.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET EXPLOIT vBulletin 5.x Unauthenticated Remote Code Execution (CVE-2019-16759)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET EXPLOIT vBulletin 5.x Unauthenticated Remote Code Execution (CVE-2019-16759)", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2028621 or 2028625 or 2028826) or rule.id: (2028621 or 2028625 or 2028826))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-16759"], - "risk_score": 100, - "rule_id": "145634a6-6d3d-4e78-bd51-ffe6f69f6bbb", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_16928.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_16928.json deleted file mode 100644 index 2c970e3248a64..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_16928.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET EXPLOIT Possible EXIM DoS (CVE-2019-16928)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET EXPLOIT Possible EXIM DoS (CVE-2019-16928)", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2028636) or rule.id: (2028636))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-16928"], - "risk_score": 100, - "rule_id": "39bb4ff1-ec7c-4379-9a07-ad24b83060bf", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_17270.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_17270.json deleted file mode 100644 index 2ed70492f52ca..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_17270.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET EXPLOIT Yachtcontrol Webservers RCE CVE-2019-17270", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET EXPLOIT Yachtcontrol Webservers RCE CVE-2019-17270", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2029153 or 2029152) or rule.id: (2029153 or 2029152))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-17270"], - "risk_score": 100, - "rule_id": "e6f42ad9-c024-46de-99d8-492d780cdd5e", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_1821.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_1821.json deleted file mode 100644 index 9c84f3042e86c..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_1821.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET WEB_SPECIFIC_APPS Cisco Prime Infrastruture RCE - CVE-2019-1821", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET WEB_SPECIFIC_APPS Cisco Prime Infrastruture RCE - CVE-2019-1821", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2027368) or rule.id: (2027368))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-1821"], - "risk_score": 100, - "rule_id": "5aed0105-a86a-4502-9a8b-169ee24b0c7f", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_19781.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_19781.json deleted file mode 100644 index 2ee5d4bff1cbe..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_19781.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET EXPLOIT Possible Citrix Application Delivery Controller Arbitrary Code Execution Attempt (CVE-2019-19781) M2", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET EXPLOIT Possible Citrix Application Delivery Controller Arbitrary Code Execution Attempt (CVE-2019-19781) M2", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2029206 or 2029255) or rule.id: (2029206 or 2029255))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-19781"], - "risk_score": 100, - "rule_id": "6fde4e79-bf78-4173-b395-73377e289a73", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_2618.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_2618.json deleted file mode 100644 index 7ca97786945ff..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_2618.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ATTACK [PTsecurity] Oracle Weblogic file upload RCE (CVE-2019-2618)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ATTACK [PTsecurity] Oracle Weblogic file upload RCE (CVE-2019-2618)", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (10004781) or rule.id: (10004781))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-2618"], - "risk_score": 100, - "rule_id": "7ba6a778-647c-4506-8314-8206cf31f513", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_2725.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_2725.json deleted file mode 100644 index 66a7c63c9b373..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_2725.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ATTACK [PTsecurity] Oracle Weblogic _async deserialization RCE Attempt (CVE-2019-2725)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ATTACK [PTsecurity] Oracle Weblogic _async deserialization RCE Attempt (CVE-2019-2725)", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (10004779 or 10004927) or rule.id: (10004779 or 10004927))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-2725"], - "risk_score": 100, - "rule_id": "f7879284-38e9-40d4-a471-6e1b38fd5a9f", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_3396.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_3396.json deleted file mode 100644 index b4a0f0284665d..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_3396.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET WEB_CLIENT Possible Confluence SSTI Exploitation Attempt - Leads to RCE/LFI (CVE-2019-3396)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET WEB_CLIENT Possible Confluence SSTI Exploitation Attempt - Leads to RCE/LFI (CVE-2019-3396)", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (10004699 or 2027333) or rule.id: (10004699 or 2027333))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-3396"], - "risk_score": 100, - "rule_id": "d51ce0e4-31fa-4ffb-a1a6-7f9fa386ea52", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_3929.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_3929.json deleted file mode 100644 index ae6e48baa0fa6..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_3929.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET EXPLOIT Attempted Remote Command Injection Outbound (CVE-2019-3929)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET EXPLOIT Attempted Remote Command Injection Outbound (CVE-2019-3929)", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2027451 or 2027450) or rule.id: (2027451 or 2027450))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-3929"], - "risk_score": 100, - "rule_id": "0a6fefd6-22dd-4c78-aba8-e949b04360b4", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_5533.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_5533.json deleted file mode 100644 index 42d9793336ae3..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_5533.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET EXPLOIT VMware VeloCloud Authorization Bypass (CVE-2019-5533)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET EXPLOIT VMware VeloCloud Authorization Bypass (CVE-2019-5533)", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2028928) or rule.id: (2028928))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-5533"], - "risk_score": 100, - "rule_id": "65012760-1f26-47a3-b2d3-a685d638483f", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_6340.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_6340.json deleted file mode 100644 index cd55b6be262dc..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_6340.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ATTACK [PTsecurity] Arbitrary PHP RCE in Drupal 8 < 8.5.11,8.6.10 (CVE-2019-6340)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ATTACK [PTsecurity] Arbitrary PHP RCE in Drupal 8 < 8.5.11,8.6.10 (CVE-2019-6340)", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (10004555) or rule.id: (10004555))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-6340"], - "risk_score": 100, - "rule_id": "4b2b4879-45c6-4721-b058-143f07aa474f", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_7256.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_7256.json deleted file mode 100644 index e8cfcb0cfc791..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_7256.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET EXPLOIT Linear eMerge E3 Unauthenticated Command Injection Inbound (CVE-2019-7256)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET EXPLOIT Linear eMerge E3 Unauthenticated Command Injection Inbound (CVE-2019-7256)", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2029207) or rule.id: (2029207))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-7256"], - "risk_score": 100, - "rule_id": "8ef47e09-39f5-494a-82b7-3aca4310ea96", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_9978.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_9978.json deleted file mode 100644 index 0537004ae4b2d..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_exploit_cve_2019_9978.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "ET WEB_CLIENT Attempted RCE in Wordpress Social Warfare Plugin Inbound (CVE-2019-9978", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata ET WEB_CLIENT Attempted RCE in Wordpress Social Warfare Plugin Inbound (CVE-2019-9978", - "query": "event.module:suricata and event.kind:alert and (suricata.eve.alert.signature_id: (2027315) or rule.id: (2027315))", - "references": ["https://nvd.nist.gov/vuln/detail/CVE-2019-9978"], - "risk_score": 100, - "rule_id": "6b185518-b84a-44b7-843c-01c95b5a2a83", - "severity": "high", - "threats": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0001", - "name": "initial access", - "reference": "https://attack.mitre.org/tactics/TA0001/" - }, - "techniques": [ - { - "id": "T1190", - "name": "exploit public-facing application", - "reference": "https://attack.mitre.org/techniques/T1190/" - } - ] - } - ], - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_ftp_traffic_on_unusual_port_internet_destination.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_ftp_traffic_on_unusual_port_internet_destination.json deleted file mode 100644 index 8c36a7052a720..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_ftp_traffic_on_unusual_port_internet_destination.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata FTP Traffic on Unusual Port, Internet Destination", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata FTP Traffic on Unusual Port, Internet Destination", - "query": "suricata.eve.alert.signature_id:2610005 and (event.module:suricata and event.kind:alert) and not destination.ip:10.0.0.0/8 and not destination.ip:172.16.0.0/12 and not destination.ip:192.168.0.0/16", - "risk_score": 50, - "rule_id": "b1adc850-0fe3-4dac-94d3-6f240071f83a", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_http_traffic_on_unusual_port_internet_destination.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_http_traffic_on_unusual_port_internet_destination.json deleted file mode 100644 index 72228ce121575..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_http_traffic_on_unusual_port_internet_destination.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata HTTP Traffic On Unusual Port, Internet Destination", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata HTTP Traffic On Unusual Port, Internet Destination", - "query": " suricata.eve.alert.signature_id:2610001 and (event.module:suricata and event.kind:alert) and not destination.ip:10.0.0.0/8 and not destination.ip:172.16.0.0/12 and not destination.ip:192.168.0.0/16", - "risk_score": 50, - "rule_id": "43795909-913c-419d-8355-7f2880694bec", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_imap_traffic_on_unusual_port_internet_destination.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_imap_traffic_on_unusual_port_internet_destination.json deleted file mode 100644 index 1f06fbb0a337d..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_imap_traffic_on_unusual_port_internet_destination.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata IMAP Traffic on Unusual Port, internet Destination", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata IMAP Traffic on Unusual Port, internet Destination", - "query": "suricata.eve.alert.signature_id:2610009 and (event.module:suricata and event.kind:alert) and not destination.ip:10.0.0.0/8 and not destination.ip:172.16.0.0/12 and not destination.ip:192.168.0.0/16", - "risk_score": 50, - "rule_id": "738ee70b-7d0f-438f-98ac-a393df58c58f", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_lazagne_artifact_in_an_http_post.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_lazagne_artifact_in_an_http_post.json deleted file mode 100644 index 9c2d818b88c5d..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_lazagne_artifact_in_an_http_post.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata LaZagne Artifact in an HTTP POST", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata LaZagne Artifact in an HTTP POST", - "query": "(event.module:suricata and event.kind:alert) and suricata.eve.alert.signature_id: (2610149 or 2610150)", - "risk_score": 50, - "rule_id": "c6e6f16f-66de-43d5-8ab7-599af536dedf", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_mimikatz_artifacts_in_an_http_post.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_mimikatz_artifacts_in_an_http_post.json deleted file mode 100644 index 0cbf4092bfa31..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_mimikatz_artifacts_in_an_http_post.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Mimikatz Artifacts in an HTTP POST", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Mimikatz Artifacts in an HTTP POST", - "query": "suricata.eve.alert.signature_id:2610155 and (event.module:suricata and event.kind:alert)", - "risk_score": 50, - "rule_id": "1b62e8af-c10d-4708-9a74-118cb1c9ed8a", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_mimikatz_string_detected_in_http_response.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_mimikatz_string_detected_in_http_response.json deleted file mode 100644 index 730aaa63ab07d..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_mimikatz_string_detected_in_http_response.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Mimikatz String Detected in HTTP Response", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Mimikatz String Detected in HTTP Response", - "query": "(event.module:suricata and event.kind:alert) and suricata.eve.alert.signature_id: (2610144 or 2610145 or 2610146 or 2610147 or 2610148)", - "risk_score": 50, - "rule_id": "2b365d3a-11a3-4bec-9698-b36c908f46ff", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nondns_traffic_on_tcp_port_53.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nondns_traffic_on_tcp_port_53.json deleted file mode 100644 index 96f180fee0990..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nondns_traffic_on_tcp_port_53.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata non-DNS Traffic on TCP Port 53", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata non-DNS Traffic on TCP Port 53", - "query": "suricata.eve.alert.signature_id:2610014 and (event.module:suricata and event.kind:alert)", - "risk_score": 50, - "rule_id": "67c7d28e-8be4-49ae-9c89-5c328ea245dc", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nondns_traffic_on_udp_port_53.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nondns_traffic_on_udp_port_53.json deleted file mode 100644 index 95458f14b0b2c..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nondns_traffic_on_udp_port_53.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata non-DNS Traffic on UDP Port 53", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata non-DNS Traffic on UDP Port 53", - "query": "suricata.eve.alert.signature_id:2610016 and (event.module:suricata and event.kind:alert)", - "risk_score": 50, - "rule_id": "ba6dea7f-ba98-4a86-b570-d05d85472e79", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nonftp_traffic_on_port_21.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nonftp_traffic_on_port_21.json deleted file mode 100644 index 42bcc2fa1bca1..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nonftp_traffic_on_port_21.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata non-FTP Traffic on Port 21", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata non-FTP Traffic on Port 21", - "query": "suricata.eve.alert.signature_id:2610006 and (event.module:suricata and event.kind:alert)", - "risk_score": 50, - "rule_id": "ee2b07ec-94dd-48b2-b46b-7bef47cc43fc", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nonhttp_traffic_on_tcp_port_80.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nonhttp_traffic_on_tcp_port_80.json deleted file mode 100644 index af681646e8224..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nonhttp_traffic_on_tcp_port_80.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata non-HTTP Traffic on TCP Port 80", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata non-HTTP Traffic on TCP Port 80", - "query": "suricata.eve.alert.signature_id:2610002 and (event.module:suricata and event.kind:alert) and not destination.ip:10.0.0.0/8 and not destination.ip:172.16.0.0/12 and not destination.ip:192.168.0.0/16", - "risk_score": 50, - "rule_id": "70f9bd9f-accc-4da8-8674-38992096ddba", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nonimap_traffic_on_port_1443_imap.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nonimap_traffic_on_port_1443_imap.json deleted file mode 100644 index 548b35165028c..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nonimap_traffic_on_port_1443_imap.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata non-IMAP Traffic on Port 1443 (IMAP)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata non-IMAP Traffic on Port 1443 (IMAP)", - "query": "suricata.eve.alert.signature_id:2610010 and (event.module:suricata and event.kind:alert)", - "risk_score": 50, - "rule_id": "241b6a1d-4f73-4b68-bd98-22e909681930", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nonsmb_traffic_on_tcp_port_139_smb.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nonsmb_traffic_on_tcp_port_139_smb.json deleted file mode 100644 index a7e57103c633d..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nonsmb_traffic_on_tcp_port_139_smb.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata non-SMB Traffic on TCP Port 139 (SMB)", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata non-SMB Traffic on TCP Port 139 (SMB)", - "query": "suricata.eve.alert.signature_id:2610011 and (event.module:suricata and event.kind:alert)", - "risk_score": 50, - "rule_id": "c259ab53-4b1a-42f6-b204-fe057c521515", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nonssh_traffic_on_port_22.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nonssh_traffic_on_port_22.json deleted file mode 100644 index 3e07bd7a97cb8..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nonssh_traffic_on_port_22.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata non-SSH Traffic on Port 22", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata non-SSH Traffic on Port 22", - "query": "suricata.eve.alert.signature_id:2610008 and (event.module:suricata and event.kind:alert)", - "risk_score": 50, - "rule_id": "256e9e8b-8366-4f23-8cbe-c9eb5ba25633", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nontls_on_tls_port.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nontls_on_tls_port.json deleted file mode 100644 index 16dc9f46f0e32..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_nontls_on_tls_port.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata non-TLS on TLS Port", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata non-TLS on TLS Port", - "query": "suricata.eve.alert.signature_id:2610004 and (event.module:suricata and event.kind:alert) and not destination.ip:10.0.0.0/8 and not destination.ip:172.16.0.0/12 and not destination.ip:192.168.0.0/16", - "risk_score": 50, - "rule_id": "b060c87f-af49-40eb-acee-561a1f1331aa", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_possible_cobalt_strike_malleable_c2_null_response.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_possible_cobalt_strike_malleable_c2_null_response.json deleted file mode 100644 index e8bc59f1b5268..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_possible_cobalt_strike_malleable_c2_null_response.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Possible Cobalt Strike Malleable C2 Null Response", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Possible Cobalt Strike Malleable C2 Null Response", - "query": "(event.module:suricata and event.kind:alert) and suricata.eve.alert.signature_id: (2610202 or 2610203)", - "risk_score": 50, - "rule_id": "6099a760-7293-4e26-8aa8-b984abb32ac6", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_possible_sql_injection_sql_commands_in_http_transactions.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_possible_sql_injection_sql_commands_in_http_transactions.json deleted file mode 100644 index 8b208e5586726..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_possible_sql_injection_sql_commands_in_http_transactions.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Possible SQL Injection - SQL Commands in HTTP Transactions", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Possible SQL Injection - SQL Commands in HTTP Transactions", - "query": "(event.module:suricata and event.kind:alert) and suricata.eve.alert.signature_id: (2610117 or 2610118 or 2610118 or 2610119 or 2610121 or 2610122 or 2610123)", - "risk_score": 50, - "rule_id": "cdfbcd5e-1d8e-47e6-b3f2-b09bce780640", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_rpc_traffic_on_http_ports.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_rpc_traffic_on_http_ports.json deleted file mode 100644 index fe3d500b42d3e..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_rpc_traffic_on_http_ports.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata RPC Traffic on HTTP Ports", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata RPC Traffic on HTTP Ports", - "query": "suricata.eve.alert.signature_id:2610012 and (event.module:suricata and event.kind:alert)", - "risk_score": 50, - "rule_id": "87e77fb6-b555-43be-adc5-f57c6aaf7cd0", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_serialized_php_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_serialized_php_detected.json deleted file mode 100644 index a59cc42fa4557..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_serialized_php_detected.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Serialized PHP Detected", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Serialized PHP Detected", - "query": "suricata.eve.alert.signature_id:2610091 and (event.module:suricata and event.kind:alert)", - "risk_score": 50, - "rule_id": "3baa5b65-d11e-40fb-a9b4-6b2a6a062d48", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_shell_exec_php_function_in_an_http_post.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_shell_exec_php_function_in_an_http_post.json deleted file mode 100644 index e4fd0e866e7cf..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_shell_exec_php_function_in_an_http_post.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata shell_exec PHP Function in an HTTP POST", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata shell_exec PHP Function in an HTTP POST", - "query": "suricata.eve.alert.signature_id:2610087 and (event.module:suricata and event.kind:alert)", - "risk_score": 50, - "rule_id": "082fca48-4707-485a-aedb-340ee77e0687", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_ssh_traffic_not_on_port_22_internet_destination.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_ssh_traffic_not_on_port_22_internet_destination.json deleted file mode 100644 index a22c3a4fdfdd4..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_ssh_traffic_not_on_port_22_internet_destination.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata SSH Traffic Not on Port 22, Internet Destination", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata SSH Traffic Not on Port 22, Internet Destination", - "query": "suricata.eve.alert.signature_id:2610007 and (event.module:suricata and event.kind:alert) and not destination.ip:10.0.0.0/8 and not destination.ip:172.16.0.0/12 and not destination.ip:192.168.0.0/16", - "risk_score": 50, - "rule_id": "82265eef-1212-4c4f-af04-f977a3060592", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_tls_traffic_on_unusual_port_internet_destination.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_tls_traffic_on_unusual_port_internet_destination.json deleted file mode 100644 index 23f1f79bc4248..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_tls_traffic_on_unusual_port_internet_destination.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata TLS Traffic on Unusual Port, Internet Destination", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata TLS Traffic on Unusual Port, Internet Destination", - "query": "suricata.eve.alert.signature_id:2610003 and (event.module:suricata and event.kind:alert) and not destination.ip:10.0.0.0/8 and not destination.ip:172.16.0.0/12 and not destination.ip:192.168.0.0/16", - "risk_score": 50, - "rule_id": "6c1db8ba-db4b-4513-a0e3-b3c857ba8b05", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_windows_executable_served_by_jpeg_web_content.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_windows_executable_served_by_jpeg_web_content.json deleted file mode 100644 index 9717beac902e5..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/suricata_windows_executable_served_by_jpeg_web_content.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Suricata Windows Executable Served by JPEG Web Content", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Suricata Windows Executable Served by JPEG Web Content", - "query": "suricata.eve.alert.signature_id:2610084 and (event.module:suricata and event.kind:alert)", - "risk_score": 50, - "rule_id": "f7f038f4-b97a-4d0c-b3b6-d5fa1ad15951", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_capturelosstoo_much_loss.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_capturelosstoo_much_loss.json deleted file mode 100644 index 87549a455c1d3..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_capturelosstoo_much_loss.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Detected Zeek capture loss exceeds the percentage threshold", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice CaptureLoss::Too_Much_Loss", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"CaptureLoss::Too_Much_Loss\" or rule.name: \"CaptureLoss::Too_Much_Loss\")", - "risk_score": 50, - "rule_id": "c115a407-799b-45d6-962e-a639bb764c06", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_conncontent_gap.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_conncontent_gap.json deleted file mode 100644 index 69a82f9840a93..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_conncontent_gap.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Data has sequence hole; perhaps due to filtering.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice Conn::Content_Gap", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"Conn::Content_Gap\" or rule.name: \"Conn::Content_Gap\")", - "risk_score": 50, - "rule_id": "22d12b64-33f4-40ce-ad57-49dd870bc8e5", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_connretransmission_inconsistency.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_connretransmission_inconsistency.json deleted file mode 100644 index c5ba4eb8082aa..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_connretransmission_inconsistency.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Possible evasion; usually just chud.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice Conn::Retransmission_Inconsistency", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"Conn::Retransmission_Inconsistency\" or rule.name: \"Conn::Retransmission_Inconsistency\")", - "risk_score": 50, - "rule_id": "53719624-55f0-4541-8370-f27f6766fb9e", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_dnsexternal_name.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_dnsexternal_name.json deleted file mode 100644 index cb5db1529aa0e..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_dnsexternal_name.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Raised when a non-local name is found to be pointing at a local host.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice DNS::External_Name", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"DNS::External_Name\" or rule.name: \"DNS::External_Name\")", - "risk_score": 50, - "rule_id": "39c40c5a-110c-45b1-876f-969212e8814b", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_ftpbruteforcing.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_ftpbruteforcing.json deleted file mode 100644 index 43bc1f05a2212..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_ftpbruteforcing.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicates a host bruteforcing FTP logins by watching for too many rejected usernames or failed passwords.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice FTP::Bruteforcing", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"FTP::Bruteforcing\" or rule.name: \"FTP::Bruteforcing\")", - "risk_score": 50, - "rule_id": "7e069475-817e-4e89-9245-1dfaa3083b11", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_ftpsite_exec_success.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_ftpsite_exec_success.json deleted file mode 100644 index 63b8b847563b5..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_ftpsite_exec_success.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicates that a successful response to a “SITE EXEC” command/arg pair was seen.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice FTP::Site_Exec_Success", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"FTP::Site_Exec_Success\" or rule.name: \"FTP::Site_Exec_Success\")", - "risk_score": 50, - "rule_id": "4b9cb3e9-e26a-4bd2-bd1f-8d451b49838f", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_heartbleedssl_heartbeat_attack.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_heartbleedssl_heartbeat_attack.json deleted file mode 100644 index adc8878f6986a..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_heartbleedssl_heartbeat_attack.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicates that a host performed a heartbleed attack or scan.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice Heartbleed::SSL_Heartbeat_Attack", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"Heartbleed::SSL_Heartbeat_Attack\" or rule.name: \"Heartbleed::SSL_Heartbeat_Attack\")", - "risk_score": 50, - "rule_id": "68a33102-3680-4581-a48a-210b23925905", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_heartbleedssl_heartbeat_attack_success.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_heartbleedssl_heartbeat_attack_success.json deleted file mode 100644 index 3f03e5483cc31..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_heartbleedssl_heartbeat_attack_success.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicates that a host performing a heartbleed attack was probably successful.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice Heartbleed::SSL_Heartbeat_Attack_Success", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"Heartbleed::SSL_Heartbeat_Attack_Success\" or rule.name: \"Heartbleed::SSL_Heartbeat_Attack_Success\")", - "risk_score": 50, - "rule_id": "241a61ae-b385-4f36-96c4-b2fb5446cc43", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_heartbleedssl_heartbeat_many_requests.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_heartbleedssl_heartbeat_many_requests.json deleted file mode 100644 index 2902c4a4b8e5f..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_heartbleedssl_heartbeat_many_requests.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicates we saw many heartbeat requests without a reply. Might be an attack.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice Heartbleed::SSL_Heartbeat_Many_Requests", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"Heartbleed::SSL_Heartbeat_Many_Requests\" or rule.name: \"Heartbleed::SSL_Heartbeat_Many_Requests\")", - "risk_score": 50, - "rule_id": "59d6a32c-753e-4c19-bb77-1befdc6e0e6a", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_heartbleedssl_heartbeat_odd_length.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_heartbleedssl_heartbeat_odd_length.json deleted file mode 100644 index 871999b842609..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_heartbleedssl_heartbeat_odd_length.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicates we saw heartbeat requests with odd length. Probably an attack or scan.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice Heartbleed::SSL_Heartbeat_Odd_Length", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"Heartbleed::SSL_Heartbeat_Odd_Length\" or rule.name: \"Heartbleed::SSL_Heartbeat_Odd_Length\")", - "risk_score": 50, - "rule_id": "0c6e7be4-6cab-4ee1-ad51-7c1ffd0e9002", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_httpsql_injection_attacker.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_httpsql_injection_attacker.json deleted file mode 100644 index fe6bcb8a88100..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_httpsql_injection_attacker.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicates that a host performing SQL injection attacks was detected.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice HTTP::SQL_Injection_Attacker", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"HTTP::SQL_Injection_Attacker\" or rule.name: \"HTTP::SQL_Injection_Attacker\")", - "risk_score": 50, - "rule_id": "4ca9ef93-7e7e-40a4-8d71-9130204d86e6", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_httpsql_injection_victim.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_httpsql_injection_victim.json deleted file mode 100644 index ed1f5bbaa13b2..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_httpsql_injection_victim.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicates that a host was seen to have SQL injection attacks against it. This is tracked by IP address as opposed to hostname.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice HTTP::SQL_Injection_Victim", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"HTTP::SQL_Injection_Victim\" or rule.name: \"HTTP::SQL_Injection_Victim\")", - "risk_score": 50, - "rule_id": "dda43d7f-69bc-487f-b05c-2b518e9db622", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_intelnotice.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_intelnotice.json deleted file mode 100644 index 615f3b4827656..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_intelnotice.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "This notice is generated when an intelligence indicator is denoted to be notice-worthy.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice Intel::Notice", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"Intel::Notice\" or rule.name: \"Intel::Notice\")", - "risk_score": 50, - "rule_id": "122e153a-78f3-4e7e-a5b5-cfe0b917f109", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_noticetally.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_noticetally.json deleted file mode 100644 index cbe9fd654c4f8..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_noticetally.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Zeek notice reporting a count of how often a notice occurred.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice Notice::Tally", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"Notice::Tally\" or rule.name: \"Notice::Tally\")", - "risk_score": 50, - "rule_id": "7581fd81-25e8-489e-bcf3-69db068b7a6c", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_packetfiltercannot_bpf_shunt_conn.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_packetfiltercannot_bpf_shunt_conn.json deleted file mode 100644 index 2d35d42eb07a1..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_packetfiltercannot_bpf_shunt_conn.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Limitations in BPF make shunting some connections with BPF impossible. This notice encompasses those various cases.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice PacketFilter::Cannot_BPF_Shunt_Conn", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"PacketFilter::Cannot_BPF_Shunt_Conn\" or rule.name: \"PacketFilter::Cannot_BPF_Shunt_Conn\")", - "risk_score": 50, - "rule_id": "0031d83e-1fb4-4dd6-b938-97ae7044b051", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_packetfiltercompile_failure.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_packetfiltercompile_failure.json deleted file mode 100644 index 4013b77fe6e4c..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_packetfiltercompile_failure.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "This notice is generated if a packet filter cannot be compiled.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice PacketFilter::Compile_Failure", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"PacketFilter::Compile_Failure\" or rule.name: \"PacketFilter::Compile_Failure\")", - "risk_score": 50, - "rule_id": "335b2ddc-f806-46e8-8ffa-114d613aac92", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_packetfilterdropped_packets.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_packetfilterdropped_packets.json deleted file mode 100644 index 21229e4055f48..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_packetfilterdropped_packets.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicates packets were dropped by the packet filter.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice PacketFilter::Dropped_Packets", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"PacketFilter::Dropped_Packets\" or rule.name: \"PacketFilter::Dropped_Packets\")", - "risk_score": 50, - "rule_id": "4f212278-329b-4088-ae59-9091003dff22", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_packetfilterinstall_failure.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_packetfilterinstall_failure.json deleted file mode 100644 index 6f6ff30f99b57..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_packetfilterinstall_failure.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Generated if a packet filter fails to install.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice PacketFilter::Install_Failure", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"PacketFilter::Install_Failure\" or rule.name: \"PacketFilter::Install_Failure\")", - "risk_score": 50, - "rule_id": "235988ec-d037-4f5f-a211-74106512b36d", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_packetfilterno_more_conn_shunts_available.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_packetfilterno_more_conn_shunts_available.json deleted file mode 100644 index 0785959078bb7..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_packetfilterno_more_conn_shunts_available.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicative that PacketFilter::max_bpf_shunts connections are already being shunted with BPF filters and no more are allowed.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice PacketFilter::No_More_Conn_Shunts_Available", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"PacketFilter::No_More_Conn_Shunts_Available\" or rule.name: \"PacketFilter::No_More_Conn_Shunts_Available\")", - "risk_score": 50, - "rule_id": "de4016de-3374-41a0-a678-21d36c70af9a", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_packetfiltertoo_long_to_compile_filter.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_packetfiltertoo_long_to_compile_filter.json deleted file mode 100644 index e8dbcaaeec43e..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_packetfiltertoo_long_to_compile_filter.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Generated when a notice takes too long to compile.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice PacketFilter::Too_Long_To_Compile_Filter", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"PacketFilter::Too_Long_To_Compile_Filter\" or rule.name: \"PacketFilter::Too_Long_To_Compile_Filter\")", - "risk_score": 50, - "rule_id": "71e93c42-7990-4233-a8a5-2631193df7db", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_protocoldetectorprotocol_found.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_protocoldetectorprotocol_found.json deleted file mode 100644 index 0caf01e3823c9..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_protocoldetectorprotocol_found.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicates a protocol was detected on a non-standard port.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice ProtocolDetector::Protocol_Found", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"ProtocolDetector::Protocol_Found\" or rule.name: \"ProtocolDetector::Protocol_Found\")", - "risk_score": 50, - "rule_id": "777586b6-4757-489e-a6e8-676b7df70b39", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_protocoldetectorserver_found.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_protocoldetectorserver_found.json deleted file mode 100644 index 196c9dc7241c8..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_protocoldetectorserver_found.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicates a server was detected on a non-standard port for the protocol.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice ProtocolDetector::Server_Found", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"ProtocolDetector::Server_Found\" or rule.name: \"ProtocolDetector::Server_Found\")", - "risk_score": 50, - "rule_id": "7d7f7635-6900-4f63-b14b-477a909ea90a", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_scanaddress_scan.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_scanaddress_scan.json deleted file mode 100644 index 34c8a126e424c..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_scanaddress_scan.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Address scans detect that a host appears to be scanning some number of destinations on a single port.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice Scan::Address_Scan", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"Scan::Address_Scan\" or rule.name: \"Scan::Address_Scan\")", - "risk_score": 50, - "rule_id": "9d320fca-4ec1-4511-bdbc-7edf9673c07d", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_scanport_scan.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_scanport_scan.json deleted file mode 100644 index 1334f2c08ad09..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_scanport_scan.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Port scans detect that an attacking host appears to be scanning a single victim host on several ports.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice Scan::Port_Scan", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"Scan::Port_Scan\" or rule.name: \"Scan::Port_Scan\")", - "risk_score": 50, - "rule_id": "d09fbf7a-47a7-4130-8dd7-b386cca81a42", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_signaturescount_signature.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_signaturescount_signature.json deleted file mode 100644 index 1dc25388dc688..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_signaturescount_signature.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "The same signature has triggered multiple times for a host.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice Signatures::Count_Signature", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"Signatures::Count_Signature\" or rule.name: \"Signatures::Count_Signature\")", - "risk_score": 50, - "rule_id": "a704589c-8ba9-4a3c-8e39-ab9360cade17", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_signaturesmultiple_sig_responders.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_signaturesmultiple_sig_responders.json deleted file mode 100644 index 06cf39c1c3dbd..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_signaturesmultiple_sig_responders.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Host has triggered the same signature on multiple hosts.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice Signatures::Multiple_Sig_Responders", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"Signatures::Multiple_Sig_Responders\" or rule.name: \"Signatures::Multiple_Sig_Responders\")", - "risk_score": 50, - "rule_id": "4f313ae8-cbc6-4082-9599-526f8ccb7303", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_signaturesmultiple_signatures.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_signaturesmultiple_signatures.json deleted file mode 100644 index 350e6dfc30e18..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_signaturesmultiple_signatures.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Host has triggered many signatures on the same host.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice Signatures::Multiple_Signatures", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"Signatures::Multiple_Signatures\" or rule.name: \"Signatures::Multiple_Signatures\")", - "risk_score": 50, - "rule_id": "ab90d81c-79e1-4f62-a61e-484c4bedb2b0", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_signaturessensitive_signature.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_signaturessensitive_signature.json deleted file mode 100644 index c1438edf2e4ac..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_signaturessensitive_signature.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Generic notice type for notice-worthy signature matches.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice Signatures::Sensitive_Signature", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"Signatures::Sensitive_Signature\" or rule.name: \"Signatures::Sensitive_Signature\")", - "risk_score": 50, - "rule_id": "ac394dec-67e8-417f-bb06-ae0bd75556b0", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_signaturessignature_summary.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_signaturessignature_summary.json deleted file mode 100644 index 7fd878ceb6c7f..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_signaturessignature_summary.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Summarize the number of times a host triggered a signature.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice Signatures::Signature_Summary", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"Signatures::Signature_Summary\" or rule.name: \"Signatures::Signature_Summary\")", - "risk_score": 50, - "rule_id": "d17fe857-eb67-4843-ab63-bf4852e49396", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_smtpblocklist_blocked_host.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_smtpblocklist_blocked_host.json deleted file mode 100644 index 1e2579dfd1b4e..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_smtpblocklist_blocked_host.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "The originator’s address is seen in the block list error message. This is useful to detect local hosts sending SPAM with a high positive rate.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice SMTP::Blocklist_Blocked_Host", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"SMTP::Blocklist_Blocked_Host\" or rule.name: \"SMTP::Blocklist_Blocked_Host\")", - "risk_score": 50, - "rule_id": "402d5f78-82cd-4320-8b69-3185e44daf07", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_smtpblocklist_error_message.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_smtpblocklist_error_message.json deleted file mode 100644 index ae4794bd5481f..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_smtpblocklist_error_message.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "An SMTP server sent a reply mentioning an SMTP block list.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice SMTP::Blocklist_Error_Message", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"SMTP::Blocklist_Error_Message\" or rule.name: \"SMTP::Blocklist_Error_Message\")", - "risk_score": 50, - "rule_id": "b9bb4a93-8c5c-4942-9193-e2dc97230034", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_smtpsuspicious_origination.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_smtpsuspicious_origination.json deleted file mode 100644 index ed871f4aa6898..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_smtpsuspicious_origination.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "SMTP message orignated from country or network configured to be suspicious.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice SMTP::Suspicious_Origination", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"SMTP::Suspicious_Origination\" or rule.name: \"SMTP::Suspicious_Origination\")", - "risk_score": 50, - "rule_id": "cc6e9fef-d936-4faf-8936-e576c089d8b2", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_softwaresoftware_version_change.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_softwaresoftware_version_change.json deleted file mode 100644 index 5a5cd3f48245f..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_softwaresoftware_version_change.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicates that an interesting software application changed versions on a host.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice Software::Software_Version_Change", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"Software::Software_Version_Change\" or rule.name: \"Software::Software_Version_Change\")", - "risk_score": 50, - "rule_id": "ea1d2c1b-ecfe-42a5-bd0b-56c7a1bd8075", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_softwarevulnerable_version.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_softwarevulnerable_version.json deleted file mode 100644 index 8addd5ed39562..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_softwarevulnerable_version.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicates that a vulnerable version of software was detected.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice Software::Vulnerable_Version", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"Software::Vulnerable_Version\" or rule.name: \"Software::Vulnerable_Version\")", - "risk_score": 50, - "rule_id": "97b4d80c-7671-4301-85a6-954aa0ba96ce", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sshinteresting_hostname_login.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sshinteresting_hostname_login.json deleted file mode 100644 index f69ab099bf6d9..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sshinteresting_hostname_login.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Generated if a login originates or responds with a host where the reverse hostname lookup resolves to a name matched by the SSH::interesting_hostnames regular expression.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice SSH::Interesting_Hostname_Login", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"SSH::Interesting_Hostname_Login\" or rule.name: \"SSH::Interesting_Hostname_Login\")", - "risk_score": 50, - "rule_id": "6a7f2b0a-3f24-4d58-aa84-243f1f0556d9", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sshlogin_by_password_guesser.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sshlogin_by_password_guesser.json deleted file mode 100644 index 3b12aae2f4dd8..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sshlogin_by_password_guesser.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicates that a host previously identified as a \"password guesser\" has now had a successful login attempt.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice SSH::Login_By_Password_Guesser", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"SSH::Login_By_Password_Guesser\" or rule.name: \"SSH::Login_By_Password_Guesser\")", - "risk_score": 50, - "rule_id": "5600ad95-2244-43db-8a7d-77eea95f80db", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sshpassword_guessing.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sshpassword_guessing.json deleted file mode 100644 index 4fd7e8ec15ed7..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sshpassword_guessing.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicates that a host has been identified as crossing the SSH::password_guesses_limit threshold with failed logins.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice SSH::Password_Guessing", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"SSH::Password_Guessing\" or rule.name: \"SSH::Password_Guessing\")", - "risk_score": 50, - "rule_id": "e278142a-4ee7-4443-9b1f-421174b0dabf", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sshwatched_country_login.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sshwatched_country_login.json deleted file mode 100644 index ecd57510441ae..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sshwatched_country_login.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "SSH login was seen to or from a \"watched\" country based on the SSH::watched_countries variable", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice SSH::Watched_Country_Login", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"SSH::Watched_Country_Login\" or rule.name: \"SSH::Watched_Country_Login\")", - "risk_score": 50, - "rule_id": "983f4b7e-38cd-4d7f-8be6-40447431561e", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslcertificate_expired.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslcertificate_expired.json deleted file mode 100644 index 0309896ed31ee..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslcertificate_expired.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicates that a certificate’s NotValidAfter date has lapsed and the certificate is now invalid.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice SSL::Certificate_Expired", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"SSL::Certificate_Expired\" or rule.name: \"SSL::Certificate_Expired\")", - "risk_score": 50, - "rule_id": "3981f48e-49a5-4a3e-9b44-900a0887526c", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslcertificate_expires_soon.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslcertificate_expires_soon.json deleted file mode 100644 index 8f76bdab1a7ea..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslcertificate_expires_soon.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicates that a certificate is going to expire within SSL::notify_when_cert_expiring_in.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice SSL::Certificate_Expires_Soon", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"SSL::Certificate_Expires_Soon\" or rule.name: \"SSL::Certificate_Expires_Soon\")", - "risk_score": 50, - "rule_id": "e8207172-3478-4b2c-85b7-6f13d97fff43", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslcertificate_not_valid_yet.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslcertificate_not_valid_yet.json deleted file mode 100644 index 785ba45744022..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslcertificate_not_valid_yet.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicates that a certificate’s NotValidBefore date is future dated.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice SSL::Certificate_Not_Valid_Yet", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"SSL::Certificate_Not_Valid_Yet\" or rule.name: \"SSL::Certificate_Not_Valid_Yet\")", - "risk_score": 50, - "rule_id": "45586490-99f6-4e11-8228-2229d727a3b4", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslinvalid_ocsp_response.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslinvalid_ocsp_response.json deleted file mode 100644 index 3704a1be0cd26..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslinvalid_ocsp_response.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "This indicates that the OCSP response was not deemed to be valid.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice SSL::Invalid_Ocsp_Response", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"SSL::Invalid_Ocsp_Response\" or rule.name: \"SSL::Invalid_Ocsp_Response\")", - "risk_score": 50, - "rule_id": "eb17fcbb-de22-4aa0-81aa-1c059bdd4f2b", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslinvalid_server_cert.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslinvalid_server_cert.json deleted file mode 100644 index c068a3ecf0d82..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslinvalid_server_cert.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "This notice indicates that the result of validating the certificate along with its full certificate chain was invalid.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice SSL::Invalid_Server_Cert", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"SSL::Invalid_Server_Cert\" or rule.name: \"SSL::Invalid_Server_Cert\")", - "risk_score": 50, - "rule_id": "13f51fe0-fc74-4c45-90f3-6fb1cd26ec66", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslold_version.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslold_version.json deleted file mode 100644 index 8d180115eadea..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslold_version.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicates that a server is using a potentially unsafe version", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice SSL::Old_Version", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"SSL::Old_Version\" or rule.name: \"SSL::Old_Version\")", - "risk_score": 50, - "rule_id": "260b680e-c3d6-4c03-90cd-03c86e9f8ec1", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslweak_cipher.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslweak_cipher.json deleted file mode 100644 index 602445d1463fe..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslweak_cipher.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicates that a server is using a potentially unsafe cipher", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice SSL::Weak_Cipher", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"SSL::Weak_Cipher\" or rule.name: \"SSL::Weak_Cipher\")", - "risk_score": 50, - "rule_id": "25886074-6ae1-41c0-8546-e8cf55ed1b4b", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslweak_key.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslweak_key.json deleted file mode 100644 index b88752e9b8c94..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_sslweak_key.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicates that a server is using a potentially unsafe key.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice SSL::Weak_Key", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"SSL::Weak_Key\" or rule.name: \"SSL::Weak_Key\")", - "risk_score": 50, - "rule_id": "e020f504-c0e5-4768-8e1f-1e2ec7bac961", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_teamcymrumalwarehashregistrymatch.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_teamcymrumalwarehashregistrymatch.json deleted file mode 100644 index 8a36b974dc4fc..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_teamcymrumalwarehashregistrymatch.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "The hash value of a file transferred over HTTP matched in the malware hash registry.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice TeamCymruMalwareHashRegistry::Match", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"TeamCymruMalwareHashRegistry::Match\" or rule.name: \"TeamCymruMalwareHashRegistry::Match\")", - "risk_score": 50, - "rule_id": "a130a0ba-b083-4630-b0ea-cceb80d7720b", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_traceroutedetected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_traceroutedetected.json deleted file mode 100644 index ec05000118f35..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_traceroutedetected.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Indicates that a host was seen running traceroutes.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice Traceroute::Detected", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"Traceroute::Detected\" or rule.name: \"Traceroute::Detected\")", - "risk_score": 50, - "rule_id": "aeefe077-f05d-44a7-b757-272fc51c334c", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_weirdactivity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_weirdactivity.json deleted file mode 100644 index dcc5dfcf124ca..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/zeek_notice_weirdactivity.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Generic unusual but notice-worthy weird activity.", - "enabled": false, - "filters": [], - "from": "now-6m", - "immutable": true, - "interval": "5m", - "language": "kuery", - "name": "Zeek Notice Weird::Activity", - "query": "event.module: zeek and event.dataset: zeek.notice and (zeek.notice.note: \"Weird::Activity\" or rule.name: \"Weird::Activity\")", - "risk_score": 50, - "rule_id": "d5ad39d0-8421-4f79-ad93-8ddbf7f553b3", - "severity": "low", - "to": "now", - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts index e238e6398845c..7b38794467df5 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts @@ -15,7 +15,7 @@ import { } from 'kibana/server'; import { SIGNALS_ID } from '../../../../common/constants'; import { AlertsClient } from '../../../../../alerting/server/alerts_client'; -import { ActionsClient } from '../../../../../actions/server/actions_client'; +import { ActionsClient } from '../../../../../../../plugins/actions/server'; import { RuleAlertParams, RuleTypeParams, RuleAlertParamsRest } from '../types'; import { RequestFacade } from '../../../types'; import { Alert } from '../../../../../alerting/server/types'; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_prepacked_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_prepacked_rules.ts index 0d7fb7918b67e..0120a90df58ae 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_prepacked_rules.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_prepacked_rules.ts @@ -5,7 +5,7 @@ */ import { SavedObjectsClientContract } from 'kibana/server'; -import { ActionsClient } from '../../../../../actions'; +import { ActionsClient } from '../../../../../../../plugins/actions/server'; import { AlertsClient } from '../../../../../alerting'; import { updateRules } from './update_rules'; import { PrepackagedRules } from '../types'; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules.ts index e2632791f859e..f6932fc37d85a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules.ts @@ -173,12 +173,11 @@ export const updateRules = async ({ } else { // enabled is null or undefined and we do not touch the rule } - return alertsClient.update({ id: rule.id, data: { tags: addTags( - tags, + tags != null ? tags : rule.tags, // Add tags as an update if it exists, otherwise re-use the older tags rule.params.ruleId, immutable != null ? immutable : rule.params.immutable // Add new one if it exists, otherwise re-use old one ), diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/import/multiple_ruleid_queries.ndjson b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/import/multiple_ruleid_queries.ndjson index a9de8b1e475a3..4c45ac7a1b38b 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/import/multiple_ruleid_queries.ndjson +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/import/multiple_ruleid_queries.ndjson @@ -1,3 +1,3 @@ -{"created_at":"2020-01-09T01:38:00.740Z","updated_at":"2020-01-09T01:38:00.740Z","created_by":"elastic_kibana","description":"Query with a rule_id that acts like an external id","enabled":true,"false_positives":[],"from":"now-6m","id":"6688f367-1aa2-4895-a5a8-b3701eecf57d","immutable":false,"interval":"5m","rule_id":"query-rule-id-1","language":"kuery","output_index":".siem-signals-frank-hassanabad-default","max_signals":100,"risk_score":1,"name":"Query with a rule id Number 1","query":"user.name: root or user.name: admin","references":[],"severity":"high","updated_by":"elastic_kibana","tags":[],"to":"now","type":"query","threats":[],"version":1} -{"created_at":"2020-01-09T01:38:00.745Z","updated_at":"2020-01-09T01:38:00.745Z","created_by":"elastic_kibana","description":"Query with a rule_id that acts like an external id","enabled":true,"false_positives":[],"from":"now-6m","id":"7a912444-6cfa-4c8f-83f4-2b26fb2a2ed9","immutable":false,"interval":"5m","rule_id":"query-rule-id-2","language":"kuery","output_index":".siem-signals-frank-hassanabad-default","max_signals":100,"risk_score":2,"name":"Query with a rule id Number 2","query":"user.name: root or user.name: admin","references":[],"severity":"low","updated_by":"elastic_kibana","tags":[],"to":"now","type":"query","threats":[],"version":1} +{"created_at":"2020-01-09T01:38:00.740Z","updated_at":"2020-01-09T01:38:00.740Z","created_by":"elastic_kibana","description":"Query with a rule_id that acts like an external id","enabled":true,"false_positives":[],"from":"now-6m","id":"6688f367-1aa2-4895-a5a8-b3701eecf57d","immutable":false,"interval":"5m","rule_id":"query-rule-id-1","language":"kuery","output_index":".siem-signals-frank-default","max_signals":100,"risk_score":1,"name":"Query with a rule id Number 1","query":"user.name: root or user.name: admin","references":[],"severity":"high","updated_by":"elastic_kibana","tags":[],"to":"now","type":"query","threats":[],"version":1} +{"created_at":"2020-01-09T01:38:00.745Z","updated_at":"2020-01-09T01:38:00.745Z","created_by":"elastic_kibana","description":"Query with a rule_id that acts like an external id","enabled":true,"false_positives":[],"from":"now-6m","id":"7a912444-6cfa-4c8f-83f4-2b26fb2a2ed9","immutable":false,"interval":"5m","rule_id":"query-rule-id-2","language":"kuery","output_index":".siem-signals-frank-default","max_signals":100,"risk_score":2,"name":"Query with a rule id Number 2","query":"user.name: root or user.name: admin","references":[],"severity":"low","updated_by":"elastic_kibana","tags":[],"to":"now","type":"query","threats":[],"version":1} {"exported_count":2,"missing_rules":[],"missing_rules_count":0} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_disabled.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_disabled.json index 38b3ed9f74696..f354351caa5f0 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_disabled.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_disabled.json @@ -4,8 +4,6 @@ "risk_score": 1, "severity": "high", "type": "query", - "from": "now-6m", - "to": "now", "query": "user.name: root or user.name: admin", "enabled": false } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_immutable.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_immutable.json deleted file mode 100644 index 681d66e16d0ba..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_immutable.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "Query which is immutable", - "description": "Example query which is immutable", - "risk_score": 1, - "severity": "high", - "type": "query", - "from": "now-6m", - "to": "now", - "query": "user.name: root or user.name: admin", - "immutable": true -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_lucene.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_lucene.json index ed8849831a479..03d5ab3c0b4e9 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_lucene.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_lucene.json @@ -4,8 +4,6 @@ "risk_score": 1, "severity": "high", "type": "query", - "from": "now-6m", - "to": "now", "query": "user.name: root or user.name: admin", "language": "lucene" } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_mitre_attack.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_mitre_attack.json index 721a172ce55d7..f728e3b988206 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_mitre_attack.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_mitre_attack.json @@ -4,8 +4,6 @@ "risk_score": 1, "severity": "high", "type": "query", - "from": "now-6m", - "to": "now", "query": "user.name: root or user.name: admin", "threats": [ { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_timelineid.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_timelineid.json index eb87a14e0c688..2bc4aa2275926 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_timelineid.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_timelineid.json @@ -4,8 +4,6 @@ "risk_score": 1, "severity": "high", "type": "query", - "from": "now-6m", - "to": "now", "query": "user.name: root or user.name: admin", "timeline_id": "timeline-id", "timeline_title": "timeline_title" diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_filter.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_filter.json index c754ab73ea21e..28ae121c7969a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_filter.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_filter.json @@ -5,8 +5,6 @@ "risk_score": 15, "severity": "high", "type": "query", - "from": "now-24h", - "to": "now", "query": "user.name: root or user.name: admin", "filters": [ { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_meta_data.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_meta_data.json index f9f5bf854e45c..8d86605d648ec 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_meta_data.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_meta_data.json @@ -4,8 +4,6 @@ "risk_score": 1, "severity": "high", "type": "query", - "from": "now-6m", - "to": "now", "query": "user.name: root or user.name: admin", "meta": { "whatever-you-want": { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_rule_id.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_rule_id.json index e4da196007527..3347fb0e724b3 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_rule_id.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_rule_id.json @@ -5,7 +5,5 @@ "risk_score": 1, "severity": "high", "type": "query", - "from": "now-6m", - "to": "now", "query": "user.name: root or user.name: admin" } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_tags.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_tags.json index 954f5942180d6..2c61f08d4b480 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_tags.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_tags.json @@ -5,8 +5,6 @@ "risk_score": 1, "severity": "high", "type": "query", - "from": "now-6m", - "to": "now", "query": "user.name: root or user.name: admin", "tags": ["tag_1", "tag_2"] } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/simplest_filters.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/simplest_filters.json index 61e68f886ffe7..37f0e541298b2 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/simplest_filters.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/simplest_filters.json @@ -4,8 +4,6 @@ "risk_score": 1, "severity": "high", "type": "query", - "from": "now-6m", - "to": "now", "filters": [ { "query": { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/simplest_query.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/simplest_query.json index e812b031a28fd..407fa1dcc0884 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/simplest_query.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/simplest_query.json @@ -4,7 +4,5 @@ "risk_score": 1, "severity": "high", "type": "query", - "from": "now-6m", - "to": "now", "query": "user.name: root or user.name: admin" } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_by_rule_id.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_by_rule_id.json index 0e0be24c00207..48b6a34cf2316 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_by_rule_id.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_by_rule_id.json @@ -5,7 +5,5 @@ "risk_score": 5, "severity": "high", "type": "saved_query", - "from": "now-6m", - "to": "now", "saved_id": "test-saved-id" } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_filters.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_filters.json index 55f95e9644b8b..2bb9845a507c3 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_filters.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_filters.json @@ -4,8 +4,6 @@ "risk_score": 1, "severity": "high", "type": "saved_query", - "from": "now-6m", - "to": "now", "filters": [ { "query": { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_query.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_query.json index ee37c4cb784d1..786dcdb377a68 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_query.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_query.json @@ -4,8 +4,6 @@ "risk_score": 1, "severity": "high", "type": "saved_query", - "from": "now-6m", - "to": "now", "query": "user.name: root or user.name: admin", "saved_id": "test-saved-id" } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_query_filter.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_query_filter.json index 19801e7a98ac2..5e5c51d2243b0 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_query_filter.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_query_filter.json @@ -5,8 +5,6 @@ "risk_score": 15, "severity": "high", "type": "query", - "from": "now-24h", - "to": "now", "query": "user.name: root or user.name: admin", "filters": [ { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/simplest_saved_query.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/simplest_saved_query.json index a3dbf0f1b09af..ac9f224313b23 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/simplest_saved_query.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/simplest_saved_query.json @@ -4,7 +4,5 @@ "risk_score": 5, "severity": "high", "type": "saved_query", - "from": "now-6m", - "to": "now", "saved_id": "test-saved-id" } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/README.md b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/README.md index 8b6508c64dc5c..38139f783c245 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/README.md +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/README.md @@ -4,15 +4,3 @@ use these type of rule based messages when writing pure REST API calls. These me more of what you would see "behind the scenes" when you are using Kibana UI which can create rules with additional "meta" data or other implementation details that aren't really a concern for a regular REST API user. - -To post all of them to see in the UI, with the scripts folder as your current working directory: - -```sh -./post_rule.sh ./rules/test_cases/*.json -``` - -To post only one at a time: - -```sh -./post_rule.sh ./rules/test_cases/.json -``` diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/multiple_ruleid_queries_corrupted.ndjson b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/imports/multiple_ruleid_queries_corrupted.ndjson similarity index 55% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/multiple_ruleid_queries_corrupted.ndjson rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/imports/multiple_ruleid_queries_corrupted.ndjson index 94fc36ef6f7bf..744bd1e078a41 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/multiple_ruleid_queries_corrupted.ndjson +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/imports/multiple_ruleid_queries_corrupted.ndjson @@ -1,4 +1,4 @@ -{"created_at":"2020-01-09T01:38:00.740Z","updated_at":"2020-01-09T01:38:00.740Z","created_by":"elastic_kibana","description":"Query with a rule_id that acts like an external id","enabled":true,"false_positives":[],"from":"now-6m","id":"6688f367-1aa2-4895-a5a8-b3701eecf57d","immutable":false,"interval":"5m","rule_id":"query-rule-id-1","language":"kuery","output_index":".siem-signals-frank-hassanabad-default","max_signals":100,"risk_score":1,"name":"Query with a rule id Number 1","query":"user.name: root or user.name: admin","references":[],"severity":"high","updated_by":"elastic_kibana","tags":[],"to":"now","type":"query","threats":[],"version":1}, -{"created_at":"2020-01-09T01:38:00.745Z","updated_at":"2020-01-09T01:38:00.745Z","created_by":"elastic_kibana","enabled":true,"false_positives":[],"from":"now-6m","id":"7a912444-6cfa-4c8f-83f4-2b26fb2a2ed9","immutable":false,"interval":"5m","rule_id":"query-rule-id-2","language":"kuery","output_index":".siem-signals-frank-hassanabad-default","max_signals":100,"risk_score":2,"name":"Query with a rule id Number 2","query":"user.name: root or user.name: admin","references":[],"severity":"low","updated_by":"elastic_kibana","tags":[],"to":"now","type":"query","threats":[],"version":1} -{"created_at":"2020-01-09T01:38:00.745Z","updated_at":"2020-01-09T01:38:00.745Z","created_by":"elastic_kibana","description":"Query with a rule_id that acts like an external id","enabled":true,"false_positives":[],"from":"now-6m","id":"7a912444-6cfa-4c8f-83f4-2b26fb2a2ed9","immutable":false,"interval":"5m","rule_id":"query-rule-id-3","language":"kuery","output_index":".siem-signals-frank-hassanabad-default","max_signals":100,"risk_score":2,"name":"Query with a rule id Number 2","query":"user.name: root or user.name: admin","references":[],"severity":"low","updated_by":"elastic_kibana","tags":[],"to":"now","type":"query","threats":[],"version":1} +{"created_at":"2020-01-09T01:38:00.740Z","updated_at":"2020-01-09T01:38:00.740Z","created_by":"elastic_kibana","description":"Query with a rule_id that acts like an external id","enabled":true,"false_positives":[],"from":"now-6m","id":"6688f367-1aa2-4895-a5a8-b3701eecf57d","immutable":false,"interval":"5m","rule_id":"query-rule-id-1","language":"kuery","output_index":".siem-signals-default","max_signals":100,"risk_score":1,"name":"Query with a rule id Number 1","query":"user.name: root or user.name: admin","references":[],"severity":"high","updated_by":"elastic_kibana","tags":[],"to":"now","type":"query","threats":[],"version":1}, +{"created_at":"2020-01-09T01:38:00.745Z","updated_at":"2020-01-09T01:38:00.745Z","created_by":"elastic_kibana","enabled":true,"false_positives":[],"from":"now-6m","id":"7a912444-6cfa-4c8f-83f4-2b26fb2a2ed9","immutable":false,"interval":"5m","rule_id":"query-rule-id-2","language":"kuery","output_index":".siem-signals-default","max_signals":100,"risk_score":2,"name":"Query with a rule id Number 2","query":"user.name: root or user.name: admin","references":[],"severity":"low","updated_by":"elastic_kibana","tags":[],"to":"now","type":"query","threats":[],"version":1} +{"created_at":"2020-01-09T01:38:00.745Z","updated_at":"2020-01-09T01:38:00.745Z","created_by":"elastic_kibana","description":"Query with a rule_id that acts like an external id","enabled":true,"false_positives":[],"from":"now-6m","id":"7a912444-6cfa-4c8f-83f4-2b26fb2a2ed9","immutable":false,"interval":"5m","rule_id":"query-rule-id-3","language":"kuery","output_index":".siem-signals-default","max_signals":100,"risk_score":2,"name":"Query with a rule id Number 2","query":"user.name: root or user.name: admin","references":[],"severity":"low","updated_by":"elastic_kibana","tags":[],"to":"now","type":"query","threats":[],"version":1} {"exported_count":2,"missing_rules":[],"missing_rules_count":0} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/filter_with_empty_query.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/queries/filter_with_empty_query.json similarity index 100% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/filter_with_empty_query.json rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/queries/filter_with_empty_query.json diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/filter_without_query.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/queries/filter_without_query.json similarity index 100% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/filter_without_query.json rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/queries/filter_without_query.json diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/query_filter_ui_meatadata_lucene.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/queries/query_filter_ui_meatadata_lucene.json similarity index 100% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/query_filter_ui_meatadata_lucene.json rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/queries/query_filter_ui_meatadata_lucene.json diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/query_filter_ui_metadata.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/queries/query_filter_ui_metadata.json similarity index 100% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/query_filter_ui_metadata.json rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/queries/query_filter_ui_metadata.json diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/query_with_errors.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/queries/query_with_errors.json similarity index 100% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/query_with_errors.json rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/queries/query_with_errors.json diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/saved_query_ui_meta_empty_query.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/queries/saved_query_ui_meta_empty_query.json similarity index 100% rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/saved_query_ui_meta_empty_query.json rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/queries/saved_query_ui_meta_empty_query.json diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/depth_test/README.md b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/depth_test/README.md new file mode 100644 index 0000000000000..ff3e9a8cf0948 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/depth_test/README.md @@ -0,0 +1,367 @@ +This is a depth test which allows users and UI's to create "funnels" of information. You can funnel your data into smaller +and smaller data sets using this. For example, you might have 1,000k of events but generate only 100k of signals off +of those events. However, you then want to generate signals on top of signals that are only 10k. Likewise you might want +signals on top of signals on top of signals to generate only 1k. + +``` +events from indexes might be 1,000k (no depth) +signals -> events would be less such as 100k +signals -> signals -> events would be even less (such as 10k) +signals -> signals -> events would be even less (such as 1k) +``` + +This folder contains a rule called + +```sh +query_single_id.json +``` + +which will write a single signal document into the signals index by searching for a single document `"query": "_id: o8G7vm8BvLT8jmu5B1-M"` . Then another rule called + +```sh +signal_on_signal_depth_1.json +``` + +which has this key part of its query: `"query": "signal.parent.depth: 1 and _id: *"` which will only create signals +from all signals that point directly to an event (signal -> event). + +Then a second rule called + +```sh +signal_on_signal_depth_2.json +``` + +which will only create signals from all signals that point directly to another signal (signal -> signal) with this query + +```json +"query": "signal.parent.depth: 2 and _id: *" +``` + +## Setup + +You should first get a valid `_id` from the system from the last 24 hours by running any query within timeline +or in the system and copying its `_id`. Once you have that `_id` add it to `query_single_id.json`. For example if you have found an `_id` +in the last 24 hours of `sQevtW8BvLT8jmu5l0TA` add it to `query_single_id.json` under the key `query` like so: + +```json +"query": "_id: sQevtW8BvLT8jmu5l0TA", +``` + +Then get your current signal index: + +```json +./get_signal_index.sh +{ + "name": ".siem-signals-default" +} +``` + +And edit the `signal_on_signal.json` and add that index to the key of `index` so we are running that rule against the signals index: + +```json +"index": ".siem-signals-default" +``` + +Next you want to clear out all of your signals and all rules: + +```sh +./hard_reset.sh +``` + +Finally, insert and start the first the query like so: + +```sh +./post_rule.sh ./rules/test_cases/signals_on_signals/depth_test/query_single_id.json +``` + +Wait 30+ seconds to ensure that the single record shows up in your signals index. You can use dev tools in Kibana +to see this by first getting your configured signals index by running: + +```ts +./get_signal_index.sh +{ + "name": ".siem-signals-default" +} +``` + +And then you can query against that: + +```ts +GET .siem-signals-default/_search +``` + +Check your parent section of the signal and you will see something like this: + +```json +"parent" : { + "rule" : "74e0dd0c-4609-416f-b65e-90f8b2564612", + "id" : "o8G7vm8BvLT8jmu5B1-M", + "type" : "event", + "index" : "filebeat-8.0.0-2019.12.18-000001", + "depth" : 1 +}, +"ancestors" : [ + { + "rule" : "74e0dd0c-4609-416f-b65e-90f8b2564612", + "id" : "o8G7vm8BvLT8jmu5B1-M", + "type" : "event", + "index" : "filebeat-8.0.0-2019.12.18-000001", + "depth" : 1 + } +] +``` + +The parent and ancestors structure is defined as: + +``` +rule -> The id of the rule. You can view the rule by ./get_rule_by_rule_id.sh ded57b36-9c4e-4ee4-805d-be4e92033e41 +id -> The original _id of the document +type -> The type of the document, it will be either event or signal +index -> The original location of the index +depth -> The depth of this signal. It will be at least 1 to indicate it is a signal generated from a event. Otherwise 2 or more to indicate a signal on signal and what depth we are at +ancestors -> An array tracking all of the parents of this particular signal. As depth increases this will too. +``` + +This is indicating that you have a single parent of an event from the signal (signal -> event) and this document has a single +ancestor of that event. Each 30 seconds that goes it will use de-duplication techniques to ensure that this signal is not re-inserted. If after +each 30 seconds you DO SEE multiple signals then the bug is a de-duplication bug and a critical bug. If you ever see a duplicate rule in the +ancestors array then that is another CRITICAL bug which needs to be fixed. + +After this is ensured, the next step is to run a single signal on top of a signal by posting once + +```sh +./post_rule.sh ./rules/test_cases/signals_on_signals/depth_test/signal_on_signal_depth_1.json +``` + +Notice in `signal_on_signal_depth_1.json` we do NOT have a `rule_id` set. This is intentional and is to make it so we can test N rules +running in the system which are generating signals on top of signals. After 30 seconds have gone by you should see that you now have two +documents in the signals index. The first signal is our original (signal -> event) document with a rule id: + +```json +"parent" : { + "rule" : "74e0dd0c-4609-416f-b65e-90f8b2564612", + "id" : "o8G7vm8BvLT8jmu5B1-M", + "type" : "event", + "index" : "filebeat-8.0.0-2019.12.18-000001", + "depth" : 1 +}, +"ancestors" : [ + { + "rule" : "74e0dd0c-4609-416f-b65e-90f8b2564612", + "id" : "o8G7vm8BvLT8jmu5B1-M", + "type" : "event", + "index" : "filebeat-8.0.0-2019.12.18-000001", + "depth" : 1 + } +] +``` + +and the second document is a signal on top of a signal like so: + +```json +"parent" : { + "rule" : "1d3b3735-66ef-4e53-b7f5-4340026cc40c", + "id" : "4cc69c1cbecdd2ace4075fd1d8a5c28e7d46e4bf31aecc8d2da39252c50c96b4", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 2 +}, +"ancestors" : [ + { + "rule" : "74e0dd0c-4609-416f-b65e-90f8b2564612", + "id" : "o8G7vm8BvLT8jmu5B1-M", + "type" : "event", + "index" : "filebeat-8.0.0-2019.12.18-000001", + "depth" : 1 + }, + { + "rule" : "1d3b3735-66ef-4e53-b7f5-4340026cc40c", + "id" : "4cc69c1cbecdd2ace4075fd1d8a5c28e7d46e4bf31aecc8d2da39252c50c96b4", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 2 + } +] +``` + +Notice that the depth indicates it is at level 2 and its parent is that of a signal. Also notice that the ancestors is an array of size 2 +indicating that this signal terminates at an event. Each and every signal ancestors array should terminate at an event and should ONLY contain 1 +event and NEVER 2 or more events. After 30+ seconds you should NOT see any new documents being created and you should be stable +at 2. Otherwise we have AND/OR a de-duplication issue, signal on signal issue. + +Now, post this same rule a second time as a second instance which is going to run against these two documents. + +```sh +./post_rule.sh ./rules/test_cases/signals_on_signals/depth_test/signal_on_signal_depth_1.json +``` + +If you were to look at the number of rules you have: + +```sh +./find_rules.sh +``` + +You should see that you have 3 rules running concurrently at this point. Write down the `id` to keep track of them + +- 1 event rule which is always finding the same event continuously (id: 74e0dd0c-4609-416f-b65e-90f8b2564612) +- 1 signal rule which is finding ALL signals at depth 1 (id: 1d3b3735-66ef-4e53-b7f5-4340026cc40c) +- 1 signal rule which is finding ALL signals at depth 1 (id: c93ddb57-e7e9-4973-9886-72ddefb4d22e) + +The expected behavior is that eventually you will get 3 total documents but not additional ones after 1+ minutes. These will be: + +The original event rule 74e0dd0c-4609-416f-b65e-90f8b2564612 (event -> signal) + +```json +"parent" : { + "rule" : "74e0dd0c-4609-416f-b65e-90f8b2564612", + "id" : "o8G7vm8BvLT8jmu5B1-M", + "type" : "event", + "index" : "filebeat-8.0.0-2019.12.18-000001", + "depth" : 1 +}, +"ancestors" : [ + { + "rule" : "74e0dd0c-4609-416f-b65e-90f8b2564612", + "id" : "o8G7vm8BvLT8jmu5B1-M", + "type" : "event", + "index" : "filebeat-8.0.0-2019.12.18-000001", + "depth" : 1 + } +] +``` + +The first signal to signal rule 1d3b3735-66ef-4e53-b7f5-4340026cc40c (signal -> event) + +```json +"parent" : { + "rule" : "1d3b3735-66ef-4e53-b7f5-4340026cc40c", + "id" : "4cc69c1cbecdd2ace4075fd1d8a5c28e7d46e4bf31aecc8d2da39252c50c96b4", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 2 +}, +"ancestors" : [ + { + "rule" : "74e0dd0c-4609-416f-b65e-90f8b2564612", + "id" : "o8G7vm8BvLT8jmu5B1-M", + "type" : "event", + "index" : "filebeat-8.0.0-2019.12.18-000001", + "depth" : 1 + }, + { + "rule" : "1d3b3735-66ef-4e53-b7f5-4340026cc40c", + "id" : "4cc69c1cbecdd2ace4075fd1d8a5c28e7d46e4bf31aecc8d2da39252c50c96b4", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 2 + } +] +``` + +Then our second signal to signal rule c93ddb57-e7e9-4973-9886-72ddefb4d22e (signal -> event) which finds the same thing as the first +signal to signal + +```json +"parent" : { + "rule" : "c93ddb57-e7e9-4973-9886-72ddefb4d22e", + "id" : "4cc69c1cbecdd2ace4075fd1d8a5c28e7d46e4bf31aecc8d2da39252c50c96b4", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 2 +}, +"ancestors" : [ + { + "rule" : "74e0dd0c-4609-416f-b65e-90f8b2564612", + "id" : "o8G7vm8BvLT8jmu5B1-M", + "type" : "event", + "index" : "filebeat-8.0.0-2019.12.18-000001", + "depth" : 1 + }, + { + "rule" : "c93ddb57-e7e9-4973-9886-72ddefb4d22e", + "id" : "4cc69c1cbecdd2ace4075fd1d8a5c28e7d46e4bf31aecc8d2da39252c50c96b4", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 2 + } +] +``` + +We should be able to post this depth level as many times as we want and get only 1 new document each time. If we decide though to +post `signal_on_signal_depth_2.json` like so: + +```sh +./post_rule.sh ./rules/test_cases/signals_on_signals/depth_test/signal_on_signal_depth_2.json +``` + +The expectation is that a document for each of the previous depth 1 documents would be produced. Since we have 2 instances of +depth 1 rules running then the signals at depth 2 will produce two new ones and those two will look like so: + +```json +"parent" : { + "rule" : "a1f7b520-5bfd-451d-af59-428f60753fee", + "id" : "365236ce5e77770508152403b4e16613f407ae4b1a135a450dcfec427f2a3231", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 3 +}, +"ancestors" : [ + { + "rule" : "74e0dd0c-4609-416f-b65e-90f8b2564612", + "id" : "o8G7vm8BvLT8jmu5B1-M", + "type" : "event", + "index" : "filebeat-8.0.0-2019.12.18-000001", + "depth" : 1 + }, + { + "rule" : "1d3b3735-66ef-4e53-b7f5-4340026cc40c", + "id" : "4cc69c1cbecdd2ace4075fd1d8a5c28e7d46e4bf31aecc8d2da39252c50c96b4", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 2 + }, + { + "rule" : "a1f7b520-5bfd-451d-af59-428f60753fee", + "id" : "365236ce5e77770508152403b4e16613f407ae4b1a135a450dcfec427f2a3231", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 3 + } +] +``` + +```json +"parent" : { + "rule" : "a1f7b520-5bfd-451d-af59-428f60753fee", + "id" : "e8b1f1adb40fd642fa524dea89ef94232e67b05e99fb0b2683f1e47e90b759fb", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 3 +}, +"ancestors" : [ + { + "rule" : "74e0dd0c-4609-416f-b65e-90f8b2564612", + "id" : "o8G7vm8BvLT8jmu5B1-M", + "type" : "event", + "index" : "filebeat-8.0.0-2019.12.18-000001", + "depth" : 1 + }, + { + "rule" : "c93ddb57-e7e9-4973-9886-72ddefb4d22e", + "id" : "4cc69c1cbecdd2ace4075fd1d8a5c28e7d46e4bf31aecc8d2da39252c50c96b4", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 2 + }, + { + "rule" : "a1f7b520-5bfd-451d-af59-428f60753fee", + "id" : "e8b1f1adb40fd642fa524dea89ef94232e67b05e99fb0b2683f1e47e90b759fb", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 3 + } +] +``` + +The total number of documents should be 5 at this point. If you were to post this same rule a second time to get a second instance +running you will end up with 7 documents as it will only re-report the first 2 and not interfere with the other rules. diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/depth_test/query_single_id.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/depth_test/query_single_id.json new file mode 100644 index 0000000000000..dc05c656d7cf1 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/depth_test/query_single_id.json @@ -0,0 +1,12 @@ +{ + "name": "Queries single id", + "description": "Finds only one id below to create a single signal. Change the query to your exact _id you want to test with", + "risk_score": 1, + "severity": "high", + "type": "query", + "from": "now-1d", + "interval": "30s", + "to": "now", + "query": "_id: o8G7vm8BvLT8jmu5B1-M", + "enabled": true +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/depth_test/signal_on_signal_depth_1.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/depth_test/signal_on_signal_depth_1.json new file mode 100644 index 0000000000000..fb13413a02791 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/depth_test/signal_on_signal_depth_1.json @@ -0,0 +1,13 @@ +{ + "name": "Signal on Signals Rule 1 Depth 1", + "description": "Example Signal on Signal where it reports everything as a signal at depth 1", + "risk_score": 1, + "severity": "high", + "type": "query", + "from": "now-1d", + "interval": "30s", + "to": "now", + "query": "signal.parent.depth: 1 and _id: *", + "enabled": true, + "index": ".siem-signals-default" +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/depth_test/signal_on_signal_depth_2.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/depth_test/signal_on_signal_depth_2.json new file mode 100644 index 0000000000000..c1b7594653ec7 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/depth_test/signal_on_signal_depth_2.json @@ -0,0 +1,13 @@ +{ + "name": "Signal on Signals Rule 1 Depth 2", + "description": "Example Signal on Signal where it reports everything as a signal at Depth 2", + "risk_score": 1, + "severity": "high", + "type": "query", + "from": "now-1d", + "interval": "30s", + "to": "now", + "query": "signal.parent.depth: 2 and _id: *", + "enabled": true, + "index": ".siem-signals-default" +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/halting_test/README.md b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/halting_test/README.md new file mode 100644 index 0000000000000..7895e579de3a6 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/halting_test/README.md @@ -0,0 +1,375 @@ +This test is to ensure that signals will "halt" eventually when they are run against themselves. This isn't how anyone should setup +signals on signals but rather how we will eventually "halt" given the worst case situations where users are running signals on top of signals +that are duplicates of each other and going very far back in time. + +It contains a rule called + +```sh +query_single_id.json +``` + +which will write a single signal document into the signals index by searching for a single document `"query": "_id: o8G7vm8BvLT8jmu5B1-M"` . Then another rule called + +```sh +signal_on_signal.json +``` + +which will always generate a signal for EVERY single document it sees `"query": "_id: *"` + +## Setup + +You should first get a valid `_id` from the system from the last 24 hours by running any query within timeline +or in the system and copying its `_id`. Once you have that `_id` add it to `query_single_id.json`. For example if you have found an `_id` +in the last 24 hours of `sQevtW8BvLT8jmu5l0TA` add it to `query_single_id.json` under the key `query` like so: + +```json +"query": "_id: sQevtW8BvLT8jmu5l0TA", +``` + +Then get your current signal index: + +```json +./get_signal_index.sh +{ + "name": ".siem-signals-default" +} +``` + +And edit the `signal_on_signal.json` and add that index to the key of `index` so we are running that rule against the signals index: + +```json +"index": ".siem-signals-default" +``` + +Next you want to clear out all of your signals and all rules: + +```sh +./hard_reset.sh +``` + +Finally, insert and start the first the query like so: + +```sh +./post_rule.sh ./rules/test_cases/signals_on_signals/halting_test/query_single_id.json +``` + +Wait 30+ seconds to ensure that the single record shows up in your signals index. You can use dev tools in Kibana +to see this by first getting your configured signals index by running: + +```ts +./get_signal_index.sh +{ + "name": ".siem-signals-default" +} +``` + +And then you can query against that: + +```ts +GET .siem-signals-default/_search +``` + +Check your parent section of the signal and you will see something like this: + +```json +"parent" : { + "rule" : "ded57b36-9c4e-4ee4-805d-be4e92033e41", + "id" : "o8G7vm8BvLT8jmu5B1-M", + "type" : "event", + "index" : "filebeat-8.0.0-2019.12.18-000001", + "depth" : 1 +}, +"ancestors" : [ + { + "rule" : "ded57b36-9c4e-4ee4-805d-be4e92033e41", + "id" : "o8G7vm8BvLT8jmu5B1-M", + "type" : "event", + "index" : "filebeat-8.0.0-2019.12.18-000001", + "depth" : 1 + } +] +``` + +The parent and ancestors structure is defined as: + +``` +rule -> The id of the rule. You can view the rule by ./get_rule_by_rule_id.sh ded57b36-9c4e-4ee4-805d-be4e92033e41 +id -> The original _id of the document +type -> The type of the document, it will be either event or signal +index -> The original location of the index +depth -> The depth of this signal. It will be at least 1 to indicate it is a signal generated from a event. Otherwise 2 or more to indicate a signal on signal and what depth we are at +ancestors -> An array tracking all of the parents of this particular signal. As depth increases this will too. +``` + +This is indicating that you have a single parent of an event from the signal (signal -> event) and this document has a single +ancestor of that event. Each 30 seconds that goes it will use de-duplication techniques to ensure that this signal is not re-inserted. If after +each 30 seconds you DO SEE multiple signals then the bug is a de-duplication bug and a critical bug. If you ever see a duplicate rule in the +ancestors array then that is another CRITICAL bug which needs to be fixed. + +After this is ensured, the next step is to run a single signal on top of a signal by posting once + +```sh +./post_rule.sh ./rules/test_cases/signals_on_signals/halting_test/signal_on_signal.json +``` + +Notice in `signal_on_signal.json` we do NOT have a `rule_id` set. This is intentional and is to make it so we can test N rules +running in the system which are generating signals on top of signals. After 30 seconds have gone by you should see that you now have two +documents in the signals index. The first signal is our original (signal -> event) document with a rule id: + +(signal -> event) + +```json +"parent" : { + "rule" : "ded57b36-9c4e-4ee4-805d-be4e92033e41", + "id" : "o8G7vm8BvLT8jmu5B1-M", + "type" : "event", + "index" : "filebeat-8.0.0-2019.12.18-000001", + "depth" : 1 +}, +"ancestors" : [ + { + "rule" : "ded57b36-9c4e-4ee4-805d-be4e92033e41", + "id" : "o8G7vm8BvLT8jmu5B1-M", + "type" : "event", + "index" : "filebeat-8.0.0-2019.12.18-000001", + "depth" : 1 + } +] +``` + +and the second document is a signal on top of a signal like so: + +(signal -> signal -> event) + +```json +"parent" : { + "rule" : "161fa5b8-0b96-4985-b066-0d99b2bcb904", + "id" : "9d8710925adbf1a9c469621805407e74334dd08ca2c2ea414840fe971a571938", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 2 +}, +"ancestors" : [ + { + "rule" : "ded57b36-9c4e-4ee4-805d-be4e92033e41", + "id" : "o8G7vm8BvLT8jmu5B1-M", + "type" : "event", + "index" : "filebeat-8.0.0-2019.12.18-000001", + "depth" : 1 + }, + { + "rule" : "161fa5b8-0b96-4985-b066-0d99b2bcb904", + "id" : "9d8710925adbf1a9c469621805407e74334dd08ca2c2ea414840fe971a571938", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 2 + } +] +``` + +Notice that the depth indicates it is at level 2 and its parent is that of a signal. Also notice that the ancestors is an array of size 2 +indicating that this signal terminates at an event. Each and every signal ancestors array should terminate at an event and should ONLY contain 1 +event and NEVER 2 or more events. After 30+ seconds you should NOT see any new documents being created and you should be stable +at 2. Otherwise we have AND/OR a de-duplication issue, signal on signal issue. + +Now, post a second signal that is going to run against these two documents. + +```sh +./post_rule.sh ./rules/test_cases/signals_on_signals/halting_test/signal_on_signal.json +``` + +If you were to look at the number of rules you have: + +```sh +./find_rules.sh +``` + +You should see that you have 3 rules running concurrently at this point. Write down the `id` to keep track of them + +- 1 event rule which is always finding the same event continuously (id: ded57b36-9c4e-4ee4-805d-be4e92033e41) +- 1 signal rule which is finding ALL signals (id: 161fa5b8-0b96-4985-b066-0d99b2bcb904) +- 1 signal rule which is finding ALL signals (id: f2b70c4a-4d8f-4db5-9ed7-d3ab0630e406) + +The expected behavior is that eventually you will get 5 total documents but not additional ones after 1+ minutes. These will be: + +The original event rule ded57b36-9c4e-4ee4-805d-be4e92033e41 (event -> signal) + +```json +"parent" : { + "rule" : "ded57b36-9c4e-4ee4-805d-be4e92033e41", + "id" : "o8G7vm8BvLT8jmu5B1-M", + "type" : "event", + "index" : "filebeat-8.0.0-2019.12.18-000001", + "depth" : 1 +}, +"ancestors" : [ + { + "rule" : "ded57b36-9c4e-4ee4-805d-be4e92033e41", + "id" : "o8G7vm8BvLT8jmu5B1-M", + "type" : "event", + "index" : "filebeat-8.0.0-2019.12.18-000001", + "depth" : 1 + } +] +``` + +The first signal to signal rule 161fa5b8-0b96-4985-b066-0d99b2bcb904 (signal -> event) + +```json +"parent" : { + "rule" : "161fa5b8-0b96-4985-b066-0d99b2bcb904", + "id" : "9d8710925adbf1a9c469621805407e74334dd08ca2c2ea414840fe971a571938", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 2 +}, +"ancestors" : [ + { + "rule" : "ded57b36-9c4e-4ee4-805d-be4e92033e41", + "id" : "o8G7vm8BvLT8jmu5B1-M", + "type" : "event", + "index" : "filebeat-8.0.0-2019.12.18-000001", + "depth" : 1 + }, + { + "rule" : "161fa5b8-0b96-4985-b066-0d99b2bcb904", + "id" : "9d8710925adbf1a9c469621805407e74334dd08ca2c2ea414840fe971a571938", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 2 + } +] +``` + +Then our second signal to signal rule f2b70c4a-4d8f-4db5-9ed7-d3ab0630e406 (signal -> event) which finds the same thing as the first +signal to signal + +```json +"parent" : { + "rule" : "f2b70c4a-4d8f-4db5-9ed7-d3ab0630e406", + "id" : "9d8710925adbf1a9c469621805407e74334dd08ca2c2ea414840fe971a571938", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 2 +}, +"ancestors" : [ + { + "rule" : "ded57b36-9c4e-4ee4-805d-be4e92033e41", + "id" : "o8G7vm8BvLT8jmu5B1-M", + "type" : "event", + "index" : "filebeat-8.0.0-2019.12.18-000001", + "depth" : 1 + }, + { + "rule" : "f2b70c4a-4d8f-4db5-9ed7-d3ab0630e406", + "id" : "9d8710925adbf1a9c469621805407e74334dd08ca2c2ea414840fe971a571938", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 2 + } +] +``` + +But then f2b70c4a-4d8f-4db5-9ed7-d3ab0630e406 also finds the first signal to signal rule from 161fa5b8-0b96-4985-b066-0d99b2bcb904 +and writes that document out with a depth of 3. (signal -> signal -> event) + +```json +"parent" : { + "rule" : "f2b70c4a-4d8f-4db5-9ed7-d3ab0630e406", + "id" : "c627e5e2576f1b10952c6c57249947e89b6153b763a59fb9e391d0b56be8e7fe", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 3 +}, +"ancestors" : [ + { + "rule" : "ded57b36-9c4e-4ee4-805d-be4e92033e41", + "id" : "o8G7vm8BvLT8jmu5B1-M", + "type" : "event", + "index" : "filebeat-8.0.0-2019.12.18-000001", + "depth" : 1 + }, + { + "rule" : "161fa5b8-0b96-4985-b066-0d99b2bcb904", + "id" : "9d8710925adbf1a9c469621805407e74334dd08ca2c2ea414840fe971a571938", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 2 + }, + { + "rule" : "f2b70c4a-4d8f-4db5-9ed7-d3ab0630e406", + "id" : "c627e5e2576f1b10952c6c57249947e89b6153b763a59fb9e391d0b56be8e7fe", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 3 + } +] +``` + +Since it wrote that document, the first signal to signal 161fa5b8-0b96-4985-b066-0d99b2bcb904 writes out it found this newly created signal +(signal -> signal -> event) + +```json +"parent" : { + "rule" : "161fa5b8-0b96-4985-b066-0d99b2bcb904", + "id" : "efbe514e8d806a5ef3da7658cfa73961e25befefc84f622e963b45dcac798868", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 3 +}, +"ancestors" : [ + { + "rule" : "ded57b36-9c4e-4ee4-805d-be4e92033e41", + "id" : "o8G7vm8BvLT8jmu5B1-M", + "type" : "event", + "index" : "filebeat-8.0.0-2019.12.18-000001", + "depth" : 1 + }, + { + "rule" : "f2b70c4a-4d8f-4db5-9ed7-d3ab0630e406", + "id" : "9d8710925adbf1a9c469621805407e74334dd08ca2c2ea414840fe971a571938", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 2 + }, + { + "rule" : "161fa5b8-0b96-4985-b066-0d99b2bcb904", + "id" : "efbe514e8d806a5ef3da7658cfa73961e25befefc84f622e963b45dcac798868", + "type" : "signal", + "index" : ".siem-signals-default-000001", + "depth" : 3 + } +] +``` + +You will be "halted" at this point as the signal ancestry and de-duplication ensures that we do not report twice on signals and that we do not +create additional duplications. So what happens if we create a 3rd rule which does a signal on a signal? + +```sh +./post_rule.sh ./rules/test_cases/signals_on_signals/halting_test/signal_on_signal.json +``` + +That 3rd signal should find all previous 5 signals and write them out. So that's 5 more. Then each signal will report on those 5 giving a depth of +4 . Grand total will be 16. You can repeat this as many times as you want and should always see an eventual constant stop time of the signals. They should +never keep increasing for this test. + +What about ordering the adding of rules between the query of the document and the signals? This order should not matter and you should get the same +results regardless of if you add the signals -> signals rules first or the query a signal event document first. The same number of documents should also +be outputted. + +Why does it take sometimes several minutes before things become stable? This is because a rule can write a signal back to the index, then another rule +wakes up and writes its document, and the previous rules on next run see this one and creates another chain. This continues until the ancestor detection +part of the code realizes that it is going to create a cyclic if it adds the same rule a second time and you no longer have a DAG (Directed Acyclic Graph) +at which point it terminates. + +What would happen if I changed the rule look-back from `"from": "now-1d"` to something smaller such as `"from": "now-30s"`? Then you won't get the same +number potentially and things are indeterministic because depending on when your rule runs it might find a previous signal and it might not. This is ok +and normal as you are then running signals on signals at the same interval as each other and the rules at the moment. A signal on a signal does not detect +that another signal has written something and it needs to re-run within the same scheduled time period. It also does not detect that another rule has just +written something and does not re-schedule its self to re-run again or against that document. + +How do I then solve the ordering problem event and signal rules writing at the same time? See the `depth_test` folder for more tests around that but you +have a few options. You can run your event rules at 5 minute intervals + 5 minute look back, then your signals rule at a 10 minute interval + 10 minute look +back which will cause it to check the latest run and the previous run for signals to signals depth of 2. For expected signals that should operate at a depth +of 3, you would increase it by another 10 minute look back for a 20 minute interval + 20 minute look back. For level 4, you would increase that to 40 minute +look back and adjust your queries accordingly to check the depth for more efficiency in querying. See `depth_test` for more information. diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/halting_test/query_single_id.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/halting_test/query_single_id.json new file mode 100644 index 0000000000000..dc05c656d7cf1 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/halting_test/query_single_id.json @@ -0,0 +1,12 @@ +{ + "name": "Queries single id", + "description": "Finds only one id below to create a single signal. Change the query to your exact _id you want to test with", + "risk_score": 1, + "severity": "high", + "type": "query", + "from": "now-1d", + "interval": "30s", + "to": "now", + "query": "_id: o8G7vm8BvLT8jmu5B1-M", + "enabled": true +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/halting_test/signal_on_signal.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/halting_test/signal_on_signal.json new file mode 100644 index 0000000000000..0f3a3e5865aa1 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/signals_on_signals/halting_test/signal_on_signal.json @@ -0,0 +1,13 @@ +{ + "name": "Signal on Signals Rule 1", + "description": "Example Signal on Signal where it reports everything as a signal", + "risk_score": 1, + "severity": "high", + "type": "query", + "from": "now-1d", + "interval": "30s", + "to": "now", + "query": "_id: *", + "enabled": true, + "index": ".siem-signals-default" +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/__mocks__/es_results.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/__mocks__/es_results.ts index ede82a597b238..9a79b27bac7e9 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/__mocks__/es_results.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/__mocks__/es_results.ts @@ -75,7 +75,7 @@ export const sampleDocWithSortId = (someUuid: string = sampleIdGuid): SignalSour sort: ['1234567891111'], }); -export const sampleEmptyDocSearchResults: SignalSearchResponse = { +export const sampleEmptyDocSearchResults = (): SignalSearchResponse => ({ took: 10, timed_out: false, _shards: { @@ -89,6 +89,44 @@ export const sampleEmptyDocSearchResults: SignalSearchResponse = { max_score: 100, hits: [], }, +}); + +export const sampleDocWithAncestors = (): SignalSearchResponse => { + const sampleDoc = sampleDocNoSortId(); + sampleDoc._source.signal = { + parent: { + rule: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + id: 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71', + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + ancestors: [ + { + rule: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + id: 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71', + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + ], + }; + + return { + took: 10, + timed_out: false, + _shards: { + total: 10, + successful: 10, + failed: 0, + skipped: 0, + }, + hits: { + total: 0, + max_score: 100, + hits: [sampleDoc], + }, + }; }; export const sampleBulkCreateDuplicateResult = { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_bulk_body.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_bulk_body.test.ts index 90860a817d270..de11bf6fcc3c1 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_bulk_body.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_bulk_body.test.ts @@ -11,6 +11,7 @@ import { sampleIdGuid, } from './__mocks__/es_results'; import { buildBulkBody } from './build_bulk_body'; +import { SignalHit } from './types'; describe('buildBulkBody', () => { beforeEach(() => { @@ -32,18 +33,28 @@ describe('buildBulkBody', () => { }); // Timestamp will potentially always be different so remove it for the test delete fakeSignalSourceHit['@timestamp']; - expect(fakeSignalSourceHit).toEqual({ + const expected: Omit & { someKey: 'someValue' } = { someKey: 'someValue', event: { kind: 'signal', }, signal: { parent: { + rule: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', id: sampleIdGuid, type: 'event', index: 'myFakeSignalIndex', depth: 1, }, + ancestors: [ + { + rule: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + id: sampleIdGuid, + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + ], original_time: 'someTimeStamp', status: 'open', rule: { @@ -74,7 +85,8 @@ describe('buildBulkBody', () => { updated_at: fakeSignalSourceHit.signal.rule?.updated_at, }, }, - }); + }; + expect(fakeSignalSourceHit).toEqual(expected); }); test('if bulk body builds original_event if it exists on the event to begin with', () => { @@ -99,7 +111,7 @@ describe('buildBulkBody', () => { }); // Timestamp will potentially always be different so remove it for the test delete fakeSignalSourceHit['@timestamp']; - expect(fakeSignalSourceHit).toEqual({ + const expected: Omit & { someKey: 'someValue' } = { someKey: 'someValue', event: { action: 'socket_opened', @@ -115,11 +127,21 @@ describe('buildBulkBody', () => { module: 'system', }, parent: { + rule: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', id: sampleIdGuid, type: 'event', index: 'myFakeSignalIndex', depth: 1, }, + ancestors: [ + { + rule: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + id: sampleIdGuid, + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + ], original_time: 'someTimeStamp', status: 'open', rule: { @@ -150,7 +172,8 @@ describe('buildBulkBody', () => { updated_at: fakeSignalSourceHit.signal.rule?.updated_at, }, }, - }); + }; + expect(fakeSignalSourceHit).toEqual(expected); }); test('if bulk body builds original_event if it exists on the event to begin with but no kind information', () => { @@ -174,7 +197,7 @@ describe('buildBulkBody', () => { }); // Timestamp will potentially always be different so remove it for the test delete fakeSignalSourceHit['@timestamp']; - expect(fakeSignalSourceHit).toEqual({ + const expected: Omit & { someKey: 'someValue' } = { someKey: 'someValue', event: { action: 'socket_opened', @@ -189,11 +212,21 @@ describe('buildBulkBody', () => { module: 'system', }, parent: { + rule: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', id: sampleIdGuid, type: 'event', index: 'myFakeSignalIndex', depth: 1, }, + ancestors: [ + { + rule: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + id: sampleIdGuid, + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + ], original_time: 'someTimeStamp', status: 'open', rule: { @@ -224,7 +257,8 @@ describe('buildBulkBody', () => { updated_at: fakeSignalSourceHit.signal.rule?.updated_at, }, }, - }); + }; + expect(fakeSignalSourceHit).toEqual(expected); }); test('if bulk body builds original_event if it exists on the event to begin with with only kind information', () => { @@ -246,7 +280,7 @@ describe('buildBulkBody', () => { }); // Timestamp will potentially always be different so remove it for the test delete fakeSignalSourceHit['@timestamp']; - expect(fakeSignalSourceHit).toEqual({ + const expected: Omit & { someKey: 'someValue' } = { someKey: 'someValue', event: { kind: 'signal', @@ -256,11 +290,21 @@ describe('buildBulkBody', () => { kind: 'event', }, parent: { + rule: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', id: sampleIdGuid, type: 'event', index: 'myFakeSignalIndex', depth: 1, }, + ancestors: [ + { + rule: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + id: sampleIdGuid, + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + ], original_time: 'someTimeStamp', status: 'open', rule: { @@ -291,6 +335,7 @@ describe('buildBulkBody', () => { created_at: fakeSignalSourceHit.signal.rule?.created_at, }, }, - }); + }; + expect(fakeSignalSourceHit).toEqual(expected); }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_signal.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_signal.test.ts index debc619fbf8b2..dcd36ab811e6a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_signal.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_signal.test.ts @@ -5,9 +5,8 @@ */ import { sampleDocNoSortId, sampleRule } from './__mocks__/es_results'; -import { buildSignal } from './build_signal'; -import { OutputRuleAlertRest } from '../types'; -import { Signal } from './types'; +import { buildSignal, buildAncestor, buildAncestorsSignal } from './build_signal'; +import { Signal, Ancestor } from './types'; describe('buildSignal', () => { beforeEach(() => { @@ -17,15 +16,25 @@ describe('buildSignal', () => { test('it builds a signal as expected without original_event if event does not exist', () => { const doc = sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'); delete doc._source.event; - const rule: Partial = sampleRule(); + const rule = sampleRule(); const signal = buildSignal(doc, rule); const expected: Signal = { parent: { + rule: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', id: 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71', type: 'event', index: 'myFakeSignalIndex', depth: 1, }, + ancestors: [ + { + rule: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + id: 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71', + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + ], original_time: 'someTimeStamp', status: 'open', rule: { @@ -66,15 +75,25 @@ describe('buildSignal', () => { kind: 'event', module: 'system', }; - const rule: Partial = sampleRule(); + const rule = sampleRule(); const signal = buildSignal(doc, rule); const expected: Signal = { parent: { + rule: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', id: 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71', type: 'event', index: 'myFakeSignalIndex', depth: 1, }, + ancestors: [ + { + rule: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + id: 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71', + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + ], original_time: 'someTimeStamp', original_event: { action: 'socket_opened', @@ -112,4 +131,131 @@ describe('buildSignal', () => { }; expect(signal).toEqual(expected); }); + + test('it builds a ancestor correctly if the parent does not exist', () => { + const doc = sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'); + doc._source.event = { + action: 'socket_opened', + dataset: 'socket', + kind: 'event', + module: 'system', + }; + const rule = sampleRule(); + const signal = buildAncestor(doc, rule); + const expected: Ancestor = { + rule: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + id: 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71', + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }; + expect(signal).toEqual(expected); + }); + + test('it builds a ancestor correctly if the parent does exist', () => { + const doc = sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'); + doc._source.event = { + action: 'socket_opened', + dataset: 'socket', + kind: 'event', + module: 'system', + }; + doc._source.signal = { + parent: { + rule: '98c0bf9e-4d38-46f4-9a6a-8a820426256b', + id: '730ddf9e-5a00-4f85-9ddf-5878ca511a87', + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + ancestors: [ + { + rule: '98c0bf9e-4d38-46f4-9a6a-8a820426256b', + id: '730ddf9e-5a00-4f85-9ddf-5878ca511a87', + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + ], + }; + const rule = sampleRule(); + const signal = buildAncestor(doc, rule); + const expected: Ancestor = { + rule: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + id: 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71', + type: 'signal', + index: 'myFakeSignalIndex', + depth: 2, + }; + expect(signal).toEqual(expected); + }); + + test('it builds a signal ancestor correctly if the parent does not exist', () => { + const doc = sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'); + doc._source.event = { + action: 'socket_opened', + dataset: 'socket', + kind: 'event', + module: 'system', + }; + const rule = sampleRule(); + const signal = buildAncestorsSignal(doc, rule); + const expected: Ancestor[] = [ + { + rule: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + id: 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71', + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + ]; + expect(signal).toEqual(expected); + }); + + test('it builds a signal ancestor correctly if the parent does exist', () => { + const doc = sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'); + doc._source.event = { + action: 'socket_opened', + dataset: 'socket', + kind: 'event', + module: 'system', + }; + doc._source.signal = { + parent: { + rule: '98c0bf9e-4d38-46f4-9a6a-8a820426256b', + id: '730ddf9e-5a00-4f85-9ddf-5878ca511a87', + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + ancestors: [ + { + rule: '98c0bf9e-4d38-46f4-9a6a-8a820426256b', + id: '730ddf9e-5a00-4f85-9ddf-5878ca511a87', + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + ], + }; + const rule = sampleRule(); + const signal = buildAncestorsSignal(doc, rule); + const expected: Ancestor[] = [ + { + rule: '98c0bf9e-4d38-46f4-9a6a-8a820426256b', + id: '730ddf9e-5a00-4f85-9ddf-5878ca511a87', + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + { + rule: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + id: 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71', + type: 'signal', + index: 'myFakeSignalIndex', + depth: 2, + }, + ]; + expect(signal).toEqual(expected); + }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_signal.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_signal.ts index 4131c843297ea..7a63d6831ea97 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_signal.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_signal.ts @@ -4,17 +4,52 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SignalSourceHit, Signal } from './types'; +import { SignalSourceHit, Signal, Ancestor } from './types'; import { OutputRuleAlertRest } from '../types'; -export const buildSignal = (doc: SignalSourceHit, rule: Partial): Signal => { - const signal: Signal = { - parent: { +export const buildAncestor = ( + doc: SignalSourceHit, + rule: Partial +): Ancestor => { + const existingSignal = doc._source.signal?.parent; + if (existingSignal != null) { + return { + rule: rule.id != null ? rule.id : '', + id: doc._id, + type: 'signal', + index: doc._index, + depth: existingSignal.depth + 1, + }; + } else { + return { + rule: rule.id != null ? rule.id : '', id: doc._id, type: 'event', index: doc._index, depth: 1, - }, + }; + } +}; + +export const buildAncestorsSignal = ( + doc: SignalSourceHit, + rule: Partial +): Signal['ancestors'] => { + const newAncestor = buildAncestor(doc, rule); + const existingAncestors = doc._source.signal?.ancestors; + if (existingAncestors != null) { + return [...existingAncestors, newAncestor]; + } else { + return [newAncestor]; + } +}; + +export const buildSignal = (doc: SignalSourceHit, rule: Partial): Signal => { + const parent = buildAncestor(doc, rule); + const ancestors = buildAncestorsSignal(doc, rule); + const signal: Signal = { + parent, + ancestors, original_time: doc._source['@timestamp'], status: 'open', rule, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts index ac6f840943f18..0644d5e467a5a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts @@ -33,7 +33,7 @@ describe('searchAfterAndBulkCreate', () => { test('if successful with empty search results', async () => { const sampleParams = sampleRuleAlertParams(); const result = await searchAfterAndBulkCreate({ - someResult: sampleEmptyDocSearchResults, + someResult: sampleEmptyDocSearchResults(), ruleParams: sampleParams, services: mockService, logger: mockLogger, @@ -51,6 +51,7 @@ describe('searchAfterAndBulkCreate', () => { expect(mockService.callCluster).toHaveBeenCalledTimes(0); expect(result).toEqual(true); }); + test('if successful iteration of while loop with maxDocs', async () => { const sampleParams = sampleRuleAlertParams(30); const someGuids = Array.from({ length: 13 }).map(x => uuid.v4()); @@ -103,6 +104,7 @@ describe('searchAfterAndBulkCreate', () => { expect(mockService.callCluster).toHaveBeenCalledTimes(5); expect(result).toEqual(true); }); + test('if unsuccessful first bulk create', async () => { const someGuids = Array.from({ length: 4 }).map(x => uuid.v4()); const sampleParams = sampleRuleAlertParams(10); @@ -126,6 +128,7 @@ describe('searchAfterAndBulkCreate', () => { expect(mockLogger.error).toHaveBeenCalled(); expect(result).toEqual(false); }); + test('if unsuccessful iteration of searchAfterAndBulkCreate due to empty sort ids', async () => { const sampleParams = sampleRuleAlertParams(); mockService.callCluster.mockReturnValueOnce({ @@ -156,6 +159,7 @@ describe('searchAfterAndBulkCreate', () => { expect(mockLogger.error).toHaveBeenCalled(); expect(result).toEqual(false); }); + test('if unsuccessful iteration of searchAfterAndBulkCreate due to empty sort ids and 0 total hits', async () => { const sampleParams = sampleRuleAlertParams(); mockService.callCluster.mockReturnValueOnce({ @@ -185,6 +189,7 @@ describe('searchAfterAndBulkCreate', () => { }); expect(result).toEqual(true); }); + test('if successful iteration of while loop with maxDocs and search after returns results with no sort ids', async () => { const sampleParams = sampleRuleAlertParams(10); const someGuids = Array.from({ length: 4 }).map(x => uuid.v4()); @@ -217,6 +222,7 @@ describe('searchAfterAndBulkCreate', () => { }); expect(result).toEqual(true); }); + test('if successful iteration of while loop with maxDocs and search after returns empty results with no sort ids', async () => { const sampleParams = sampleRuleAlertParams(10); const someGuids = Array.from({ length: 4 }).map(x => uuid.v4()); @@ -230,7 +236,7 @@ describe('searchAfterAndBulkCreate', () => { }, ], }) - .mockReturnValueOnce(sampleEmptyDocSearchResults); + .mockReturnValueOnce(sampleEmptyDocSearchResults()); const result = await searchAfterAndBulkCreate({ someResult: repeatedSearchResultsWithSortId(4, 1, someGuids), ruleParams: sampleParams, @@ -249,6 +255,7 @@ describe('searchAfterAndBulkCreate', () => { }); expect(result).toEqual(true); }); + test('if returns false when singleSearchAfter throws an exception', async () => { const sampleParams = sampleRuleAlertParams(10); const someGuids = Array.from({ length: 4 }).map(x => uuid.v4()); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts index 32f2c86914770..143fad602daea 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts @@ -134,6 +134,27 @@ export const signalRulesAlertType = ({ logger.warn( `Signal rule name: "${name}", id: "${alertId}", rule_id: "${ruleId}" has a time gap of ${gap.humanize()} (${gap.asMilliseconds()}ms), and could be missing signals within that time. Consider increasing your look behind time or adding more Kibana instances.` ); + // write a failure status whenever we have a time gap + // this is a temporary solution until general activity + // monitoring is developed as a feature + const gapDate = new Date().toISOString(); + await services.savedObjectsClient.create(ruleStatusSavedObjectType, { + alertId, + statusDate: gapDate, + status: 'failed', + lastFailureAt: gapDate, + lastSuccessAt: currentStatusSavedObject.attributes.lastSuccessAt, + lastFailureMessage: `Signal rule name: "${name}", id: "${alertId}", rule_id: "${ruleId}" has a time gap of ${gap.humanize()} (${gap.asMilliseconds()}ms), and could be missing signals within that time. Consider increasing your look behind time or adding more Kibana instances.`, + lastSuccessMessage: currentStatusSavedObject.attributes.lastSuccessMessage, + }); + + if (ruleStatusSavedObjects.saved_objects.length >= 6) { + // delete fifth status and prepare to insert a newer one. + const toDelete = ruleStatusSavedObjects.saved_objects.slice(5); + await toDelete.forEach(async item => + services.savedObjectsClient.delete(ruleStatusSavedObjectType, item.id) + ); + } } // set searchAfter page size to be the lesser of default page size or maxSignals. const searchAfterSize = @@ -246,7 +267,7 @@ export const signalRulesAlertType = ({ // TODO: Error handling and writing of errors into a signal that has error // handling/conditions logger.error( - `Error from signal rule name: "${name}", id: "${alertId}", rule_id: "${ruleId}"` + `Error from signal rule name: "${name}", id: "${alertId}", rule_id: "${ruleId}", ${err.message}` ); const sDate = new Date().toISOString(); currentStatusSavedObject.attributes.status = 'failed'; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts index ca4b1d1e8e84a..d5f11c91a2b7c 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts @@ -14,10 +14,11 @@ import { sampleEmptyDocSearchResults, sampleBulkCreateDuplicateResult, sampleBulkCreateErrorResult, + sampleDocWithAncestors, } from './__mocks__/es_results'; import { savedObjectsClientMock } from 'src/core/server/mocks'; import { DEFAULT_SIGNALS_INDEX } from '../../../../common/constants'; -import { singleBulkCreate } from './single_bulk_create'; +import { singleBulkCreate, filterDuplicateRules } from './single_bulk_create'; export const mockService = { callCluster: jest.fn(), @@ -131,9 +132,9 @@ describe('singleBulkCreate', () => { expect(firstHash).not.toEqual(secondHash); }); }); + test('create successful bulk create', async () => { const sampleParams = sampleRuleAlertParams(); - const sampleSearchResult = sampleDocSearchResultsNoSortId; mockService.callCluster.mockReturnValueOnce({ took: 100, errors: false, @@ -144,7 +145,7 @@ describe('singleBulkCreate', () => { ], }); const successfulsingleBulkCreate = await singleBulkCreate({ - someResult: sampleSearchResult(), + someResult: sampleDocSearchResultsNoSortId(), ruleParams: sampleParams, services: mockService, logger: mockLogger, @@ -159,9 +160,9 @@ describe('singleBulkCreate', () => { }); expect(successfulsingleBulkCreate).toEqual(true); }); + test('create successful bulk create with docs with no versioning', async () => { const sampleParams = sampleRuleAlertParams(); - const sampleSearchResult = sampleDocSearchResultsNoSortIdNoVersion; mockService.callCluster.mockReturnValueOnce({ took: 100, errors: false, @@ -172,7 +173,7 @@ describe('singleBulkCreate', () => { ], }); const successfulsingleBulkCreate = await singleBulkCreate({ - someResult: sampleSearchResult(), + someResult: sampleDocSearchResultsNoSortIdNoVersion(), ruleParams: sampleParams, services: mockService, logger: mockLogger, @@ -187,12 +188,12 @@ describe('singleBulkCreate', () => { }); expect(successfulsingleBulkCreate).toEqual(true); }); + test('create unsuccessful bulk create due to empty search results', async () => { const sampleParams = sampleRuleAlertParams(); - const sampleSearchResult = sampleEmptyDocSearchResults; mockService.callCluster.mockReturnValue(false); const successfulsingleBulkCreate = await singleBulkCreate({ - someResult: sampleSearchResult, + someResult: sampleEmptyDocSearchResults(), ruleParams: sampleParams, services: mockService, logger: mockLogger, @@ -253,4 +254,71 @@ describe('singleBulkCreate', () => { expect(mockLogger.error).toHaveBeenCalled(); expect(successfulsingleBulkCreate).toEqual(true); }); + + test('filter duplicate rules will return an empty array given an empty array', () => { + const filtered = filterDuplicateRules( + '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + sampleEmptyDocSearchResults() + ); + expect(filtered).toEqual([]); + }); + + test('filter duplicate rules will return nothing filtered when the two rule ids do not match with each other', () => { + const filtered = filterDuplicateRules('some id', sampleDocWithAncestors()); + expect(filtered).toEqual([ + { + _index: 'myFakeSignalIndex', + _type: 'doc', + _score: 100, + _version: 1, + _id: 'e1e08ddc-5e37-49ff-a258-5393aa44435a', + _source: { + someKey: 'someValue', + '@timestamp': 'someTimeStamp', + signal: { + parent: { + rule: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + id: 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71', + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + ancestors: [ + { + rule: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + id: 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71', + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + ], + }, + }, + }, + ]); + }); + + test('filters duplicate rules will return empty array when the two rule ids match each other', () => { + const filtered = filterDuplicateRules( + '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + sampleDocWithAncestors() + ); + expect(filtered).toEqual([]); + }); + + test('filter duplicate rules will return back search responses if they do not have a signal and will NOT filter the source out', () => { + const ancestors = sampleDocWithAncestors(); + ancestors.hits.hits[0]._source = { '@timestamp': 'some timestamp' }; + const filtered = filterDuplicateRules('04128c15-0d1b-4716-a4c5-46997ac7f3bd', ancestors); + expect(filtered).toEqual([ + { + _index: 'myFakeSignalIndex', + _type: 'doc', + _score: 100, + _version: 1, + _id: 'e1e08ddc-5e37-49ff-a258-5393aa44435a', + _source: { '@timestamp': 'some timestamp' }, + }, + ]); + }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.ts index a6290e57eb225..cb5de4c974927 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.ts @@ -28,6 +28,28 @@ interface SingleBulkCreateParams { tags: string[]; } +/** + * This is for signals on signals to work correctly. If given a rule id this will check if + * that rule id already exists in the ancestor tree of each signal search response and remove + * those documents so they cannot be created as a signal since we do not want a rule id to + * ever be capable of re-writing the same signal continuously if both the _input_ and _output_ + * of the signals index happens to be the same index. + * @param ruleId The rule id + * @param signalSearchResponse The search response that has all the documents + */ +export const filterDuplicateRules = ( + ruleId: string, + signalSearchResponse: SignalSearchResponse +) => { + return signalSearchResponse.hits.hits.filter(doc => { + if (doc._source.signal == null) { + return true; + } else { + return !doc._source.signal.ancestors.some(ancestor => ancestor.rule === ruleId); + } + }); +}; + // Bulk Index documents. export const singleBulkCreate = async ({ someResult, @@ -43,6 +65,8 @@ export const singleBulkCreate = async ({ enabled, tags, }: SingleBulkCreateParams): Promise => { + someResult.hits.hits = filterDuplicateRules(id, someResult); + if (someResult.hits.hits.length === 0) { return true; } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/types.ts index 544250858a083..9b7b2b8f1fff9 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/types.ts @@ -51,11 +51,16 @@ export type SearchTypes = | boolean | boolean[] | object - | object[]; + | object[] + | undefined; export interface SignalSource { [key: string]: SearchTypes; '@timestamp': string; + signal?: { + parent: Ancestor; + ancestors: Ancestor[]; + }; } export interface BulkResponse { @@ -123,14 +128,18 @@ export type SignalRuleAlertTypeDefinition = Omit & { executor: ({ services, params, state }: RuleExecutorOptions) => Promise; }; +export interface Ancestor { + rule: string; + id: string; + type: string; + index: string; + depth: number; +} + export interface Signal { rule: Partial; - parent: { - id: string; - type: string; - index: string; - depth: number; - }; + parent: Ancestor; + ancestors: Ancestor[]; original_time: string; original_event?: SearchTypes; status: 'open' | 'closed'; diff --git a/x-pack/legacy/plugins/siem/server/plugin.ts b/x-pack/legacy/plugins/siem/server/plugin.ts index 9d1983cf1d4da..96eef2f44e5a0 100644 --- a/x-pack/legacy/plugins/siem/server/plugin.ts +++ b/x-pack/legacy/plugins/siem/server/plugin.ts @@ -14,6 +14,7 @@ import { noteSavedObjectType, pinnedEventSavedObjectType, timelineSavedObjectType, + ruleStatusSavedObjectType, } from './saved_objects'; export type SiemPluginSecurity = Pick; @@ -57,6 +58,7 @@ export class Plugin { noteSavedObjectType, pinnedEventSavedObjectType, timelineSavedObjectType, + ruleStatusSavedObjectType, ], read: ['config'], }, @@ -80,6 +82,7 @@ export class Plugin { noteSavedObjectType, pinnedEventSavedObjectType, timelineSavedObjectType, + ruleStatusSavedObjectType, ], }, ui: [ diff --git a/x-pack/legacy/plugins/siem/server/saved_objects.ts b/x-pack/legacy/plugins/siem/server/saved_objects.ts index 1d65e351fc86a..8b9a1891c8a50 100644 --- a/x-pack/legacy/plugins/siem/server/saved_objects.ts +++ b/x-pack/legacy/plugins/siem/server/saved_objects.ts @@ -12,9 +12,17 @@ import { timelineSavedObjectType, timelineSavedObjectMappings, } from './lib/timeline/saved_object_mappings'; -import { ruleStatusSavedObjectMappings } from './lib/detection_engine/rules/saved_object_mappings'; +import { + ruleStatusSavedObjectMappings, + ruleStatusSavedObjectType, +} from './lib/detection_engine/rules/saved_object_mappings'; -export { noteSavedObjectType, pinnedEventSavedObjectType, timelineSavedObjectType }; +export { + noteSavedObjectType, + pinnedEventSavedObjectType, + ruleStatusSavedObjectType, + timelineSavedObjectType, +}; export const savedObjectMappings = { ...timelineSavedObjectMappings, ...noteSavedObjectMappings, diff --git a/x-pack/legacy/plugins/siem/server/types.ts b/x-pack/legacy/plugins/siem/server/types.ts index bbcfbe7b9c0ec..3fa2268afe92c 100644 --- a/x-pack/legacy/plugins/siem/server/types.ts +++ b/x-pack/legacy/plugins/siem/server/types.ts @@ -9,6 +9,8 @@ import { Legacy } from 'kibana'; export interface ServerFacade { config: Legacy.Server['config']; plugins: { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + actions: any; // We have to do this at the moment because the types are not compatible alerting?: Legacy.Server['plugins']['alerting']; elasticsearch: Legacy.Server['plugins']['elasticsearch']; spaces: Legacy.Server['plugins']['spaces']; diff --git a/x-pack/legacy/plugins/spaces/index.ts b/x-pack/legacy/plugins/spaces/index.ts index 934b44b4accaf..2e6b878794777 100644 --- a/x-pack/legacy/plugins/spaces/index.ts +++ b/x-pack/legacy/plugins/spaces/index.ts @@ -127,9 +127,6 @@ export const spaces = (kibana: Record) => kibanaIndex: config.get('kibana.index'), }, savedObjects: server.savedObjects, - tutorial: { - addScopedTutorialContextFactory: server.addScopedTutorialContextFactory, - }, auditLogger: { create: (pluginId: string) => new AuditLogger(server, pluginId, server.config(), server.plugins.xpack_main.info), diff --git a/x-pack/legacy/plugins/task_manager/server/task_manager.mock.ts b/x-pack/legacy/plugins/task_manager/server/task_manager.mock.ts deleted file mode 100644 index a4b80d902d098..0000000000000 --- a/x-pack/legacy/plugins/task_manager/server/task_manager.mock.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { - TaskManagerSetupContract, - TaskManagerStartContract, -} from '../../../../plugins/task_manager/server'; -import { Subject } from 'rxjs'; - -export const taskManagerMock = { - setup(overrides: Partial> = {}) { - const mocked: jest.Mocked = { - registerTaskDefinitions: jest.fn(), - addMiddleware: jest.fn(), - config$: new Subject(), - registerLegacyAPI: jest.fn(), - ...overrides, - }; - return mocked; - }, - start(overrides: Partial> = {}) { - const mocked: jest.Mocked = { - ensureScheduled: jest.fn(), - schedule: jest.fn(), - fetch: jest.fn(), - runNow: jest.fn(), - remove: jest.fn(), - ...overrides, - }; - return mocked; - }, -}; diff --git a/x-pack/legacy/plugins/uptime/index.ts b/x-pack/legacy/plugins/uptime/index.ts index e090a2c85e136..cf7332f97d466 100644 --- a/x-pack/legacy/plugins/uptime/index.ts +++ b/x-pack/legacy/plugins/uptime/index.ts @@ -9,6 +9,7 @@ import { resolve } from 'path'; import { PluginInitializerContext } from 'src/core/server'; import { PLUGIN } from './common/constants'; import { KibanaServer, plugin } from './server'; +import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/utils'; export const uptime = (kibana: any) => new kibana.Plugin({ @@ -30,6 +31,7 @@ export const uptime = (kibana: any) => main: 'plugins/uptime/app', order: 8900, url: '/app/uptime#/', + category: DEFAULT_APP_CATEGORIES.observability, }, home: ['plugins/uptime/register_feature'], }, diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/embedded_map.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/embedded_map.tsx index 98780d23c5a62..11f6565734782 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/embedded_map.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/embedded_map.tsx @@ -12,6 +12,7 @@ import { start } from '../../../../../../../../../src/legacy/core_plugins/embedd import * as i18n from './translations'; // @ts-ignore import { MAP_SAVED_OBJECT_TYPE } from '../../../../../../maps/common/constants'; +import { Location } from '../../../../../common/runtime_types'; import { MapEmbeddable } from './types'; import { getLayerList } from './map_config'; @@ -22,10 +23,7 @@ export interface EmbeddedMapProps { downPoints: LocationPoint[]; } -export interface LocationPoint { - lat: string; - lon: string; -} +export type LocationPoint = Required; const EmbeddedPanel = styled.div` z-index: auto; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/map_config.ts b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/map_config.ts index d4601baefdf30..a43edae438252 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/map_config.ts +++ b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/map_config.ts @@ -5,7 +5,7 @@ */ import lowPolyLayerFeatures from './low_poly_layer.json'; -import { LocationPoint } from './embedded_map'; +import { LocationPoint } from './embedded_map.js'; import { UptimeAppColors } from '../../../../uptime_app'; /** @@ -16,7 +16,7 @@ import { UptimeAppColors } from '../../../../uptime_app'; export const getLayerList = ( upPoints: LocationPoint[], downPoints: LocationPoint[], - { gray, danger }: Pick + { danger }: Pick ) => { return [getLowPolyLayer(), getDownPointsLayer(downPoints, danger), getUpPointsLayer(upPoints)]; }; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_map.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_map.tsx index d35e1281260e2..c93e16d0a080b 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_map.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_map.tsx @@ -9,7 +9,7 @@ import styled from 'styled-components'; import { EuiFlexGroup, EuiFlexItem, EuiErrorBoundary } from '@elastic/eui'; import { LocationStatusTags } from './location_status_tags'; import { EmbeddedMap, LocationPoint } from './embeddables/embedded_map'; -import { MonitorLocations } from '../../../../common/runtime_types'; +import { MonitorLocations, MonitorLocation } from '../../../../common/runtime_types'; import { UNNAMED_LOCATION } from '../../../../common/constants'; import { LocationMissingWarning } from './location_missing'; @@ -32,15 +32,23 @@ export const LocationMap = ({ monitorLocations }: LocationMapProps) => { let isGeoInfoMissing = false; if (monitorLocations?.locations) { - monitorLocations.locations.forEach((item: any) => { - if (item.geo?.name !== UNNAMED_LOCATION) { - if (item.summary.down === 0) { - upPoints.push(item.geo.location); + monitorLocations.locations.forEach((item: MonitorLocation) => { + if (item.geo?.name === UNNAMED_LOCATION || !item.geo?.location) { + isGeoInfoMissing = true; + } else if ( + item.geo?.name !== UNNAMED_LOCATION && + !!item.geo.location.lat && + !!item.geo.location.lon + ) { + // TypeScript doesn't infer that the above checks in this block's condition + // ensure that lat and lon are defined when we try to pass the location object directly, + // but if we destructure the values it does. Improvement to this block is welcome. + const { lat, lon } = item.geo.location; + if (item?.summary?.down === 0) { + upPoints.push({ lat, lon }); } else { - downPoints.push(item.geo.location); + downPoints.push({ lat, lon }); } - } else if (item.geo?.name === UNNAMED_LOCATION) { - isGeoInfoMissing = true; } }); } diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/__snapshots__/doc_link_body.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/__snapshots__/doc_link_body.test.tsx.snap new file mode 100644 index 0000000000000..cfdbb5184d170 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/__snapshots__/doc_link_body.test.tsx.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`PingListExpandedRow renders expected elements for valid props 1`] = ` + + + docs +   + + , + } + } + /> + +`; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/__snapshots__/expanded_row.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/__snapshots__/expanded_row.test.tsx.snap index 9fdca0d5b99f1..9aed8780682ee 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/__snapshots__/expanded_row.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/__snapshots__/expanded_row.test.tsx.snap @@ -1,77 +1,228 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`PingListExpandedRow doesn't render list items if the body field is undefined 1`] = ` - + + + + + + + `; exports[`PingListExpandedRow doesn't render list items if the http field is undefined 1`] = ` - + + + + + + + `; exports[`PingListExpandedRow doesn't render list items if the response field is undefined 1`] = ` - + + + + + + + `; exports[`PingListExpandedRow renders error information when an error field is present 1`] = ` - - Forbidden - , - "title": "Error", - }, - Object { - "description": - The Title", - "hash": "testhash", - } - } - /> - - , - "title": "Response Body", - }, - ] - } -/> + + + + + Forbidden + , + "title": "Error", + }, + Object { + "description": + The Title", + "hash": "testhash", + } + } + /> + + + , + "title": "Response Body", + }, + ] + } + /> + + + `; exports[`PingListExpandedRow renders expected elements for valid props 1`] = ` - - The Title", - "hash": "testhash", - } - } - /> - - , - "title": "Response Body", - }, - ] - } -/> + + + + + The Title", + "hash": "testhash", + } + } + /> + + + , + "title": "Response Body", + }, + ] + } + /> + + + +`; + +exports[`PingListExpandedRow renders link to docs if body is not recorded but it is present 1`] = ` +
+
+
+
+ +
+
+
+
+ Response Body +
+
+
+ Body size is 1MB. +
+
+
+ Body not recorded. Read our + + docs  + + + for more information on recording response bodies. +
+
+
+
+
+
+
+`; + +exports[`PingListExpandedRow shallow renders link to docs if body is not recorded but it is present 1`] = ` + + + + + + + + , + "title": "Response Body", + }, + ] + } + /> + + + `; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/doc_link_body.test.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/doc_link_body.test.tsx new file mode 100644 index 0000000000000..a29f647a22613 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/doc_link_body.test.tsx @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { shallowWithIntl } from 'test_utils/enzyme_helpers'; +import React from 'react'; +import { DocLinkForBody } from '../doc_link_body'; + +describe('PingListExpandedRow', () => { + it('renders expected elements for valid props', () => { + expect(shallowWithIntl()).toMatchSnapshot(); + }); +}); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/expanded_row.test.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/expanded_row.test.tsx index f8914b1d46484..9dbe48ec5553a 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/expanded_row.test.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/__tests__/expanded_row.test.tsx @@ -4,10 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { shallowWithIntl } from 'test_utils/enzyme_helpers'; +import { mountWithIntl, renderWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; import React from 'react'; import { PingListExpandedRowComponent } from '../expanded_row'; import { Ping } from '../../../../../common/graphql/types'; +import { DocLinkForBody } from '../doc_link_body'; describe('PingListExpandedRow', () => { let ping: Ping; @@ -56,4 +57,26 @@ describe('PingListExpandedRow', () => { delete ping.http; expect(shallowWithIntl()).toMatchSnapshot(); }); + + it(`shallow renders link to docs if body is not recorded but it is present`, () => { + // @ts-ignore this shouldn't be undefined unless the beforeEach block is modified + delete ping.http.response.body.content; + expect(shallowWithIntl()).toMatchSnapshot(); + }); + + it(`renders link to docs if body is not recorded but it is present`, () => { + // @ts-ignore this shouldn't be undefined unless the beforeEach block is modified + delete ping.http.response.body.content; + expect(renderWithIntl()).toMatchSnapshot(); + }); + + it(`mount component to find link to docs if body is not recorded but it is present`, () => { + // @ts-ignore this shouldn't be undefined unless the beforeEach block is modified + delete ping.http.response.body.content; + const component = mountWithIntl(); + + const docLinkComponent = component.find(DocLinkForBody); + + expect(docLinkComponent).toHaveLength(1); + }); }); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/doc_link_body.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/doc_link_body.tsx new file mode 100644 index 0000000000000..9640529f391c0 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/doc_link_body.tsx @@ -0,0 +1,36 @@ +/* + * 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 { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; +import { EuiIcon, EuiLink, EuiText } from '@elastic/eui'; + +const bodyDocsLink = + 'https://www.elastic.co/guide/en/beats/heartbeat/current/configuration-heartbeat-options.html#monitor-http-response'; + +export const DocLinkForBody = () => { + const docsLink = ( + + {i18n.translate('xpack.uptime.pingList.drawer.body.docsLink', { + defaultMessage: 'docs', + description: 'Docs link to set response body', + })} +   + + + ); + + return ( + + + + ); +}; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/expanded_row.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/expanded_row.tsx index 34aef698ee5df..c684235122e34 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/expanded_row.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/expanded_row.tsx @@ -5,10 +5,19 @@ */ // @ts-ignore formatNumber import { formatNumber } from '@elastic/eui/lib/services/format'; -import { EuiCodeBlock, EuiDescriptionList, EuiText } from '@elastic/eui'; -import React, { Fragment } from 'react'; +import { + EuiCallOut, + EuiCodeBlock, + EuiDescriptionList, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiText, +} from '@elastic/eui'; +import React from 'react'; import { i18n } from '@kbn/i18n'; import { Ping, HttpBody } from '../../../../common/graphql/types'; +import { DocLinkForBody } from './doc_link_body'; interface Props { ping: Ping; @@ -52,7 +61,7 @@ export const PingListExpandedRowComponent = ({ ping }: Props) => { } // Show the body, if present - if (ping.http && ping.http.response && ping.http.response.body) { + if (ping.http?.response?.body) { const body = ping.http.response.body; listItems.push({ @@ -60,12 +69,21 @@ export const PingListExpandedRowComponent = ({ ping }: Props) => { defaultMessage: 'Response Body', }), description: ( - + <> - - + + {body.content ? : } + ), }); } - return ; + return ( + + + + + + + + ); }; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/ping_list.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/ping_list.tsx index 85517144edfc6..e8825dacc0078 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/ping_list.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list/ping_list.tsx @@ -115,6 +115,14 @@ export const PingListComponent = ({ }) ); + const pings: Ping[] = data?.allPings?.pings ?? []; + + const hasStatus: boolean = pings.reduce( + (hasHttpStatus: boolean, currentPing: Ping) => + hasHttpStatus || !!currentPing.http?.response?.status_code, + false + ); + const columns: any[] = [ { field: 'monitor.status', @@ -172,55 +180,57 @@ export const PingListComponent = ({ }), }, { - align: 'right', + align: hasStatus ? 'right' : 'center', field: 'error.type', name: i18n.translate('xpack.uptime.pingList.errorTypeColumnLabel', { defaultMessage: 'Error type', }), render: (error: string) => error ?? '-', }, - ]; - const pings: Ping[] = data?.allPings?.pings ?? []; - const hasStatus: boolean = pings.reduce( - (hasHttpStatus: boolean, currentPing: Ping) => - hasHttpStatus || !!currentPing.http?.response?.status_code, - false - ); - if (hasStatus) { - columns.push({ - field: 'http.response.status_code', + // Only add this column is there is any status present in list + ...(hasStatus + ? [ + { + field: 'http.response.status_code', + align: 'right', + name: ( + + {i18n.translate('xpack.uptime.pingList.responseCodeColumnLabel', { + defaultMessage: 'Response code', + })} + + ), + render: (statusCode: string) => ( + + {statusCode} + + ), + }, + ] + : []), + { align: 'right', - name: ( - - {i18n.translate('xpack.uptime.pingList.responseCodeColumnLabel', { - defaultMessage: 'Response code', - })} - - ), - render: (statusCode: string) => ( - - {statusCode}{' '} - - ), - }); - } + width: '24px', + isExpander: true, + render: (item: Ping) => { + return ( + toggleDetails(item, itemIdToExpandedRowMap, setItemIdToExpandedRowMap)} + disabled={!item.error && !(item.http?.response?.body?.bytes > 0)} + aria-label={ + itemIdToExpandedRowMap[item.id] + ? i18n.translate('xpack.uptime.pingList.collapseRow', { + defaultMessage: 'Collapse', + }) + : i18n.translate('xpack.uptime.pingList.expandRow', { defaultMessage: 'Expand' }) + } + iconType={itemIdToExpandedRowMap[item.id] ? 'arrowUp' : 'arrowDown'} + /> + ); + }, + }, + ]; - columns.push({ - align: 'right', - width: '24px', - isExpander: true, - render: (item: Ping) => ( - toggleDetails(item, itemIdToExpandedRowMap, setItemIdToExpandedRowMap)} - aria-label={ - itemIdToExpandedRowMap[item.id] - ? i18n.translate('xpack.uptime.pingList.collapseRow', { defaultMessage: 'Collapse' }) - : i18n.translate('xpack.uptime.pingList.expandRow', { defaultMessage: 'Expand' }) - } - iconType={itemIdToExpandedRowMap[item.id] ? 'arrowUp' : 'arrowDown'} - /> - ), - }); const pagination: Pagination = { initialPageSize: 20, pageIndex: 0, diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/framework/adapter_types.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/framework/adapter_types.ts index b490bf17e292c..2f72081a70988 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/framework/adapter_types.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/framework/adapter_types.ts @@ -48,7 +48,7 @@ export interface UptimeCoreSetup { } export interface UptimeCorePlugins { - savedObjects: SavedObjectsLegacyService; + savedObjects: SavedObjectsLegacyService; usageCollection: UsageCollectionSetup; xpack: any; } diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/elasticsearch_monitors_adapter.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/elasticsearch_monitors_adapter.ts index c86e0db9ae04a..e433931f03c8e 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/elasticsearch_monitors_adapter.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/elasticsearch_monitors_adapter.ts @@ -346,16 +346,18 @@ export const elasticsearchMonitorsAdapter: UMMonitorsAdapter = { const result = await callES('search', params); const locations = result?.aggregations?.location?.buckets ?? []; - const getGeo = (locGeo: any) => { + const getGeo = (locGeo: { name: string; location?: string }) => { if (locGeo) { const { name, location } = locGeo; - const latLon = location.trim().split(','); + const latLon = location?.trim().split(','); return { name, - location: { - lat: latLon[0], - lon: latLon[1], - }, + location: latLon + ? { + lat: latLon[0], + lon: latLon[1], + } + : undefined, }; } else { return { diff --git a/x-pack/legacy/plugins/watcher/common/constants/action_states.ts b/x-pack/legacy/plugins/watcher/common/constants/action_states.ts index e9501fc2a60c4..ac4da650463f8 100644 --- a/x-pack/legacy/plugins/watcher/common/constants/action_states.ts +++ b/x-pack/legacy/plugins/watcher/common/constants/action_states.ts @@ -36,4 +36,9 @@ export const ACTION_STATES: { [key: string]: string } = { CONFIG_ERROR: i18n.translate('xpack.watcher.constants.actionStates.configErrorStateText', { defaultMessage: 'Config error', }), + + // Action status is unknown; we should never end up in this state + UNKNOWN: i18n.translate('xpack.watcher.constants.actionStates.unknownStateText', { + defaultMessage: 'Unknown', + }), }; diff --git a/x-pack/legacy/plugins/watcher/public/np_ready/application/components/watch_status.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/components/watch_status.tsx index 8afd174f8561e..a254f43723877 100644 --- a/x-pack/legacy/plugins/watcher/public/np_ready/application/components/watch_status.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/components/watch_status.tsx @@ -23,6 +23,8 @@ function StatusIcon({ status }: { status: string }) { return ; case WATCH_STATES.CONFIG_ERROR: case WATCH_STATES.ERROR: + case ACTION_STATES.UNKNOWN: + return ; case ACTION_STATES.CONFIG_ERROR: case ACTION_STATES.ERROR: return ; diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/models/action_status/__tests__/action_status.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/action_status/__tests__/action_status.js index 21e27a1b12c46..7b55ff1692603 100644 --- a/x-pack/legacy/plugins/watcher/server/np_ready/models/action_status/__tests__/action_status.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/models/action_status/__tests__/action_status.js @@ -106,11 +106,39 @@ describe('action_status', () => { }; }); - it(`correctly calculates ACTION_STATES.ERROR`, () => { - upstreamJson.actionStatusJson.last_execution.successful = false; - const actionStatus = ActionStatus.fromUpstreamJson(upstreamJson); + describe(`correctly calculates ACTION_STATES.ERROR`, () => { + it('lastExecutionSuccessful is equal to false', () => { + upstreamJson.actionStatusJson.last_execution.successful = false; + const actionStatus = ActionStatus.fromUpstreamJson(upstreamJson); + expect(actionStatus.state).to.be(ACTION_STATES.ERROR); + }); - expect(actionStatus.state).to.be(ACTION_STATES.ERROR); + it('action is acked and lastAcknowledged is less than lastExecution', () => { + const actionStatus = ActionStatus.fromUpstreamJson({ + ...upstreamJson, + actionStatusJson: { + ack: { + state: 'acked', + timestamp: '2017-03-01T00:00:00.000Z', + }, + last_execution: { + timestamp: '2017-03-02T00:00:00.000Z', + }, + }, + }); + expect(actionStatus.state).to.be(ACTION_STATES.ERROR); + }); + + it('action is ackable and lastSuccessfulExecution is less than lastExecution', () => { + delete upstreamJson.actionStatusJson.last_throttle; + upstreamJson.actionStatusJson.ack.state = 'ackable'; + upstreamJson.actionStatusJson.last_successful_execution.timestamp = + '2017-03-01T00:00:00.000Z'; + upstreamJson.actionStatusJson.last_execution.timestamp = '2017-03-02T00:00:00.000Z'; + const actionStatus = ActionStatus.fromUpstreamJson(upstreamJson); + + expect(actionStatus.state).to.be(ACTION_STATES.ERROR); + }); }); it('correctly calculates ACTION_STATES.CONFIG_ERROR', () => { @@ -192,18 +220,7 @@ describe('action_status', () => { }); }); - it(`correctly calculates ACTION_STATES.ERROR`, () => { - delete upstreamJson.actionStatusJson.last_throttle; - upstreamJson.actionStatusJson.ack.state = 'ackable'; - upstreamJson.actionStatusJson.last_successful_execution.timestamp = - '2017-03-01T00:00:00.000Z'; - upstreamJson.actionStatusJson.last_execution.timestamp = '2017-03-02T00:00:00.000Z'; - const actionStatus = ActionStatus.fromUpstreamJson(upstreamJson); - - expect(actionStatus.state).to.be(ACTION_STATES.ERROR); - }); - - it(`throws an error if it can not determine ACTION_STATE`, () => { + it(`correctly calculates ACTION_STATES.UNKNOWN if it can not determine state`, () => { upstreamJson = { id: 'my-action', actionStatusJson: { @@ -213,9 +230,7 @@ describe('action_status', () => { }; const actionStatus = ActionStatus.fromUpstreamJson(upstreamJson); - expect(() => { - actionStatus.state; - }).to.throwError(/could not determine action status/i); + expect(actionStatus.state).to.be(ACTION_STATES.UNKNOWN); }); }); diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/models/action_status/action_status.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/action_status/action_status.js index 6a484e7d4303a..9f6b7da1e3a69 100644 --- a/x-pack/legacy/plugins/watcher/server/np_ready/models/action_status/action_status.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/models/action_status/action_status.js @@ -5,7 +5,7 @@ */ import { get } from 'lodash'; -import { badImplementation, badRequest } from 'boom'; +import { badRequest } from 'boom'; import { getMoment } from '../../../../common/lib/get_moment'; import { ACTION_STATES } from '../../../../common/constants'; import { i18n } from '@kbn/i18n'; @@ -46,6 +46,11 @@ export class ActionStatus { return ACTION_STATES.ACKNOWLEDGED; } + // A user could potentially land in this state if running on multiple nodes and timing is off + if (ackState === 'acked' && this.lastAcknowledged < this.lastExecution) { + return ACTION_STATES.ERROR; + } + if (ackState === 'ackable' && this.lastThrottled >= this.lastExecution) { return ACTION_STATES.THROTTLED; } @@ -58,20 +63,10 @@ export class ActionStatus { return ACTION_STATES.ERROR; } - // At this point, we cannot determine the action status so we thrown an error. + // At this point, we cannot determine the action status so mark it as "unknown". // We should never get to this point in the code. If we do, it means we are // missing an action status and the logic to determine it. - throw badImplementation( - i18n.translate( - 'xpack.watcher.models.actionStatus.notDetermineActionStatusBadImplementationMessage', - { - defaultMessage: 'Could not determine action status; action = {actionStatusJson}', - values: { - actionStatusJson: JSON.stringify(actionStatusJson), - }, - } - ) - ); + return ACTION_STATES.UNKNOWN; } get isAckable() { diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_delete_route.ts b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_delete_route.ts index 3402cc283dba0..26b6f96f6cb8c 100644 --- a/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_delete_route.ts +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_delete_route.ts @@ -24,8 +24,9 @@ export function registerDeleteRoute(deps: RouteDependencies, legacy: ServerShim) const { watchId } = request.params; try { - await deleteWatch(callWithRequest, watchId); - return response.noContent(); + return response.ok({ + body: await deleteWatch(callWithRequest, watchId), + }); } catch (e) { // Case: Error from Elasticsearch JS client if (isEsError(e)) { diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_save_route.ts b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_save_route.ts index 5d22392d49ed8..df4117dee2bfd 100644 --- a/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_save_route.ts +++ b/x-pack/legacy/plugins/watcher/server/np_ready/routes/api/watch/register_save_route.ts @@ -76,8 +76,9 @@ export function registerSaveRoute(deps: RouteDependencies, legacy: ServerShim) { try { // Create new watch - await saveWatch(callWithRequest, id, serializedWatch); - return response.noContent(); + return response.ok({ + body: await saveWatch(callWithRequest, id, serializedWatch), + }); } catch (e) { // Case: Error from Elasticsearch JS client if (isEsError(e)) { diff --git a/x-pack/legacy/plugins/actions/README.md b/x-pack/plugins/actions/README.md similarity index 99% rename from x-pack/legacy/plugins/actions/README.md rename to x-pack/plugins/actions/README.md index 3d7409d1613e1..31a847a249e2a 100644 --- a/x-pack/legacy/plugins/actions/README.md +++ b/x-pack/plugins/actions/README.md @@ -92,7 +92,7 @@ This is the primary function for an action type. Whenever the action needs to ex ### Example The built-in email action type provides a good example of creating an action type with non-trivial configuration and params: -[x-pack/legacy/plugins/actions/server/builtin_action_types/email.ts](server/builtin_action_types/email.ts) +[x-pack/plugins/actions/server/builtin_action_types/email.ts](server/builtin_action_types/email.ts) ## RESTful API diff --git a/x-pack/plugins/actions/kibana.json b/x-pack/plugins/actions/kibana.json new file mode 100644 index 0000000000000..ff7f22af6261d --- /dev/null +++ b/x-pack/plugins/actions/kibana.json @@ -0,0 +1,10 @@ +{ + "id": "actions", + "server": true, + "version": "8.0.0", + "kibanaVersion": "kibana", + "configPath": ["xpack", "actions"], + "requiredPlugins": ["licensing", "taskManager", "encryptedSavedObjects", "event_log"], + "optionalPlugins": ["spaces"], + "ui": false +} diff --git a/x-pack/legacy/plugins/actions/server/action_type_registry.mock.ts b/x-pack/plugins/actions/server/action_type_registry.mock.ts similarity index 100% rename from x-pack/legacy/plugins/actions/server/action_type_registry.mock.ts rename to x-pack/plugins/actions/server/action_type_registry.mock.ts diff --git a/x-pack/legacy/plugins/actions/server/action_type_registry.test.ts b/x-pack/plugins/actions/server/action_type_registry.test.ts similarity index 98% rename from x-pack/legacy/plugins/actions/server/action_type_registry.test.ts rename to x-pack/plugins/actions/server/action_type_registry.test.ts index 63f1b545179c7..79c9a44901079 100644 --- a/x-pack/legacy/plugins/actions/server/action_type_registry.test.ts +++ b/x-pack/plugins/actions/server/action_type_registry.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { taskManagerMock } from '../../../../plugins/task_manager/server/task_manager.mock'; +import { taskManagerMock } from '../../task_manager/server/task_manager.mock'; import { ActionTypeRegistry } from './action_type_registry'; import { ExecutorType } from './types'; import { ActionExecutor, ExecutorError, TaskRunnerFactory } from './lib'; diff --git a/x-pack/legacy/plugins/actions/server/action_type_registry.ts b/x-pack/plugins/actions/server/action_type_registry.ts similarity index 97% rename from x-pack/legacy/plugins/actions/server/action_type_registry.ts rename to x-pack/plugins/actions/server/action_type_registry.ts index 351c1add7b451..9f494a60be02e 100644 --- a/x-pack/legacy/plugins/actions/server/action_type_registry.ts +++ b/x-pack/plugins/actions/server/action_type_registry.ts @@ -6,7 +6,7 @@ import Boom from 'boom'; import { i18n } from '@kbn/i18n'; -import { RunContext, TaskManagerSetupContract } from '../../../../plugins/task_manager/server'; +import { RunContext, TaskManagerSetupContract } from '../../task_manager/server'; import { ExecutorError, TaskRunnerFactory } from './lib'; import { ActionType } from './types'; import { ActionsConfigurationUtilities } from './actions_config'; diff --git a/x-pack/legacy/plugins/actions/server/actions_client.mock.ts b/x-pack/plugins/actions/server/actions_client.mock.ts similarity index 100% rename from x-pack/legacy/plugins/actions/server/actions_client.mock.ts rename to x-pack/plugins/actions/server/actions_client.mock.ts diff --git a/x-pack/legacy/plugins/actions/server/actions_client.test.ts b/x-pack/plugins/actions/server/actions_client.test.ts similarity index 98% rename from x-pack/legacy/plugins/actions/server/actions_client.test.ts rename to x-pack/plugins/actions/server/actions_client.test.ts index dfbd2db4b6842..feca08fad922c 100644 --- a/x-pack/legacy/plugins/actions/server/actions_client.test.ts +++ b/x-pack/plugins/actions/server/actions_client.test.ts @@ -10,14 +10,14 @@ import { ActionTypeRegistry } from './action_type_registry'; import { ActionsClient } from './actions_client'; import { ExecutorType } from './types'; import { ActionExecutor, TaskRunnerFactory } from './lib'; -import { taskManagerMock } from '../../../../plugins/task_manager/server/task_manager.mock'; +import { taskManagerMock } from '../../task_manager/server/task_manager.mock'; import { configUtilsMock } from './actions_config.mock'; import { getActionsConfigurationUtilities } from './actions_config'; import { elasticsearchServiceMock, savedObjectsClientMock, -} from '../../../../../src/core/server/mocks'; +} from '../../../../src/core/server/mocks'; const defaultKibanaIndex = '.kibana'; const savedObjectsClient = savedObjectsClientMock.create(); diff --git a/x-pack/legacy/plugins/actions/server/actions_client.ts b/x-pack/plugins/actions/server/actions_client.ts similarity index 99% rename from x-pack/legacy/plugins/actions/server/actions_client.ts rename to x-pack/plugins/actions/server/actions_client.ts index 104439ca4401f..7ac8ea24b5218 100644 --- a/x-pack/legacy/plugins/actions/server/actions_client.ts +++ b/x-pack/plugins/actions/server/actions_client.ts @@ -10,7 +10,7 @@ import { SavedObjectsClientContract, SavedObjectAttributes, SavedObject, -} from 'src/core/server'; +} from '../../../../src/core/server'; import { ActionTypeRegistry } from './action_type_registry'; import { validateConfig, validateSecrets } from './lib'; diff --git a/x-pack/legacy/plugins/actions/server/actions_config.mock.ts b/x-pack/plugins/actions/server/actions_config.mock.ts similarity index 100% rename from x-pack/legacy/plugins/actions/server/actions_config.mock.ts rename to x-pack/plugins/actions/server/actions_config.mock.ts diff --git a/x-pack/legacy/plugins/actions/server/actions_config.test.ts b/x-pack/plugins/actions/server/actions_config.test.ts similarity index 100% rename from x-pack/legacy/plugins/actions/server/actions_config.test.ts rename to x-pack/plugins/actions/server/actions_config.test.ts diff --git a/x-pack/legacy/plugins/actions/server/actions_config.ts b/x-pack/plugins/actions/server/actions_config.ts similarity index 100% rename from x-pack/legacy/plugins/actions/server/actions_config.ts rename to x-pack/plugins/actions/server/actions_config.ts diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/email.test.ts b/x-pack/plugins/actions/server/builtin_action_types/email.test.ts similarity index 98% rename from x-pack/legacy/plugins/actions/server/builtin_action_types/email.test.ts rename to x-pack/plugins/actions/server/builtin_action_types/email.test.ts index 74263c603c11e..4ad4fe96f3447 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/email.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/email.test.ts @@ -8,8 +8,8 @@ jest.mock('./lib/send_email', () => ({ sendEmail: jest.fn(), })); -import { Logger } from '../../../../../../src/core/server'; -import { savedObjectsClientMock } from '../../../../../../src/core/server/mocks'; +import { Logger } from '../../../../../src/core/server'; +import { savedObjectsClientMock } from '../../../../../src/core/server/mocks'; import { ActionType, ActionTypeExecutorOptions } from '../types'; import { configUtilsMock } from '../actions_config.mock'; diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/email.ts b/x-pack/plugins/actions/server/builtin_action_types/email.ts similarity index 99% rename from x-pack/legacy/plugins/actions/server/builtin_action_types/email.ts rename to x-pack/plugins/actions/server/builtin_action_types/email.ts index 94d7852e76fad..b209e7bbca6f7 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/email.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/email.ts @@ -12,7 +12,7 @@ import nodemailerGetService from 'nodemailer/lib/well-known'; import { sendEmail, JSON_TRANSPORT_SERVICE } from './lib/send_email'; import { nullableType } from './lib/nullable'; import { portSchema } from './lib/schemas'; -import { Logger } from '../../../../../../src/core/server'; +import { Logger } from '../../../../../src/core/server'; import { ActionType, ActionTypeExecutorOptions, ActionTypeExecutorResult } from '../types'; import { ActionsConfigurationUtilities } from '../actions_config'; diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/es_index.test.ts b/x-pack/plugins/actions/server/builtin_action_types/es_index.test.ts similarity index 99% rename from x-pack/legacy/plugins/actions/server/builtin_action_types/es_index.test.ts rename to x-pack/plugins/actions/server/builtin_action_types/es_index.test.ts index dbac84ef681f1..a305f85650b9c 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/es_index.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/es_index.test.ts @@ -10,7 +10,7 @@ jest.mock('./lib/send_email', () => ({ import { ActionType, ActionTypeExecutorOptions } from '../types'; import { validateConfig, validateParams } from '../lib'; -import { savedObjectsClientMock } from '../../../../../../src/core/server/mocks'; +import { savedObjectsClientMock } from '../../../../../src/core/server/mocks'; import { createActionTypeRegistry } from './index.test'; import { ActionParamsType, ActionTypeConfigType } from './es_index'; diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/es_index.ts b/x-pack/plugins/actions/server/builtin_action_types/es_index.ts similarity index 98% rename from x-pack/legacy/plugins/actions/server/builtin_action_types/es_index.ts rename to x-pack/plugins/actions/server/builtin_action_types/es_index.ts index ddf33ba63f71a..f8217046b2ea5 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/es_index.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/es_index.ts @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; import { schema, TypeOf } from '@kbn/config-schema'; import { nullableType } from './lib/nullable'; -import { Logger } from '../../../../../../src/core/server'; +import { Logger } from '../../../../../src/core/server'; import { ActionType, ActionTypeExecutorOptions, ActionTypeExecutorResult } from '../types'; // config definition diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/index.test.ts b/x-pack/plugins/actions/server/builtin_action_types/index.test.ts similarity index 86% rename from x-pack/legacy/plugins/actions/server/builtin_action_types/index.test.ts rename to x-pack/plugins/actions/server/builtin_action_types/index.test.ts index 5fcf39c2e8fdd..ff732f58d6c94 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/index.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/index.test.ts @@ -6,10 +6,10 @@ import { ActionExecutor, TaskRunnerFactory } from '../lib'; import { ActionTypeRegistry } from '../action_type_registry'; -import { taskManagerMock } from '../../../../../plugins/task_manager/server/task_manager.mock'; +import { taskManagerMock } from '../../../task_manager/server/task_manager.mock'; import { registerBuiltInActionTypes } from './index'; -import { Logger } from '../../../../../../src/core/server'; -import { loggingServiceMock } from '../../../../../../src/core/server/mocks'; +import { Logger } from '../../../../../src/core/server'; +import { loggingServiceMock } from '../../../../../src/core/server/mocks'; import { configUtilsMock } from '../actions_config.mock'; const ACTION_TYPE_IDS = ['.index', '.email', '.pagerduty', '.server-log', '.slack', '.webhook']; diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/index.ts b/x-pack/plugins/actions/server/builtin_action_types/index.ts similarity index 96% rename from x-pack/legacy/plugins/actions/server/builtin_action_types/index.ts rename to x-pack/plugins/actions/server/builtin_action_types/index.ts index 33f2b21799814..a92a279d08439 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/index.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/index.ts @@ -6,7 +6,7 @@ import { ActionTypeRegistry } from '../action_type_registry'; import { ActionsConfigurationUtilities } from '../actions_config'; -import { Logger } from '../../../../../../src/core/server'; +import { Logger } from '../../../../../src/core/server'; import { getActionType as getEmailActionType } from './email'; import { getActionType as getIndexActionType } from './es_index'; diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/lib/http_rersponse_retry_header.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/http_rersponse_retry_header.ts similarity index 100% rename from x-pack/legacy/plugins/actions/server/builtin_action_types/lib/http_rersponse_retry_header.ts rename to x-pack/plugins/actions/server/builtin_action_types/lib/http_rersponse_retry_header.ts diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/lib/nullable.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/nullable.ts similarity index 100% rename from x-pack/legacy/plugins/actions/server/builtin_action_types/lib/nullable.ts rename to x-pack/plugins/actions/server/builtin_action_types/lib/nullable.ts diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/lib/post_pagerduty.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/post_pagerduty.ts similarity index 100% rename from x-pack/legacy/plugins/actions/server/builtin_action_types/lib/post_pagerduty.ts rename to x-pack/plugins/actions/server/builtin_action_types/lib/post_pagerduty.ts diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/lib/post_servicenow.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/post_servicenow.ts similarity index 100% rename from x-pack/legacy/plugins/actions/server/builtin_action_types/lib/post_servicenow.ts rename to x-pack/plugins/actions/server/builtin_action_types/lib/post_servicenow.ts diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/lib/result_type.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/result_type.ts similarity index 100% rename from x-pack/legacy/plugins/actions/server/builtin_action_types/lib/result_type.ts rename to x-pack/plugins/actions/server/builtin_action_types/lib/result_type.ts diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/lib/schemas.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/schemas.ts similarity index 100% rename from x-pack/legacy/plugins/actions/server/builtin_action_types/lib/schemas.ts rename to x-pack/plugins/actions/server/builtin_action_types/lib/schemas.ts diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/lib/send_email.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.ts similarity index 97% rename from x-pack/legacy/plugins/actions/server/builtin_action_types/lib/send_email.ts rename to x-pack/plugins/actions/server/builtin_action_types/lib/send_email.ts index ab949dbe2722d..74eead0708545 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/lib/send_email.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.ts @@ -9,7 +9,7 @@ import nodemailer from 'nodemailer'; import { default as MarkdownIt } from 'markdown-it'; -import { Logger } from '../../../../../../../src/core/server'; +import { Logger } from '../../../../../../src/core/server'; // an email "service" which doesn't actually send, just returns what it would send export const JSON_TRANSPORT_SERVICE = '__json'; diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/lib/string_utils.test.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/string_utils.test.ts similarity index 100% rename from x-pack/legacy/plugins/actions/server/builtin_action_types/lib/string_utils.test.ts rename to x-pack/plugins/actions/server/builtin_action_types/lib/string_utils.test.ts diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/lib/string_utils.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/string_utils.ts similarity index 100% rename from x-pack/legacy/plugins/actions/server/builtin_action_types/lib/string_utils.ts rename to x-pack/plugins/actions/server/builtin_action_types/lib/string_utils.ts diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.test.ts b/x-pack/plugins/actions/server/builtin_action_types/pagerduty.test.ts similarity index 99% rename from x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.test.ts rename to x-pack/plugins/actions/server/builtin_action_types/pagerduty.test.ts index f60fdf7fef95e..ab860e4c3bbba 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/pagerduty.test.ts @@ -11,10 +11,10 @@ jest.mock('./lib/post_pagerduty', () => ({ import { getActionType } from './pagerduty'; import { ActionType, Services, ActionTypeExecutorOptions } from '../types'; import { validateConfig, validateSecrets, validateParams } from '../lib'; -import { savedObjectsClientMock } from '../../../../../../src/core/server/mocks'; +import { savedObjectsClientMock } from '../../../../../src/core/server/mocks'; import { postPagerduty } from './lib/post_pagerduty'; import { createActionTypeRegistry } from './index.test'; -import { Logger } from '../../../../../../src/core/server'; +import { Logger } from '../../../../../src/core/server'; import { configUtilsMock } from '../actions_config.mock'; const postPagerdutyMock = postPagerduty as jest.Mock; diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.ts b/x-pack/plugins/actions/server/builtin_action_types/pagerduty.ts similarity index 99% rename from x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.ts rename to x-pack/plugins/actions/server/builtin_action_types/pagerduty.ts index b26621702cf5b..62f46d3d62503 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/pagerduty.ts @@ -8,7 +8,7 @@ import { curry } from 'lodash'; import { i18n } from '@kbn/i18n'; import { schema, TypeOf } from '@kbn/config-schema'; import { postPagerduty } from './lib/post_pagerduty'; -import { Logger } from '../../../../../../src/core/server'; +import { Logger } from '../../../../../src/core/server'; import { ActionType, ActionTypeExecutorOptions, ActionTypeExecutorResult } from '../types'; import { ActionsConfigurationUtilities } from '../actions_config'; diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.test.ts b/x-pack/plugins/actions/server/builtin_action_types/server_log.test.ts similarity index 96% rename from x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.test.ts rename to x-pack/plugins/actions/server/builtin_action_types/server_log.test.ts index 8f28b9e8f5125..6a4482f362c2b 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/server_log.test.ts @@ -6,8 +6,8 @@ import { ActionType } from '../types'; import { validateParams } from '../lib'; -import { Logger } from '../../../../../../src/core/server'; -import { savedObjectsClientMock } from '../../../../../../src/core/server/mocks'; +import { Logger } from '../../../../../src/core/server'; +import { savedObjectsClientMock } from '../../../../../src/core/server/mocks'; import { createActionTypeRegistry } from './index.test'; const ACTION_TYPE_ID = '.server-log'; diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.ts b/x-pack/plugins/actions/server/builtin_action_types/server_log.ts similarity index 97% rename from x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.ts rename to x-pack/plugins/actions/server/builtin_action_types/server_log.ts index 34b8602eeba36..62406cfaf66e1 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/server_log.ts @@ -8,7 +8,7 @@ import { curry } from 'lodash'; import { i18n } from '@kbn/i18n'; import { schema, TypeOf } from '@kbn/config-schema'; -import { Logger } from '../../../../../../src/core/server'; +import { Logger } from '../../../../../src/core/server'; import { ActionType, ActionTypeExecutorOptions, ActionTypeExecutorResult } from '../types'; import { withoutControlCharacters } from './lib/string_utils'; diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/servicenow.test.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow.test.ts similarity index 99% rename from x-pack/legacy/plugins/actions/server/builtin_action_types/servicenow.test.ts rename to x-pack/plugins/actions/server/builtin_action_types/servicenow.test.ts index a445c6afde4d5..a6c43f48fa803 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/servicenow.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow.test.ts @@ -11,7 +11,7 @@ jest.mock('./lib/post_servicenow', () => ({ import { getActionType } from './servicenow'; import { ActionType, Services, ActionTypeExecutorOptions } from '../types'; import { validateConfig, validateSecrets, validateParams } from '../lib'; -import { savedObjectsClientMock } from '../../../../../../src/core/server/mocks'; +import { savedObjectsClientMock } from '../../../../../src/core/server/mocks'; import { postServiceNow } from './lib/post_servicenow'; import { createActionTypeRegistry } from './index.test'; import { configUtilsMock } from '../actions_config.mock'; diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/servicenow.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow.ts similarity index 100% rename from x-pack/legacy/plugins/actions/server/builtin_action_types/servicenow.ts rename to x-pack/plugins/actions/server/builtin_action_types/servicenow.ts diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/slack.test.ts b/x-pack/plugins/actions/server/builtin_action_types/slack.test.ts similarity index 98% rename from x-pack/legacy/plugins/actions/server/builtin_action_types/slack.test.ts rename to x-pack/plugins/actions/server/builtin_action_types/slack.test.ts index aebc9c4993599..d33574666cebd 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/slack.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/slack.test.ts @@ -5,7 +5,7 @@ */ import { ActionType, Services, ActionTypeExecutorOptions } from '../types'; -import { savedObjectsClientMock } from '../../../../../../src/core/server/mocks'; +import { savedObjectsClientMock } from '../../../../../src/core/server/mocks'; import { validateParams, validateSecrets } from '../lib'; import { getActionType } from './slack'; import { configUtilsMock } from '../actions_config.mock'; diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/slack.ts b/x-pack/plugins/actions/server/builtin_action_types/slack.ts similarity index 100% rename from x-pack/legacy/plugins/actions/server/builtin_action_types/slack.ts rename to x-pack/plugins/actions/server/builtin_action_types/slack.ts diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/webhook.test.ts b/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts similarity index 99% rename from x-pack/legacy/plugins/actions/server/builtin_action_types/webhook.test.ts rename to x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts index b95fef97ac7b9..ae1d8c3fddc8b 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/webhook.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts @@ -9,7 +9,7 @@ import { validateConfig, validateSecrets, validateParams } from '../lib'; import { configUtilsMock } from '../actions_config.mock'; import { ActionType } from '../types'; import { createActionTypeRegistry } from './index.test'; -import { Logger } from '../../../../../../src/core/server'; +import { Logger } from '../../../../../src/core/server'; const ACTION_TYPE_ID = '.webhook'; diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/webhook.ts b/x-pack/plugins/actions/server/builtin_action_types/webhook.ts similarity index 99% rename from x-pack/legacy/plugins/actions/server/builtin_action_types/webhook.ts rename to x-pack/plugins/actions/server/builtin_action_types/webhook.ts index fa88d3c72c163..f7efb3b1e746c 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/webhook.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/webhook.ts @@ -14,7 +14,7 @@ import { nullableType } from './lib/nullable'; import { isOk, promiseResult, Result } from './lib/result_type'; import { ActionType, ActionTypeExecutorOptions, ActionTypeExecutorResult } from '../types'; import { ActionsConfigurationUtilities } from '../actions_config'; -import { Logger } from '../../../../../../src/core/server'; +import { Logger } from '../../../../../src/core/server'; // config definition enum WebhookMethods { diff --git a/x-pack/plugins/actions/server/config.test.ts b/x-pack/plugins/actions/server/config.test.ts new file mode 100644 index 0000000000000..67b7553c4a736 --- /dev/null +++ b/x-pack/plugins/actions/server/config.test.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { configSchema } from './config'; + +describe('config validation', () => { + test('action defaults', () => { + const config: Record = {}; + expect(configSchema.validate(config)).toMatchInlineSnapshot(` + Object { + "enabled": true, + "enabledActionTypes": Array [ + "*", + ], + "whitelistedHosts": Array [ + "*", + ], + } + `); + }); +}); diff --git a/x-pack/plugins/actions/server/config.ts b/x-pack/plugins/actions/server/config.ts new file mode 100644 index 0000000000000..9e4795be6c208 --- /dev/null +++ b/x-pack/plugins/actions/server/config.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema, TypeOf } from '@kbn/config-schema'; +import { WhitelistedHosts, EnabledActionTypes } from './actions_config'; + +export const configSchema = schema.object({ + enabled: schema.boolean({ defaultValue: true }), + whitelistedHosts: schema.arrayOf( + schema.oneOf([schema.string({ hostname: true }), schema.literal(WhitelistedHosts.Any)]), + { + defaultValue: [WhitelistedHosts.Any], + } + ), + enabledActionTypes: schema.arrayOf( + schema.oneOf([schema.string(), schema.literal(EnabledActionTypes.Any)]), + { + defaultValue: [WhitelistedHosts.Any], + } + ), +}); + +export type ActionsConfig = TypeOf; diff --git a/x-pack/legacy/plugins/actions/server/constants/plugin.ts b/x-pack/plugins/actions/server/constants/plugin.ts similarity index 85% rename from x-pack/legacy/plugins/actions/server/constants/plugin.ts rename to x-pack/plugins/actions/server/constants/plugin.ts index 27cf0a8d2bf88..68082ccaa1399 100644 --- a/x-pack/legacy/plugins/actions/server/constants/plugin.ts +++ b/x-pack/plugins/actions/server/constants/plugin.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { LICENSE_TYPE_BASIC, LicenseType } from '../../../../common/constants'; +import { LICENSE_TYPE_BASIC, LicenseType } from '../../../../legacy/common/constants'; export const PLUGIN = { ID: 'actions', diff --git a/x-pack/legacy/plugins/actions/server/create_execute_function.test.ts b/x-pack/plugins/actions/server/create_execute_function.test.ts similarity index 96% rename from x-pack/legacy/plugins/actions/server/create_execute_function.test.ts rename to x-pack/plugins/actions/server/create_execute_function.test.ts index 7dbcfce5ee335..258efb1500ab2 100644 --- a/x-pack/legacy/plugins/actions/server/create_execute_function.test.ts +++ b/x-pack/plugins/actions/server/create_execute_function.test.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { taskManagerMock } from '../../../../plugins/task_manager/server/task_manager.mock'; +import { taskManagerMock } from '../../task_manager/server/task_manager.mock'; import { createExecuteFunction } from './create_execute_function'; -import { savedObjectsClientMock } from '../../../../../src/core/server/mocks'; +import { savedObjectsClientMock } from '../../../../src/core/server/mocks'; const mockTaskManager = taskManagerMock.start(); const savedObjectsClient = savedObjectsClientMock.create(); diff --git a/x-pack/legacy/plugins/actions/server/create_execute_function.ts b/x-pack/plugins/actions/server/create_execute_function.ts similarity index 93% rename from x-pack/legacy/plugins/actions/server/create_execute_function.ts rename to x-pack/plugins/actions/server/create_execute_function.ts index ddd8b1df2327b..42db37bbe89f4 100644 --- a/x-pack/legacy/plugins/actions/server/create_execute_function.ts +++ b/x-pack/plugins/actions/server/create_execute_function.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SavedObjectsClientContract } from 'src/core/server'; -import { TaskManagerStartContract } from '../../../../plugins/task_manager/server'; +import { SavedObjectsClientContract } from '../../../../src/core/server'; +import { TaskManagerStartContract } from '../../task_manager/server'; import { GetBasePathFunction } from './types'; interface CreateExecuteFunctionOptions { diff --git a/x-pack/plugins/actions/server/index.ts b/x-pack/plugins/actions/server/index.ts new file mode 100644 index 0000000000000..27098bc541790 --- /dev/null +++ b/x-pack/plugins/actions/server/index.ts @@ -0,0 +1,19 @@ +/* + * 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 { PluginInitializerContext } from '../../../../src/core/server'; +import { ActionsPlugin } from './plugin'; +import { configSchema } from './config'; + +export { ActionsPlugin, ActionResult, ActionTypeExecutorOptions, ActionType } from './types'; +export { ActionsClient } from './actions_client'; +export { PluginSetupContract, PluginStartContract } from './plugin'; + +export const plugin = (initContext: PluginInitializerContext) => new ActionsPlugin(initContext); + +export const config = { + schema: configSchema, +}; diff --git a/x-pack/legacy/plugins/actions/server/lib/action_executor.mock.ts b/x-pack/plugins/actions/server/lib/action_executor.mock.ts similarity index 100% rename from x-pack/legacy/plugins/actions/server/lib/action_executor.mock.ts rename to x-pack/plugins/actions/server/lib/action_executor.mock.ts diff --git a/x-pack/legacy/plugins/actions/server/lib/action_executor.test.ts b/x-pack/plugins/actions/server/lib/action_executor.test.ts similarity index 91% rename from x-pack/legacy/plugins/actions/server/lib/action_executor.test.ts rename to x-pack/plugins/actions/server/lib/action_executor.test.ts index 7d9bf20e22ace..7d4233db0f8d9 100644 --- a/x-pack/legacy/plugins/actions/server/lib/action_executor.test.ts +++ b/x-pack/plugins/actions/server/lib/action_executor.test.ts @@ -4,15 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import Hapi from 'hapi'; +import { KibanaRequest } from '../../../../../src/core/server'; import { schema } from '@kbn/config-schema'; import { ActionExecutor } from './action_executor'; import { actionTypeRegistryMock } from '../action_type_registry.mock'; -import { encryptedSavedObjectsMock } from '../../../../../plugins/encrypted_saved_objects/server/mocks'; -import { - savedObjectsClientMock, - loggingServiceMock, -} from '../../../../../../src/core/server/mocks'; +import { encryptedSavedObjectsMock } from '../../../encrypted_saved_objects/server/mocks'; +import { savedObjectsClientMock, loggingServiceMock } from '../../../../../src/core/server/mocks'; +import { createEventLoggerMock } from '../../../event_log/server/event_logger.mock'; const actionExecutor = new ActionExecutor(); const savedObjectsClient = savedObjectsClientMock.create(); @@ -32,32 +30,18 @@ const executeParams = { params: { foo: true, }, - request: { - headers: {}, - getBasePath: () => '', - path: '/', - route: { settings: {} }, - url: { - href: '/', - }, - raw: { - req: { - url: '/', - }, - }, - } as Hapi.Request, + request: {} as KibanaRequest, }; actionExecutor.initialize({ logger: loggingServiceMock.create().get(), - spaces() { - return { - getSpaceId: () => 'some-namespace', - } as any; - }, + spaces: { + getSpaceId: () => 'some-namespace', + } as any, getServices, actionTypeRegistry, encryptedSavedObjectsPlugin, + eventLogger: createEventLoggerMock(), }); beforeEach(() => jest.resetAllMocks()); diff --git a/x-pack/legacy/plugins/actions/server/lib/action_executor.ts b/x-pack/plugins/actions/server/lib/action_executor.ts similarity index 55% rename from x-pack/legacy/plugins/actions/server/lib/action_executor.ts rename to x-pack/plugins/actions/server/lib/action_executor.ts index f0259c739654b..be6916a74fe88 100644 --- a/x-pack/legacy/plugins/actions/server/lib/action_executor.ts +++ b/x-pack/plugins/actions/server/lib/action_executor.ts @@ -4,10 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Hapi from 'hapi'; -import { PluginStartContract as EncryptedSavedObjectsStartContract } from '../../../../../plugins/encrypted_saved_objects/server'; -import { LegacySpacesPlugin as SpacesPluginStartContract } from '../../../spaces'; -import { Logger } from '../../../../../../src/core/server'; +import { Logger, KibanaRequest } from '../../../../../src/core/server'; import { validateParams, validateConfig, validateSecrets } from './validate_with_schema'; import { ActionTypeExecutorResult, @@ -15,18 +12,23 @@ import { GetServicesFunction, RawAction, } from '../types'; +import { PluginStartContract as EncryptedSavedObjectsStartContract } from '../../../encrypted_saved_objects/server'; +import { SpacesServiceSetup } from '../../../spaces/server'; +import { EVENT_LOG_ACTIONS } from '../plugin'; +import { IEvent, IEventLogger } from '../../../event_log/server'; export interface ActionExecutorContext { logger: Logger; - spaces: () => SpacesPluginStartContract | undefined; + spaces?: SpacesServiceSetup; getServices: GetServicesFunction; encryptedSavedObjectsPlugin: EncryptedSavedObjectsStartContract; actionTypeRegistry: ActionTypeRegistryContract; + eventLogger: IEventLogger; } export interface ExecuteOptions { actionId: string; - request: Hapi.Request; + request: KibanaRequest; params: Record; } @@ -54,16 +56,15 @@ export class ActionExecutor { } const { - logger, spaces, getServices, encryptedSavedObjectsPlugin, actionTypeRegistry, + eventLogger, } = this.actionExecutorContext!; - const spacesPlugin = spaces(); const services = getServices(request); - const namespace = spacesPlugin && spacesPlugin.getSpaceId(request); + const namespace = spaces && spaces.getSpaceId(request); // Ensure user can read the action before processing const { @@ -89,9 +90,9 @@ export class ActionExecutor { ); const actionType = actionTypeRegistry.get(actionTypeId); - let validatedParams; - let validatedConfig; - let validatedSecrets; + let validatedParams: Record; + let validatedConfig: Record; + let validatedSecrets: Record; try { validatedParams = validateParams(actionType, params); @@ -101,11 +102,16 @@ export class ActionExecutor { return { status: 'error', actionId, message: err.message, retry: false }; } - let result: ActionTypeExecutorResult | null = null; - const actionLabel = `${actionId} - ${actionTypeId} - ${name}`; + const actionLabel = `${actionTypeId}:${actionId}: ${name}`; + const event: IEvent = { + event: { action: EVENT_LOG_ACTIONS.execute }, + kibana: { namespace, saved_objects: [{ type: 'action', id: actionId }] }, + }; + eventLogger.startTiming(event); + let rawResult: ActionTypeExecutorResult | null | undefined | void; try { - result = await actionType.executor({ + rawResult = await actionType.executor({ actionId, services, params: validatedParams, @@ -113,15 +119,51 @@ export class ActionExecutor { secrets: validatedSecrets, }); } catch (err) { - logger.warn(`action executed unsuccessfully: ${actionLabel} - ${err.message}`); - throw err; + rawResult = { + actionId, + status: 'error', + message: 'an error occurred while running the action executor', + serviceMessage: err.message, + retry: false, + }; } + eventLogger.stopTiming(event); - logger.debug(`action executed successfully: ${actionLabel}`); - - // return basic response if none provided - if (result == null) return { status: 'ok', actionId }; + // allow null-ish return to indicate success + const result = rawResult || { + actionId, + status: 'ok', + }; + + if (result.status === 'ok') { + event.message = `action executed: ${actionLabel}`; + } else if (result.status === 'error') { + event.message = `action execution failure: ${actionLabel}`; + event.error = event.error || {}; + event.error.message = actionErrorToMessage(result); + } else { + event.message = `action execution returned unexpected result: ${actionLabel}`; + event.error = event.error || {}; + event.error.message = 'action execution returned unexpected result'; + } + eventLogger.logEvent(event); return result; } } + +function actionErrorToMessage(result: ActionTypeExecutorResult): string { + let message = result.message || 'unknown error running action'; + + if (result.serviceMessage) { + message = `${message}: ${result.serviceMessage}`; + } + + if (result.retry instanceof Date) { + message = `${message}; retry at ${result.retry.toISOString()}`; + } else if (result.retry) { + message = `${message}; retry: ${JSON.stringify(result.retry)}`; + } + + return message; +} diff --git a/x-pack/legacy/plugins/actions/server/lib/executor_error.ts b/x-pack/plugins/actions/server/lib/executor_error.ts similarity index 100% rename from x-pack/legacy/plugins/actions/server/lib/executor_error.ts rename to x-pack/plugins/actions/server/lib/executor_error.ts diff --git a/x-pack/legacy/plugins/actions/server/lib/index.ts b/x-pack/plugins/actions/server/lib/index.ts similarity index 100% rename from x-pack/legacy/plugins/actions/server/lib/index.ts rename to x-pack/plugins/actions/server/lib/index.ts diff --git a/x-pack/plugins/actions/server/lib/license_api_access.ts b/x-pack/plugins/actions/server/lib/license_api_access.ts new file mode 100644 index 0000000000000..2e650ebf5eb17 --- /dev/null +++ b/x-pack/plugins/actions/server/lib/license_api_access.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Boom from 'boom'; +import { LicenseState } from './license_state'; + +export function verifyApiAccess(licenseState: LicenseState) { + const licenseCheckResults = licenseState.getLicenseInformation(); + + if (licenseCheckResults.showAppLink && licenseCheckResults.enableAppLink) { + return null; + } + + throw Boom.forbidden(licenseCheckResults.message); +} diff --git a/x-pack/plugins/actions/server/lib/license_state.mock.ts b/x-pack/plugins/actions/server/lib/license_state.mock.ts new file mode 100644 index 0000000000000..f36f3a9eaeade --- /dev/null +++ b/x-pack/plugins/actions/server/lib/license_state.mock.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { of } from 'rxjs'; +import { LicenseState } from './license_state'; +import { LICENSE_CHECK_STATE, ILicense } from '../../../licensing/server'; + +export const mockLicenseState = () => { + const license: ILicense = { + uid: '123', + status: 'active', + isActive: true, + signature: 'sig', + isAvailable: true, + toJSON: () => ({ + signature: 'sig', + }), + getUnavailableReason: () => undefined, + hasAtLeast() { + return true; + }, + check() { + return { + state: LICENSE_CHECK_STATE.Valid, + }; + }, + getFeature() { + return { + isAvailable: true, + isEnabled: true, + }; + }, + }; + return new LicenseState(of(license)); +}; diff --git a/x-pack/legacy/plugins/actions/server/lib/license_state.test.ts b/x-pack/plugins/actions/server/lib/license_state.test.ts similarity index 91% rename from x-pack/legacy/plugins/actions/server/lib/license_state.test.ts rename to x-pack/plugins/actions/server/lib/license_state.test.ts index e58c52f63c8cb..dbb70857dad5c 100644 --- a/x-pack/legacy/plugins/actions/server/lib/license_state.test.ts +++ b/x-pack/plugins/actions/server/lib/license_state.test.ts @@ -6,8 +6,8 @@ import expect from '@kbn/expect'; import { LicenseState } from './license_state'; -import { licensingMock } from '../../../../../plugins/licensing/server/mocks'; -import { LICENSE_CHECK_STATE } from '../../../../../plugins/licensing/server'; +import { licensingMock } from '../../../licensing/server/mocks'; +import { LICENSE_CHECK_STATE } from '../../../licensing/server'; describe('license_state', () => { let getRawLicense: any; diff --git a/x-pack/legacy/plugins/actions/server/lib/license_state.ts b/x-pack/plugins/actions/server/lib/license_state.ts similarity index 80% rename from x-pack/legacy/plugins/actions/server/lib/license_state.ts rename to x-pack/plugins/actions/server/lib/license_state.ts index b4de23ef0a949..7b25e55ac0ba1 100644 --- a/x-pack/legacy/plugins/actions/server/lib/license_state.ts +++ b/x-pack/plugins/actions/server/lib/license_state.ts @@ -4,11 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; import { i18n } from '@kbn/i18n'; import { Observable, Subscription } from 'rxjs'; -import { ILicense, LICENSE_CHECK_STATE } from '../../../../../plugins/licensing/common/types'; -import { assertNever } from '../../../../../../src/core/utils'; +import { assertNever } from '../../../../../src/core/utils'; +import { ILicense, LICENSE_CHECK_STATE } from '../../../licensing/common/types'; import { PLUGIN } from '../constants/plugin'; export interface ActionsLicenseInformation { @@ -79,16 +78,3 @@ export class LicenseState { } } } - -export function verifyApiAccessFactory(licenseState: LicenseState) { - function verifyApiAccess() { - const licenseCheckResults = licenseState.getLicenseInformation(); - - if (licenseCheckResults.showAppLink && licenseCheckResults.enableAppLink) { - return null; - } - - throw Boom.forbidden(licenseCheckResults.message); - } - return verifyApiAccess; -} diff --git a/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.test.ts b/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts similarity index 94% rename from x-pack/legacy/plugins/actions/server/lib/task_runner_factory.test.ts rename to x-pack/plugins/actions/server/lib/task_runner_factory.test.ts index ad2b74da0d7d4..2246193057d0e 100644 --- a/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.test.ts +++ b/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts @@ -7,15 +7,13 @@ import sinon from 'sinon'; import { ExecutorError } from './executor_error'; import { ActionExecutor } from './action_executor'; -import { ConcreteTaskInstance, TaskStatus } from '../../../../../plugins/task_manager/server'; +import { ConcreteTaskInstance, TaskStatus } from '../../../task_manager/server'; import { TaskRunnerFactory } from './task_runner_factory'; import { actionTypeRegistryMock } from '../action_type_registry.mock'; import { actionExecutorMock } from './action_executor.mock'; -import { encryptedSavedObjectsMock } from '../../../../../plugins/encrypted_saved_objects/server/mocks'; -import { - savedObjectsClientMock, - loggingServiceMock, -} from '../../../../../../src/core/server/mocks'; +import { encryptedSavedObjectsMock } from '../../../encrypted_saved_objects/server/mocks'; +import { savedObjectsClientMock, loggingServiceMock } from 'src/core/server/mocks'; +import { createEventLoggerMock } from '../../../event_log/server/event_logger.mock'; const spaceIdToNamespace = jest.fn(); const actionTypeRegistry = actionTypeRegistryMock.create(); @@ -60,8 +58,8 @@ const actionExecutorInitializerParams = { logger: loggingServiceMock.create().get(), getServices: jest.fn().mockReturnValue(services), actionTypeRegistry, - spaces: () => undefined, encryptedSavedObjectsPlugin: mockedEncryptedSavedObjectsPlugin, + eventLogger: createEventLoggerMock(), }; const taskRunnerFactoryInitializerParams = { spaceIdToNamespace, diff --git a/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.ts b/x-pack/plugins/actions/server/lib/task_runner_factory.ts similarity index 95% rename from x-pack/legacy/plugins/actions/server/lib/task_runner_factory.ts rename to x-pack/plugins/actions/server/lib/task_runner_factory.ts index 2dc3d1161399e..59da7bdfab318 100644 --- a/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.ts +++ b/x-pack/plugins/actions/server/lib/task_runner_factory.ts @@ -6,8 +6,8 @@ import { ActionExecutorContract } from './action_executor'; import { ExecutorError } from './executor_error'; -import { RunContext } from '../../../../../plugins/task_manager/server'; -import { PluginStartContract as EncryptedSavedObjectsStartContract } from '../../../../../plugins/encrypted_saved_objects/server'; +import { RunContext } from '../../../task_manager/server'; +import { PluginStartContract as EncryptedSavedObjectsStartContract } from '../../../encrypted_saved_objects/server'; import { ActionTaskParams, GetBasePathFunction, SpaceIdToNamespaceFunction } from '../types'; export interface TaskRunnerContext { diff --git a/x-pack/legacy/plugins/actions/server/lib/validate_with_schema.test.ts b/x-pack/plugins/actions/server/lib/validate_with_schema.test.ts similarity index 100% rename from x-pack/legacy/plugins/actions/server/lib/validate_with_schema.test.ts rename to x-pack/plugins/actions/server/lib/validate_with_schema.test.ts diff --git a/x-pack/legacy/plugins/actions/server/lib/validate_with_schema.ts b/x-pack/plugins/actions/server/lib/validate_with_schema.ts similarity index 100% rename from x-pack/legacy/plugins/actions/server/lib/validate_with_schema.ts rename to x-pack/plugins/actions/server/lib/validate_with_schema.ts diff --git a/x-pack/legacy/plugins/security/public/views/management/index.js b/x-pack/plugins/actions/server/mocks.ts similarity index 80% rename from x-pack/legacy/plugins/security/public/views/management/index.js rename to x-pack/plugins/actions/server/mocks.ts index 0ed6fe09ef80a..4e000160aa477 100644 --- a/x-pack/legacy/plugins/security/public/views/management/index.js +++ b/x-pack/plugins/actions/server/mocks.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import './management'; +export { actionsClientMock } from './actions_client.mock'; diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts new file mode 100644 index 0000000000000..6412593488cf8 --- /dev/null +++ b/x-pack/plugins/actions/server/plugin.ts @@ -0,0 +1,259 @@ +/* + * 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 { first, map } from 'rxjs/operators'; +import { + PluginInitializerContext, + Plugin, + CoreSetup, + CoreStart, + IClusterClient, + KibanaRequest, + Logger, + SharedGlobalConfig, + RequestHandler, + IContextProvider, + SavedObjectsServiceStart, +} from '../../../../src/core/server'; + +import { + PluginSetupContract as EncryptedSavedObjectsSetupContract, + PluginStartContract as EncryptedSavedObjectsStartContract, +} from '../../encrypted_saved_objects/server'; +import { TaskManagerSetupContract, TaskManagerStartContract } from '../../task_manager/server'; +import { LicensingPluginSetup } from '../../licensing/server'; +import { SpacesPluginSetup, SpacesServiceSetup } from '../../spaces/server'; + +import { ActionsConfig } from './config'; +import { Services } from './types'; +import { ActionExecutor, TaskRunnerFactory } from './lib'; +import { ActionsClient } from './actions_client'; +import { ActionTypeRegistry } from './action_type_registry'; +import { ExecuteOptions } from './create_execute_function'; +import { createExecuteFunction } from './create_execute_function'; +import { registerBuiltInActionTypes } from './builtin_action_types'; + +import { getActionsConfigurationUtilities } from './actions_config'; + +import { + createActionRoute, + deleteActionRoute, + findActionRoute, + getActionRoute, + updateActionRoute, + listActionTypesRoute, + executeActionRoute, +} from './routes'; +import { LicenseState } from './lib/license_state'; +import { IEventLogger, IEventLogService } from '../../event_log/server'; + +const EVENT_LOG_PROVIDER = 'actions'; +export const EVENT_LOG_ACTIONS = { + execute: 'execute', + executeViaHttp: 'execute-via-http', +}; + +export interface PluginSetupContract { + registerType: ActionTypeRegistry['register']; +} + +export interface PluginStartContract { + execute(options: ExecuteOptions): Promise; + getActionsClientWithRequest(request: KibanaRequest): Promise; +} + +export interface ActionsPluginsSetup { + taskManager: TaskManagerSetupContract; + encryptedSavedObjects: EncryptedSavedObjectsSetupContract; + licensing: LicensingPluginSetup; + spaces?: SpacesPluginSetup; + event_log: IEventLogService; +} +export interface ActionsPluginsStart { + encryptedSavedObjects: EncryptedSavedObjectsStartContract; + taskManager: TaskManagerStartContract; +} + +export class ActionsPlugin implements Plugin, PluginStartContract> { + private readonly kibanaIndex: Promise; + private readonly config: Promise; + + private readonly logger: Logger; + private serverBasePath?: string; + private adminClient?: IClusterClient; + private taskRunnerFactory?: TaskRunnerFactory; + private actionTypeRegistry?: ActionTypeRegistry; + private actionExecutor?: ActionExecutor; + private licenseState: LicenseState | null = null; + private spaces?: SpacesServiceSetup; + private eventLogger?: IEventLogger; + + constructor(initContext: PluginInitializerContext) { + this.config = initContext.config + .create() + .pipe(first()) + .toPromise(); + + this.kibanaIndex = initContext.config.legacy.globalConfig$ + .pipe( + first(), + map((config: SharedGlobalConfig) => config.kibana.index) + ) + .toPromise(); + + this.logger = initContext.logger.get('actions'); + } + + public async setup(core: CoreSetup, plugins: ActionsPluginsSetup): Promise { + // Encrypted attributes + // - `secrets` properties will be encrypted + // - `config` will be included in AAD + // - everything else excluded from AAD + plugins.encryptedSavedObjects.registerType({ + type: 'action', + attributesToEncrypt: new Set(['secrets']), + attributesToExcludeFromAAD: new Set(['name']), + }); + plugins.encryptedSavedObjects.registerType({ + type: 'action_task_params', + attributesToEncrypt: new Set(['apiKey']), + }); + + plugins.event_log.registerProviderActions(EVENT_LOG_PROVIDER, Object.values(EVENT_LOG_ACTIONS)); + this.eventLogger = plugins.event_log.getLogger({ + event: { provider: EVENT_LOG_PROVIDER }, + }); + + const actionExecutor = new ActionExecutor(); + const taskRunnerFactory = new TaskRunnerFactory(actionExecutor); + const actionsConfigUtils = getActionsConfigurationUtilities( + (await this.config) as ActionsConfig + ); + const actionTypeRegistry = new ActionTypeRegistry({ + taskRunnerFactory, + taskManager: plugins.taskManager, + actionsConfigUtils, + }); + this.taskRunnerFactory = taskRunnerFactory; + this.actionTypeRegistry = actionTypeRegistry; + this.serverBasePath = core.http.basePath.serverBasePath; + this.actionExecutor = actionExecutor; + this.adminClient = core.elasticsearch.adminClient; + this.spaces = plugins.spaces?.spacesService; + + registerBuiltInActionTypes({ + logger: this.logger, + actionTypeRegistry, + actionsConfigUtils, + }); + + core.http.registerRouteHandlerContext( + 'actions', + this.createRouteHandlerContext(await this.kibanaIndex) + ); + + // Routes + this.licenseState = new LicenseState(plugins.licensing.license$); + const router = core.http.createRouter(); + createActionRoute(router, this.licenseState); + deleteActionRoute(router, this.licenseState); + getActionRoute(router, this.licenseState); + findActionRoute(router, this.licenseState); + updateActionRoute(router, this.licenseState); + listActionTypesRoute(router, this.licenseState); + executeActionRoute(router, this.licenseState, actionExecutor); + + return { + registerType: actionTypeRegistry.register.bind(actionTypeRegistry), + }; + } + + public start(core: CoreStart, plugins: ActionsPluginsStart): PluginStartContract { + const { + logger, + actionExecutor, + actionTypeRegistry, + taskRunnerFactory, + kibanaIndex, + adminClient, + } = this; + + actionExecutor!.initialize({ + logger, + eventLogger: this.eventLogger!, + spaces: this.spaces, + getServices: this.getServicesFactory(core.savedObjects), + encryptedSavedObjectsPlugin: plugins.encryptedSavedObjects, + actionTypeRegistry: actionTypeRegistry!, + }); + + taskRunnerFactory!.initialize({ + encryptedSavedObjectsPlugin: plugins.encryptedSavedObjects, + getBasePath: this.getBasePath, + spaceIdToNamespace: this.spaceIdToNamespace, + }); + + return { + execute: createExecuteFunction({ + taskManager: plugins.taskManager, + getScopedSavedObjectsClient: core.savedObjects.getScopedClient, + getBasePath: this.getBasePath, + }), + // Ability to get an actions client from legacy code + async getActionsClientWithRequest(request: KibanaRequest) { + return new ActionsClient({ + savedObjectsClient: core.savedObjects.getScopedClient(request), + actionTypeRegistry: actionTypeRegistry!, + defaultKibanaIndex: await kibanaIndex, + scopedClusterClient: adminClient!.asScoped(request), + }); + }, + }; + } + + private getServicesFactory( + savedObjects: SavedObjectsServiceStart + ): (request: KibanaRequest) => Services { + const { adminClient } = this; + return request => ({ + callCluster: adminClient!.asScoped(request).callAsCurrentUser, + savedObjectsClient: savedObjects.getScopedClient(request), + }); + } + + private createRouteHandlerContext = ( + defaultKibanaIndex: string + ): IContextProvider, 'actions'> => { + const { actionTypeRegistry, adminClient } = this; + return async function actionsRouteHandlerContext(context, request) { + return { + getActionsClient: () => { + return new ActionsClient({ + savedObjectsClient: context.core!.savedObjects.client, + actionTypeRegistry: actionTypeRegistry!, + defaultKibanaIndex, + scopedClusterClient: adminClient!.asScoped(request), + }); + }, + listTypes: actionTypeRegistry!.list.bind(actionTypeRegistry!), + }; + }; + }; + + private spaceIdToNamespace = (spaceId?: string): string | undefined => { + return this.spaces && spaceId ? this.spaces.spaceIdToNamespace(spaceId) : undefined; + }; + + private getBasePath = (spaceId?: string): string => { + return this.spaces && spaceId ? this.spaces.getBasePath(spaceId) : this.serverBasePath!; + }; + + public stop() { + if (this.licenseState) { + this.licenseState.clean(); + } + } +} diff --git a/x-pack/plugins/actions/server/routes/_mock_handler_arguments.ts b/x-pack/plugins/actions/server/routes/_mock_handler_arguments.ts new file mode 100644 index 0000000000000..ec525adb8eab6 --- /dev/null +++ b/x-pack/plugins/actions/server/routes/_mock_handler_arguments.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { RequestHandlerContext, KibanaRequest, KibanaResponseFactory } from 'kibana/server'; +import { identity } from 'lodash'; +import { httpServerMock } from '../../../../../src/core/server/mocks'; + +export function mockHandlerArguments( + { actionsClient, listTypes: listTypesRes = [] }: any, + req: any, + res?: Array> +): [RequestHandlerContext, KibanaRequest, KibanaResponseFactory] { + const listTypes = jest.fn(() => listTypesRes); + return [ + ({ + actions: { + listTypes, + getActionsClient() { + return ( + actionsClient || { + get: jest.fn(), + delete: jest.fn(), + update: jest.fn(), + find: jest.fn(), + create: jest.fn(), + } + ); + }, + }, + } as unknown) as RequestHandlerContext, + req as KibanaRequest, + mockResponseFactory(res), + ]; +} + +export const mockResponseFactory = (resToMock: Array> = []) => { + const factory: jest.Mocked = httpServerMock.createResponseFactory(); + resToMock.forEach((key: string) => { + if (key in factory) { + Object.defineProperty(factory, key, { + value: jest.fn(identity), + }); + } + }); + return (factory as unknown) as KibanaResponseFactory; +}; diff --git a/x-pack/plugins/actions/server/routes/create.test.ts b/x-pack/plugins/actions/server/routes/create.test.ts new file mode 100644 index 0000000000000..6f7ebf2735edd --- /dev/null +++ b/x-pack/plugins/actions/server/routes/create.test.ts @@ -0,0 +1,135 @@ +/* + * 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 { createActionRoute } from './create'; +import { mockRouter, RouterMock } from '../../../../../src/core/server/http/router/router.mock'; +import { mockLicenseState } from '../lib/license_state.mock'; +import { verifyApiAccess } from '../lib/license_api_access'; +import { mockHandlerArguments } from './_mock_handler_arguments'; + +jest.mock('../lib/license_api_access.ts', () => ({ + verifyApiAccess: jest.fn(), +})); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('createActionRoute', () => { + it('creates an action with proper parameters', async () => { + const licenseState = mockLicenseState(); + const router: RouterMock = mockRouter.create(); + + createActionRoute(router, licenseState); + + const [config, handler] = router.post.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/api/action"`); + expect(config.options).toMatchInlineSnapshot(` + Object { + "tags": Array [ + "access:actions-all", + ], + } + `); + + const createResult = { + id: '1', + name: 'My name', + actionTypeId: 'abc', + config: { foo: true }, + }; + const actionsClient = { + create: jest.fn().mockResolvedValueOnce(createResult), + }; + + const [context, req, res] = mockHandlerArguments( + { actionsClient }, + { + body: { + name: 'My name', + actionTypeId: 'abc', + config: { foo: true }, + secrets: {}, + }, + }, + ['ok'] + ); + + expect(await handler(context, req, res)).toEqual({ body: createResult }); + + expect(actionsClient.create).toHaveBeenCalledTimes(1); + expect(actionsClient.create.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "action": Object { + "actionTypeId": "abc", + "config": Object { + "foo": true, + }, + "name": "My name", + "secrets": Object {}, + }, + }, + ] + `); + + expect(res.ok).toHaveBeenCalledWith({ + body: createResult, + }); + }); + + it('ensures the license allows creating actions', async () => { + const licenseState = mockLicenseState(); + const router: RouterMock = mockRouter.create(); + + createActionRoute(router, licenseState); + + const [, handler] = router.post.mock.calls[0]; + + const actionsClient = { + create: jest.fn().mockResolvedValueOnce({ + id: '1', + name: 'My name', + actionTypeId: 'abc', + config: { foo: true }, + }), + }; + + const [context, req, res] = mockHandlerArguments(actionsClient, {}); + + await handler(context, req, res); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); + + it('ensures the license check prevents creating actions', async () => { + const licenseState = mockLicenseState(); + const router: RouterMock = mockRouter.create(); + + (verifyApiAccess as jest.Mock).mockImplementation(() => { + throw new Error('OMG'); + }); + + createActionRoute(router, licenseState); + + const [, handler] = router.post.mock.calls[0]; + + const actionsClient = { + create: jest.fn().mockResolvedValueOnce({ + id: '1', + name: 'My name', + actionTypeId: 'abc', + config: { foo: true }, + }), + }; + + const [context, req, res] = mockHandlerArguments(actionsClient, {}); + + expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); +}); diff --git a/x-pack/plugins/actions/server/routes/create.ts b/x-pack/plugins/actions/server/routes/create.ts new file mode 100644 index 0000000000000..f8f9aff9323a0 --- /dev/null +++ b/x-pack/plugins/actions/server/routes/create.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema, TypeOf } from '@kbn/config-schema'; +import { + IRouter, + RequestHandlerContext, + KibanaRequest, + IKibanaResponse, + KibanaResponseFactory, +} from 'kibana/server'; +import { ActionResult } from '../types'; +import { LicenseState } from '../lib/license_state'; +import { verifyApiAccess } from '../lib/license_api_access'; + +export const bodySchema = schema.object({ + name: schema.string(), + actionTypeId: schema.string(), + config: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }), + secrets: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }), +}); + +export const createActionRoute = (router: IRouter, licenseState: LicenseState) => { + router.post( + { + path: `/api/action`, + validate: { + body: bodySchema, + }, + options: { + tags: ['access:actions-all'], + }, + }, + router.handleLegacyErrors(async function( + context: RequestHandlerContext, + req: KibanaRequest, any>, + res: KibanaResponseFactory + ): Promise> { + verifyApiAccess(licenseState); + + const actionsClient = context.actions.getActionsClient(); + const action = req.body; + const actionRes: ActionResult = await actionsClient.create({ action }); + return res.ok({ + body: actionRes, + }); + }) + ); +}; diff --git a/x-pack/plugins/actions/server/routes/delete.test.ts b/x-pack/plugins/actions/server/routes/delete.test.ts new file mode 100644 index 0000000000000..e44f325413428 --- /dev/null +++ b/x-pack/plugins/actions/server/routes/delete.test.ts @@ -0,0 +1,111 @@ +/* + * 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 { deleteActionRoute } from './delete'; +import { mockRouter, RouterMock } from '../../../../../src/core/server/http/router/router.mock'; +import { mockLicenseState } from '../lib/license_state.mock'; +import { verifyApiAccess } from '../lib/license_api_access'; +import { mockHandlerArguments } from './_mock_handler_arguments'; + +jest.mock('../lib/license_api_access.ts', () => ({ + verifyApiAccess: jest.fn(), +})); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('deleteActionRoute', () => { + it('deletes an action with proper parameters', async () => { + const licenseState = mockLicenseState(); + const router: RouterMock = mockRouter.create(); + + deleteActionRoute(router, licenseState); + + const [config, handler] = router.delete.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/api/action/{id}"`); + expect(config.options).toMatchInlineSnapshot(` + Object { + "tags": Array [ + "access:actions-all", + ], + } + `); + + const actionsClient = { + delete: jest.fn().mockResolvedValueOnce({}), + }; + + const [context, req, res] = mockHandlerArguments( + { actionsClient }, + { + params: { + id: '1', + }, + }, + ['noContent'] + ); + + expect(await handler(context, req, res)).toEqual(undefined); + + expect(actionsClient.delete).toHaveBeenCalledTimes(1); + expect(actionsClient.delete.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "id": "1", + }, + ] + `); + + expect(res.noContent).toHaveBeenCalled(); + }); + + it('ensures the license allows deleting actions', async () => { + const licenseState = mockLicenseState(); + const router: RouterMock = mockRouter.create(); + + deleteActionRoute(router, licenseState); + + const [, handler] = router.delete.mock.calls[0]; + + const actionsClient = { + delete: jest.fn().mockResolvedValueOnce({}), + }; + + const [context, req, res] = mockHandlerArguments(actionsClient, { + params: { id: '1' }, + }); + + await handler(context, req, res); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); + + it('ensures the license check prevents deleting actions', async () => { + const licenseState = mockLicenseState(); + const router: RouterMock = mockRouter.create(); + + (verifyApiAccess as jest.Mock).mockImplementation(() => { + throw new Error('OMG'); + }); + + deleteActionRoute(router, licenseState); + + const [, handler] = router.delete.mock.calls[0]; + + const actionsClient = { + delete: jest.fn().mockResolvedValueOnce({}), + }; + + const [context, req, res] = mockHandlerArguments(actionsClient, { + id: '1', + }); + + expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); +}); diff --git a/x-pack/plugins/actions/server/routes/delete.ts b/x-pack/plugins/actions/server/routes/delete.ts new file mode 100644 index 0000000000000..d96523997ad34 --- /dev/null +++ b/x-pack/plugins/actions/server/routes/delete.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +/* + * 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, TypeOf } from '@kbn/config-schema'; +import { + IRouter, + RequestHandlerContext, + KibanaRequest, + IKibanaResponse, + KibanaResponseFactory, +} from 'kibana/server'; +import { LicenseState } from '../lib/license_state'; +import { verifyApiAccess } from '../lib/license_api_access'; + +const paramSchema = schema.object({ + id: schema.string(), +}); + +export const deleteActionRoute = (router: IRouter, licenseState: LicenseState) => { + router.delete( + { + path: `/api/action/{id}`, + validate: { + params: paramSchema, + }, + options: { + tags: ['access:actions-all'], + }, + }, + router.handleLegacyErrors(async function( + context: RequestHandlerContext, + req: KibanaRequest, any, any, any>, + res: KibanaResponseFactory + ): Promise> { + verifyApiAccess(licenseState); + const actionsClient = context.actions.getActionsClient(); + const { id } = req.params; + await actionsClient.delete({ id }); + return res.noContent(); + }) + ); +}; diff --git a/x-pack/plugins/actions/server/routes/execute.test.ts b/x-pack/plugins/actions/server/routes/execute.test.ts new file mode 100644 index 0000000000000..d8b57b2fb849a --- /dev/null +++ b/x-pack/plugins/actions/server/routes/execute.test.ts @@ -0,0 +1,184 @@ +/* + * 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 { executeActionRoute } from './execute'; +import { mockRouter, RouterMock } from '../../../../../src/core/server/http/router/router.mock'; +import { mockLicenseState } from '../lib/license_state.mock'; +import { verifyApiAccess } from '../lib/license_api_access'; +import { mockHandlerArguments } from './_mock_handler_arguments'; +import { ActionExecutorContract } from '../lib'; + +jest.mock('../lib/license_api_access.ts', () => ({ + verifyApiAccess: jest.fn(), +})); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('executeActionRoute', () => { + it('executes an action with proper parameters', async () => { + const licenseState = mockLicenseState(); + const router: RouterMock = mockRouter.create(); + + const [context, req, res] = mockHandlerArguments( + {}, + { + body: { + params: { + someData: 'data', + }, + }, + params: { + id: '1', + }, + }, + ['ok'] + ); + + const executeResult = { + actionId: '1', + status: 'ok', + }; + const actionExecutor = { + initialize: jest.fn(), + execute: jest.fn(async ({ params, request, actionId }) => { + return executeResult; + }), + } as jest.Mocked; + + executeActionRoute(router, licenseState, actionExecutor); + + const [config, handler] = router.post.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/api/action/{id}/_execute"`); + expect(config.options).toMatchInlineSnapshot(` + Object { + "tags": Array [ + "access:actions-read", + ], + } + `); + + expect(await handler(context, req, res)).toEqual({ body: executeResult }); + + expect(actionExecutor.execute).toHaveBeenCalledWith({ + actionId: '1', + params: { + someData: 'data', + }, + request: req, + }); + + expect(res.ok).toHaveBeenCalled(); + }); + + it('returns a "204 NO CONTENT" when the executor returns a nullish value', async () => { + const licenseState = mockLicenseState(); + const router: RouterMock = mockRouter.create(); + + const [context, req, res] = mockHandlerArguments( + {}, + { + body: { + params: {}, + }, + params: { + id: '1', + }, + }, + ['noContent'] + ); + + const actionExecutor = { + initialize: jest.fn(), + execute: jest.fn(), + } as jest.Mocked; + + executeActionRoute(router, licenseState, actionExecutor); + + const [, handler] = router.post.mock.calls[0]; + + expect(await handler(context, req, res)).toEqual(undefined); + + expect(actionExecutor.execute).toHaveBeenCalledWith({ + actionId: '1', + params: {}, + request: req, + }); + + expect(res.ok).not.toHaveBeenCalled(); + expect(res.noContent).toHaveBeenCalled(); + }); + + it('ensures the license allows action execution', async () => { + const licenseState = mockLicenseState(); + const router: RouterMock = mockRouter.create(); + + const [context, req, res] = mockHandlerArguments( + {}, + { + body: {}, + params: {}, + }, + ['ok'] + ); + + const actionExecutor = { + initialize: jest.fn(), + execute: jest.fn(async ({ params, request, actionId }) => { + return { + actionId: '1', + status: 'ok', + }; + }), + } as jest.Mocked; + + executeActionRoute(router, licenseState, actionExecutor); + + const [, handler] = router.post.mock.calls[0]; + + await handler(context, req, res); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); + + it('ensures the license check prevents action execution', async () => { + const licenseState = mockLicenseState(); + const router: RouterMock = mockRouter.create(); + + (verifyApiAccess as jest.Mock).mockImplementation(() => { + throw new Error('OMG'); + }); + + const [context, req, res] = mockHandlerArguments( + {}, + { + body: {}, + params: {}, + }, + ['ok'] + ); + + const actionExecutor = { + initialize: jest.fn(), + execute: jest.fn(async ({ params, request, actionId }) => { + return { + actionId: '1', + status: 'ok', + }; + }), + } as jest.Mocked; + + executeActionRoute(router, licenseState, actionExecutor); + + const [, handler] = router.post.mock.calls[0]; + + expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); +}); diff --git a/x-pack/plugins/actions/server/routes/execute.ts b/x-pack/plugins/actions/server/routes/execute.ts new file mode 100644 index 0000000000000..afccee3b5e70e --- /dev/null +++ b/x-pack/plugins/actions/server/routes/execute.ts @@ -0,0 +1,64 @@ +/* + * 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, TypeOf } from '@kbn/config-schema'; +import { + IRouter, + RequestHandlerContext, + KibanaRequest, + IKibanaResponse, + KibanaResponseFactory, +} from 'kibana/server'; +import { LicenseState } from '../lib/license_state'; +import { verifyApiAccess } from '../lib/license_api_access'; + +import { ActionExecutorContract } from '../lib'; +import { ActionTypeExecutorResult } from '../types'; + +const paramSchema = schema.object({ + id: schema.string(), +}); + +const bodySchema = schema.object({ + params: schema.recordOf(schema.string(), schema.any()), +}); + +export const executeActionRoute = ( + router: IRouter, + licenseState: LicenseState, + actionExecutor: ActionExecutorContract +) => { + router.post( + { + path: '/api/action/{id}/_execute', + validate: { + body: bodySchema, + params: paramSchema, + }, + options: { + tags: ['access:actions-read'], + }, + }, + router.handleLegacyErrors(async function( + context: RequestHandlerContext, + req: KibanaRequest, any, TypeOf, any>, + res: KibanaResponseFactory + ): Promise> { + verifyApiAccess(licenseState); + const { params } = req.body; + const { id } = req.params; + const body: ActionTypeExecutorResult = await actionExecutor.execute({ + params, + request: req, + actionId: id, + }); + return body + ? res.ok({ + body, + }) + : res.noContent(); + }) + ); +}; diff --git a/x-pack/plugins/actions/server/routes/find.test.ts b/x-pack/plugins/actions/server/routes/find.test.ts new file mode 100644 index 0000000000000..862e26132fdc3 --- /dev/null +++ b/x-pack/plugins/actions/server/routes/find.test.ts @@ -0,0 +1,151 @@ +/* + * 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 { findActionRoute } from './find'; +import { mockRouter, RouterMock } from '../../../../../src/core/server/http/router/router.mock'; +import { mockLicenseState } from '../lib/license_state.mock'; +import { verifyApiAccess } from '../lib/license_api_access'; +import { mockHandlerArguments } from './_mock_handler_arguments'; + +jest.mock('../lib/license_api_access.ts', () => ({ + verifyApiAccess: jest.fn(), +})); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('findActionRoute', () => { + it('finds actions with proper parameters', async () => { + const licenseState = mockLicenseState(); + const router: RouterMock = mockRouter.create(); + + findActionRoute(router, licenseState); + + const [config, handler] = router.get.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/api/action/_find"`); + expect(config.options).toMatchInlineSnapshot(` + Object { + "tags": Array [ + "access:actions-read", + ], + } + `); + + const findResult = { + page: 1, + perPage: 1, + total: 0, + data: [], + }; + const actionsClient = { + find: jest.fn().mockResolvedValueOnce(findResult), + }; + + const [context, req, res] = mockHandlerArguments( + { actionsClient }, + { + query: { + per_page: 1, + page: 1, + default_search_operator: 'OR', + }, + }, + ['ok'] + ); + + expect(await handler(context, req, res)).toMatchInlineSnapshot(` + Object { + "body": Object { + "data": Array [], + "page": 1, + "perPage": 1, + "total": 0, + }, + } + `); + + expect(actionsClient.find).toHaveBeenCalledTimes(1); + expect(actionsClient.find.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "options": Object { + "defaultSearchOperator": "OR", + "fields": undefined, + "filter": undefined, + "page": 1, + "perPage": 1, + "search": undefined, + "sortField": undefined, + }, + }, + ] + `); + + expect(res.ok).toHaveBeenCalledWith({ + body: findResult, + }); + }); + + it('ensures the license allows finding actions', async () => { + const licenseState = mockLicenseState(); + const router: RouterMock = mockRouter.create(); + + findActionRoute(router, licenseState); + + const [, handler] = router.get.mock.calls[0]; + + const actionsClient = { + find: jest.fn().mockResolvedValueOnce({ + page: 1, + perPage: 1, + total: 0, + data: [], + }), + }; + + const [context, req, res] = mockHandlerArguments(actionsClient, { + query: { + per_page: 1, + page: 1, + default_search_operator: 'OR', + }, + }); + + await handler(context, req, res); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); + + it('ensures the license check prevents finding actions', async () => { + const licenseState = mockLicenseState(); + const router: RouterMock = mockRouter.create(); + + (verifyApiAccess as jest.Mock).mockImplementation(() => { + throw new Error('OMG'); + }); + + findActionRoute(router, licenseState); + + const [, handler] = router.get.mock.calls[0]; + + const [context, req, res] = mockHandlerArguments( + {}, + { + query: { + per_page: 1, + page: 1, + default_search_operator: 'OR', + }, + }, + ['ok'] + ); + expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); +}); diff --git a/x-pack/plugins/actions/server/routes/find.ts b/x-pack/plugins/actions/server/routes/find.ts new file mode 100644 index 0000000000000..04656d19bfeb4 --- /dev/null +++ b/x-pack/plugins/actions/server/routes/find.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 { schema, TypeOf } from '@kbn/config-schema'; +import { + IRouter, + RequestHandlerContext, + KibanaRequest, + IKibanaResponse, + KibanaResponseFactory, +} from 'kibana/server'; +import { FindOptions } from '../../../../legacy/plugins/alerting/server/alerts_client'; +import { LicenseState } from '../lib/license_state'; +import { verifyApiAccess } from '../lib/license_api_access'; + +// config definition +const querySchema = schema.object({ + per_page: schema.number({ defaultValue: 20, min: 0 }), + page: schema.number({ defaultValue: 1, min: 1 }), + search: schema.maybe(schema.string()), + default_search_operator: schema.oneOf([schema.literal('OR'), schema.literal('AND')], { + defaultValue: 'OR', + }), + search_fields: schema.maybe(schema.oneOf([schema.arrayOf(schema.string()), schema.string()])), + sort_field: schema.maybe(schema.string()), + has_reference: schema.maybe( + // use nullable as maybe is currently broken + // in config-schema + schema.nullable( + schema.object({ + type: schema.string(), + id: schema.string(), + }) + ) + ), + fields: schema.maybe(schema.arrayOf(schema.string())), + filter: schema.maybe(schema.string()), +}); + +export const findActionRoute = (router: IRouter, licenseState: LicenseState) => { + router.get( + { + path: `/api/action/_find`, + validate: { + query: querySchema, + }, + options: { + tags: ['access:actions-read'], + }, + }, + router.handleLegacyErrors(async function( + context: RequestHandlerContext, + req: KibanaRequest, any, any>, + res: KibanaResponseFactory + ): Promise> { + verifyApiAccess(licenseState); + const actionsClient = context.actions.getActionsClient(); + const query = req.query; + const options: FindOptions['options'] = { + perPage: query.per_page, + page: query.page, + search: query.search, + defaultSearchOperator: query.default_search_operator, + sortField: query.sort_field, + fields: query.fields, + filter: query.filter, + }; + + if (query.search_fields) { + options.searchFields = Array.isArray(query.search_fields) + ? query.search_fields + : [query.search_fields]; + } + + if (query.has_reference) { + options.hasReference = query.has_reference; + } + + const findResult = await actionsClient.find({ + options, + }); + return res.ok({ + body: findResult, + }); + }) + ); +}; diff --git a/x-pack/plugins/actions/server/routes/get.test.ts b/x-pack/plugins/actions/server/routes/get.test.ts new file mode 100644 index 0000000000000..8762a68b192f2 --- /dev/null +++ b/x-pack/plugins/actions/server/routes/get.test.ts @@ -0,0 +1,139 @@ +/* + * 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 { getActionRoute } from './get'; +import { mockRouter, RouterMock } from '../../../../../src/core/server/http/router/router.mock'; +import { mockLicenseState } from '../lib/license_state.mock'; +import { verifyApiAccess } from '../lib/license_api_access'; +import { mockHandlerArguments } from './_mock_handler_arguments'; + +jest.mock('../lib/license_api_access.ts', () => ({ + verifyApiAccess: jest.fn(), +})); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('getActionRoute', () => { + it('gets an action with proper parameters', async () => { + const licenseState = mockLicenseState(); + const router: RouterMock = mockRouter.create(); + + getActionRoute(router, licenseState); + + const [config, handler] = router.get.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/api/action/{id}"`); + expect(config.options).toMatchInlineSnapshot(` + Object { + "tags": Array [ + "access:actions-read", + ], + } + `); + + const getResult = { + id: '1', + actionTypeId: '2', + name: 'action name', + config: {}, + }; + const actionsClient = { + get: jest.fn().mockResolvedValueOnce(getResult), + }; + + const [context, req, res] = mockHandlerArguments( + { actionsClient }, + { + params: { id: '1' }, + }, + ['ok'] + ); + + expect(await handler(context, req, res)).toMatchInlineSnapshot(` + Object { + "body": Object { + "actionTypeId": "2", + "config": Object {}, + "id": "1", + "name": "action name", + }, + } + `); + + expect(actionsClient.get).toHaveBeenCalledTimes(1); + expect(actionsClient.get.mock.calls[0][0].id).toEqual('1'); + + expect(res.ok).toHaveBeenCalledWith({ + body: getResult, + }); + }); + + it('ensures the license allows getting actions', async () => { + const licenseState = mockLicenseState(); + const router: RouterMock = mockRouter.create(); + + getActionRoute(router, licenseState); + + const [, handler] = router.get.mock.calls[0]; + + const actionsClient = { + get: jest.fn().mockResolvedValueOnce({ + id: '1', + actionTypeId: '2', + name: 'action name', + config: {}, + }), + }; + + const [context, req, res] = mockHandlerArguments( + actionsClient, + { + params: { id: '1' }, + }, + ['ok'] + ); + + await handler(context, req, res); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); + + it('ensures the license check prevents getting actions', async () => { + const licenseState = mockLicenseState(); + const router: RouterMock = mockRouter.create(); + + (verifyApiAccess as jest.Mock).mockImplementation(() => { + throw new Error('OMG'); + }); + + getActionRoute(router, licenseState); + + const [, handler] = router.get.mock.calls[0]; + + const actionsClient = { + get: jest.fn().mockResolvedValueOnce({ + id: '1', + actionTypeId: '2', + name: 'action name', + config: {}, + }), + }; + + const [context, req, res] = mockHandlerArguments( + actionsClient, + { + params: { id: '1' }, + }, + ['ok'] + ); + + expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); +}); diff --git a/x-pack/plugins/actions/server/routes/get.ts b/x-pack/plugins/actions/server/routes/get.ts new file mode 100644 index 0000000000000..26aa74da5d36b --- /dev/null +++ b/x-pack/plugins/actions/server/routes/get.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema, TypeOf } from '@kbn/config-schema'; +import { + IRouter, + RequestHandlerContext, + KibanaRequest, + IKibanaResponse, + KibanaResponseFactory, +} from 'kibana/server'; +import { LicenseState } from '../lib/license_state'; +import { verifyApiAccess } from '../lib/license_api_access'; + +const paramSchema = schema.object({ + id: schema.string(), +}); + +export const getActionRoute = (router: IRouter, licenseState: LicenseState) => { + router.get( + { + path: `/api/action/{id}`, + validate: { + params: paramSchema, + }, + options: { + tags: ['access:actions-read'], + }, + }, + router.handleLegacyErrors(async function( + context: RequestHandlerContext, + req: KibanaRequest, any, any, any>, + res: KibanaResponseFactory + ): Promise> { + verifyApiAccess(licenseState); + const actionsClient = context.actions.getActionsClient(); + const { id } = req.params; + return res.ok({ + body: await actionsClient.get({ id }), + }); + }) + ); +}; diff --git a/x-pack/legacy/plugins/actions/server/routes/index.ts b/x-pack/plugins/actions/server/routes/index.ts similarity index 91% rename from x-pack/legacy/plugins/actions/server/routes/index.ts rename to x-pack/plugins/actions/server/routes/index.ts index 95e2d008d90ac..33191132bece5 100644 --- a/x-pack/legacy/plugins/actions/server/routes/index.ts +++ b/x-pack/plugins/actions/server/routes/index.ts @@ -10,4 +10,4 @@ export { findActionRoute } from './find'; export { getActionRoute } from './get'; export { updateActionRoute } from './update'; export { listActionTypesRoute } from './list_action_types'; -export { getExecuteActionRoute } from './execute'; +export { executeActionRoute } from './execute'; diff --git a/x-pack/plugins/actions/server/routes/list_action_types.test.ts b/x-pack/plugins/actions/server/routes/list_action_types.test.ts new file mode 100644 index 0000000000000..87cc4dfee5336 --- /dev/null +++ b/x-pack/plugins/actions/server/routes/list_action_types.test.ts @@ -0,0 +1,147 @@ +/* + * 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 { listActionTypesRoute } from './list_action_types'; +import { mockRouter, RouterMock } from '../../../../../src/core/server/http/router/router.mock'; +import { mockLicenseState } from '../lib/license_state.mock'; +import { verifyApiAccess } from '../lib/license_api_access'; +import { mockHandlerArguments } from './_mock_handler_arguments'; + +jest.mock('../lib/license_api_access.ts', () => ({ + verifyApiAccess: jest.fn(), +})); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('listActionTypesRoute', () => { + it('lists action types with proper parameters', async () => { + const licenseState = mockLicenseState(); + const router: RouterMock = mockRouter.create(); + + listActionTypesRoute(router, licenseState); + + const [config, handler] = router.get.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/api/action/types"`); + expect(config.options).toMatchInlineSnapshot(` + Object { + "tags": Array [ + "access:actions-read", + ], + } + `); + + const listTypes = [ + { + id: '1', + name: 'name', + enabled: true, + }, + ]; + + const [context, req, res] = mockHandlerArguments({ listTypes }, {}, ['ok']); + + expect(await handler(context, req, res)).toMatchInlineSnapshot(` + Object { + "body": Array [ + Object { + "enabled": true, + "id": "1", + "name": "name", + }, + ], + } + `); + + expect(context.actions.listTypes).toHaveBeenCalledTimes(1); + + expect(res.ok).toHaveBeenCalledWith({ + body: listTypes, + }); + }); + + it('ensures the license allows listing action types', async () => { + const licenseState = mockLicenseState(); + const router: RouterMock = mockRouter.create(); + + listActionTypesRoute(router, licenseState); + + const [config, handler] = router.get.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/api/action/types"`); + expect(config.options).toMatchInlineSnapshot(` + Object { + "tags": Array [ + "access:actions-read", + ], + } + `); + + const listTypes = [ + { + id: '1', + name: 'name', + enabled: true, + }, + ]; + + const [context, req, res] = mockHandlerArguments( + { listTypes }, + { + params: { id: '1' }, + }, + ['ok'] + ); + + await handler(context, req, res); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); + + it('ensures the license check prevents listing action types', async () => { + const licenseState = mockLicenseState(); + const router: RouterMock = mockRouter.create(); + + (verifyApiAccess as jest.Mock).mockImplementation(() => { + throw new Error('OMG'); + }); + + listActionTypesRoute(router, licenseState); + + const [config, handler] = router.get.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/api/action/types"`); + expect(config.options).toMatchInlineSnapshot(` + Object { + "tags": Array [ + "access:actions-read", + ], + } + `); + + const listTypes = [ + { + id: '1', + name: 'name', + enabled: true, + }, + ]; + + const [context, req, res] = mockHandlerArguments( + { listTypes }, + { + params: { id: '1' }, + }, + ['ok'] + ); + + expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); +}); diff --git a/x-pack/plugins/actions/server/routes/list_action_types.ts b/x-pack/plugins/actions/server/routes/list_action_types.ts new file mode 100644 index 0000000000000..0b9791eedb39c --- /dev/null +++ b/x-pack/plugins/actions/server/routes/list_action_types.ts @@ -0,0 +1,37 @@ +/* + * 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 { + IRouter, + RequestHandlerContext, + KibanaRequest, + IKibanaResponse, + KibanaResponseFactory, +} from 'kibana/server'; +import { LicenseState } from '../lib/license_state'; +import { verifyApiAccess } from '../lib/license_api_access'; + +export const listActionTypesRoute = (router: IRouter, licenseState: LicenseState) => { + router.get( + { + path: `/api/action/types`, + validate: {}, + options: { + tags: ['access:actions-read'], + }, + }, + router.handleLegacyErrors(async function( + context: RequestHandlerContext, + req: KibanaRequest, + res: KibanaResponseFactory + ): Promise> { + verifyApiAccess(licenseState); + return res.ok({ + body: context.actions.listTypes(), + }); + }) + ); +}; diff --git a/x-pack/plugins/actions/server/routes/update.test.ts b/x-pack/plugins/actions/server/routes/update.test.ts new file mode 100644 index 0000000000000..1090193761395 --- /dev/null +++ b/x-pack/plugins/actions/server/routes/update.test.ts @@ -0,0 +1,168 @@ +/* + * 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 { updateActionRoute } from './update'; +import { mockRouter, RouterMock } from '../../../../../src/core/server/http/router/router.mock'; +import { mockLicenseState } from '../lib/license_state.mock'; +import { verifyApiAccess } from '../lib/license_api_access'; +import { mockHandlerArguments } from './_mock_handler_arguments'; + +jest.mock('../lib/license_api_access.ts', () => ({ + verifyApiAccess: jest.fn(), +})); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('updateActionRoute', () => { + it('updates an action with proper parameters', async () => { + const licenseState = mockLicenseState(); + const router: RouterMock = mockRouter.create(); + + updateActionRoute(router, licenseState); + + const [config, handler] = router.put.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/api/action/{id}"`); + expect(config.options).toMatchInlineSnapshot(` + Object { + "tags": Array [ + "access:actions-all", + ], + } + `); + + const updateResult = { + id: '1', + actionTypeId: 'my-action-type-id', + name: 'My name', + config: { foo: true }, + }; + + const actionsClient = { + update: jest.fn().mockResolvedValueOnce(updateResult), + }; + + const [context, req, res] = mockHandlerArguments( + { actionsClient }, + { + params: { + id: '1', + }, + body: { + name: 'My name', + config: { foo: true }, + secrets: { key: 'i8oh34yf9783y39' }, + }, + }, + ['ok'] + ); + + expect(await handler(context, req, res)).toEqual({ body: updateResult }); + + expect(actionsClient.update).toHaveBeenCalledTimes(1); + expect(actionsClient.update.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "action": Object { + "config": Object { + "foo": true, + }, + "name": "My name", + "secrets": Object { + "key": "i8oh34yf9783y39", + }, + }, + "id": "1", + }, + ] + `); + + expect(res.ok).toHaveBeenCalled(); + }); + + it('ensures the license allows deleting actions', async () => { + const licenseState = mockLicenseState(); + const router: RouterMock = mockRouter.create(); + + updateActionRoute(router, licenseState); + + const [, handler] = router.put.mock.calls[0]; + + const updateResult = { + id: '1', + actionTypeId: 'my-action-type-id', + name: 'My name', + config: { foo: true }, + }; + + const actionsClient = { + update: jest.fn().mockResolvedValueOnce(updateResult), + }; + + const [context, req, res] = mockHandlerArguments( + { actionsClient }, + { + params: { + id: '1', + }, + body: { + name: 'My name', + config: { foo: true }, + secrets: { key: 'i8oh34yf9783y39' }, + }, + }, + ['ok'] + ); + + await handler(context, req, res); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); + + it('ensures the license check prevents deleting actions', async () => { + const licenseState = mockLicenseState(); + const router: RouterMock = mockRouter.create(); + + (verifyApiAccess as jest.Mock).mockImplementation(() => { + throw new Error('OMG'); + }); + + updateActionRoute(router, licenseState); + + const [, handler] = router.put.mock.calls[0]; + + const updateResult = { + id: '1', + actionTypeId: 'my-action-type-id', + name: 'My name', + config: { foo: true }, + }; + + const actionsClient = { + update: jest.fn().mockResolvedValueOnce(updateResult), + }; + + const [context, req, res] = mockHandlerArguments( + { actionsClient }, + { + params: { + id: '1', + }, + body: { + name: 'My name', + config: { foo: true }, + secrets: { key: 'i8oh34yf9783y39' }, + }, + }, + ['ok'] + ); + + expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); +}); diff --git a/x-pack/plugins/actions/server/routes/update.ts b/x-pack/plugins/actions/server/routes/update.ts new file mode 100644 index 0000000000000..9c5f32e8b9119 --- /dev/null +++ b/x-pack/plugins/actions/server/routes/update.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema, TypeOf } from '@kbn/config-schema'; +import { + IRouter, + RequestHandlerContext, + KibanaRequest, + IKibanaResponse, + KibanaResponseFactory, +} from 'kibana/server'; +import { LicenseState } from '../lib/license_state'; +import { verifyApiAccess } from '../lib/license_api_access'; + +const paramSchema = schema.object({ + id: schema.string(), +}); + +const bodySchema = schema.object({ + name: schema.string(), + config: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }), + secrets: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }), +}); + +export const updateActionRoute = (router: IRouter, licenseState: LicenseState) => { + router.put( + { + path: `/api/action/{id}`, + validate: { + body: bodySchema, + params: paramSchema, + }, + options: { + tags: ['access:actions-all'], + }, + }, + router.handleLegacyErrors(async function( + context: RequestHandlerContext, + req: KibanaRequest, any, TypeOf, any>, + res: KibanaResponseFactory + ): Promise> { + verifyApiAccess(licenseState); + const actionsClient = context.actions.getActionsClient(); + const { id } = req.params; + const { name, config, secrets } = req.body; + return res.ok({ + body: await actionsClient.update({ + id, + action: { name, config, secrets }, + }), + }); + }) + ); +}; diff --git a/x-pack/legacy/plugins/actions/server/types.ts b/x-pack/plugins/actions/server/types.ts similarity index 88% rename from x-pack/legacy/plugins/actions/server/types.ts rename to x-pack/plugins/actions/server/types.ts index 6a6fb7d660cbb..2358f499c9f98 100644 --- a/x-pack/legacy/plugins/actions/server/types.ts +++ b/x-pack/plugins/actions/server/types.ts @@ -4,9 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SavedObjectsClientContract, SavedObjectAttributes } from 'src/core/server'; +import { SavedObjectsClientContract, SavedObjectAttributes } from '../../../../src/core/server'; import { ActionTypeRegistry } from './action_type_registry'; import { PluginSetupContract, PluginStartContract } from './plugin'; +import { ActionsClient } from './actions_client'; export type WithoutQueryAndParams = Pick>; export type GetServicesFunction = (request: any) => Services; @@ -19,6 +20,15 @@ export interface Services { savedObjectsClient: SavedObjectsClientContract; } +declare module 'src/core/server' { + interface RequestHandlerContext { + actions: { + getActionsClient: () => ActionsClient; + listTypes: ActionTypeRegistry['list']; + }; + } +} + export interface ActionsPlugin { setup: PluginSetupContract; start: PluginStartContract; @@ -63,7 +73,7 @@ export interface ActionTypeExecutorResult { // signature of the action type executor function export type ExecutorType = ( options: ActionTypeExecutorOptions -) => Promise; +) => Promise; interface ValidatorType { validate(value: any): any; diff --git a/x-pack/plugins/apm/kibana.json b/x-pack/plugins/apm/kibana.json index 40fee07742cf6..d60846131da74 100644 --- a/x-pack/plugins/apm/kibana.json +++ b/x-pack/plugins/apm/kibana.json @@ -5,5 +5,6 @@ "kibanaVersion": "kibana", "configPath": ["xpack", "apm"], "ui": false, - "requiredPlugins": ["apm_oss", "data", "home"] + "requiredPlugins": ["apm_oss", "data", "home"], + "optionalPlugins": ["cloud"] } diff --git a/x-pack/plugins/apm/server/plugin.ts b/x-pack/plugins/apm/server/plugin.ts index 11d91efdf6b01..83ece92aebe45 100644 --- a/x-pack/plugins/apm/server/plugin.ts +++ b/x-pack/plugins/apm/server/plugin.ts @@ -18,6 +18,9 @@ import { createApmAgentConfigurationIndex } from '../../../legacy/plugins/apm/se import { createApmApi } from '../../../legacy/plugins/apm/server/routes/create_apm_api'; import { getApmIndices } from '../../../legacy/plugins/apm/server/lib/settings/apm_indices/get_apm_indices'; import { APMConfig, mergeConfigs, APMXPackConfig } from '.'; +import { HomeServerPluginSetup } from '../../../../src/plugins/home/server'; +import { tutorialProvider } from './tutorial'; +import { CloudSetup } from '../../cloud/server'; export interface LegacySetup { server: Server; @@ -44,6 +47,8 @@ export class APMPlugin implements Plugin { core: CoreSetup, plugins: { apm_oss: APMOSSPlugin extends Plugin ? TSetup : never; + home: HomeServerPluginSetup; + cloud?: CloudSetup; } ) { const config$ = this.initContext.config.create(); @@ -68,6 +73,21 @@ export class APMPlugin implements Plugin { }); }); + plugins.home.tutorials.registerTutorial( + tutorialProvider({ + isEnabled: this.currentConfig['xpack.apm.ui.enabled'], + indexPatternTitle: this.currentConfig['apm_oss.indexPattern'], + cloud: plugins.cloud, + indices: { + errorIndices: this.currentConfig['apm_oss.errorIndices'], + metricsIndices: this.currentConfig['apm_oss.metricsIndices'], + onboardingIndices: this.currentConfig['apm_oss.onboardingIndices'], + sourcemapIndices: this.currentConfig['apm_oss.sourcemapIndices'], + transactionIndices: this.currentConfig['apm_oss.transactionIndices'], + }, + }) + ); + return { config$: mergedConfig$, registerLegacyAPI: once((__LEGACY: LegacySetup) => { diff --git a/src/legacy/core_plugins/kibana/server/tutorials/apm/envs/elastic_cloud.js b/x-pack/plugins/apm/server/tutorial/envs/elastic_cloud.ts similarity index 60% rename from src/legacy/core_plugins/kibana/server/tutorials/apm/envs/elastic_cloud.js rename to x-pack/plugins/apm/server/tutorial/envs/elastic_cloud.ts index 64698a54eed3c..9c66dd299b2a5 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/apm/envs/elastic_cloud.js +++ b/x-pack/plugins/apm/server/tutorial/envs/elastic_cloud.ts @@ -1,25 +1,11 @@ /* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. + * 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 { get, has } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { INSTRUCTION_VARIANT } from '../../../../common/tutorials/instruction_variant'; +import { INSTRUCTION_VARIANT } from '../../../../../../src/plugins/home/server'; import { createNodeAgentInstructions, @@ -32,13 +18,10 @@ import { createJavaAgentInstructions, createDotNetAgentInstructions, } from '../instructions/apm_agent_instructions'; +import { CloudSetup } from '../../../../cloud/server'; -function getIfExists(obj, key) { - return has(obj, key) && get(obj, key); -} - -export function createElasticCloudInstructions(cloudSetup) { - const apmServerUrl = getIfExists(cloudSetup, 'apm.url'); +export function createElasticCloudInstructions(cloudSetup?: CloudSetup) { + const apmServerUrl = cloudSetup?.apm.url; const instructionSets = []; if (!apmServerUrl) { @@ -52,10 +35,10 @@ export function createElasticCloudInstructions(cloudSetup) { }; } -function getApmServerInstructionSet(cloudSetup) { - const cloudId = getIfExists(cloudSetup, 'cloudId'); +function getApmServerInstructionSet(cloudSetup?: CloudSetup) { + const cloudId = cloudSetup?.cloudId; return { - title: i18n.translate('kbn.server.tutorials.apm.apmServer.title', { + title: i18n.translate('xpack.apm.tutorial.apmServer.title', { defaultMessage: 'APM Server', }), instructionVariants: [ @@ -64,7 +47,7 @@ function getApmServerInstructionSet(cloudSetup) { instructions: [ { title: 'Enable the APM Server in the ESS console', - textPre: i18n.translate('kbn.server.tutorials.apm.elasticCloud.textPre', { + textPre: i18n.translate('xpack.apm.tutorial.elasticCloud.textPre', { defaultMessage: 'To enable the APM Server go to [the Elastic Cloud console](https://cloud.elastic.co/deployments?q={cloudId}) and enable APM in the deployment settings. Once enabled, refresh this page.', values: { cloudId }, @@ -76,12 +59,12 @@ function getApmServerInstructionSet(cloudSetup) { }; } -function getApmAgentInstructionSet(cloudSetup) { - const apmServerUrl = getIfExists(cloudSetup, 'apm.url'); - const secretToken = getIfExists(cloudSetup, 'apm.secretToken'); +function getApmAgentInstructionSet(cloudSetup?: CloudSetup) { + const apmServerUrl = cloudSetup?.apm.url; + const secretToken = cloudSetup?.apm.secretToken; return { - title: i18n.translate('kbn.server.tutorials.apm.elasticCloudInstructions.title', { + title: i18n.translate('xpack.apm.tutorial.elasticCloudInstructions.title', { defaultMessage: 'APM Agents', }), instructionVariants: [ diff --git a/src/legacy/core_plugins/kibana/server/tutorials/apm/envs/on_prem.js b/x-pack/plugins/apm/server/tutorial/envs/on_prem.ts similarity index 65% rename from src/legacy/core_plugins/kibana/server/tutorials/apm/envs/on_prem.js rename to x-pack/plugins/apm/server/tutorial/envs/on_prem.ts index 4cbbbf89bf122..03cd21119b30b 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/apm/envs/on_prem.js +++ b/x-pack/plugins/apm/server/tutorial/envs/on_prem.ts @@ -1,24 +1,11 @@ /* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. + * 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 { INSTRUCTION_VARIANT } from '../../../../common/tutorials/instruction_variant'; +import { INSTRUCTION_VARIANT } from '../../../../../../src/plugins/home/server'; import { createWindowsServerInstructions, createEditConfig, @@ -40,7 +27,19 @@ import { createDotNetAgentInstructions, } from '../instructions/apm_agent_instructions'; -export function onPremInstructions(config) { +export function onPremInstructions({ + errorIndices, + transactionIndices, + metricsIndices, + sourcemapIndices, + onboardingIndices, +}: { + errorIndices: string; + transactionIndices: string; + metricsIndices: string; + sourcemapIndices: string; + onboardingIndices: string; +}) { const EDIT_CONFIG = createEditConfig(); const START_SERVER_UNIX = createStartServerUnix(); const START_SERVER_UNIX_SYSV = createStartServerUnixSysv(); @@ -48,14 +47,14 @@ export function onPremInstructions(config) { return { instructionSets: [ { - title: i18n.translate('kbn.server.tutorials.apm.apmServer.title', { + title: i18n.translate('xpack.apm.tutorial.apmServer.title', { defaultMessage: 'APM Server', }), callOut: { - title: i18n.translate('kbn.server.tutorials.apm.apmServer.callOut.title', { + title: i18n.translate('xpack.apm.tutorial.apmServer.callOut.title', { defaultMessage: 'Important: Updating to 7.0 or higher', }), - message: i18n.translate('kbn.server.tutorials.apm.apmServer.callOut.message', { + message: i18n.translate('xpack.apm.tutorial.apmServer.callOut.message', { defaultMessage: `Please make sure your APM Server is updated to 7.0 or higher. \ You can also migrate your 6.x data with the migration assistant found in Kibana's management section.`, }), @@ -80,25 +79,25 @@ export function onPremInstructions(config) { }, ], statusCheck: { - title: i18n.translate('kbn.server.tutorials.apm.apmServer.statusCheck.title', { + title: i18n.translate('xpack.apm.tutorial.apmServer.statusCheck.title', { defaultMessage: 'APM Server status', }), - text: i18n.translate('kbn.server.tutorials.apm.apmServer.statusCheck.text', { + text: i18n.translate('xpack.apm.tutorial.apmServer.statusCheck.text', { defaultMessage: 'Make sure APM Server is running before you start implementing the APM agents.', }), - btnLabel: i18n.translate('kbn.server.tutorials.apm.apmServer.statusCheck.btnLabel', { + btnLabel: i18n.translate('xpack.apm.tutorial.apmServer.statusCheck.btnLabel', { defaultMessage: 'Check APM Server status', }), - success: i18n.translate('kbn.server.tutorials.apm.apmServer.statusCheck.successMessage', { + success: i18n.translate('xpack.apm.tutorial.apmServer.statusCheck.successMessage', { defaultMessage: 'You have correctly setup APM Server', }), - error: i18n.translate('kbn.server.tutorials.apm.apmServer.statusCheck.errorMessage', { + error: i18n.translate('xpack.apm.tutorial.apmServer.statusCheck.errorMessage', { defaultMessage: 'No APM Server detected. Please make sure it is running and you have updated to 7.0 or higher.', }), esHitsCheck: { - index: config.get('apm_oss.onboardingIndices'), + index: onboardingIndices, query: { bool: { filter: [ @@ -111,7 +110,7 @@ export function onPremInstructions(config) { }, }, { - title: i18n.translate('kbn.server.tutorials.apm.apmAgents.title', { + title: i18n.translate('xpack.apm.tutorial.apmAgents.title', { defaultMessage: 'APM Agents', }), instructionVariants: [ @@ -153,29 +152,24 @@ export function onPremInstructions(config) { }, ], statusCheck: { - title: i18n.translate('kbn.server.tutorials.apm.apmAgents.statusCheck.title', { + title: i18n.translate('xpack.apm.tutorial.apmAgents.statusCheck.title', { defaultMessage: 'Agent status', }), - text: i18n.translate('kbn.server.tutorials.apm.apmAgents.statusCheck.text', { + text: i18n.translate('xpack.apm.tutorial.apmAgents.statusCheck.text', { defaultMessage: 'Make sure your application is running and the agents are sending data.', }), - btnLabel: i18n.translate('kbn.server.tutorials.apm.apmAgents.statusCheck.btnLabel', { + btnLabel: i18n.translate('xpack.apm.tutorial.apmAgents.statusCheck.btnLabel', { defaultMessage: 'Check agent status', }), - success: i18n.translate('kbn.server.tutorials.apm.apmAgents.statusCheck.successMessage', { + success: i18n.translate('xpack.apm.tutorial.apmAgents.statusCheck.successMessage', { defaultMessage: 'Data successfully received from one or more agents', }), - error: i18n.translate('kbn.server.tutorials.apm.apmAgents.statusCheck.errorMessage', { + error: i18n.translate('xpack.apm.tutorial.apmAgents.statusCheck.errorMessage', { defaultMessage: 'No data has been received from agents yet', }), esHitsCheck: { - index: [ - config.get('apm_oss.errorIndices'), - config.get('apm_oss.transactionIndices'), - config.get('apm_oss.metricsIndices'), - config.get('apm_oss.sourcemapIndices'), - ], + index: [errorIndices, transactionIndices, metricsIndices, sourcemapIndices], query: { bool: { filter: [ diff --git a/x-pack/plugins/apm/server/tutorial/index.ts b/x-pack/plugins/apm/server/tutorial/index.ts new file mode 100644 index 0000000000000..bb5d553b26b36 --- /dev/null +++ b/x-pack/plugins/apm/server/tutorial/index.ts @@ -0,0 +1,100 @@ +/* + * 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 { onPremInstructions } from './envs/on_prem'; +import { createElasticCloudInstructions } from './envs/elastic_cloud'; +import apmIndexPattern from './index_pattern.json'; +import { CloudSetup } from '../../../cloud/server'; +import { ArtifactsSchema, TutorialsCategory } from '../../../../../src/plugins/home/server'; + +const apmIntro = i18n.translate('xpack.apm.tutorial.introduction', { + defaultMessage: 'Collect in-depth performance metrics and errors from inside your applications.', +}); + +export const tutorialProvider = ({ + isEnabled, + indexPatternTitle, + cloud, + indices, +}: { + isEnabled: boolean; + indexPatternTitle: string; + cloud?: CloudSetup; + indices: { + errorIndices: string; + transactionIndices: string; + metricsIndices: string; + sourcemapIndices: string; + onboardingIndices: string; + }; +}) => () => { + const savedObjects = [ + { + ...apmIndexPattern, + attributes: { + ...apmIndexPattern.attributes, + title: indexPatternTitle, + }, + }, + ]; + + const artifacts: ArtifactsSchema = { + dashboards: [ + { + id: '8d3ed660-7828-11e7-8c47-65b845b5cfb3', + linkLabel: i18n.translate( + 'xpack.apm.tutorial.specProvider.artifacts.dashboards.linkLabel', + { + defaultMessage: 'APM dashboard', + } + ), + isOverview: true, + }, + ], + }; + + if (isEnabled) { + artifacts.application = { + path: '/app/apm', + label: i18n.translate('xpack.apm.tutorial.specProvider.artifacts.application.label', { + defaultMessage: 'Launch APM', + }), + }; + } + + return { + id: 'apm', + name: i18n.translate('xpack.apm.tutorial.specProvider.name', { + defaultMessage: 'APM', + }), + category: TutorialsCategory.OTHER, + shortDescription: apmIntro, + longDescription: i18n.translate('xpack.apm.tutorial.specProvider.longDescription', { + defaultMessage: + 'Application Performance Monitoring (APM) collects in-depth \ +performance metrics and errors from inside your application. \ +It allows you to monitor the performance of thousands of applications in real time. \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: + '{config.docs.base_url}guide/en/apm/get-started/{config.docs.version}/index.html', + }, + }), + euiIconType: 'logoAPM', + artifacts, + onPrem: onPremInstructions(indices), + elasticCloud: createElasticCloudInstructions(cloud), + previewImagePath: '/plugins/kibana/home/tutorial_resources/apm/apm.png', + savedObjects, + savedObjectsInstallMsg: i18n.translate( + 'xpack.apm.tutorial.specProvider.savedObjectsInstallMsg', + { + defaultMessage: 'An APM index pattern is required for some features in the APM UI.', + } + ), + }; +}; diff --git a/src/legacy/core_plugins/kibana/server/tutorials/apm/index_pattern.json b/x-pack/plugins/apm/server/tutorial/index_pattern.json similarity index 100% rename from src/legacy/core_plugins/kibana/server/tutorials/apm/index_pattern.json rename to x-pack/plugins/apm/server/tutorial/index_pattern.json diff --git a/src/legacy/core_plugins/kibana/server/tutorials/apm/instructions/apm_agent_instructions.js b/x-pack/plugins/apm/server/tutorial/instructions/apm_agent_instructions.ts similarity index 65% rename from src/legacy/core_plugins/kibana/server/tutorials/apm/instructions/apm_agent_instructions.js rename to x-pack/plugins/apm/server/tutorial/instructions/apm_agent_instructions.ts index b47793a1610fd..d1ef92df20f6f 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/apm/instructions/apm_agent_instructions.js +++ b/x-pack/plugins/apm/server/tutorial/instructions/apm_agent_instructions.ts @@ -1,67 +1,51 @@ /* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. + * 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 const createNodeAgentInstructions = (apmServerUrl = '', secretToken = '') => [ { - title: i18n.translate('kbn.server.tutorials.apm.nodeClient.install.title', { + title: i18n.translate('xpack.apm.tutorial.nodeClient.install.title', { defaultMessage: 'Install the APM agent', }), - textPre: i18n.translate('kbn.server.tutorials.apm.nodeClient.install.textPre', { + textPre: i18n.translate('xpack.apm.tutorial.nodeClient.install.textPre', { defaultMessage: 'Install the APM agent for Node.js as a dependency to your application.', }), commands: ['npm install elastic-apm-node --save'], }, { - title: i18n.translate('kbn.server.tutorials.apm.nodeClient.configure.title', { + title: i18n.translate('xpack.apm.tutorial.nodeClient.configure.title', { defaultMessage: 'Configure the agent', }), - textPre: i18n.translate('kbn.server.tutorials.apm.nodeClient.configure.textPre', { + textPre: i18n.translate('xpack.apm.tutorial.nodeClient.configure.textPre', { defaultMessage: 'Agents are libraries that run inside of your application process. \ APM services are created programmatically based on the `serviceName`. \ This agent supports a vararity of frameworks but can also be used with your custom stack.', }), commands: `// ${i18n.translate( - 'kbn.server.tutorials.apm.nodeClient.configure.commands.addThisToTheFileTopComment', + 'xpack.apm.tutorial.nodeClient.configure.commands.addThisToTheFileTopComment', { defaultMessage: 'Add this to the VERY top of the first file loaded in your app', } )} var apm = require('elastic-apm-node').start({curlyOpen} // ${i18n.translate( - 'kbn.server.tutorials.apm.nodeClient.configure.commands.setRequiredServiceNameComment', + 'xpack.apm.tutorial.nodeClient.configure.commands.setRequiredServiceNameComment', { defaultMessage: 'Override service name from package.json', } )} - // ${i18n.translate( - 'kbn.server.tutorials.apm.nodeClient.configure.commands.allowedCharactersComment', - { - defaultMessage: 'Allowed characters: a-z, A-Z, 0-9, -, _, and space', - } - )} + // ${i18n.translate('xpack.apm.tutorial.nodeClient.configure.commands.allowedCharactersComment', { + defaultMessage: 'Allowed characters: a-z, A-Z, 0-9, -, _, and space', + })} serviceName: '', // ${i18n.translate( - 'kbn.server.tutorials.apm.nodeClient.configure.commands.useIfApmRequiresTokenComment', + 'xpack.apm.tutorial.nodeClient.configure.commands.useIfApmRequiresTokenComment', { defaultMessage: 'Use if APM Server requires a token', } @@ -69,7 +53,7 @@ var apm = require('elastic-apm-node').start({curlyOpen} secretToken: '${secretToken}', // ${i18n.translate( - 'kbn.server.tutorials.apm.nodeClient.configure.commands.setCustomApmServerUrlComment', + 'xpack.apm.tutorial.nodeClient.configure.commands.setCustomApmServerUrlComment', { defaultMessage: 'Set custom APM Server URL (default: {defaultApmServerUrl})', values: { defaultApmServerUrl: 'http://localhost:8200' }, @@ -77,7 +61,7 @@ var apm = require('elastic-apm-node').start({curlyOpen} )} serverUrl: '${apmServerUrl}' {curlyClose})`.split('\n'), - textPost: i18n.translate('kbn.server.tutorials.apm.nodeClient.configure.textPost', { + textPost: i18n.translate('xpack.apm.tutorial.nodeClient.configure.textPost', { defaultMessage: 'See [the documentation]({documentationLink}) for advanced usage, including how to use with \ [Babel/ES Modules]({babelEsModulesLink}).', @@ -92,25 +76,25 @@ var apm = require('elastic-apm-node').start({curlyOpen} export const createDjangoAgentInstructions = (apmServerUrl = '', secretToken = '') => [ { - title: i18n.translate('kbn.server.tutorials.apm.djangoClient.install.title', { + title: i18n.translate('xpack.apm.tutorial.djangoClient.install.title', { defaultMessage: 'Install the APM agent', }), - textPre: i18n.translate('kbn.server.tutorials.apm.djangoClient.install.textPre', { + textPre: i18n.translate('xpack.apm.tutorial.djangoClient.install.textPre', { defaultMessage: 'Install the APM agent for Python as a dependency.', }), commands: ['$ pip install elastic-apm'], }, { - title: i18n.translate('kbn.server.tutorials.apm.djangoClient.configure.title', { + title: i18n.translate('xpack.apm.tutorial.djangoClient.configure.title', { defaultMessage: 'Configure the agent', }), - textPre: i18n.translate('kbn.server.tutorials.apm.djangoClient.configure.textPre', { + textPre: i18n.translate('xpack.apm.tutorial.djangoClient.configure.textPre', { defaultMessage: 'Agents are libraries that run inside of your application process. \ APM services are created programmatically based on the `SERVICE_NAME`.', }), commands: `# ${i18n.translate( - 'kbn.server.tutorials.apm.djangoClient.configure.commands.addAgentComment', + 'xpack.apm.tutorial.djangoClient.configure.commands.addAgentComment', { defaultMessage: 'Add the agent to the installed apps', } @@ -122,13 +106,13 @@ INSTALLED_APPS = ( ELASTIC_APM = {curlyOpen} # ${i18n.translate( - 'kbn.server.tutorials.apm.djangoClient.configure.commands.setRequiredServiceNameComment', + 'xpack.apm.tutorial.djangoClient.configure.commands.setRequiredServiceNameComment', { defaultMessage: 'Set required service name. Allowed characters:', } )} # ${i18n.translate( - 'kbn.server.tutorials.apm.djangoClient.configure.commands.allowedCharactersComment', + 'xpack.apm.tutorial.djangoClient.configure.commands.allowedCharactersComment', { defaultMessage: 'a-z, A-Z, 0-9, -, _, and space', } @@ -136,7 +120,7 @@ ELASTIC_APM = {curlyOpen} 'SERVICE_NAME': '', # ${i18n.translate( - 'kbn.server.tutorials.apm.djangoClient.configure.commands.useIfApmServerRequiresTokenComment', + 'xpack.apm.tutorial.djangoClient.configure.commands.useIfApmServerRequiresTokenComment', { defaultMessage: 'Use if APM Server requires a token', } @@ -144,7 +128,7 @@ ELASTIC_APM = {curlyOpen} 'SECRET_TOKEN': '${secretToken}', # ${i18n.translate( - 'kbn.server.tutorials.apm.djangoClient.configure.commands.setCustomApmServerUrlComment', + 'xpack.apm.tutorial.djangoClient.configure.commands.setCustomApmServerUrlComment', { defaultMessage: 'Set custom APM Server URL (default: {defaultApmServerUrl})', values: { defaultApmServerUrl: 'http://localhost:8200' }, @@ -154,7 +138,7 @@ ELASTIC_APM = {curlyOpen} {curlyClose} # ${i18n.translate( - 'kbn.server.tutorials.apm.djangoClient.configure.commands.addTracingMiddlewareComment', + 'xpack.apm.tutorial.djangoClient.configure.commands.addTracingMiddlewareComment', { defaultMessage: 'To send performance metrics, add our tracing middleware:', } @@ -163,7 +147,7 @@ MIDDLEWARE = ( 'elasticapm.contrib.django.middleware.TracingMiddleware', #... )`.split('\n'), - textPost: i18n.translate('kbn.server.tutorials.apm.djangoClient.configure.textPost', { + textPost: i18n.translate('xpack.apm.tutorial.djangoClient.configure.textPost', { defaultMessage: 'See the [documentation]({documentationLink}) for advanced usage.', values: { documentationLink: @@ -175,25 +159,25 @@ MIDDLEWARE = ( export const createFlaskAgentInstructions = (apmServerUrl = '', secretToken = '') => [ { - title: i18n.translate('kbn.server.tutorials.apm.flaskClient.install.title', { + title: i18n.translate('xpack.apm.tutorial.flaskClient.install.title', { defaultMessage: 'Install the APM agent', }), - textPre: i18n.translate('kbn.server.tutorials.apm.flaskClient.install.textPre', { + textPre: i18n.translate('xpack.apm.tutorial.flaskClient.install.textPre', { defaultMessage: 'Install the APM agent for Python as a dependency.', }), commands: ['$ pip install elastic-apm[flask]'], }, { - title: i18n.translate('kbn.server.tutorials.apm.flaskClient.configure.title', { + title: i18n.translate('xpack.apm.tutorial.flaskClient.configure.title', { defaultMessage: 'Configure the agent', }), - textPre: i18n.translate('kbn.server.tutorials.apm.flaskClient.configure.textPre', { + textPre: i18n.translate('xpack.apm.tutorial.flaskClient.configure.textPre', { defaultMessage: 'Agents are libraries that run inside of your application process. \ APM services are created programmatically based on the `SERVICE_NAME`.', }), commands: `# ${i18n.translate( - 'kbn.server.tutorials.apm.flaskClient.configure.commands.initializeUsingEnvironmentVariablesComment', + 'xpack.apm.tutorial.flaskClient.configure.commands.initializeUsingEnvironmentVariablesComment', { defaultMessage: 'initialize using environment variables', } @@ -202,30 +186,24 @@ from elasticapm.contrib.flask import ElasticAPM app = Flask(__name__) apm = ElasticAPM(app) -# ${i18n.translate( - 'kbn.server.tutorials.apm.flaskClient.configure.commands.configureElasticApmComment', - { - defaultMessage: "or configure to use ELASTIC_APM in your application's settings", - } - )} +# ${i18n.translate('xpack.apm.tutorial.flaskClient.configure.commands.configureElasticApmComment', { + defaultMessage: "or configure to use ELASTIC_APM in your application's settings", + })} from elasticapm.contrib.flask import ElasticAPM app.config['ELASTIC_APM'] = {curlyOpen} # ${i18n.translate( - 'kbn.server.tutorials.apm.flaskClient.configure.commands.setRequiredServiceNameComment', + 'xpack.apm.tutorial.flaskClient.configure.commands.setRequiredServiceNameComment', { defaultMessage: 'Set required service name. Allowed characters:', } )} - # ${i18n.translate( - 'kbn.server.tutorials.apm.flaskClient.configure.commands.allowedCharactersComment', - { - defaultMessage: 'a-z, A-Z, 0-9, -, _, and space', - } - )} + # ${i18n.translate('xpack.apm.tutorial.flaskClient.configure.commands.allowedCharactersComment', { + defaultMessage: 'a-z, A-Z, 0-9, -, _, and space', + })} 'SERVICE_NAME': '', # ${i18n.translate( - 'kbn.server.tutorials.apm.flaskClient.configure.commands.useIfApmServerRequiresTokenComment', + 'xpack.apm.tutorial.flaskClient.configure.commands.useIfApmServerRequiresTokenComment', { defaultMessage: 'Use if APM Server requires a token', } @@ -233,7 +211,7 @@ app.config['ELASTIC_APM'] = {curlyOpen} 'SECRET_TOKEN': '${secretToken}', # ${i18n.translate( - 'kbn.server.tutorials.apm.flaskClient.configure.commands.setCustomApmServerUrlComment', + 'xpack.apm.tutorial.flaskClient.configure.commands.setCustomApmServerUrlComment', { defaultMessage: 'Set custom APM Server URL (default: {defaultApmServerUrl})', values: { defaultApmServerUrl: 'http://localhost:8200' }, @@ -243,7 +221,7 @@ app.config['ELASTIC_APM'] = {curlyOpen} {curlyClose} apm = ElasticAPM(app)`.split('\n'), - textPost: i18n.translate('kbn.server.tutorials.apm.flaskClient.configure.textPost', { + textPost: i18n.translate('xpack.apm.tutorial.flaskClient.configure.textPost', { defaultMessage: 'See the [documentation]({documentationLink}) for advanced usage.', values: { documentationLink: @@ -255,19 +233,19 @@ apm = ElasticAPM(app)`.split('\n'), export const createRailsAgentInstructions = (apmServerUrl = '', secretToken = '') => [ { - title: i18n.translate('kbn.server.tutorials.apm.railsClient.install.title', { + title: i18n.translate('xpack.apm.tutorial.railsClient.install.title', { defaultMessage: 'Install the APM agent', }), - textPre: i18n.translate('kbn.server.tutorials.apm.railsClient.install.textPre', { + textPre: i18n.translate('xpack.apm.tutorial.railsClient.install.textPre', { defaultMessage: 'Add the agent to your Gemfile.', }), commands: [`gem 'elastic-apm'`], }, { - title: i18n.translate('kbn.server.tutorials.apm.railsClient.configure.title', { + title: i18n.translate('xpack.apm.tutorial.railsClient.configure.title', { defaultMessage: 'Configure the agent', }), - textPre: i18n.translate('kbn.server.tutorials.apm.railsClient.configure.textPre', { + textPre: i18n.translate('xpack.apm.tutorial.railsClient.configure.textPre', { defaultMessage: 'APM is automatically started when your app boots. Configure the agent, by creating the config file {configFile}', values: { configFile: '`config/elastic_apm.yml`' }, @@ -283,7 +261,7 @@ export const createRailsAgentInstructions = (apmServerUrl = '', secretToken = '' # Set custom APM Server URL (default: http://localhost:8200) # server_url: '${apmServerUrl || 'http://localhost:8200'}'`.split('\n'), - textPost: i18n.translate('kbn.server.tutorials.apm.railsClient.configure.textPost', { + textPost: i18n.translate('xpack.apm.tutorial.railsClient.configure.textPost', { defaultMessage: 'See the [documentation]({documentationLink}) for configuration options and advanced usage.\n\n', values: { @@ -295,19 +273,19 @@ export const createRailsAgentInstructions = (apmServerUrl = '', secretToken = '' export const createRackAgentInstructions = (apmServerUrl = '', secretToken = '') => [ { - title: i18n.translate('kbn.server.tutorials.apm.rackClient.install.title', { + title: i18n.translate('xpack.apm.tutorial.rackClient.install.title', { defaultMessage: 'Install the APM agent', }), - textPre: i18n.translate('kbn.server.tutorials.apm.rackClient.install.textPre', { + textPre: i18n.translate('xpack.apm.tutorial.rackClient.install.textPre', { defaultMessage: 'Add the agent to your Gemfile.', }), commands: [`gem 'elastic-apm'`], }, { - title: i18n.translate('kbn.server.tutorials.apm.rackClient.configure.title', { + title: i18n.translate('xpack.apm.tutorial.rackClient.configure.title', { defaultMessage: 'Configure the agent', }), - textPre: i18n.translate('kbn.server.tutorials.apm.rackClient.configure.textPre', { + textPre: i18n.translate('xpack.apm.tutorial.rackClient.configure.textPre', { defaultMessage: 'For Rack or a compatible framework (e.g. Sinatra), include the middleware in your app and start the agent.', }), @@ -322,13 +300,13 @@ export const createRackAgentInstructions = (apmServerUrl = '', secretToken = '') ElasticAPM.start( app: MySinatraApp, # ${i18n.translate( - 'kbn.server.tutorials.apm.rackClient.configure.commands.requiredComment', + 'xpack.apm.tutorial.rackClient.configure.commands.requiredComment', { defaultMessage: 'required', } )} config_file: '' # ${i18n.translate( - 'kbn.server.tutorials.apm.rackClient.configure.commands.optionalComment', + 'xpack.apm.tutorial.rackClient.configure.commands.optionalComment', { defaultMessage: 'optional, defaults to config/elastic_apm.yml', } @@ -340,23 +318,20 @@ export const createRackAgentInstructions = (apmServerUrl = '', secretToken = '') at_exit {curlyOpen} ElasticAPM.stop {curlyClose}`.split('\n'), }, { - title: i18n.translate('kbn.server.tutorials.apm.rackClient.createConfig.title', { + title: i18n.translate('xpack.apm.tutorial.rackClient.createConfig.title', { defaultMessage: 'Create config file', }), - textPre: i18n.translate('kbn.server.tutorials.apm.rackClient.createConfig.textPre', { + textPre: i18n.translate('xpack.apm.tutorial.rackClient.createConfig.textPre', { defaultMessage: 'Create a config file {configFile}:', values: { configFile: '`config/elastic_apm.yml`' }, }), commands: `# config/elastic_apm.yml: +# ${i18n.translate('xpack.apm.tutorial.rackClient.createConfig.commands.setServiceNameComment', { + defaultMessage: 'Set service name - allowed characters: a-z, A-Z, 0-9, -, _ and space', + })} # ${i18n.translate( - 'kbn.server.tutorials.apm.rackClient.createConfig.commands.setServiceNameComment', - { - defaultMessage: 'Set service name - allowed characters: a-z, A-Z, 0-9, -, _ and space', - } - )} -# ${i18n.translate( - 'kbn.server.tutorials.apm.rackClient.createConfig.commands.defaultsToTheNameOfRackAppClassComment', + 'xpack.apm.tutorial.rackClient.createConfig.commands.defaultsToTheNameOfRackAppClassComment', { defaultMessage: "Defaults to the name of your Rack app's class.", } @@ -364,7 +339,7 @@ export const createRackAgentInstructions = (apmServerUrl = '', secretToken = '') # service_name: 'my-service' # ${i18n.translate( - 'kbn.server.tutorials.apm.rackClient.createConfig.commands.useIfApmServerRequiresTokenComment', + 'xpack.apm.tutorial.rackClient.createConfig.commands.useIfApmServerRequiresTokenComment', { defaultMessage: 'Use if APM Server requires a token', } @@ -372,14 +347,14 @@ export const createRackAgentInstructions = (apmServerUrl = '', secretToken = '') # secret_token: '${secretToken}' # ${i18n.translate( - 'kbn.server.tutorials.apm.rackClient.createConfig.commands.setCustomApmServerComment', + 'xpack.apm.tutorial.rackClient.createConfig.commands.setCustomApmServerComment', { defaultMessage: 'Set custom APM Server URL (default: {defaultServerUrl})', values: { defaultServerUrl: 'http://localhost:8200' }, } )} # server_url: '${apmServerUrl || 'http://localhost:8200'}'`.split('\n'), - textPost: i18n.translate('kbn.server.tutorials.apm.rackClient.createConfig.textPost', { + textPost: i18n.translate('xpack.apm.tutorial.rackClient.createConfig.textPost', { defaultMessage: 'See the [documentation]({documentationLink}) for configuration options and advanced usage.\n\n', values: { @@ -391,10 +366,10 @@ export const createRackAgentInstructions = (apmServerUrl = '', secretToken = '') export const createJsAgentInstructions = (apmServerUrl = '') => [ { - title: i18n.translate('kbn.server.tutorials.apm.jsClient.enableRealUserMonitoring.title', { + title: i18n.translate('xpack.apm.tutorial.jsClient.enableRealUserMonitoring.title', { defaultMessage: 'Enable Real User Monitoring support in APM server', }), - textPre: i18n.translate('kbn.server.tutorials.apm.jsClient.enableRealUserMonitoring.textPre', { + textPre: i18n.translate('xpack.apm.tutorial.jsClient.enableRealUserMonitoring.textPre', { defaultMessage: 'APM Server disables RUM support by default. See the [documentation]({documentationLink}) \ for details on how to enable RUM support.', @@ -405,10 +380,10 @@ for details on how to enable RUM support.', }), }, { - title: i18n.translate('kbn.server.tutorials.apm.jsClient.installDependency.title', { + title: i18n.translate('xpack.apm.tutorial.jsClient.installDependency.title', { defaultMessage: 'Set up the Agent as a dependency', }), - textPre: i18n.translate('kbn.server.tutorials.apm.jsClient.installDependency.textPre', { + textPre: i18n.translate('xpack.apm.tutorial.jsClient.installDependency.textPre', { defaultMessage: 'You can install the Agent as a dependency to your application with \ `npm install @elastic/apm-rum --save`.\n\n\ @@ -418,7 +393,7 @@ The Agent can then be initialized and configured in your application like this:' var apm = initApm({curlyOpen} // ${i18n.translate( - 'kbn.server.tutorials.apm.jsClient.installDependency.commands.setRequiredServiceNameComment', + 'xpack.apm.tutorial.jsClient.installDependency.commands.setRequiredServiceNameComment', { defaultMessage: 'Set required service name (allowed characters: a-z, A-Z, 0-9, -, _, and space)', @@ -427,7 +402,7 @@ var apm = initApm({curlyOpen} serviceName: 'your-app-name', // ${i18n.translate( - 'kbn.server.tutorials.apm.jsClient.installDependency.commands.setCustomApmServerUrlComment', + 'xpack.apm.tutorial.jsClient.installDependency.commands.setCustomApmServerUrlComment', { defaultMessage: 'Set custom APM Server URL (default: {defaultApmServerUrl})', values: { defaultApmServerUrl: 'http://localhost:8200' }, @@ -436,14 +411,14 @@ var apm = initApm({curlyOpen} serverUrl: '${apmServerUrl}', // ${i18n.translate( - 'kbn.server.tutorials.apm.jsClient.installDependency.commands.setServiceVersionComment', + 'xpack.apm.tutorial.jsClient.installDependency.commands.setServiceVersionComment', { defaultMessage: 'Set service version (required for source map feature)', } )} serviceVersion: '' {curlyClose})`.split('\n'), - textPost: i18n.translate('kbn.server.tutorials.apm.jsClient.installDependency.textPost', { + textPost: i18n.translate('xpack.apm.tutorial.jsClient.installDependency.textPost', { defaultMessage: 'Framework integrations, like React or Angular, have custom dependencies. \ See the [integration documentation]({docLink}) for more information.', @@ -454,10 +429,10 @@ See the [integration documentation]({docLink}) for more information.', }), }, { - title: i18n.translate('kbn.server.tutorials.apm.jsClient.scriptTags.title', { + title: i18n.translate('xpack.apm.tutorial.jsClient.scriptTags.title', { defaultMessage: 'Set up the Agent with Script Tags', }), - textPre: i18n.translate('kbn.server.tutorials.apm.jsClient.scriptTags.textPre', { + textPre: i18n.translate('xpack.apm.tutorial.jsClient.scriptTags.textPre', { defaultMessage: "Alternatively, you can use Script tags to set up and configure the Agent. \ Add a `