From 8868d08745c4fb760bb82d5a2fc2e3a60da67afb Mon Sep 17 00:00:00 2001 From: Pierre Gayvallet Date: Tue, 31 Oct 2023 13:55:03 +0100 Subject: [PATCH 01/21] make `isInlineScriptingEnabled` resilient to ES errors (#170208) ## Summary Fix https://github.com/elastic/kibana/issues/163787 Change the way `isInlineScriptingEnabled` function to retry retryable errors from ES (similar to how the valid connection or migration ES calls do) --- .../index.ts | 1 + .../src/is_scripting_enabled.test.mocks.ts | 15 ++++ .../src/is_scripting_enabled.test.ts | 55 ++++++++++++++ .../src/is_scripting_enabled.ts | 47 ++++++++---- .../src/retryable_es_client_errors.test.ts | 73 +++++++++++++++++++ .../src/retryable_es_client_errors.ts | 40 ++++++++++ .../catch_retryable_es_client_errors.ts | 23 +----- 7 files changed, 220 insertions(+), 34 deletions(-) create mode 100644 packages/core/elasticsearch/core-elasticsearch-server-internal/src/is_scripting_enabled.test.mocks.ts create mode 100644 packages/core/elasticsearch/core-elasticsearch-server-internal/src/retryable_es_client_errors.test.ts create mode 100644 packages/core/elasticsearch/core-elasticsearch-server-internal/src/retryable_es_client_errors.ts diff --git a/packages/core/elasticsearch/core-elasticsearch-server-internal/index.ts b/packages/core/elasticsearch/core-elasticsearch-server-internal/index.ts index 3d81cebf9dc85..983be835a8dbb 100644 --- a/packages/core/elasticsearch/core-elasticsearch-server-internal/index.ts +++ b/packages/core/elasticsearch/core-elasticsearch-server-internal/index.ts @@ -30,4 +30,5 @@ export { CoreElasticsearchRouteHandlerContext } from './src/elasticsearch_route_ export { retryCallCluster, migrationRetryCallCluster } from './src/retry_call_cluster'; export { isInlineScriptingEnabled } from './src/is_scripting_enabled'; export { getCapabilitiesFromClient } from './src/get_capabilities'; +export { isRetryableEsClientError } from './src/retryable_es_client_errors'; export type { ClusterInfo } from './src/get_cluster_info'; diff --git a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/is_scripting_enabled.test.mocks.ts b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/is_scripting_enabled.test.mocks.ts new file mode 100644 index 0000000000000..99485dca9a581 --- /dev/null +++ b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/is_scripting_enabled.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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export const isRetryableEsClientErrorMock = jest.fn(); + +jest.doMock('./retryable_es_client_errors', () => { + return { + isRetryableEsClientError: isRetryableEsClientErrorMock, + }; +}); diff --git a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/is_scripting_enabled.test.ts b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/is_scripting_enabled.test.ts index d2922c0161c6f..57d40936b8242 100644 --- a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/is_scripting_enabled.test.ts +++ b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/is_scripting_enabled.test.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { isRetryableEsClientErrorMock } from './is_scripting_enabled.test.mocks'; import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; import { isInlineScriptingEnabled } from './is_scripting_enabled'; @@ -94,4 +95,58 @@ describe('isInlineScriptingEnabled', () => { expect(await isInlineScriptingEnabled({ client })).toEqual(false); }); + + describe('resiliency', () => { + beforeEach(() => { + isRetryableEsClientErrorMock.mockReset(); + }); + + const mockSuccessOnce = () => { + client.cluster.getSettings.mockResolvedValueOnce({ + transient: {}, + persistent: {}, + defaults: {}, + }); + }; + const mockErrorOnce = () => { + client.cluster.getSettings.mockResponseImplementationOnce(() => { + throw Error('ERR CON REFUSED'); + }); + }; + + it('retries the ES api call in case of retryable error', async () => { + isRetryableEsClientErrorMock.mockReturnValue(true); + + mockErrorOnce(); + mockSuccessOnce(); + + await expect(isInlineScriptingEnabled({ client, maxRetryDelay: 1 })).resolves.toEqual(true); + expect(client.cluster.getSettings).toHaveBeenCalledTimes(2); + }); + + it('throws in case of non-retryable error', async () => { + isRetryableEsClientErrorMock.mockReturnValue(false); + + mockErrorOnce(); + mockSuccessOnce(); + + await expect(isInlineScriptingEnabled({ client, maxRetryDelay: 0.1 })).rejects.toThrowError( + 'ERR CON REFUSED' + ); + }); + + it('retries up to `maxRetries` times', async () => { + isRetryableEsClientErrorMock.mockReturnValue(true); + + mockErrorOnce(); + mockErrorOnce(); + mockErrorOnce(); + mockSuccessOnce(); + + await expect( + isInlineScriptingEnabled({ client, maxRetryDelay: 0.1, maxRetries: 2 }) + ).rejects.toThrowError('ERR CON REFUSED'); + expect(client.cluster.getSettings).toHaveBeenCalledTimes(3); + }); + }); }); diff --git a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/is_scripting_enabled.ts b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/is_scripting_enabled.ts index 6a3900229c0d4..ca3ca5b5c59aa 100644 --- a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/is_scripting_enabled.ts +++ b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/is_scripting_enabled.ts @@ -6,27 +6,48 @@ * Side Public License, v 1. */ +import { defer, map, retry, timer, firstValueFrom, throwError } from 'rxjs'; import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { isRetryableEsClientError } from './retryable_es_client_errors'; const scriptAllowedTypesKey = 'script.allowed_types'; export const isInlineScriptingEnabled = async ({ client, + maxRetries = 20, + maxRetryDelay = 64, }: { client: ElasticsearchClient; + maxRetries?: number; + maxRetryDelay?: number; }): Promise => { - const settings = await client.cluster.getSettings({ - include_defaults: true, - flat_settings: true, - }); + return firstValueFrom( + defer(() => { + return client.cluster.getSettings({ + include_defaults: true, + flat_settings: true, + }); + }).pipe( + retry({ + count: maxRetries, + delay: (error, retryIndex) => { + if (isRetryableEsClientError(error)) { + const retryDelay = 1000 * Math.min(Math.pow(2, retryIndex), maxRetryDelay); // 2s, 4s, 8s, 16s, 32s, 64s, 64s, 64s ... + return timer(retryDelay); + } else { + return throwError(error); + } + }, + }), + map((settings) => { + const scriptAllowedTypes: string[] = + settings.transient[scriptAllowedTypesKey] ?? + settings.persistent[scriptAllowedTypesKey] ?? + settings.defaults![scriptAllowedTypesKey] ?? + []; - // priority: transient -> persistent -> default - const scriptAllowedTypes: string[] = - settings.transient[scriptAllowedTypesKey] ?? - settings.persistent[scriptAllowedTypesKey] ?? - settings.defaults![scriptAllowedTypesKey] ?? - []; - - // when unspecified, the setting as a default `[]` value that means that both scriptings are allowed. - return scriptAllowedTypes.length === 0 || scriptAllowedTypes.includes('inline'); + return scriptAllowedTypes.length === 0 || scriptAllowedTypes.includes('inline'); + }) + ) + ); }; diff --git a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/retryable_es_client_errors.test.ts b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/retryable_es_client_errors.test.ts new file mode 100644 index 0000000000000..45015cece5b43 --- /dev/null +++ b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/retryable_es_client_errors.test.ts @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { errors as esErrors } from '@elastic/elasticsearch'; +import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; +import { isRetryableEsClientError } from './retryable_es_client_errors'; + +describe('isRetryableEsClientError', () => { + describe('returns `false` for', () => { + test('non-retryable response errors', async () => { + const error = new esErrors.ResponseError( + elasticsearchClientMock.createApiResponse({ + body: { error: { type: 'cluster_block_exception' } }, + statusCode: 400, + }) + ); + + expect(isRetryableEsClientError(error)).toEqual(false); + }); + }); + + describe('returns `true` for', () => { + it('NoLivingConnectionsError', () => { + const error = new esErrors.NoLivingConnectionsError( + 'reason', + elasticsearchClientMock.createApiResponse() + ); + + expect(isRetryableEsClientError(error)).toEqual(true); + }); + + it('ConnectionError', () => { + const error = new esErrors.ConnectionError( + 'reason', + elasticsearchClientMock.createApiResponse() + ); + expect(isRetryableEsClientError(error)).toEqual(true); + }); + + it('TimeoutError', () => { + const error = new esErrors.TimeoutError( + 'reason', + elasticsearchClientMock.createApiResponse() + ); + expect(isRetryableEsClientError(error)).toEqual(true); + }); + + it('ResponseError of type snapshot_in_progress_exception', () => { + const error = new esErrors.ResponseError( + elasticsearchClientMock.createApiResponse({ + body: { error: { type: 'snapshot_in_progress_exception' } }, + }) + ); + expect(isRetryableEsClientError(error)).toEqual(true); + }); + + it.each([503, 401, 403, 408, 410, 429])('ResponseError with %p status code', (statusCode) => { + const error = new esErrors.ResponseError( + elasticsearchClientMock.createApiResponse({ + statusCode, + body: { error: { type: 'reason' } }, + }) + ); + + expect(isRetryableEsClientError(error)).toEqual(true); + }); + }); +}); diff --git a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/retryable_es_client_errors.ts b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/retryable_es_client_errors.ts new file mode 100644 index 0000000000000..2ba0ff20a2732 --- /dev/null +++ b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/retryable_es_client_errors.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { errors as EsErrors } from '@elastic/elasticsearch'; + +const retryResponseStatuses = [ + 503, // ServiceUnavailable + 401, // AuthorizationException + 403, // AuthenticationException + 408, // RequestTimeout + 410, // Gone + 429, // TooManyRequests -> ES circuit breaker +]; + +/** + * Returns true if the given elasticsearch error should be retried + * by retry-based resiliency systems such as the SO migration, false otherwise. + */ +export const isRetryableEsClientError = (e: EsErrors.ElasticsearchClientError): boolean => { + if ( + e instanceof EsErrors.NoLivingConnectionsError || + e instanceof EsErrors.ConnectionError || + e instanceof EsErrors.TimeoutError || + (e instanceof EsErrors.ResponseError && + (retryResponseStatuses.includes(e?.statusCode!) || + // ES returns a 400 Bad Request when trying to close or delete an + // index while snapshots are in progress. This should have been a 503 + // so once https://github.com/elastic/elasticsearch/issues/65883 is + // fixed we can remove this. + e?.body?.error?.type === 'snapshot_in_progress_exception')) + ) { + return true; + } + return false; +}; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/catch_retryable_es_client_errors.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/catch_retryable_es_client_errors.ts index 74877c9386422..965742a2abb8f 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/catch_retryable_es_client_errors.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/catch_retryable_es_client_errors.ts @@ -8,15 +8,7 @@ import * as Either from 'fp-ts/lib/Either'; import { errors as EsErrors } from '@elastic/elasticsearch'; - -const retryResponseStatuses = [ - 503, // ServiceUnavailable - 401, // AuthorizationException - 403, // AuthenticationException - 408, // RequestTimeout - 410, // Gone - 429, // TooManyRequests -> ES circuit breaker -]; +import { isRetryableEsClientError } from '@kbn/core-elasticsearch-server-internal'; export interface RetryableEsClientError { type: 'retryable_es_client_error'; @@ -27,18 +19,7 @@ export interface RetryableEsClientError { export const catchRetryableEsClientErrors = ( e: EsErrors.ElasticsearchClientError ): Either.Either => { - if ( - e instanceof EsErrors.NoLivingConnectionsError || - e instanceof EsErrors.ConnectionError || - e instanceof EsErrors.TimeoutError || - (e instanceof EsErrors.ResponseError && - (retryResponseStatuses.includes(e?.statusCode!) || - // ES returns a 400 Bad Request when trying to close or delete an - // index while snapshots are in progress. This should have been a 503 - // so once https://github.com/elastic/elasticsearch/issues/65883 is - // fixed we can remove this. - e?.body?.error?.type === 'snapshot_in_progress_exception')) - ) { + if (isRetryableEsClientError(e)) { return Either.left({ type: 'retryable_es_client_error' as const, message: e?.message, From 508f30926495949e9d9b1f6505071aee1c511f6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loix?= Date: Tue, 31 Oct 2023 13:07:51 +0000 Subject: [PATCH 02/21] [Serverless nav] Update docs with panels API (#170221) --- .../src/project_navigation.ts | 6 +- .../serverless_projects_documentation.mdx | 150 ++++++++++++++++-- 2 files changed, 136 insertions(+), 20 deletions(-) diff --git a/packages/core/chrome/core-chrome-browser/src/project_navigation.ts b/packages/core/chrome/core-chrome-browser/src/project_navigation.ts index e7c1689048b0b..b879cf6f716d7 100644 --- a/packages/core/chrome/core-chrome-browser/src/project_navigation.ts +++ b/packages/core/chrome/core-chrome-browser/src/project_navigation.ts @@ -83,11 +83,7 @@ interface NodeDefinitionBase { */ breadcrumbStatus?: 'hidden' | 'visible'; /** - * Optional status to for the side navigation. "hidden" and "visible" are self explanatory. - * The `renderAsItem` status is _only_ for group nodes (nodes with children declared or with - * the "nodeType" set to `group`) and allow to render the node as an "item" instead of the head of - * a group. This is usefull to have sub-pages declared in the tree that will correctly be mapped - * in the Breadcrumbs, but are not rendered in the side navigation. + * Optional status to indicate if the node should be hidden in the side nav (but still present in the navigation tree). * @default 'visible' */ sideNavStatus?: SideNavNodeStatus; diff --git a/packages/shared-ux/chrome/serverless_projects_documentation.mdx b/packages/shared-ux/chrome/serverless_projects_documentation.mdx index 498d2a0737f9c..25727791e0380 100644 --- a/packages/shared-ux/chrome/serverless_projects_documentation.mdx +++ b/packages/shared-ux/chrome/serverless_projects_documentation.mdx @@ -48,11 +48,11 @@ In the following sections, we will explore each of these building blocks in deta > Left Side Navigation is available in shared_ux storybook under the `Chrome/Navigation` section. You can explore the components and their properties there. > `yarn storybook shared_ux` -The left side navigation is a primary way for users to navigate through different sections of the project. It consists of a tree of navigation items that can be expanded and collapsed. Apart from the navigation tree it also supports special pre-built blocks like recently accessed items. The main part of the navigation tree is project's navigation: this is fully configured and supported by the project teams (e.g. Observability). We also provide pre-configured platform sections as presets that solutions can use as part of their navigation (e.g. `ml`, `analytics`). Solutions can customize those sections to their needs. +The left side navigation is a primary way for users to navigate through different sections of the project. It consists of a tree of navigation items that can be expanded/collapsed or opened in a side panel. Apart from the navigation tree it also supports special pre-built blocks like recently accessed items. The main part of the navigation tree is project's navigation: this is fully configured and supported by the project teams (e.g. Observability). We also provide pre-configured platform sections as presets that solutions can use as part of their navigation (e.g. `ml`, `analytics`). Solutions can customize those sections to their needs. There are two approaches to building the side navigation: -1. **Navigation tree definition**: Developers provide a navigation tree definition. This approach is recommended if the use case works with the existing building blocks. +1. **Navigation tree definition**: Developers provide a navigation tree definition. ~~This approach is recommended if the use case works with the existing building blocks.~~ :warning: We are planing in deprecating this approach, use the React components instead. 2. **React components**: Alternatively, we provide a set of pre-built components that can be used to construct the left side navigation. These components include: @@ -69,6 +69,8 @@ By leveraging these components, you can create a customized left side navigation ### Navigation Tree Definition +:warning: **Soon to be deprecated** :warning: - Use the React components instead. + Use the `NavigationTreeDefinition` interface to create your left side navigation using a tree definition. This interface allows you to define the complete navigation tree, including the **body** and **footer** of the navigation. #### Example @@ -109,6 +111,19 @@ const navigationTree: NavigationTreeDefinition = { }, ], }, + { + id: 'search', + title: 'Search', + renderAs: 'accordion', // This group will be rendered as an accordion + children: [ + { + link: 'foo', + }, + { + link: 'bar', + }, + ], + }, ], }, { @@ -126,6 +141,7 @@ const navigationTree: NavigationTreeDefinition = { children: [ { id: 'settings', + renderAs: 'accordion', children: [ { link: 'management', @@ -190,25 +206,41 @@ The `RootNavigationItemDefinition` is one of: - `GroupDefinition` - `RecentlyAccessedDefinition` +- `PresetDefinition` +- `ItemDefinition` + +All those interfaces extend the `NodeDefinition` which has the following **common properties**: + +### `NodeDefinition` + +| Property | Type | Description| +| -------------------- | -------------------------| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `id` | `Id` (extends `string`) | Optional ID of the navigation node.| +| `title` | `string` | Optional title of the navigation node. If not provided and a "link" is provided, the title will be the Deep link title.| +| `link` | `LinkId` | Optional App ID or deep link ID for the navigation node. [More about deep links](#deep-links)| +| `icon` | `string` | Optional icon for the navigation node. Note that not all navigation depths will render the icon.| +| `href` | `string` | Use `href` for absolute links only. Internal links should use "link".| +| `getIsActive` | `function` | Optional function to control the active state. This function is called whenever the location changes.| +| `sideNavStatus` | `'hidden'\|'visible'` | Optional status to indicate if the node should be hidden in the side nav (but still present in the navigation tree). | +| `breadcrumbStatus` | `'hidden'\|'visible'` | An optional flag to indicate if the breadcrumb should be hidden when this node is active. The default value is `'visible'`.| +| `spaceBefore` | `EuiThemeSize\|null` | Optional vertical space to add before this node. It defaults to `null` except for group node at **tree depth 1** where it defaults to `"m"`. | + ##### `GroupDefinition` The `GroupDefinition` interface represents a group of items in the side navigation. It extends the `NodeDefinition` interface and has the following additional properties: -| Property | Type | Description | +| Property | Type | Description| | -------------------- | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `type` | `'navGroup'` | Indicates that this item is a navigation group. | -| `defaultIsCollapsed` | `boolean \| undefined` | Determines if the group is initially collapsed or expanded. Use `undefined` (recommended) to open the group if any of its children nodes match the current URL, `false` to always open the group, or `true` to always collapse it. | -| `preset` | `NavigationGroupPreset` | A preset value for the group, such as `'analytics'`, `'devtools'`, `'ml'`, or `'management'`. | -| `id` | `Id` | Optional ID of the navigation node. | -| `title` | `string` | Optional title of the navigation node. If not provided and a "link" is provided, the title will be the Deep link title. | -| `link` | `LinkId` | Optional App ID or deep link ID for the navigation node. [More about deep links](#deep-links) | -| `cloudLink` | `CloudLinkId` | Optional cloud link ID for the navigation node. [More about cloud links](#cloud-links) | -| `icon` | `string` | Optional icon for the navigation node. Note that not all navigation depths will render the icon. | -| `children` | `NodeDefinition[]` | Optional children of the navigation node. | -| `href` | `string` | Use `href` for absolute links only. Internal links should use "link". | -| `getIsActive` | `function` | Optional function to control the active state. This function is called whenever the location changes. | -| `breadcrumbStatus` | `'hidden'\|'visible'` | An optional flag to indicate if the breadcrumb should be hidden when this node is active. The default value is `'visible'`. | +| `type` | `'navGroup'` | Indicates that this item is a navigation group.| +| `children` | `NodeDefinition[]` | Children of the group navigation node. This is recursive, groups can contain other groups. | +| `defaultIsCollapsed` | `boolean \| undefined` | Determines if the group is initially collapsed or expanded. Use `undefined` (recommended) to open the group if any of its children nodes match the current URL, `false` to always open the group, or `true` to always collapse it.| +| `cloudLink` | `CloudLinkId` | Optional cloud link ID for the navigation node. [More about cloud links](#cloud-links)| +| `renderAs` | `'block'\|'accordion'\|'panelOpener'\|'item'`| Property to indicate how the group should be rendered.
* `'block'`: this is the default, renders the group as a block of items.
* `'accordion'`: wraps the items in an `EuiAccordion`.
* `'panelOpener'`: renders as an item with a `link` (required) + an icon button to open a panel on the right of the side nav.
* `'item'`: renders the group as an item in the side nav. This is useful when you want to declare descendant links of a node that will be displayed in the breadcrumb as such but you don't want to render any of the `children` in the side nav. | +| `appendHorizontalRule` | `boolean` | Optional flag to indicate if a horizontal rule should be rendered after the node.
Note: this property is currently only available for group nodes in the navigation **panel** opening on the right of the side nav. | +| `isCollapsible` | `boolean` | Optional flag to indicate if the accordion is collapsible (when `renderAs` is set to `'accordion'`.| + + ##### `RecentlyAccessedDefinition` @@ -218,6 +250,94 @@ The `GroupDefinition` interface represents a group of items in the side navigati | `recentlyAccessed$` | `Observable` | An optional observable for recently accessed items. If not provided, the recently accessed items from the Chrome service will be used. | | `defaultIsCollapsed` | `boolean` | If set to `true`, the recently accessed list will be collapsed by default. The default value is `false`. | +##### `PresetDefinition` + +The `PresetDefinition` interface represents a group of items which are prepopulated based on a preset. It extends the `GroupDefinition` interface (without `children` as those are pre-defined) and has the following additional properties: + +| Property | Type | Description| +| -------------------- | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `type` | `'preset'` | Indicates that this item is a navigation group.| +| `preset` | `NavigationGroupPreset` | A preset value for the group, such as `'analytics'`, `'devtools'`, `'ml'`, or `'management'`.| | + +##### `ItemDefinition` + +The `GroupDefinition` interface represents a group of items in the side navigation. It extends the `NodeDefinition` interface and has the following additional properties: + + +| Property | Type | Description| +| -------------------- | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `type` | `'navItem'` | Indicates that this item is a navigation item.| +| `cloudLink` | `CloudLinkId` | Optional cloud link ID for the navigation node. [More about cloud links](#cloud-links)| +| `openInNewTab` | `boolean` | Optional flag to indicate if the target page should be opened in a new Browser tab.
Note: this property is currently only used in the navigation **panel** opening on the right of the side nav.| +| `withBadge` | `boolean` | Optional flag to indicate if a badge should be rendered next to the text.
Note: this property is currently only used in the navigation **panel** opening on the right of the side nav.| +| `badgeOptions` | `{ text?: string; }` | If `withBadge` is true, this object can be used to customize the badge. | + +### Panels + +As seen in the API above, the `renderAs` property can be used to render a group as a panel opener. This is useful when you want to display a group of links in the side navigation and display its content in a panel on the right of the side navigation. +The content of the panel can be auto-generaged (based on the group's children) or manually provided. + +#### Auto-generated panels + +When the panel content is auto-generated, the group's children will be rendered in the panel. Those `children` can be items or other groups (that render as `'block' (default) or `'accordion'`). +The panel will be opened when the user clicks on the group's icon button. The panel will be closed when the user clicks on the group's icon again or when the user clicks outside of the panel. + +#### Manually provided panels + +When the panel content is manually provided, the group's `children` are used for the navigation tree definition (and the breadcrumbs) but the actual UI content rendered inside the panel is provided through JSX. + +```tsx +// 1. Define the PanelContentProvider +// ----------------------------------- +const panelContentProvider: PanelContentProvider = (id: string) => { + // The full ID of the node icon button that was clicked is provided (e.g. "root.group1.itemA") + // You can use this ID to determine which panel content to render + + if (id === 'foo1') { + // Return the JSX to render in the panel for this node. + return { + content: ({ + /** Handler to close the panel */ + closePanel, + /** ChromeNavigationNode - The node that has been clicked in the main nav */ + selectedNode, + /** ChromeProjectNavigationNode[][] - Active nodes that match the current URL location */ + activeNodes, + }) => { + return ( +
+ This is a custom component to render in the panel. + closePanel()}>Close panel +
+ ); + }, + }; + } + + if (id === 'foo2') { + // If need be you can only customize the "Title" of the panel and leave the content + // to be auto-generated. + return { + title:
Custom title
, + }; + } + + // All other nodes content ids that haven't match will be auto-generated +}; + +// 2. Pass it to the or components +// -------------------------------------------------------------------- + + + + {...} + +; + +// or using +; +``` + ### React components If you need other navigation sections in your navigation you will need to use our React components. They have the same properties as seen above except the `unstyled` prop that we will detail below. From bf64c22717525fafb5946bd400cb3b70434e3f6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Tue, 31 Oct 2023 14:23:34 +0100 Subject: [PATCH 03/21] [APM] Simplify cleanup of alerts in API tests (#170111) --- .../test/apm_api_integration/configs/index.ts | 23 +++---- .../tests/alerts/anomaly_alert.spec.ts | 14 ++--- .../alerts/error_count_threshold.spec.ts | 22 +------ ...ate.ts => cleanup_rule_and_alert_state.ts} | 8 ++- .../tests/alerts/transaction_duration.spec.ts | 30 ++------- .../alerts/transaction_error_rate.spec.ts | 30 ++------- .../tests/anomalies/anomaly_charts.spec.ts | 63 ++++++++----------- 7 files changed, 56 insertions(+), 134 deletions(-) rename x-pack/test/apm_api_integration/tests/alerts/helpers/{cleanup_state.ts => cleanup_rule_and_alert_state.ts} (79%) diff --git a/x-pack/test/apm_api_integration/configs/index.ts b/x-pack/test/apm_api_integration/configs/index.ts index 2e45cca1c50ae..cdefe3011e49f 100644 --- a/x-pack/test/apm_api_integration/configs/index.ts +++ b/x-pack/test/apm_api_integration/configs/index.ts @@ -14,36 +14,33 @@ const apmDebugLogger = { appenders: ['console'], }; +const kibanaConfig = { + 'xpack.apm.forceSyntheticSource': 'true', + 'logging.loggers': [apmDebugLogger], + 'server.publicBaseUrl': 'http://mockedPublicBaseUrl', +}; + const apmFtrConfigs = { basic: { license: 'basic' as const, - kibanaConfig: { - 'xpack.apm.forceSyntheticSource': 'true', - 'logging.loggers': [apmDebugLogger], - 'server.publicBaseUrl': 'http://mockedPublicBaseUrl', - }, + kibanaConfig, }, trial: { license: 'trial' as const, - kibanaConfig: { - 'xpack.apm.forceSyntheticSource': 'true', - 'logging.loggers': [apmDebugLogger], - }, + kibanaConfig, }, rules: { license: 'trial' as const, kibanaConfig: { + ...kibanaConfig, 'xpack.ruleRegistry.write.enabled': 'true', - 'xpack.apm.forceSyntheticSource': 'true', - 'logging.loggers': [apmDebugLogger], }, }, cloud: { license: 'basic' as const, kibanaConfig: { + ...kibanaConfig, 'xpack.apm.agent.migrations.enabled': 'true', - 'xpack.apm.forceSyntheticSource': 'true', - 'logging.loggers': [apmDebugLogger], }, }, }; diff --git a/x-pack/test/apm_api_integration/tests/alerts/anomaly_alert.spec.ts b/x-pack/test/apm_api_integration/tests/alerts/anomaly_alert.spec.ts index 430cebb4d0c02..cae769750b4cc 100644 --- a/x-pack/test/apm_api_integration/tests/alerts/anomaly_alert.spec.ts +++ b/x-pack/test/apm_api_integration/tests/alerts/anomaly_alert.spec.ts @@ -13,8 +13,9 @@ import { range } from 'lodash'; import { ML_ANOMALY_SEVERITY } from '@kbn/ml-anomaly-utils/anomaly_severity'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createAndRunApmMlJobs } from '../../common/utils/create_and_run_apm_ml_jobs'; -import { createApmRule, deleteApmRules } from './helpers/alerting_api_helper'; +import { createApmRule } from './helpers/alerting_api_helper'; import { waitForActiveRule } from './helpers/wait_for_active_rule'; +import { cleanupRuleAndAlertState } from './helpers/cleanup_rule_and_alert_state'; export default function ApiTest({ getService }: FtrProviderContext) { const registry = getService('registry'); @@ -69,14 +70,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); async function cleanup() { - try { - await synthtraceEsClient.clean(); - await deleteApmRules(supertest); - await ml.cleanMlIndices(); - logger.info('Completed cleaned up'); - } catch (e) { - logger.info('Could not cleanup', e); - } + await synthtraceEsClient.clean(); + await cleanupRuleAndAlertState({ es, supertest, logger }); + await ml.cleanMlIndices(); } describe('with ml jobs', () => { diff --git a/x-pack/test/apm_api_integration/tests/alerts/error_count_threshold.spec.ts b/x-pack/test/apm_api_integration/tests/alerts/error_count_threshold.spec.ts index 73a6386ea1ed3..1cfcbc165b17f 100644 --- a/x-pack/test/apm_api_integration/tests/alerts/error_count_threshold.spec.ts +++ b/x-pack/test/apm_api_integration/tests/alerts/error_count_threshold.spec.ts @@ -14,16 +14,13 @@ import { omit } from 'lodash'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createApmRule, - deleteRuleById, - deleteAlertsByRuleId, fetchServiceInventoryAlertCounts, fetchServiceTabAlertCount, ApmAlertFields, createIndexConnector, - deleteActionConnector, getIndexAction, } from './helpers/alerting_api_helper'; -import { cleanupAllState } from './helpers/cleanup_state'; +import { cleanupRuleAndAlertState } from './helpers/cleanup_rule_and_alert_state'; import { waitForAlertsForRule } from './helpers/wait_for_alerts_for_rule'; import { waitForIndexConnectorResults } from './helpers/wait_for_index_connector_results'; import { waitForActiveRule } from './helpers/wait_for_active_rule'; @@ -55,8 +52,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { }; before(async () => { - cleanupAllState({ es, supertest }); - const opbeansJava = apm .service({ name: 'opbeans-java', environment: 'production', agentName: 'java' }) .instance('instance'); @@ -134,13 +129,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); after(async () => { - try { - await deleteActionConnector({ supertest, es, actionId }); - await deleteRuleById({ supertest, ruleId }); - await deleteAlertsByRuleId({ es, ruleId }); - } catch (e) { - logger.info('Could not delete rule or action connector', e); - } + await cleanupRuleAndAlertState({ es, supertest, logger }); }); it('checks if rule is active', async () => { @@ -285,12 +274,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); after(async () => { - try { - await deleteRuleById({ supertest, ruleId }); - await deleteAlertsByRuleId({ es, ruleId }); - } catch (e) { - logger.info('Could not delete rule', e); - } + await cleanupRuleAndAlertState({ es, supertest, logger }); }); it('produces one alert for the opbeans-php service', async () => { diff --git a/x-pack/test/apm_api_integration/tests/alerts/helpers/cleanup_state.ts b/x-pack/test/apm_api_integration/tests/alerts/helpers/cleanup_rule_and_alert_state.ts similarity index 79% rename from x-pack/test/apm_api_integration/tests/alerts/helpers/cleanup_state.ts rename to x-pack/test/apm_api_integration/tests/alerts/helpers/cleanup_rule_and_alert_state.ts index 243ca1f71a36e..144446a28b3d7 100644 --- a/x-pack/test/apm_api_integration/tests/alerts/helpers/cleanup_state.ts +++ b/x-pack/test/apm_api_integration/tests/alerts/helpers/cleanup_rule_and_alert_state.ts @@ -6,6 +6,7 @@ */ import { Client } from '@elastic/elasticsearch'; +import { ToolingLog } from '@kbn/tooling-log'; import type { SuperTest, Test } from 'supertest'; import { clearKibanaApmEventLog, @@ -14,12 +15,14 @@ import { deleteActionConnectorIndex, } from './alerting_api_helper'; -export async function cleanupAllState({ +export async function cleanupRuleAndAlertState({ es, supertest, + logger, }: { es: Client; supertest: SuperTest; + logger: ToolingLog; }) { try { await Promise.all([ @@ -29,7 +32,6 @@ export async function cleanupAllState({ await clearKibanaApmEventLog(es), ]); } catch (e) { - // eslint-disable-next-line no-console - console.error(`An error occured while cleaning up the state: ${e}`); + logger.error(`An error occured while cleaning up the state: ${e}`); } } diff --git a/x-pack/test/apm_api_integration/tests/alerts/transaction_duration.spec.ts b/x-pack/test/apm_api_integration/tests/alerts/transaction_duration.spec.ts index 84d34f29b7c86..bb8c9833bbc6d 100644 --- a/x-pack/test/apm_api_integration/tests/alerts/transaction_duration.spec.ts +++ b/x-pack/test/apm_api_integration/tests/alerts/transaction_duration.spec.ts @@ -15,15 +15,11 @@ import { createApmRule, fetchServiceInventoryAlertCounts, fetchServiceTabAlertCount, - deleteAlertsByRuleId, - deleteRuleById, - clearKibanaApmEventLog, ApmAlertFields, createIndexConnector, getIndexAction, - deleteActionConnector, } from './helpers/alerting_api_helper'; -import { cleanupAllState } from './helpers/cleanup_state'; +import { cleanupRuleAndAlertState } from './helpers/cleanup_rule_and_alert_state'; import { waitForAlertsForRule } from './helpers/wait_for_alerts_for_rule'; import { waitForActiveRule } from './helpers/wait_for_active_rule'; import { waitForIndexConnectorResults } from './helpers/wait_for_index_connector_results'; @@ -49,8 +45,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { registry.when('transaction duration alert', { config: 'basic', archives: [] }, () => { before(async () => { - cleanupAllState({ es, supertest }); - const opbeansJava = apm .service({ name: 'opbeans-java', environment: 'production', agentName: 'java' }) .instance('instance'); @@ -77,12 +71,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); after(async () => { - try { - await synthtraceEsClient.clean(); - await clearKibanaApmEventLog(es); - } catch (e) { - logger.info('Could not clear apm event log', e); - } + await synthtraceEsClient.clean(); }); describe('create rule for opbeans-java without kql filter', () => { @@ -111,13 +100,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); after(async () => { - try { - await deleteActionConnector({ supertest, es, actionId }); - await deleteRuleById({ supertest, ruleId }); - await deleteAlertsByRuleId({ es, ruleId }); - } catch (e) { - logger.info('Could not delete rule or action connector', e); - } + await cleanupRuleAndAlertState({ es, supertest, logger }); }); it('checks if rule is active', async () => { @@ -229,12 +212,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); after(async () => { - try { - await deleteAlertsByRuleId({ es, ruleId }); - await deleteRuleById({ supertest, ruleId }); - } catch (e) { - logger.info('Could not delete rule or action connector', e); - } + await cleanupRuleAndAlertState({ es, supertest, logger }); }); it('checks if rule is active', async () => { diff --git a/x-pack/test/apm_api_integration/tests/alerts/transaction_error_rate.spec.ts b/x-pack/test/apm_api_integration/tests/alerts/transaction_error_rate.spec.ts index cdbe034b4c6ae..056ee4e272d61 100644 --- a/x-pack/test/apm_api_integration/tests/alerts/transaction_error_rate.spec.ts +++ b/x-pack/test/apm_api_integration/tests/alerts/transaction_error_rate.spec.ts @@ -15,15 +15,11 @@ import { createApmRule, fetchServiceInventoryAlertCounts, fetchServiceTabAlertCount, - deleteAlertsByRuleId, - clearKibanaApmEventLog, - deleteRuleById, ApmAlertFields, getIndexAction, createIndexConnector, - deleteActionConnector, } from './helpers/alerting_api_helper'; -import { cleanupAllState } from './helpers/cleanup_state'; +import { cleanupRuleAndAlertState } from './helpers/cleanup_rule_and_alert_state'; import { waitForAlertsForRule } from './helpers/wait_for_alerts_for_rule'; import { waitForActiveRule } from './helpers/wait_for_active_rule'; import { waitForIndexConnectorResults } from './helpers/wait_for_index_connector_results'; @@ -38,8 +34,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { registry.when('transaction error rate alert', { config: 'basic', archives: [] }, () => { before(async () => { - cleanupAllState({ es, supertest }); - const opbeansJava = apm .service({ name: 'opbeans-java', environment: 'production', agentName: 'java' }) .instance('instance'); @@ -76,12 +70,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); after(async () => { - try { - await synthtraceEsClient.clean(); - await clearKibanaApmEventLog(es); - } catch (e) { - logger.info('Could not clean up apm event log', e); - } + await synthtraceEsClient.clean(); }); describe('create rule without kql query', () => { @@ -121,13 +110,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); after(async () => { - try { - await deleteActionConnector({ supertest, es, actionId }); - await deleteRuleById({ supertest, ruleId }); - await deleteAlertsByRuleId({ es, ruleId }); - } catch (e) { - logger.info('Could not delete rule or action connector', e); - } + await cleanupRuleAndAlertState({ es, supertest, logger }); }); it('checks if rule is active', async () => { @@ -250,12 +233,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); after(async () => { - try { - await deleteRuleById({ supertest, ruleId }); - await deleteAlertsByRuleId({ es, ruleId }); - } catch (e) { - logger.info('Could not delete rule', e); - } + await cleanupRuleAndAlertState({ es, supertest, logger }); }); it('indexes alert document with all group-by fields', async () => { diff --git a/x-pack/test/apm_api_integration/tests/anomalies/anomaly_charts.spec.ts b/x-pack/test/apm_api_integration/tests/anomalies/anomaly_charts.spec.ts index 681443b69dff7..05d7da0e2ca30 100644 --- a/x-pack/test/apm_api_integration/tests/anomalies/anomaly_charts.spec.ts +++ b/x-pack/test/apm_api_integration/tests/anomalies/anomaly_charts.spec.ts @@ -11,6 +11,7 @@ import { Environment } from '@kbn/apm-plugin/common/environment_rt'; import { apm, timerange } from '@kbn/apm-synthtrace-client'; import expect from '@kbn/expect'; import { last, omit, range } from 'lodash'; +import moment from 'moment'; import { ApmApiError } from '../../common/apm_api_supertest'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createAndRunApmMlJobs } from '../../common/utils/create_and_run_apm_ml_jobs'; @@ -21,9 +22,13 @@ export default function ApiTest({ getService }: FtrProviderContext) { const ml = getService('ml'); const es = getService('es'); const logger = getService('log'); - const synthtraceEsClient = getService('synthtraceEsClient'); + const start = moment().subtract(2, 'days'); + const end = moment(); + const spikeStart = moment().subtract(8, 'hours'); + const spikeEnd = moment().subtract(6, 'hours'); + async function statusOf(p: Promise<{ status: number }>) { try { const { status } = await p; @@ -38,14 +43,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { function getAnomalyCharts( { - start, - end, transactionType, serviceName, environment, }: { - start: string; - end: string; transactionType: string; serviceName: string; environment: Environment; @@ -59,8 +60,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { serviceName, }, query: { - start, - end, + start: start.toISOString(), + end: end.toISOString(), transactionType, environment, }, @@ -77,8 +78,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { getAnomalyCharts({ serviceName: 'a', transactionType: 'request', - start: '2021-01-01T00:00:00.000Z', - end: '2021-01-01T00:15:00.000Z', environment: 'ENVIRONMENT_ALL', }) ); @@ -92,12 +91,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { 'fetching service anomalies with a trial license', { config: 'trial', archives: [] }, () => { - const start = '2021-01-01T00:00:00.000Z'; - const end = '2021-01-08T00:15:00.000Z'; - - const spikeStart = new Date('2021-01-03T00:00:00.000Z').getTime(); - const spikeEnd = new Date('2021-01-03T02:00:00.000Z').getTime(); - const NORMAL_DURATION = 100; const NORMAL_RATE = 1; @@ -110,13 +103,13 @@ export default function ApiTest({ getService }: FtrProviderContext) { .service({ name: 'b', environment: 'development', agentName: 'go' }) .instance('b'); - const events = timerange(new Date(start).getTime(), new Date(end).getTime()) + const events = timerange(start.valueOf(), end.valueOf()) .interval('1m') .rate(1) .generator((timestamp) => { - const isInSpike = timestamp >= spikeStart && timestamp < spikeEnd; + const isInSpike = timestamp >= spikeStart.valueOf() && timestamp < spikeEnd.valueOf(); const count = isInSpike ? 4 : NORMAL_RATE; - const duration = isInSpike ? 1000 : NORMAL_DURATION; + const duration = isInSpike ? 10000 : NORMAL_DURATION; const outcome = isInSpike ? 'failure' : 'success'; return [ @@ -139,9 +132,14 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); after(async () => { - await synthtraceEsClient.clean(); + await cleanup(); }); + async function cleanup() { + await synthtraceEsClient.clean(); + await ml.cleanMlIndices(); + } + it('returns a 403 for a user without access to ML', async () => { expect( await statusOf( @@ -149,8 +147,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { { serviceName: 'a', transactionType: 'request', - start, - end, environment: 'ENVIRONMENT_ALL', }, apmApiClient.noMlAccessUser @@ -165,8 +161,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { getAnomalyCharts({ serviceName: 'a', transactionType: 'request', - start, - end, environment: 'ENVIRONMENT_ALL', }) ); @@ -185,17 +179,11 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); }); - after(async () => { - await ml.cleanMlIndices(); - }); - it('returns a 200 for a user _with_ access to ML', async () => { const status = await statusOf( getAnomalyCharts({ serviceName: 'a', transactionType: 'request', - start, - end, environment: 'ENVIRONMENT_ALL', }) ); @@ -209,15 +197,13 @@ export default function ApiTest({ getService }: FtrProviderContext) { let latencySeries: ServiceAnomalyTimeseries | undefined; let throughputSeries: ServiceAnomalyTimeseries | undefined; let failureRateSeries: ServiceAnomalyTimeseries | undefined; - const endTimeMs = new Date(end).getTime(); + const endTimeMs = end.valueOf(); before(async () => { allAnomalyTimeseries = ( await getAnomalyCharts({ serviceName: 'a', transactionType: 'request', - start, - end, environment: 'ENVIRONMENT_ALL', }) ).body.allAnomalyTimeseries; @@ -246,12 +232,11 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect( allAnomalyTimeseries.every((spec) => - spec.bounds.every( - (bound) => bound.x >= new Date(start).getTime() && bound.x <= endTimeMs - ) + spec.bounds.every((bound) => bound.x >= start.valueOf() && bound.x <= endTimeMs) ) ); }); + it('returns model plots with latest bucket matching the end time', () => { expect(allAnomalyTimeseries.every((spec) => last(spec.bounds)?.x === endTimeMs)); }); @@ -310,19 +295,21 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect( latencyAnomalies?.every( - (anomaly) => anomaly.x >= spikeStart && (anomaly.actual ?? 0) > NORMAL_DURATION + (anomaly) => + anomaly.x >= spikeStart.valueOf() && (anomaly.actual ?? 0) > NORMAL_DURATION ) ); expect( throughputAnomalies?.every( - (anomaly) => anomaly.x >= spikeStart && (anomaly.actual ?? 0) > NORMAL_RATE + (anomaly) => + anomaly.x >= spikeStart.valueOf() && (anomaly.actual ?? 0) > NORMAL_RATE ) ); expect( failureRateAnomalies?.every( - (anomaly) => anomaly.x >= spikeStart && (anomaly.actual ?? 0) > 0 + (anomaly) => anomaly.x >= spikeStart.valueOf() && (anomaly.actual ?? 0) > 0 ) ); }); From 2e51c2517f52e2add1b2889bfc667c390d1f5441 Mon Sep 17 00:00:00 2001 From: Elastic Machine Date: Wed, 1 Nov 2023 01:09:28 +1100 Subject: [PATCH 04/21] [main] Sync bundled packages with Package Storage (#168698) Automated by https://internal-ci.elastic.co/job/package_storage/job/sync-bundled-packages-job/job/main/8080/ Co-authored-by: apmmachine --- fleet_packages.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fleet_packages.json b/fleet_packages.json index 4640e150b1a36..ed422f0cfcb7e 100644 --- a/fleet_packages.json +++ b/fleet_packages.json @@ -38,7 +38,7 @@ }, { "name": "fleet_server", - "version": "1.3.1" + "version": "1.4.0" }, { "name": "profiler_symbolizer", From a1bde01a8aafdbd0ca3bed418a3848e865801038 Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Tue, 31 Oct 2023 15:26:43 +0100 Subject: [PATCH 05/21] [Serverless/Breadcrumbs] Use `EuiBreadcrumbs` instead of `EuiHeaderBreadcrumbs` (#169838) ## Summary fix https://github.com/elastic/kibana/issues/166593 https://github.com/elastic/kibana/assets/7784120/2569007b-92b6-47d0-a893-8747fbf17d2b - "Projects" link removed, now it is part of breadcrumb. This also makes the header more responsive as we get the flexibility from the breadcrumbs. - Added "View all projects" - Added "Manage project" link --- .../src/chrome_service.tsx | 8 +- .../src/project_navigation/breadcrumbs.tsx | 63 ++++++++++-- .../project_navigation/home_breadcrumbs.tsx | 25 ----- .../project_navigation_service.test.ts | 99 ++++++++++++++++--- .../project_navigation_service.ts | 29 ++++-- .../core-chrome-browser-internal/src/types.ts | 6 ++ .../src/ui/project/breadcrumbs.tsx | 47 +++++++++ .../src/ui/project/header.test.tsx | 14 --- .../src/ui/project/header.tsx | 31 +----- .../src/chrome_service.mock.ts | 1 + x-pack/plugins/serverless/public/plugin.tsx | 3 + .../page_objects/svl_common_navigation.ts | 3 - .../test_suites/observability/navigation.ts | 2 +- .../test_suites/search/navigation.ts | 2 +- .../test_suites/security/ftr/navigation.ts | 2 +- 15 files changed, 229 insertions(+), 106 deletions(-) delete mode 100644 packages/core/chrome/core-chrome-browser-internal/src/project_navigation/home_breadcrumbs.tsx create mode 100644 packages/core/chrome/core-chrome-browser-internal/src/ui/project/breadcrumbs.tsx diff --git a/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx b/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx index 0e99c96656d59..eaa30238cde89 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx +++ b/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx @@ -295,6 +295,11 @@ export class ChromeService { projectNavigation.setProjectName(projectName); }; + const setProjectUrl = (projectUrl: string) => { + validateChromeStyle(); + projectNavigation.setProjectUrl(projectUrl); + }; + const isIE = () => { const ua = window.navigator.userAgent; const msie = ua.indexOf('MSIE '); // IE 10 or older @@ -387,8 +392,6 @@ export class ChromeService { loadingCount$={http.getLoadingCount$()} headerBanner$={headerBanner$.pipe(takeUntil(this.stop$))} homeHref$={projectNavigation.getProjectHome$()} - projectsUrl$={projectNavigation.getProjectsUrl$()} - projectName$={projectNavigation.getProjectName$()} docLinks={docLinks} kibanaVersion={injectedMetadata.getKibanaVersion()} prependBasePath={http.basePath.prepend} @@ -521,6 +524,7 @@ export class ChromeService { project: { setHome: setProjectHome, setProjectsUrl, + setProjectUrl, setProjectName, setNavigation: setProjectNavigation, setSideNavComponent: setProjectSideNavComponent, diff --git a/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/breadcrumbs.tsx b/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/breadcrumbs.tsx index 1f0057e9670de..8bd690fba8a7e 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/breadcrumbs.tsx +++ b/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/breadcrumbs.tsx @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { EuiContextMenuPanel, EuiContextMenuItem } from '@elastic/eui'; import { AppDeepLinkId, ChromeProjectBreadcrumb, @@ -13,15 +14,21 @@ import { ChromeSetProjectBreadcrumbsParams, ChromeBreadcrumb, } from '@kbn/core-chrome-browser'; -import { createHomeBreadcrumb } from './home_breadcrumbs'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import React from 'react'; export function buildBreadcrumbs({ - homeHref, + projectsUrl, + projectName, + projectUrl, projectBreadcrumbs, activeNodes, chromeBreadcrumbs, }: { - homeHref: string; + projectsUrl?: string; + projectName?: string; + projectUrl?: string; projectBreadcrumbs: { breadcrumbs: ChromeProjectBreadcrumb[]; params: ChromeSetProjectBreadcrumbsParams; @@ -29,12 +36,10 @@ export function buildBreadcrumbs({ chromeBreadcrumbs: ChromeBreadcrumb[]; activeNodes: ChromeProjectNavigationNode[][]; }): ChromeProjectBreadcrumb[] { - const homeBreadcrumb = createHomeBreadcrumb({ - homeHref, - }); + const rootCrumb = buildRootCrumb({ projectsUrl, projectName, projectUrl }); if (projectBreadcrumbs.params.absolute) { - return [homeBreadcrumb, ...projectBreadcrumbs.breadcrumbs]; + return [rootCrumb, ...projectBreadcrumbs.breadcrumbs]; } // breadcrumbs take the first active path @@ -52,7 +57,7 @@ export function buildBreadcrumbs({ // if there are project breadcrumbs set, use them if (projectBreadcrumbs.breadcrumbs.length !== 0) { - return [homeBreadcrumb, ...navBreadcrumbs, ...projectBreadcrumbs.breadcrumbs]; + return [rootCrumb, ...navBreadcrumbs, ...projectBreadcrumbs.breadcrumbs]; } // otherwise try to merge legacy breadcrumbs with navigational project breadcrumbs using deeplinkid @@ -70,12 +75,50 @@ export function buildBreadcrumbs({ } if (chromeBreadcrumbStartIndex === -1) { - return [homeBreadcrumb, ...navBreadcrumbs]; + return [rootCrumb, ...navBreadcrumbs]; } else { return [ - homeBreadcrumb, + rootCrumb, ...navBreadcrumbs.slice(0, navBreadcrumbEndIndex), ...chromeBreadcrumbs.slice(chromeBreadcrumbStartIndex), ]; } } + +function buildRootCrumb({ + projectsUrl, + projectName, + projectUrl, +}: { + projectsUrl?: string; + projectName?: string; + projectUrl?: string; +}): ChromeProjectBreadcrumb { + return { + text: + projectName ?? + i18n.translate('core.ui.primaryNav.cloud.projectLabel', { + defaultMessage: 'Project', + }), + popoverContent: ( + + + , + + + , + ]} + /> + ), + popoverProps: { panelPaddingSize: 'none' }, + }; +} diff --git a/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/home_breadcrumbs.tsx b/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/home_breadcrumbs.tsx deleted file mode 100644 index 62bfad1b78090..0000000000000 --- a/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/home_breadcrumbs.tsx +++ /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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { ChromeProjectBreadcrumb } from '@kbn/core-chrome-browser'; -import { EuiIcon } from '@elastic/eui'; -import React from 'react'; -import { i18n } from '@kbn/i18n'; - -export const createHomeBreadcrumb = ({ - homeHref, -}: { - homeHref: string; -}): ChromeProjectBreadcrumb => { - return { - text: , - title: i18n.translate('core.ui.chrome.breadcrumbs.homeLink', { defaultMessage: 'Home' }), - href: homeHref, - 'data-test-subj': 'breadcrumb-home', - }; -}; diff --git a/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/project_navigation_service.test.ts b/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/project_navigation_service.test.ts index 52e27669e5515..7e1347f8534c0 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/project_navigation_service.test.ts +++ b/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/project_navigation_service.test.ts @@ -86,12 +86,35 @@ describe('breadcrumbs', () => { expect(breadcrumbs).toMatchInlineSnapshot(` Array [ Object { - "data-test-subj": "breadcrumb-home", - "href": "/", - "text": + + , + + + , + ] + } + size="s" />, - "title": "Home", + "popoverProps": Object { + "panelPaddingSize": "none", + }, + "text": "Project", }, Object { "deepLinkId": "navItem1", @@ -125,12 +148,35 @@ describe('breadcrumbs', () => { expect(breadcrumbs).toMatchInlineSnapshot(` Array [ Object { - "data-test-subj": "breadcrumb-home", - "href": "/", - "text": + + , + + + , + ] + } + size="s" />, - "title": "Home", + "popoverProps": Object { + "panelPaddingSize": "none", + }, + "text": "Project", }, Object { "href": "/custom1", @@ -158,12 +204,35 @@ describe('breadcrumbs', () => { expect(breadcrumbs).toMatchInlineSnapshot(` Array [ Object { - "data-test-subj": "breadcrumb-home", - "href": "/", - "text": + + , + + + , + ] + } + size="s" />, - "title": "Home", + "popoverProps": Object { + "panelPaddingSize": "none", + }, + "text": "Project", }, Object { "deepLinkId": "navItem1", diff --git a/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/project_navigation_service.ts b/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/project_navigation_service.ts index 9fb81c0c9a977..38766a026cdcc 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/project_navigation_service.ts +++ b/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/project_navigation_service.ts @@ -48,6 +48,7 @@ export class ProjectNavigationService { private projectHome$ = new BehaviorSubject(undefined); private projectsUrl$ = new BehaviorSubject(undefined); private projectName$ = new BehaviorSubject(undefined); + private projectUrl$ = new BehaviorSubject(undefined); private projectNavigation$ = new BehaviorSubject(undefined); private activeNodes$ = new BehaviorSubject([]); private projectNavigationNavTreeFlattened: Record = {}; @@ -106,6 +107,9 @@ export class ProjectNavigationService { getProjectName$: () => { return this.projectName$.asObservable(); }, + setProjectUrl: (projectUrl: string) => { + this.projectUrl$.next(projectUrl); + }, setProjectNavigation: (projectNavigation: ChromeProjectNavigation) => { this.projectNavigation$.next(projectNavigation); this.projectNavigationNavTreeFlattened = flattenNav(projectNavigation.navigationTree); @@ -136,17 +140,30 @@ export class ProjectNavigationService { return combineLatest([ this.projectBreadcrumbs$, this.activeNodes$, - this.projectHome$.pipe(map((homeHref) => homeHref ?? '/')), chromeBreadcrumbs$, + this.projectsUrl$, + this.projectUrl$, + this.projectName$, ]).pipe( - map(([projectBreadcrumbs, activeNodes, homeHref, chromeBreadcrumbs]) => { - return buildBreadcrumbs({ - homeHref: this.http?.basePath.prepend?.(homeHref) ?? homeHref, + map( + ([ projectBreadcrumbs, activeNodes, chromeBreadcrumbs, - }); - }) + projectsUrl, + projectUrl, + projectName, + ]) => { + return buildBreadcrumbs({ + projectUrl, + projectName, + projectsUrl, + projectBreadcrumbs, + activeNodes, + chromeBreadcrumbs, + }); + } + ) ); }, }; diff --git a/packages/core/chrome/core-chrome-browser-internal/src/types.ts b/packages/core/chrome/core-chrome-browser-internal/src/types.ts index 009aa57ee6b3b..aefe477dd01f1 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/types.ts +++ b/packages/core/chrome/core-chrome-browser-internal/src/types.ts @@ -56,6 +56,12 @@ export interface InternalChromeStart extends ChromeStart { */ setProjectName(projectName: string): void; + /** + * Sets the project url. + * @param projectUrl + */ + setProjectUrl(projectUrl: string): void; + /** * Sets the project navigation config to be used for rendering project navigation. * It is used for default project sidenav, project breadcrumbs, tracking active deep link. diff --git a/packages/core/chrome/core-chrome-browser-internal/src/ui/project/breadcrumbs.tsx b/packages/core/chrome/core-chrome-browser-internal/src/ui/project/breadcrumbs.tsx new file mode 100644 index 0000000000000..4ce22ba727e11 --- /dev/null +++ b/packages/core/chrome/core-chrome-browser-internal/src/ui/project/breadcrumbs.tsx @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { EuiBreadcrumbs } from '@elastic/eui'; +import classNames from 'classnames'; +import React from 'react'; +import useObservable from 'react-use/lib/useObservable'; +import { Observable } from 'rxjs'; +import type { ChromeBreadcrumb } from '@kbn/core-chrome-browser'; + +interface Props { + breadcrumbs$: Observable; +} + +export function Breadcrumbs({ breadcrumbs$ }: Props) { + const breadcrumbs = useObservable(breadcrumbs$, []); + let crumbs = breadcrumbs; + + if (breadcrumbs.length === 0) { + crumbs = [{ text: 'Kibana' }]; + } + + crumbs = crumbs.map((breadcrumb, i) => { + const isLast = i === breadcrumbs.length - 1; + const { deepLinkId, ...rest } = breadcrumb; + + return { + ...rest, + href: isLast ? undefined : breadcrumb.href, + onClick: isLast ? undefined : breadcrumb.onClick, + 'data-test-subj': classNames( + 'breadcrumb', + deepLinkId && `breadcrumb-deepLinkId-${deepLinkId}`, + breadcrumb['data-test-subj'], + i === 0 && 'first', + isLast && 'last' + ), + }; + }); + + return ; +} diff --git a/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.test.tsx b/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.test.tsx index 703d1f0b5de5f..04e5773e176bd 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.test.tsx +++ b/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.test.tsx @@ -28,8 +28,6 @@ describe('Header', () => { helpSupportUrl$: Rx.of('app/help'), helpMenuLinks$: Rx.of([]), homeHref$: Rx.of('app/home'), - projectsUrl$: Rx.of('/projects/'), - projectName$: Rx.of('My Project'), kibanaVersion: '8.9', loadingCount$: Rx.of(0), navControlsLeft$: Rx.of([]), @@ -49,16 +47,4 @@ describe('Header', () => { expect(await screen.findByTestId('euiCollapsibleNavButton')).toBeVisible(); expect(await screen.findByText('Hello, world!')).toBeVisible(); }); - - it('displays the link to projects', async () => { - render( - - Hello, world! - - ); - - const projectsLink = screen.getByTestId('projectsLink'); - expect(projectsLink).toHaveAttribute('href', '/projects/'); - expect(projectsLink).toHaveTextContent('My Project'); - }); }); diff --git a/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.tsx b/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.tsx index 7ac11ecb5bc54..0892b0c363911 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.tsx +++ b/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.tsx @@ -8,7 +8,6 @@ import { EuiHeader, - EuiHeaderLink, EuiHeaderLogo, EuiHeaderSection, EuiHeaderSectionItem, @@ -36,7 +35,7 @@ import React, { useCallback } from 'react'; import useObservable from 'react-use/lib/useObservable'; import { debounceTime, Observable, of } from 'rxjs'; import { useHeaderActionMenuMounter } from '../header/header_action_menu'; -import { HeaderBreadcrumbs } from '../header/header_breadcrumbs'; +import { Breadcrumbs } from './breadcrumbs'; import { HeaderHelpMenu } from '../header/header_help_menu'; import { HeaderNavControls } from '../header/header_nav_controls'; import { HeaderTopBanner } from '../header/header_top_banner'; @@ -51,6 +50,7 @@ const getHeaderCss = ({ size }: EuiThemeComputed) => ({ min-width: 56px; /* 56 = 40 + 8 + 8 */ padding: 0 ${size.s}; cursor: pointer; + margin-left: -${size.s}; // to get equal spacing between .euiCollapsibleNavButtonWrapper, logo and breadcrumbs `, logo: css` min-width: 0; /* overrides min-width: 40px */ @@ -62,12 +62,6 @@ const getHeaderCss = ({ size }: EuiThemeComputed) => ({ top: 2px; `, }, - projectName: { - link: css` - /* TODO: make header layout more flexible? */ - max-width: 320px; - `, - }, }); type HeaderCss = ReturnType; @@ -78,11 +72,6 @@ const headerStrings = { defaultMessage: 'Go to home page', }), }, - cloud: { - linkToProjects: i18n.translate('core.ui.primaryNav.cloud.linkToProjects', { - defaultMessage: 'Projects', - }), - }, nav: { closeNavAriaLabel: i18n.translate('core.ui.primaryNav.toggleNavAriaLabel', { defaultMessage: 'Toggle primary navigation', @@ -101,8 +90,6 @@ export interface Props { helpSupportUrl$: Observable; helpMenuLinks$: Observable; homeHref$: Observable; - projectsUrl$: Observable; - projectName$: Observable; kibanaVersion: string; application: InternalApplicationStart; loadingCount$: ReturnType; @@ -177,8 +164,6 @@ export const ProjectHeader = ({ ...observables }: Props) => { const headerActionMenuMounter = useHeaderActionMenuMounter(observables.actionMenu$); - const projectsUrl = useObservable(observables.projectsUrl$); - const projectName = useObservable(observables.projectName$); const { euiTheme } = useEuiTheme(); const headerCss = getHeaderCss(euiTheme); const { logo: logoCss } = headerCss; @@ -215,19 +200,9 @@ export const ProjectHeader = ({ - - - {projectName ?? headerStrings.cloud.linkToProjects} - - - - + diff --git a/packages/core/chrome/core-chrome-browser-mocks/src/chrome_service.mock.ts b/packages/core/chrome/core-chrome-browser-mocks/src/chrome_service.mock.ts index 191edc708b64a..4f1732d940c37 100644 --- a/packages/core/chrome/core-chrome-browser-mocks/src/chrome_service.mock.ts +++ b/packages/core/chrome/core-chrome-browser-mocks/src/chrome_service.mock.ts @@ -71,6 +71,7 @@ const createStartContractMock = () => { project: { setHome: jest.fn(), setProjectsUrl: jest.fn(), + setProjectUrl: jest.fn(), setProjectName: jest.fn(), setNavigation: jest.fn(), setSideNavComponent: jest.fn(), diff --git a/x-pack/plugins/serverless/public/plugin.tsx b/x-pack/plugins/serverless/public/plugin.tsx index 6333d9a5f58a0..8a60db4c03760 100644 --- a/x-pack/plugins/serverless/public/plugin.tsx +++ b/x-pack/plugins/serverless/public/plugin.tsx @@ -69,6 +69,9 @@ export class ServerlessPlugin if (dependencies.cloud.serverless.projectName) { project.setProjectName(dependencies.cloud.serverless.projectName); } + if (dependencies.cloud.deploymentUrl) { + project.setProjectUrl(dependencies.cloud.deploymentUrl); + } return { setSideNavComponent: (sideNavigationComponent) => diff --git a/x-pack/test_serverless/functional/page_objects/svl_common_navigation.ts b/x-pack/test_serverless/functional/page_objects/svl_common_navigation.ts index 41101d5a653d4..5c497b47a6e15 100644 --- a/x-pack/test_serverless/functional/page_objects/svl_common_navigation.ts +++ b/x-pack/test_serverless/functional/page_objects/svl_common_navigation.ts @@ -153,9 +153,6 @@ export function SvlCommonNavigationProvider(ctx: FtrProviderContext) { async expectExists() { await testSubjects.existOrFail('breadcrumbs'); }, - async clickHome() { - await testSubjects.click('~breadcrumb-home'); - }, async expectBreadcrumbExists(by: { deepLinkId: AppDeepLinkId } | { text: string }) { log.debug( 'ServerlessCommonNavigation.breadcrumbs.expectBreadcrumbExists', diff --git a/x-pack/test_serverless/functional/test_suites/observability/navigation.ts b/x-pack/test_serverless/functional/test_suites/observability/navigation.ts index 4b94df8c51bcc..a295d93e009bc 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/navigation.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/navigation.ts @@ -76,7 +76,7 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({ deepLinkId: 'management' }); // navigate back to serverless oblt overview - await svlCommonNavigation.breadcrumbs.clickHome(); + await svlCommonNavigation.clickLogo(); await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({ deepLinkId: 'observabilityOnboarding', }); diff --git a/x-pack/test_serverless/functional/test_suites/search/navigation.ts b/x-pack/test_serverless/functional/test_suites/search/navigation.ts index 26c2c7cc47ce8..b4244a96b82af 100644 --- a/x-pack/test_serverless/functional/test_suites/search/navigation.ts +++ b/x-pack/test_serverless/functional/test_suites/search/navigation.ts @@ -61,7 +61,7 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { await testSubjects.existOrFail(`indicesTab`); // navigate back to serverless search overview - await svlCommonNavigation.breadcrumbs.clickHome(); + await svlCommonNavigation.clickLogo(); await svlCommonNavigation.sidenav.expectLinkActive({ deepLinkId: 'serverlessElasticsearch', }); diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/navigation.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/navigation.ts index ef33a898de4fb..e3c8de72570b5 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/navigation.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/navigation.ts @@ -40,7 +40,7 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { deepLinkId: 'securitySolutionUI:alerts' as AppDeepLinkId, }); await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Alerts' }); - await svlCommonNavigation.breadcrumbs.clickHome(); + await svlCommonNavigation.clickLogo(); await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Get started' }); }); From ce6cb902788861a1bc50efd878198efafb6a7a3f Mon Sep 17 00:00:00 2001 From: Kevin Delemme Date: Tue, 31 Oct 2023 11:15:03 -0400 Subject: [PATCH 06/21] fix(slo): remove groupBy on update (#170233) --- .../apm_availability_indicator_type_form.tsx | 2 ++ .../apm_latency/apm_latency_indicator_type_form.tsx | 2 ++ .../slo_edit/components/common/index_field_selector.tsx | 6 ++++-- .../custom_kql/custom_kql_indicator_type_form.tsx | 2 ++ .../components/custom_metric/custom_metric_type_form.tsx | 2 ++ .../components/histogram/histogram_indicator_type_form.tsx | 2 ++ .../timeslice_metric/timeslice_metric_indicator.tsx | 2 ++ 7 files changed, 16 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/apm_availability/apm_availability_indicator_type_form.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/apm_availability/apm_availability_indicator_type_form.tsx index d22509da43599..b58ee97634387 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/components/apm_availability/apm_availability_indicator_type_form.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/components/apm_availability/apm_availability_indicator_type_form.tsx @@ -7,6 +7,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiIconTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { ALL_VALUE } from '@kbn/slo-schema/src/schema/common'; import React, { useEffect } from 'react'; import { useFormContext } from 'react-hook-form'; import { useFetchApmIndex } from '../../../../hooks/slo/use_fetch_apm_indices'; @@ -136,6 +137,7 @@ export function ApmAvailabilityIndicatorTypeForm() { {i18n.translate('xpack.observability.slo.sloEdit.groupBy.label', { diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/apm_latency/apm_latency_indicator_type_form.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/apm_latency/apm_latency_indicator_type_form.tsx index af357ea0c18d1..32b5729762abd 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/components/apm_latency/apm_latency_indicator_type_form.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/components/apm_latency/apm_latency_indicator_type_form.tsx @@ -7,6 +7,7 @@ import { EuiFieldNumber, EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiIconTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { ALL_VALUE } from '@kbn/slo-schema/src/schema/common'; import React, { useEffect } from 'react'; import { Controller, useFormContext } from 'react-hook-form'; import { useFetchApmIndex } from '../../../../hooks/slo/use_fetch_apm_indices'; @@ -179,6 +180,7 @@ export function ApmLatencyIndicatorTypeForm() { {i18n.translate('xpack.observability.slo.sloEdit.groupBy.label', { diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/common/index_field_selector.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/common/index_field_selector.tsx index ef9121e871bbd..936d39dfa9b9a 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/components/common/index_field_selector.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/components/common/index_field_selector.tsx @@ -20,6 +20,7 @@ interface Props { isDisabled: boolean; isLoading: boolean; isRequired?: boolean; + defaultValue?: string; } export function IndexFieldSelector({ indexFields, @@ -29,6 +30,7 @@ export function IndexFieldSelector({ isDisabled, isLoading, isRequired = false, + defaultValue = '', }: Props) { const { control, getFieldState } = useFormContext(); const [options, setOptions] = useState(createOptionsFromFields(indexFields)); @@ -41,7 +43,7 @@ export function IndexFieldSelector({ { diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/custom_kql/custom_kql_indicator_type_form.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/custom_kql/custom_kql_indicator_type_form.tsx index 8555750fd6ff1..6c5d4dce730bf 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/components/custom_kql/custom_kql_indicator_type_form.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/components/custom_kql/custom_kql_indicator_type_form.tsx @@ -7,6 +7,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiIconTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { ALL_VALUE } from '@kbn/slo-schema/src/schema/common'; import React from 'react'; import { useFormContext } from 'react-hook-form'; import { useFetchIndexPatternFields } from '../../../../hooks/slo/use_fetch_index_pattern_fields'; @@ -138,6 +139,7 @@ export function CustomKqlIndicatorTypeForm() { {i18n.translate('xpack.observability.slo.sloEdit.groupBy.label', { diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/custom_metric/custom_metric_type_form.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/custom_metric/custom_metric_type_form.tsx index 45870dcd68bd0..d69619d1121ef 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/components/custom_metric/custom_metric_type_form.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/components/custom_metric/custom_metric_type_form.tsx @@ -15,6 +15,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; +import { ALL_VALUE } from '@kbn/slo-schema/src/schema/common'; import React from 'react'; import { useFormContext } from 'react-hook-form'; import { useFetchIndexPatternFields } from '../../../../hooks/slo/use_fetch_index_pattern_fields'; @@ -153,6 +154,7 @@ export function CustomMetricIndicatorTypeForm() { {i18n.translate('xpack.observability.slo.sloEdit.groupBy.label', { diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/histogram/histogram_indicator_type_form.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/histogram/histogram_indicator_type_form.tsx index eb18e0a363628..50a84c121d73c 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/components/histogram/histogram_indicator_type_form.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/components/histogram/histogram_indicator_type_form.tsx @@ -15,6 +15,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; +import { ALL_VALUE } from '@kbn/slo-schema/src/schema/common'; import React from 'react'; import { useFormContext } from 'react-hook-form'; import { useFetchIndexPatternFields } from '../../../../hooks/slo/use_fetch_index_pattern_fields'; @@ -140,6 +141,7 @@ export function HistogramIndicatorTypeForm() { {i18n.translate('xpack.observability.slo.sloEdit.groupBy.label', { diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/timeslice_metric/timeslice_metric_indicator.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/timeslice_metric/timeslice_metric_indicator.tsx index 5d455a601e3d7..b2d788bf28654 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/components/timeslice_metric/timeslice_metric_indicator.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/components/timeslice_metric/timeslice_metric_indicator.tsx @@ -18,6 +18,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import React from 'react'; import { useFormContext } from 'react-hook-form'; +import { ALL_VALUE } from '@kbn/slo-schema/src/schema/common'; import { useFetchIndexPatternFields } from '../../../../hooks/slo/use_fetch_index_pattern_fields'; import { CreateSLOForm } from '../../types'; import { DataPreviewChart } from '../common/data_preview_chart'; @@ -130,6 +131,7 @@ export function TimesliceMetricIndicatorTypeForm() { {i18n.translate('xpack.observability.slo.sloEdit.groupBy.label', { From 7ad80a0884801e40ca8fb2209ff6f5388084895d Mon Sep 17 00:00:00 2001 From: Jeramy Soucy Date: Tue, 31 Oct 2023 11:30:13 -0400 Subject: [PATCH 07/21] =?UTF-8?q?Upgrades=20browserify-sign@4.0.4=E2=86=92?= =?UTF-8?q?4.2.2=20(#170137)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Upgrades browserify-sign from v4.0.4 to v4.2.2 --- yarn.lock | 85 +++++++++++++++++++++++++++---------------------------- 1 file changed, 42 insertions(+), 43 deletions(-) diff --git a/yarn.lock b/yarn.lock index 2d38221a2d11a..d6bbe16792417 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11241,16 +11241,7 @@ asap@^2.0.0, asap@~2.0.3: resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= -asn1.js@^4.0.0: - version "4.10.1" - resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" - integrity sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw== - dependencies: - bn.js "^4.0.0" - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - -asn1.js@^5.0.0: +asn1.js@^5.0.0, asn1.js@^5.2.0: version "5.4.1" resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07" integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA== @@ -11924,11 +11915,16 @@ blurhash@^2.0.1: resolved "https://registry.yarnpkg.com/blurhash/-/blurhash-2.0.1.tgz#7f134ad0cf3cbb6bcceb81ea51b82e1423009dca" integrity sha512-qAJW99ZIEVJqLKvR6EUtMavaalYiFgfHNvwO6eiqHE7RTBZYGQLPJvzs4WlnqSQPxZgqSPH/n4kRJIHzb/Y7dg== -bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.11.9: +bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9: version "4.11.9" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828" integrity sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw== +bn.js@^5.0.0, bn.js@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" + integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== + body-parser@1.19.2: version "1.19.2" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.2.tgz#4714ccd9c157d44797b8b5607d72c0b89952f26e" @@ -12117,26 +12113,28 @@ browserify-optional@^1.0.1: ast-types "^0.7.0" browser-resolve "^1.8.1" -browserify-rsa@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524" - integrity sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ= +browserify-rsa@^4.0.0, browserify-rsa@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.1.0.tgz#b2fd06b5b75ae297f7ce2dc651f918f5be158c8d" + integrity sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog== dependencies: - bn.js "^4.1.0" + bn.js "^5.0.0" randombytes "^2.0.1" browserify-sign@^4.0.0: - version "4.0.4" - resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.0.4.tgz#aa4eb68e5d7b658baa6bf6a57e630cbd7a93d298" - integrity sha1-qk62jl17ZYuqa/alfmMMvXqT0pg= - dependencies: - bn.js "^4.1.1" - browserify-rsa "^4.0.0" - create-hash "^1.1.0" - create-hmac "^1.1.2" - elliptic "^6.0.0" - inherits "^2.0.1" - parse-asn1 "^5.0.0" + version "4.2.2" + resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.2.tgz#e78d4b69816d6e3dd1c747e64e9947f9ad79bc7e" + integrity sha512-1rudGyeYY42Dk6texmv7c4VcQ0EsvVbLwZkA+AQB7SxvXxmcD93jcHie8bzecJ+ChDlmAm2Qyu0+Ccg5uhZXCg== + dependencies: + bn.js "^5.2.1" + browserify-rsa "^4.1.0" + create-hash "^1.2.0" + create-hmac "^1.1.7" + elliptic "^6.5.4" + inherits "^2.0.4" + parse-asn1 "^5.1.6" + readable-stream "^3.6.2" + safe-buffer "^5.2.1" browserify-zlib@^0.2.0: version "0.2.0" @@ -13498,20 +13496,21 @@ create-ecdh@^4.0.0: bn.js "^4.1.0" elliptic "^6.0.0" -create-hash@^1.1.0, create-hash@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.1.3.tgz#606042ac8b9262750f483caddab0f5819172d8fd" - integrity sha1-YGBCrIuSYnUPSDyt2rD1gZFy2P0= +create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" + integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== dependencies: cipher-base "^1.0.1" inherits "^2.0.1" - ripemd160 "^2.0.0" + md5.js "^1.3.4" + ripemd160 "^2.0.1" sha.js "^2.4.0" -create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: - version "1.1.6" - resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.6.tgz#acb9e221a4e17bdb076e90657c42b93e3726cf06" - integrity sha1-rLniIaThe9sHbpBlfEK5PjcmzwY= +create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" + integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== dependencies: cipher-base "^1.0.3" create-hash "^1.1.0" @@ -15225,7 +15224,7 @@ element-resize-detector@^1.2.2: dependencies: batch-processor "1.0.0" -elliptic@^6.0.0: +elliptic@^6.0.0, elliptic@^6.5.4: version "6.5.4" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== @@ -23653,16 +23652,16 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" -parse-asn1@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.0.tgz#37c4f9b7ed3ab65c74817b5f2480937fbf97c712" - integrity sha1-N8T5t+06tlx0gXtfJICTf7+XxxI= +parse-asn1@^5.0.0, parse-asn1@^5.1.6: + version "5.1.6" + resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.6.tgz#385080a3ec13cb62a62d39409cb3e88844cdaed4" + integrity sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw== dependencies: - asn1.js "^4.0.0" + asn1.js "^5.2.0" browserify-aes "^1.0.0" - create-hash "^1.1.0" evp_bytestokey "^1.0.0" pbkdf2 "^3.0.3" + safe-buffer "^5.1.1" parse-entities@^2.0.0: version "2.0.0" From 40e524bd0973734e8de424d9f975158ea7ddd275 Mon Sep 17 00:00:00 2001 From: Ignacio Rivas Date: Tue, 31 Oct 2023 17:09:53 +0100 Subject: [PATCH 08/21] [Index Management] Support managed_by field and fix never delete data logic (#169064) --- .../helpers/test_subjects.ts | 5 + .../home/data_streams_tab.helpers.ts | 3 + .../home/data_streams_tab.test.ts | 157 +++++++++++++++--- .../common/lib/data_stream_serialization.ts | 21 ++- .../common/types/data_streams.ts | 21 ++- .../public/application/lib/data_streams.tsx | 39 +++++ .../data_stream_detail_panel.tsx | 133 ++++++++++++--- .../data_stream_detail_panel/index.ts | 2 +- .../data_stream_table/data_stream_table.tsx | 36 +++- .../edit_data_retention_modal.tsx | 131 ++++++++++++--- .../public/application/services/api.ts | 21 ++- .../api/data_streams/register_get_route.ts | 2 + .../api/data_streams/register_put_route.ts | 17 +- .../index_management/data_streams.ts | 22 +++ .../data_streams_tab/data_streams_tab.ts | 18 ++ .../index_management/data_streams.ts | 18 ++ 16 files changed, 548 insertions(+), 98 deletions(-) diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts index 0f4ce20f0c156..52ac8313ef4d0 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts @@ -102,6 +102,11 @@ export type TestSubjects = | 'filter-option-h' | 'infiniteRetentionPeriod.input' | 'saveButton' + | 'dsIsFullyManagedByILM' + | 'someIndicesAreManagedByILMCallout' + | 'viewIlmPolicyLink' + | 'viewAllIndicesLink' + | 'dataRetentionEnabledField.input' | 'enrichPoliciesInsuficientPrivileges' | 'dataRetentionDetail' | 'createIndexSaveButton'; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts index 3a6add88c2840..631b3e838f8ff 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts @@ -264,9 +264,12 @@ export const createDataStreamPayload = (dataStream: Partial): DataSt { name: 'indexName', uuid: 'indexId', + preferILM: false, + managedBy: 'Data stream lifecycle', }, ], generation: 1, + nextGenerationManagedBy: 'Data stream lifecycle', health: 'green', indexTemplateName: 'indexTemplate', storageSize: '1b', diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts index dc4c228322bbd..e78ad9d167f87 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts @@ -446,6 +446,32 @@ describe('Data Streams tab', () => { ); }); + test('can disable lifecycle', async () => { + const { + actions: { clickNameAt, clickEditDataRetentionButton }, + } = testBed; + + await clickNameAt(0); + + clickEditDataRetentionButton(); + + httpRequestsMockHelpers.setEditDataRetentionResponse('dataStream1', { + success: true, + }); + + testBed.form.toggleEuiSwitch('dataRetentionEnabledField.input'); + + await act(async () => { + testBed.find('saveButton').simulate('click'); + }); + testBed.component.update(); + + expect(httpSetup.put).toHaveBeenLastCalledWith( + `${API_BASE_PATH}/data_streams/dataStream1/data_retention`, + expect.objectContaining({ body: JSON.stringify({ enabled: false }) }) + ); + }); + test('allows to set infinite retention period', async () => { const { actions: { clickNameAt, clickEditDataRetentionButton }, @@ -499,6 +525,110 @@ describe('Data Streams tab', () => { expect(findDetailPanelDataRetentionDetail().exists()).toBeTruthy(); }); }); + + describe('shows all possible states according to who manages the data stream', () => { + const ds1 = createDataStreamPayload({ + name: 'dataStream1', + nextGenerationManagedBy: 'Index Lifecycle Management', + lifecycle: undefined, + indices: [ + { + managedBy: 'Index Lifecycle Management', + name: 'indexName', + uuid: 'indexId', + preferILM: true, + }, + ], + }); + + const ds2 = createDataStreamPayload({ + name: 'dataStream2', + nextGenerationManagedBy: 'Data stream lifecycle', + lifecycle: { + enabled: true, + data_retention: '7d', + }, + indices: [ + { + managedBy: 'Index Lifecycle Management', + name: 'indexName1', + uuid: 'indexId1', + preferILM: true, + }, + { + managedBy: 'Index Lifecycle Management', + name: 'indexName2', + uuid: 'indexId2', + preferILM: true, + }, + { + managedBy: 'Index Lifecycle Management', + name: 'indexName3', + uuid: 'indexId3', + preferILM: true, + }, + { + managedBy: 'Index Lifecycle Management', + name: 'indexName4', + uuid: 'indexId4', + preferILM: true, + }, + ], + }); + + beforeEach(async () => { + const { setLoadDataStreamsResponse } = httpRequestsMockHelpers; + + setLoadDataStreamsResponse([ds1, ds2]); + + testBed = await setup(httpSetup, { + history: createMemoryHistory(), + url: urlServiceMock, + }); + + await act(async () => { + testBed.actions.goToDataStreamsList(); + }); + testBed.component.update(); + }); + + test('when fully managed by ILM, user cannot edit data retention', async () => { + const { setLoadDataStreamResponse } = httpRequestsMockHelpers; + + setLoadDataStreamResponse(ds1.name, ds1); + + const { actions, find, exists } = testBed; + + await actions.clickNameAt(0); + expect(find('dataRetentionDetail').text()).toBe('Disabled'); + + // There should be a warning that the data stream is fully managed by ILM + expect(exists('dsIsFullyManagedByILM')).toBe(true); + + // Edit data retention button should not be visible + testBed.find('manageDataStreamButton').simulate('click'); + expect(exists('editDataRetentionButton')).toBe(false); + }); + + test('when partially managed by dsl but has backing indices managed by ILM should show a warning', async () => { + const { setLoadDataStreamResponse } = httpRequestsMockHelpers; + + setLoadDataStreamResponse(ds2.name, ds2); + + const { actions, find, exists } = testBed; + + await actions.clickNameAt(1); + expect(find('dataRetentionDetail').text()).toBe('7d'); + + actions.clickEditDataRetentionButton(); + + // There should be a warning that the data stream is managed by DSL + // but the backing indices that are managed by ILM wont be affected. + expect(exists('someIndicesAreManagedByILMCallout')).toBe(true); + expect(exists('viewIlmPolicyLink')).toBe(true); + expect(exists('viewAllIndicesLink')).toBe(true); + }); + }); }); describe('when there are special characters', () => { @@ -569,33 +699,6 @@ describe('Data Streams tab', () => { expect(findDetailPanelIlmPolicyLink().prop('href')).toBe('/test/my_ilm_policy'); }); - test('with ILM updating data retention should be disabled', async () => { - const { setLoadDataStreamsResponse, setLoadDataStreamResponse } = httpRequestsMockHelpers; - - const dataStreamForDetailPanel = createDataStreamPayload({ - name: 'dataStream1', - ilmPolicyName: 'my_ilm_policy', - }); - - setLoadDataStreamsResponse([dataStreamForDetailPanel]); - setLoadDataStreamResponse(dataStreamForDetailPanel.name, dataStreamForDetailPanel); - - testBed = await setup(httpSetup, { - history: createMemoryHistory(), - url: urlServiceMock, - }); - await act(async () => { - testBed.actions.goToDataStreamsList(); - }); - testBed.component.update(); - - const { actions } = testBed; - await actions.clickNameAt(0); - - testBed.find('manageDataStreamButton').simulate('click'); - expect(testBed.find('editDataRetentionButton').exists()).toBeFalsy(); - }); - test('with an ILM url locator and no ILM policy', async () => { const { setLoadDataStreamsResponse, setLoadDataStreamResponse } = httpRequestsMockHelpers; diff --git a/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts b/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts index b9743727a1756..d7bd1d9e92f95 100644 --- a/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts +++ b/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts @@ -23,16 +23,28 @@ export function deserializeDataStream(dataStreamFromEs: EnhancedDataStreamFromEs privileges, hidden, lifecycle, + next_generation_managed_by: nextGenerationManagedBy, } = dataStreamFromEs; return { name, timeStampField, indices: indices.map( - // eslint-disable-next-line @typescript-eslint/naming-convention - ({ index_name, index_uuid }: { index_name: string; index_uuid: string }) => ({ - name: index_name, - uuid: index_uuid, + ({ + index_name: indexName, + index_uuid: indexUuid, + prefer_ilm: preferILM, + managed_by: managedBy, + }: { + index_name: string; + index_uuid: string; + prefer_ilm: boolean; + managed_by: string; + }) => ({ + name: indexName, + uuid: indexUuid, + preferILM, + managedBy, }) ), generation, @@ -46,6 +58,7 @@ export function deserializeDataStream(dataStreamFromEs: EnhancedDataStreamFromEs privileges, hidden, lifecycle, + nextGenerationManagedBy, }; } diff --git a/x-pack/plugins/index_management/common/types/data_streams.ts b/x-pack/plugins/index_management/common/types/data_streams.ts index f0bd12d96fde5..80a4be29ee924 100644 --- a/x-pack/plugins/index_management/common/types/data_streams.ts +++ b/x-pack/plugins/index_management/common/types/data_streams.ts @@ -28,23 +28,27 @@ type Privileges = PrivilegesFromEs; export type HealthFromEs = 'GREEN' | 'YELLOW' | 'RED'; +export interface DataStreamIndexFromEs { + index_name: string; + index_uuid: string; + prefer_ilm: boolean; + managed_by: string; +} + +export type Health = 'green' | 'yellow' | 'red'; + export interface EnhancedDataStreamFromEs extends IndicesDataStream { store_size?: IndicesDataStreamsStatsDataStreamsStatsItem['store_size']; store_size_bytes?: IndicesDataStreamsStatsDataStreamsStatsItem['store_size_bytes']; maximum_timestamp?: IndicesDataStreamsStatsDataStreamsStatsItem['maximum_timestamp']; + indices: DataStreamIndexFromEs[]; + next_generation_managed_by: string; privileges: { delete_index: boolean; manage_data_stream_lifecycle: boolean; }; } -export interface DataStreamIndexFromEs { - index_name: string; - index_uuid: string; -} - -export type Health = 'green' | 'yellow' | 'red'; - export interface DataStream { name: string; timeStampField: TimestampField; @@ -59,6 +63,7 @@ export interface DataStream { _meta?: Metadata; privileges: Privileges; hidden: boolean; + nextGenerationManagedBy: string; lifecycle?: IndicesDataLifecycleWithRollover & { enabled?: boolean; }; @@ -67,4 +72,6 @@ export interface DataStream { export interface DataStreamIndex { name: string; uuid: string; + preferILM: boolean; + managedBy: string; } diff --git a/x-pack/plugins/index_management/public/application/lib/data_streams.tsx b/x-pack/plugins/index_management/public/application/lib/data_streams.tsx index ad068dde91a22..90b6001d3d949 100644 --- a/x-pack/plugins/index_management/public/application/lib/data_streams.tsx +++ b/x-pack/plugins/index_management/public/application/lib/data_streams.tsx @@ -76,3 +76,42 @@ export const getLifecycleValue = ( return lifecycle?.data_retention; }; + +export const isDataStreamFullyManagedByILM = (dataStream?: DataStream | null) => { + return ( + dataStream?.nextGenerationManagedBy?.toLowerCase() === 'index lifecycle management' && + dataStream?.indices?.every( + (index) => index.managedBy.toLowerCase() === 'index lifecycle management' + ) + ); +}; + +export const isDataStreamFullyManagedByDSL = (dataStream?: DataStream | null) => { + return ( + dataStream?.nextGenerationManagedBy?.toLowerCase() === 'data stream lifecycle' && + dataStream?.indices?.every((index) => index.managedBy.toLowerCase() === 'data stream lifecycle') + ); +}; + +export const isDSLWithILMIndices = (dataStream?: DataStream | null) => { + if (dataStream?.nextGenerationManagedBy?.toLowerCase() === 'data stream lifecycle') { + const ilmIndices = dataStream?.indices?.filter( + (index) => index.managedBy.toLowerCase() === 'index lifecycle management' + ); + const dslIndices = dataStream?.indices?.filter( + (index) => index.managedBy.toLowerCase() === 'data stream lifecycle' + ); + + // When there arent any ILM indices, there's no need to show anything. + if (!ilmIndices?.length) { + return; + } + + return { + ilmIndices, + dslIndices, + }; + } + + return; +}; diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx index 96449e6de5238..8784c7f7257b3 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx @@ -22,11 +22,15 @@ import { EuiFlyoutHeader, EuiIconTip, EuiLink, + EuiTextColor, EuiTitle, EuiIcon, + EuiToolTip, EuiPopover, EuiContextMenu, EuiContextMenuPanelDescriptor, + EuiCallOut, + EuiSpacer, } from '@elastic/eui'; import { DiscoverLink } from '../../../../lib/discover_link'; @@ -39,6 +43,10 @@ import { EditDataRetentionModal } from '../edit_data_retention_modal'; import { humanizeTimeStamp } from '../humanize_time_stamp'; import { getIndexListUri, getTemplateDetailsLink } from '../../../../services/routing'; import { ILM_PAGES_POLICY_EDIT } from '../../../../constants'; +import { + isDataStreamFullyManagedByILM, + isDataStreamFullyManagedByDSL, +} from '../../../../lib/data_streams'; import { useAppContext } from '../../../../app_context'; import { DataStreamsBadges } from '../data_stream_badges'; import { useIlmLocator } from '../../../../services/use_ilm_locator'; @@ -99,6 +107,16 @@ interface Props { onClose: (shouldReload?: boolean) => void; } +export const ConditionalWrap = ({ + condition, + wrap, + children, +}: { + condition: boolean; + wrap: (wrappedChildren: React.ReactNode) => JSX.Element; + children: JSX.Element; +}): JSX.Element => (condition ? wrap(children) : children); + export const DataStreamDetailPanel: React.FunctionComponent = ({ dataStreamName, onClose, @@ -111,6 +129,7 @@ export const DataStreamDetailPanel: React.FunctionComponent = ({ const ilmPolicyLink = useIlmLocator(ILM_PAGES_POLICY_EDIT, dataStream?.ilmPolicyName); const { history } = useAppContext(); + let indicesLink; let content; @@ -154,14 +173,38 @@ export const DataStreamDetailPanel: React.FunctionComponent = ({ defaultMessage: 'Index lifecycle policy', }), toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyToolTip', { - defaultMessage: `The index lifecycle policy that manages the data in the data stream.`, + defaultMessage: `The index lifecycle policy that manages the data in the data stream. `, }), - content: ilmPolicyLink ? ( - - {ilmPolicyName} - + content: isDataStreamFullyManagedByDSL(dataStream) ? ( + + <> + {ilmPolicyLink ? ( + + {ilmPolicyName} + + ) : ( + ilmPolicyName + )} + + ) : ( - ilmPolicyName + <> + {ilmPolicyLink ? ( + + {ilmPolicyName} + + ) : ( + ilmPolicyName + )} + ), dataTestSubj: 'ilmPolicyDetail', }); @@ -170,6 +213,14 @@ export const DataStreamDetailPanel: React.FunctionComponent = ({ return managementDetails; }; + indicesLink = ( + + {indices.length} + + ); + const defaultDetails = [ { name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.healthTitle', { @@ -216,16 +267,7 @@ export const DataStreamDetailPanel: React.FunctionComponent = ({ toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.indicesToolTip', { defaultMessage: `The data stream's current backing indices.`, }), - content: ( - - {indices.length} - - ), + content: indicesLink, dataTestSubj: 'indicesDetail', }, { @@ -271,9 +313,16 @@ export const DataStreamDetailPanel: React.FunctionComponent = ({ defaultMessage: 'Data retention', }), toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.dataRetentionToolTip', { - defaultMessage: 'The amount of time to retain the data in the data stream.', + defaultMessage: `Data is kept at least this long before being automatically deleted. The data retention value only applies to the data managed directly by the data stream. If some data is subject to an index lifecycle management policy, then the data retention value set for the data stream doesn't apply to that data.`, }), - content: getLifecycleValue(lifecycle), + content: ( + {children}} + > + <>{getLifecycleValue(lifecycle)} + + ), dataTestSubj: 'dataRetentionDetail', }, ]; @@ -281,7 +330,43 @@ export const DataStreamDetailPanel: React.FunctionComponent = ({ const managementDetails = getManagementDetails(); const details = [...defaultDetails, ...managementDetails]; - content = ; + content = ( + <> + {isDataStreamFullyManagedByILM(dataStream) && ( + <> + +

+ + + + ), + }} + /> +

+
+ + + + )} + + + + ); } const closePopover = () => { @@ -310,7 +395,8 @@ export const DataStreamDetailPanel: React.FunctionComponent = ({ defaultMessage: 'Data stream options', }), items: [ - ...(!dataStream?.ilmPolicyName && dataStream?.privileges?.manage_data_stream_lifecycle + ...(!isDataStreamFullyManagedByILM(dataStream) && + dataStream?.privileges?.manage_data_stream_lifecycle ? [ { key: 'editDataRetention', @@ -364,7 +450,7 @@ export const DataStreamDetailPanel: React.FunctionComponent = ({ /> )} - {isEditingDataRetention && ( + {isEditingDataRetention && dataStream && ( { if (data && data?.hasUpdatedDataRetention) { @@ -373,8 +459,9 @@ export const DataStreamDetailPanel: React.FunctionComponent = ({ setIsEditingDataRetention(false); } }} - dataStreamName={dataStreamName} - lifecycle={dataStream?.lifecycle} + ilmPolicyName={dataStream?.ilmPolicyName} + ilmPolicyLink={ilmPolicyLink} + dataStream={dataStream} /> )} diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/index.ts b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/index.ts index dbff310fe947c..d6c33961ab03c 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/index.ts +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { DataStreamDetailPanel } from './data_stream_detail_panel'; +export { DataStreamDetailPanel, ConditionalWrap } from './data_stream_detail_panel'; diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx index 1bf8886ccbf73..97293c9a5f13b 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useState, Fragment } from 'react'; +import React, { useState, Fragment, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { @@ -15,6 +15,7 @@ import { EuiLink, EuiIcon, EuiToolTip, + EuiTextColor, } from '@elastic/eui'; import { ScopedHistory } from '@kbn/core/public'; @@ -27,6 +28,12 @@ import { DataHealth } from '../../../../components'; import { DeleteDataStreamConfirmationModal } from '../delete_data_stream_confirmation_modal'; import { humanizeTimeStamp } from '../humanize_time_stamp'; import { DataStreamsBadges } from '../data_stream_badges'; +import { ConditionalWrap } from '../data_stream_detail_panel'; +import { isDataStreamFullyManagedByILM } from '../../../../lib/data_streams'; + +interface TableDataStream extends DataStream { + isDataStreamFullyManagedByILM: boolean; +} interface Props { dataStreams?: DataStream[]; @@ -49,7 +56,14 @@ export const DataStreamTable: React.FunctionComponent = ({ const [dataStreamsToDelete, setDataStreamsToDelete] = useState([]); const { config } = useAppContext(); - const columns: Array> = []; + const data = useMemo(() => { + return (dataStreams || []).map((dataStream) => ({ + ...dataStream, + isDataStreamFullyManagedByILM: isDataStreamFullyManagedByILM(dataStream), + })); + }, [dataStreams]); + + const columns: Array> = []; columns.push({ field: 'name', @@ -137,8 +151,7 @@ export const DataStreamTable: React.FunctionComponent = ({ name: ( @@ -151,7 +164,14 @@ export const DataStreamTable: React.FunctionComponent = ({ ), truncateText: true, sortable: true, - render: (lifecycle: DataStream['lifecycle']) => getLifecycleValue(lifecycle, INFINITE_AS_ICON), + render: (lifecycle: DataStream['lifecycle'], dataStream) => ( + {children}} + > + <>{getLifecycleValue(lifecycle, INFINITE_AS_ICON)} + + ), }); columns.push({ @@ -235,8 +255,8 @@ export const DataStreamTable: React.FunctionComponent = ({ <> {dataStreamsToDelete && dataStreamsToDelete.length > 0 ? ( { - if (data && data.hasDeletedDataStreams) { + onClose={(res) => { + if (res && res.hasDeletedDataStreams) { reload(); } else { setDataStreamsToDelete([]); @@ -246,7 +266,7 @@ export const DataStreamTable: React.FunctionComponent = ({ /> ) : null} void; } @@ -146,13 +151,83 @@ const configurationFormSchema: FormSchema = { } ), }, + dataRetentionEnabled: { + type: FIELD_TYPES.TOGGLE, + defaultValue: false, + label: i18n.translate( + 'xpack.idxMgmt.dataStreamsDetailsPanel.editDataRetentionModal.dataRetentionEnabledField', + { + defaultMessage: 'Enable data retention', + } + ), + }, }; -export const EditDataRetentionModal: React.FunctionComponent = ({ - lifecycle, +interface MixedIndicesCalloutProps { + history: ScopedHistory; + ilmPolicyLink: string; + ilmPolicyName?: string; + dataStreamName: string; +} + +const MixedIndicesCallout = ({ + ilmPolicyLink, + ilmPolicyName, dataStreamName, + history, +}: MixedIndicesCalloutProps) => { + return ( + +

+ + {ilmPolicyName} + + ), + viewAllIndicesLink: ( + + + + ), + }} + /> +

+
+ ); +}; + +export const EditDataRetentionModal: React.FunctionComponent = ({ + dataStream, + ilmPolicyName, + ilmPolicyLink, onClose, }) => { + const lifecycle = dataStream?.lifecycle; + const dataStreamName = dataStream?.name as string; + + const { history } = useAppContext(); + const dslWithIlmIndices = isDSLWithILMIndices(dataStream); const { size, unit } = splitSizeAndUnits(lifecycle?.data_retention as string); const { services: { notificationService }, @@ -162,6 +237,7 @@ export const EditDataRetentionModal: React.FunctionComponent = ({ defaultValue: { dataRetention: size, timeUnit: unit || 'd', + dataRetentionEnabled: lifecycle?.enabled, // When data retention is not set and lifecycle is enabled, is the only scenario in // which data retention will be infinite. If lifecycle isnt set or is not enabled, we // dont have inifinite data retention. @@ -184,7 +260,11 @@ export const EditDataRetentionModal: React.FunctionComponent = ({ if (responseData) { const successMessage = i18n.translate( 'xpack.idxMgmt.dataStreamsDetailsPanel.editDataRetentionModal.successDataRetentionNotification', - { defaultMessage: 'Data retention updated' } + { + defaultMessage: + 'Data retention {disabledDataRetention, plural, one { disabled } other { updated } }', + values: { disabledDataRetention: !data.dataRetentionEnabled ? 1 : 0 }, + } ); notificationService.showSuccessToast(successMessage); @@ -214,21 +294,29 @@ export const EditDataRetentionModal: React.FunctionComponent = ({ {' '} - - - {i18n.translate( - 'xpack.idxMgmt.dataStreamsDetailsPanel.editDataRetentionModal.techPreviewLabel', - { - defaultMessage: 'Technical preview', - } - )} - - + /> + {dslWithIlmIndices && ( + <> + + + + )} + + + = ({ componentProps={{ fullWidth: false, euiFieldProps: { - disabled: formData.infiniteRetentionPeriod, + disabled: formData.infiniteRetentionPeriod || !formData.dataRetentionEnabled, 'data-test-subj': `dataRetentionValue`, min: 1, append: ( = ({ path="infiniteRetentionPeriod" component={ToggleField} data-test-subj="infiniteRetentionPeriod" + componentProps={{ + euiFieldProps: { + disabled: !formData.dataRetentionEnabled, + }, + }} /> diff --git a/x-pack/plugins/index_management/public/application/services/api.ts b/x-pack/plugins/index_management/public/application/services/api.ts index 30c8339840018..de911383cf26c 100644 --- a/x-pack/plugins/index_management/public/application/services/api.ts +++ b/x-pack/plugins/index_management/public/application/services/api.ts @@ -85,14 +85,27 @@ export async function deleteDataStreams(dataStreams: string[]) { export async function updateDataRetention( name: string, - data: { dataRetention: string; timeUnit: string; infiniteRetentionPeriod: boolean } + data: { + dataRetention: string; + timeUnit: string; + infiniteRetentionPeriod: boolean; + dataRetentionEnabled: boolean; + } ) { + let body; + + if (!data.dataRetentionEnabled) { + body = { enabled: false }; + } else { + body = data.infiniteRetentionPeriod + ? {} + : { dataRetention: `${data.dataRetention}${data.timeUnit}` }; + } + return sendRequest({ path: `${API_BASE_PATH}/data_streams/${encodeURIComponent(name)}/data_retention`, method: 'put', - body: data.infiniteRetentionPeriod - ? {} - : { dataRetention: `${data.dataRetention}${data.timeUnit}` }, + body, }); } diff --git a/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts b/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts index 8ca301e6bee07..a3aaf4192432b 100644 --- a/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts @@ -28,6 +28,7 @@ const enhanceDataStreams = ({ dataStreamsPrivileges?: SecurityHasPrivilegesResponse; }): EnhancedDataStreamFromEs[] => { return dataStreams.map((dataStream) => { + // @ts-expect-error @elastic/elasticsearch next_generation_managed_by prop is still not in the ES types const enhancedDataStream: EnhancedDataStreamFromEs = { ...dataStream, privileges: { @@ -143,6 +144,7 @@ export function registerGetOneRoute({ router, lib: { handleEsError }, config }: if (dataStreams[0]) { let dataStreamsPrivileges; + if (config.isSecurityEnabled()) { dataStreamsPrivileges = await getDataStreamsPrivileges(client, [dataStreams[0].name]); } diff --git a/x-pack/plugins/index_management/server/routes/api/data_streams/register_put_route.ts b/x-pack/plugins/index_management/server/routes/api/data_streams/register_put_route.ts index cba52ce9101d0..536f6d6287453 100644 --- a/x-pack/plugins/index_management/server/routes/api/data_streams/register_put_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/data_streams/register_put_route.ts @@ -16,6 +16,7 @@ export function registerPutDataRetention({ router, lib: { handleEsError } }: Rou }); const bodySchema = schema.object({ dataRetention: schema.maybe(schema.string()), + enabled: schema.maybe(schema.boolean()), }); router.put( @@ -25,15 +26,21 @@ export function registerPutDataRetention({ router, lib: { handleEsError } }: Rou }, async (context, request, response) => { const { name } = request.params as TypeOf; - const { dataRetention } = request.body as TypeOf; + const { dataRetention, enabled } = request.body as TypeOf; const { client } = (await context.core).elasticsearch; try { - await client.asCurrentUser.indices.putDataLifecycle({ - name, - data_retention: dataRetention, - }); + // Only when enabled is explicitly set to false, we delete the data retention policy. + if (enabled === false) { + await client.asCurrentUser.indices.deleteDataLifecycle({ name }); + } else { + // Otherwise, we create or update the data retention policy. + await client.asCurrentUser.indices.putDataLifecycle({ + name, + data_retention: dataRetention, + }); + } return response.ok({ body: { success: true } }); } catch (error) { diff --git a/x-pack/test/api_integration/apis/management/index_management/data_streams.ts b/x-pack/test/api_integration/apis/management/index_management/data_streams.ts index e0373a405cde7..6ed1ec9ed1c4c 100644 --- a/x-pack/test/api_integration/apis/management/index_management/data_streams.ts +++ b/x-pack/test/api_integration/apis/management/index_management/data_streams.ts @@ -128,8 +128,11 @@ export default function ({ getService }: FtrProviderContext) { { name: indexName, uuid, + preferILM: true, + managedBy: 'Data stream lifecycle', }, ], + nextGenerationManagedBy: 'Data stream lifecycle', generation: 1, health: 'yellow', indexTemplateName: testDataStreamName, @@ -167,12 +170,15 @@ export default function ({ getService }: FtrProviderContext) { indices: [ { name: indexName, + managedBy: 'Data stream lifecycle', + preferILM: true, uuid, }, ], generation: 1, health: 'yellow', indexTemplateName: testDataStreamName, + nextGenerationManagedBy: 'Data stream lifecycle', maxTimeStamp: 0, hidden: false, lifecycle: { @@ -202,12 +208,15 @@ export default function ({ getService }: FtrProviderContext) { indices: [ { name: indexName, + managedBy: 'Data stream lifecycle', + preferILM: true, uuid, }, ], generation: 1, health: 'yellow', indexTemplateName: testDataStreamName, + nextGenerationManagedBy: 'Data stream lifecycle', maxTimeStamp: 0, hidden: false, lifecycle: { @@ -244,6 +253,19 @@ export default function ({ getService }: FtrProviderContext) { expect(body).to.eql({ success: true }); }); + + it('can disable lifecycle for a given policy', async () => { + const { body } = await supertest + .put(`${API_BASE_PATH}/data_streams/${testDataStreamName}/data_retention`) + .set('kbn-xsrf', 'xxx') + .send({ enabled: false }) + .expect(200); + + expect(body).to.eql({ success: true }); + + const datastream = await getDatastream(testDataStreamName); + expect(datastream.lifecycle).to.be(undefined); + }); }); describe('Delete', () => { diff --git a/x-pack/test/functional/apps/index_management/data_streams_tab/data_streams_tab.ts b/x-pack/test/functional/apps/index_management/data_streams_tab/data_streams_tab.ts index eea731575f8f3..9d3da94fead4a 100644 --- a/x-pack/test/functional/apps/index_management/data_streams_tab/data_streams_tab.ts +++ b/x-pack/test/functional/apps/index_management/data_streams_tab/data_streams_tab.ts @@ -109,5 +109,23 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const successToast = await toasts.getToastElement(1); expect(await successToast.getVisibleText()).to.contain('Data retention updated'); }); + + it('allows to disable data retention', async () => { + // Open details flyout + await pageObjects.indexManagement.clickDataStreamAt(0); + // Open the edit retention dialog + await testSubjects.click('manageDataStreamButton'); + await testSubjects.click('editDataRetentionButton'); + + // Disable infinite retention + await testSubjects.click('dataRetentionEnabledField > input'); + + // Submit the form + await testSubjects.click('saveButton'); + + // Expect to see a success toast + const successToast = await toasts.getToastElement(1); + expect(await successToast.getVisibleText()).to.contain('Data retention disabled'); + }); }); }; diff --git a/x-pack/test_serverless/functional/test_suites/common/management/index_management/data_streams.ts b/x-pack/test_serverless/functional/test_suites/common/management/index_management/data_streams.ts index 88cdcba00d181..bbf55e359d360 100644 --- a/x-pack/test_serverless/functional/test_suites/common/management/index_management/data_streams.ts +++ b/x-pack/test_serverless/functional/test_suites/common/management/index_management/data_streams.ts @@ -119,5 +119,23 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const successToast = await toasts.getToastElement(1); expect(await successToast.getVisibleText()).to.contain('Data retention updated'); }); + + it('allows to disable data retention', async () => { + // Open details flyout + await pageObjects.indexManagement.clickDataStreamAt(0); + // Open the edit retention dialog + await testSubjects.click('manageDataStreamButton'); + await testSubjects.click('editDataRetentionButton'); + + // Disable infinite retention + await testSubjects.click('dataRetentionEnabledField > input'); + + // Submit the form + await testSubjects.click('saveButton'); + + // Expect to see a success toast + const successToast = await toasts.getToastElement(1); + expect(await successToast.getVisibleText()).to.contain('Data retention disabled'); + }); }); }; From 376c50cdae7c3588032547022d8ae7a2c3c8661e Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Tue, 31 Oct 2023 17:29:04 +0100 Subject: [PATCH 09/21] [Serverless] Unskip search navigation test (#170224) ## Summary fix https://github.com/elastic/kibana/issues/166597 --- .../src/ui/components/navigation_item.tsx | 7 ++++++- .../functional/test_suites/search/navigation.ts | 17 ++++++++++------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/packages/shared-ux/chrome/navigation/src/ui/components/navigation_item.tsx b/packages/shared-ux/chrome/navigation/src/ui/components/navigation_item.tsx index 87564319d518c..dae8cef6f4eee 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/components/navigation_item.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/components/navigation_item.tsx @@ -9,6 +9,7 @@ import React, { Fragment, useEffect, useMemo } from 'react'; import type { AppDeepLinkId, ChromeProjectNavigationNode } from '@kbn/core-chrome-browser'; import { EuiCollapsibleNavItem } from '@elastic/eui'; +import classNames from 'classnames'; import { useNavigation as useNavigationServices } from '../../services'; import { useInitNavNode } from '../hooks'; @@ -74,6 +75,10 @@ function NavigationItemComp< if (isRootLevel) { const href = getNavigationNodeHref(navNode); + const dataTestSubj = classNames(`nav-item`, { + [`nav-item-deepLinkId-${navNode.deepLink?.id}`]: !!navNode.deepLink, + [`nav-item-isActive`]: navNode.isActive, + }); return ( { diff --git a/x-pack/test_serverless/functional/test_suites/search/navigation.ts b/x-pack/test_serverless/functional/test_suites/search/navigation.ts index b4244a96b82af..03f229f21b47a 100644 --- a/x-pack/test_serverless/functional/test_suites/search/navigation.ts +++ b/x-pack/test_serverless/functional/test_suites/search/navigation.ts @@ -16,8 +16,7 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const browser = getService('browser'); - // FLAKY: https://github.com/elastic/kibana/issues/166597 - describe.skip('navigation', function () { + describe('navigation', function () { before(async () => { await svlCommonPage.login(); await svlSearchNavigation.navigateToLandingPage(); @@ -65,7 +64,7 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { await svlCommonNavigation.sidenav.expectLinkActive({ deepLinkId: 'serverlessElasticsearch', }); - await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({ text: `Getting started` }); + await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({ text: `Get started` }); await testSubjects.existOrFail(`svlSearchOverviewPage`); await expectNoPageReload(); @@ -73,16 +72,20 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { it("management apps from the sidenav hide the 'stack management' root from the breadcrumbs", async () => { await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'management:triggersActions' }); - await svlCommonNavigation.breadcrumbs.expectBreadcrumbTexts(['Alerts', 'Rules']); + await svlCommonNavigation.breadcrumbs.expectBreadcrumbTexts(['Explore', 'Alerts', 'Rules']); await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'management:index_management' }); - await svlCommonNavigation.breadcrumbs.expectBreadcrumbTexts(['Index Management', 'Indices']); + await svlCommonNavigation.breadcrumbs.expectBreadcrumbTexts([ + 'Content', + 'Index Management', + 'Indices', + ]); await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'management:ingest_pipelines' }); - await svlCommonNavigation.breadcrumbs.expectBreadcrumbTexts(['Ingest Pipelines']); + await svlCommonNavigation.breadcrumbs.expectBreadcrumbTexts(['Content', 'Ingest Pipelines']); await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'management:api_keys' }); - await svlCommonNavigation.breadcrumbs.expectBreadcrumbTexts(['API keys']); + await svlCommonNavigation.breadcrumbs.expectBreadcrumbTexts(['Security', 'API keys']); }); it('navigate management', async () => { From e7980c6ef8b27a4bca4b8750fbd99d7dabd18e54 Mon Sep 17 00:00:00 2001 From: Elastic Machine Date: Wed, 1 Nov 2023 03:30:00 +1100 Subject: [PATCH 10/21] [main] Sync bundled packages with Package Storage (#170250) Automated by https://buildkite.com/elastic/package-storage-infra-sync-bundled-packages/builds/15 --- fleet_packages.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fleet_packages.json b/fleet_packages.json index ed422f0cfcb7e..0ffa6c1b105ba 100644 --- a/fleet_packages.json +++ b/fleet_packages.json @@ -30,7 +30,7 @@ }, { "name": "elastic_agent", - "version": "1.14.0" + "version": "1.15.0" }, { "name": "endpoint", @@ -56,6 +56,6 @@ }, { "name": "security_detection_engine", - "version": "8.10.3" + "version": "8.11.2" } -] +] \ No newline at end of file From 58adee01a0d048754dd3909a579140c3456afcb6 Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Tue, 31 Oct 2023 17:39:47 +0100 Subject: [PATCH 11/21] [Security Solution] Support Serverless Cypress tests with different roles (#169017) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Addresses:** https://github.com/elastic/kibana/issues/164451 ## Summary This PR allows to run role based reused between ESS and Serverless Cypress tests. ## Details The main idea behind is to make environmental differences for tests unnoticeable. As Serverless env already has roles and users but ESS env allows to create any possible role and user we just need to create Serverless roles and corresponding users + specific ESS roles and corresponding users in ESS env before running any ESS tests. This way tests will run in a similar env and don't have to bother by roles/users creation in test suites. This is achieved by using separate Cypress support files (Cypress includes `support/e2e.js` by default) `ess_e2e.ts` and `serverless_e2e.ts` executed for corresponding environments. `ess_e2e.ts` contains logic to create mentioned above roles and users while `serverless_e2e.ts` doesn't contain such logic. _Only one user created per role and user has the same name as its corresponding role with `changeme` password._ To have an ability to create roles we need to store their definitions somewhere. It's also convenient to have JSON definitions instead of YAML. Plus Serverless roles should be pulled from `project-controller` repo but it's not addressed in this PR. I've chosen the following locations - Serverless Security roles in `packages/kbn-es/src/serverless_resources/security_roles.json`. While `@kbn/es` is a common package it has `serverless_resources` folder containing `roles.yml` with a mix of `https://github.com/elastic/project-controller/blob/main/internal/project/observability/config/roles.yml`, `https://github.com/elastic/project-controller/blob/main/internal/project/esproject/config/roles.yml` and `https://github.com/elastic/project-controller/blob/main/internal/project/security/config/roles.yml` copied from `project-controller` and used for ES data restore. As there is no automation yet it looks logical to keep Security roles subset next to ES Serverless resources. - ESS Security specific roles in `x-pack/plugins/security_solution/common/test/ess_roles.json` On top of that the following has been done - `reader` role replaced with `t1_analyst` where possible in tests (besides `e2e/explore/cases/attach_alert_to_case.cy.ts` but it's purely ESS test so it's fine) as `reader` is ESS specific and make harder to run the same tests in ESS and Serverless environments but both roles are almost equivalent - `login()` helper function accepts all known roles (Serverless + ESS) but throws an exception if a custom ESS role is used under Serverless env - `x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users` isn't necessary anymore as `security_roles.json` + `ess_roles.json` contain all the necessary data to create roles and users ### Does it enable role support for MKI environments? No. This PR only enabling role support for Non-MKI Serverless environments. MKI env has predefined roles but not users. This will be addressed in a follow up PR. ## Flaky test runner Two unskiped in this PR Serverless Cypress tests using non default role `detection_response/detection_alerts/missing_privileges_callout.cy.ts` and `detection_response/prebuilt_rules/prebuilt_rules_install_update_authorization.cy.ts` [150 runs](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/3723) 🟢 (there is one env related failure but it doesn't look related to the changes in this PR) --- .../kbn-es/src/serverless_resources/README.md | 9 + .../serverless_resources/security_roles.json | 353 ++++++++++++++++++ packages/kbn-es/tsconfig.json | 11 +- test/common/services/security/role.ts | 5 +- test/tsconfig.json | 2 +- .../common/test/ess_roles.json | 136 +++++++ .../security_solution/common/test/index.ts | 25 +- .../scripts/roles_users/README.md | 13 - .../roles_users/detections_admin/README.md | 1 - .../delete_detections_user.sh | 11 - .../detections_admin/detections_role.json | 44 --- .../detections_admin/detections_user.json | 6 - .../detections_admin/get_detections_role.sh | 11 - .../roles_users/detections_admin/index.ts | 10 - .../detections_admin/post_detections_role.sh | 12 - .../detections_admin/post_detections_user.sh | 14 - .../scripts/roles_users/hunter/README.md | 11 - .../hunter/delete_detections_user.sh | 11 - .../roles_users/hunter/detections_role.json | 45 --- .../roles_users/hunter/detections_user.json | 6 - .../roles_users/hunter/get_detections_role.sh | 11 - .../hunter/post_detections_role.sh | 14 - .../hunter/post_detections_user.sh | 14 - .../roles_users/hunter_no_actions/README.md | 11 - .../delete_detections_user.sh | 11 - .../hunter_no_actions/detections_role.json | 44 --- .../hunter_no_actions/detections_user.json | 6 - .../hunter_no_actions/get_detections_role.sh | 11 - .../roles_users/hunter_no_actions/index.ts | 10 - .../hunter_no_actions/post_detections_role.sh | 14 - .../hunter_no_actions/post_detections_user.sh | 14 - .../scripts/roles_users/index.ts | 16 - .../roles_users/platform_engineer/README.md | 5 - .../delete_detections_user.sh | 11 - .../platform_engineer/detections_role.json | 49 --- .../platform_engineer/detections_user.json | 6 - .../platform_engineer/get_detections_role.sh | 11 - .../roles_users/platform_engineer/index.ts | 10 - .../platform_engineer/post_detections_role.sh | 14 - .../platform_engineer/post_detections_user.sh | 15 - .../scripts/roles_users/reader/README.md | 3 - .../reader/delete_detections_user.sh | 11 - .../roles_users/reader/detections_role.json | 38 -- .../roles_users/reader/detections_user.json | 6 - .../roles_users/reader/get_detections_role.sh | 11 - .../scripts/roles_users/reader/index.ts | 10 - .../reader/post_detections_role.sh | 15 - .../reader/post_detections_user.sh | 14 - .../scripts/roles_users/rule_author/README.md | 5 - .../rule_author/delete_detections_user.sh | 11 - .../rule_author/detections_role.json | 48 --- .../rule_author/detections_user.json | 6 - .../rule_author/get_detections_role.sh | 11 - .../scripts/roles_users/rule_author/index.ts | 10 - .../rule_author/post_detections_role.sh | 14 - .../rule_author/post_detections_user.sh | 14 - .../scripts/roles_users/soc_manager/README.md | 5 - .../soc_manager/delete_detections_user.sh | 11 - .../soc_manager/detections_role.json | 48 --- .../soc_manager/detections_user.json | 6 - .../soc_manager/get_detections_role.sh | 11 - .../scripts/roles_users/soc_manager/index.ts | 10 - .../soc_manager/post_detections_role.sh | 15 - .../soc_manager/post_detections_user.sh | 15 - .../scripts/roles_users/t1_analyst/README.md | 3 - .../t1_analyst/delete_detections_user.sh | 11 - .../t1_analyst/detections_role.json | 37 -- .../t1_analyst/detections_user.json | 6 - .../t1_analyst/get_detections_role.sh | 11 - .../scripts/roles_users/t1_analyst/index.ts | 10 - .../t1_analyst/post_detections_role.sh | 15 - .../t1_analyst/post_detections_user.sh | 14 - .../scripts/roles_users/t2_analyst/README.md | 5 - .../t2_analyst/delete_detections_user.sh | 11 - .../t2_analyst/detections_role.json | 42 --- .../t2_analyst/detections_user.json | 6 - .../t2_analyst/get_detections_role.sh | 11 - .../scripts/roles_users/t2_analyst/index.ts | 10 - .../t2_analyst/post_detections_role.sh | 14 - .../t2_analyst/post_detections_user.sh | 14 - .../plugins/security_solution/tsconfig.json | 1 + .../security_solution/roles_users_utils.ts | 126 +------ .../group10/read_privileges.ts | 65 ---- .../missing_privileges_callout.cy.ts | 7 +- .../install_update_authorization.cy.ts | 12 +- .../authorization/all_rules_read_only.cy.ts | 4 +- .../rule_details_flow/read_only_view.cy.ts | 4 +- .../read_only.cy.ts | 4 +- .../explore/cases/attach_alert_to_case.cy.ts | 4 +- .../cypress/e2e/explore/cases/creation.cy.ts | 5 +- .../investigations/timelines/creation.cy.ts | 4 +- .../cypress/env_var_names_constants.ts | 30 ++ .../cypress/support/cypress_grep.d.ts} | 8 +- .../cypress/support/e2e.js | 39 -- .../cypress/support/e2e.ts | 39 ++ .../cypress/support/setup_users.ts | 50 +++ .../cypress/tasks/common.ts | 10 +- .../cypress/tasks/edit_rule.ts | 4 +- .../cypress/tasks/login.ts | 188 ++++------ .../cypress/tasks/navigation.ts | 8 +- .../cypress/tasks/rule_details.ts | 4 +- .../cypress/tasks/rules_management.ts | 4 +- .../cypress/tsconfig.json | 2 +- .../apps/endpoint/endpoint_permissions.ts | 24 +- 104 files changed, 804 insertions(+), 1443 deletions(-) create mode 100644 packages/kbn-es/src/serverless_resources/security_roles.json create mode 100644 x-pack/plugins/security_solution/common/test/ess_roles.json delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/README.md delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/README.md delete mode 100755 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/delete_detections_user.sh delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/detections_role.json delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/detections_user.json delete mode 100755 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/get_detections_role.sh delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/index.ts delete mode 100755 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/post_detections_role.sh delete mode 100755 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/post_detections_user.sh delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/README.md delete mode 100755 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/delete_detections_user.sh delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/detections_role.json delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/detections_user.json delete mode 100755 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/get_detections_role.sh delete mode 100755 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/post_detections_role.sh delete mode 100755 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/post_detections_user.sh delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter_no_actions/README.md delete mode 100755 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter_no_actions/delete_detections_user.sh delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter_no_actions/detections_role.json delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter_no_actions/detections_user.json delete mode 100755 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter_no_actions/get_detections_role.sh delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter_no_actions/index.ts delete mode 100755 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter_no_actions/post_detections_role.sh delete mode 100755 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter_no_actions/post_detections_user.sh delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/index.ts delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/README.md delete mode 100755 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/delete_detections_user.sh delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/detections_role.json delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/detections_user.json delete mode 100755 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/get_detections_role.sh delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/index.ts delete mode 100755 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/post_detections_role.sh delete mode 100755 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/post_detections_user.sh delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/README.md delete mode 100755 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/delete_detections_user.sh delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/detections_role.json delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/detections_user.json delete mode 100755 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/get_detections_role.sh delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/index.ts delete mode 100755 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/post_detections_role.sh delete mode 100755 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/post_detections_user.sh delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/README.md delete mode 100755 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/delete_detections_user.sh delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/detections_role.json delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/detections_user.json delete mode 100755 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/get_detections_role.sh delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/index.ts delete mode 100755 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/post_detections_role.sh delete mode 100755 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/post_detections_user.sh delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/README.md delete mode 100755 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/delete_detections_user.sh delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/detections_role.json delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/detections_user.json delete mode 100755 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/get_detections_role.sh delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/index.ts delete mode 100755 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/post_detections_role.sh delete mode 100755 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/post_detections_user.sh delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/README.md delete mode 100755 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/delete_detections_user.sh delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/detections_role.json delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/detections_user.json delete mode 100755 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/get_detections_role.sh delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/index.ts delete mode 100755 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/post_detections_role.sh delete mode 100755 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/post_detections_user.sh delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/README.md delete mode 100755 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/delete_detections_user.sh delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/detections_role.json delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/detections_user.json delete mode 100755 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/get_detections_role.sh delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/index.ts delete mode 100755 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/post_detections_role.sh delete mode 100755 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/post_detections_user.sh create mode 100644 x-pack/test/security_solution_cypress/cypress/env_var_names_constants.ts rename x-pack/{plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/index.ts => test/security_solution_cypress/cypress/support/cypress_grep.d.ts} (63%) delete mode 100644 x-pack/test/security_solution_cypress/cypress/support/e2e.js create mode 100644 x-pack/test/security_solution_cypress/cypress/support/e2e.ts create mode 100644 x-pack/test/security_solution_cypress/cypress/support/setup_users.ts diff --git a/packages/kbn-es/src/serverless_resources/README.md b/packages/kbn-es/src/serverless_resources/README.md index 8ead2197be3ea..0af28f82a1dec 100644 --- a/packages/kbn-es/src/serverless_resources/README.md +++ b/packages/kbn-es/src/serverless_resources/README.md @@ -2,6 +2,15 @@ The resources in this directory are used for seeding Elasticsearch Serverless images with users, roles and tokens for SSL and authentication. Serverless requires file realm authentication, so we will bind mount them into the containers at `/usr/share/elasticsearch/config/`. +## Roles + +Roles defined in `roles.yml` intended to mock a Serverless deployment. It must be in sync with `project-controller` defined roles and used in real (MKI) environments. In case of some differences tests may pass against Serverless snapshot environment but fail against MKI environments creating confusion. + +### Why `security_roles.json` is here? + +`security_roles.json` is a subset of defined in `roles.yml` roles in a JSON format and extended with necessary fields +to be compatible with `/api/security/role/{roleName}` endpoint. It's consumed by test environments like Cypress to be able to run different scenarios. + ## Users ### Default user diff --git a/packages/kbn-es/src/serverless_resources/security_roles.json b/packages/kbn-es/src/serverless_resources/security_roles.json new file mode 100644 index 0000000000000..5ac286a41c164 --- /dev/null +++ b/packages/kbn-es/src/serverless_resources/security_roles.json @@ -0,0 +1,353 @@ +{ + "t1_analyst": { + "name": "t1_analyst", + "elasticsearch": { + "cluster": [], + "indices": [ + { + "names": [".alerts-security*", ".siem-signals-*"], + "privileges": ["read", "write", "maintenance"] + }, + { + "names": [ + "apm-*-transaction*", + "traces-apm*", + "auditbeat-*", + "endgame-*", + "filebeat-*", + "logs-*", + "packetbeat-*", + "winlogbeat-*", + "metrics-endpoint.metadata_current_*", + ".fleet-agents*", + ".fleet-actions*" + ], + "privileges": ["read"] + } + ], + "run_as": [] + }, + "kibana": [ + { + "feature": { + "ml": ["read"], + "siem": ["read", "read_alerts"], + "securitySolutionAssistant": ["all"], + "securitySolutionCases": ["read"], + "actions": ["read"], + "builtInAlerts": ["read"] + }, + "spaces": ["*"], + "base": [] + } + ] + }, + "t2_analyst": { + "name": "t2_analyst", + "elasticsearch": { + "cluster": [], + "indices": [ + { + "names": [".alerts-security*", ".siem-signals-*"], + "privileges": ["read", "write", "maintenance"] + }, + { + "names": [ + ".lists*", + ".items*", + "apm-*-transaction*", + "traces-apm*", + "auditbeat-*", + "endgame-*", + "filebeat-*", + "logs-*", + "packetbeat-*", + "winlogbeat-*", + "metrics-endpoint.metadata_current_*", + ".fleet-agents*", + ".fleet-actions*" + ], + "privileges": ["read"] + } + ], + "run_as": [] + }, + "kibana": [ + { + "feature": { + "ml": ["read"], + "siem": ["read", "read_alerts"], + "securitySolutionAssistant": ["all"], + "securitySolutionCases": ["read"], + "actions": ["read"], + "builtInAlerts": ["read"] + }, + "spaces": ["*"], + "base": [] + } + ] + }, + "t3_analyst": { + "name": "t3_analyst", + "elasticsearch": { + "cluster": [], + "indices": [ + { + "names": [ + "apm-*-transaction*", + "traces-apm*", + "auditbeat-*", + "endgame-*", + "filebeat-*", + "logs-*", + "packetbeat-*", + "winlogbeat-*" + ], + "privileges": ["read", "write"] + }, + { + "names": [".alerts-security*", ".siem-signals-*"], + "privileges": ["read", "write"] + }, + { + "names": [".lists*", ".items*"], + "privileges": ["read", "write"] + }, + { + "names": ["metrics-endpoint.metadata_current_*", ".fleet-agents*", ".fleet-actions*"], + "privileges": ["read"] + } + ], + "run_as": [] + }, + "kibana": [ + { + "feature": { + "ml": ["read"], + "siem": [ + "all", + "read_alerts", + "crud_alerts", + "endpoint_list_all", + "trusted_applications_all", + "event_filters_all", + "host_isolation_exceptions_all", + "blocklist_all", + "policy_management_read", + "host_isolation_all", + "process_operations_all", + "actions_log_management_all", + "file_operations_all" + ], + "securitySolutionCases": ["all"], + "actions": ["read"], + "builtInAlerts": ["all"], + "osquery": ["all"] + }, + "spaces": ["*"], + "base": [] + } + ] + }, + "rule_author": { + "name": "rule_author", + "elasticsearch": { + "cluster": [], + "indices": [ + { + "names": [ + "apm-*-transaction*", + "traces-apm*", + "auditbeat-*", + "endgame-*", + "filebeat-*", + "logs-*", + "packetbeat-*", + "winlogbeat-*", + ".lists*", + ".items*" + ], + "privileges": ["read", "write"] + }, + { + "names": [ + ".alerts-security*", + ".preview.alerts-security*", + ".internal.preview.alerts-security*", + ".siem-signals-*" + ], + "privileges": ["read", "write", "maintenance", "view_index_metadata"] + }, + { + "names": ["metrics-endpoint.metadata_current_*", ".fleet-agents*", ".fleet-actions*"], + "privileges": ["read"] + } + ], + "run_as": [] + }, + "kibana": [ + { + "feature": { + "ml": ["read"], + "siem": ["all", "read_alerts", "crud_alerts"], + "securitySolutionAssistant": ["all"], + "securitySolutionCases": ["all"], + "actions": ["read"], + "builtInAlerts": ["all"] + }, + "spaces": ["*"], + "base": [] + } + ] + }, + "soc_manager": { + "name": "soc_manager", + "elasticsearch": { + "cluster": [], + "indices": [ + { + "names": [ + "apm-*-transaction*", + "traces-apm*", + "auditbeat-*", + "endgame-*", + "filebeat-*", + "logs-*", + "packetbeat-*", + "winlogbeat-*", + ".lists*", + ".items*" + ], + "privileges": ["read", "write"] + }, + { + "names": [ + ".alerts-security*", + ".preview.alerts-security*", + ".internal.preview.alerts-security*", + ".siem-signals-*" + ], + "privileges": ["read", "write", "manage"] + }, + { + "names": ["metrics-endpoint.metadata_current_*", ".fleet-agents*", ".fleet-actions*"], + "privileges": ["read"] + } + ], + "run_as": [] + }, + "kibana": [ + { + "feature": { + "ml": ["read"], + "siem": ["all", "read_alerts", "crud_alerts"], + "securitySolutionAssistant": ["all"], + "securitySolutionCases": ["all"], + "actions": ["all"], + "builtInAlerts": ["all"] + }, + "spaces": ["*"], + "base": [] + } + ] + }, + "detections_admin": { + "name": "detections_admin", + "elasticsearch": { + "cluster": ["manage"], + "indices": [ + { + "names": [ + ".siem-signals-*", + ".alerts-security*", + ".preview.alerts-security*", + ".internal.preview.alerts-security*", + ".lists*", + ".items*", + "apm-*-transaction*", + "traces-apm*", + "auditbeat-*", + "endgame-*", + "filebeat-*", + "logs-*", + "packetbeat-*", + "winlogbeat-*" + ], + "privileges": ["manage", "write", "read"] + }, + { + "names": ["metrics-endpoint.metadata_current_*", ".fleet-agents*", ".fleet-actions*"], + "privileges": ["read"] + } + ], + "run_as": [] + }, + "kibana": [ + { + "feature": { + "ml": ["all"], + "siem": ["all", "read_alerts", "crud_alerts"], + "securitySolutionAssistant": ["all"], + "securitySolutionCases": ["all"], + "actions": ["read"], + "builtInAlerts": ["all"], + "dev_tools": ["all"] + }, + "spaces": ["*"], + "base": [] + } + ] + }, + "platform_engineer": { + "name": "platform_engineer", + "elasticsearch": { + "cluster": ["manage"], + "indices": [ + { + "names": [".lists*", ".items*"], + "privileges": ["all"] + }, + { + "names": [ + "apm-*-transaction*", + "traces-apm*", + "auditbeat-*", + "endgame-*", + "filebeat-*", + "logs-*", + "packetbeat-*", + "winlogbeat-*", + "metrics-endpoint.metadata_current_*", + ".fleet-agents*", + ".fleet-actions*" + ], + "privileges": ["all"] + }, + { + "names": [ + ".alerts-security*", + ".preview.alerts-security*", + ".internal.preview.alerts-security*", + ".siem-signals-*" + ], + "privileges": ["all"] + } + ], + "run_as": [] + }, + "kibana": [ + { + "feature": { + "ml": ["all"], + "siem": ["all", "read_alerts", "crud_alerts"], + "securitySolutionAssistant": ["all"], + "securitySolutionCases": ["all"], + "actions": ["all"], + "builtInAlerts": ["all"] + }, + "spaces": ["*"], + "base": [] + } + ] + } +} diff --git a/packages/kbn-es/tsconfig.json b/packages/kbn-es/tsconfig.json index deece402b3794..75059c2ef69cd 100644 --- a/packages/kbn-es/tsconfig.json +++ b/packages/kbn-es/tsconfig.json @@ -3,19 +3,14 @@ "compilerOptions": { "outDir": "target/types" }, - "include": [ - "**/*.ts", - "**/*.js" - ], - "exclude": [ - "target/**/*", - ], + "include": ["**/*.ts", "**/*.js", "**/*.json"], + "exclude": ["target/**/*"], "kbn_references": [ "@kbn/tooling-log", "@kbn/dev-utils", "@kbn/dev-proc-runner", "@kbn/ci-stats-reporter", "@kbn/jest-serializers", - "@kbn/repo-info", + "@kbn/repo-info" ] } diff --git a/test/common/services/security/role.ts b/test/common/services/security/role.ts index 51b50a5dda82f..692a691cd87f4 100644 --- a/test/common/services/security/role.ts +++ b/test/common/services/security/role.ts @@ -18,7 +18,10 @@ export class Role { const { data, status, statusText } = await this.kibanaServer.request({ path: `/api/security/role/${name}`, method: 'PUT', - body: role, + body: { + kibana: role.kibana, + elasticsearch: role.elasticsearch, + }, retries: 0, }); if (status !== 204) { diff --git a/test/tsconfig.json b/test/tsconfig.json index fb20896356807..a763d6f6a44d6 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -70,6 +70,6 @@ "@kbn/core-http-common", "@kbn/event-annotation-plugin", "@kbn/event-annotation-common", - "@kbn/links-plugin" + "@kbn/links-plugin", ] } diff --git a/x-pack/plugins/security_solution/common/test/ess_roles.json b/x-pack/plugins/security_solution/common/test/ess_roles.json new file mode 100644 index 0000000000000..d21fe90e2de02 --- /dev/null +++ b/x-pack/plugins/security_solution/common/test/ess_roles.json @@ -0,0 +1,136 @@ +{ + "reader": { + "name": "reader", + "elasticsearch": { + "cluster": [], + "indices": [ + { + "names": [ + ".siem-signals-*", + ".alerts-security*", + ".lists*", + ".items*", + "metrics-endpoint.metadata_current_*", + ".fleet-agents*", + ".fleet-actions*" + ], + "privileges": ["read"] + }, + { + "names": ["*"], + "privileges": ["read", "maintenance", "view_index_metadata"] + } + ], + "run_as": [] + }, + "kibana": [ + { + "feature": { + "ml": ["read"], + "siem": ["read", "read_alerts"], + "securitySolutionAssistant": ["none"], + "securitySolutionCases": ["read"], + "actions": ["read"], + "builtInAlerts": ["read"] + }, + "spaces": ["*"], + "base": [] + } + ] + }, + "hunter": { + "name": "hunter", + "elasticsearch": { + "cluster": [], + "indices": [ + { + "names": [ + "apm-*-transaction*", + "traces-apm*", + "auditbeat-*", + "endgame-*", + "filebeat-*", + "logs-*", + "packetbeat-*", + "winlogbeat-*" + ], + "privileges": ["read", "write"] + }, + { + "names": [".alerts-security*", ".siem-signals-*"], + "privileges": ["read", "write"] + }, + { + "names": [".lists*", ".items*"], + "privileges": ["read", "write"] + }, + { + "names": ["metrics-endpoint.metadata_current_*", ".fleet-agents*", ".fleet-actions*"], + "privileges": ["read"] + } + ], + "run_as": [] + }, + "kibana": [ + { + "feature": { + "ml": ["read"], + "siem": ["all", "read_alerts", "crud_alerts"], + "securitySolutionAssistant": ["all"], + "securitySolutionCases": ["all"], + "actions": ["read"], + "builtInAlerts": ["all"] + }, + "spaces": ["*"], + "base": [] + } + ] + }, + "hunter_no_actions": { + "name": "hunter_no_actions", + "elasticsearch": { + "cluster": [], + "indices": [ + { + "names": [ + "apm-*-transaction*", + "traces-apm*", + "auditbeat-*", + "endgame-*", + "filebeat-*", + "logs-*", + "packetbeat-*", + "winlogbeat-*" + ], + "privileges": ["read", "write"] + }, + { + "names": [".alerts-security*", ".siem-signals-*"], + "privileges": ["read", "write"] + }, + { + "names": [".lists*", ".items*"], + "privileges": ["read", "write"] + }, + { + "names": ["metrics-endpoint.metadata_current_*", ".fleet-agents*", ".fleet-actions*"], + "privileges": ["read"] + } + ], + "run_as": [] + }, + "kibana": [ + { + "feature": { + "ml": ["read"], + "siem": ["all", "read_alerts", "crud_alerts"], + "securitySolutionAssistant": ["all"], + "securitySolutionCases": ["all"], + "builtInAlerts": ["all"] + }, + "spaces": ["*"], + "base": [] + } + ] + } +} diff --git a/x-pack/plugins/security_solution/common/test/index.ts b/x-pack/plugins/security_solution/common/test/index.ts index bb1d5e9db489a..ac2fd661320ce 100644 --- a/x-pack/plugins/security_solution/common/test/index.ts +++ b/x-pack/plugins/security_solution/common/test/index.ts @@ -5,17 +5,30 @@ * 2.0. */ -// For the source of these roles please consult the PR these were introduced https://github.com/elastic/kibana/pull/81866#issue-511165754 +import serverlessRoleDefinitions from '@kbn/es/src/serverless_resources/security_roles.json'; +import essRoleDefinitions from './ess_roles.json'; + +type ServerlessSecurityRoleName = keyof typeof serverlessRoleDefinitions; +type EssSecurityRoleName = keyof typeof essRoleDefinitions; + +export const KNOWN_SERVERLESS_ROLE_DEFINITIONS = serverlessRoleDefinitions; +export const KNOWN_ESS_ROLE_DEFINITIONS = essRoleDefinitions; + +export type SecurityRoleName = ServerlessSecurityRoleName | EssSecurityRoleName; + export enum ROLES { - soc_manager = 'soc_manager', - reader = 'reader', + // Serverless roles t1_analyst = 't1_analyst', t2_analyst = 't2_analyst', - hunter = 'hunter', - hunter_no_actions = 'hunter_no_actions', + t3_analyst = 't3_analyst', rule_author = 'rule_author', - platform_engineer = 'platform_engineer', + soc_manager = 'soc_manager', detections_admin = 'detections_admin', + platform_engineer = 'platform_engineer', + // ESS roles + reader = 'reader', + hunter = 'hunter', + hunter_no_actions = 'hunter_no_actions', } /** diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/README.md b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/README.md deleted file mode 100644 index 3d6ac856a79ad..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/README.md +++ /dev/null @@ -1,13 +0,0 @@ -1. When first starting up elastic, detections will not be available until you visit the page with a SOC Manager role or Platform Engineer role -2. I gave the Hunter role "all" privileges for saved objects management and builtInAlerts so that they can create rules. -3. Rule Author has the ability to create rules and create value lists - -| Role | Data Sources | Security Solution ML Jobs/Results | Lists | Rules/Exceptions | Action Connectors | Signals/Alerts | -| :------------------------------------------: | :----------: | :-------------------------------: | :---------: | :--------------: | :---------------: | :------------------------------: | -| Reader (read-only user) | read | read | read | read | read | read | -| T1 Analyst | read | read | none | read | read | read, write | -| T2 Analyst | read | read | read | read | read | read, write | -| Hunter / T3 Analyst | read, write | read | read | read, write | read | read, write | -| Rule Author / Manager / Detections Engineer | read, write | read | read, write | read, write | read | read, write, view_index_metadata | -| SOC Manager | read, write | read | read, write | read, write | all | read, write, manage | -| Platform Engineer (data ingest, cluster ops) | read, write | all | all | read, write | all | all | diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/README.md b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/README.md deleted file mode 100644 index 2ebcedcc75d95..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/README.md +++ /dev/null @@ -1 +0,0 @@ -This user contains all the possible privileges listed in our detections privileges docs https://www.elastic.co/guide/en/security/current/detections-permissions-section.html This user has higher privileges than the Platform Engineer user diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/delete_detections_user.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/delete_detections_user.sh deleted file mode 100755 index c8bcdb151e740..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/delete_detections_user.sh +++ /dev/null @@ -1,11 +0,0 @@ - -# -# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -# or more contributor license agreements. Licensed under the Elastic License -# 2.0; you may not use this file except in compliance with the Elastic License -# 2.0. -# - -curl -v -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ - -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ --XDELETE ${ELASTICSEARCH_URL}/_security/user/detections_admin diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/detections_role.json deleted file mode 100644 index 133083cec2601..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/detections_role.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "elasticsearch": { - "cluster": ["manage"], - "indices": [ - { - "names": [ - ".siem-signals-*", - ".alerts-security*", - ".preview.alerts-security*", - ".internal.preview.alerts-security*", - ".lists*", - ".items*", - "apm-*-transaction*", - "traces-apm*", - "auditbeat-*", - "endgame-*", - "filebeat-*", - "logs-*", - "packetbeat-*", - "winlogbeat-*" - ], - "privileges": ["manage", "write", "read"] - }, - { - "names": ["metrics-endpoint.metadata_current_*", ".fleet-agents*", ".fleet-actions*"], - "privileges": ["read"] - } - ] - }, - "kibana": [ - { - "feature": { - "ml": ["all"], - "siem": ["all", "read_alerts", "crud_alerts"], - "securitySolutionAssistant": ["all"], - "securitySolutionCases": ["all"], - "actions": ["read"], - "builtInAlerts": ["all"], - "dev_tools": ["all"] - }, - "spaces": ["*"] - } - ] -} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/detections_user.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/detections_user.json deleted file mode 100644 index 9910d9b516a20..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/detections_user.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "password": "changeme", - "roles": ["detections_admin"], - "full_name": "Detections User", - "email": "detections-user@example.com" -} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/get_detections_role.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/get_detections_role.sh deleted file mode 100755 index a29728642ed40..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/get_detections_role.sh +++ /dev/null @@ -1,11 +0,0 @@ - -# -# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -# or more contributor license agreements. Licensed under the Elastic License -# 2.0; you may not use this file except in compliance with the Elastic License -# 2.0. -# - -curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ - -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ --XGET ${KIBANA_URL}/api/security/role/detections_admin | jq -S . diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/index.ts deleted file mode 100644 index 5ed44652b5946..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as detectionsAdminUser from './detections_user.json'; -import * as detectionsAdminRole from './detections_role.json'; -export { detectionsAdminUser, detectionsAdminRole }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/post_detections_role.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/post_detections_role.sh deleted file mode 100755 index 56b3901700c8c..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/post_detections_role.sh +++ /dev/null @@ -1,12 +0,0 @@ - -# -# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -# or more contributor license agreements. Licensed under the Elastic License -# 2.0; you may not use this file except in compliance with the Elastic License -# 2.0. -# - -curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ - -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ --XPUT ${KIBANA_URL}/api/security/role/detections_admin \ --d @detections_role.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/post_detections_user.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/post_detections_user.sh deleted file mode 100755 index 55f845128889b..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/post_detections_user.sh +++ /dev/null @@ -1,14 +0,0 @@ - -# -# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -# or more contributor license agreements. Licensed under the Elastic License -# 2.0; you may not use this file except in compliance with the Elastic License -# 2.0. -# - -USER=(${@:-./detections_user.json}) - -curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ - -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ - ${ELASTICSEARCH_URL}/_security/user/detections_admin \ --d @${USER} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/README.md b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/README.md deleted file mode 100644 index 1344c5bbb0891..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/README.md +++ /dev/null @@ -1,11 +0,0 @@ -This user can CRUD rules and signals. The main difference here is the user has - -```json -"builtInAlerts": ["all"], -``` - -privileges whereas the T1 and T2 have "read" privileges which prevents them from creating rules - -| Role | Data Sources | Security Solution ML Jobs/Results | Lists | Rules/Exceptions | Action Connectors | Signals/Alerts | -| :-----------------: | :----------: | :------------------: | :---: | :--------------: | :---------------: | :------------: | -| Hunter / T3 Analyst | read, write | read | read | read, write | read | read, write | diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/delete_detections_user.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/delete_detections_user.sh deleted file mode 100755 index 595f0a49282d8..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/delete_detections_user.sh +++ /dev/null @@ -1,11 +0,0 @@ - -# -# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -# or more contributor license agreements. Licensed under the Elastic License -# 2.0; you may not use this file except in compliance with the Elastic License -# 2.0. -# - -curl -v -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ - -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ --XDELETE ${ELASTICSEARCH_URL}/_security/user/hunter diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/detections_role.json deleted file mode 100644 index 23a1256dac4aa..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/detections_role.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "elasticsearch": { - "cluster": [], - "indices": [ - { - "names": [ - "apm-*-transaction*", - "traces-apm*", - "auditbeat-*", - "endgame-*", - "filebeat-*", - "logs-*", - "packetbeat-*", - "winlogbeat-*" - ], - "privileges": ["read", "write"] - }, - { - "names": [".alerts-security*", ".siem-signals-*"], - "privileges": ["read", "write"] - }, - { - "names": [".lists*", ".items*"], - "privileges": ["read", "write"] - }, - { - "names": ["metrics-endpoint.metadata_current_*", ".fleet-agents*", ".fleet-actions*"], - "privileges": ["read"] - } - ] - }, - "kibana": [ - { - "feature": { - "ml": ["read"], - "siem": ["all", "read_alerts", "crud_alerts"], - "securitySolutionAssistant": ["all"], - "securitySolutionCases": ["all"], - "actions": ["read"], - "builtInAlerts": ["all"] - }, - "spaces": ["*"] - } - ] -} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/detections_user.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/detections_user.json deleted file mode 100644 index f9454cc0ad2fe..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/detections_user.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "password": "changeme", - "roles": ["hunter"], - "full_name": "Hunter", - "email": "detections-reader@example.com" -} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/get_detections_role.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/get_detections_role.sh deleted file mode 100755 index 7ec850ce220bb..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/get_detections_role.sh +++ /dev/null @@ -1,11 +0,0 @@ - -# -# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -# or more contributor license agreements. Licensed under the Elastic License -# 2.0; you may not use this file except in compliance with the Elastic License -# 2.0. -# - -curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ - -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ --XGET ${KIBANA_URL}/api/security/role/hunter | jq -S . diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/post_detections_role.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/post_detections_role.sh deleted file mode 100755 index debffe0fcac4c..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/post_detections_role.sh +++ /dev/null @@ -1,14 +0,0 @@ - -# -# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -# or more contributor license agreements. Licensed under the Elastic License -# 2.0; you may not use this file except in compliance with the Elastic License -# 2.0. -# - -ROLE=(${@:-./detections_role.json}) - -curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ - -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ --XPUT ${KIBANA_URL}/api/security/role/hunter \ --d @${ROLE} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/post_detections_user.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/post_detections_user.sh deleted file mode 100755 index ab2a053081394..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/post_detections_user.sh +++ /dev/null @@ -1,14 +0,0 @@ - -# -# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -# or more contributor license agreements. Licensed under the Elastic License -# 2.0; you may not use this file except in compliance with the Elastic License -# 2.0. -# - -USER=(${@:-./detections_user.json}) - -curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ - -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ - ${ELASTICSEARCH_URL}/_security/user/hunter \ --d @${USER} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter_no_actions/README.md b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter_no_actions/README.md deleted file mode 100644 index 7708972614098..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter_no_actions/README.md +++ /dev/null @@ -1,11 +0,0 @@ -This user can CRUD rules and signals. The main difference here is the user has - -```json -"builtInAlerts": ["all"], -``` - -privileges whereas the T1 and T2 have "read" privileges which prevents them from creating rules - -| Role | Data Sources | Security Solution ML Jobs/Results | Lists | Rules/Exceptions | Action Connectors | Signals/Alerts | -| :-----------------: | :----------: | :------------------: | :---: | :--------------: | :---------------: | :------------: | -| Hunter / T3 Analyst | read, write | read | read | read, write | none | read, write | diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter_no_actions/delete_detections_user.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter_no_actions/delete_detections_user.sh deleted file mode 100755 index 8f2ffcb27f111..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter_no_actions/delete_detections_user.sh +++ /dev/null @@ -1,11 +0,0 @@ - -# -# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -# or more contributor license agreements. Licensed under the Elastic License -# 2.0; you may not use this file except in compliance with the Elastic License -# 2.0. -# - -curl -v -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ - -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ --XDELETE ${ELASTICSEARCH_URL}/_security/user/hunter_no_actions diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter_no_actions/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter_no_actions/detections_role.json deleted file mode 100644 index 6b392c18f8caa..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter_no_actions/detections_role.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "elasticsearch": { - "cluster": [], - "indices": [ - { - "names": [ - "apm-*-transaction*", - "traces-apm*", - "auditbeat-*", - "endgame-*", - "filebeat-*", - "logs-*", - "packetbeat-*", - "winlogbeat-*" - ], - "privileges": ["read", "write"] - }, - { - "names": [".alerts-security*", ".siem-signals-*"], - "privileges": ["read", "write"] - }, - { - "names": [".lists*", ".items*"], - "privileges": ["read", "write"] - }, - { - "names": ["metrics-endpoint.metadata_current_*", ".fleet-agents*", ".fleet-actions*"], - "privileges": ["read"] - } - ] - }, - "kibana": [ - { - "feature": { - "ml": ["read"], - "siem": ["all", "read_alerts", "crud_alerts"], - "securitySolutionAssistant": ["all"], - "securitySolutionCases": ["all"], - "builtInAlerts": ["all"] - }, - "spaces": ["*"] - } - ] -} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter_no_actions/detections_user.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter_no_actions/detections_user.json deleted file mode 100644 index c059863b3ca1f..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter_no_actions/detections_user.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "password": "changeme", - "roles": ["hunter_no_actions"], - "full_name": "Hunter No Actions", - "email": "detections-reader@example.com" -} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter_no_actions/get_detections_role.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter_no_actions/get_detections_role.sh deleted file mode 100755 index 49deae0c6c450..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter_no_actions/get_detections_role.sh +++ /dev/null @@ -1,11 +0,0 @@ - -# -# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -# or more contributor license agreements. Licensed under the Elastic License -# 2.0; you may not use this file except in compliance with the Elastic License -# 2.0. -# - -curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ - -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ --XGET ${KIBANA_URL}/api/security/role/hunter_no_actions | jq -S . diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter_no_actions/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter_no_actions/index.ts deleted file mode 100644 index 16d50f9b59daa..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter_no_actions/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as hunterNoActionsUser from './detections_user.json'; -import * as hunterNoActionsRole from './detections_role.json'; -export { hunterNoActionsUser, hunterNoActionsRole }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter_no_actions/post_detections_role.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter_no_actions/post_detections_role.sh deleted file mode 100755 index aa4f832649b08..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter_no_actions/post_detections_role.sh +++ /dev/null @@ -1,14 +0,0 @@ - -# -# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -# or more contributor license agreements. Licensed under the Elastic License -# 2.0; you may not use this file except in compliance with the Elastic License -# 2.0. -# - -ROLE=(${@:-./detections_role.json}) - -curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ - -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ --XPUT ${KIBANA_URL}/api/security/role/hunter_no_actions \ --d @${ROLE} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter_no_actions/post_detections_user.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter_no_actions/post_detections_user.sh deleted file mode 100755 index 4840cf3c903eb..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter_no_actions/post_detections_user.sh +++ /dev/null @@ -1,14 +0,0 @@ - -# -# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -# or more contributor license agreements. Licensed under the Elastic License -# 2.0; you may not use this file except in compliance with the Elastic License -# 2.0. -# - -USER=(${@:-./detections_user.json}) - -curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ - -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ - ${ELASTICSEARCH_URL}/_security/user/hunter_no_actions \ --d @${USER} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/index.ts deleted file mode 100644 index 7bcef506a6671..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/index.ts +++ /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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export * from './detections_admin'; -export * from './hunter'; -export * from './hunter_no_actions'; -export * from './platform_engineer'; -export * from './reader'; -export * from './rule_author'; -export * from './soc_manager'; -export * from './t1_analyst'; -export * from './t2_analyst'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/README.md b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/README.md deleted file mode 100644 index b9173c973abab..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/README.md +++ /dev/null @@ -1,5 +0,0 @@ -essentially a superuser for security solution - -| Role | Data Sources | Security Solution ML Jobs/Results | Lists | Rules/Exceptions | Action Connectors | Signals/Alerts | -| :------------------------------------------: | :----------: | :------------------: | :---: | :--------------: | :---------------: | :------------: | -| Platform Engineer (data ingest, cluster ops) | all | all | all | read, write | all | all | diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/delete_detections_user.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/delete_detections_user.sh deleted file mode 100755 index cb2b0467f44ca..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/delete_detections_user.sh +++ /dev/null @@ -1,11 +0,0 @@ - -# -# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -# or more contributor license agreements. Licensed under the Elastic License -# 2.0; you may not use this file except in compliance with the Elastic License -# 2.0. -# - -curl -v -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ - -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ --XDELETE ${ELASTICSEARCH_URL}/_security/user/platform_engineer diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/detections_role.json deleted file mode 100644 index 17b6e45f8c72d..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/detections_role.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "elasticsearch": { - "cluster": ["manage"], - "indices": [ - { - "names": [".lists*", ".items*"], - "privileges": ["all"] - }, - { - "names": [ - "apm-*-transaction*", - "traces-apm*", - "auditbeat-*", - "endgame-*", - "filebeat-*", - "logs-*", - "packetbeat-*", - "winlogbeat-*", - "metrics-endpoint.metadata_current_*", - ".fleet-agents*", - ".fleet-actions*" - ], - "privileges": ["all"] - }, - { - "names": [ - ".alerts-security*", - ".preview.alerts-security*", - ".internal.preview.alerts-security*", - ".siem-signals-*" - ], - "privileges": ["all"] - } - ] - }, - "kibana": [ - { - "feature": { - "ml": ["all"], - "siem": ["all", "read_alerts", "crud_alerts"], - "securitySolutionAssistant": ["all"], - "securitySolutionCases": ["all"], - "actions": ["all"], - "builtInAlerts": ["all"] - }, - "spaces": ["*"] - } - ] -} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/detections_user.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/detections_user.json deleted file mode 100644 index 8c4eab8b05e6e..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/detections_user.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "password": "changeme", - "roles": ["platform_engineer"], - "full_name": "platform engineer", - "email": "detections-reader@example.com" -} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/get_detections_role.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/get_detections_role.sh deleted file mode 100755 index 95fa058193b58..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/get_detections_role.sh +++ /dev/null @@ -1,11 +0,0 @@ - -# -# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -# or more contributor license agreements. Licensed under the Elastic License -# 2.0; you may not use this file except in compliance with the Elastic License -# 2.0. -# - -curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ - -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ --XGET ${KIBANA_URL}/api/security/role/platform_engineer | jq -S . diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/index.ts deleted file mode 100644 index c017c970af35f..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as platformEngineerUser from './detections_user.json'; -import * as platformEngineerRole from './detections_role.json'; -export { platformEngineerUser, platformEngineerRole }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/post_detections_role.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/post_detections_role.sh deleted file mode 100755 index 1272b309ca60b..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/post_detections_role.sh +++ /dev/null @@ -1,14 +0,0 @@ - -# -# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -# or more contributor license agreements. Licensed under the Elastic License -# 2.0; you may not use this file except in compliance with the Elastic License -# 2.0. -# - -ROLE=(${@:-./detections_role.json}) - -curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ - -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ --XPUT ${KIBANA_URL}/api/security/role/platform_engineer \ --d @${ROLE} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/post_detections_user.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/post_detections_user.sh deleted file mode 100755 index bc0f17f09455e..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/post_detections_user.sh +++ /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 -# 2.0; you may not use this file except in compliance with the Elastic License -# 2.0. -# - -USER=(${@:-./detections_user.json}) - - -curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ - -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ - ${ELASTICSEARCH_URL}/_security/user/platform_engineer \ --d @${USER} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/README.md b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/README.md deleted file mode 100644 index 313ccdd9478e2..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/README.md +++ /dev/null @@ -1,3 +0,0 @@ -| Role | Data Sources | Security Solution ML Jobs/Results | Lists | Rules/Exceptions | Actions Connectors | Signals/Alerts | -| :----: | :----------: | :-------------------------------: | :---: | :--------------: | :----------------: | :------------: | -| Reader | read | read | read | read | read | read | diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/delete_detections_user.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/delete_detections_user.sh deleted file mode 100755 index 57704f7abf0d3..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/delete_detections_user.sh +++ /dev/null @@ -1,11 +0,0 @@ - -# -# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -# or more contributor license agreements. Licensed under the Elastic License -# 2.0; you may not use this file except in compliance with the Elastic License -# 2.0. -# - -curl -v -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ - -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ --XDELETE ${ELASTICSEARCH_URL}/_security/user/reader diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/detections_role.json deleted file mode 100644 index 137091bc7f795..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/detections_role.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "elasticsearch": { - "cluster": [], - "indices": [ - { - "names" : [ - ".siem-signals-*", - ".alerts-security*", - ".lists*", - ".items*", - "metrics-endpoint.metadata_current_*", - ".fleet-agents*", - ".fleet-actions*" - ], - "privileges" : ["read"] - }, - { - "names": [ - "*" - ], - "privileges": ["read", "maintenance", "view_index_metadata"] - } - ] - }, - "kibana": [ - { - "feature": { - "ml": ["read"], - "siem": ["read", "read_alerts"], - "securitySolutionAssistant": ["none"], - "securitySolutionCases": ["read"], - "actions": ["read"], - "builtInAlerts": ["read"] - }, - "spaces": ["*"] - } - ] -} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/detections_user.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/detections_user.json deleted file mode 100644 index 25d514a1d738b..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/detections_user.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "password": "changeme", - "roles": ["reader"], - "full_name": "Reader", - "email": "detections-reader@example.com" -} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/get_detections_role.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/get_detections_role.sh deleted file mode 100755 index 37db6e10ced55..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/get_detections_role.sh +++ /dev/null @@ -1,11 +0,0 @@ - -# -# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -# or more contributor license agreements. Licensed under the Elastic License -# 2.0; you may not use this file except in compliance with the Elastic License -# 2.0. -# - -curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ - -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ --XGET ${KIBANA_URL}/api/security/role/reader | jq -S . diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/index.ts deleted file mode 100644 index bde1710e25aa1..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as readerUser from './detections_user.json'; -import * as readerRole from './detections_role.json'; -export { readerUser, readerRole }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/post_detections_role.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/post_detections_role.sh deleted file mode 100755 index 8805d641a8257..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/post_detections_role.sh +++ /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 -# 2.0; you may not use this file except in compliance with the Elastic License -# 2.0. -# - -# Uses a default if no argument is specified -ROLE=(${@:-./detections_role.json}) - -curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ - -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ --XPUT ${KIBANA_URL}/api/security/role/reader \ --d @${ROLE} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/post_detections_user.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/post_detections_user.sh deleted file mode 100755 index 8a93326a820b7..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/post_detections_user.sh +++ /dev/null @@ -1,14 +0,0 @@ - -# -# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -# or more contributor license agreements. Licensed under the Elastic License -# 2.0; you may not use this file except in compliance with the Elastic License -# 2.0. -# - -USER=(${@:-./detections_user.json}) - -curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ - -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ - ${ELASTICSEARCH_URL}/_security/user/reader \ --d @${USER} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/README.md b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/README.md deleted file mode 100644 index 1d2ef736f580c..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/README.md +++ /dev/null @@ -1,5 +0,0 @@ -rule author has the same privileges as hunter with the additional privileges of uploading value lists - -| Role | Data Sources | Security Solution ML Jobs/Results | Lists | Rules/Exceptions | Action Connectors | Signals/Alerts | -| :-----------------------------------------: | :----------: | :------------------: | :---------: | :--------------: | :---------------: | :------------------------------: | -| Rule Author / Manager / Detections Engineer | read, write | read | read, write | read, write | read | read, write, view_index_metadata | diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/delete_detections_user.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/delete_detections_user.sh deleted file mode 100755 index 112657b1b5b8a..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/delete_detections_user.sh +++ /dev/null @@ -1,11 +0,0 @@ - -# -# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -# or more contributor license agreements. Licensed under the Elastic License -# 2.0; you may not use this file except in compliance with the Elastic License -# 2.0. -# - -curl -v -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ - -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ --XDELETE ${ELASTICSEARCH_URL}/_security/user/rule_author diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/detections_role.json deleted file mode 100644 index dafe85548d4d0..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/detections_role.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "elasticsearch": { - "cluster": [], - "indices": [ - { - "names": [ - "apm-*-transaction*", - "traces-apm*", - "auditbeat-*", - "endgame-*", - "filebeat-*", - "logs-*", - "packetbeat-*", - "winlogbeat-*", - ".lists*", - ".items*" - ], - "privileges": ["read", "write"] - }, - { - "names": [ - ".alerts-security*", - ".preview.alerts-security*", - ".internal.preview.alerts-security*", - ".siem-signals-*" - ], - "privileges": ["read", "write", "maintenance", "view_index_metadata"] - }, - { - "names": ["metrics-endpoint.metadata_current_*", ".fleet-agents*", ".fleet-actions*"], - "privileges": ["read"] - } - ] - }, - "kibana": [ - { - "feature": { - "ml": ["read"], - "siem": ["all", "read_alerts", "crud_alerts"], - "securitySolutionAssistant": ["all"], - "securitySolutionCases": ["all"], - "actions": ["read"], - "builtInAlerts": ["all"] - }, - "spaces": ["*"] - } - ] -} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/detections_user.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/detections_user.json deleted file mode 100644 index ae08072b5890e..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/detections_user.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "password": "changeme", - "roles": ["rule_author"], - "full_name": "rule author", - "email": "detections-reader@example.com" -} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/get_detections_role.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/get_detections_role.sh deleted file mode 100755 index a4ab0a60400b6..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/get_detections_role.sh +++ /dev/null @@ -1,11 +0,0 @@ - -# -# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -# or more contributor license agreements. Licensed under the Elastic License -# 2.0; you may not use this file except in compliance with the Elastic License -# 2.0. -# - -curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ - -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ --XGET ${KIBANA_URL}/api/security/role/rule_author | jq -S . diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/index.ts deleted file mode 100644 index 90efa9179bd10..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as ruleAuthorUser from './detections_user.json'; -import * as ruleAuthorRole from './detections_role.json'; -export { ruleAuthorUser, ruleAuthorRole }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/post_detections_role.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/post_detections_role.sh deleted file mode 100755 index e78ae27fa1fbc..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/post_detections_role.sh +++ /dev/null @@ -1,14 +0,0 @@ - -# -# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -# or more contributor license agreements. Licensed under the Elastic License -# 2.0; you may not use this file except in compliance with the Elastic License -# 2.0. -# - -ROLE=(${@:-./detections_role.json}) - -curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ - -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ --XPUT ${KIBANA_URL}/api/security/role/rule_author \ --d @${ROLE} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/post_detections_user.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/post_detections_user.sh deleted file mode 100755 index 34b1f10ca6d47..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/post_detections_user.sh +++ /dev/null @@ -1,14 +0,0 @@ - -# -# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -# or more contributor license agreements. Licensed under the Elastic License -# 2.0; you may not use this file except in compliance with the Elastic License -# 2.0. -# - -USER=(${@:-./detections_user.json}) - -curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ - -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ - ${ELASTICSEARCH_URL}/_security/user/rule_author \ --d @${USER} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/README.md b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/README.md deleted file mode 100644 index fef99dfed2fbb..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/README.md +++ /dev/null @@ -1,5 +0,0 @@ -SOC Manager has all of the privileges of a rule author role with the additional privilege of managing the signals index. It can't create the signals index though. - -| Role | Data Sources | Security Solution ML Jobs/Results | Lists | Rules/Exceptions | Action Connectors | Signals/Alerts | -| :---------: | :----------: | :------------------: | :---------: | :--------------: | :---------------: | :-----------------: | -| SOC Manager | read, write | read | read, write | read, write | all | read, write, manage | diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/delete_detections_user.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/delete_detections_user.sh deleted file mode 100755 index 1bf103592b682..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/delete_detections_user.sh +++ /dev/null @@ -1,11 +0,0 @@ - -# -# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -# or more contributor license agreements. Licensed under the Elastic License -# 2.0; you may not use this file except in compliance with the Elastic License -# 2.0. -# - -curl -v -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ - -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ --XDELETE ${ELASTICSEARCH_URL}/_security/user/soc_manager diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/detections_role.json deleted file mode 100644 index 5e3aa868f6147..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/detections_role.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "elasticsearch": { - "cluster": [], - "indices": [ - { - "names": [ - "apm-*-transaction*", - "traces-apm*", - "auditbeat-*", - "endgame-*", - "filebeat-*", - "logs-*", - "packetbeat-*", - "winlogbeat-*", - ".lists*", - ".items*" - ], - "privileges": ["read", "write"] - }, - { - "names": [ - ".alerts-security*", - ".preview.alerts-security*", - ".internal.preview.alerts-security*", - ".siem-signals-*" - ], - "privileges": ["read", "write", "manage"] - }, - { - "names": ["metrics-endpoint.metadata_current_*", ".fleet-agents*", ".fleet-actions*"], - "privileges": ["read"] - } - ] - }, - "kibana": [ - { - "feature": { - "ml": ["read"], - "siem": ["all", "read_alerts", "crud_alerts"], - "securitySolutionAssistant": ["all"], - "securitySolutionCases": ["all"], - "actions": ["all"], - "builtInAlerts": ["all"] - }, - "spaces": ["*"] - } - ] -} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/detections_user.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/detections_user.json deleted file mode 100644 index 18c7cc2312bf5..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/detections_user.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "password": "changeme", - "roles": ["soc_manager"], - "full_name": "SOC manager", - "email": "detections-reader@example.com" -} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/get_detections_role.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/get_detections_role.sh deleted file mode 100755 index b6bf637bfc9d8..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/get_detections_role.sh +++ /dev/null @@ -1,11 +0,0 @@ - -# -# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -# or more contributor license agreements. Licensed under the Elastic License -# 2.0; you may not use this file except in compliance with the Elastic License -# 2.0. -# - -curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ - -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ --XGET ${KIBANA_URL}/api/security/role/soc_manager | jq -S . diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/index.ts deleted file mode 100644 index 4aea99753641d..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as socManagerUser from './detections_user.json'; -import * as socManagerRole from './detections_role.json'; -export { socManagerUser, socManagerRole }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/post_detections_role.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/post_detections_role.sh deleted file mode 100755 index bf7c19e2e3ab0..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/post_detections_role.sh +++ /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 -# 2.0; you may not use this file except in compliance with the Elastic License -# 2.0. -# - -ROLE=(${@:-./detections_role.json}) - - -curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ - -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ --XPUT ${KIBANA_URL}/api/security/role/soc_manager \ --d @${ROLE} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/post_detections_user.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/post_detections_user.sh deleted file mode 100755 index 447bf7ea7cb00..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/post_detections_user.sh +++ /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 -# 2.0; you may not use this file except in compliance with the Elastic License -# 2.0. -# - -USER=(${@:-./detections_user.json}) - - -curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ - -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ - ${ELASTICSEARCH_URL}/_security/user/soc_manager \ --d @${USER} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/README.md b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/README.md deleted file mode 100644 index 9ba0deba763aa..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/README.md +++ /dev/null @@ -1,3 +0,0 @@ -| Role | Data Sources | Security Solution ML Jobs/Results | Lists | Rules/Exceptions | Actions Connectors | Signals/Alerts | -| :--------: | :----------: | :------------------: | :---: | :--------------: | :----------------: | :------------: | -| T1 Analyst | read | read | none | read | read | read, write | diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/delete_detections_user.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/delete_detections_user.sh deleted file mode 100755 index d08b15e589bf1..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/delete_detections_user.sh +++ /dev/null @@ -1,11 +0,0 @@ - -# -# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -# or more contributor license agreements. Licensed under the Elastic License -# 2.0; you may not use this file except in compliance with the Elastic License -# 2.0. -# - -curl -v -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ - -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ --XDELETE ${ELASTICSEARCH_URL}/_security/user/t1_analyst diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/detections_role.json deleted file mode 100644 index d670fd9555f59..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/detections_role.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "elasticsearch": { - "cluster": [], - "indices": [ - { "names": [".alerts-security*", ".siem-signals-*"], "privileges": ["read", "write", "maintenance"] }, - { - "names": [ - "apm-*-transaction*", - "traces-apm*", - "auditbeat-*", - "endgame-*", - "filebeat-*", - "logs-*", - "packetbeat-*", - "winlogbeat-*", - "metrics-endpoint.metadata_current_*", - ".fleet-agents*", - ".fleet-actions*" - ], - "privileges": ["read"] - } - ] - }, - "kibana": [ - { - "feature": { - "ml": ["read"], - "siem": ["read", "read_alerts"], - "securitySolutionAssistant": ["all"], - "securitySolutionCases": ["read"], - "actions": ["read"], - "builtInAlerts": ["read"] - }, - "spaces": ["*"] - } - ] -} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/detections_user.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/detections_user.json deleted file mode 100644 index 203abec8ad433..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/detections_user.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "password": "changeme", - "roles": ["t1_analyst"], - "full_name": "T1 Analyst", - "email": "detections-reader@example.com" -} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/get_detections_role.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/get_detections_role.sh deleted file mode 100755 index bbf34ece0d6be..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/get_detections_role.sh +++ /dev/null @@ -1,11 +0,0 @@ - -# -# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -# or more contributor license agreements. Licensed under the Elastic License -# 2.0; you may not use this file except in compliance with the Elastic License -# 2.0. -# - -curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ - -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ --XGET ${KIBANA_URL}/api/security/role/t1_analyst | jq -S . diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/index.ts deleted file mode 100644 index 402b29c9ffde2..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t1AnalystUser from './detections_user.json'; -import * as t1AnalystRole from './detections_role.json'; -export { t1AnalystUser, t1AnalystRole }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/post_detections_role.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/post_detections_role.sh deleted file mode 100755 index c091b87f29153..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/post_detections_role.sh +++ /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 -# 2.0; you may not use this file except in compliance with the Elastic License -# 2.0. -# - -# Uses a default if no argument is specified -ROLE=(${@:-./detections_role.json}) - -curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ - -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ --XPUT ${KIBANA_URL}/api/security/role/t1_analyst \ --d @${ROLE} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/post_detections_user.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/post_detections_user.sh deleted file mode 100755 index 234ff7d005cf6..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/post_detections_user.sh +++ /dev/null @@ -1,14 +0,0 @@ - -# -# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -# or more contributor license agreements. Licensed under the Elastic License -# 2.0; you may not use this file except in compliance with the Elastic License -# 2.0. -# - -USER=(${@:-./detections_user.json}) - -curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ - -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ - ${ELASTICSEARCH_URL}/_security/user/t1_analyst \ --d @${USER} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/README.md b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/README.md deleted file mode 100644 index 3988e88870755..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/README.md +++ /dev/null @@ -1,5 +0,0 @@ -This role can view rules. Essentially there is no difference between a T1 and T2 analyst. - -| Role | Data Sources | Security Solution ML Jobs/Results | Lists | Rules/Exceptions | Action Connectors | Signals/Alerts | -| :--------: | :----------: | :------------------: | :---: | :--------------: | :---------------: | :------------: | -| T2 Analyst | read | read | read | read | read | read, write | diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/delete_detections_user.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/delete_detections_user.sh deleted file mode 100755 index 6dccb0d8c6067..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/delete_detections_user.sh +++ /dev/null @@ -1,11 +0,0 @@ - -# -# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -# or more contributor license agreements. Licensed under the Elastic License -# 2.0; you may not use this file except in compliance with the Elastic License -# 2.0. -# - -curl -v -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ - -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ --XDELETE ${ELASTICSEARCH_URL}/_security/user/t2_analyst diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/detections_role.json deleted file mode 100644 index 4db91de93709a..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/detections_role.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "elasticsearch": { - "cluster": [], - "indices": [ - { - "names": [".alerts-security*", ".siem-signals-*"], - "privileges": ["read", "write", "maintenance"] - }, - { - "names": [ - ".lists*", - ".items*", - "apm-*-transaction*", - "traces-apm*", - "auditbeat-*", - "endgame-*", - "filebeat-*", - "logs-*", - "packetbeat-*", - "winlogbeat-*", - "metrics-endpoint.metadata_current_*", - ".fleet-agents*", - ".fleet-actions*" - ], - "privileges": ["read"] - } - ] - }, - "kibana": [ - { - "feature": { - "ml": ["read"], - "siem": ["read", "read_alerts"], - "securitySolutionAssistant": ["all"], - "securitySolutionCases": ["read"], - "actions": ["read"], - "builtInAlerts": ["read"] - }, - "spaces": ["*"] - } - ] -} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/detections_user.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/detections_user.json deleted file mode 100644 index 3f5da2752314f..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/detections_user.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "password": "changeme", - "roles": ["t2_analyst"], - "full_name": "t2 analyst", - "email": "detections-reader@example.com" -} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/get_detections_role.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/get_detections_role.sh deleted file mode 100755 index ce9149d8b9fc7..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/get_detections_role.sh +++ /dev/null @@ -1,11 +0,0 @@ - -# -# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -# or more contributor license agreements. Licensed under the Elastic License -# 2.0; you may not use this file except in compliance with the Elastic License -# 2.0. -# - -curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ - -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ --XGET ${KIBANA_URL}/api/security/role/t2_analyst | jq -S . diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/index.ts deleted file mode 100644 index 5ca611d2ea075..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t2AnalystUser from './detections_user.json'; -import * as t2AnalystRole from './detections_role.json'; -export { t2AnalystUser, t2AnalystRole }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/post_detections_role.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/post_detections_role.sh deleted file mode 100755 index 4523b65b67cb7..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/post_detections_role.sh +++ /dev/null @@ -1,14 +0,0 @@ - -# -# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -# or more contributor license agreements. Licensed under the Elastic License -# 2.0; you may not use this file except in compliance with the Elastic License -# 2.0. -# - -ROLE=(${@:-./detections_role.json}) - -curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ - -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ --XPUT ${KIBANA_URL}/api/security/role/t2_analyst \ --d @${ROLE} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/post_detections_user.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/post_detections_user.sh deleted file mode 100755 index 3a901490515af..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/post_detections_user.sh +++ /dev/null @@ -1,14 +0,0 @@ - -# -# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -# or more contributor license agreements. Licensed under the Elastic License -# 2.0; you may not use this file except in compliance with the Elastic License -# 2.0. -# - -USER=(${@:-./detections_user.json}) - -curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ - -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ - ${ELASTICSEARCH_URL}/_security/user/t2_analyst \ --d @${USER} diff --git a/x-pack/plugins/security_solution/tsconfig.json b/x-pack/plugins/security_solution/tsconfig.json index 59acd2f3422cb..16ad154a95d83 100644 --- a/x-pack/plugins/security_solution/tsconfig.json +++ b/x-pack/plugins/security_solution/tsconfig.json @@ -9,6 +9,7 @@ "server/**/*", "scripts/**/*", // have to declare *.json explicitly due to https://github.com/microsoft/TypeScript/issues/25636 + "common/**/*.json", "server/**/*.json", "scripts/**/*.json", "public/**/*.json", diff --git a/x-pack/test/common/services/security_solution/roles_users_utils.ts b/x-pack/test/common/services/security_solution/roles_users_utils.ts index f8e18fadc992e..f88a8de03eaf0 100644 --- a/x-pack/test/common/services/security_solution/roles_users_utils.ts +++ b/x-pack/test/common/services/security_solution/roles_users_utils.ts @@ -5,32 +5,17 @@ * 2.0. */ -import { assertUnreachable } from '@kbn/security-solution-plugin/common/utility_types'; import { - t1AnalystUser, - t2AnalystUser, - hunterUser, - hunterNoActionsUser, - ruleAuthorUser, - socManagerUser, - platformEngineerUser, - detectionsAdminUser, - readerUser, - t1AnalystRole, - t2AnalystRole, - hunterRole, - hunterNoActionsRole, - ruleAuthorRole, - socManagerRole, - platformEngineerRole, - detectionsAdminRole, - readerRole, -} from '@kbn/security-solution-plugin/server/lib/detection_engine/scripts/roles_users'; - -import { ROLES } from '@kbn/security-solution-plugin/common/test'; + KNOWN_ESS_ROLE_DEFINITIONS, + KNOWN_SERVERLESS_ROLE_DEFINITIONS, +} from '@kbn/security-solution-plugin/common/test'; +import type { SecurityRoleName } from '@kbn/security-solution-plugin/common/test'; import { FtrProviderContext } from '../../ftr_provider_context'; -export { ROLES }; +const KNOWN_ROLE_DEFINITIONS = { + ...KNOWN_SERVERLESS_ROLE_DEFINITIONS, + ...KNOWN_ESS_ROLE_DEFINITIONS, +}; /** * creates a security solution centric role and a user (both having the same name) @@ -39,45 +24,18 @@ export { ROLES }; */ export const createUserAndRole = async ( getService: FtrProviderContext['getService'], - role: ROLES + role: SecurityRoleName ): Promise => { - switch (role) { - case ROLES.detections_admin: - return postRoleAndUser( - ROLES.detections_admin, - detectionsAdminRole, - detectionsAdminUser, - getService - ); - case ROLES.t1_analyst: - return postRoleAndUser(ROLES.t1_analyst, t1AnalystRole, t1AnalystUser, getService); - case ROLES.t2_analyst: - return postRoleAndUser(ROLES.t2_analyst, t2AnalystRole, t2AnalystUser, getService); - case ROLES.hunter: - return postRoleAndUser(ROLES.hunter, hunterRole, hunterUser, getService); - case ROLES.hunter_no_actions: - return postRoleAndUser( - ROLES.hunter_no_actions, - hunterNoActionsRole, - hunterNoActionsUser, - getService - ); - case ROLES.rule_author: - return postRoleAndUser(ROLES.rule_author, ruleAuthorRole, ruleAuthorUser, getService); - case ROLES.soc_manager: - return postRoleAndUser(ROLES.soc_manager, socManagerRole, socManagerUser, getService); - case ROLES.platform_engineer: - return postRoleAndUser( - ROLES.platform_engineer, - platformEngineerRole, - platformEngineerUser, - getService - ); - case ROLES.reader: - return postRoleAndUser(ROLES.reader, readerRole, readerUser, getService); - default: - return assertUnreachable(role); - } + const securityService = getService('security'); + const roleDefinition = KNOWN_ROLE_DEFINITIONS[role]; + + await securityService.role.create(role, roleDefinition); + await securityService.user.create(role, { + password: 'changeme', + roles: [role], + full_name: role, + email: 'detections-reader@example.com', + }); }; /** @@ -88,53 +46,9 @@ export const createUserAndRole = async ( */ export const deleteUserAndRole = async ( getService: FtrProviderContext['getService'], - roleName: ROLES + roleName: SecurityRoleName ): Promise => { const securityService = getService('security'); await securityService.user.delete(roleName); await securityService.role.delete(roleName); }; - -interface UserInterface { - password: string; - roles: string[]; - full_name: string; - email: string; -} - -interface RoleInterface { - elasticsearch: { - cluster: string[]; - indices: Array<{ - names: string[]; - privileges: string[]; - }>; - }; - kibana: Array<{ - feature: { - ml: string[]; - siem: string[]; - actions?: string[]; - builtInAlerts: string[]; - }; - spaces: string[]; - }>; -} - -export const postRoleAndUser = async ( - roleName: string, - role: RoleInterface, - user: UserInterface, - getService: FtrProviderContext['getService'] -): Promise => { - const securityService = getService('security'); - await securityService.role.create(roleName, { - kibana: role.kibana, - elasticsearch: role.elasticsearch, - }); - await securityService.user.create(roleName, { - password: 'changeme', - full_name: user.full_name, - roles: user.roles, - }); -}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/read_privileges.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/read_privileges.ts index fa5453f07d22f..b95c6771367f4 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/read_privileges.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/read_privileges.ts @@ -77,71 +77,6 @@ export default ({ getService }: FtrProviderContext) => { }); }); - it('should return expected privileges for a "reader" user', async () => { - await createUserAndRole(getService, ROLES.reader); - const { body } = await supertestWithoutAuth - .get(DETECTION_ENGINE_PRIVILEGES_URL) - .auth(ROLES.reader, 'changeme') - .send() - .expect(200); - expect(body).to.eql({ - username: 'reader', - has_all_requested: false, - cluster: { - monitor_ml: false, - manage_ccr: false, - manage_index_templates: false, - monitor_watcher: false, - monitor_transform: false, - read_ilm: false, - manage_api_key: false, - manage_security: false, - manage_own_api_key: false, - manage_saml: false, - all: false, - manage_ilm: false, - manage_ingest_pipelines: false, - read_ccr: false, - manage_rollup: false, - monitor: false, - manage_watcher: false, - manage: false, - manage_transform: false, - manage_token: false, - manage_ml: false, - manage_pipeline: false, - monitor_rollup: false, - transport_client: false, - create_snapshot: false, - }, - index: { - '.alerts-security.alerts-default': { - all: false, - manage_ilm: false, - read: true, - create_index: false, - read_cross_cluster: false, - index: false, - monitor: false, - delete: false, - manage: false, - delete_index: false, - create_doc: false, - view_index_metadata: true, - create: false, - manage_follow_index: false, - manage_leader_index: false, - maintenance: true, - write: false, - }, - }, - application: {}, - is_authenticated: true, - has_encryption_key: true, - }); - await deleteUserAndRole(getService, ROLES.reader); - }); - it('should return expected privileges for a "t1_analyst" user', async () => { await createUserAndRole(getService, ROLES.t1_analyst); const { body } = await supertestWithoutAuth diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/missing_privileges_callout.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/missing_privileges_callout.cy.ts index f38899300ed7f..d6b4aec5bf3ea 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/missing_privileges_callout.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_alerts/missing_privileges_callout.cy.ts @@ -24,8 +24,8 @@ import { import { ruleDetailsUrl } from '../../../urls/rule_details'; const loadPageAsReadOnlyUser = (url: string) => { - login(ROLES.reader); - visit(url, { role: ROLES.reader }); + login(ROLES.t1_analyst); + visit(url, { role: ROLES.t1_analyst }); waitForPageTitleToBeShown(); }; @@ -44,8 +44,7 @@ const waitForPageTitleToBeShown = () => { cy.get(PAGE_TITLE).should('be.visible'); }; -// TODO: https://github.com/elastic/kibana/issues/161539 -describe('Detections > Callouts', { tags: ['@ess', '@skipInServerless'] }, () => { +describe('Detections > Callouts', { tags: ['@ess', '@serverless'] }, () => { before(() => { // First, we have to open the app on behalf of a privileged user in order to initialize it. // Otherwise the app will be disabled and show a "welcome"-like page. diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/install_update_authorization.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/install_update_authorization.cy.ts index f95a4274a5181..052306817a87d 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/install_update_authorization.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/prebuilt_rules/install_update_authorization.cy.ts @@ -57,20 +57,18 @@ const RULE_2 = createRuleAssetSavedObject({ }); const loadPageAsReadOnlyUser = (url: string) => { - login(ROLES.reader); - visit(url, { role: ROLES.reader }); + login(ROLES.t1_analyst); + visit(url, { role: ROLES.t1_analyst }); }; const loginPageAsWriteAuthorizedUser = (url: string) => { - login(ROLES.hunter); - visit(url); + login(ROLES.t3_analyst); + visit(url, { role: ROLES.t3_analyst }); }; -// TODO: https://github.com/elastic/kibana/issues/164451 We should find a way to make this spec work in Serverless -// TODO: https://github.com/elastic/kibana/issues/161540 describe( 'Detection rules, Prebuilt Rules Installation and Update - Authorization/RBAC', - { tags: ['@ess', '@serverless', '@skipInServerless'] }, + { tags: ['@ess', '@serverless'] }, () => { beforeEach(() => { preventPrebuiltRulesPackageInstallation(); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/authorization/all_rules_read_only.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/authorization/all_rules_read_only.cy.ts index 9b7ef2a116b30..51fdcd6c242f1 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/authorization/all_rules_read_only.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/authorization/all_rules_read_only.cy.ts @@ -34,8 +34,8 @@ describe('All rules - read only', { tags: ['@ess', '@serverless', '@skipInServer }); beforeEach(() => { - login(ROLES.reader); - visitRulesManagementTable(ROLES.reader); + login(ROLES.t1_analyst); + visitRulesManagementTable(ROLES.t1_analyst); cy.get(RULE_NAME).should('have.text', getNewRule().name); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/read_only_view.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/read_only_view.cy.ts index fafbb2232e55b..935668db1a5a6 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/read_only_view.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/read_only_view.cy.ts @@ -54,8 +54,8 @@ describe('Exceptions viewer read only', { tags: ['@ess'] }, () => { ); }); - login(ROLES.reader); - visitRulesManagementTable(ROLES.reader); + login(ROLES.t1_analyst); + visitRulesManagementTable(ROLES.t1_analyst); goToRuleDetailsOf('Test exceptions rule'); goToExceptionsTab(); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/read_only.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/read_only.cy.ts index b0ba8beae3821..b115508e2b598 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/read_only.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/read_only.cy.ts @@ -31,8 +31,8 @@ describe('Shared exception lists - read only', { tags: ['@ess', '@skipInServerle // Create exception list not used by any rules createExceptionList(getExceptionList(), getExceptionList().list_id); - login(ROLES.reader); - visit(EXCEPTIONS_URL, { role: ROLES.reader }); + login(ROLES.t1_analyst); + visit(EXCEPTIONS_URL, { role: ROLES.t1_analyst }); // Using cy.contains because we do not care about the exact text, // just checking number of lists shown diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/attach_alert_to_case.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/attach_alert_to_case.cy.ts index f10681a516146..83f31f35bc7ad 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/attach_alert_to_case.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/attach_alert_to_case.cy.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { ROLES } from '@kbn/security-solution-plugin/common/test'; +import { ROLES, SecurityRoleName } from '@kbn/security-solution-plugin/common/test'; import { getNewRule } from '../../../objects/rule'; @@ -19,7 +19,7 @@ import { ALERTS_URL } from '../../../urls/navigation'; import { ATTACH_ALERT_TO_CASE_BUTTON, TIMELINE_CONTEXT_MENU_BTN } from '../../../screens/alerts'; import { LOADING_INDICATOR } from '../../../screens/security_header'; -const loadDetectionsPage = (role: ROLES) => { +const loadDetectionsPage = (role: SecurityRoleName) => { login(role); visit(ALERTS_URL, { role }); waitForAlertsToPopulate(); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/creation.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/creation.cy.ts index 79730b3c45854..6e66299f5d42a 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/creation.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/creation.cy.ts @@ -53,6 +53,7 @@ import { login } from '../../../tasks/login'; import { visit, visitWithTimeRange } from '../../../tasks/navigation'; import { CASES_URL, OVERVIEW_URL } from '../../../urls/navigation'; +import { ELASTICSEARCH_USERNAME } from '../../../env_var_names_constants'; // Tracked by https://github.com/elastic/security-team/issues/7696 describe('Cases', { tags: ['@ess', '@serverless'] }, () => { @@ -107,10 +108,10 @@ describe('Cases', { tags: ['@ess', '@serverless'] }, () => { ); cy.get(CASE_DETAILS_USERNAMES) .eq(REPORTER) - .should('have.text', Cypress.env('ELASTICSEARCH_USERNAME')); + .should('have.text', Cypress.env(ELASTICSEARCH_USERNAME)); cy.get(CASE_DETAILS_USERNAMES) .eq(PARTICIPANTS) - .should('have.text', Cypress.env('ELASTICSEARCH_USERNAME')); + .should('have.text', Cypress.env(ELASTICSEARCH_USERNAME)); cy.get(CASE_DETAILS_TAGS).should('have.text', expectedTags); EXPECTED_METRICS.forEach((metric) => { diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/creation.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/creation.cy.ts index b7236d7ea0d80..9ffeade285dc7 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/creation.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/creation.cy.ts @@ -88,8 +88,8 @@ describe('Timelines', (): void => { context('Privileges: READ', { tags: '@ess' }, () => { beforeEach(() => { - login(ROLES.reader); - visitWithTimeRange(OVERVIEW_URL, { role: ROLES.reader }); + login(ROLES.t1_analyst); + visitWithTimeRange(OVERVIEW_URL, { role: ROLES.t1_analyst }); }); it('should not be able to create/update timeline ', () => { diff --git a/x-pack/test/security_solution_cypress/cypress/env_var_names_constants.ts b/x-pack/test/security_solution_cypress/cypress/env_var_names_constants.ts new file mode 100644 index 0000000000000..20f44653f72f0 --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/env_var_names_constants.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/** + * The `CYPRESS_ELASTICSEARCH_USERNAME` environment variable specifies the + * username to be used when authenticating with Kibana + */ +export const ELASTICSEARCH_USERNAME = 'ELASTICSEARCH_USERNAME'; + +/** + * The `CYPRESS_ELASTICSEARCH_PASSWORD` environment variable specifies the + * username to be used when authenticating with Kibana + */ +export const ELASTICSEARCH_PASSWORD = 'ELASTICSEARCH_PASSWORD'; + +/** + * The `IS_SERVERLESS` environment variable specifies wether the currently running + * environment is serverless snapshot. + */ +export const IS_SERVERLESS = 'IS_SERVERLESS'; + +/** + * The `CLOUD_SERVERLESS` environment variable specifies wether the currently running + * environment is a real MKI. + */ +export const CLOUD_SERVERLESS = 'CLOUD_SERVERLESS'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/index.ts b/x-pack/test/security_solution_cypress/cypress/support/cypress_grep.d.ts similarity index 63% rename from x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/index.ts rename to x-pack/test/security_solution_cypress/cypress/support/cypress_grep.d.ts index 3411589de7721..d771f32f48672 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/index.ts +++ b/x-pack/test/security_solution_cypress/cypress/support/cypress_grep.d.ts @@ -5,6 +5,8 @@ * 2.0. */ -import * as hunterUser from './detections_user.json'; -import * as hunterRole from './detections_role.json'; -export { hunterUser, hunterRole }; +declare module '@cypress/grep' { + function registerCypressGrep(): void; + + export = registerCypressGrep; +} diff --git a/x-pack/test/security_solution_cypress/cypress/support/e2e.js b/x-pack/test/security_solution_cypress/cypress/support/e2e.js deleted file mode 100644 index 4335470845f9b..0000000000000 --- a/x-pack/test/security_solution_cypress/cypress/support/e2e.js +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -// *********************************************************** -// This example support/index.js is processed and -// loaded automatically before your test files. -// -// This is a great place to put global configuration and -// behavior that modifies Cypress. -// -// You can change the location of this file or turn off -// automatically serving support files with the -// 'supportFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/configuration -// *********************************************************** - -// Import commands.js using ES2015 syntax: -import './commands'; -import 'cypress-real-events/support'; -import registerCypressGrep from '@cypress/grep'; - -before(() => { - cy.task('esArchiverLoad', { archiveName: 'auditbeat' }); -}); - -registerCypressGrep(); - -Cypress.on('uncaught:exception', () => { - return false; -}); - -// Alternatively you can use CommonJS syntax: -// require('./commands') diff --git a/x-pack/test/security_solution_cypress/cypress/support/e2e.ts b/x-pack/test/security_solution_cypress/cypress/support/e2e.ts new file mode 100644 index 0000000000000..eb3488178485f --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/support/e2e.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import './commands'; +import 'cypress-real-events/support'; +import registerCypressGrep from '@cypress/grep'; +import { + KNOWN_ESS_ROLE_DEFINITIONS, + KNOWN_SERVERLESS_ROLE_DEFINITIONS, +} from '@kbn/security-solution-plugin/common/test'; +import { setupUsers } from './setup_users'; +import { CLOUD_SERVERLESS, IS_SERVERLESS } from '../env_var_names_constants'; + +before(() => { + cy.task('esArchiverLoad', { archiveName: 'auditbeat' }); +}); + +if (!Cypress.env(IS_SERVERLESS) && !Cypress.env(CLOUD_SERVERLESS)) { + // Create Serverless + ESS roles and corresponding users. This helps to seamlessly reuse tests + // between ESS and Serverless having all the necessary users set up. + before(() => { + const KNOWN_ROLE_DEFINITIONS = [ + ...Object.values(KNOWN_SERVERLESS_ROLE_DEFINITIONS), + ...Object.values(KNOWN_ESS_ROLE_DEFINITIONS), + ]; + + setupUsers(KNOWN_ROLE_DEFINITIONS); + }); +} + +registerCypressGrep(); + +Cypress.on('uncaught:exception', () => { + return false; +}); diff --git a/x-pack/test/security_solution_cypress/cypress/support/setup_users.ts b/x-pack/test/security_solution_cypress/cypress/support/setup_users.ts new file mode 100644 index 0000000000000..e1dc4c952eac7 --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/support/setup_users.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Role } from '@kbn/security-plugin/common'; +import { rootRequest } from '../tasks/common'; + +/** + * Utility function creates roles and corresponding users per each role with names + * matching role names. Each user gets the same `password` passed in which is + * `changeme` by default. + * + * @param roles an array of security `Role`s + * @param password custom password if `changeme` doesn't fit + */ +export function setupUsers(roles: Role[], password = 'changeme'): void { + for (const role of roles) { + createRole(role); + createUser(role.name, password, [role.name]); + } +} + +function createRole(role: Role): void { + const { name: roleName, ...roleDefinition } = role; + + rootRequest({ + method: 'PUT', + url: `/api/security/role/${roleName}`, + body: roleDefinition, + }); +} + +function createUser(username: string, password: string, roles: string[] = []): void { + const user = { + username, + password, + roles, + full_name: username, + email: '', + }; + + rootRequest({ + method: 'POST', + url: `/internal/security/users/${username}`, + body: user, + }); +} diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/common.ts b/x-pack/test/security_solution_cypress/cypress/tasks/common.ts index f42916db561f5..3337d122ab936 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/common.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/common.ts @@ -11,6 +11,7 @@ import { KIBANA_LOADING_ICON } from '../screens/security_header'; import { EUI_BASIC_TABLE_LOADING } from '../screens/common/controls'; import { deleteAllDocuments } from './api_calls/elasticsearch'; import { DEFAULT_ALERTS_INDEX_PATTERN } from './api_calls/alerts'; +import { ELASTICSEARCH_PASSWORD, ELASTICSEARCH_USERNAME } from '../env_var_names_constants'; const primaryButton = 0; @@ -21,11 +22,14 @@ const primaryButton = 0; const dndSloppyClickDetectionThreshold = 5; export const API_AUTH = Object.freeze({ - user: Cypress.env('ELASTICSEARCH_USERNAME'), - pass: Cypress.env('ELASTICSEARCH_PASSWORD'), + user: Cypress.env(ELASTICSEARCH_USERNAME), + pass: Cypress.env(ELASTICSEARCH_PASSWORD), }); -export const API_HEADERS = Object.freeze({ 'kbn-xsrf': 'cypress' }); +export const API_HEADERS = Object.freeze({ + 'kbn-xsrf': 'cypress-creds', + 'x-elastic-internal-origin': 'security-solution', +}); export const rootRequest = ( options: Partial diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/edit_rule.ts b/x-pack/test/security_solution_cypress/cypress/tasks/edit_rule.ts index fa7e2bd175dc0..0f3d9ee86529d 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/edit_rule.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/edit_rule.ts @@ -5,12 +5,12 @@ * 2.0. */ -import { ROLES } from '@kbn/security-solution-plugin/common/test'; +import type { SecurityRoleName } from '@kbn/security-solution-plugin/common/test'; import { BACK_TO_RULE_DETAILS, EDIT_SUBMIT_BUTTON } from '../screens/edit_rule'; import { editRuleUrl } from '../urls/edit_rule'; import { visit } from './navigation'; -export function visitEditRulePage(ruleId: string, role?: ROLES): void { +export function visitEditRulePage(ruleId: string, role?: SecurityRoleName): void { visit(editRuleUrl(ruleId), { role }); } diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/login.ts b/x-pack/test/security_solution_cypress/cypress/tasks/login.ts index e01b80e7c1f06..26702a47c9427 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/login.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/login.ts @@ -8,10 +8,17 @@ import * as yaml from 'js-yaml'; import type { UrlObject } from 'url'; import Url from 'url'; - -import type { ROLES } from '@kbn/security-solution-plugin/common/test'; import { LoginState } from '@kbn/security-plugin/common/login_state'; +import type { SecurityRoleName } from '@kbn/security-solution-plugin/common/test'; +import { KNOWN_SERVERLESS_ROLE_DEFINITIONS } from '@kbn/security-solution-plugin/common/test'; import { LOGOUT_URL } from '../urls/navigation'; +import { rootRequest } from './common'; +import { + CLOUD_SERVERLESS, + ELASTICSEARCH_PASSWORD, + ELASTICSEARCH_USERNAME, + IS_SERVERLESS, +} from '../env_var_names_constants'; /** * Credentials in the `kibana.dev.yml` config file will be used to authenticate @@ -32,16 +39,33 @@ const ELASTICSEARCH_USERNAME_CONFIG_PATH = 'config.elasticsearch.username'; const ELASTICSEARCH_PASSWORD_CONFIG_PATH = 'config.elasticsearch.password'; /** - * The `CYPRESS_ELASTICSEARCH_USERNAME` environment variable specifies the - * username to be used when authenticating with Kibana + * Authenticates with Kibana using, if specified, credentials specified by + * environment variables. The credentials in `kibana.dev.yml` will be used + * for authentication when the environment variables are unset. + * + * To speed the execution of tests, prefer this non-interactive authentication, + * which is faster than authentication via Kibana's interactive login page. */ -const ELASTICSEARCH_USERNAME = 'ELASTICSEARCH_USERNAME'; +export const login = (role?: SecurityRoleName): void => { + if (role != null) { + loginWithRole(role); + } else if (credentialsProvidedByEnvironment()) { + loginViaEnvironmentCredentials(); + } else { + loginViaConfig(); + } +}; -/** - * The `CYPRESS_ELASTICSEARCH_PASSWORD` environment variable specifies the - * username to be used when authenticating with Kibana - */ -const ELASTICSEARCH_PASSWORD = 'ELASTICSEARCH_PASSWORD'; +export interface User { + username: string; + password: string; +} + +export const loginWithUser = (user: User): void => { + cy.session(user, () => { + loginWithUsernameAndPassword(user.username, user.password); + }); +}; /** * cy.visit will default to the baseUrl which uses the default kibana test user @@ -51,7 +75,7 @@ const ELASTICSEARCH_PASSWORD = 'ELASTICSEARCH_PASSWORD'; * @param role string role/user to log in with * @param route string route to visit */ -export const getUrlWithRoute = (role: ROLES, route: string) => { +export const getUrlWithRoute = (role: SecurityRoleName, route: string): string => { const url = Cypress.config().baseUrl; const kibana = new URL(String(url)); const theUrl = `${Url.format({ @@ -66,18 +90,13 @@ export const getUrlWithRoute = (role: ROLES, route: string) => { return theUrl; }; -export interface User { - username: string; - password: string; -} - /** * Builds a URL with basic auth using the passed in user. * * @param user the user information to build the basic auth with * @param route string route to visit */ -export const constructUrlWithUser = (user: User, route: string) => { +export const constructUrlWithUser = (user: User, route: string): string => { const url = Cypress.config().baseUrl; const kibana = new URL(String(url)); const hostname = kibana.hostname; @@ -94,103 +113,27 @@ export const constructUrlWithUser = (user: User, route: string) => { return builtUrl.href; }; -const getCurlScriptEnvVars = () => ({ - ELASTICSEARCH_URL: Cypress.env('ELASTICSEARCH_URL'), - ELASTICSEARCH_USERNAME: Cypress.env('ELASTICSEARCH_USERNAME'), - ELASTICSEARCH_PASSWORD: Cypress.env('ELASTICSEARCH_PASSWORD'), - KIBANA_URL: Cypress.config().baseUrl, -}); - -const postRoleAndUser = (role: ROLES) => { - const env = getCurlScriptEnvVars(); - const detectionsRoleScriptPath = `../../plugins/security_solution/server/lib/detection_engine/scripts/roles_users/${role}/post_detections_role.sh`; - const detectionsRoleJsonPath = `../../plugins/security_solution/server/lib/detection_engine/scripts/roles_users/${role}/detections_role.json`; - const detectionsUserScriptPath = `../../plugins/security_solution/server/lib/detection_engine/scripts/roles_users/${role}/post_detections_user.sh`; - const detectionsUserJsonPath = `../../plugins/security_solution/server/lib/detection_engine/scripts/roles_users/${role}/detections_user.json`; - - // post the role - cy.exec(`bash ${detectionsRoleScriptPath} ${detectionsRoleJsonPath}`, { - env, - }); - - // post the user associated with the role to elasticsearch - cy.exec(`bash ${detectionsUserScriptPath} ${detectionsUserJsonPath}`, { - env, - }); -}; - -export const deleteRoleAndUser = (role: ROLES) => { - const env = getCurlScriptEnvVars(); - const detectionsUserDeleteScriptPath = `../../plugins/security_solution/server/lib/detection_engine/scripts/roles_users/${role}/delete_detections_user.sh`; - - // delete the role - cy.exec(`bash ${detectionsUserDeleteScriptPath}`, { - env, - }); -}; - -const loginWithUsernameAndPassword = (username: string, password: string) => { - const baseUrl = Cypress.config().baseUrl; - if (!baseUrl) { - throw Error(`Cypress config baseUrl not set!`); +/** + * Authenticates with a predefined role + * + * @param role role name + */ +const loginWithRole = (role: SecurityRoleName): void => { + if ( + (Cypress.env(IS_SERVERLESS) || Cypress.env(CLOUD_SERVERLESS)) && + !(role in KNOWN_SERVERLESS_ROLE_DEFINITIONS) + ) { + throw new Error(`An attempt to log in with unsupported by Serverless role "${role}".`); } - // Programmatically authenticate without interacting with the Kibana login page. - const headers = { 'kbn-xsrf': 'cypress-creds', 'x-elastic-internal-origin': 'security-solution' }; - cy.request({ headers, url: `${baseUrl}/internal/security/login_state` }).then( - (loginState) => { - const basicProvider = loginState.body.selector.providers.find( - (provider) => provider.type === 'basic' - ); - - return cy.request({ - url: `${baseUrl}/internal/security/login`, - method: 'POST', - headers, - body: { - providerType: basicProvider?.type, - providerName: basicProvider?.name, - currentURL: '/', - params: { username, password }, - }, - }); - } - ); -}; - -export const loginWithUser = (user: User) => { - cy.session(user, () => { - loginWithUsernameAndPassword(user.username, user.password); - }); -}; - -const loginWithRole = (role: ROLES) => { - postRoleAndUser(role); + const password = 'changeme'; cy.log(`origin: ${Cypress.config().baseUrl}`); cy.session(role, () => { - loginWithUsernameAndPassword(role, 'changeme'); + loginWithUsernameAndPassword(role, password); }); }; -/** - * Authenticates with Kibana using, if specified, credentials specified by - * environment variables. The credentials in `kibana.dev.yml` will be used - * for authentication when the environment variables are unset. - * - * To speed the execution of tests, prefer this non-interactive authentication, - * which is faster than authentication via Kibana's interactive login page. - */ -export const login = (role?: ROLES) => { - if (role != null) { - loginWithRole(role); - } else if (credentialsProvidedByEnvironment()) { - loginViaEnvironmentCredentials(); - } else { - loginViaConfig(); - } -}; - /** * Returns `true` if the credentials used to login to Kibana are provided * via environment variables @@ -204,7 +147,7 @@ const credentialsProvidedByEnvironment = (): boolean => * environment variables, and POSTing the username and password directly to * Kibana's `/internal/security/login` endpoint, bypassing the login page (for speed). */ -const loginViaEnvironmentCredentials = () => { +const loginViaEnvironmentCredentials = (): void => { cy.log( `Authenticating via environment credentials from the \`CYPRESS_${ELASTICSEARCH_USERNAME}\` and \`CYPRESS_${ELASTICSEARCH_PASSWORD}\` environment variables` ); @@ -222,7 +165,7 @@ const loginViaEnvironmentCredentials = () => { * `kibana.dev.yml` file and POSTing the username and password directly to * Kibana's `/internal/security/login` endpoint, bypassing the login page (for speed). */ -const loginViaConfig = () => { +const loginViaConfig = (): void => { cy.log( `Authenticating via config credentials \`${ELASTICSEARCH_USERNAME_CONFIG_PATH}\` and \`${ELASTICSEARCH_PASSWORD_CONFIG_PATH}\` from \`${KIBANA_DEV_YML_PATH}\`` ); @@ -256,6 +199,33 @@ export const getEnvAuth = (): User => { } }; -export const logout = () => { +export const logout = (): void => { cy.visit(LOGOUT_URL); }; + +const loginWithUsernameAndPassword = (username: string, password: string): void => { + const baseUrl = Cypress.config().baseUrl; + if (!baseUrl) { + throw Error(`Cypress config baseUrl not set!`); + } + + // Programmatically authenticate without interacting with the Kibana login page. + rootRequest({ + url: `${baseUrl}/internal/security/login_state`, + }).then((loginState) => { + const basicProvider = loginState.body.selector.providers.find( + (provider) => provider.type === 'basic' + ); + + return rootRequest({ + url: `${baseUrl}/internal/security/login`, + method: 'POST', + body: { + providerType: basicProvider?.type, + providerName: basicProvider?.name, + currentURL: '/', + params: { username, password }, + }, + }); + }); +}; diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/navigation.ts b/x-pack/test/security_solution_cypress/cypress/tasks/navigation.ts index dc12c26d1f9c9..20e34387d7f12 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/navigation.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/navigation.ts @@ -7,8 +7,8 @@ import { encode } from '@kbn/rison'; -import type { ROLES } from '@kbn/security-solution-plugin/common/test'; import { NEW_FEATURES_TOUR_STORAGE_KEYS } from '@kbn/security-solution-plugin/common/constants'; +import type { SecurityRoleName } from '@kbn/security-solution-plugin/common/test'; import { hostDetailsUrl, userDetailsUrl } from '../urls/navigation'; import { constructUrlWithUser, getUrlWithRoute, User } from './login'; @@ -16,7 +16,7 @@ export const visit = ( url: string, options?: { visitOptions?: Partial; - role?: ROLES; + role?: SecurityRoleName; } ) => { cy.visit(options?.role ? getUrlWithRoute(options.role, url) : url, { @@ -35,7 +35,7 @@ export const visitWithTimeRange = ( url: string, options?: { visitOptions?: Partial; - role?: ROLES; + role?: SecurityRoleName; } ) => { const timerangeConfig = { @@ -74,7 +74,7 @@ export const visitWithTimeRange = ( }); }; -export const visitTimeline = (timelineId: string, role?: ROLES) => { +export const visitTimeline = (timelineId: string, role?: SecurityRoleName) => { const route = `/app/security/timelines?timeline=(id:'${timelineId}',isOpen:!t)`; cy.visit(role ? getUrlWithRoute(role, route) : route, { onBeforeLoad: disableNewFeaturesTours, diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/rule_details.ts b/x-pack/test/security_solution_cypress/cypress/tasks/rule_details.ts index a81be48d229e9..1dadb67a96987 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/rule_details.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/rule_details.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ROLES } from '@kbn/security-solution-plugin/common/test'; +import type { SecurityRoleName } from '@kbn/security-solution-plugin/common/test'; import type { Exception } from '../objects/exception'; import { RULE_MANAGEMENT_PAGE_BREADCRUMB } from '../screens/breadcrumbs'; import { PAGE_CONTENT_SPINNER } from '../screens/common/page'; @@ -47,7 +47,7 @@ import { visit } from './navigation'; interface VisitRuleDetailsPageOptions { tab?: RuleDetailsTabs; - role?: ROLES; + role?: SecurityRoleName; } export function visitRuleDetailsPage(ruleId: string, options?: VisitRuleDetailsPageOptions): void { diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/rules_management.ts b/x-pack/test/security_solution_cypress/cypress/tasks/rules_management.ts index 5f795ce97d524..663692aa905d4 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/rules_management.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/rules_management.ts @@ -5,13 +5,13 @@ * 2.0. */ -import type { ROLES } from '@kbn/security-solution-plugin/common/test'; +import type { SecurityRoleName } from '@kbn/security-solution-plugin/common/test'; import { LAST_BREADCRUMB, RULE_MANAGEMENT_PAGE_BREADCRUMB } from '../screens/breadcrumbs'; import { RULES_MANAGEMENT_URL } from '../urls/rules_management'; import { resetRulesTableState } from './common'; import { visit } from './navigation'; -export function visitRulesManagementTable(role?: ROLES): void { +export function visitRulesManagementTable(role?: SecurityRoleName): void { resetRulesTableState(); // Clear persistent rules filter data before page loading visit(RULES_MANAGEMENT_URL, { role }); } diff --git a/x-pack/test/security_solution_cypress/cypress/tsconfig.json b/x-pack/test/security_solution_cypress/cypress/tsconfig.json index b82ce28aa8f04..ff33f1ac69a13 100644 --- a/x-pack/test/security_solution_cypress/cypress/tsconfig.json +++ b/x-pack/test/security_solution_cypress/cypress/tsconfig.json @@ -38,6 +38,6 @@ "@kbn/lists-plugin", "@kbn/securitysolution-list-constants", "@kbn/security-plugin", - "@kbn/management-settings-ids" + "@kbn/management-settings-ids", ] } diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_permissions.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_permissions.ts index 6e1850373af81..51d6ae8bcfadc 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_permissions.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_permissions.ts @@ -7,12 +7,9 @@ import expect from '@kbn/expect'; import { IndexedHostsAndAlertsResponse } from '@kbn/security-solution-plugin/common/endpoint/index_data'; +import { SecurityRoleName } from '@kbn/security-solution-plugin/common/test'; import { FtrProviderContext } from '../../ftr_provider_context'; -import { - createUserAndRole, - deleteUserAndRole, - ROLES, -} from '../../../common/services/security_solution'; +import { createUserAndRole, deleteUserAndRole } from '../../../common/services/security_solution'; export default ({ getPageObjects, getService }: FtrProviderContext) => { const PageObjects = getPageObjects(['security', 'endpoint', 'detections', 'hosts']); @@ -35,11 +32,22 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); // Run the same set of tests against all of the Security Solution roles - for (const role of Object.keys(ROLES) as Array) { + const ROLES: SecurityRoleName[] = [ + 't1_analyst', + 't2_analyst', + 'rule_author', + 'soc_manager', + 'detections_admin', + 'platform_engineer', + 'hunter', + 'hunter_no_actions', + ]; + + for (const role of ROLES) { describe(`when running with user/role [${role}]`, () => { before(async () => { // create role/user - await createUserAndRole(getService, ROLES[role]); + await createUserAndRole(getService, role); // log back in with new uer await PageObjects.security.login(role, 'changeme'); @@ -51,7 +59,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await PageObjects.security.forceLogout(); // delete role/user - await deleteUserAndRole(getService, ROLES[role]); + await deleteUserAndRole(getService, role); }); it('should NOT allow access to endpoint management pages', async () => { From f4e380794003676ae38f0706adc5445b81d2b950 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yulia=20=C4=8Cech?= <6585477+yuliacech@users.noreply.github.com> Date: Tue, 31 Oct 2023 17:56:29 +0100 Subject: [PATCH 12/21] [Index Management] Implement index overview cards (#168153) ## Summary This PR implements a new design for the Overview tab on the index details page. There are 4 cards, but only 3 can be displayed at one time: Storage, Status and Aliases/Data Stream (a backing index of the data stream can't have any aliases). On Serverless, there is no Storage and Status cards, so the whole section is either empty or 1 card is displayed for Aliases or Data Stream. ### Screenshots #### (Stateful) Index without aliases and data stream Screenshot 2023-10-27 at 17 25 56 #### (Stateful) Index with aliases Screenshot 2023-10-27 at 17 27 05 Aliases flyout Screenshot 2023-10-27 at 17 27 39 #### (Stateful) Backing index of a data stream Screenshot 2023-10-27 at 17 28 16 #### (Serverless) Index without cards Screenshot 2023-10-09 at 16 16 04 #### (Serverless) Index with aliases Screenshot 2023-10-09 at 16 16 34 #### (Serverless) Backing index of a data stream Screenshot 2023-10-24 at 17 29 53 ### Screen recordings #### (Stateful) Index with aliases https://github.com/elastic/kibana/assets/6585477/c44dc6a7-dc2b-4297-88ae-1ecc22f828da #### (Stateful) Backing index of a data stream https://github.com/elastic/kibana/assets/6585477/5206e427-b373-4ec8-a2ca-253b125afaee ### How to test - Start either a stateful or serverless ES and Kibana: `yarn es snapshot` and `yarn start` or `yarn es serverless --ssl` and `yarn serverless-es --ssl` - Use following commands in Dev Tools Console to create an index and add some aliases ``` PUT /test_index POST _aliases { "actions": [ { "add": { "index": "test_index", "alias": "test_alias_1" } } ] } ``` - Use following commdans in Dev Tools to create a data stream ``` PUT _index_template/test-index-template { "index_patterns": ["test-data-stream*"], "data_stream": { }, "priority": 500 } PUT _data_stream/test-data-stream ``` ### Checklist Delete any items that are not applicable to this PR. - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Ignacio Rivas --- .../index_details_page.helpers.ts | 43 ++++- .../index_details_page.test.tsx | 160 +++++++++++++++++- .../index_details_page/mocks.ts | 4 +- .../index_list/details_page/details_page.tsx | 4 + .../details_page_overview/aliases_details.tsx | 123 ++++++++++++++ .../data_stream_details.tsx | 154 +++++++++++++++++ .../details_page_overview.tsx | 121 +++---------- .../extensions_summary.tsx | 4 +- .../details_page_overview/overview_card.tsx | 67 ++++++++ .../details_page_overview/status_details.tsx | 120 +++++++++++++ .../details_page_overview/storage_details.tsx | 109 ++++++++++++ x-pack/plugins/index_management/tsconfig.json | 1 + 12 files changed, 799 insertions(+), 111 deletions(-) create mode 100644 x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/aliases_details.tsx create mode 100644 x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/data_stream_details.tsx create mode 100644 x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/overview_card.tsx create mode 100644 x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/status_details.tsx create mode 100644 x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/storage_details.tsx diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.helpers.ts index 231c47515c2cc..cd0c81715cbe9 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.helpers.ts @@ -78,8 +78,15 @@ export interface IndexDetailsPageTestBed extends TestBed { isWarningDisplayed: () => boolean; }; overview: { - indexStatsContentExists: () => boolean; - indexDetailsContentExists: () => boolean; + storageDetailsExist: () => boolean; + getStorageDetailsContent: () => string; + statusDetailsExist: () => boolean; + getStatusDetailsContent: () => string; + aliasesDetailsExist: () => boolean; + getAliasesDetailsContent: () => string; + dataStreamDetailsExist: () => boolean; + getDataStreamDetailsContent: () => string; + reloadDataStreamDetails: () => Promise; addDocCodeBlockExists: () => boolean; extensionSummaryExists: (index: number) => boolean; }; @@ -138,11 +145,35 @@ export const setup = async ({ }; const overview = { - indexStatsContentExists: () => { - return exists('overviewTabIndexStats'); + storageDetailsExist: () => { + return exists('indexDetailsStorage'); }, - indexDetailsContentExists: () => { - return exists('overviewTabIndexDetails'); + getStorageDetailsContent: () => { + return find('indexDetailsStorage').text(); + }, + statusDetailsExist: () => { + return exists('indexDetailsStatus'); + }, + getStatusDetailsContent: () => { + return find('indexDetailsStatus').text(); + }, + aliasesDetailsExist: () => { + return exists('indexDetailsAliases'); + }, + getAliasesDetailsContent: () => { + return find('indexDetailsAliases').text(); + }, + dataStreamDetailsExist: () => { + return exists('indexDetailsDataStream'); + }, + getDataStreamDetailsContent: () => { + return find('indexDetailsDataStream').text(); + }, + reloadDataStreamDetails: async () => { + await act(async () => { + find('indexDetailsDataStreamReload').simulate('click'); + }); + component.update(); }, addDocCodeBlockExists: () => { return exists('codeBlockControlsPanel'); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.tsx index 326f48c056815..76247c483b3c4 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.tsx @@ -10,16 +10,20 @@ import { IndexDetailsPageTestBed, setup } from './index_details_page.helpers'; import { act } from 'react-dom/test-utils'; import React from 'react'; + import { IndexDetailsSection, IndexDetailsTab, IndexDetailsTabIds, } from '../../../common/constants'; -import { API_BASE_PATH, INTERNAL_API_BASE_PATH } from '../../../common'; +import { API_BASE_PATH, Index, INTERNAL_API_BASE_PATH } from '../../../common'; + import { breadcrumbService, IndexManagementBreadcrumb, } from '../../../public/application/services/breadcrumbs'; +import { humanizeTimeStamp } from '../../../public/application/sections/home/data_stream_list/humanize_time_stamp'; +import { createDataStreamPayload } from '../home/data_streams_tab.helpers'; import { testIndexEditableSettingsAll, testIndexEditableSettingsLimited, @@ -234,13 +238,149 @@ describe('', () => { ); }); - it('renders index details', () => { - expect(testBed.actions.overview.indexDetailsContentExists()).toBe(true); - expect(testBed.actions.overview.indexStatsContentExists()).toBe(true); - expect(testBed.actions.overview.addDocCodeBlockExists()).toBe(true); + it('renders storage details', () => { + const storageDetails = testBed.actions.overview.getStorageDetailsContent(); + expect(storageDetails).toBe( + `Storage${testIndexMock.primary_size}Primary${testIndexMock.size}TotalShards${testIndexMock.primary} Primary / ${testIndexMock.replica} Replicas ` + ); + }); + + it('renders status details', () => { + const statusDetails = testBed.actions.overview.getStatusDetailsContent(); + expect(statusDetails).toBe( + `Status${'Open'}${'Healthy'}${testIndexMock.documents} Document / ${ + testIndexMock.documents_deleted + } Deleted` + ); + }); + + describe('aliases', () => { + it('not rendered when no aliases', async () => { + const aliasesExist = testBed.actions.overview.aliasesDetailsExist(); + expect(aliasesExist).toBe(false); + }); + + it('renders less than 3 aliases', async () => { + const aliases = ['test_alias1', 'test_alias2']; + const testWith2Aliases = { + ...testIndexMock, + aliases, + }; + + httpRequestsMockHelpers.setLoadIndexDetailsResponse(testIndexName, testWith2Aliases); + + await act(async () => { + testBed = await setup({ httpSetup }); + }); + testBed.component.update(); + + const aliasesExist = testBed.actions.overview.aliasesDetailsExist(); + expect(aliasesExist).toBe(true); + + const aliasesContent = testBed.actions.overview.getAliasesDetailsContent(); + expect(aliasesContent).toBe( + `Aliases${aliases.length}AliasesView all aliases${aliases.join('')}` + ); + }); + + it('renders more than 3 aliases', async () => { + const aliases = ['test_alias1', 'test_alias2', 'test_alias3', 'test_alias4', 'test_alias5']; + const testWith5Aliases = { + ...testIndexMock, + aliases, + }; + + httpRequestsMockHelpers.setLoadIndexDetailsResponse(testIndexName, testWith5Aliases); + + await act(async () => { + testBed = await setup({ httpSetup }); + }); + testBed.component.update(); + + const aliasesExist = testBed.actions.overview.aliasesDetailsExist(); + expect(aliasesExist).toBe(true); + + const aliasesContent = testBed.actions.overview.getAliasesDetailsContent(); + expect(aliasesContent).toBe( + `Aliases${aliases.length}AliasesView all aliases${aliases.slice(0, 3).join('')}+${2}` + ); + }); + }); + + describe('data stream', () => { + it('not rendered when no data stream', async () => { + const aliasesExist = testBed.actions.overview.dataStreamDetailsExist(); + expect(aliasesExist).toBe(false); + }); + + it('renders data stream details', async () => { + const dataStreamName = 'test_data_stream'; + const testWithDataStream: Index = { + ...testIndexMock, + data_stream: dataStreamName, + }; + const dataStreamDetails = createDataStreamPayload({ + name: dataStreamName, + generation: 5, + maxTimeStamp: 1696600607689, + }); + + httpRequestsMockHelpers.setLoadIndexDetailsResponse(testIndexName, testWithDataStream); + httpRequestsMockHelpers.setLoadDataStreamResponse(dataStreamName, dataStreamDetails); + + await act(async () => { + testBed = await setup({ httpSetup }); + }); + testBed.component.update(); + + const dataStreamDetailsExist = testBed.actions.overview.dataStreamDetailsExist(); + expect(dataStreamDetailsExist).toBe(true); + + const dataStreamContent = testBed.actions.overview.getDataStreamDetailsContent(); + expect(dataStreamContent).toBe( + `Data stream${ + dataStreamDetails.generation + }GenerationsSee detailsRelated templateLast update${humanizeTimeStamp( + dataStreamDetails.maxTimeStamp! + )}` + ); + }); + + it('renders error message if the request fails', async () => { + const dataStreamName = 'test_data_stream'; + const testWithDataStream: Index = { + ...testIndexMock, + data_stream: dataStreamName, + }; + + httpRequestsMockHelpers.setLoadIndexDetailsResponse(testIndexName, testWithDataStream); + httpRequestsMockHelpers.setLoadDataStreamResponse(dataStreamName, undefined, { + statusCode: 400, + message: `Unable to load data stream details`, + }); + + await act(async () => { + testBed = await setup({ httpSetup }); + }); + testBed.component.update(); + + const dataStreamDetailsExist = testBed.actions.overview.dataStreamDetailsExist(); + expect(dataStreamDetailsExist).toBe(true); + + const dataStreamContent = testBed.actions.overview.getDataStreamDetailsContent(); + expect(dataStreamContent).toBe( + `Data streamUnable to load data stream detailsReloadLast update` + ); + + // already sent 3 requests while setting up the component + const numberOfRequests = 3; + expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests); + await testBed.actions.overview.reloadDataStreamDetails(); + expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests + 1); + }); }); - it('hides index stats from detail panels if enableIndexStats===false', async () => { + it('hides storage and status details if enableIndexStats===false', async () => { await act(async () => { testBed = await setup({ httpSetup, @@ -251,8 +391,12 @@ describe('', () => { }); testBed.component.update(); - expect(testBed.actions.overview.indexDetailsContentExists()).toBe(true); - expect(testBed.actions.overview.indexStatsContentExists()).toBe(false); + expect(testBed.actions.overview.statusDetailsExist()).toBe(false); + expect(testBed.actions.overview.storageDetailsExist()).toBe(false); + }); + + it('renders code block', () => { + expect(testBed.actions.overview.addDocCodeBlockExists()).toBe(true); }); describe('extension service summary', () => { diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/mocks.ts b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/mocks.ts index 8adab61ec9a56..abcf0d481431e 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/mocks.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/mocks.ts @@ -14,10 +14,10 @@ export const testIndexMock: Index = { name: testIndexName, uuid: 'test1234', primary: '1', - replica: '1', + replica: '2', documents: 1, documents_deleted: 0, - size: '10kb', + size: '20kb', primary_size: '10kb', isFrozen: false, aliases: 'none', diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page.tsx index b5141052199f4..f3e54ae946a99 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page.tsx @@ -254,6 +254,10 @@ export const DetailsPage: FunctionComponent< navigateToAllIndices={navigateToAllIndices} />, ]} + rightSideGroupProps={{ + wrap: false, + }} + responsive="reverse" tabs={headerTabs} /> diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/aliases_details.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/aliases_details.tsx new file mode 100644 index 0000000000000..8ea5a1fa491ce --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/aliases_details.tsx @@ -0,0 +1,123 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FunctionComponent, useState } from 'react'; +import { css } from '@emotion/react'; +import { euiThemeVars } from '@kbn/ui-theme'; +import { i18n } from '@kbn/i18n'; +import { + EuiBadge, + EuiBadgeGroup, + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutHeader, + EuiListGroup, + EuiListGroupItem, + EuiText, + EuiTextColor, + EuiTitle, +} from '@elastic/eui'; + +import { Index } from '../../../../../../../common'; +import { OverviewCard } from './overview_card'; + +const MAX_VISIBLE_ALIASES = 3; + +export const AliasesDetails: FunctionComponent<{ aliases: Index['aliases'] }> = ({ aliases }) => { + const [isShowingAliases, setIsShowingAliases] = useState(false); + if (!Array.isArray(aliases)) { + return null; + } + const aliasesBadges = aliases.slice(0, MAX_VISIBLE_ALIASES).map((alias) => ( + + {alias} + + )); + return ( + <> + + + + {aliases.length} + + + + + {i18n.translate( + 'xpack.idxMgmt.indexDetails.overviewTab.aliases.aliasesCountLabel', + { + defaultMessage: '{aliases, plural, one {Alias} other {Aliases}}', + values: { aliases: aliases.length }, + } + )} + + + + ), + right: ( + { + setIsShowingAliases(true); + }} + > + View all aliases + + ), + }} + footer={{ + left: ( + + {aliasesBadges} + {aliases.length > MAX_VISIBLE_ALIASES && ( + +{aliases.length - MAX_VISIBLE_ALIASES} + )} + + ), + }} + /> + {isShowingAliases && ( + setIsShowingAliases(false)}> + + +

+ {i18n.translate('xpack.idxMgmt.indexDetails.overviewTab.aliases.cardTitle', { + defaultMessage: 'Aliases', + })} +

+
+
+ + + {aliases.map((alias) => ( + + ))} + + +
+ )} + + ); +}; diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/data_stream_details.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/data_stream_details.tsx new file mode 100644 index 0000000000000..0ebc7afae33d7 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/data_stream_details.tsx @@ -0,0 +1,154 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FunctionComponent, ReactNode } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText, EuiTextColor } from '@elastic/eui'; +import { css } from '@emotion/react'; +import { euiThemeVars } from '@kbn/ui-theme'; +import { reactRouterNavigate } from '@kbn/kibana-react-plugin/public'; +import { SectionLoading } from '@kbn/es-ui-shared-plugin/public'; + +import { FormattedMessage } from '@kbn/i18n-react'; +import { getDataStreamDetailsLink } from '../../../../../services/routing'; +import { getTemplateDetailsLink } from '../../../../../..'; +import { useLoadDataStream } from '../../../../../services/api'; +import { useAppContext } from '../../../../../app_context'; +import { humanizeTimeStamp } from '../../../data_stream_list/humanize_time_stamp'; +import { OverviewCard } from './overview_card'; + +export const DataStreamDetails: FunctionComponent<{ dataStreamName: string }> = ({ + dataStreamName, +}) => { + const { error, data: dataStream, isLoading, resendRequest } = useLoadDataStream(dataStreamName); + const { history } = useAppContext(); + const hasError = !isLoading && (error || !dataStream); + let contentLeft: ReactNode = ( + + + + {dataStream?.generation} + + + + + {i18n.translate('xpack.idxMgmt.indexDetails.overviewTab.dataStream.generationLabel', { + defaultMessage: '{generations, plural, one {Generation} other {Generations}}', + values: { generations: dataStream?.generation }, + })} + + + + ); + let contentRight: ReactNode = ( + + + + {i18n.translate('xpack.idxMgmt.indexDetails.overviewTab.dataStream.dataStreamLinkLabel', { + defaultMessage: 'See details', + })} + + + + + {i18n.translate('xpack.idxMgmt.indexDetails.overviewTab.dataStream.templateLinkLabel', { + defaultMessage: 'Related template', + })} + + + + ); + + if (isLoading) { + contentLeft = ( + + + + ); + contentRight = null; + } + if (hasError) { + contentLeft = ( + + + + + + ); + contentRight = ( + + + + ); + } + return ( + + + + + + {i18n.translate('xpack.idxMgmt.indexDetails.overviewTab.dataStream.lastUpdateLabel', { + defaultMessage: 'Last update', + })} + + {!isLoading && !hasError && ( + + + {dataStream?.maxTimeStamp + ? humanizeTimeStamp(dataStream.maxTimeStamp) + : i18n.translate( + 'xpack.idxMgmt.indexDetails.overviewTab.dataStream.maxTimeStampNoneMessage', + { + defaultMessage: `Never`, + } + )} + + + )} + + ), + }} + /> + ); +}; diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/details_page_overview.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/details_page_overview.tsx index ea0466fe1c115..624a58f6e722a 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/details_page_overview.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/details_page_overview.tsx @@ -10,14 +10,14 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiSpacer, - EuiPanel, EuiFlexGroup, EuiFlexItem, - EuiStat, EuiTitle, EuiText, EuiTextColor, EuiLink, + EuiFlexGrid, + useIsWithinBreakpoints, } from '@elastic/eui'; import { CodeBox, @@ -26,12 +26,16 @@ import { getLanguageDefinitionCodeSnippet, getConsoleRequest, } from '@kbn/search-api-panels'; +import { StatusDetails } from './status_details'; import type { Index } from '../../../../../../../common'; import { useAppContext } from '../../../../../app_context'; import { documentationService } from '../../../../../services'; import { breadcrumbService, IndexManagementBreadcrumb } from '../../../../../services/breadcrumbs'; import { languageDefinitions, curlDefinition } from './languages'; import { ExtensionsSummary } from './extensions_summary'; +import { DataStreamDetails } from './data_stream_details'; +import { StorageDetails } from './storage_details'; +import { AliasesDetails } from './aliases_details'; interface Props { indexDetails: Index; @@ -41,13 +45,17 @@ export const DetailsPageOverview: React.FunctionComponent = ({ indexDetai const { name, status, + health, documents, documents_deleted: documentsDeleted, primary, replica, aliases, + data_stream: dataStream, + size, + primary_size: primarySize, } = indexDetails; - const { config, core, plugins } = useAppContext(); + const { core, plugins } = useAppContext(); useEffect(() => { breadcrumbService.setBreadcrumbs(IndexManagementBreadcrumb.indexDetailsOverview); @@ -65,99 +73,24 @@ export const DetailsPageOverview: React.FunctionComponent = ({ indexDetai indexName: name, }; + const isLarge = useIsWithinBreakpoints(['xl']); + return ( <> - - {config.enableIndexStats && ( - - - - - - - - - - - - - - - - )} - - - - - {primary && ( - - - - )} - - {replica && ( - - - - )} - - - - - - - - + + + + + + + + {dataStream && } + diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/extensions_summary.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/extensions_summary.tsx index 3788988ec72aa..b119a99cd1a0a 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/extensions_summary.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/extensions_summary.tsx @@ -23,7 +23,9 @@ export const ExtensionsSummary: FunctionComponent<{ index: Index }> = ({ index } } return ( - {summary} + + {summary} + ); diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/overview_card.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/overview_card.tsx new file mode 100644 index 0000000000000..4aa6c804dc40b --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/overview_card.tsx @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FunctionComponent, ReactNode } from 'react'; +import { css } from '@emotion/react'; +import { euiThemeVars } from '@kbn/ui-theme'; +import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiSplitPanel, EuiTitle } from '@elastic/eui'; + +interface Props { + title: string; + content: { + left: ReactNode; + right: ReactNode; + }; + footer?: { + left?: ReactNode; + right?: ReactNode; + }; + 'data-test-subj'?: string; +} + +export const OverviewCard: FunctionComponent = ({ + title, + content: { left: contentLeft, right: contentRight }, + footer: { left: footerLeft, right: footerRight } = {}, + 'data-test-subj': dataTestSubj, +}) => { + return ( + + + + +

{title}

+
+ + + {contentLeft} + {contentRight} + +
+ + + {footerLeft && {footerLeft}} + {footerRight && {footerRight}} + + +
+
+ ); +}; diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/status_details.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/status_details.tsx new file mode 100644 index 0000000000000..8895daceceaf9 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/status_details.tsx @@ -0,0 +1,120 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FunctionComponent } from 'react'; +import { i18n } from '@kbn/i18n'; +import { + EuiBadge, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiText, + EuiTextColor, + EuiBadgeProps, +} from '@elastic/eui'; +import { css } from '@emotion/react'; +import { euiThemeVars } from '@kbn/ui-theme'; + +import { useAppContext } from '../../../../../app_context'; +import { Index } from '../../../../../../../common'; +import { OverviewCard } from './overview_card'; + +type NormalizedHealth = 'green' | 'red' | 'yellow'; +const healthToBadgeMapping: Record< + NormalizedHealth, + { color: EuiBadgeProps['color']; label: string } +> = { + green: { + color: 'success', + label: i18n.translate('xpack.idxMgmt.indexDetails.overviewTab.health.greenLabel', { + defaultMessage: 'Healthy', + }), + }, + yellow: { + color: 'warning', + label: i18n.translate('xpack.idxMgmt.indexDetails.overviewTab.health.yellowLabel', { + defaultMessage: 'Warning', + }), + }, + red: { + color: 'danger', + label: i18n.translate('xpack.idxMgmt.indexDetails.overviewTab.health.redLabel', { + defaultMessage: 'Critical', + }), + }, +}; + +export const StatusDetails: FunctionComponent<{ + documents: Index['documents']; + documentsDeleted: Index['documents_deleted']; + status: Index['status']; + health: Index['health']; +}> = ({ documents, documentsDeleted, status, health }) => { + const { config } = useAppContext(); + if (!config.enableIndexStats || !health) { + return null; + } + const badgeConfig = healthToBadgeMapping[health.toLowerCase() as NormalizedHealth]; + const healthBadge = {badgeConfig.label}; + + return ( + + {status === 'close' + ? i18n.translate('xpack.idxMgmt.indexDetails.overviewTab.status.closedLabel', { + defaultMessage: 'Closed', + }) + : i18n.translate('xpack.idxMgmt.indexDetails.overviewTab.status.openLabel', { + defaultMessage: 'Open', + })} + + ), + right: ( +
+ {healthBadge} +
+ ), + }} + footer={{ + left: ( + + + + + + + {i18n.translate('xpack.idxMgmt.indexDetails.overviewTab.status.documentsLabel', { + defaultMessage: + '{documents, plural, one {# Document} other {# Documents}} / {documentsDeleted} Deleted', + values: { + documents, + documentsDeleted, + }, + })} + + + + ), + }} + /> + ); +}; diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/storage_details.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/storage_details.tsx new file mode 100644 index 0000000000000..22b47815ce957 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/storage_details.tsx @@ -0,0 +1,109 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FunctionComponent } from 'react'; +import { css } from '@emotion/react'; +import { i18n } from '@kbn/i18n'; +import { euiThemeVars } from '@kbn/ui-theme'; +import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText, EuiTextColor } from '@elastic/eui'; + +import { useAppContext } from '../../../../../app_context'; +import { Index } from '../../../../../../../common'; +import { OverviewCard } from './overview_card'; + +export const StorageDetails: FunctionComponent<{ + primarySize: Index['primary_size']; + size: Index['size']; + primary: Index['primary']; + replica: Index['replica']; +}> = ({ primarySize, size, primary, replica }) => { + const { config } = useAppContext(); + if (!config.enableIndexStats) { + return null; + } + return ( + + + + {primarySize} + + + + + {i18n.translate('xpack.idxMgmt.indexDetails.overviewTab.storage.primarySizeLabel', { + defaultMessage: 'Primary', + })} + + + + ), + right: ( + + + + {size} + + + + + {i18n.translate('xpack.idxMgmt.indexDetails.overviewTab.storage.totalSizeLabel', { + defaultMessage: 'Total', + })} + + + + ), + }} + footer={{ + left: ( + + + + + + + {i18n.translate('xpack.idxMgmt.indexDetails.overviewTab.storage.shardsLabel', { + defaultMessage: 'Shards', + })} + + + + ), + right: ( + + {i18n.translate( + 'xpack.idxMgmt.indexDetails.overviewTab.storage.primariesReplicasLabel', + { + defaultMessage: + '{primary, plural, one {# Primary} other {# Primaries}} / {replica, plural, one {# Replica} other {# Replicas}} ', + values: { + primary, + replica, + }, + } + )} + + ), + }} + /> + ); +}; diff --git a/x-pack/plugins/index_management/tsconfig.json b/x-pack/plugins/index_management/tsconfig.json index 2a917c34363bc..7b52920e5e5d4 100644 --- a/x-pack/plugins/index_management/tsconfig.json +++ b/x-pack/plugins/index_management/tsconfig.json @@ -40,6 +40,7 @@ "@kbn/core-http-browser", "@kbn/search-api-panels", "@kbn/cloud-plugin", + "@kbn/ui-theme", ], "exclude": [ "target/**/*", From 8420d5776b17d3f3e749edb770973e623e20a6e1 Mon Sep 17 00:00:00 2001 From: Juan Pablo Djeredjian Date: Tue, 31 Oct 2023 18:25:14 +0100 Subject: [PATCH 13/21] [Security Solution] Fix Rules Management tour (#170237) ## Summary The Rules Management tour was broken because of two changes: - the update of the prebuilt rules tags to include prefixes. So the `Guided Onboarding` tag turned into `Use Case: Guided Onboarding`. Since we fetched our example rule by the tag, one of the steps of the rule broke. - the UI for installing rules changed with the new workflow, that now navigates to a new Add Elastic Rules page. Previously the tour guided the user to install all rules, trusting that the installation would include the example rule. Now, we need to guide the user to that rule. ## Testing In order to activate the rules while running locally, in the file `x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/guided_onboarding/rules_management_tour.tsx` hardcode the variable: ``` const isRulesStepActive = true; ``` ## TO-DO - Cover Rule Management tour with tests: https://github.com/elastic/kibana/issues/170238 ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --- .../rules_table/guided_onboarding/rules_management_tour.tsx | 4 ++-- .../rules_table/guided_onboarding/translations.ts | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/guided_onboarding/rules_management_tour.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/guided_onboarding/rules_management_tour.tsx index 1ebe9db27a922..5cd5a8e30808f 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/guided_onboarding/rules_management_tour.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/guided_onboarding/rules_management_tour.tsx @@ -32,7 +32,7 @@ const GUIDED_ONBOARDING_RULES_FILTER = { filter: '', showCustomRules: false, showElasticRules: true, - tags: ['Guided Onboarding'], + tags: ['Use Case: Guided Onboarding'], }; export enum GuidedOnboardingRulesStatus { @@ -84,7 +84,7 @@ export const RulesManagementTour = () => { } if (onboardingRules.total === 0) { - // Onboarding rules are not installed - show the install/update rules step + // Onboarding rules are not installed - show the navigate to Add Rules page step return GuidedOnboardingRulesStatus.installRules; } diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/guided_onboarding/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/guided_onboarding/translations.ts index 0347efa182a5b..7b20dcc3130fd 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/guided_onboarding/translations.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/guided_onboarding/translations.ts @@ -10,14 +10,15 @@ import { i18n } from '@kbn/i18n'; export const INSTALL_PREBUILT_RULES_TITLE = i18n.translate( 'xpack.securitySolution.detectionEngine.rules.guidedOnboarding.installPrebuiltRules.title', { - defaultMessage: 'Load the Elastic prebuilt rules', + defaultMessage: 'Install your first prebuilt rule', } ); export const INSTALL_PREBUILT_RULES_CONTENT = i18n.translate( 'xpack.securitySolution.detectionEngine.rules.guidedOnboarding.installPrebuiltRules.content', { - defaultMessage: 'To get started you need to load the Elastic prebuilt rules.', + defaultMessage: + 'Navigate to the Add Elastic Rules page and install the example "My First Rule".', } ); From 8103a445855f72dd1755a4fd4364e4573ec6a194 Mon Sep 17 00:00:00 2001 From: Angela Chuang <6295984+angorayc@users.noreply.github.com> Date: Tue, 31 Oct 2023 17:30:08 +0000 Subject: [PATCH 14/21] [SecuritySolution] Fix topN legend actions - filter in / out in timeline (#170127) ## Summary https://github.com/elastic/kibana/issues/168199 https://github.com/elastic/kibana/issues/169656 https://github.com/elastic/kibana/assets/6295984/ff5cee55-6da5-4636-85f5-a697a302f8b5 --------- Co-authored-by: Michael Olorunnisola Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../layered_xy_vis.test.ts | 2 + .../expression_functions/layered_xy_vis_fn.ts | 2 + .../expression_functions/xy_vis.test.ts | 4 + .../common/expression_functions/xy_vis_fn.ts | 2 + .../common/helpers/visualization.ts | 2 + .../public/components/legend_action.test.tsx | 3 +- .../public/components/legend_action.tsx | 2 + .../components/legend_action_popover.tsx | 98 ++++++++++------ .../public/components/xy_chart.tsx | 3 + .../xy_chart_renderer.tsx | 1 + .../expressions/common/execution/types.ts | 2 + .../common/expression_renderers/types.ts | 1 + src/plugins/expressions/public/loader.ts | 1 + .../react_expression_renderer.tsx | 1 + src/plugins/expressions/public/render.ts | 5 + src/plugins/expressions/public/types/index.ts | 1 + .../lens/public/embeddable/embeddable.tsx | 2 + .../public/embeddable/expression_wrapper.tsx | 3 + .../public/actions/filter/index.ts | 4 + .../public/actions/filter/lens/filter_in.ts | 29 +++++ .../actions/filter/lens/filter_in_timeline.ts | 29 +++++ .../public/actions/filter/lens/filter_out.ts | 30 +++++ .../filter/lens/filter_out_timeline.ts | 30 +++++ .../public/actions/filter/lens/helpers.ts | 111 ++++++++++++++++++ .../public/actions/register.ts | 36 +++++- .../visualization_actions/lens_embeddable.tsx | 8 +- .../components/visualization_actions/utils.ts | 32 +++++ .../cypress/screens/alerts.ts | 6 +- 28 files changed, 405 insertions(+), 45 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/actions/filter/lens/filter_in.ts create mode 100644 x-pack/plugins/security_solution/public/actions/filter/lens/filter_in_timeline.ts create mode 100644 x-pack/plugins/security_solution/public/actions/filter/lens/filter_out.ts create mode 100644 x-pack/plugins/security_solution/public/actions/filter/lens/filter_out_timeline.ts create mode 100644 x-pack/plugins/security_solution/public/actions/filter/lens/helpers.ts diff --git a/src/plugins/chart_expressions/expression_xy/common/expression_functions/layered_xy_vis.test.ts b/src/plugins/chart_expressions/expression_xy/common/expression_functions/layered_xy_vis.test.ts index eae70ea54f0c9..78eb7c54ebf8a 100644 --- a/src/plugins/chart_expressions/expression_xy/common/expression_functions/layered_xy_vis.test.ts +++ b/src/plugins/chart_expressions/expression_xy/common/expression_functions/layered_xy_vis.test.ts @@ -10,6 +10,7 @@ import { layeredXyVisFunction } from '.'; import { createMockExecutionContext } from '@kbn/expressions-plugin/common/mocks'; import { sampleArgs, sampleExtendedLayer } from '../__mocks__'; import { XY_VIS } from '../constants'; +import { shouldShowLegendActionDefault } from '../helpers/visualization'; describe('layeredXyVis', () => { test('it renders with the specified data and args', async () => { @@ -30,6 +31,7 @@ describe('layeredXyVis', () => { syncTooltips: false, syncCursor: true, canNavigateToLens: false, + shouldShowLegendAction: shouldShowLegendActionDefault, }, }); }); diff --git a/src/plugins/chart_expressions/expression_xy/common/expression_functions/layered_xy_vis_fn.ts b/src/plugins/chart_expressions/expression_xy/common/expression_functions/layered_xy_vis_fn.ts index cf1325f09bf22..5a1a79ef984fc 100644 --- a/src/plugins/chart_expressions/expression_xy/common/expression_functions/layered_xy_vis_fn.ts +++ b/src/plugins/chart_expressions/expression_xy/common/expression_functions/layered_xy_vis_fn.ts @@ -18,6 +18,7 @@ import { validateAxes, } from './validate'; import { appendLayerIds, getDataLayers } from '../helpers'; +import { shouldShowLegendActionDefault } from '../helpers/visualization'; export const layeredXyVisFn: LayeredXyVisFn['fn'] = async (data, args, handlers) => { const layers = appendLayerIds(args.layers ?? [], 'layers'); @@ -66,6 +67,7 @@ export const layeredXyVisFn: LayeredXyVisFn['fn'] = async (data, args, handlers) syncTooltips: handlers?.isSyncTooltipsEnabled?.() ?? false, syncCursor: handlers?.isSyncCursorEnabled?.() ?? true, overrides: handlers.variables?.overrides as XYRender['value']['overrides'], + shouldShowLegendAction: handlers?.shouldShowLegendAction ?? shouldShowLegendActionDefault, }, }; }; diff --git a/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis.test.ts b/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis.test.ts index 9a71ec92d7a51..e0c825597d328 100644 --- a/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis.test.ts +++ b/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis.test.ts @@ -10,6 +10,7 @@ import { xyVisFunction } from '.'; import { createMockExecutionContext } from '@kbn/expressions-plugin/common/mocks'; import { sampleArgs, sampleLayer } from '../__mocks__'; import { XY_VIS } from '../constants'; +import { shouldShowLegendActionDefault } from '../helpers/visualization'; describe('xyVis', () => { test('it renders with the specified data and args', async () => { @@ -42,6 +43,7 @@ describe('xyVis', () => { syncColors: false, syncTooltips: false, syncCursor: true, + shouldShowLegendAction: shouldShowLegendActionDefault, }, }); }); @@ -352,6 +354,7 @@ describe('xyVis', () => { syncColors: false, syncTooltips: false, syncCursor: true, + shouldShowLegendAction: shouldShowLegendActionDefault, }, }); }); @@ -401,6 +404,7 @@ describe('xyVis', () => { syncTooltips: false, syncCursor: true, overrides, + shouldShowLegendAction: shouldShowLegendActionDefault, }, }); }); diff --git a/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis_fn.ts b/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis_fn.ts index 03df575b3c653..1eb4357e57c84 100644 --- a/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis_fn.ts +++ b/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis_fn.ts @@ -30,6 +30,7 @@ import { validateAxes, } from './validate'; import { logDatatable } from '../utils'; +import { shouldShowLegendActionDefault } from '../helpers/visualization'; const createDataLayer = (args: XYArgs, table: Datatable): DataLayerConfigResult => { const accessors = getAccessors(args, table); @@ -139,6 +140,7 @@ export const xyVisFn: XyVisFn['fn'] = async (data, args, handlers) => { syncTooltips: handlers?.isSyncTooltipsEnabled?.() ?? false, syncCursor: handlers?.isSyncCursorEnabled?.() ?? true, overrides: handlers.variables?.overrides as XYRender['value']['overrides'], + shouldShowLegendAction: handlers?.shouldShowLegendAction ?? shouldShowLegendActionDefault, }, }; }; diff --git a/src/plugins/chart_expressions/expression_xy/common/helpers/visualization.ts b/src/plugins/chart_expressions/expression_xy/common/helpers/visualization.ts index 66d4c11a9f7ae..8f740c2578d7e 100644 --- a/src/plugins/chart_expressions/expression_xy/common/helpers/visualization.ts +++ b/src/plugins/chart_expressions/expression_xy/common/helpers/visualization.ts @@ -19,3 +19,5 @@ export function isTimeChart(layers: CommonXYDataLayerConfigResult[]) { (!l.xScaleType || l.xScaleType === XScaleTypes.TIME) ); } + +export const shouldShowLegendActionDefault = () => true; diff --git a/src/plugins/chart_expressions/expression_xy/public/components/legend_action.test.tsx b/src/plugins/chart_expressions/expression_xy/public/components/legend_action.test.tsx index 1398fc64357cb..47e36adab06f8 100644 --- a/src/plugins/chart_expressions/expression_xy/public/components/legend_action.test.tsx +++ b/src/plugins/chart_expressions/expression_xy/public/components/legend_action.test.tsx @@ -203,7 +203,8 @@ describe('getLegendAction', function () { formattedColumns: {}, }, }, - {} + {}, + () => true ); let wrapper: ReactWrapper; diff --git a/src/plugins/chart_expressions/expression_xy/public/components/legend_action.tsx b/src/plugins/chart_expressions/expression_xy/public/components/legend_action.tsx index f5b00f696d04f..2ae2c21b8c0d8 100644 --- a/src/plugins/chart_expressions/expression_xy/public/components/legend_action.tsx +++ b/src/plugins/chart_expressions/expression_xy/public/components/legend_action.tsx @@ -28,6 +28,7 @@ export const getLegendAction = ( fieldFormats: LayersFieldFormats, formattedDatatables: DatatablesWithFormatInfo, titles: LayersAccessorsTitles, + shouldShowLegendAction?: (actionId: string) => boolean, singleTable?: boolean ): LegendAction => React.memo(({ series: [xySeries] }) => { @@ -109,6 +110,7 @@ export const getLegendAction = ( } onFilter={filterHandler} legendCellValueActions={legendCellValueActions} + shouldShowLegendAction={shouldShowLegendAction} /> ); }); diff --git a/src/plugins/chart_expressions/expression_xy/public/components/legend_action_popover.tsx b/src/plugins/chart_expressions/expression_xy/public/components/legend_action_popover.tsx index 9fa488db24357..aa2db4c4eb47c 100644 --- a/src/plugins/chart_expressions/expression_xy/public/components/legend_action_popover.tsx +++ b/src/plugins/chart_expressions/expression_xy/public/components/legend_action_popover.tsx @@ -6,11 +6,18 @@ * Side Public License, v 1. */ -import React, { useState } from 'react'; +import React, { useState, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiContextMenuPanelDescriptor, EuiIcon, EuiPopover, EuiContextMenu } from '@elastic/eui'; +import { + EuiContextMenuPanelDescriptor, + EuiIcon, + EuiPopover, + EuiContextMenu, + EuiContextMenuPanelItemDescriptor, +} from '@elastic/eui'; import { useLegendAction } from '@elastic/charts'; import type { CellValueAction } from '../types'; +import { shouldShowLegendActionDefault } from '../../common/helpers/visualization'; export type LegendCellValueActions = Array< Omit & { execute: () => void } @@ -29,57 +36,70 @@ export interface LegendActionPopoverProps { * Compatible actions to be added to the popover actions */ legendCellValueActions?: LegendCellValueActions; + shouldShowLegendAction?: (actionId: string) => boolean; } export const LegendActionPopover: React.FunctionComponent = ({ label, onFilter, legendCellValueActions = [], + shouldShowLegendAction = shouldShowLegendActionDefault, }) => { const [popoverOpen, setPopoverOpen] = useState(false); const [ref, onClose] = useLegendAction(); - const legendCellValueActionPanelItems = legendCellValueActions.map((action) => ({ - name: action.displayName, - 'data-test-subj': `legend-${label}-${action.id}`, - icon: , - onClick: () => { - action.execute(); - setPopoverOpen(false); - }, - })); - - const panels: EuiContextMenuPanelDescriptor[] = [ - { - id: 'main', - title: label, - items: [ - { - name: i18n.translate('expressionXY.legend.filterForValueButtonAriaLabel', { - defaultMessage: 'Filter for', - }), - 'data-test-subj': `legend-${label}-filterIn`, - icon: , - onClick: () => { - setPopoverOpen(false); - onFilter(); - }, + const panels: EuiContextMenuPanelDescriptor[] = useMemo(() => { + const defaultActions = [ + { + id: 'filterIn', + displayName: i18n.translate('expressionXY.legend.filterForValueButtonAriaLabel', { + defaultMessage: 'Filter for', + }), + 'data-test-subj': `legend-${label}-filterIn`, + iconType: 'plusInCircle', + execute: () => { + setPopoverOpen(false); + onFilter(); }, - { - name: i18n.translate('expressionXY.legend.filterOutValueButtonAriaLabel', { - defaultMessage: 'Filter out', - }), - 'data-test-subj': `legend-${label}-filterOut`, - icon: , + }, + { + id: 'filterOut', + displayName: i18n.translate('expressionXY.legend.filterOutValueButtonAriaLabel', { + defaultMessage: 'Filter out', + }), + 'data-test-subj': `legend-${label}-filterOut`, + iconType: 'minusInCircle', + execute: () => { + setPopoverOpen(false); + onFilter({ negate: true }); + }, + }, + ]; + + const legendCellValueActionPanelItems = [...defaultActions, ...legendCellValueActions].reduce< + EuiContextMenuPanelItemDescriptor[] + >((acc, action) => { + if (shouldShowLegendAction(action.id)) { + acc.push({ + name: action.displayName, + 'data-test-subj': `legend-${label}-${action.id}`, + icon: , onClick: () => { + action.execute(); setPopoverOpen(false); - onFilter({ negate: true }); }, - }, - ...legendCellValueActionPanelItems, - ], - }, - ]; + }); + } + return acc; + }, []); + return [ + { + id: 'main', + title: label, + items: legendCellValueActionPanelItems, + }, + ]; + }, [label, legendCellValueActions, onFilter, shouldShowLegendAction]); const Button = (
& { renderComplete: () => void; uiState?: PersistedState; timeFormat: string; + shouldShowLegendAction?: (actionId: string) => boolean; }; function nonNullable(v: T): v is NonNullable { @@ -207,6 +208,7 @@ export function XYChart({ uiState, timeFormat, overrides, + shouldShowLegendAction, }: XYChartRenderProps) { const { legend, @@ -839,6 +841,7 @@ export function XYChart({ fieldFormats, formattedDatatables, titles, + shouldShowLegendAction, singleTable ) : undefined diff --git a/src/plugins/chart_expressions/expression_xy/public/expression_renderers/xy_chart_renderer.tsx b/src/plugins/chart_expressions/expression_xy/public/expression_renderers/xy_chart_renderer.tsx index c2561191deb9a..401af740375b2 100644 --- a/src/plugins/chart_expressions/expression_xy/public/expression_renderers/xy_chart_renderer.tsx +++ b/src/plugins/chart_expressions/expression_xy/public/expression_renderers/xy_chart_renderer.tsx @@ -277,6 +277,7 @@ export const getXyChartRenderer = ({ syncCursor={config.syncCursor} uiState={handlers.uiState as PersistedState} renderComplete={renderComplete} + shouldShowLegendAction={handlers.shouldShowLegendAction} />
diff --git a/src/plugins/expressions/common/execution/types.ts b/src/plugins/expressions/common/execution/types.ts index dddc503285942..eed9628444cc7 100644 --- a/src/plugins/expressions/common/execution/types.ts +++ b/src/plugins/expressions/common/execution/types.ts @@ -84,6 +84,8 @@ export interface ExecutionContext< * Logs datatable. */ logDatatable?(name: string, datatable: Datatable): void; + + shouldShowLegendAction?: (actionId: string) => boolean; } /** diff --git a/src/plugins/expressions/common/expression_renderers/types.ts b/src/plugins/expressions/common/expression_renderers/types.ts index 7dae307aa6c01..e75e0af849ed3 100644 --- a/src/plugins/expressions/common/expression_renderers/types.ts +++ b/src/plugins/expressions/common/expression_renderers/types.ts @@ -105,4 +105,5 @@ export interface IInterpreterRenderHandlers { uiState?: unknown; getExecutionContext(): KibanaExecutionContext | undefined; + shouldShowLegendAction?: (actionId: string) => boolean; } diff --git a/src/plugins/expressions/public/loader.ts b/src/plugins/expressions/public/loader.ts index f10b8db1f1287..c3d7b1fb9920d 100644 --- a/src/plugins/expressions/public/loader.ts +++ b/src/plugins/expressions/public/loader.ts @@ -63,6 +63,7 @@ export class ExpressionLoader { hasCompatibleActions: params?.hasCompatibleActions, getCompatibleCellValueActions: params?.getCompatibleCellValueActions, executionContext: params?.executionContext, + shouldShowLegendAction: params?.shouldShowLegendAction, }); this.render$ = this.renderHandler.render$; this.update$ = this.renderHandler.update$; diff --git a/src/plugins/expressions/public/react_expression_renderer/react_expression_renderer.tsx b/src/plugins/expressions/public/react_expression_renderer/react_expression_renderer.tsx index 1d479bd9b4c1c..7c299e1bc7240 100644 --- a/src/plugins/expressions/public/react_expression_renderer/react_expression_renderer.tsx +++ b/src/plugins/expressions/public/react_expression_renderer/react_expression_renderer.tsx @@ -24,6 +24,7 @@ export interface ReactExpressionRendererProps error?: ExpressionRenderError | null ) => React.ReactElement | React.ReactElement[]; padding?: 'xs' | 's' | 'm' | 'l' | 'xl'; + shouldShowLegendAction?: (actionId: string) => boolean; } export type ReactExpressionRendererType = React.ComponentType; diff --git a/src/plugins/expressions/public/render.ts b/src/plugins/expressions/public/render.ts index a7b919625b8d6..6bb9c4d0836ba 100644 --- a/src/plugins/expressions/public/render.ts +++ b/src/plugins/expressions/public/render.ts @@ -36,6 +36,7 @@ export interface ExpressionRenderHandlerParams { hasCompatibleActions?: (event: ExpressionRendererEvent) => Promise; getCompatibleCellValueActions?: (data: object[]) => Promise; executionContext?: KibanaExecutionContext; + shouldShowLegendAction?: (actionId: string) => boolean; } type UpdateValue = IInterpreterRenderUpdateParams; @@ -66,6 +67,7 @@ export class ExpressionRenderHandler { hasCompatibleActions = async () => false, getCompatibleCellValueActions = async () => [], executionContext, + shouldShowLegendAction, }: ExpressionRenderHandlerParams = {} ) { this.element = element; @@ -118,6 +120,9 @@ export class ExpressionRenderHandler { }, hasCompatibleActions, getCompatibleCellValueActions, + shouldShowLegendAction: (actionId: string) => { + return shouldShowLegendAction?.(actionId) ?? true; + }, }; } diff --git a/src/plugins/expressions/public/types/index.ts b/src/plugins/expressions/public/types/index.ts index 870b44e9bc02c..a96c0629ce8a3 100644 --- a/src/plugins/expressions/public/types/index.ts +++ b/src/plugins/expressions/public/types/index.ts @@ -67,6 +67,7 @@ export interface IExpressionLoaderParams { * By default, it equals 1000. */ throttle?: number; + shouldShowLegendAction?: (actionId: string) => boolean; } export interface ExpressionRenderError extends Error { diff --git a/x-pack/plugins/lens/public/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/embeddable/embeddable.tsx index 1817a3eaa8175..75a869e617327 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable.tsx +++ b/x-pack/plugins/lens/public/embeddable/embeddable.tsx @@ -176,6 +176,7 @@ interface LensBaseEmbeddableInput extends EmbeddableInput { onTableRowClick?: ( data: Simplify ) => void; + shouldShowLegendAction?: (actionId: string) => boolean; } export type LensByValueInput = { @@ -1103,6 +1104,7 @@ export class Embeddable }} noPadding={this.visDisplayOptions.noPadding} docLinks={this.deps.coreStart.docLinks} + shouldShowLegendAction={input.shouldShowLegendAction} /> boolean; } export function ExpressionWrapper({ @@ -73,6 +74,7 @@ export function ExpressionWrapper({ lensInspector, noPadding, docLinks, + shouldShowLegendAction, }: ExpressionWrapperProps) { if (!expression) return null; return ( @@ -104,6 +106,7 @@ export function ExpressionWrapper({ onEvent={handleEvent} hasCompatibleActions={hasCompatibleActions} getCompatibleCellValueActions={getCompatibleCellValueActions} + shouldShowLegendAction={shouldShowLegendAction} /> diff --git a/x-pack/plugins/security_solution/public/actions/filter/index.ts b/x-pack/plugins/security_solution/public/actions/filter/index.ts index 3eadceb7d55b9..2759d5cc20d36 100644 --- a/x-pack/plugins/security_solution/public/actions/filter/index.ts +++ b/x-pack/plugins/security_solution/public/actions/filter/index.ts @@ -9,3 +9,7 @@ export { createFilterInCellActionFactory } from './cell_action/filter_in'; export { createFilterOutCellActionFactory } from './cell_action/filter_out'; export { createFilterInDiscoverCellActionFactory } from './discover/filter_in'; export { createFilterOutDiscoverCellActionFactory } from './discover/filter_out'; +export { createTimelineHistogramFilterInLegendActionFactory } from './lens/filter_in_timeline'; +export { createFilterInHistogramLegendActionFactory } from './lens/filter_in'; +export { createTimelineHistogramFilterOutLegendActionFactory } from './lens/filter_out_timeline'; +export { createFilterOutHistogramLegendActionFactory } from './lens/filter_out'; diff --git a/x-pack/plugins/security_solution/public/actions/filter/lens/filter_in.ts b/x-pack/plugins/security_solution/public/actions/filter/lens/filter_in.ts new file mode 100644 index 0000000000000..aee91a849d898 --- /dev/null +++ b/x-pack/plugins/security_solution/public/actions/filter/lens/filter_in.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { SecurityAppStore } from '../../../common/store'; + +import type { StartServices } from '../../../types'; +import { createHistogramFilterLegendActionFactory } from './helpers'; + +export const HISTOGRAM_LEGEND_ACTION_FILTER_IN = 'histogramLegendActionFilterIn'; + +export const createFilterInHistogramLegendActionFactory = ({ + store, + order, + services, +}: { + store: SecurityAppStore; + order: number; + services: StartServices; +}) => + createHistogramFilterLegendActionFactory({ + id: HISTOGRAM_LEGEND_ACTION_FILTER_IN, + order, + store, + services, + }); diff --git a/x-pack/plugins/security_solution/public/actions/filter/lens/filter_in_timeline.ts b/x-pack/plugins/security_solution/public/actions/filter/lens/filter_in_timeline.ts new file mode 100644 index 0000000000000..6721972f0bcfb --- /dev/null +++ b/x-pack/plugins/security_solution/public/actions/filter/lens/filter_in_timeline.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { SecurityAppStore } from '../../../common/store'; + +import type { StartServices } from '../../../types'; +import { createHistogramFilterLegendActionFactory } from './helpers'; + +export const TIMELINE_HISTOGRAM_LEGEND_ACTION_FILTER_IN = 'timelineHistogramLegendActionFilterIn'; + +export const createTimelineHistogramFilterInLegendActionFactory = ({ + store, + order, + services, +}: { + store: SecurityAppStore; + order: number; + services: StartServices; +}) => + createHistogramFilterLegendActionFactory({ + id: TIMELINE_HISTOGRAM_LEGEND_ACTION_FILTER_IN, + order, + store, + services, + }); diff --git a/x-pack/plugins/security_solution/public/actions/filter/lens/filter_out.ts b/x-pack/plugins/security_solution/public/actions/filter/lens/filter_out.ts new file mode 100644 index 0000000000000..4e32a3bee1b1f --- /dev/null +++ b/x-pack/plugins/security_solution/public/actions/filter/lens/filter_out.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { SecurityAppStore } from '../../../common/store'; + +import type { StartServices } from '../../../types'; +import { createHistogramFilterLegendActionFactory } from './helpers'; + +export const HISTOGRAM_LEGEND_ACTION_FILTER_OUT = 'histogramLegendActionFilterOut'; + +export const createFilterOutHistogramLegendActionFactory = ({ + store, + order, + services, +}: { + store: SecurityAppStore; + order: number; + services: StartServices; +}) => + createHistogramFilterLegendActionFactory({ + id: HISTOGRAM_LEGEND_ACTION_FILTER_OUT, + order, + store, + services, + negate: true, + }); diff --git a/x-pack/plugins/security_solution/public/actions/filter/lens/filter_out_timeline.ts b/x-pack/plugins/security_solution/public/actions/filter/lens/filter_out_timeline.ts new file mode 100644 index 0000000000000..1712c94c21b79 --- /dev/null +++ b/x-pack/plugins/security_solution/public/actions/filter/lens/filter_out_timeline.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { SecurityAppStore } from '../../../common/store'; + +import type { StartServices } from '../../../types'; +import { createHistogramFilterLegendActionFactory } from './helpers'; + +export const TIMELINE_HISTOGRAM_LEGEND_ACTION_FILTER_OUT = 'timelineHistogramLegendActionFilterOut'; + +export const createTimelineHistogramFilterOutLegendActionFactory = ({ + store, + order, + services, +}: { + store: SecurityAppStore; + order: number; + services: StartServices; +}) => + createHistogramFilterLegendActionFactory({ + id: TIMELINE_HISTOGRAM_LEGEND_ACTION_FILTER_OUT, + order, + store, + services, + negate: true, + }); diff --git a/x-pack/plugins/security_solution/public/actions/filter/lens/helpers.ts b/x-pack/plugins/security_solution/public/actions/filter/lens/helpers.ts new file mode 100644 index 0000000000000..d138561aab1b6 --- /dev/null +++ b/x-pack/plugins/security_solution/public/actions/filter/lens/helpers.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { addFilterIn, addFilterOut } from '@kbn/cell-actions'; +import { + isValueSupportedByDefaultActions, + valueToArray, + filterOutNullableValues, +} from '@kbn/cell-actions/src/actions/utils'; +import { isErrorEmbeddable } from '@kbn/embeddable-plugin/public'; +import type { CellValueContext } from '@kbn/embeddable-plugin/public'; +import { createAction } from '@kbn/ui-actions-plugin/public'; +import { ACTION_INCOMPATIBLE_VALUE_WARNING } from '@kbn/cell-actions/src/actions/translations'; +import { i18n } from '@kbn/i18n'; +import { KibanaServices } from '../../../common/lib/kibana'; +import { timelineSelectors } from '../../../timelines/store/timeline'; +import { fieldHasCellActions, isInSecurityApp, isLensEmbeddable } from '../../utils'; +import { TimelineId } from '../../../../common/types'; +import { SecurityCellActionType } from '../../constants'; +import type { SecurityAppStore } from '../../../common/store'; +import type { StartServices } from '../../../types'; +import { HISTOGRAM_LEGEND_ACTION_FILTER_IN } from './filter_in'; +import { HISTOGRAM_LEGEND_ACTION_FILTER_OUT } from './filter_out'; + +function isDataColumnsValid(data?: CellValueContext['data']): boolean { + return ( + !!data && + data.length > 0 && + data.every(({ columnMeta }) => columnMeta && fieldHasCellActions(columnMeta.field)) + ); +} + +export const createHistogramFilterLegendActionFactory = ({ + id, + order, + store, + services, + negate, +}: { + id: string; + order: number; + store: SecurityAppStore; + services: StartServices; + negate?: boolean; +}) => { + const { application: applicationService } = KibanaServices.get(); + let currentAppId: string | undefined; + applicationService.currentAppId$.subscribe((appId) => { + currentAppId = appId; + }); + const getTimelineById = timelineSelectors.getTimelineByIdSelector(); + const { notifications } = services; + const { filterManager } = services.data.query; + + return createAction({ + id, + order, + getIconType: () => (negate ? 'minusInCircle' : 'plusInCircle'), + getDisplayName: () => + negate + ? i18n.translate('xpack.securitySolution.actions.filterOutTimeline', { + defaultMessage: `Filter out`, + }) + : i18n.translate('xpack.securitySolution.actions.filterForTimeline', { + defaultMessage: `Filter for`, + }), + type: SecurityCellActionType.FILTER, + isCompatible: async ({ embeddable, data }) => + !isErrorEmbeddable(embeddable) && + isLensEmbeddable(embeddable) && + isDataColumnsValid(data) && + isInSecurityApp(currentAppId), + execute: async ({ data }) => { + const field = data[0]?.columnMeta?.field; + const rawValue = data[0]?.value; + const value = filterOutNullableValues(valueToArray(rawValue)); + + if (!isValueSupportedByDefaultActions(value)) { + notifications.toasts.addWarning({ + title: ACTION_INCOMPATIBLE_VALUE_WARNING, + }); + return; + } + + if (!field) return; + + const timeline = getTimelineById(store.getState(), TimelineId.active); + services.topValuesPopover.closePopover(); + + if (!negate) { + addFilterIn({ + filterManager: + id === HISTOGRAM_LEGEND_ACTION_FILTER_IN ? filterManager : timeline.filterManager, + fieldName: field, + value, + }); + } else { + addFilterOut({ + filterManager: + id === HISTOGRAM_LEGEND_ACTION_FILTER_OUT ? filterManager : timeline.filterManager, + fieldName: field, + value, + }); + } + }, + }); +}; diff --git a/x-pack/plugins/security_solution/public/actions/register.ts b/x-pack/plugins/security_solution/public/actions/register.ts index 6f154fba11b21..5aa0794ae9c49 100644 --- a/x-pack/plugins/security_solution/public/actions/register.ts +++ b/x-pack/plugins/security_solution/public/actions/register.ts @@ -13,8 +13,12 @@ import type { StartServices } from '../types'; import { createFilterInCellActionFactory, createFilterInDiscoverCellActionFactory, + createTimelineHistogramFilterInLegendActionFactory, + createFilterInHistogramLegendActionFactory, createFilterOutCellActionFactory, createFilterOutDiscoverCellActionFactory, + createFilterOutHistogramLegendActionFactory, + createTimelineHistogramFilterOutLegendActionFactory, } from './filter'; import { createAddToTimelineLensAction, @@ -53,11 +57,39 @@ export const registerUIActions = ( const registerLensEmbeddableActions = (store: SecurityAppStore, services: StartServices) => { const { uiActions } = services; - const addToTimelineAction = createAddToTimelineLensAction({ store, order: 1 }); + const addToTimelineAction = createAddToTimelineLensAction({ store, order: 4 }); uiActions.addTriggerAction(CELL_VALUE_TRIGGER, addToTimelineAction); - const copyToClipboardAction = createCopyToClipboardLensAction({ order: 2 }); + const copyToClipboardAction = createCopyToClipboardLensAction({ order: 5 }); uiActions.addTriggerAction(CELL_VALUE_TRIGGER, copyToClipboardAction); + + const filterInTimelineLegendActions = createTimelineHistogramFilterInLegendActionFactory({ + store, + order: 0, + services, + }); + uiActions.addTriggerAction(CELL_VALUE_TRIGGER, filterInTimelineLegendActions); + + const filterOutTimelineLegendActions = createTimelineHistogramFilterOutLegendActionFactory({ + store, + order: 1, + services, + }); + uiActions.addTriggerAction(CELL_VALUE_TRIGGER, filterOutTimelineLegendActions); + + const filterInLegendActions = createFilterInHistogramLegendActionFactory({ + store, + order: 2, + services, + }); + uiActions.addTriggerAction(CELL_VALUE_TRIGGER, filterInLegendActions); + + const filterOutLegendActions = createFilterOutHistogramLegendActionFactory({ + store, + order: 3, + services, + }); + uiActions.addTriggerAction(CELL_VALUE_TRIGGER, filterOutLegendActions); }; const registerDiscoverCellActions = (store: SecurityAppStore, services: StartServices) => { diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx index 943bf4dab0d80..df564277122ce 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx @@ -24,7 +24,7 @@ import { inputsSelectors } from '../../store'; import { useDeepEqualSelector } from '../../hooks/use_selector'; import { ModalInspectQuery } from '../inspect/modal'; import { InputsModelId } from '../../store/inputs/constants'; -import { getRequestsAndResponses } from './utils'; +import { getRequestsAndResponses, showLegendActionsByActionId } from './utils'; import { SourcererScopeName } from '../../store/sourcerer/model'; import { VisualizationActions } from './actions'; @@ -218,6 +218,11 @@ const LensEmbeddableComponent: React.FC = ({ [attributes?.state?.adHocDataViews] ); + const shouldShowLegendAction = useCallback( + (actionId: string) => showLegendActionsByActionId({ actionId, scopeId }), + [scopeId] + ); + if (!searchSessionId) { return null; } @@ -281,6 +286,7 @@ const LensEmbeddableComponent: React.FC = ({ showInspector={false} syncTooltips={false} syncCursor={false} + shouldShowLegendAction={shouldShowLegendAction} /> )} diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/utils.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/utils.ts index a934478218111..678cc16c915ef 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/utils.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/utils.ts @@ -6,10 +6,17 @@ */ import type { Filter } from '@kbn/es-query'; + import { SecurityPageName } from '../../../../common/constants'; +import { HISTOGRAM_LEGEND_ACTION_FILTER_IN } from '../../../actions/filter/lens/filter_in'; +import { TIMELINE_HISTOGRAM_LEGEND_ACTION_FILTER_IN } from '../../../actions/filter/lens/filter_in_timeline'; +import { HISTOGRAM_LEGEND_ACTION_FILTER_OUT } from '../../../actions/filter/lens/filter_out'; +import { TIMELINE_HISTOGRAM_LEGEND_ACTION_FILTER_OUT } from '../../../actions/filter/lens/filter_out_timeline'; import type { Request } from './types'; export const VISUALIZATION_ACTIONS_BUTTON_CLASS = 'histogram-actions-trigger'; +export const FILTER_IN_LEGEND_ACTION = `filterIn`; +export const FILTER_OUT_LEGEND_ACTION = `filterOut`; const pageFilterFieldMap: Record = { [SecurityPageName.hosts]: 'host', @@ -192,3 +199,28 @@ export const parseVisualizationData = (data: string[]): T[] => return acc; } }, [] as T[]); + +export const showLegendActionsByActionId = ({ + actionId, + scopeId, +}: { + actionId: string; + scopeId: string; +}) => { + switch (actionId) { + /** We no longer use Lens' default filter in / out actions + * as extra custom actions needed after filters applied. + * For example: hide the topN panel after filters applied */ + case FILTER_IN_LEGEND_ACTION: + case FILTER_OUT_LEGEND_ACTION: + return false; + case HISTOGRAM_LEGEND_ACTION_FILTER_IN: + case HISTOGRAM_LEGEND_ACTION_FILTER_OUT: + return scopeId !== 'timeline'; + case TIMELINE_HISTOGRAM_LEGEND_ACTION_FILTER_IN: + case TIMELINE_HISTOGRAM_LEGEND_ACTION_FILTER_OUT: + return scopeId === 'timeline'; + default: + return true; + } +}; diff --git a/x-pack/test/security_solution_cypress/cypress/screens/alerts.ts b/x-pack/test/security_solution_cypress/cypress/screens/alerts.ts index d0681a4348e06..f616b63e45e19 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/alerts.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/alerts.ts @@ -140,8 +140,10 @@ export const SELECT_HISTOGRAM = '[data-test-subj="chart-select-trend"]'; export const LEGEND_ACTIONS = { ADD_TO_TIMELINE: (ruleName: string) => `[data-test-subj="legend-${ruleName}-embeddable_addToTimeline"]`, - FILTER_FOR: (ruleName: string) => `[data-test-subj="legend-${ruleName}-filterIn"]`, - FILTER_OUT: (ruleName: string) => `[data-test-subj="legend-${ruleName}-filterOut"]`, + FILTER_FOR: (ruleName: string) => + `[data-test-subj="legend-${ruleName}-histogramLegendActionFilterIn"]`, + FILTER_OUT: (ruleName: string) => + `[data-test-subj="legend-${ruleName}-histogramLegendActionFilterOut"]`, COPY: (ruleName: string) => `[data-test-subj="legend-${ruleName}-embeddable_copyToClipboard"]`, }; From 1fa5d60ccaa149a6854b43853dca37ad22855887 Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Tue, 31 Oct 2023 12:50:18 -0500 Subject: [PATCH 15/21] skip failing test suite (#170239) --- .../__jest__/client_integration/home/enrich_policies.test.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/enrich_policies.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/home/enrich_policies.test.tsx index 9e6c1c7e998a0..6d0cafe0c938b 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/enrich_policies.test.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/enrich_policies.test.tsx @@ -31,7 +31,8 @@ jest.mock('@kbn/kibana-react-plugin/public', () => { }; }); -describe('Enrich policies tab', () => { +// Failing: See https://github.com/elastic/kibana/issues/170239 +describe.skip('Enrich policies tab', () => { const { httpSetup, httpRequestsMockHelpers, setDelayResponse } = setupEnvironment(); let testBed: EnrichPoliciesTestBed; From a509a3abf88e4ae6a8a1985b083b8c379024146d Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Tue, 31 Oct 2023 18:24:34 +0000 Subject: [PATCH 16/21] [ML] Allow temporary data views in AD job wizards (#170112) When creating a brand new job, temporary data views can be created and used in the wizard. When cloning a job where the data view cannot be found, a new temporary data view is created to be used in the wizard. This can happen if the data view used to create the original job has been deleted or the job was created with a temporary data view. https://github.com/elastic/kibana/assets/22172091/2b9c2125-2b0c-449d-a226-82267f64567b Also overrides the animation for the expanded rows in the AD jobs list which can cause strange behaviour when changing tabs in the expanded row. --------- Co-authored-by: Quynh Nguyen (Quinn) <43350163+qn895@users.noreply.github.com> --- .../components/anomalies_table/links_menu.tsx | 2 +- .../components/jobs_list/jobs_list.js | 1 + .../jobs/jobs_list/components/utils.js | 20 -------------- .../new_job/pages/index_or_search/page.tsx | 2 +- .../preconfigured_job_redirect.ts | 26 ++++++++++++++----- .../jobs/new_job/pages/new_job/page.tsx | 18 +++++++++---- .../ml/public/application/util/index_utils.ts | 19 +++++++++++++- .../translations/translations/fr-FR.json | 1 - .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - .../single_metric_job.ts | 10 ++++--- 11 files changed, 60 insertions(+), 41 deletions(-) diff --git a/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.tsx b/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.tsx index d65dd8955b8bc..96a8d1a358262 100644 --- a/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.tsx +++ b/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.tsx @@ -197,7 +197,7 @@ export const LinksMenuUI = (props: LinksMenuProps) => { const getDataViewId = async () => { const index = job.datafeed_config.indices[0]; - const dataViewId = await getDataViewIdFromName(index); + const dataViewId = await getDataViewIdFromName(index, job); // If data view doesn't exist for some reasons if (!dataViewId && !unmounted) { diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/jobs_list.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/jobs_list.js index 92913f7861108..b7988b0f71a81 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/jobs_list.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/jobs_list.js @@ -399,6 +399,7 @@ export class JobsList extends Component { rowProps={(item) => ({ 'data-test-subj': `mlJobListRow row-${item.id}`, })} + css={{ '.euiTableRow-isExpandedRow .euiTableCellContent': { animation: 'none' } }} /> ); } diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js index 9dfef0ff8b5a3..840b34ca70ceb 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js @@ -16,7 +16,6 @@ import { import { getApplication, getToastNotifications } from '../../../util/dependency_cache'; import { ml } from '../../../services/ml_api_service'; import { stringMatch } from '../../../util/string_utils'; -import { getDataViewNames } from '../../../util/index_utils'; import { JOB_STATE, DATAFEED_STATE } from '../../../../../common/constants/states'; import { JOB_ACTION } from '../../../../../common/constants/job_actions'; import { parseInterval } from '../../../../../common/util/parse_interval'; @@ -222,25 +221,6 @@ export async function cloneJob(jobId) { loadFullJob(jobId, false), ]); - const dataViewNames = await getDataViewNames(); - const dataViewTitle = datafeed.indices.join(','); - const jobIndicesAvailable = dataViewNames.includes(dataViewTitle); - - if (jobIndicesAvailable === false) { - const warningText = i18n.translate( - 'xpack.ml.jobsList.managementActions.noSourceDataViewForClone', - { - defaultMessage: - 'Unable to clone the anomaly detection job {jobId}. No data view exists for index {dataViewTitle}.', - values: { jobId, dataViewTitle }, - } - ); - getToastNotificationService().displayDangerToast(warningText, { - 'data-test-subj': 'mlCloneJobNoDataViewExistsWarningToast', - }); - return; - } - const createdBy = originalJob?.custom_settings?.created_by; if ( cloneableJob !== undefined && diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/index_or_search/page.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/index_or_search/page.tsx index 0bfa0ad5acdb7..0a53ac7b4a81e 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/index_or_search/page.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/index_or_search/page.tsx @@ -80,7 +80,7 @@ export const Page: FC = ({ nextStepPath }) => { uiSettings, }} > - + diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/index_or_search/preconfigured_job_redirect.ts b/x-pack/plugins/ml/public/application/jobs/new_job/pages/index_or_search/preconfigured_job_redirect.ts index 3f288d8f6191f..b13cde5e94ccd 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/index_or_search/preconfigured_job_redirect.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/index_or_search/preconfigured_job_redirect.ts @@ -8,7 +8,7 @@ import type { ApplicationStart } from '@kbn/core/public'; import type { DataViewsContract } from '@kbn/data-views-plugin/public'; import { mlJobService } from '../../../../services/job_service'; -import { Datafeed } from '../../../../../../common/types/anomaly_detection_jobs'; +import type { Job, Datafeed } from '../../../../../../common/types/anomaly_detection_jobs'; import { CREATED_BY_LABEL, JOB_TYPE } from '../../../../../../common/constants/new_job'; export async function preConfiguredJobRedirect( @@ -19,7 +19,7 @@ export async function preConfiguredJobRedirect( const { createdBy, job, datafeed } = mlJobService.tempJobCloningObjects; if (job && datafeed) { - const dataViewId = await getDataViewIdFromName(datafeed, dataViewsService); + const dataViewId = await getDataViewIdFromDatafeed(job, datafeed, dataViewsService); if (dataViewId === null) { return Promise.resolve(); } @@ -72,7 +72,8 @@ async function getWizardUrlFromCloningJob(createdBy: string | undefined, dataVie return `jobs/new_job/${page}?index=${dataViewId}&_g=()`; } -async function getDataViewIdFromName( +async function getDataViewIdFromDatafeed( + job: Job, datafeed: Datafeed, dataViewsService: DataViewsContract ): Promise { @@ -80,9 +81,20 @@ async function getDataViewIdFromName( throw new Error('Data views are not initialized!'); } - const [dv] = await dataViewsService?.find(datafeed.indices.join(',')); - if (!dv) { - return null; + const indexPattern = datafeed.indices.join(','); + + const dataViews = await dataViewsService?.find(indexPattern); + const dataView = dataViews.find((dv) => dv.getIndexPattern() === indexPattern); + if (dataView === undefined) { + // create a temporary data view if we can't find one + // matching the index pattern + const tempDataView = await dataViewsService.create({ + id: undefined, + name: indexPattern, + title: indexPattern, + timeFieldName: job.data_description.time_field!, + }); + return tempDataView.id ?? null; } - return dv.id ?? dv.title; + return dataView.id ?? null; } diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/new_job/page.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/new_job/page.tsx index 62c5841a94ff1..4974e8f6064ca 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/new_job/page.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/new_job/page.tsx @@ -232,11 +232,19 @@ export const Page: FC = ({ existingJobsAndGroups, jobType }) => {
- + {dataSourceContext.selectedDataView.isPersisted() ? ( + + ) : ( + + )} title); } -export async function getDataViewIdFromName(name: string): Promise { +/** + * Retrieves the data view ID from the given name. + * If a job is passed in, a temporary data view will be created if the requested data view doesn't exist. + * @param name - The name or index pattern of the data view. + * @param job - Optional job object. + * @returns The data view ID or null if it doesn't exist. + */ +export async function getDataViewIdFromName(name: string, job?: Job): Promise { const dataViewsService = getDataViews(); if (dataViewsService === null) { throw new Error('Data views are not initialized!'); @@ -28,6 +36,15 @@ export async function getDataViewIdFromName(name: string): Promise dv.getIndexPattern() === name); if (!dataView) { + if (job !== undefined) { + const tempDataView = await dataViewsService.create({ + id: undefined, + name, + title: name, + timeFieldName: job.data_description.time_field!, + }); + return tempDataView.id ?? null; + } return null; } return dataView.id ?? dataView.getIndexPattern(); diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index d1e0682fac22e..b32b2d085fb78 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -22522,7 +22522,6 @@ "xpack.ml.jobsList.jobDetails.forecastsTable.viewAriaLabel": "Afficher la prévision créée le {createdDate}", "xpack.ml.jobsList.jobFilterBar.invalidSearchErrorMessage": "Recherche non valide : {errorMessage}", "xpack.ml.jobsList.jobFilterBar.jobGroupTitle": "({jobsCount, plural, one {# tâche} many {# tâches} other {# tâches}})", - "xpack.ml.jobsList.managementActions.noSourceDataViewForClone": "Impossible de cloner la tâche de détection des anomalies {jobId}. Il n'existe aucune vue de données pour l'index {dataViewTitle}.", "xpack.ml.jobsList.missingSavedObjectWarning.link": " {link}", "xpack.ml.jobsList.multiJobActions.groupSelector.applyGroupsToJobTitle": "Appliquer des groupes {jobsCount, plural, one {tâche} many {tâches} other {aux tâches}}", "xpack.ml.jobsList.multiJobsActions.closeJobsLabel": "Fermer {jobsCount, plural, one {tâche} many {tâches} other {les tâches}}", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 582fe51373f9a..7a8949da3a233 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -22534,7 +22534,6 @@ "xpack.ml.jobsList.jobDetails.forecastsTable.viewAriaLabel": "{createdDate}に作成された予測を表示", "xpack.ml.jobsList.jobFilterBar.invalidSearchErrorMessage": "無効な検索:{errorMessage}", "xpack.ml.jobsList.jobFilterBar.jobGroupTitle": "({jobsCount, plural, other {#個のジョブ}})", - "xpack.ml.jobsList.managementActions.noSourceDataViewForClone": "異常検知ジョブ{jobId}を複製できません。インデックス{dataViewTitle}のデータビューは存在しません。", "xpack.ml.jobsList.missingSavedObjectWarning.link": " {link}", "xpack.ml.jobsList.multiJobActions.groupSelector.applyGroupsToJobTitle": "{jobsCount, plural, other {ジョブ}}にグループを適用", "xpack.ml.jobsList.multiJobsActions.closeJobsLabel": "{jobsCount, plural, other {ジョブ}}を閉じる", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 264c37f71ad83..21fddc2d98547 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -22533,7 +22533,6 @@ "xpack.ml.jobsList.jobDetails.forecastsTable.viewAriaLabel": "查看在 {createdDate} 创建的预测", "xpack.ml.jobsList.jobFilterBar.invalidSearchErrorMessage": "无效搜索:{errorMessage}", "xpack.ml.jobsList.jobFilterBar.jobGroupTitle": "({jobsCount, plural, other {# 个作业}})", - "xpack.ml.jobsList.managementActions.noSourceDataViewForClone": "无法克隆异常检测作业 {jobId}。对于索引 {dataViewTitle},不存在数据视图。", "xpack.ml.jobsList.missingSavedObjectWarning.link": " {link}", "xpack.ml.jobsList.multiJobActions.groupSelector.applyGroupsToJobTitle": "将组应用到{jobsCount, plural, other {作业}}", "xpack.ml.jobsList.multiJobsActions.closeJobsLabel": "关闭{jobsCount, plural, other {作业}}", diff --git a/x-pack/test/functional/apps/ml/anomaly_detection_jobs/single_metric_job.ts b/x-pack/test/functional/apps/ml/anomaly_detection_jobs/single_metric_job.ts index 1b61c36469d60..b1575f0fa57ba 100644 --- a/x-pack/test/functional/apps/ml/anomaly_detection_jobs/single_metric_job.ts +++ b/x-pack/test/functional/apps/ml/anomaly_detection_jobs/single_metric_job.ts @@ -228,7 +228,7 @@ export default function ({ getService }: FtrProviderContext) { await ml.api.assertDetectorResultsExist(jobId, 0); }); - it('job cloning fails in the single metric wizard if a matching data view does not exist', async () => { + it('job cloning creates a temporary data view and opens the single metric wizard if a matching data view does not exist', async () => { await ml.testExecution.logTestStep('delete data view used by job'); await ml.testResources.deleteIndexPatternByTitle(indexPatternString); @@ -236,15 +236,19 @@ export default function ({ getService }: FtrProviderContext) { await browser.refresh(); await ml.testExecution.logTestStep( - 'job cloning clicks the clone action and displays an error toast' + 'job cloning clicks the clone action and loads the single metric wizard' ); - await ml.jobTable.clickCloneJobActionWhenNoDataViewExists(jobId); + await ml.jobTable.clickCloneJobAction(jobId); + await ml.jobTypeSelection.assertSingleMetricJobWizardOpen(); }); it('job cloning opens the existing job in the single metric wizard', async () => { await ml.testExecution.logTestStep('recreate data view used by job'); await ml.testResources.createIndexPatternIfNeeded(indexPatternString, '@timestamp'); + await ml.navigation.navigateToMl(); + await ml.navigation.navigateToJobManagement(); + // Refresh page to ensure page has correct cache of data views await browser.refresh(); From fce380dadbf156d8f24f919c1bd680548653ce27 Mon Sep 17 00:00:00 2001 From: Abdul Wahab Zahid Date: Tue, 31 Oct 2023 19:45:32 +0100 Subject: [PATCH 17/21] [Synthetics] Improve reading user permissions (#169601) ## Summary The PR improves the way to determine what permissions user has. --- .../hooks/use_synthetics_priviliges.tsx | 5 +- .../public/hooks/use_capabilities.ts | 48 +++++++++++++++++++ .../empty_state/empty_state_error.tsx | 4 +- .../routes/index_state/get_index_status.ts | 18 ++++++- 4 files changed, 71 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_synthetics_priviliges.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_synthetics_priviliges.tsx index 26f77151010e9..812b540eea3ae 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_synthetics_priviliges.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_synthetics_priviliges.tsx @@ -19,6 +19,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { css } from '@emotion/react'; import { i18n } from '@kbn/i18n'; import { selectOverviewStatus } from '../state/overview_status'; +import { useCanReadSyntheticsIndex } from '../../../hooks/use_capabilities'; import { LICENSE_MISSING_ERROR, LICENSE_NOT_ACTIVE_ERROR, @@ -28,9 +29,11 @@ import { import { useSyntheticsSettingsContext } from '../contexts'; export const useSyntheticsPrivileges = () => { + const { canRead: canReadSyntheticsIndex, loading: isCanReadLoading } = + useCanReadSyntheticsIndex(); const { error } = useSelector(selectOverviewStatus); - if (error?.body?.message?.startsWith('MissingIndicesPrivileges:')) { + if (!isCanReadLoading && !canReadSyntheticsIndex) { return ( { @@ -23,3 +26,48 @@ export const useCanUsePublicLocations = (monLocations?: MonitorLocations) => { return !!canUsePublicLocations; }; + +export const useCanReadSyntheticsIndex = () => { + const { + services: { data: dataPublicPluginStart }, + } = useKibana<{ data: DataPublicPluginStart }>(); + + const { data, loading, status } = useFetcher< + Promise<{ canRead: boolean; error: undefined | unknown }> + >(() => { + return new Promise((resolve) => { + dataPublicPluginStart.search + .search( + { + terminate_after: 1, + params: { + index: SYNTHETICS_INDEX_PATTERN, + size: 0, + }, + }, + { + legacyHitsTotal: false, + } + ) + .subscribe({ + next: (_) => { + resolve({ canRead: true, error: undefined }); + }, + error: (error: { err: { statusCode: number } }) => { + if (error.err?.statusCode >= 400 && error.err?.statusCode < 500) { + resolve({ canRead: false, error }); + } else { + resolve({ canRead: true, error }); + } + }, + }); + }); + }, []); + + return { + canRead: data?.canRead, + error: data?.error, + loading, + status, + }; +}; diff --git a/x-pack/plugins/uptime/public/legacy_uptime/components/overview/empty_state/empty_state_error.tsx b/x-pack/plugins/uptime/public/legacy_uptime/components/overview/empty_state/empty_state_error.tsx index 224eaffa5f16a..86fe73feee4e3 100644 --- a/x-pack/plugins/uptime/public/legacy_uptime/components/overview/empty_state/empty_state_error.tsx +++ b/x-pack/plugins/uptime/public/legacy_uptime/components/overview/empty_state/empty_state_error.tsx @@ -16,7 +16,9 @@ interface EmptyStateErrorProps { export const EmptyStateError = ({ errors }: EmptyStateErrorProps) => { const unauthorized = errors.find( - (error) => error.message && error.message.includes('unauthorized') + (error) => + (error.message && error.message.includes('unauthorized')) || + (error.body?.message && error.body.message.includes('unauthorized')) ); return ( diff --git a/x-pack/plugins/uptime/server/legacy_uptime/routes/index_state/get_index_status.ts b/x-pack/plugins/uptime/server/legacy_uptime/routes/index_state/get_index_status.ts index 81a40541ced50..60c52e3aceca7 100644 --- a/x-pack/plugins/uptime/server/legacy_uptime/routes/index_state/get_index_status.ts +++ b/x-pack/plugins/uptime/server/legacy_uptime/routes/index_state/get_index_status.ts @@ -19,8 +19,22 @@ export const createGetIndexStatusRoute: UMRestApiRouteFactory = (libs: UMServerL to: schema.maybe(schema.string()), }), }, - handler: async ({ uptimeEsClient, request }): Promise => { + handler: async ({ uptimeEsClient, request, response }): Promise => { const { from, to } = request.query; - return await libs.requests.getIndexStatus({ uptimeEsClient, range: { from, to } }); + try { + return await libs.requests.getIndexStatus({ uptimeEsClient, range: { from, to } }); + } catch (e) { + if (e.meta?.statusCode === 403) { + return response.customError({ + statusCode: 403, + body: { + message: + 'unauthorized: You do not have the required permissions to read uptime indices', + }, + }); + } + + throw e; + } }, }); From 041961fc82ccb6de3dc0ad83e77f024ce7f37ced Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Tue, 31 Oct 2023 19:08:57 +0000 Subject: [PATCH 18/21] skip flaky suite (#169888) --- x-pack/plugins/osquery/cypress/e2e/all/live_query_packs.cy.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/osquery/cypress/e2e/all/live_query_packs.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/live_query_packs.cy.ts index cc6a367668b08..fb014737962f5 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/live_query_packs.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/live_query_packs.cy.ts @@ -18,7 +18,8 @@ import { LIVE_QUERY_EDITOR } from '../../screens/live_query'; import { loadPack, cleanupPack, cleanupCase, loadCase } from '../../tasks/api_fixtures'; import { ServerlessRoleName } from '../../support/roles'; -describe('ALL - Live Query Packs', { tags: ['@ess', '@serverless'] }, () => { +// FLAKY: https://github.com/elastic/kibana/issues/169888 +describe.skip('ALL - Live Query Packs', { tags: ['@ess', '@serverless'] }, () => { let packName: string; let packId: string; let caseId: string; From 9e0e6a86b238fb8f58e5d431d06e10741c111807 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Tue, 31 Oct 2023 19:10:26 +0000 Subject: [PATCH 19/21] skip flaky suite (#169958) --- .../public/management/cypress/e2e/endpoint_alerts.cy.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint_alerts.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint_alerts.cy.ts index 06b33141bad1b..e4f913d851735 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint_alerts.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint_alerts.cy.ts @@ -19,7 +19,8 @@ import { login, ROLE } from '../tasks/login'; import { EXECUTE_ROUTE } from '../../../../common/endpoint/constants'; import { waitForActionToComplete } from '../tasks/response_actions'; -describe('Endpoint generated alerts', { tags: ['@ess', '@serverless'] }, () => { +// FLAKY: https://github.com/elastic/kibana/issues/169958 +describe.skip('Endpoint generated alerts', { tags: ['@ess', '@serverless'] }, () => { let indexedPolicy: IndexedFleetEndpointPolicyResponse; let policy: PolicyData; let createdHost: CreateAndEnrollEndpointHostResponse; From 3cf19b2e4b8ddff151106f8dd7a5922866bd6fda Mon Sep 17 00:00:00 2001 From: James Rodewig Date: Tue, 31 Oct 2023 15:30:03 -0400 Subject: [PATCH 20/21] [DOCS] Remove duplicate saved objects OAS files (#170155) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- bundle/bundled.json | 326 ----------------------------------------- bundle/bundled.yaml | 209 -------------------------- bundle/entrypoint.json | 326 ----------------------------------------- bundle/entrypoint.yaml | 209 -------------------------- 4 files changed, 1070 deletions(-) delete mode 100644 bundle/bundled.json delete mode 100644 bundle/bundled.yaml delete mode 100644 bundle/entrypoint.json delete mode 100644 bundle/entrypoint.yaml diff --git a/bundle/bundled.json b/bundle/bundled.json deleted file mode 100644 index 21c305d6f4216..0000000000000 --- a/bundle/bundled.json +++ /dev/null @@ -1,326 +0,0 @@ -{ - "openapi": "3.1.0", - "info": { - "title": "Saved objects", - "description": "OpenAPI schema for saved object endpoints", - "version": "0.1", - "contact": { - "name": "Kibana Core Team" - }, - "license": { - "name": "Elastic License 2.0", - "url": "https://www.elastic.co/licensing/elastic-license" - } - }, - "servers": [ - { - "url": "http://localhost:5601", - "description": "local" - } - ], - "security": [ - { - "basicAuth": [] - }, - { - "apiKeyAuth": [] - } - ], - "tags": [ - { - "name": "saved objects", - "description": "Manage Kibana saved objects, including dashboards, visualizations, and more." - } - ], - "paths": { - "/api/saved_objects/_export": { - "post": { - "summary": "Retrieve sets of saved objects that you want to import into Kibana.", - "operationId": "exportSavedObjects", - "description": "This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. NOTE: The `savedObjects.maxImportExportSize` configuration setting limits the number of saved objects which may be exported.\n", - "tags": [ - "saved objects" - ], - "parameters": [ - { - "$ref": "#/components/parameters/kbn_xsrf" - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "anyOf": [ - { - "required": [ - "type" - ] - }, - { - "required": [ - "objects" - ] - } - ], - "properties": { - "excludeExportDetails": { - "description": "Do not add export details entry at the end of the stream.", - "type": "boolean", - "default": false - }, - "includeReferencesDeep": { - "description": "Includes all of the referenced objects in the exported objects.", - "type": "boolean" - }, - "objects": { - "description": "A list of objects to export.", - "type": "array", - "items": { - "type": "object" - } - }, - "type": { - "description": "The saved object types to include in the export.", - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - } - } - }, - "examples": { - "exportSavedObjectsRequest": { - "$ref": "#/components/examples/export_objects_request" - } - } - } - } - }, - "responses": { - "200": { - "description": "Indicates a successful call.", - "content": { - "multipart/form-data": { - "schema": { - "type": "string" - }, - "examples": { - "exportSavedObjectsResponse": { - "$ref": "#/components/examples/export_objects_response" - } - } - } - } - }, - "400": { - "description": "Bad request.", - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } - } - } - } - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "/api/saved_objects/_import": { - "post": { - "summary": "Create sets of Kibana saved objects from a file created by the export API.", - "operationId": "importSavedObjects", - "description": "This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. Saved objects can be imported only into the same version, a newer minor on the same major, or the next major. Exported saved objects are not backwards compatible and cannot be imported into an older version of Kibana.\n", - "tags": [ - "saved objects" - ], - "parameters": [ - { - "$ref": "#/components/parameters/kbn_xsrf" - }, - { - "in": "query", - "name": "compatibilityMode", - "schema": { - "type": "boolean" - }, - "required": false, - "description": "Applies various adjustments to the saved objects that are being imported to maintain compatibility between different Kibana versions. Use this option only if you encounter issues with imported saved objects. NOTE: This option cannot be used with the `createNewCopies` option.\n" - }, - { - "in": "query", - "name": "createNewCopies", - "schema": { - "type": "boolean" - }, - "required": false, - "description": "Creates copies of saved objects, regenerates each object ID, and resets the origin. When used, potential conflict errors are avoided. NOTE: This option cannot be used with the `overwrite` and `compatibilityMode` options.\n" - }, - { - "in": "query", - "name": "overwrite", - "schema": { - "type": "boolean" - }, - "required": false, - "description": "Overwrites saved objects when they already exist. When used, potential conflict errors are automatically resolved by overwriting the destination object. NOTE: This option cannot be used with the `createNewCopies` option.\n" - } - ], - "requestBody": { - "required": true, - "content": { - "multipart/form-data": { - "schema": { - "type": "object", - "description": "A file exported using the export API. NOTE: The `savedObjects.maxImportExportSize` configuration setting limits the number of saved objects which may be included in this file. Similarly, the `savedObjects.maxImportPayloadBytes` setting limits the overall size of the file that can be imported.\n" - } - } - } - }, - "responses": { - "200": { - "description": "Indicates a successful call.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "errors": { - "type": "array", - "description": "Indicates the import was unsuccessful and specifies the objects that failed to import. One object may result in multiple errors, which requires separate steps to resolve. For instance, a `missing_references` error and conflict error.\n" - }, - "success": { - "type": "boolean", - "description": "Indicates when the import was successfully completed. When set to false, some objects may not have been created. For additional information, refer to the `errors` and `successResults` properties.\n" - }, - "successCount": { - "type": "integer", - "description": "Indicates the number of successfully imported records." - }, - "successResults": { - "type": "array", - "items": { - "type": "object" - }, - "description": "Indicates the objects that are successfully imported, with any metadata if applicable. Objects are created only when all resolvable errors are addressed, including conflicts and missing references. If objects are created as new copies, each entry in the `successResults` array includes a `destinationId` attribute.\n" - }, - "warnings": { - "type": "array" - } - } - }, - "examples": { - "importObjectsResponse": { - "$ref": "#/components/examples/import_objects_response" - } - } - } - } - }, - "400": { - "description": "Bad request.", - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } - } - } - } - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - } - }, - "components": { - "securitySchemes": { - "basicAuth": { - "type": "http", - "scheme": "basic" - }, - "apiKeyAuth": { - "type": "apiKey", - "in": "header", - "name": "ApiKey" - } - }, - "parameters": { - "kbn_xsrf": { - "schema": { - "type": "string" - }, - "in": "header", - "name": "kbn-xsrf", - "description": "Cross-site request forgery protection", - "required": true - } - }, - "examples": { - "export_objects_request": { - "summary": "Export a specific saved object.", - "value": { - "objects": [ - { - "type": "index-pattern", - "id": "90943e30-9a47-11e8-b64d-95841ca0b247" - } - ], - "includeReferencesDeep": false - } - }, - "export_objects_response": { - "summary": "The export objects API response contains a JSON record for each exported object and an export result details record.", - "value": "{\"attributes\":{\"fieldFormatMap\":\"{\\\"hour_of_day\\\":{}}\",\"name\":\"Kibana Sample Data Logs\",\"runtimeFieldMap\":\"{\\\"hour_of_day\\\":{\\\"type\\\":\\\"long\\\",\\\"script\\\":{\\\"source\\\":\\\"emit(doc['timestamp'].value.getHour());\\\"}}}\",\"timeFieldName\":\"timestamp\",\"title\":\"kibana_sample_data_logs\"},\"coreMigrationVersion\":\"8.8.0\",\"created_at\":\"2023-07-25T19:36:36.695Z\",\"id\":\"90943e30-9a47-11e8-b64d-95841ca0b247\",\"managed\":false,\"references\":[],\"type\":\"index-pattern\",\"typeMigrationVersion\":\"8.0.0\",\"updated_at\":\"2023-07-25T19:36:36.695Z\",\"version\":\"WzM5LDJd\"}\n{\"excludedObjects\":[],\"excludedObjectsCount\":0,\"exportedCount\":1,\"missingRefCount\":0,\"missingReferences\":[]}\n" - }, - "import_objects_response": { - "summary": "The import objects API response indicates a successful import and the objects are created. Since these objects are created as new copies, each entry in the successResults array includes a destinationId attribute.", - "value": { - "successCount": 1, - "success": true, - "warnings": [], - "successResults": [ - { - "type": "index-pattern", - "id": "90943e30-9a47-11e8-b64d-95841ca0b247", - "meta": { - "title": "Kibana Sample Data Logs", - "icon": "indexPatternApp" - }, - "managed": false, - "destinationId": "82d2760c-468f-49cf-83aa-b9a35b6a8943" - } - ] - } - } - } - } -} \ No newline at end of file diff --git a/bundle/bundled.yaml b/bundle/bundled.yaml deleted file mode 100644 index 0facbd5446640..0000000000000 --- a/bundle/bundled.yaml +++ /dev/null @@ -1,209 +0,0 @@ -openapi: 3.1.0 -info: - title: Saved objects - description: OpenAPI schema for saved object endpoints - version: '0.1' - contact: - name: Kibana Core Team - license: - name: Elastic License 2.0 - url: https://www.elastic.co/licensing/elastic-license -servers: - - url: http://localhost:5601 - description: local -security: - - basicAuth: [] - - apiKeyAuth: [] -tags: - - name: saved objects - description: Manage Kibana saved objects, including dashboards, visualizations, and more. -paths: - /api/saved_objects/_export: - post: - summary: Retrieve sets of saved objects that you want to import into Kibana. - operationId: exportSavedObjects - description: | - This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. NOTE: The `savedObjects.maxImportExportSize` configuration setting limits the number of saved objects which may be exported. - tags: - - saved objects - parameters: - - $ref: '#/components/parameters/kbn_xsrf' - requestBody: - required: true - content: - application/json: - schema: - type: object - anyOf: - - required: - - type - - required: - - objects - properties: - excludeExportDetails: - description: Do not add export details entry at the end of the stream. - type: boolean - default: false - includeReferencesDeep: - description: Includes all of the referenced objects in the exported objects. - type: boolean - objects: - description: A list of objects to export. - type: array - items: - type: object - type: - description: The saved object types to include in the export. - oneOf: - - type: string - - type: array - items: - type: string - examples: - exportSavedObjectsRequest: - $ref: '#/components/examples/export_objects_request' - responses: - '200': - description: Indicates a successful call. - content: - multipart/form-data: - schema: - type: string - examples: - exportSavedObjectsResponse: - $ref: '#/components/examples/export_objects_response' - '400': - description: Bad request. - content: - application/json: - schema: - type: object - additionalProperties: true - servers: - - url: https://localhost:5601 - servers: - - url: https://localhost:5601 - /api/saved_objects/_import: - post: - summary: Create sets of Kibana saved objects from a file created by the export API. - operationId: importSavedObjects - description: | - This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. Saved objects can be imported only into the same version, a newer minor on the same major, or the next major. Exported saved objects are not backwards compatible and cannot be imported into an older version of Kibana. - tags: - - saved objects - parameters: - - $ref: '#/components/parameters/kbn_xsrf' - - in: query - name: compatibilityMode - schema: - type: boolean - required: false - description: | - Applies various adjustments to the saved objects that are being imported to maintain compatibility between different Kibana versions. Use this option only if you encounter issues with imported saved objects. NOTE: This option cannot be used with the `createNewCopies` option. - - in: query - name: createNewCopies - schema: - type: boolean - required: false - description: | - Creates copies of saved objects, regenerates each object ID, and resets the origin. When used, potential conflict errors are avoided. NOTE: This option cannot be used with the `overwrite` and `compatibilityMode` options. - - in: query - name: overwrite - schema: - type: boolean - required: false - description: | - Overwrites saved objects when they already exist. When used, potential conflict errors are automatically resolved by overwriting the destination object. NOTE: This option cannot be used with the `createNewCopies` option. - requestBody: - required: true - content: - multipart/form-data: - schema: - type: object - description: | - A file exported using the export API. NOTE: The `savedObjects.maxImportExportSize` configuration setting limits the number of saved objects which may be included in this file. Similarly, the `savedObjects.maxImportPayloadBytes` setting limits the overall size of the file that can be imported. - responses: - '200': - description: Indicates a successful call. - content: - application/json: - schema: - type: object - properties: - errors: - type: array - description: | - Indicates the import was unsuccessful and specifies the objects that failed to import. One object may result in multiple errors, which requires separate steps to resolve. For instance, a `missing_references` error and conflict error. - success: - type: boolean - description: | - Indicates when the import was successfully completed. When set to false, some objects may not have been created. For additional information, refer to the `errors` and `successResults` properties. - successCount: - type: integer - description: Indicates the number of successfully imported records. - successResults: - type: array - items: - type: object - description: | - Indicates the objects that are successfully imported, with any metadata if applicable. Objects are created only when all resolvable errors are addressed, including conflicts and missing references. If objects are created as new copies, each entry in the `successResults` array includes a `destinationId` attribute. - warnings: - type: array - examples: - importObjectsResponse: - $ref: '#/components/examples/import_objects_response' - '400': - description: Bad request. - content: - application/json: - schema: - type: object - additionalProperties: true - servers: - - url: https://localhost:5601 - servers: - - url: https://localhost:5601 -components: - securitySchemes: - basicAuth: - type: http - scheme: basic - apiKeyAuth: - type: apiKey - in: header - name: ApiKey - parameters: - kbn_xsrf: - schema: - type: string - in: header - name: kbn-xsrf - description: Cross-site request forgery protection - required: true - examples: - export_objects_request: - summary: Export a specific saved object. - value: - objects: - - type: index-pattern - id: 90943e30-9a47-11e8-b64d-95841ca0b247 - includeReferencesDeep: false - export_objects_response: - summary: The export objects API response contains a JSON record for each exported object and an export result details record. - value: | - {"attributes":{"fieldFormatMap":"{\"hour_of_day\":{}}","name":"Kibana Sample Data Logs","runtimeFieldMap":"{\"hour_of_day\":{\"type\":\"long\",\"script\":{\"source\":\"emit(doc['timestamp'].value.getHour());\"}}}","timeFieldName":"timestamp","title":"kibana_sample_data_logs"},"coreMigrationVersion":"8.8.0","created_at":"2023-07-25T19:36:36.695Z","id":"90943e30-9a47-11e8-b64d-95841ca0b247","managed":false,"references":[],"type":"index-pattern","typeMigrationVersion":"8.0.0","updated_at":"2023-07-25T19:36:36.695Z","version":"WzM5LDJd"} - {"excludedObjects":[],"excludedObjectsCount":0,"exportedCount":1,"missingRefCount":0,"missingReferences":[]} - import_objects_response: - summary: The import objects API response indicates a successful import and the objects are created. Since these objects are created as new copies, each entry in the successResults array includes a destinationId attribute. - value: - successCount: 1 - success: true - warnings: [] - successResults: - - type: index-pattern - id: 90943e30-9a47-11e8-b64d-95841ca0b247 - meta: - title: Kibana Sample Data Logs - icon: indexPatternApp - managed: false - destinationId: 82d2760c-468f-49cf-83aa-b9a35b6a8943 diff --git a/bundle/entrypoint.json b/bundle/entrypoint.json deleted file mode 100644 index 21c305d6f4216..0000000000000 --- a/bundle/entrypoint.json +++ /dev/null @@ -1,326 +0,0 @@ -{ - "openapi": "3.1.0", - "info": { - "title": "Saved objects", - "description": "OpenAPI schema for saved object endpoints", - "version": "0.1", - "contact": { - "name": "Kibana Core Team" - }, - "license": { - "name": "Elastic License 2.0", - "url": "https://www.elastic.co/licensing/elastic-license" - } - }, - "servers": [ - { - "url": "http://localhost:5601", - "description": "local" - } - ], - "security": [ - { - "basicAuth": [] - }, - { - "apiKeyAuth": [] - } - ], - "tags": [ - { - "name": "saved objects", - "description": "Manage Kibana saved objects, including dashboards, visualizations, and more." - } - ], - "paths": { - "/api/saved_objects/_export": { - "post": { - "summary": "Retrieve sets of saved objects that you want to import into Kibana.", - "operationId": "exportSavedObjects", - "description": "This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. NOTE: The `savedObjects.maxImportExportSize` configuration setting limits the number of saved objects which may be exported.\n", - "tags": [ - "saved objects" - ], - "parameters": [ - { - "$ref": "#/components/parameters/kbn_xsrf" - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "anyOf": [ - { - "required": [ - "type" - ] - }, - { - "required": [ - "objects" - ] - } - ], - "properties": { - "excludeExportDetails": { - "description": "Do not add export details entry at the end of the stream.", - "type": "boolean", - "default": false - }, - "includeReferencesDeep": { - "description": "Includes all of the referenced objects in the exported objects.", - "type": "boolean" - }, - "objects": { - "description": "A list of objects to export.", - "type": "array", - "items": { - "type": "object" - } - }, - "type": { - "description": "The saved object types to include in the export.", - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - } - } - }, - "examples": { - "exportSavedObjectsRequest": { - "$ref": "#/components/examples/export_objects_request" - } - } - } - } - }, - "responses": { - "200": { - "description": "Indicates a successful call.", - "content": { - "multipart/form-data": { - "schema": { - "type": "string" - }, - "examples": { - "exportSavedObjectsResponse": { - "$ref": "#/components/examples/export_objects_response" - } - } - } - } - }, - "400": { - "description": "Bad request.", - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } - } - } - } - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "/api/saved_objects/_import": { - "post": { - "summary": "Create sets of Kibana saved objects from a file created by the export API.", - "operationId": "importSavedObjects", - "description": "This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. Saved objects can be imported only into the same version, a newer minor on the same major, or the next major. Exported saved objects are not backwards compatible and cannot be imported into an older version of Kibana.\n", - "tags": [ - "saved objects" - ], - "parameters": [ - { - "$ref": "#/components/parameters/kbn_xsrf" - }, - { - "in": "query", - "name": "compatibilityMode", - "schema": { - "type": "boolean" - }, - "required": false, - "description": "Applies various adjustments to the saved objects that are being imported to maintain compatibility between different Kibana versions. Use this option only if you encounter issues with imported saved objects. NOTE: This option cannot be used with the `createNewCopies` option.\n" - }, - { - "in": "query", - "name": "createNewCopies", - "schema": { - "type": "boolean" - }, - "required": false, - "description": "Creates copies of saved objects, regenerates each object ID, and resets the origin. When used, potential conflict errors are avoided. NOTE: This option cannot be used with the `overwrite` and `compatibilityMode` options.\n" - }, - { - "in": "query", - "name": "overwrite", - "schema": { - "type": "boolean" - }, - "required": false, - "description": "Overwrites saved objects when they already exist. When used, potential conflict errors are automatically resolved by overwriting the destination object. NOTE: This option cannot be used with the `createNewCopies` option.\n" - } - ], - "requestBody": { - "required": true, - "content": { - "multipart/form-data": { - "schema": { - "type": "object", - "description": "A file exported using the export API. NOTE: The `savedObjects.maxImportExportSize` configuration setting limits the number of saved objects which may be included in this file. Similarly, the `savedObjects.maxImportPayloadBytes` setting limits the overall size of the file that can be imported.\n" - } - } - } - }, - "responses": { - "200": { - "description": "Indicates a successful call.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "errors": { - "type": "array", - "description": "Indicates the import was unsuccessful and specifies the objects that failed to import. One object may result in multiple errors, which requires separate steps to resolve. For instance, a `missing_references` error and conflict error.\n" - }, - "success": { - "type": "boolean", - "description": "Indicates when the import was successfully completed. When set to false, some objects may not have been created. For additional information, refer to the `errors` and `successResults` properties.\n" - }, - "successCount": { - "type": "integer", - "description": "Indicates the number of successfully imported records." - }, - "successResults": { - "type": "array", - "items": { - "type": "object" - }, - "description": "Indicates the objects that are successfully imported, with any metadata if applicable. Objects are created only when all resolvable errors are addressed, including conflicts and missing references. If objects are created as new copies, each entry in the `successResults` array includes a `destinationId` attribute.\n" - }, - "warnings": { - "type": "array" - } - } - }, - "examples": { - "importObjectsResponse": { - "$ref": "#/components/examples/import_objects_response" - } - } - } - } - }, - "400": { - "description": "Bad request.", - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": true - } - } - } - } - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - }, - "servers": [ - { - "url": "https://localhost:5601" - } - ] - } - }, - "components": { - "securitySchemes": { - "basicAuth": { - "type": "http", - "scheme": "basic" - }, - "apiKeyAuth": { - "type": "apiKey", - "in": "header", - "name": "ApiKey" - } - }, - "parameters": { - "kbn_xsrf": { - "schema": { - "type": "string" - }, - "in": "header", - "name": "kbn-xsrf", - "description": "Cross-site request forgery protection", - "required": true - } - }, - "examples": { - "export_objects_request": { - "summary": "Export a specific saved object.", - "value": { - "objects": [ - { - "type": "index-pattern", - "id": "90943e30-9a47-11e8-b64d-95841ca0b247" - } - ], - "includeReferencesDeep": false - } - }, - "export_objects_response": { - "summary": "The export objects API response contains a JSON record for each exported object and an export result details record.", - "value": "{\"attributes\":{\"fieldFormatMap\":\"{\\\"hour_of_day\\\":{}}\",\"name\":\"Kibana Sample Data Logs\",\"runtimeFieldMap\":\"{\\\"hour_of_day\\\":{\\\"type\\\":\\\"long\\\",\\\"script\\\":{\\\"source\\\":\\\"emit(doc['timestamp'].value.getHour());\\\"}}}\",\"timeFieldName\":\"timestamp\",\"title\":\"kibana_sample_data_logs\"},\"coreMigrationVersion\":\"8.8.0\",\"created_at\":\"2023-07-25T19:36:36.695Z\",\"id\":\"90943e30-9a47-11e8-b64d-95841ca0b247\",\"managed\":false,\"references\":[],\"type\":\"index-pattern\",\"typeMigrationVersion\":\"8.0.0\",\"updated_at\":\"2023-07-25T19:36:36.695Z\",\"version\":\"WzM5LDJd\"}\n{\"excludedObjects\":[],\"excludedObjectsCount\":0,\"exportedCount\":1,\"missingRefCount\":0,\"missingReferences\":[]}\n" - }, - "import_objects_response": { - "summary": "The import objects API response indicates a successful import and the objects are created. Since these objects are created as new copies, each entry in the successResults array includes a destinationId attribute.", - "value": { - "successCount": 1, - "success": true, - "warnings": [], - "successResults": [ - { - "type": "index-pattern", - "id": "90943e30-9a47-11e8-b64d-95841ca0b247", - "meta": { - "title": "Kibana Sample Data Logs", - "icon": "indexPatternApp" - }, - "managed": false, - "destinationId": "82d2760c-468f-49cf-83aa-b9a35b6a8943" - } - ] - } - } - } - } -} \ No newline at end of file diff --git a/bundle/entrypoint.yaml b/bundle/entrypoint.yaml deleted file mode 100644 index 0facbd5446640..0000000000000 --- a/bundle/entrypoint.yaml +++ /dev/null @@ -1,209 +0,0 @@ -openapi: 3.1.0 -info: - title: Saved objects - description: OpenAPI schema for saved object endpoints - version: '0.1' - contact: - name: Kibana Core Team - license: - name: Elastic License 2.0 - url: https://www.elastic.co/licensing/elastic-license -servers: - - url: http://localhost:5601 - description: local -security: - - basicAuth: [] - - apiKeyAuth: [] -tags: - - name: saved objects - description: Manage Kibana saved objects, including dashboards, visualizations, and more. -paths: - /api/saved_objects/_export: - post: - summary: Retrieve sets of saved objects that you want to import into Kibana. - operationId: exportSavedObjects - description: | - This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. NOTE: The `savedObjects.maxImportExportSize` configuration setting limits the number of saved objects which may be exported. - tags: - - saved objects - parameters: - - $ref: '#/components/parameters/kbn_xsrf' - requestBody: - required: true - content: - application/json: - schema: - type: object - anyOf: - - required: - - type - - required: - - objects - properties: - excludeExportDetails: - description: Do not add export details entry at the end of the stream. - type: boolean - default: false - includeReferencesDeep: - description: Includes all of the referenced objects in the exported objects. - type: boolean - objects: - description: A list of objects to export. - type: array - items: - type: object - type: - description: The saved object types to include in the export. - oneOf: - - type: string - - type: array - items: - type: string - examples: - exportSavedObjectsRequest: - $ref: '#/components/examples/export_objects_request' - responses: - '200': - description: Indicates a successful call. - content: - multipart/form-data: - schema: - type: string - examples: - exportSavedObjectsResponse: - $ref: '#/components/examples/export_objects_response' - '400': - description: Bad request. - content: - application/json: - schema: - type: object - additionalProperties: true - servers: - - url: https://localhost:5601 - servers: - - url: https://localhost:5601 - /api/saved_objects/_import: - post: - summary: Create sets of Kibana saved objects from a file created by the export API. - operationId: importSavedObjects - description: | - This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. Saved objects can be imported only into the same version, a newer minor on the same major, or the next major. Exported saved objects are not backwards compatible and cannot be imported into an older version of Kibana. - tags: - - saved objects - parameters: - - $ref: '#/components/parameters/kbn_xsrf' - - in: query - name: compatibilityMode - schema: - type: boolean - required: false - description: | - Applies various adjustments to the saved objects that are being imported to maintain compatibility between different Kibana versions. Use this option only if you encounter issues with imported saved objects. NOTE: This option cannot be used with the `createNewCopies` option. - - in: query - name: createNewCopies - schema: - type: boolean - required: false - description: | - Creates copies of saved objects, regenerates each object ID, and resets the origin. When used, potential conflict errors are avoided. NOTE: This option cannot be used with the `overwrite` and `compatibilityMode` options. - - in: query - name: overwrite - schema: - type: boolean - required: false - description: | - Overwrites saved objects when they already exist. When used, potential conflict errors are automatically resolved by overwriting the destination object. NOTE: This option cannot be used with the `createNewCopies` option. - requestBody: - required: true - content: - multipart/form-data: - schema: - type: object - description: | - A file exported using the export API. NOTE: The `savedObjects.maxImportExportSize` configuration setting limits the number of saved objects which may be included in this file. Similarly, the `savedObjects.maxImportPayloadBytes` setting limits the overall size of the file that can be imported. - responses: - '200': - description: Indicates a successful call. - content: - application/json: - schema: - type: object - properties: - errors: - type: array - description: | - Indicates the import was unsuccessful and specifies the objects that failed to import. One object may result in multiple errors, which requires separate steps to resolve. For instance, a `missing_references` error and conflict error. - success: - type: boolean - description: | - Indicates when the import was successfully completed. When set to false, some objects may not have been created. For additional information, refer to the `errors` and `successResults` properties. - successCount: - type: integer - description: Indicates the number of successfully imported records. - successResults: - type: array - items: - type: object - description: | - Indicates the objects that are successfully imported, with any metadata if applicable. Objects are created only when all resolvable errors are addressed, including conflicts and missing references. If objects are created as new copies, each entry in the `successResults` array includes a `destinationId` attribute. - warnings: - type: array - examples: - importObjectsResponse: - $ref: '#/components/examples/import_objects_response' - '400': - description: Bad request. - content: - application/json: - schema: - type: object - additionalProperties: true - servers: - - url: https://localhost:5601 - servers: - - url: https://localhost:5601 -components: - securitySchemes: - basicAuth: - type: http - scheme: basic - apiKeyAuth: - type: apiKey - in: header - name: ApiKey - parameters: - kbn_xsrf: - schema: - type: string - in: header - name: kbn-xsrf - description: Cross-site request forgery protection - required: true - examples: - export_objects_request: - summary: Export a specific saved object. - value: - objects: - - type: index-pattern - id: 90943e30-9a47-11e8-b64d-95841ca0b247 - includeReferencesDeep: false - export_objects_response: - summary: The export objects API response contains a JSON record for each exported object and an export result details record. - value: | - {"attributes":{"fieldFormatMap":"{\"hour_of_day\":{}}","name":"Kibana Sample Data Logs","runtimeFieldMap":"{\"hour_of_day\":{\"type\":\"long\",\"script\":{\"source\":\"emit(doc['timestamp'].value.getHour());\"}}}","timeFieldName":"timestamp","title":"kibana_sample_data_logs"},"coreMigrationVersion":"8.8.0","created_at":"2023-07-25T19:36:36.695Z","id":"90943e30-9a47-11e8-b64d-95841ca0b247","managed":false,"references":[],"type":"index-pattern","typeMigrationVersion":"8.0.0","updated_at":"2023-07-25T19:36:36.695Z","version":"WzM5LDJd"} - {"excludedObjects":[],"excludedObjectsCount":0,"exportedCount":1,"missingRefCount":0,"missingReferences":[]} - import_objects_response: - summary: The import objects API response indicates a successful import and the objects are created. Since these objects are created as new copies, each entry in the successResults array includes a destinationId attribute. - value: - successCount: 1 - success: true - warnings: [] - successResults: - - type: index-pattern - id: 90943e30-9a47-11e8-b64d-95841ca0b247 - meta: - title: Kibana Sample Data Logs - icon: indexPatternApp - managed: false - destinationId: 82d2760c-468f-49cf-83aa-b9a35b6a8943 From ce430e56f19b13e0807608d36ca94ae347c4d48f Mon Sep 17 00:00:00 2001 From: Zacqary Adam Xeper Date: Tue, 31 Oct 2023 14:54:57 -0500 Subject: [PATCH 21/21] [RAM] Add HTTP versioning to resolve, snooze, and unsnooze APIs (#168886) ## Summary **REOPENED version of https://github.com/elastic/kibana/pull/163359, git history got too complicated and triggered too many codeowners** Part of #157883 Converts `_resolve`, `_snooze`, `_unsnooze` to new HTTP versioned model ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../routes/r_rule/response/schemas/v1.ts | 12 +- .../routes/rule/apis/bulk_edit/index.ts | 14 +- .../routes/rule/apis/bulk_edit/schemas/v1.ts | 32 +---- .../routes/rule/apis/bulk_edit/types/v1.ts | 3 +- .../common/routes/rule/apis/create/index.ts | 2 - .../routes/rule/apis/create/schemas/v1.ts | 22 +-- .../common/routes/rule/apis/resolve/index.ts | 14 ++ .../schemas}/latest.ts | 0 .../routes/rule/apis/resolve/schemas/v1.ts | 12 ++ .../resolve/types}/latest.ts | 0 .../routes/rule/apis/resolve/types/v1.ts | 11 ++ .../common/routes/rule/apis/snooze/index.ts | 13 ++ .../routes/rule/apis/snooze/schemas/latest.ts | 8 ++ .../routes/rule/apis/snooze/schemas/v1.ts | 17 +++ .../common/routes/rule/apis/unsnooze/index.ts | 13 ++ .../rule/apis/unsnooze/schemas/latest.ts | 8 ++ .../routes/rule/apis/unsnooze/schemas/v1.ts | 18 +++ .../routes/rule/common/constants/latest.ts | 8 ++ .../rule/{response => common}/constants/v1.ts | 0 .../common/routes/rule/common/index.ts | 38 ++++++ .../bulk_edit/validation => request}/index.ts | 4 +- .../routes/rule/request/schemas/latest.ts | 8 ++ .../common/routes/rule/request/schemas/v1.ts | 19 +++ .../common/routes/rule/response/index.ts | 36 +---- .../common/routes/rule/response/schemas/v1.ts | 20 ++- .../common/routes/rule/validation/index.ts | 3 +- .../validation/validate_notify_when/v1.ts | 2 +- .../validate_snooze_schedule/latest.ts | 8 ++ .../validation/validate_snooze_schedule/v1.ts | 8 +- x-pack/plugins/alerting/common/rule.ts | 5 +- .../r_rule/schemas/r_rule_schema.ts | 12 +- .../types/bulk_edit_rules_options.ts | 2 +- .../application/rule/methods/resolve/index.ts | 9 ++ .../rule/methods/resolve/resolve_rule.ts | 106 +++++++++++++++ .../rule/methods/resolve/schemas/index.ts | 8 ++ .../schemas/resolve_rule_params_schema.ts | 12 ++ .../rule/methods/resolve/types/index.ts | 8 ++ .../methods/resolve/types/resolved_rule.ts | 9 ++ .../application/rule/methods/snooze/index.ts | 9 ++ .../rule/methods/snooze/schemas/index.ts | 9 ++ .../snooze/schemas/snooze_rule_body_schema.ts | 13 ++ .../schemas/snooze_rule_params_schema.ts | 12 ++ .../rule/methods/snooze/snooze_rule.ts} | 78 +++++------ .../rule/methods/snooze/types/index.ts | 8 ++ .../snooze/types/snooze_rule_options.ts | 13 ++ .../rule/methods/unsnooze/index.ts | 9 ++ .../rule/methods/unsnooze/schemas/index.ts | 8 ++ .../schemas/unsnooze_rule_params_schema.ts | 13 ++ .../rule/methods/unsnooze/unsnooze_rule.ts} | 56 ++++---- .../data/r_rule/types/r_rule_attributes.ts | 12 +- .../alerting/server/data/rule/index.ts | 4 + .../server/data/rule/methods/get_rule_so.ts | 22 +++ .../data/rule/methods/resolve_rule_so.ts | 24 ++++ .../plugins/alerting/server/routes/index.ts | 6 +- .../alerting/server/routes/resolve_rule.ts | 91 ------------- .../server/routes/rule/apis/resolve/index.ts | 8 ++ .../apis/resolve/resolve_rule_route.test.ts} | 38 +++--- .../rule/apis/resolve/resolve_rule_route.ts | 52 +++++++ .../rule/apis/resolve/transforms/index.ts | 10 ++ .../transform_resolve_response/latest.ts | 8 ++ .../transform_resolve_response/v1.ts | 18 +++ .../server/routes/rule/apis/snooze/index.ts | 8 ++ .../apis/snooze/snooze_rule_route.test.ts} | 12 +- .../apis/snooze/snooze_rule_route.ts} | 46 ++----- .../rule/apis/snooze/transforms/index.ts | 10 ++ .../transform_snooze_body/latest.ts | 8 ++ .../transforms/transform_snooze_body/v1.ts | 17 +++ .../server/routes/rule/apis/unsnooze/index.ts | 8 ++ .../rule/apis/unsnooze/transforms/index.ts | 10 ++ .../transform_unsnooze_body/latest.ts | 8 ++ .../transforms/transform_unsnooze_body/v1.ts | 10 ++ .../unsnooze/unsnooze_rule_route.test.ts} | 12 +- .../apis/unsnooze/unsnooze_rule_route.ts} | 35 ++--- .../transform_rule_to_rule_response/v1.ts | 1 + .../rules_client/common/snooze_utils.ts | 36 ++--- .../rules_client/lib/get_alert_from_raw.ts | 3 + .../rules_client/lib/get_rule_saved_object.ts | 39 ++++++ .../alerting/server/rules_client/lib/index.ts | 4 + .../lib/resolve_rule_saved_object.ts | 39 ++++++ .../server/rules_client/lib/update_meta.ts | 3 + .../lib/update_meta_attributes.ts | 25 ++++ .../server/rules_client/methods/mute_all.ts | 7 +- .../server/rules_client/methods/resolve.ts | 81 ----------- .../server/rules_client/methods/unmute_all.ts | 7 +- .../server/rules_client/rules_client.ts | 12 +- .../server/rules_client/tests/resolve.test.ts | 127 +++++++----------- x-pack/plugins/alerting/server/types.ts | 3 + x-pack/plugins/alerting/tsconfig.json | 3 +- .../components/notify_badge/notify_badge.tsx | 1 - 89 files changed, 1059 insertions(+), 561 deletions(-) create mode 100644 x-pack/plugins/alerting/common/routes/rule/apis/resolve/index.ts rename x-pack/plugins/alerting/common/routes/rule/apis/{bulk_edit/validation/validate_snooze_schedule => resolve/schemas}/latest.ts (100%) create mode 100644 x-pack/plugins/alerting/common/routes/rule/apis/resolve/schemas/v1.ts rename x-pack/plugins/alerting/common/routes/rule/{response/constants => apis/resolve/types}/latest.ts (100%) create mode 100644 x-pack/plugins/alerting/common/routes/rule/apis/resolve/types/v1.ts create mode 100644 x-pack/plugins/alerting/common/routes/rule/apis/snooze/index.ts create mode 100644 x-pack/plugins/alerting/common/routes/rule/apis/snooze/schemas/latest.ts create mode 100644 x-pack/plugins/alerting/common/routes/rule/apis/snooze/schemas/v1.ts create mode 100644 x-pack/plugins/alerting/common/routes/rule/apis/unsnooze/index.ts create mode 100644 x-pack/plugins/alerting/common/routes/rule/apis/unsnooze/schemas/latest.ts create mode 100644 x-pack/plugins/alerting/common/routes/rule/apis/unsnooze/schemas/v1.ts create mode 100644 x-pack/plugins/alerting/common/routes/rule/common/constants/latest.ts rename x-pack/plugins/alerting/common/routes/rule/{response => common}/constants/v1.ts (100%) create mode 100644 x-pack/plugins/alerting/common/routes/rule/common/index.ts rename x-pack/plugins/alerting/common/routes/rule/{apis/bulk_edit/validation => request}/index.ts (59%) create mode 100644 x-pack/plugins/alerting/common/routes/rule/request/schemas/latest.ts create mode 100644 x-pack/plugins/alerting/common/routes/rule/request/schemas/v1.ts create mode 100644 x-pack/plugins/alerting/common/routes/rule/validation/validate_snooze_schedule/latest.ts rename x-pack/plugins/alerting/common/routes/rule/{apis/bulk_edit => }/validation/validate_snooze_schedule/v1.ts (74%) create mode 100644 x-pack/plugins/alerting/server/application/rule/methods/resolve/index.ts create mode 100644 x-pack/plugins/alerting/server/application/rule/methods/resolve/resolve_rule.ts create mode 100644 x-pack/plugins/alerting/server/application/rule/methods/resolve/schemas/index.ts create mode 100644 x-pack/plugins/alerting/server/application/rule/methods/resolve/schemas/resolve_rule_params_schema.ts create mode 100644 x-pack/plugins/alerting/server/application/rule/methods/resolve/types/index.ts create mode 100644 x-pack/plugins/alerting/server/application/rule/methods/resolve/types/resolved_rule.ts create mode 100644 x-pack/plugins/alerting/server/application/rule/methods/snooze/index.ts create mode 100644 x-pack/plugins/alerting/server/application/rule/methods/snooze/schemas/index.ts create mode 100644 x-pack/plugins/alerting/server/application/rule/methods/snooze/schemas/snooze_rule_body_schema.ts create mode 100644 x-pack/plugins/alerting/server/application/rule/methods/snooze/schemas/snooze_rule_params_schema.ts rename x-pack/plugins/alerting/server/{rules_client/methods/snooze.ts => application/rule/methods/snooze/snooze_rule.ts} (53%) create mode 100644 x-pack/plugins/alerting/server/application/rule/methods/snooze/types/index.ts create mode 100644 x-pack/plugins/alerting/server/application/rule/methods/snooze/types/snooze_rule_options.ts create mode 100644 x-pack/plugins/alerting/server/application/rule/methods/unsnooze/index.ts create mode 100644 x-pack/plugins/alerting/server/application/rule/methods/unsnooze/schemas/index.ts create mode 100644 x-pack/plugins/alerting/server/application/rule/methods/unsnooze/schemas/unsnooze_rule_params_schema.ts rename x-pack/plugins/alerting/server/{rules_client/methods/unsnooze.ts => application/rule/methods/unsnooze/unsnooze_rule.ts} (54%) create mode 100644 x-pack/plugins/alerting/server/data/rule/methods/get_rule_so.ts create mode 100644 x-pack/plugins/alerting/server/data/rule/methods/resolve_rule_so.ts delete mode 100644 x-pack/plugins/alerting/server/routes/resolve_rule.ts create mode 100644 x-pack/plugins/alerting/server/routes/rule/apis/resolve/index.ts rename x-pack/plugins/alerting/server/routes/{resolve_rule.test.ts => rule/apis/resolve/resolve_rule_route.test.ts} (75%) create mode 100644 x-pack/plugins/alerting/server/routes/rule/apis/resolve/resolve_rule_route.ts create mode 100644 x-pack/plugins/alerting/server/routes/rule/apis/resolve/transforms/index.ts create mode 100644 x-pack/plugins/alerting/server/routes/rule/apis/resolve/transforms/transform_resolve_response/latest.ts create mode 100644 x-pack/plugins/alerting/server/routes/rule/apis/resolve/transforms/transform_resolve_response/v1.ts create mode 100644 x-pack/plugins/alerting/server/routes/rule/apis/snooze/index.ts rename x-pack/plugins/alerting/server/routes/{snooze_rule.test.ts => rule/apis/snooze/snooze_rule_route.test.ts} (90%) rename x-pack/plugins/alerting/server/routes/{snooze_rule.ts => rule/apis/snooze/snooze_rule_route.ts} (52%) create mode 100644 x-pack/plugins/alerting/server/routes/rule/apis/snooze/transforms/index.ts create mode 100644 x-pack/plugins/alerting/server/routes/rule/apis/snooze/transforms/transform_snooze_body/latest.ts create mode 100644 x-pack/plugins/alerting/server/routes/rule/apis/snooze/transforms/transform_snooze_body/v1.ts create mode 100644 x-pack/plugins/alerting/server/routes/rule/apis/unsnooze/index.ts create mode 100644 x-pack/plugins/alerting/server/routes/rule/apis/unsnooze/transforms/index.ts create mode 100644 x-pack/plugins/alerting/server/routes/rule/apis/unsnooze/transforms/transform_unsnooze_body/latest.ts create mode 100644 x-pack/plugins/alerting/server/routes/rule/apis/unsnooze/transforms/transform_unsnooze_body/v1.ts rename x-pack/plugins/alerting/server/routes/{unsnooze_rule.test.ts => rule/apis/unsnooze/unsnooze_rule_route.test.ts} (83%) rename x-pack/plugins/alerting/server/routes/{unsnooze_rule.ts => rule/apis/unsnooze/unsnooze_rule_route.ts} (60%) create mode 100644 x-pack/plugins/alerting/server/rules_client/lib/get_rule_saved_object.ts create mode 100644 x-pack/plugins/alerting/server/rules_client/lib/resolve_rule_saved_object.ts create mode 100644 x-pack/plugins/alerting/server/rules_client/lib/update_meta_attributes.ts delete mode 100644 x-pack/plugins/alerting/server/rules_client/methods/resolve.ts diff --git a/x-pack/plugins/alerting/common/routes/r_rule/response/schemas/v1.ts b/x-pack/plugins/alerting/common/routes/r_rule/response/schemas/v1.ts index 0b32b61746ec9..13cb7cb3824f4 100644 --- a/x-pack/plugins/alerting/common/routes/r_rule/response/schemas/v1.ts +++ b/x-pack/plugins/alerting/common/routes/r_rule/response/schemas/v1.ts @@ -38,10 +38,10 @@ export const rRuleResponseSchema = schema.object({ byweekday: schema.maybe(schema.arrayOf(schema.oneOf([schema.string(), schema.number()]))), bymonth: schema.maybe(schema.arrayOf(schema.number())), bysetpos: schema.maybe(schema.arrayOf(schema.number())), - bymonthday: schema.arrayOf(schema.number()), - byyearday: schema.arrayOf(schema.number()), - byweekno: schema.arrayOf(schema.number()), - byhour: schema.arrayOf(schema.number()), - byminute: schema.arrayOf(schema.number()), - bysecond: schema.arrayOf(schema.number()), + bymonthday: schema.maybe(schema.arrayOf(schema.number())), + byyearday: schema.maybe(schema.arrayOf(schema.number())), + byweekno: schema.maybe(schema.arrayOf(schema.number())), + byhour: schema.maybe(schema.arrayOf(schema.number())), + byminute: schema.maybe(schema.arrayOf(schema.number())), + bysecond: schema.maybe(schema.arrayOf(schema.number())), }); diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/bulk_edit/index.ts b/x-pack/plugins/alerting/common/routes/rule/apis/bulk_edit/index.ts index 1ae4a238cbf21..801e13088fab6 100644 --- a/x-pack/plugins/alerting/common/routes/rule/apis/bulk_edit/index.ts +++ b/x-pack/plugins/alerting/common/routes/rule/apis/bulk_edit/index.ts @@ -5,24 +5,14 @@ * 2.0. */ -export { - ruleSnoozeScheduleSchema, - bulkEditOperationsSchema, - bulkEditRulesRequestBodySchema, -} from './schemas/latest'; -export type { - RuleSnoozeSchedule, - BulkEditRulesRequestBody, - BulkEditRulesResponse, -} from './types/latest'; +export { bulkEditOperationsSchema, bulkEditRulesRequestBodySchema } from './schemas/latest'; +export type { BulkEditRulesRequestBody, BulkEditRulesResponse } from './types/latest'; export { - ruleSnoozeScheduleSchema as ruleSnoozeScheduleSchemaV1, bulkEditOperationsSchema as bulkEditOperationsSchemaV1, bulkEditRulesRequestBodySchema as bulkEditRulesRequestBodySchemaV1, } from './schemas/v1'; export type { - RuleSnoozeSchedule as RuleSnoozeScheduleV1, BulkEditRulesRequestBody as BulkEditRulesRequestBodyV1, BulkEditRulesResponse as BulkEditRulesResponseV1, } from './types/v1'; diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/bulk_edit/schemas/v1.ts b/x-pack/plugins/alerting/common/routes/rule/apis/bulk_edit/schemas/v1.ts index 54e70cde689ac..3a17eaee30974 100644 --- a/x-pack/plugins/alerting/common/routes/rule/apis/bulk_edit/schemas/v1.ts +++ b/x-pack/plugins/alerting/common/routes/rule/apis/bulk_edit/schemas/v1.ts @@ -6,19 +6,10 @@ */ import { schema } from '@kbn/config-schema'; -import { validateDurationV1, validateNotifyWhenV1 } from '../../../validation'; -import { validateSnoozeScheduleV1 } from '../validation'; +import { validateDurationV1 } from '../../../validation'; import { rRuleRequestSchemaV1 } from '../../../../r_rule'; -import { ruleNotifyWhenV1 } from '../../../response'; - -const notifyWhenSchema = schema.oneOf( - [ - schema.literal(ruleNotifyWhenV1.CHANGE), - schema.literal(ruleNotifyWhenV1.ACTIVE), - schema.literal(ruleNotifyWhenV1.THROTTLE), - ], - { validate: validateNotifyWhenV1 } -); +import { notifyWhenSchemaV1, scheduleIdsSchemaV1 } from '../../../response'; +import { ruleSnoozeScheduleSchemaV1 } from '../../../request'; export const scheduleIdsSchema = schema.maybe(schema.arrayOf(schema.string())); @@ -28,15 +19,6 @@ export const ruleSnoozeScheduleSchema = schema.object({ rRule: rRuleRequestSchemaV1, }); -const ruleSnoozeScheduleSchemaWithValidation = schema.object( - { - id: schema.maybe(schema.string()), - duration: schema.number(), - rRule: rRuleRequestSchemaV1, - }, - { validate: validateSnoozeScheduleV1 } -); - const ruleActionSchema = schema.object({ group: schema.string(), id: schema.string(), @@ -46,7 +28,7 @@ const ruleActionSchema = schema.object({ schema.object({ summary: schema.boolean(), throttle: schema.nullable(schema.string()), - notifyWhen: notifyWhenSchema, + notifyWhen: notifyWhenSchemaV1, }) ), }); @@ -80,17 +62,17 @@ export const bulkEditOperationsSchema = schema.arrayOf( schema.object({ operation: schema.literal('set'), field: schema.literal('notifyWhen'), - value: notifyWhenSchema, + value: notifyWhenSchemaV1, }), schema.object({ operation: schema.oneOf([schema.literal('set')]), field: schema.literal('snoozeSchedule'), - value: ruleSnoozeScheduleSchemaWithValidation, + value: ruleSnoozeScheduleSchemaV1, }), schema.object({ operation: schema.oneOf([schema.literal('delete')]), field: schema.literal('snoozeSchedule'), - value: schema.maybe(scheduleIdsSchema), + value: schema.maybe(scheduleIdsSchemaV1), }), schema.object({ operation: schema.literal('set'), diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/bulk_edit/types/v1.ts b/x-pack/plugins/alerting/common/routes/rule/apis/bulk_edit/types/v1.ts index 8f98d1c140746..3070f09964c3c 100644 --- a/x-pack/plugins/alerting/common/routes/rule/apis/bulk_edit/types/v1.ts +++ b/x-pack/plugins/alerting/common/routes/rule/apis/bulk_edit/types/v1.ts @@ -6,9 +6,8 @@ */ import type { TypeOf } from '@kbn/config-schema'; import { RuleParamsV1, RuleResponseV1 } from '../../../response'; -import { ruleSnoozeScheduleSchemaV1, bulkEditRulesRequestBodySchemaV1 } from '..'; +import { bulkEditRulesRequestBodySchemaV1 } from '..'; -export type RuleSnoozeSchedule = TypeOf; export type BulkEditRulesRequestBody = TypeOf; interface BulkEditActionSkippedResult { diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/create/index.ts b/x-pack/plugins/alerting/common/routes/rule/apis/create/index.ts index 92ac6d67aece7..b7d96ceed715c 100644 --- a/x-pack/plugins/alerting/common/routes/rule/apis/create/index.ts +++ b/x-pack/plugins/alerting/common/routes/rule/apis/create/index.ts @@ -6,7 +6,6 @@ */ export { - notifyWhenSchema, actionFrequencySchema, actionAlertsFilterSchema, actionSchema, @@ -23,7 +22,6 @@ export type { } from './types/latest'; export { - notifyWhenSchema as notifyWhenSchemaV1, actionFrequencySchema as actionFrequencySchemaV1, actionAlertsFilterSchema as actionAlertsFilterSchemaV1, actionSchema as actionSchemaV1, diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/create/schemas/v1.ts b/x-pack/plugins/alerting/common/routes/rule/apis/create/schemas/v1.ts index 98d82abf62be4..e471dd3cea530 100644 --- a/x-pack/plugins/alerting/common/routes/rule/apis/create/schemas/v1.ts +++ b/x-pack/plugins/alerting/common/routes/rule/apis/create/schemas/v1.ts @@ -6,26 +6,12 @@ */ import { schema } from '@kbn/config-schema'; -import { ruleNotifyWhenV1 } from '../../../response'; -import { - validateNotifyWhenV1, - validateDurationV1, - validateHoursV1, - validateTimezoneV1, -} from '../../../validation'; - -export const notifyWhenSchema = schema.oneOf( - [ - schema.literal(ruleNotifyWhenV1.CHANGE), - schema.literal(ruleNotifyWhenV1.ACTIVE), - schema.literal(ruleNotifyWhenV1.THROTTLE), - ], - { validate: validateNotifyWhenV1 } -); +import { validateDurationV1, validateHoursV1, validateTimezoneV1 } from '../../../validation'; +import { notifyWhenSchemaV1 } from '../../../response'; export const actionFrequencySchema = schema.object({ summary: schema.boolean(), - notify_when: notifyWhenSchema, + notify_when: notifyWhenSchemaV1, throttle: schema.nullable(schema.string({ validate: validateDurationV1 })), }); @@ -91,7 +77,7 @@ export const createBodySchema = schema.object({ interval: schema.string({ validate: validateDurationV1 }), }), actions: schema.arrayOf(actionSchema, { defaultValue: [] }), - notify_when: schema.maybe(schema.nullable(notifyWhenSchema)), + notify_when: schema.maybe(schema.nullable(notifyWhenSchemaV1)), }); export const createParamsSchema = schema.object({ diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/resolve/index.ts b/x-pack/plugins/alerting/common/routes/rule/apis/resolve/index.ts new file mode 100644 index 0000000000000..140ec00701414 --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/rule/apis/resolve/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { resolveParamsSchema } from './schemas/latest'; + +export { resolveParamsSchema as resolveParamsSchemaV1 } from './schemas/v1'; + +export type { ResolveRuleResponse } from './types/latest'; + +export type { ResolveRuleResponse as ResolveRuleResponseV1 } from './types/v1'; diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/bulk_edit/validation/validate_snooze_schedule/latest.ts b/x-pack/plugins/alerting/common/routes/rule/apis/resolve/schemas/latest.ts similarity index 100% rename from x-pack/plugins/alerting/common/routes/rule/apis/bulk_edit/validation/validate_snooze_schedule/latest.ts rename to x-pack/plugins/alerting/common/routes/rule/apis/resolve/schemas/latest.ts diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/resolve/schemas/v1.ts b/x-pack/plugins/alerting/common/routes/rule/apis/resolve/schemas/v1.ts new file mode 100644 index 0000000000000..26f23e23af6dc --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/rule/apis/resolve/schemas/v1.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; + +export const resolveParamsSchema = schema.object({ + id: schema.string(), +}); diff --git a/x-pack/plugins/alerting/common/routes/rule/response/constants/latest.ts b/x-pack/plugins/alerting/common/routes/rule/apis/resolve/types/latest.ts similarity index 100% rename from x-pack/plugins/alerting/common/routes/rule/response/constants/latest.ts rename to x-pack/plugins/alerting/common/routes/rule/apis/resolve/types/latest.ts diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/resolve/types/v1.ts b/x-pack/plugins/alerting/common/routes/rule/apis/resolve/types/v1.ts new file mode 100644 index 0000000000000..eb86327c07712 --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/rule/apis/resolve/types/v1.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { RuleParamsV1, RuleResponseV1 } from '../../../response'; + +export interface ResolveRuleResponse { + body: RuleResponseV1; +} diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/snooze/index.ts b/x-pack/plugins/alerting/common/routes/rule/apis/snooze/index.ts new file mode 100644 index 0000000000000..cfd7ad0cbce05 --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/rule/apis/snooze/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { snoozeParamsSchema, snoozeBodySchema } from './schemas/latest'; + +export { + snoozeParamsSchema as snoozeParamsSchemaV1, + snoozeBodySchema as snoozeBodySchemaV1, +} from './schemas/v1'; diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/snooze/schemas/latest.ts b/x-pack/plugins/alerting/common/routes/rule/apis/snooze/schemas/latest.ts new file mode 100644 index 0000000000000..25300c97a6d2e --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/rule/apis/snooze/schemas/latest.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './v1'; diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/snooze/schemas/v1.ts b/x-pack/plugins/alerting/common/routes/rule/apis/snooze/schemas/v1.ts new file mode 100644 index 0000000000000..9b12ab9767512 --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/rule/apis/snooze/schemas/v1.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; +import { ruleSnoozeScheduleSchemaV1 } from '../../../request'; + +export const snoozeParamsSchema = schema.object({ + id: schema.string(), +}); + +export const snoozeBodySchema = schema.object({ + snooze_schedule: ruleSnoozeScheduleSchemaV1, +}); diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/unsnooze/index.ts b/x-pack/plugins/alerting/common/routes/rule/apis/unsnooze/index.ts new file mode 100644 index 0000000000000..1fb769c46d345 --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/rule/apis/unsnooze/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { unsnoozeParamsSchema, unsnoozeBodySchema } from './schemas/latest'; + +export { + unsnoozeParamsSchema as unsnoozeParamsSchemaV1, + unsnoozeBodySchema as unsnoozeBodySchemaV1, +} from './schemas/v1'; diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/unsnooze/schemas/latest.ts b/x-pack/plugins/alerting/common/routes/rule/apis/unsnooze/schemas/latest.ts new file mode 100644 index 0000000000000..25300c97a6d2e --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/rule/apis/unsnooze/schemas/latest.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './v1'; diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/unsnooze/schemas/v1.ts b/x-pack/plugins/alerting/common/routes/rule/apis/unsnooze/schemas/v1.ts new file mode 100644 index 0000000000000..0da0e1b4ecd0b --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/rule/apis/unsnooze/schemas/v1.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; + +export const unsnoozeParamsSchema = schema.object({ + id: schema.string(), +}); + +const scheduleIdsSchema = schema.maybe(schema.arrayOf(schema.string())); + +export const unsnoozeBodySchema = schema.object({ + schedule_ids: scheduleIdsSchema, +}); diff --git a/x-pack/plugins/alerting/common/routes/rule/common/constants/latest.ts b/x-pack/plugins/alerting/common/routes/rule/common/constants/latest.ts new file mode 100644 index 0000000000000..25300c97a6d2e --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/rule/common/constants/latest.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './v1'; diff --git a/x-pack/plugins/alerting/common/routes/rule/response/constants/v1.ts b/x-pack/plugins/alerting/common/routes/rule/common/constants/v1.ts similarity index 100% rename from x-pack/plugins/alerting/common/routes/rule/response/constants/v1.ts rename to x-pack/plugins/alerting/common/routes/rule/common/constants/v1.ts diff --git a/x-pack/plugins/alerting/common/routes/rule/common/index.ts b/x-pack/plugins/alerting/common/routes/rule/common/index.ts new file mode 100644 index 0000000000000..5989a3a993e7a --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/rule/common/index.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { + ruleNotifyWhen, + ruleLastRunOutcomeValues, + ruleExecutionStatusValues, + ruleExecutionStatusErrorReason, + ruleExecutionStatusWarningReason, +} from './constants/latest'; + +export type { + RuleNotifyWhen, + RuleLastRunOutcomeValues, + RuleExecutionStatusValues, + RuleExecutionStatusErrorReason, + RuleExecutionStatusWarningReason, +} from './constants/latest'; + +export { + ruleNotifyWhen as ruleNotifyWhenV1, + ruleLastRunOutcomeValues as ruleLastRunOutcomeValuesV1, + ruleExecutionStatusValues as ruleExecutionStatusValuesV1, + ruleExecutionStatusErrorReason as ruleExecutionStatusErrorReasonV1, + ruleExecutionStatusWarningReason as ruleExecutionStatusWarningReasonV1, +} from './constants/v1'; + +export type { + RuleNotifyWhen as RuleNotifyWhenV1, + RuleLastRunOutcomeValues as RuleLastRunOutcomeValuesV1, + RuleExecutionStatusValues as RuleExecutionStatusValuesV1, + RuleExecutionStatusErrorReason as RuleExecutionStatusErrorReasonV1, + RuleExecutionStatusWarningReason as RuleExecutionStatusWarningReasonV1, +} from './constants/v1'; diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/bulk_edit/validation/index.ts b/x-pack/plugins/alerting/common/routes/rule/request/index.ts similarity index 59% rename from x-pack/plugins/alerting/common/routes/rule/apis/bulk_edit/validation/index.ts rename to x-pack/plugins/alerting/common/routes/rule/request/index.ts index c11ce4f73f42c..bf500b636e7d9 100644 --- a/x-pack/plugins/alerting/common/routes/rule/apis/bulk_edit/validation/index.ts +++ b/x-pack/plugins/alerting/common/routes/rule/request/index.ts @@ -5,6 +5,6 @@ * 2.0. */ -export { validateSnoozeSchedule } from './validate_snooze_schedule/latest'; +export { ruleSnoozeScheduleSchema } from './schemas/latest'; -export { validateSnoozeSchedule as validateSnoozeScheduleV1 } from './validate_snooze_schedule/v1'; +export { ruleSnoozeScheduleSchema as ruleSnoozeScheduleSchemaV1 } from './schemas/v1'; diff --git a/x-pack/plugins/alerting/common/routes/rule/request/schemas/latest.ts b/x-pack/plugins/alerting/common/routes/rule/request/schemas/latest.ts new file mode 100644 index 0000000000000..25300c97a6d2e --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/rule/request/schemas/latest.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './v1'; diff --git a/x-pack/plugins/alerting/common/routes/rule/request/schemas/v1.ts b/x-pack/plugins/alerting/common/routes/rule/request/schemas/v1.ts new file mode 100644 index 0000000000000..af9cf6a3ad3a6 --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/rule/request/schemas/v1.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; +import { rRuleRequestSchemaV1 } from '../../../r_rule'; +import { validateSnoozeScheduleV1 } from '../../validation'; + +export const ruleSnoozeScheduleSchema = schema.object( + { + id: schema.maybe(schema.string()), + duration: schema.number(), + rRule: rRuleRequestSchemaV1, + }, + { validate: validateSnoozeScheduleV1 } +); diff --git a/x-pack/plugins/alerting/common/routes/rule/response/index.ts b/x-pack/plugins/alerting/common/routes/rule/response/index.ts index cdf693c7867eb..ec8734240cc2b 100644 --- a/x-pack/plugins/alerting/common/routes/rule/response/index.ts +++ b/x-pack/plugins/alerting/common/routes/rule/response/index.ts @@ -14,6 +14,8 @@ export { monitoringSchema, ruleResponseSchema, ruleSnoozeScheduleSchema, + notifyWhenSchema, + scheduleIdsSchema, } from './schemas/latest'; export type { @@ -24,22 +26,6 @@ export type { Monitoring, } from './types/latest'; -export { - ruleNotifyWhen, - ruleLastRunOutcomeValues, - ruleExecutionStatusValues, - ruleExecutionStatusErrorReason, - ruleExecutionStatusWarningReason, -} from './constants/latest'; - -export type { - RuleNotifyWhen, - RuleLastRunOutcomeValues, - RuleExecutionStatusValues, - RuleExecutionStatusErrorReason, - RuleExecutionStatusWarningReason, -} from './constants/latest'; - export { ruleParamsSchema as ruleParamsSchemaV1, actionParamsSchema as actionParamsSchemaV1, @@ -49,24 +35,10 @@ export { monitoringSchema as monitoringSchemaV1, ruleResponseSchema as ruleResponseSchemaV1, ruleSnoozeScheduleSchema as ruleSnoozeScheduleSchemaV1, + notifyWhenSchema as notifyWhenSchemaV1, + scheduleIdsSchema as scheduleIdsSchemaV1, } from './schemas/v1'; -export { - ruleNotifyWhen as ruleNotifyWhenV1, - ruleLastRunOutcomeValues as ruleLastRunOutcomeValuesV1, - ruleExecutionStatusValues as ruleExecutionStatusValuesV1, - ruleExecutionStatusErrorReason as ruleExecutionStatusErrorReasonV1, - ruleExecutionStatusWarningReason as ruleExecutionStatusWarningReasonV1, -} from './constants/v1'; - -export type { - RuleNotifyWhen as RuleNotifyWhenV1, - RuleLastRunOutcomeValues as RuleLastRunOutcomeValuesV1, - RuleExecutionStatusValues as RuleExecutionStatusValuesV1, - RuleExecutionStatusErrorReason as RuleExecutionStatusErrorReasonV1, - RuleExecutionStatusWarningReason as RuleExecutionStatusWarningReasonV1, -} from './constants/v1'; - export type { RuleParams as RuleParamsV1, RuleResponse as RuleResponseV1, diff --git a/x-pack/plugins/alerting/common/routes/rule/response/schemas/v1.ts b/x-pack/plugins/alerting/common/routes/rule/response/schemas/v1.ts index 1c093314f7a47..fe02146c5ef62 100644 --- a/x-pack/plugins/alerting/common/routes/rule/response/schemas/v1.ts +++ b/x-pack/plugins/alerting/common/routes/rule/response/schemas/v1.ts @@ -13,17 +13,21 @@ import { ruleExecutionStatusErrorReason as ruleExecutionStatusErrorReasonV1, ruleExecutionStatusWarningReason as ruleExecutionStatusWarningReasonV1, ruleLastRunOutcomeValues as ruleLastRunOutcomeValuesV1, -} from '../constants/v1'; +} from '../../common/constants/v1'; +import { validateNotifyWhenV1 } from '../../validation'; export const ruleParamsSchema = schema.recordOf(schema.string(), schema.maybe(schema.any())); export const actionParamsSchema = schema.recordOf(schema.string(), schema.maybe(schema.any())); export const mappedParamsSchema = schema.recordOf(schema.string(), schema.maybe(schema.any())); -const notifyWhenSchema = schema.oneOf([ - schema.literal(ruleNotifyWhenV1.CHANGE), - schema.literal(ruleNotifyWhenV1.ACTIVE), - schema.literal(ruleNotifyWhenV1.THROTTLE), -]); +export const notifyWhenSchema = schema.oneOf( + [ + schema.literal(ruleNotifyWhenV1.CHANGE), + schema.literal(ruleNotifyWhenV1.ACTIVE), + schema.literal(ruleNotifyWhenV1.THROTTLE), + ], + { validate: validateNotifyWhenV1 } +); const intervalScheduleSchema = schema.object({ interval: schema.string(), @@ -182,9 +186,9 @@ export const monitoringSchema = schema.object({ }); export const ruleSnoozeScheduleSchema = schema.object({ + id: schema.maybe(schema.string()), duration: schema.number(), rRule: rRuleResponseSchemaV1, - id: schema.maybe(schema.string()), skipRecurrences: schema.maybe(schema.arrayOf(schema.string())), }); @@ -221,3 +225,5 @@ export const ruleResponseSchema = schema.object({ running: schema.maybe(schema.nullable(schema.boolean())), view_in_app_relative_url: schema.maybe(schema.nullable(schema.string())), }); + +export const scheduleIdsSchema = schema.maybe(schema.arrayOf(schema.string())); diff --git a/x-pack/plugins/alerting/common/routes/rule/validation/index.ts b/x-pack/plugins/alerting/common/routes/rule/validation/index.ts index a0322c2256e74..710f15f0d4c9d 100644 --- a/x-pack/plugins/alerting/common/routes/rule/validation/index.ts +++ b/x-pack/plugins/alerting/common/routes/rule/validation/index.ts @@ -7,10 +7,11 @@ export { validateDuration } from './validate_duration/latest'; export { validateHours } from './validate_hours/latest'; -export { validateNotifyWhen } from './validate_notify_when/latest'; export { validateTimezone } from './validate_timezone/latest'; +export { validateSnoozeSchedule } from './validate_snooze_schedule/latest'; export { validateDuration as validateDurationV1 } from './validate_duration/v1'; export { validateHours as validateHoursV1 } from './validate_hours/v1'; export { validateNotifyWhen as validateNotifyWhenV1 } from './validate_notify_when/v1'; export { validateTimezone as validateTimezoneV1 } from './validate_timezone/v1'; +export { validateSnoozeSchedule as validateSnoozeScheduleV1 } from './validate_snooze_schedule/v1'; diff --git a/x-pack/plugins/alerting/common/routes/rule/validation/validate_notify_when/v1.ts b/x-pack/plugins/alerting/common/routes/rule/validation/validate_notify_when/v1.ts index e58e5f5ef7cfd..38ccd877c49ae 100644 --- a/x-pack/plugins/alerting/common/routes/rule/validation/validate_notify_when/v1.ts +++ b/x-pack/plugins/alerting/common/routes/rule/validation/validate_notify_when/v1.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ruleNotifyWhenV1, RuleNotifyWhenV1 } from '../../response'; +import { ruleNotifyWhenV1, RuleNotifyWhenV1 } from '../../common'; export function validateNotifyWhen(notifyWhen: string) { if (Object.values(ruleNotifyWhenV1).includes(notifyWhen as RuleNotifyWhenV1)) { diff --git a/x-pack/plugins/alerting/common/routes/rule/validation/validate_snooze_schedule/latest.ts b/x-pack/plugins/alerting/common/routes/rule/validation/validate_snooze_schedule/latest.ts new file mode 100644 index 0000000000000..25300c97a6d2e --- /dev/null +++ b/x-pack/plugins/alerting/common/routes/rule/validation/validate_snooze_schedule/latest.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './v1'; diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/bulk_edit/validation/validate_snooze_schedule/v1.ts b/x-pack/plugins/alerting/common/routes/rule/validation/validate_snooze_schedule/v1.ts similarity index 74% rename from x-pack/plugins/alerting/common/routes/rule/apis/bulk_edit/validation/validate_snooze_schedule/v1.ts rename to x-pack/plugins/alerting/common/routes/rule/validation/validate_snooze_schedule/v1.ts index 65460110cd107..4a93f130e8248 100644 --- a/x-pack/plugins/alerting/common/routes/rule/apis/bulk_edit/validation/validate_snooze_schedule/v1.ts +++ b/x-pack/plugins/alerting/common/routes/rule/validation/validate_snooze_schedule/v1.ts @@ -5,11 +5,15 @@ * 2.0. */ +import { TypeOf } from '@kbn/config-schema'; import { Frequency } from '@kbn/rrule'; import moment from 'moment'; -import { RuleSnoozeScheduleV1 } from '../..'; +import { rRuleRequestSchema } from '../../../r_rule'; -export const validateSnoozeSchedule = (schedule: RuleSnoozeScheduleV1) => { +export const validateSnoozeSchedule = (schedule: { + rRule: TypeOf; + duration: number; +}) => { const intervalIsDaily = schedule.rRule.freq === Frequency.DAILY; const durationInDays = moment.duration(schedule.duration, 'milliseconds').asDays(); if (intervalIsDaily && schedule.rRule.interval && durationInDays >= schedule.rRule.interval) { diff --git a/x-pack/plugins/alerting/common/rule.ts b/x-pack/plugins/alerting/common/rule.ts index 5a8e1b275ef7e..ff74752df8695 100644 --- a/x-pack/plugins/alerting/common/rule.ts +++ b/x-pack/plugins/alerting/common/rule.ts @@ -191,7 +191,10 @@ export type SanitizedRule = Omit< > & { actions: SanitizedRuleAction[] }; export type ResolvedSanitizedRule = SanitizedRule & - Omit; + Omit & { + outcome: string; + alias_target_id?: string; + }; export type SanitizedRuleConfig = Pick< SanitizedRule, diff --git a/x-pack/plugins/alerting/server/application/r_rule/schemas/r_rule_schema.ts b/x-pack/plugins/alerting/server/application/r_rule/schemas/r_rule_schema.ts index 6f8b2f8f4fc7a..5325c571f5d3e 100644 --- a/x-pack/plugins/alerting/server/application/r_rule/schemas/r_rule_schema.ts +++ b/x-pack/plugins/alerting/server/application/r_rule/schemas/r_rule_schema.ts @@ -37,10 +37,10 @@ export const rRuleSchema = schema.object({ byweekday: schema.maybe(schema.arrayOf(schema.oneOf([schema.string(), schema.number()]))), bymonth: schema.maybe(schema.arrayOf(schema.number())), bysetpos: schema.maybe(schema.arrayOf(schema.number())), - bymonthday: schema.arrayOf(schema.number()), - byyearday: schema.arrayOf(schema.number()), - byweekno: schema.arrayOf(schema.number()), - byhour: schema.arrayOf(schema.number()), - byminute: schema.arrayOf(schema.number()), - bysecond: schema.arrayOf(schema.number()), + bymonthday: schema.maybe(schema.arrayOf(schema.number())), + byyearday: schema.maybe(schema.arrayOf(schema.number())), + byweekno: schema.maybe(schema.arrayOf(schema.number())), + byhour: schema.maybe(schema.arrayOf(schema.number())), + byminute: schema.maybe(schema.arrayOf(schema.number())), + bysecond: schema.maybe(schema.arrayOf(schema.number())), }); diff --git a/x-pack/plugins/alerting/server/application/rule/methods/bulk_edit/types/bulk_edit_rules_options.ts b/x-pack/plugins/alerting/server/application/rule/methods/bulk_edit/types/bulk_edit_rules_options.ts index a74b7fe152069..c099cde044363 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/bulk_edit/types/bulk_edit_rules_options.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/bulk_edit/types/bulk_edit_rules_options.ts @@ -45,7 +45,7 @@ export type BulkEditOptionsFilter = BulkEditOptionsCo }; export type BulkEditOptionsIds = BulkEditOptionsCommon & { - ids: string[]; + ids?: string[]; }; export type BulkEditSkipReason = 'RULE_NOT_MODIFIED'; diff --git a/x-pack/plugins/alerting/server/application/rule/methods/resolve/index.ts b/x-pack/plugins/alerting/server/application/rule/methods/resolve/index.ts new file mode 100644 index 0000000000000..b8d44beb0610e --- /dev/null +++ b/x-pack/plugins/alerting/server/application/rule/methods/resolve/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export type { ResolveParams } from './resolve_rule'; +export { resolveRule } from './resolve_rule'; diff --git a/x-pack/plugins/alerting/server/application/rule/methods/resolve/resolve_rule.ts b/x-pack/plugins/alerting/server/application/rule/methods/resolve/resolve_rule.ts new file mode 100644 index 0000000000000..cbde52a44b1fe --- /dev/null +++ b/x-pack/plugins/alerting/server/application/rule/methods/resolve/resolve_rule.ts @@ -0,0 +1,106 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import Boom from '@hapi/boom'; +import { withSpan } from '@kbn/apm-utils'; +import { AlertConsumers } from '@kbn/rule-data-utils'; +import { resolveRuleSavedObject } from '../../../../rules_client/lib'; +import { ruleAuditEvent, RuleAuditAction } from '../../../../rules_client/common/audit_events'; +import { RuleTypeParams } from '../../../../types'; +import { ReadOperations, AlertingAuthorizationEntity } from '../../../../authorization'; +import { RulesClientContext } from '../../../../rules_client/types'; +import { formatLegacyActions } from '../../../../rules_client/lib'; +import { transformRuleAttributesToRuleDomain, transformRuleDomainToRule } from '../../transforms'; +import { Rule } from '../../types'; +import { ruleSchema } from '../../schemas'; +import { resolveRuleParamsSchema } from './schemas'; +import type { ResolvedSanitizedRule } from '../../../../types'; + +export interface ResolveParams { + id: string; + includeSnoozeData?: boolean; +} + +export async function resolveRule( + context: RulesClientContext, + { id, includeSnoozeData = false }: ResolveParams +): // TODO (http-versioning): This should be of type Rule, change this when all rule types are fixed +Promise> { + try { + resolveRuleParamsSchema.validate({ id }); + } catch (error) { + throw Boom.badRequest(`Error validating resolve params - ${error.message}`); + } + const { saved_object: result, ...resolveResponse } = await withSpan( + { name: 'resolveRuleSavedObject', type: 'rules' }, + () => + resolveRuleSavedObject(context, { + ruleId: id, + }) + ); + try { + await context.authorization.ensureAuthorized({ + ruleTypeId: result.attributes.alertTypeId, + consumer: result.attributes.consumer, + operation: ReadOperations.Get, + entity: AlertingAuthorizationEntity.Rule, + }); + } catch (error) { + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.RESOLVE, + savedObject: { type: 'alert', id }, + error, + }) + ); + throw error; + } + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.RESOLVE, + savedObject: { type: 'alert', id }, + }) + ); + + const ruleDomain = transformRuleAttributesToRuleDomain(result.attributes, { + id: result.id, + logger: context.logger, + ruleType: context.ruleTypeRegistry.get(result.attributes.alertTypeId), + references: result.references, + includeSnoozeData, + }); + + const rule = transformRuleDomainToRule(ruleDomain); + + try { + ruleSchema.validate(rule); + } catch (error) { + context.logger.warn(`Error validating resolve data - ${error.message}`); + } + + // format legacy actions for SIEM rules + if (result.attributes.consumer === AlertConsumers.SIEM) { + // @ts-expect-error formatLegacyActions uses common Rule type instead of server; wontfix as this function is deprecated + const [migratedRule] = await formatLegacyActions([rule], { + savedObjectsClient: context.unsecuredSavedObjectsClient, + logger: context.logger, + }); + + return { + ...(migratedRule as Rule), + ...resolveResponse, + // TODO (http-versioning): Remove this cast, this enables us to move forward + // without fixing all of other solution types + } as ResolvedSanitizedRule; + } + + return { + ...rule, + ...resolveResponse, + // TODO (http-versioning): Remove this cast, this enables us to move forward + // without fixing all of other solution types + } as ResolvedSanitizedRule; +} diff --git a/x-pack/plugins/alerting/server/application/rule/methods/resolve/schemas/index.ts b/x-pack/plugins/alerting/server/application/rule/methods/resolve/schemas/index.ts new file mode 100644 index 0000000000000..09bccf1170ab4 --- /dev/null +++ b/x-pack/plugins/alerting/server/application/rule/methods/resolve/schemas/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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { resolveRuleParamsSchema } from './resolve_rule_params_schema'; diff --git a/x-pack/plugins/alerting/server/application/rule/methods/resolve/schemas/resolve_rule_params_schema.ts b/x-pack/plugins/alerting/server/application/rule/methods/resolve/schemas/resolve_rule_params_schema.ts new file mode 100644 index 0000000000000..a4371befd9f78 --- /dev/null +++ b/x-pack/plugins/alerting/server/application/rule/methods/resolve/schemas/resolve_rule_params_schema.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; + +export const resolveRuleParamsSchema = schema.object({ + id: schema.string(), +}); diff --git a/x-pack/plugins/alerting/server/application/rule/methods/resolve/types/index.ts b/x-pack/plugins/alerting/server/application/rule/methods/resolve/types/index.ts new file mode 100644 index 0000000000000..a4b7c56caab19 --- /dev/null +++ b/x-pack/plugins/alerting/server/application/rule/methods/resolve/types/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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './resolved_rule'; diff --git a/x-pack/plugins/alerting/server/application/rule/methods/resolve/types/resolved_rule.ts b/x-pack/plugins/alerting/server/application/rule/methods/resolve/types/resolved_rule.ts new file mode 100644 index 0000000000000..d9d0c3f0c5e8f --- /dev/null +++ b/x-pack/plugins/alerting/server/application/rule/methods/resolve/types/resolved_rule.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { Rule } from '../../../types'; + +export type ResolvedRule = Rule & { outcome: string; alias_target_id?: string }; diff --git a/x-pack/plugins/alerting/server/application/rule/methods/snooze/index.ts b/x-pack/plugins/alerting/server/application/rule/methods/snooze/index.ts new file mode 100644 index 0000000000000..842e092d40d57 --- /dev/null +++ b/x-pack/plugins/alerting/server/application/rule/methods/snooze/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export type { SnoozeRuleOptions } from './types'; +export { snoozeRule } from './snooze_rule'; diff --git a/x-pack/plugins/alerting/server/application/rule/methods/snooze/schemas/index.ts b/x-pack/plugins/alerting/server/application/rule/methods/snooze/schemas/index.ts new file mode 100644 index 0000000000000..b0744b893bbfb --- /dev/null +++ b/x-pack/plugins/alerting/server/application/rule/methods/snooze/schemas/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { snoozeRuleParamsSchema } from './snooze_rule_params_schema'; +export { snoozeRuleBodySchema } from './snooze_rule_body_schema'; diff --git a/x-pack/plugins/alerting/server/application/rule/methods/snooze/schemas/snooze_rule_body_schema.ts b/x-pack/plugins/alerting/server/application/rule/methods/snooze/schemas/snooze_rule_body_schema.ts new file mode 100644 index 0000000000000..180e69fb33ad1 --- /dev/null +++ b/x-pack/plugins/alerting/server/application/rule/methods/snooze/schemas/snooze_rule_body_schema.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; +import { ruleSnoozeScheduleSchema as ruleSnoozeScheduleRequestSchema } from '../../../../../../common/routes/rule/request'; + +export const snoozeRuleBodySchema = schema.object({ + snoozeSchedule: ruleSnoozeScheduleRequestSchema, +}); diff --git a/x-pack/plugins/alerting/server/application/rule/methods/snooze/schemas/snooze_rule_params_schema.ts b/x-pack/plugins/alerting/server/application/rule/methods/snooze/schemas/snooze_rule_params_schema.ts new file mode 100644 index 0000000000000..2bacdb1c116f2 --- /dev/null +++ b/x-pack/plugins/alerting/server/application/rule/methods/snooze/schemas/snooze_rule_params_schema.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; + +export const snoozeRuleParamsSchema = schema.object({ + id: schema.string(), +}); diff --git a/x-pack/plugins/alerting/server/rules_client/methods/snooze.ts b/x-pack/plugins/alerting/server/application/rule/methods/snooze/snooze_rule.ts similarity index 53% rename from x-pack/plugins/alerting/server/rules_client/methods/snooze.ts rename to x-pack/plugins/alerting/server/application/rule/methods/snooze/snooze_rule.ts index 6f1187526b521..b10df195e5b77 100644 --- a/x-pack/plugins/alerting/server/rules_client/methods/snooze.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/snooze/snooze_rule.ts @@ -6,26 +6,32 @@ */ import Boom from '@hapi/boom'; -import { RawRule, RuleSnoozeSchedule } from '../../types'; -import { WriteOperations, AlertingAuthorizationEntity } from '../../authorization'; -import { retryIfConflicts } from '../../lib/retry_if_conflicts'; -import { partiallyUpdateAlert } from '../../saved_objects'; -import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; -import { validateSnoozeStartDate } from '../../lib/validate_snooze_date'; -import { RuleMutedError } from '../../lib/errors/rule_muted'; -import { RulesClientContext } from '../types'; -import { getSnoozeAttributes, verifySnoozeAttributeScheduleLimit } from '../common'; -import { updateMeta } from '../lib'; +import { withSpan } from '@kbn/apm-utils'; +import { getRuleSavedObject } from '../../../../rules_client/lib'; +import { ruleAuditEvent, RuleAuditAction } from '../../../../rules_client/common/audit_events'; +import { WriteOperations, AlertingAuthorizationEntity } from '../../../../authorization'; +import { retryIfConflicts } from '../../../../lib/retry_if_conflicts'; +import { validateSnoozeStartDate } from '../../../../lib/validate_snooze_date'; +import { RuleMutedError } from '../../../../lib/errors/rule_muted'; +import { RulesClientContext } from '../../../../rules_client/types'; +import { + getSnoozeAttributes, + verifySnoozeAttributeScheduleLimit, +} from '../../../../rules_client/common'; +import { updateRuleSo } from '../../../../data/rule'; +import { updateMetaAttributes } from '../../../../rules_client/lib/update_meta_attributes'; +import { snoozeRuleParamsSchema } from './schemas'; +import type { SnoozeRuleOptions } from './types'; -export interface SnoozeParams { - id: string; - snoozeSchedule: RuleSnoozeSchedule; -} - -export async function snooze( +export async function snoozeRule( context: RulesClientContext, - { id, snoozeSchedule }: SnoozeParams + { id, snoozeSchedule }: SnoozeRuleOptions ): Promise { + try { + snoozeRuleParamsSchema.validate({ id }); + } catch (error) { + throw Boom.badRequest(`Error validating snooze params - ${error.message}`); + } const snoozeDateValidationMsg = validateSnoozeStartDate(snoozeSchedule.rRule.dtstart); if (snoozeDateValidationMsg) { throw new RuleMutedError(snoozeDateValidationMsg); @@ -40,17 +46,14 @@ export async function snooze( async function snoozeWithOCC( context: RulesClientContext, - { - id, - snoozeSchedule, - }: { - id: string; - snoozeSchedule: RuleSnoozeSchedule; - } + { id, snoozeSchedule }: SnoozeRuleOptions ) { - const { attributes, version } = await context.unsecuredSavedObjectsClient.get( - 'alert', - id + const { attributes, version } = await withSpan( + { name: 'getRuleSavedObject', type: 'rules' }, + () => + getRuleSavedObject(context, { + ruleId: id, + }) ); try { @@ -93,17 +96,14 @@ async function snoozeWithOCC( throw Boom.badRequest(error.message); } - const updateAttributes = updateMeta(context, { - ...newAttrs, - updatedBy: await context.getUserName(), - updatedAt: new Date().toISOString(), - }); - const updateOptions = { version }; - - await partiallyUpdateAlert( - context.unsecuredSavedObjectsClient, + await updateRuleSo({ + savedObjectsClient: context.unsecuredSavedObjectsClient, + savedObjectsUpdateOptions: { version }, id, - updateAttributes, - updateOptions - ); + updateRuleAttributes: updateMetaAttributes(context, { + ...newAttrs, + updatedBy: await context.getUserName(), + updatedAt: new Date().toISOString(), + }), + }); } diff --git a/x-pack/plugins/alerting/server/application/rule/methods/snooze/types/index.ts b/x-pack/plugins/alerting/server/application/rule/methods/snooze/types/index.ts new file mode 100644 index 0000000000000..ce69db70c3b8b --- /dev/null +++ b/x-pack/plugins/alerting/server/application/rule/methods/snooze/types/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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './snooze_rule_options'; diff --git a/x-pack/plugins/alerting/server/application/rule/methods/snooze/types/snooze_rule_options.ts b/x-pack/plugins/alerting/server/application/rule/methods/snooze/types/snooze_rule_options.ts new file mode 100644 index 0000000000000..77d077b6ee0e5 --- /dev/null +++ b/x-pack/plugins/alerting/server/application/rule/methods/snooze/types/snooze_rule_options.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { TypeOf } from '@kbn/config-schema'; +import { ruleSnoozeScheduleSchema as ruleSnoozeScheduleRequestSchema } from '../../../../../../common/routes/rule/request'; + +export interface SnoozeRuleOptions { + id: string; + snoozeSchedule: TypeOf; +} diff --git a/x-pack/plugins/alerting/server/application/rule/methods/unsnooze/index.ts b/x-pack/plugins/alerting/server/application/rule/methods/unsnooze/index.ts new file mode 100644 index 0000000000000..3b897aa4675df --- /dev/null +++ b/x-pack/plugins/alerting/server/application/rule/methods/unsnooze/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export type { UnsnoozeParams } from './unsnooze_rule'; +export { unsnoozeRule } from './unsnooze_rule'; diff --git a/x-pack/plugins/alerting/server/application/rule/methods/unsnooze/schemas/index.ts b/x-pack/plugins/alerting/server/application/rule/methods/unsnooze/schemas/index.ts new file mode 100644 index 0000000000000..8abf96bb36f04 --- /dev/null +++ b/x-pack/plugins/alerting/server/application/rule/methods/unsnooze/schemas/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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { unsnoozeRuleParamsSchema } from './unsnooze_rule_params_schema'; diff --git a/x-pack/plugins/alerting/server/application/rule/methods/unsnooze/schemas/unsnooze_rule_params_schema.ts b/x-pack/plugins/alerting/server/application/rule/methods/unsnooze/schemas/unsnooze_rule_params_schema.ts new file mode 100644 index 0000000000000..3f5b26e072519 --- /dev/null +++ b/x-pack/plugins/alerting/server/application/rule/methods/unsnooze/schemas/unsnooze_rule_params_schema.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; + +export const unsnoozeRuleParamsSchema = schema.object({ + id: schema.string(), + scheduleIds: schema.maybe(schema.arrayOf(schema.string())), +}); diff --git a/x-pack/plugins/alerting/server/rules_client/methods/unsnooze.ts b/x-pack/plugins/alerting/server/application/rule/methods/unsnooze/unsnooze_rule.ts similarity index 54% rename from x-pack/plugins/alerting/server/rules_client/methods/unsnooze.ts rename to x-pack/plugins/alerting/server/application/rule/methods/unsnooze/unsnooze_rule.ts index 59d0ea62eb3ff..c47b39f28aedb 100644 --- a/x-pack/plugins/alerting/server/rules_client/methods/unsnooze.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/unsnooze/unsnooze_rule.ts @@ -5,21 +5,24 @@ * 2.0. */ -import { RawRule } from '../../types'; -import { WriteOperations, AlertingAuthorizationEntity } from '../../authorization'; -import { retryIfConflicts } from '../../lib/retry_if_conflicts'; -import { partiallyUpdateAlert } from '../../saved_objects'; -import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; -import { RulesClientContext } from '../types'; -import { updateMeta } from '../lib'; -import { getUnsnoozeAttributes } from '../common'; +import Boom from '@hapi/boom'; +import { withSpan } from '@kbn/apm-utils'; +import { ruleAuditEvent, RuleAuditAction } from '../../../../rules_client/common/audit_events'; +import { getRuleSavedObject } from '../../../../rules_client/lib'; +import { WriteOperations, AlertingAuthorizationEntity } from '../../../../authorization'; +import { retryIfConflicts } from '../../../../lib/retry_if_conflicts'; +import { RulesClientContext } from '../../../../rules_client/types'; +import { getUnsnoozeAttributes } from '../../../../rules_client/common'; +import { updateRuleSo } from '../../../../data/rule'; +import { updateMetaAttributes } from '../../../../rules_client/lib/update_meta_attributes'; +import { unsnoozeRuleParamsSchema } from './schemas'; export interface UnsnoozeParams { id: string; scheduleIds?: string[]; } -export async function unsnooze( +export async function unsnoozeRule( context: RulesClientContext, { id, scheduleIds }: UnsnoozeParams ): Promise { @@ -31,9 +34,17 @@ export async function unsnooze( } async function unsnoozeWithOCC(context: RulesClientContext, { id, scheduleIds }: UnsnoozeParams) { - const { attributes, version } = await context.unsecuredSavedObjectsClient.get( - 'alert', - id + try { + unsnoozeRuleParamsSchema.validate({ id, scheduleIds }); + } catch (error) { + throw Boom.badRequest(`Error validating unsnooze params - ${error.message}`); + } + const { attributes, version } = await withSpan( + { name: 'getRuleSavedObject', type: 'rules' }, + () => + getRuleSavedObject(context, { + ruleId: id, + }) ); try { @@ -69,17 +80,14 @@ async function unsnoozeWithOCC(context: RulesClientContext, { id, scheduleIds }: context.ruleTypeRegistry.ensureRuleTypeEnabled(attributes.alertTypeId); const newAttrs = getUnsnoozeAttributes(attributes, scheduleIds); - const updateAttributes = updateMeta(context, { - ...newAttrs, - updatedBy: await context.getUserName(), - updatedAt: new Date().toISOString(), - }); - const updateOptions = { version }; - - await partiallyUpdateAlert( - context.unsecuredSavedObjectsClient, + await updateRuleSo({ + savedObjectsClient: context.unsecuredSavedObjectsClient, + savedObjectsUpdateOptions: { version }, id, - updateAttributes, - updateOptions - ); + updateRuleAttributes: updateMetaAttributes(context, { + ...newAttrs, + updatedBy: await context.getUserName(), + updatedAt: new Date().toISOString(), + }), + }); } diff --git a/x-pack/plugins/alerting/server/data/r_rule/types/r_rule_attributes.ts b/x-pack/plugins/alerting/server/data/r_rule/types/r_rule_attributes.ts index e29bc5c7ab7e2..54b2547a71b00 100644 --- a/x-pack/plugins/alerting/server/data/r_rule/types/r_rule_attributes.ts +++ b/x-pack/plugins/alerting/server/data/r_rule/types/r_rule_attributes.ts @@ -20,10 +20,10 @@ export interface RRuleAttributes { byweekday?: Array; bymonth?: number[]; bysetpos?: number[]; - bymonthday: number[]; - byyearday: number[]; - byweekno: number[]; - byhour: number[]; - byminute: number[]; - bysecond: number[]; + bymonthday?: number[]; + byyearday?: number[]; + byweekno?: number[]; + byhour?: number[]; + byminute?: number[]; + bysecond?: number[]; } diff --git a/x-pack/plugins/alerting/server/data/rule/index.ts b/x-pack/plugins/alerting/server/data/rule/index.ts index f926adc23f39c..e61b182b92090 100644 --- a/x-pack/plugins/alerting/server/data/rule/index.ts +++ b/x-pack/plugins/alerting/server/data/rule/index.ts @@ -17,5 +17,9 @@ export { bulkCreateRulesSo } from './methods/bulk_create_rule_so'; export type { BulkCreateRulesSoParams } from './methods/bulk_create_rule_so'; export { bulkDeleteRulesSo } from './methods/bulk_delete_rules_so'; export type { BulkDeleteRulesSoParams } from './methods/bulk_delete_rules_so'; +export { resolveRuleSo } from './methods/resolve_rule_so'; +export type { ResolveRuleSoParams } from './methods/resolve_rule_so'; +export { getRuleSo } from './methods/get_rule_so'; +export type { GetRuleSoParams } from './methods/get_rule_so'; export { bulkDisableRulesSo } from './methods/bulk_disable_rules_so'; export type { BulkDisableRulesSoParams } from './methods/bulk_disable_rules_so'; diff --git a/x-pack/plugins/alerting/server/data/rule/methods/get_rule_so.ts b/x-pack/plugins/alerting/server/data/rule/methods/get_rule_so.ts new file mode 100644 index 0000000000000..c8777afef3f78 --- /dev/null +++ b/x-pack/plugins/alerting/server/data/rule/methods/get_rule_so.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SavedObjectsClientContract, SavedObject } from '@kbn/core/server'; +import { SavedObjectsGetOptions } from '@kbn/core-saved-objects-api-server'; +import { RuleAttributes } from '../types'; + +export interface GetRuleSoParams { + savedObjectsClient: SavedObjectsClientContract; + id: string; + savedObjectsGetOptions?: SavedObjectsGetOptions; +} + +export const getRuleSo = (params: GetRuleSoParams): Promise> => { + const { savedObjectsClient, id, savedObjectsGetOptions } = params; + + return savedObjectsClient.get('alert', id, savedObjectsGetOptions); +}; diff --git a/x-pack/plugins/alerting/server/data/rule/methods/resolve_rule_so.ts b/x-pack/plugins/alerting/server/data/rule/methods/resolve_rule_so.ts new file mode 100644 index 0000000000000..b8061cc6e3c3c --- /dev/null +++ b/x-pack/plugins/alerting/server/data/rule/methods/resolve_rule_so.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SavedObjectsClientContract, SavedObjectsResolveResponse } from '@kbn/core/server'; +import { SavedObjectsResolveOptions } from '@kbn/core-saved-objects-api-server'; +import { RuleAttributes } from '../types'; + +export interface ResolveRuleSoParams { + savedObjectsClient: SavedObjectsClientContract; + id: string; + savedObjectsResolveOptions?: SavedObjectsResolveOptions; +} + +export const resolveRuleSo = ( + params: ResolveRuleSoParams +): Promise> => { + const { savedObjectsClient, id, savedObjectsResolveOptions } = params; + + return savedObjectsClient.resolve('alert', id, savedObjectsResolveOptions); +}; diff --git a/x-pack/plugins/alerting/server/routes/index.ts b/x-pack/plugins/alerting/server/routes/index.ts index 93c66c45ce2af..d28854fb3ac6a 100644 --- a/x-pack/plugins/alerting/server/routes/index.ts +++ b/x-pack/plugins/alerting/server/routes/index.ts @@ -29,7 +29,7 @@ import { getActionErrorLogRoute } from './get_action_error_log'; import { getRuleExecutionKPIRoute } from './get_rule_execution_kpi'; import { getRuleStateRoute } from './get_rule_state'; import { healthRoute } from './health'; -import { resolveRuleRoute } from './resolve_rule'; +import { resolveRuleRoute } from './rule/apis/resolve'; import { ruleTypesRoute } from './rule_types'; import { muteAllRuleRoute } from './mute_all_rule'; import { muteAlertRoute } from './rule/apis/mute_alert/mute_alert'; @@ -37,8 +37,8 @@ import { unmuteAllRuleRoute } from './unmute_all_rule'; import { unmuteAlertRoute } from './unmute_alert'; import { updateRuleApiKeyRoute } from './update_rule_api_key'; import { bulkEditInternalRulesRoute } from './rule/apis/bulk_edit/bulk_edit_rules_route'; -import { snoozeRuleRoute } from './snooze_rule'; -import { unsnoozeRuleRoute } from './unsnooze_rule'; +import { snoozeRuleRoute } from './rule/apis/snooze'; +import { unsnoozeRuleRoute } from './rule/apis/unsnooze'; import { runSoonRoute } from './run_soon'; import { bulkDeleteRulesRoute } from './rule/apis/bulk_delete/bulk_delete_rules_route'; import { bulkEnableRulesRoute } from './bulk_enable_rules'; diff --git a/x-pack/plugins/alerting/server/routes/resolve_rule.ts b/x-pack/plugins/alerting/server/routes/resolve_rule.ts deleted file mode 100644 index e42dd6795f14a..0000000000000 --- a/x-pack/plugins/alerting/server/routes/resolve_rule.ts +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { omit } from 'lodash'; -import { schema } from '@kbn/config-schema'; -import { IRouter } from '@kbn/core/server'; -import { ILicenseState } from '../lib'; -import { - verifyAccessAndContext, - RewriteResponseCase, - rewriteRuleLastRun, - rewriteActionsRes, -} from './lib'; -import { - RuleTypeParams, - AlertingRequestHandlerContext, - INTERNAL_BASE_ALERTING_API_PATH, - ResolvedSanitizedRule, -} from '../types'; - -const paramSchema = schema.object({ - id: schema.string(), -}); - -const rewriteBodyRes: RewriteResponseCase> = ({ - alertTypeId, - createdBy, - updatedBy, - createdAt, - updatedAt, - apiKeyOwner, - apiKeyCreatedByUser, - notifyWhen, - muteAll, - mutedInstanceIds, - executionStatus, - actions, - scheduledTaskId, - lastRun, - nextRun, - ...rest -}) => ({ - ...rest, - rule_type_id: alertTypeId, - created_by: createdBy, - updated_by: updatedBy, - created_at: createdAt, - updated_at: updatedAt, - api_key_owner: apiKeyOwner, - notify_when: notifyWhen, - mute_all: muteAll, - muted_alert_ids: mutedInstanceIds, - scheduled_task_id: scheduledTaskId, - execution_status: executionStatus && { - ...omit(executionStatus, 'lastExecutionDate', 'lastDuration'), - last_execution_date: executionStatus.lastExecutionDate, - last_duration: executionStatus.lastDuration, - }, - actions: rewriteActionsRes(actions), - ...(lastRun ? { last_run: rewriteRuleLastRun(lastRun) } : {}), - ...(nextRun ? { next_run: nextRun } : {}), - ...(apiKeyCreatedByUser !== undefined ? { api_key_created_by_user: apiKeyCreatedByUser } : {}), -}); - -export const resolveRuleRoute = ( - router: IRouter, - licenseState: ILicenseState -) => { - router.get( - { - path: `${INTERNAL_BASE_ALERTING_API_PATH}/rule/{id}/_resolve`, - validate: { - params: paramSchema, - }, - }, - router.handleLegacyErrors( - verifyAccessAndContext(licenseState, async function (context, req, res) { - const rulesClient = (await context.alerting).getRulesClient(); - const { id } = req.params; - const rule = await rulesClient.resolve({ id, includeSnoozeData: true }); - return res.ok({ - body: rewriteBodyRes(rule), - }); - }) - ) - ); -}; diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/resolve/index.ts b/x-pack/plugins/alerting/server/routes/rule/apis/resolve/index.ts new file mode 100644 index 0000000000000..5b8e8120f92f9 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/rule/apis/resolve/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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { resolveRuleRoute } from './resolve_rule_route'; diff --git a/x-pack/plugins/alerting/server/routes/resolve_rule.test.ts b/x-pack/plugins/alerting/server/routes/rule/apis/resolve/resolve_rule_route.test.ts similarity index 75% rename from x-pack/plugins/alerting/server/routes/resolve_rule.test.ts rename to x-pack/plugins/alerting/server/routes/rule/apis/resolve/resolve_rule_route.test.ts index 273a7ea7fb8aa..e83cd05b02edd 100644 --- a/x-pack/plugins/alerting/server/routes/resolve_rule.test.ts +++ b/x-pack/plugins/alerting/server/routes/rule/apis/resolve/resolve_rule_route.test.ts @@ -6,17 +6,17 @@ */ import { pick } from 'lodash'; -import { resolveRuleRoute } from './resolve_rule'; +import { resolveRuleRoute } from './resolve_rule_route'; import { httpServiceMock } from '@kbn/core/server/mocks'; -import { licenseStateMock } from '../lib/license_state.mock'; -import { verifyApiAccess } from '../lib/license_api_access'; -import { mockHandlerArguments } from './_mock_handler_arguments'; -import { rulesClientMock } from '../rules_client.mock'; -import { ResolvedSanitizedRule } from '../types'; -import { AsApiContract } from './lib'; +import { licenseStateMock } from '../../../../lib/license_state.mock'; +import { verifyApiAccess } from '../../../../lib/license_api_access'; +import { mockHandlerArguments } from '../../../_mock_handler_arguments'; +import { rulesClientMock } from '../../../../rules_client.mock'; +import { ResolvedRule } from '../../../../application/rule/methods/resolve/types'; +import { ResolvedSanitizedRule } from '../../../../../common'; const rulesClient = rulesClientMock.create(); -jest.mock('../lib/license_api_access', () => ({ +jest.mock('../../../../lib/license_api_access', () => ({ verifyApiAccess: jest.fn(), })); @@ -25,7 +25,7 @@ beforeEach(() => { }); describe('resolveRuleRoute', () => { - const mockedRule: ResolvedSanitizedRule<{ + const mockedRule: ResolvedRule<{ bar: boolean; }> = { id: '1', @@ -67,7 +67,7 @@ describe('resolveRuleRoute', () => { revision: 0, }; - const resolveResult: AsApiContract> = { + const resolveResult = { ...pick( mockedRule, 'consumer', @@ -86,13 +86,13 @@ describe('resolveRuleRoute', () => { updated_by: mockedRule.updatedBy, api_key_owner: mockedRule.apiKeyOwner, muted_alert_ids: mockedRule.mutedInstanceIds, - created_at: mockedRule.createdAt, - updated_at: mockedRule.updatedAt, + created_at: mockedRule.createdAt.toISOString(), + updated_at: mockedRule.updatedAt.toISOString(), id: mockedRule.id, revision: mockedRule.revision, execution_status: { status: mockedRule.executionStatus.status, - last_execution_date: mockedRule.executionStatus.lastExecutionDate, + last_execution_date: mockedRule.executionStatus.lastExecutionDate.toISOString(), }, actions: [ { @@ -115,7 +115,9 @@ describe('resolveRuleRoute', () => { expect(config.path).toMatchInlineSnapshot(`"/internal/alerting/rule/{id}/_resolve"`); - rulesClient.resolve.mockResolvedValueOnce(mockedRule); + // TODO (http-versioning): Remove this cast, this enables us to move forward + // without fixing all of other solution types + rulesClient.resolve.mockResolvedValueOnce(mockedRule as ResolvedSanitizedRule); const [context, req, res] = mockHandlerArguments( { rulesClient }, @@ -142,7 +144,9 @@ describe('resolveRuleRoute', () => { const [, handler] = router.get.mock.calls[0]; - rulesClient.resolve.mockResolvedValueOnce(mockedRule); + // TODO (http-versioning): Remove this cast, this enables us to move forward + // without fixing all of other solution types + rulesClient.resolve.mockResolvedValueOnce(mockedRule as ResolvedSanitizedRule); const [context, req, res] = mockHandlerArguments( { rulesClient }, @@ -169,7 +173,9 @@ describe('resolveRuleRoute', () => { const [, handler] = router.get.mock.calls[0]; - rulesClient.resolve.mockResolvedValueOnce(mockedRule); + // TODO (http-versioning): Remove this cast, this enables us to move forward + // without fixing all of other solution types + rulesClient.resolve.mockResolvedValueOnce(mockedRule as ResolvedSanitizedRule); const [context, req, res] = mockHandlerArguments( { rulesClient }, diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/resolve/resolve_rule_route.ts b/x-pack/plugins/alerting/server/routes/rule/apis/resolve/resolve_rule_route.ts new file mode 100644 index 0000000000000..2e6f016b5f6ac --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/rule/apis/resolve/resolve_rule_route.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { TypeOf } from '@kbn/config-schema'; +import { IRouter } from '@kbn/core/server'; +import { RuleParamsV1 } from '../../../../../common/routes/rule/response'; +import { ResolvedRule } from '../../../../application/rule/methods/resolve/types'; +import { + resolveParamsSchemaV1, + ResolveRuleResponseV1, +} from '../../../../../common/routes/rule/apis/resolve'; +import { ILicenseState } from '../../../../lib'; +import { verifyAccessAndContext } from '../../../lib'; +import { AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH } from '../../../../types'; +import { transformResolveResponseV1 } from './transforms'; + +export type ResolveRuleRequestParamsV1 = TypeOf; + +export const resolveRuleRoute = ( + router: IRouter, + licenseState: ILicenseState +) => { + router.get( + { + path: `${INTERNAL_BASE_ALERTING_API_PATH}/rule/{id}/_resolve`, + validate: { + params: resolveParamsSchemaV1, + }, + }, + router.handleLegacyErrors( + verifyAccessAndContext(licenseState, async function (context, req, res) { + const rulesClient = (await context.alerting).getRulesClient(); + const params: ResolveRuleRequestParamsV1 = req.params; + const { id } = params; + // TODO (http-versioning): Remove this cast, this enables us to move forward + // without fixing all of other solution types + const rule = (await rulesClient.resolve({ + id, + includeSnoozeData: true, + })) as ResolvedRule; + const response: ResolveRuleResponseV1 = { + body: transformResolveResponseV1(rule), + }; + return res.ok(response); + }) + ) + ); +}; diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/resolve/transforms/index.ts b/x-pack/plugins/alerting/server/routes/rule/apis/resolve/transforms/index.ts new file mode 100644 index 0000000000000..bda58b81a7238 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/rule/apis/resolve/transforms/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { transformResolveResponse } from './transform_resolve_response/latest'; + +export { transformResolveResponse as transformResolveResponseV1 } from './transform_resolve_response/v1'; diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/resolve/transforms/transform_resolve_response/latest.ts b/x-pack/plugins/alerting/server/routes/rule/apis/resolve/transforms/transform_resolve_response/latest.ts new file mode 100644 index 0000000000000..25300c97a6d2e --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/rule/apis/resolve/transforms/transform_resolve_response/latest.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './v1'; diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/resolve/transforms/transform_resolve_response/v1.ts b/x-pack/plugins/alerting/server/routes/rule/apis/resolve/transforms/transform_resolve_response/v1.ts new file mode 100644 index 0000000000000..060f6892403f4 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/rule/apis/resolve/transforms/transform_resolve_response/v1.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ResolvedRule } from '../../../../../../application/rule/methods/resolve/types'; +import { RuleParams } from '../../../../../../application/rule/types'; +import { transformRuleToRuleResponseV1 } from '../../../../transforms'; + +export const transformResolveResponse = ( + rule: ResolvedRule +) => ({ + ...transformRuleToRuleResponseV1(rule), + outcome: rule.outcome, + alias_target_id: rule.alias_target_id, +}); diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/snooze/index.ts b/x-pack/plugins/alerting/server/routes/rule/apis/snooze/index.ts new file mode 100644 index 0000000000000..2554a9040c650 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/rule/apis/snooze/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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { snoozeRuleRoute } from './snooze_rule_route'; diff --git a/x-pack/plugins/alerting/server/routes/snooze_rule.test.ts b/x-pack/plugins/alerting/server/routes/rule/apis/snooze/snooze_rule_route.test.ts similarity index 90% rename from x-pack/plugins/alerting/server/routes/snooze_rule.test.ts rename to x-pack/plugins/alerting/server/routes/rule/apis/snooze/snooze_rule_route.test.ts index ab24103672521..51d1b9a23cda8 100644 --- a/x-pack/plugins/alerting/server/routes/snooze_rule.test.ts +++ b/x-pack/plugins/alerting/server/routes/rule/apis/snooze/snooze_rule_route.test.ts @@ -5,15 +5,15 @@ * 2.0. */ -import { snoozeRuleRoute } from './snooze_rule'; +import { snoozeRuleRoute } from './snooze_rule_route'; import { httpServiceMock } from '@kbn/core/server/mocks'; -import { licenseStateMock } from '../lib/license_state.mock'; -import { mockHandlerArguments } from './_mock_handler_arguments'; -import { rulesClientMock } from '../rules_client.mock'; -import { RuleTypeDisabledError } from '../lib/errors/rule_type_disabled'; +import { licenseStateMock } from '../../../../lib/license_state.mock'; +import { mockHandlerArguments } from '../../../_mock_handler_arguments'; +import { rulesClientMock } from '../../../../rules_client.mock'; +import { RuleTypeDisabledError } from '../../../../lib/errors/rule_type_disabled'; const rulesClient = rulesClientMock.create(); -jest.mock('../lib/license_api_access', () => ({ +jest.mock('../../../../lib/license_api_access', () => ({ verifyApiAccess: jest.fn(), })); diff --git a/x-pack/plugins/alerting/server/routes/snooze_rule.ts b/x-pack/plugins/alerting/server/routes/rule/apis/snooze/snooze_rule_route.ts similarity index 52% rename from x-pack/plugins/alerting/server/routes/snooze_rule.ts rename to x-pack/plugins/alerting/server/routes/rule/apis/snooze/snooze_rule_route.ts index 1ffac168fcd00..93b619b50d82c 100644 --- a/x-pack/plugins/alerting/server/routes/snooze_rule.ts +++ b/x-pack/plugins/alerting/server/routes/rule/apis/snooze/snooze_rule_route.ts @@ -5,36 +5,18 @@ * 2.0. */ +import { TypeOf } from '@kbn/config-schema'; import { IRouter } from '@kbn/core/server'; -import { schema } from '@kbn/config-schema'; -import { ILicenseState, RuleMutedError } from '../lib'; -import { verifyAccessAndContext, rRuleSchema } from './lib'; -import { SnoozeOptions } from '../rules_client'; -import { AlertingRequestHandlerContext, INTERNAL_ALERTING_SNOOZE_RULE } from '../types'; -import { validateSnoozeSchedule } from '../lib/validate_snooze_schedule'; +import { + snoozeBodySchema, + snoozeParamsSchema, +} from '../../../../../common/routes/rule/apis/snooze'; +import { ILicenseState, RuleMutedError } from '../../../../lib'; +import { verifyAccessAndContext } from '../../../lib'; +import { AlertingRequestHandlerContext, INTERNAL_ALERTING_SNOOZE_RULE } from '../../../../types'; +import { transformSnoozeBodyV1 } from './transforms'; -const paramSchema = schema.object({ - id: schema.string(), -}); - -export const snoozeScheduleSchema = schema.object( - { - id: schema.maybe(schema.string()), - duration: schema.number(), - rRule: rRuleSchema, - }, - { validate: validateSnoozeSchedule } -); - -const bodySchema = schema.object({ - snooze_schedule: snoozeScheduleSchema, -}); - -const rewriteBodyReq: (opts: { - snooze_schedule: SnoozeOptions['snoozeSchedule']; -}) => SnoozeOptions = ({ snooze_schedule: snoozeSchedule }) => ({ - snoozeSchedule, -}); +export type SnoozeRuleRequestParamsV1 = TypeOf; export const snoozeRuleRoute = ( router: IRouter, @@ -44,15 +26,15 @@ export const snoozeRuleRoute = ( { path: INTERNAL_ALERTING_SNOOZE_RULE, validate: { - params: paramSchema, - body: bodySchema, + params: snoozeParamsSchema, + body: snoozeBodySchema, }, }, router.handleLegacyErrors( verifyAccessAndContext(licenseState, async function (context, req, res) { const rulesClient = (await context.alerting).getRulesClient(); - const params = req.params; - const body = rewriteBodyReq(req.body); + const params: SnoozeRuleRequestParamsV1 = req.params; + const body = transformSnoozeBodyV1(req.body); try { await rulesClient.snooze({ ...params, ...body }); return res.noContent(); diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/snooze/transforms/index.ts b/x-pack/plugins/alerting/server/routes/rule/apis/snooze/transforms/index.ts new file mode 100644 index 0000000000000..fce05888d26fa --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/rule/apis/snooze/transforms/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { transformSnoozeBody } from './transform_snooze_body/latest'; + +export { transformSnoozeBody as transformSnoozeBodyV1 } from './transform_snooze_body/v1'; diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/snooze/transforms/transform_snooze_body/latest.ts b/x-pack/plugins/alerting/server/routes/rule/apis/snooze/transforms/transform_snooze_body/latest.ts new file mode 100644 index 0000000000000..25300c97a6d2e --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/rule/apis/snooze/transforms/transform_snooze_body/latest.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './v1'; diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/snooze/transforms/transform_snooze_body/v1.ts b/x-pack/plugins/alerting/server/routes/rule/apis/snooze/transforms/transform_snooze_body/v1.ts new file mode 100644 index 0000000000000..ff7c890ef9cbd --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/rule/apis/snooze/transforms/transform_snooze_body/v1.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { TypeOf } from '@kbn/config-schema'; +import { snoozeBodySchemaV1 } from '../../../../../../../common/routes/rule/apis/snooze'; + +type SnoozeBodySchema = TypeOf; + +export const transformSnoozeBody: (opts: SnoozeBodySchema) => { + snoozeSchedule: SnoozeBodySchema['snooze_schedule']; +} = ({ snooze_schedule: snoozeSchedule }) => ({ + snoozeSchedule, +}); diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/unsnooze/index.ts b/x-pack/plugins/alerting/server/routes/rule/apis/unsnooze/index.ts new file mode 100644 index 0000000000000..5702c1a2da283 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/rule/apis/unsnooze/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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { unsnoozeRuleRoute } from './unsnooze_rule_route'; diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/unsnooze/transforms/index.ts b/x-pack/plugins/alerting/server/routes/rule/apis/unsnooze/transforms/index.ts new file mode 100644 index 0000000000000..1ebc46c14819e --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/rule/apis/unsnooze/transforms/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { transformUnsnoozeBody } from './transform_unsnooze_body/latest'; + +export { transformUnsnoozeBody as transformUnsnoozeBodyV1 } from './transform_unsnooze_body/v1'; diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/unsnooze/transforms/transform_unsnooze_body/latest.ts b/x-pack/plugins/alerting/server/routes/rule/apis/unsnooze/transforms/transform_unsnooze_body/latest.ts new file mode 100644 index 0000000000000..25300c97a6d2e --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/rule/apis/unsnooze/transforms/transform_unsnooze_body/latest.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './v1'; diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/unsnooze/transforms/transform_unsnooze_body/v1.ts b/x-pack/plugins/alerting/server/routes/rule/apis/unsnooze/transforms/transform_unsnooze_body/v1.ts new file mode 100644 index 0000000000000..bbaa37cf81f3b --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/rule/apis/unsnooze/transforms/transform_unsnooze_body/v1.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const transformUnsnoozeBody: (opts: { schedule_ids?: string[] }) => { + scheduleIds?: string[]; +} = ({ schedule_ids: scheduleIds }) => (scheduleIds ? { scheduleIds } : {}); diff --git a/x-pack/plugins/alerting/server/routes/unsnooze_rule.test.ts b/x-pack/plugins/alerting/server/routes/rule/apis/unsnooze/unsnooze_rule_route.test.ts similarity index 83% rename from x-pack/plugins/alerting/server/routes/unsnooze_rule.test.ts rename to x-pack/plugins/alerting/server/routes/rule/apis/unsnooze/unsnooze_rule_route.test.ts index 12a4c250bef1d..1d69b00eb8486 100644 --- a/x-pack/plugins/alerting/server/routes/unsnooze_rule.test.ts +++ b/x-pack/plugins/alerting/server/routes/rule/apis/unsnooze/unsnooze_rule_route.test.ts @@ -5,15 +5,15 @@ * 2.0. */ -import { unsnoozeRuleRoute } from './unsnooze_rule'; +import { unsnoozeRuleRoute } from './unsnooze_rule_route'; import { httpServiceMock } from '@kbn/core/server/mocks'; -import { licenseStateMock } from '../lib/license_state.mock'; -import { mockHandlerArguments } from './_mock_handler_arguments'; -import { rulesClientMock } from '../rules_client.mock'; -import { RuleTypeDisabledError } from '../lib/errors/rule_type_disabled'; +import { licenseStateMock } from '../../../../lib/license_state.mock'; +import { mockHandlerArguments } from '../../../_mock_handler_arguments'; +import { rulesClientMock } from '../../../../rules_client.mock'; +import { RuleTypeDisabledError } from '../../../../lib/errors/rule_type_disabled'; const rulesClient = rulesClientMock.create(); -jest.mock('../lib/license_api_access', () => ({ +jest.mock('../../../../lib/license_api_access', () => ({ verifyApiAccess: jest.fn(), })); diff --git a/x-pack/plugins/alerting/server/routes/unsnooze_rule.ts b/x-pack/plugins/alerting/server/routes/rule/apis/unsnooze/unsnooze_rule_route.ts similarity index 60% rename from x-pack/plugins/alerting/server/routes/unsnooze_rule.ts rename to x-pack/plugins/alerting/server/routes/rule/apis/unsnooze/unsnooze_rule_route.ts index 5efea85c93ec1..e37b5c0217f0a 100644 --- a/x-pack/plugins/alerting/server/routes/unsnooze_rule.ts +++ b/x-pack/plugins/alerting/server/routes/rule/apis/unsnooze/unsnooze_rule_route.ts @@ -5,25 +5,18 @@ * 2.0. */ +import { TypeOf } from '@kbn/config-schema'; import { IRouter } from '@kbn/core/server'; -import { schema } from '@kbn/config-schema'; -import { ILicenseState, RuleMutedError } from '../lib'; -import { verifyAccessAndContext, RewriteRequestCase } from './lib'; -import { AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH } from '../types'; +import { + unsnoozeBodySchema, + unsnoozeParamsSchema, +} from '../../../../../common/routes/rule/apis/unsnooze'; +import { ILicenseState, RuleMutedError } from '../../../../lib'; +import { verifyAccessAndContext } from '../../../lib'; +import { AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH } from '../../../../types'; +import { transformUnsnoozeBodyV1 } from './transforms'; -const paramSchema = schema.object({ - id: schema.string(), -}); - -export const scheduleIdsSchema = schema.maybe(schema.arrayOf(schema.string())); - -const bodySchema = schema.object({ - schedule_ids: scheduleIdsSchema, -}); - -const rewriteBodyReq: RewriteRequestCase<{ scheduleIds?: string[] }> = ({ - schedule_ids: scheduleIds, -}) => (scheduleIds ? { scheduleIds } : {}); +export type UnsnoozeRuleRequestParamsV1 = TypeOf; export const unsnoozeRuleRoute = ( router: IRouter, @@ -33,15 +26,15 @@ export const unsnoozeRuleRoute = ( { path: `${INTERNAL_BASE_ALERTING_API_PATH}/rule/{id}/_unsnooze`, validate: { - params: paramSchema, - body: bodySchema, + params: unsnoozeParamsSchema, + body: unsnoozeBodySchema, }, }, router.handleLegacyErrors( verifyAccessAndContext(licenseState, async function (context, req, res) { const rulesClient = (await context.alerting).getRulesClient(); - const params = req.params; - const body = rewriteBodyReq(req.body); + const params: UnsnoozeRuleRequestParamsV1 = req.params; + const body = transformUnsnoozeBodyV1(req.body); try { await rulesClient.unsnooze({ ...params, ...body }); return res.noContent(); diff --git a/x-pack/plugins/alerting/server/routes/rule/transforms/transform_rule_to_rule_response/v1.ts b/x-pack/plugins/alerting/server/routes/rule/transforms/transform_rule_to_rule_response/v1.ts index e43427fe4470a..74508cec9a640 100644 --- a/x-pack/plugins/alerting/server/routes/rule/transforms/transform_rule_to_rule_response/v1.ts +++ b/x-pack/plugins/alerting/server/routes/rule/transforms/transform_rule_to_rule_response/v1.ts @@ -82,6 +82,7 @@ export const transformRuleToRuleResponse = ( mute_all: rule.muteAll, ...(rule.notifyWhen !== undefined ? { notify_when: rule.notifyWhen } : {}), muted_alert_ids: rule.mutedInstanceIds, + ...(rule.scheduledTaskId !== undefined ? { scheduled_task_id: rule.scheduledTaskId } : {}), execution_status: { status: rule.executionStatus.status, ...(rule.executionStatus.error ? { error: rule.executionStatus.error } : {}), diff --git a/x-pack/plugins/alerting/server/rules_client/common/snooze_utils.ts b/x-pack/plugins/alerting/server/rules_client/common/snooze_utils.ts index edf61427b8339..1f8cf501f7868 100644 --- a/x-pack/plugins/alerting/server/rules_client/common/snooze_utils.ts +++ b/x-pack/plugins/alerting/server/rules_client/common/snooze_utils.ts @@ -6,18 +6,19 @@ */ import { i18n } from '@kbn/i18n'; -import { RawRule, RuleSnoozeSchedule } from '../../types'; import { + Rule, RuleDomain, RuleParams, RuleSnoozeSchedule as RuleDomainSnoozeSchedule, } from '../../application/rule/types'; +import { RuleAttributes } from '../../data/rule/types'; import { getActiveScheduledSnoozes } from '../../lib/is_rule_snoozed'; -/** - * @deprecated TODO (http-versioning): Deprecate this once we fix all RawRule types - */ -export function getSnoozeAttributes(attributes: RawRule, snoozeSchedule: RuleSnoozeSchedule) { +export function getSnoozeAttributes( + attributes: RuleAttributes, + snoozeSchedule: RuleDomainSnoozeSchedule +) { // If duration is -1, instead mute all const { id: snoozeId, duration } = snoozeSchedule; @@ -69,10 +70,7 @@ export function getBulkSnooze( }; } -/** - * @deprecated TODO (http-versioning): Deprecate this once we fix all RawRule types - */ -export function getUnsnoozeAttributes(attributes: RawRule, scheduleIds?: string[]) { +export function getUnsnoozeAttributes(attributes: RuleAttributes, scheduleIds?: string[]) { const snoozeSchedule = scheduleIds ? clearScheduledSnoozesAttributesById(attributes, scheduleIds) : clearCurrentActiveSnoozeAttributes(attributes); @@ -106,10 +104,7 @@ export function getBulkUnsnooze( }; } -/** - * @deprecated TODO (http-versioning): Deprecate this once we fix all RawRule types - */ -export function clearUnscheduledSnoozeAttributes(attributes: RawRule) { +export function clearUnscheduledSnoozeAttributes(attributes: RuleAttributes) { // Clear any snoozes that have no ID property. These are "simple" snoozes created with the quick UI, e.g. snooze for 3 days starting now return attributes.snoozeSchedule ? attributes.snoozeSchedule.filter((s) => typeof s.id !== 'undefined') @@ -120,10 +115,7 @@ export function clearUnscheduledSnooze(rule: RuleDoma return rule.snoozeSchedule ? rule.snoozeSchedule.filter((s) => typeof s.id !== 'undefined') : []; } -/** - * @deprecated TODO (http-versioning): Deprecate this once we fix all RawRule types - */ -export function clearScheduledSnoozesAttributesById(attributes: RawRule, ids: string[]) { +export function clearScheduledSnoozesAttributesById(attributes: RuleAttributes, ids: string[]) { return attributes.snoozeSchedule ? attributes.snoozeSchedule.filter((s) => s.id && !ids.includes(s.id)) : []; @@ -136,10 +128,7 @@ export function clearScheduledSnoozesById( return rule.snoozeSchedule ? rule.snoozeSchedule.filter((s) => s.id && !ids.includes(s.id)) : []; } -/** - * @deprecated TODO (http-versioning): Deprecate this once we fix all RawRule types - */ -export function clearCurrentActiveSnoozeAttributes(attributes: RawRule) { +export function clearCurrentActiveSnoozeAttributes(attributes: RuleAttributes) { // First attempt to cancel a simple (unscheduled) snooze const clearedUnscheduledSnoozes = clearUnscheduledSnoozeAttributes(attributes); // Now clear any scheduled snoozes that are currently active and never recur @@ -193,10 +182,7 @@ export function clearCurrentActiveSnooze(rule: RuleDo return clearedSnoozesAndSkippedRecurringSnoozes; } -/** - * @deprecated TODO (http-versioning): Deprecate this once we fix all RawRule types - */ -export function verifySnoozeAttributeScheduleLimit(attributes: Partial) { +export function verifySnoozeAttributeScheduleLimit(attributes: Partial) { const schedules = attributes.snoozeSchedule?.filter((snooze) => snooze.id); if (schedules && schedules.length > 5) { throw Error( diff --git a/x-pack/plugins/alerting/server/rules_client/lib/get_alert_from_raw.ts b/x-pack/plugins/alerting/server/rules_client/lib/get_alert_from_raw.ts index 2b4dbce71f76a..ddf1e40728b28 100644 --- a/x-pack/plugins/alerting/server/rules_client/lib/get_alert_from_raw.ts +++ b/x-pack/plugins/alerting/server/rules_client/lib/get_alert_from_raw.ts @@ -37,6 +37,9 @@ export interface GetAlertFromRawParams { omitGeneratedValues?: boolean; } +/** + * @deprecated in favor of transformRuleAttributesToRuleDomain + */ export function getAlertFromRaw( context: RulesClientContext, id: string, diff --git a/x-pack/plugins/alerting/server/rules_client/lib/get_rule_saved_object.ts b/x-pack/plugins/alerting/server/rules_client/lib/get_rule_saved_object.ts new file mode 100644 index 0000000000000..43e0344e259fd --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/lib/get_rule_saved_object.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SavedObject } from '@kbn/core/server'; +import { withSpan } from '@kbn/apm-utils'; +import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; +import { RulesClientContext } from '../types'; +import { getRuleSo } from '../../data/rule'; +import { RuleAttributes } from '../../data/rule/types'; + +interface GetRuleSavedObjectParams { + ruleId: string; +} + +export async function getRuleSavedObject( + context: RulesClientContext, + params: GetRuleSavedObjectParams +): Promise> { + const { ruleId } = params; + + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.GET, + outcome: 'unknown', + savedObject: { type: 'alert', id: ruleId }, + }) + ); + + return await withSpan({ name: 'unsecuredSavedObjectsClient.get', type: 'rules' }, () => + getRuleSo({ + id: ruleId, + savedObjectsClient: context.unsecuredSavedObjectsClient, + }) + ); +} diff --git a/x-pack/plugins/alerting/server/rules_client/lib/index.ts b/x-pack/plugins/alerting/server/rules_client/lib/index.ts index ab1f33df01c10..ede7c3034a7f2 100644 --- a/x-pack/plugins/alerting/server/rules_client/lib/index.ts +++ b/x-pack/plugins/alerting/server/rules_client/lib/index.ts @@ -6,9 +6,13 @@ */ export { createRuleSavedObject } from './create_rule_saved_object'; +export { resolveRuleSavedObject } from './resolve_rule_saved_object'; +export { getRuleSavedObject } from './get_rule_saved_object'; + export { extractReferences } from './extract_references'; export { validateActions } from './validate_actions'; export { updateMeta } from './update_meta'; +export { updateMetaAttributes } from './update_meta_attributes'; export * from './get_alert_from_raw'; export { getAuthorizationFilter } from './get_authorization_filter'; export { checkAuthorizationAndGetTotal } from './check_authorization_and_get_total'; diff --git a/x-pack/plugins/alerting/server/rules_client/lib/resolve_rule_saved_object.ts b/x-pack/plugins/alerting/server/rules_client/lib/resolve_rule_saved_object.ts new file mode 100644 index 0000000000000..101f846ae9ba9 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/lib/resolve_rule_saved_object.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SavedObjectsResolveResponse } from '@kbn/core/server'; +import { withSpan } from '@kbn/apm-utils'; +import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; +import { RulesClientContext } from '../types'; +import { resolveRuleSo } from '../../data/rule'; +import { RuleAttributes } from '../../data/rule/types'; + +interface ResolveRuleSavedObjectParams { + ruleId: string; +} + +export async function resolveRuleSavedObject( + context: RulesClientContext, + params: ResolveRuleSavedObjectParams +): Promise> { + const { ruleId } = params; + + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.RESOLVE, + outcome: 'unknown', + savedObject: { type: 'alert', id: ruleId }, + }) + ); + + return await withSpan({ name: 'unsecuredSavedObjectsClient.resolve', type: 'rules' }, () => + resolveRuleSo({ + id: ruleId, + savedObjectsClient: context.unsecuredSavedObjectsClient, + }) + ); +} diff --git a/x-pack/plugins/alerting/server/rules_client/lib/update_meta.ts b/x-pack/plugins/alerting/server/rules_client/lib/update_meta.ts index 5fbe2b275f077..278a6ac267df5 100644 --- a/x-pack/plugins/alerting/server/rules_client/lib/update_meta.ts +++ b/x-pack/plugins/alerting/server/rules_client/lib/update_meta.ts @@ -8,6 +8,9 @@ import { RawRule } from '../../types'; import { RulesClientContext } from '../types'; +/** + * @deprecated Use updateMetaAttributes instead + */ export function updateMeta>( context: RulesClientContext, alertAttributes: T diff --git a/x-pack/plugins/alerting/server/rules_client/lib/update_meta_attributes.ts b/x-pack/plugins/alerting/server/rules_client/lib/update_meta_attributes.ts new file mode 100644 index 0000000000000..716e0a8beacb9 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/lib/update_meta_attributes.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { RuleAttributes } from '../../data/rule/types'; +import { RulesClientContext } from '../types'; + +export function updateMetaAttributes>( + context: RulesClientContext, + alertAttributes: T +): T { + if (alertAttributes.hasOwnProperty('apiKey') || alertAttributes.hasOwnProperty('apiKeyOwner')) { + return { + ...alertAttributes, + meta: { + ...(alertAttributes.meta ?? {}), + versionApiKeyLastmodified: context.kibanaVersion, + }, + }; + } + return alertAttributes; +} diff --git a/x-pack/plugins/alerting/server/rules_client/methods/mute_all.ts b/x-pack/plugins/alerting/server/rules_client/methods/mute_all.ts index ca5584da706a7..72a693aace7ac 100644 --- a/x-pack/plugins/alerting/server/rules_client/methods/mute_all.ts +++ b/x-pack/plugins/alerting/server/rules_client/methods/mute_all.ts @@ -11,8 +11,9 @@ import { retryIfConflicts } from '../../lib/retry_if_conflicts'; import { partiallyUpdateAlert } from '../../saved_objects'; import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; import { RulesClientContext } from '../types'; -import { updateMeta } from '../lib'; +import { updateMetaAttributes } from '../lib'; import { clearUnscheduledSnoozeAttributes } from '../common'; +import { RuleAttributes } from '../../data/rule/types'; export async function muteAll(context: RulesClientContext, { id }: { id: string }): Promise { return await retryIfConflicts( @@ -60,10 +61,10 @@ async function muteAllWithOCC(context: RulesClientContext, { id }: { id: string context.ruleTypeRegistry.ensureRuleTypeEnabled(attributes.alertTypeId); - const updateAttributes = updateMeta(context, { + const updateAttributes = updateMetaAttributes(context, { muteAll: true, mutedInstanceIds: [], - snoozeSchedule: clearUnscheduledSnoozeAttributes(attributes), + snoozeSchedule: clearUnscheduledSnoozeAttributes(attributes as RuleAttributes), updatedBy: await context.getUserName(), updatedAt: new Date().toISOString(), }); diff --git a/x-pack/plugins/alerting/server/rules_client/methods/resolve.ts b/x-pack/plugins/alerting/server/rules_client/methods/resolve.ts deleted file mode 100644 index a663b9aac56d1..0000000000000 --- a/x-pack/plugins/alerting/server/rules_client/methods/resolve.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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { AlertConsumers } from '@kbn/rule-data-utils'; - -import { RawRule, RuleTypeParams, ResolvedSanitizedRule } from '../../types'; -import { ReadOperations, AlertingAuthorizationEntity } from '../../authorization'; -import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; -import { getAlertFromRaw } from '../lib/get_alert_from_raw'; -import { RulesClientContext } from '../types'; -import { formatLegacyActions } from '../lib'; - -export interface ResolveParams { - id: string; - includeLegacyId?: boolean; - includeSnoozeData?: boolean; -} - -export async function resolve( - context: RulesClientContext, - { id, includeLegacyId, includeSnoozeData = false }: ResolveParams -): Promise> { - const { saved_object: result, ...resolveResponse } = - await context.unsecuredSavedObjectsClient.resolve('alert', id); - try { - await context.authorization.ensureAuthorized({ - ruleTypeId: result.attributes.alertTypeId, - consumer: result.attributes.consumer, - operation: ReadOperations.Get, - entity: AlertingAuthorizationEntity.Rule, - }); - } catch (error) { - context.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.RESOLVE, - savedObject: { type: 'alert', id }, - error, - }) - ); - throw error; - } - context.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.RESOLVE, - savedObject: { type: 'alert', id }, - }) - ); - - const rule = getAlertFromRaw( - context, - result.id, - result.attributes.alertTypeId, - result.attributes, - result.references, - includeLegacyId, - false, - includeSnoozeData - ); - - // format legacy actions for SIEM rules - if (result.attributes.consumer === AlertConsumers.SIEM) { - const [migratedRule] = await formatLegacyActions([rule], { - savedObjectsClient: context.unsecuredSavedObjectsClient, - logger: context.logger, - }); - - return { - ...migratedRule, - ...resolveResponse, - }; - } - - return { - ...rule, - ...resolveResponse, - }; -} diff --git a/x-pack/plugins/alerting/server/rules_client/methods/unmute_all.ts b/x-pack/plugins/alerting/server/rules_client/methods/unmute_all.ts index e69f09a219700..52403e2d8f70e 100644 --- a/x-pack/plugins/alerting/server/rules_client/methods/unmute_all.ts +++ b/x-pack/plugins/alerting/server/rules_client/methods/unmute_all.ts @@ -11,8 +11,9 @@ import { retryIfConflicts } from '../../lib/retry_if_conflicts'; import { partiallyUpdateAlert } from '../../saved_objects'; import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; import { RulesClientContext } from '../types'; -import { updateMeta } from '../lib'; +import { updateMetaAttributes } from '../lib'; import { clearUnscheduledSnoozeAttributes } from '../common'; +import { RuleAttributes } from '../../data/rule/types'; export async function unmuteAll( context: RulesClientContext, @@ -63,10 +64,10 @@ async function unmuteAllWithOCC(context: RulesClientContext, { id }: { id: strin context.ruleTypeRegistry.ensureRuleTypeEnabled(attributes.alertTypeId); - const updateAttributes = updateMeta(context, { + const updateAttributes = updateMetaAttributes(context, { muteAll: false, mutedInstanceIds: [], - snoozeSchedule: clearUnscheduledSnoozeAttributes(attributes), + snoozeSchedule: clearUnscheduledSnoozeAttributes(attributes as RuleAttributes), updatedBy: await context.getUserName(), updatedAt: new Date().toISOString(), }); diff --git a/x-pack/plugins/alerting/server/rules_client/rules_client.ts b/x-pack/plugins/alerting/server/rules_client/rules_client.ts index 12212b772ce71..0a2da42d7e424 100644 --- a/x-pack/plugins/alerting/server/rules_client/rules_client.ts +++ b/x-pack/plugins/alerting/server/rules_client/rules_client.ts @@ -12,8 +12,10 @@ import { parseDuration } from '../../common/parse_duration'; import { RulesClientContext, BulkOptions } from './types'; import { clone, CloneArguments } from './methods/clone'; import { createRule, CreateRuleParams } from '../application/rule/methods/create'; +import { snoozeRule, SnoozeRuleOptions } from '../application/rule/methods/snooze'; +import { unsnoozeRule, UnsnoozeParams } from '../application/rule/methods/unsnooze'; import { get, GetParams } from './methods/get'; -import { resolve, ResolveParams } from './methods/resolve'; +import { resolveRule, ResolveParams } from '../application/rule/methods/resolve'; import { getAlertState, GetAlertStateParams } from './methods/get_alert_state'; import { getAlertSummary, GetAlertSummaryParams } from './methods/get_alert_summary'; import { @@ -54,8 +56,6 @@ import { bulkEnableRules } from './methods/bulk_enable'; import { updateApiKey } from './methods/update_api_key'; import { enable } from './methods/enable'; import { disable } from './methods/disable'; -import { snooze, SnoozeParams } from './methods/snooze'; -import { unsnooze, UnsnoozeParams } from './methods/unsnooze'; import { clearExpiredSnoozes } from './methods/clear_expired_snoozes'; import { muteInstance } from '../application/rule/methods/mute_alert/mute_instance'; import { muteAll } from './methods/mute_all'; @@ -130,7 +130,7 @@ export class RulesClient { public get = (params: GetParams) => get(this.context, params); public resolve = (params: ResolveParams) => - resolve(this.context, params); + resolveRule(this.context, params); public update = (params: UpdateOptions) => update(this.context, params); @@ -162,8 +162,8 @@ export class RulesClient { public enable = (options: { id: string }) => enable(this.context, options); public disable = (options: { id: string }) => disable(this.context, options); - public snooze = (options: SnoozeParams) => snooze(this.context, options); - public unsnooze = (options: UnsnoozeParams) => unsnooze(this.context, options); + public snooze = (options: SnoozeRuleOptions) => snoozeRule(this.context, options); + public unsnooze = (options: UnsnoozeParams) => unsnoozeRule(this.context, options); public clearExpiredSnoozes = (options: { rule: Pick, 'id' | 'snoozeSchedule'>; diff --git a/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts index 0cf9e792cc1bf..70c6388c41ff4 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts @@ -96,6 +96,11 @@ describe('resolve()', () => { }, ], notifyWhen: 'onActiveAlert', + executionStatus: { + status: 'ok', + last_execution_date: new Date().toISOString(), + last_duration: 10, + }, }, references: [ { @@ -123,82 +128,11 @@ describe('resolve()', () => { "alertTypeId": "123", "alias_target_id": "2", "createdAt": 2019-02-12T21:01:22.479Z, - "id": "1", - "notifyWhen": "onActiveAlert", - "outcome": "aliasMatch", - "params": Object { - "bar": true, + "executionStatus": Object { + "lastExecutionDate": 2019-02-12T21:01:22.479Z, + "status": "ok", }, - "schedule": Object { - "interval": "10s", - }, - "snoozeSchedule": Array [], - "updatedAt": 2019-02-12T21:01:22.479Z, - } - `); - expect(unsecuredSavedObjectsClient.resolve).toHaveBeenCalledTimes(1); - expect(unsecuredSavedObjectsClient.resolve.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "alert", - "1", - ] - `); - }); - - test('calls saved objects client with id and includeLegacyId params', async () => { - const rulesClient = new RulesClient(rulesClientParams); - unsecuredSavedObjectsClient.resolve.mockResolvedValueOnce({ - saved_object: { - id: '1', - type: 'alert', - attributes: { - legacyId: 'some-legacy-id', - alertTypeId: '123', - schedule: { interval: '10s' }, - params: { - bar: true, - }, - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - actions: [ - { - group: 'default', - actionRef: 'action_0', - params: { - foo: true, - }, - }, - ], - notifyWhen: 'onActiveAlert', - }, - references: [ - { - name: 'action_0', - type: 'action', - id: '1', - }, - ], - }, - outcome: 'aliasMatch', - alias_target_id: '2', - }); - const result = await rulesClient.resolve({ id: '1', includeLegacyId: true }); - expect(result).toMatchInlineSnapshot(` - Object { - "actions": Array [ - Object { - "group": "default", - "id": "1", - "params": Object { - "foo": true, - }, - }, - ], - "alertTypeId": "123", - "alias_target_id": "2", - "createdAt": 2019-02-12T21:01:22.479Z, "id": "1", - "legacyId": "some-legacy-id", "notifyWhen": "onActiveAlert", "outcome": "aliasMatch", "params": Object { @@ -213,11 +147,12 @@ describe('resolve()', () => { `); expect(unsecuredSavedObjectsClient.resolve).toHaveBeenCalledTimes(1); expect(unsecuredSavedObjectsClient.resolve.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "alert", - "1", - ] - `); + Array [ + "alert", + "1", + undefined, + ] + `); }); test('calls saved objects client with id and includeSnoozeData params', async () => { @@ -256,6 +191,11 @@ describe('resolve()', () => { }, ], notifyWhen: 'onActiveAlert', + executionStatus: { + status: 'ok', + last_execution_date: new Date().toISOString(), + last_duration: 10, + }, }, references: [ { @@ -323,6 +263,11 @@ describe('resolve()', () => { }, ], notifyWhen: 'onActiveAlert', + executionStatus: { + status: 'ok', + last_execution_date: new Date().toISOString(), + last_duration: 10, + }, }, references: [ { @@ -363,6 +308,10 @@ describe('resolve()', () => { "alertTypeId": "123", "alias_target_id": "2", "createdAt": 2019-02-12T21:01:22.479Z, + "executionStatus": Object { + "lastExecutionDate": 2019-02-12T21:01:22.479Z, + "status": "ok", + }, "id": "1", "notifyWhen": "onActiveAlert", "outcome": "aliasMatch", @@ -400,6 +349,11 @@ describe('resolve()', () => { }, }, ], + executionStatus: { + status: 'ok', + last_execution_date: new Date().toISOString(), + last_duration: 10, + }, }, references: [], }, @@ -505,6 +459,11 @@ describe('resolve()', () => { }, }, ], + executionStatus: { + status: 'ok', + last_execution_date: new Date().toISOString(), + last_duration: 10, + }, }, references: [ { @@ -563,6 +522,11 @@ describe('resolve()', () => { bar: true, }, actions: [], + executionStatus: { + status: 'ok', + last_execution_date: new Date().toISOString(), + last_duration: 10, + }, }, references: [], }, @@ -633,6 +597,11 @@ describe('resolve()', () => { }, ], notifyWhen: 'onActiveAlert', + executionStatus: { + status: 'ok', + last_execution_date: new Date().toISOString(), + last_duration: 10, + }, }, references: [ { diff --git a/x-pack/plugins/alerting/server/types.ts b/x-pack/plugins/alerting/server/types.ts index 26eed5f254bc9..d5e0b331833d5 100644 --- a/x-pack/plugins/alerting/server/types.ts +++ b/x-pack/plugins/alerting/server/types.ts @@ -442,6 +442,9 @@ export interface RawRuleExecutionStatus extends SavedObjectAttributes { }; } +/** + * @deprecated in favor of Rule + */ export interface RawRule extends SavedObjectAttributes { enabled: boolean; name: string; diff --git a/x-pack/plugins/alerting/tsconfig.json b/x-pack/plugins/alerting/tsconfig.json index 987ad98b927cf..9eb0b05f7884c 100644 --- a/x-pack/plugins/alerting/tsconfig.json +++ b/x-pack/plugins/alerting/tsconfig.json @@ -59,7 +59,8 @@ "@kbn/serverless", "@kbn/core-http-router-server-mocks", "@kbn/core-elasticsearch-server", - "@kbn/core-application-common" + "@kbn/core-application-common", + "@kbn/core-saved-objects-api-server" ], "exclude": ["target/**/*"] } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/notify_badge/notify_badge.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/notify_badge/notify_badge.tsx index 8ddb44d0c6c67..e09892471419b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/notify_badge/notify_badge.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/notify_badge/notify_badge.tsx @@ -199,7 +199,6 @@ export const RulesListNotifyBadge: React.FunctionComponent { - // TODO: Implement scheduled snooze button return (