diff --git a/.github/ISSUE_TEMPLATE/v8_breaking_change.md b/.github/ISSUE_TEMPLATE/v8_breaking_change.md deleted file mode 100644 index 8ef570947dd41..0000000000000 --- a/.github/ISSUE_TEMPLATE/v8_breaking_change.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -name: 8.0 Breaking change -about: Breaking changes from 7.x -> 8.0 -title: "[Breaking change]" -labels: Feature:Upgrade Assistant, Breaking Change -assignees: '' - ---- - - - -## Change description - -**Which release will ship the breaking change?** - -8.0 - -**Is this a Kibana or Elasticsearch breaking change?** - - - -**Describe the change. How will it manifest to users?** - -**How many users will be affected?** - - - - -**Are there any edge cases?** - -**[For Kibana deprecations] Can the change be registered with the [Kibana deprecation service](https://github.com/elastic/kibana/blob/main/docs/development/core/server/kibana-plugin-core-server.deprecationsservicesetup.md)?** - - - - - -**[For Elasticsearch deprecations] Can the Upgrade Assistant make the migration easier for users? Please explain the proposed solution in as much detail as possible.** - - - - -## Test Data - - - -## Cross links - - \ No newline at end of file diff --git a/docs/api-generated/connectors/connector-apis-passthru.asciidoc b/docs/api-generated/connectors/connector-apis-passthru.asciidoc index 5bd4a4bc44f45..385807de6a7d4 100644 --- a/docs/api-generated/connectors/connector-apis-passthru.asciidoc +++ b/docs/api-generated/connectors/connector-apis-passthru.asciidoc @@ -46,7 +46,9 @@ Any modifications made to this file will be overwritten.
spaceId (required)
-
Path Parameter — An identifier for the space. If /s/ and the identifier are omitted from the path, the default space is used. default: null
+
Path Parameter — An identifier for the space. If /s/ and the identifier are omitted from the path, the default space is used. default: null
id (required)
+ +
Path Parameter — An UUID v1 or v4 identifier for the connector. If you omit this parameter, an identifier is randomly generated. default: null

Consumes

@@ -970,7 +972,8 @@ Any modifications made to this file will be overwritten.
  • create_connector_request_servicenow - Create ServiceNow ITSM connector request
  • create_connector_request_servicenow_itom - Create ServiceNow ITOM connector request
  • create_connector_request_servicenow_sir - Create ServiceNow SecOps connector request
  • -
  • create_connector_request_slack - Create Slack connector request
  • +
  • create_connector_request_slack_api - Create Slack connector request
  • +
  • create_connector_request_slack_webhook - Create Slack connector request
  • create_connector_request_swimlane - Create Swimlane connector request
  • create_connector_request_teams - Create Microsoft Teams connector request
  • create_connector_request_tines - Create Tines connector request
  • @@ -1016,9 +1019,8 @@ Any modifications made to this file will be overwritten.
  • secrets_properties_opsgenie - Connector secrets properties for an Opsgenie connector
  • secrets_properties_resilient - Connector secrets properties for IBM Resilient connector
  • secrets_properties_servicenow - Connector secrets properties for ServiceNow ITOM, ServiceNow ITSM, and ServiceNow SecOps connectors
  • -
  • secrets_properties_slack - Connector secrets properties for a Slack connector
  • -
  • secrets_properties_slack_oneOf -
  • -
  • secrets_properties_slack_oneOf_1 -
  • +
  • secrets_properties_slack_api - Connector secrets properties for a Web API Slack connector
  • +
  • secrets_properties_slack_webhook - Connector secrets properties for a Webhook Slack connector
  • secrets_properties_swimlane - Connector secrets properties for a Swimlane connector
  • updateConnector_400_response -
  • update_connector_request_cases_webhook - Update Webhook - Case Managment connector request
  • @@ -1029,7 +1031,8 @@ Any modifications made to this file will be overwritten.
  • update_connector_request_serverlog - Update server log connector request
  • update_connector_request_servicenow - Update ServiceNow ITSM connector or ServiceNow SecOps request
  • update_connector_request_servicenow_itom - Create ServiceNow ITOM connector request
  • -
  • update_connector_request_slack - Update Slack connector request
  • +
  • update_connector_request_slack_api - Update Slack connector request
  • +
  • update_connector_request_slack_webhook - Update Slack connector request
  • update_connector_request_swimlane - Update Swimlane connector request
  • @@ -1785,14 +1788,25 @@ Any modifications made to this file will be overwritten.
    -

    create_connector_request_slack - Create Slack connector request Up

    +

    create_connector_request_slack_api - Create Slack connector request Up

    +
    The Slack connector uses Slack Incoming Webhooks.
    +
    +
    connector_type_id
    String The type of connector.
    +
    Enum:
    +
    .slack_api
    +
    name
    String The display name for the connector.
    +
    secrets
    +
    +
    +
    +

    create_connector_request_slack_webhook - Create Slack connector request Up

    The Slack connector uses Slack Incoming Webhooks.
    connector_type_id
    String The type of connector.
    Enum:
    .slack
    name
    String The display name for the connector.
    -
    secrets
    +
    secrets
    @@ -2240,25 +2254,17 @@ Any modifications made to this file will be overwritten.
    -

    secrets_properties_slack - Connector secrets properties for a Slack connector Up

    +

    secrets_properties_slack_api - Connector secrets properties for a Web API Slack connector Up

    Defines secrets for connectors when type is .slack.
    -
    token
    String The Slack bot user OAuth token.
    -
    webhookUrl
    String The Slack webhook url.
    +
    token
    String Slack bot user OAuth token.
    -

    secrets_properties_slack_oneOf - Up

    -
    -
    -
    token
    String The Slack bot user OAuth token.
    -
    -
    -
    -

    secrets_properties_slack_oneOf_1 - Up

    -
    +

    secrets_properties_slack_webhook - Connector secrets properties for a Webhook Slack connector Up

    +
    Defines secrets for connectors when type is .slack.
    -
    webhookUrl
    String The Slack webhook url.
    +
    webhookUrl
    String Slack webhook url.
    @@ -2347,14 +2353,19 @@ Any modifications made to this file will be overwritten.
    -

    update_connector_request_slack - Update Slack connector request Up

    +

    update_connector_request_slack_api - Update Slack connector request Up

    name
    String The display name for the connector.
    -
    config (optional)
    -
    Enum:
    -
    web_api
    webhook
    -
    secrets
    +
    secrets
    +
    +
    +
    +

    update_connector_request_slack_webhook - Update Slack connector request Up

    +
    +
    +
    name
    String The display name for the connector.
    +
    secrets
    diff --git a/packages/kbn-doc-links/src/get_doc_links.ts b/packages/kbn-doc-links/src/get_doc_links.ts index 6ca707eec2998..7505ec280328a 100644 --- a/packages/kbn-doc-links/src/get_doc_links.ts +++ b/packages/kbn-doc-links/src/get_doc_links.ts @@ -740,5 +740,8 @@ export const getDocLinks = ({ kibanaBranch }: GetDocLinkOptions): DocLinks => { appSearch: `${SEARCH_UI_DOCS}tutorials/app-search`, elasticsearch: `${SEARCH_UI_DOCS}tutorials/elasticsearch`, }, + synthetics: { + featureRoles: `${ELASTIC_WEBSITE_URL}guide/en/observability/${DOC_LINK_VERSION}/synthetics-feature-roles.html`, + }, }); }; diff --git a/packages/kbn-doc-links/src/types.ts b/packages/kbn-doc-links/src/types.ts index 38f0829ac3516..a2bc513c5fb1e 100644 --- a/packages/kbn-doc-links/src/types.ts +++ b/packages/kbn-doc-links/src/types.ts @@ -509,4 +509,7 @@ export interface DocLinks { readonly appSearch: string; readonly elasticsearch: string; }; + readonly synthetics: { + readonly featureRoles: string; + }; } diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 895920d372ed4..aa57c432af51b 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -117,7 +117,7 @@ pageLoadAssetSize: securitySolution: 66738 serverless: 16573 serverlessObservability: 16582 - serverlessSearch: 20555 + serverlessSearch: 22555 serverlessSecurity: 41807 sessionView: 77750 share: 71239 diff --git a/packages/shared-ux/chrome/navigation/mocks/src/jest.ts b/packages/shared-ux/chrome/navigation/mocks/src/jest.ts index 1f8356195eadd..27c40f5b52efa 100644 --- a/packages/shared-ux/chrome/navigation/mocks/src/jest.ts +++ b/packages/shared-ux/chrome/navigation/mocks/src/jest.ts @@ -6,16 +6,19 @@ * Side Public License, v 1. */ +import { BehaviorSubject } from 'rxjs'; import { NavigationServices, ChromeNavigationNodeViewModel } from '../../types'; export const getServicesMock = (): NavigationServices => { const navigateToUrl = jest.fn().mockResolvedValue(undefined); const basePath = { prepend: jest.fn((path: string) => `/base${path}`) }; - const loadingCount = 0; + const loadingCount$ = new BehaviorSubject(0); + const recentlyAccessed$ = new BehaviorSubject([]); return { basePath, - loadingCount, + loadingCount$, + recentlyAccessed$, navIsOpen: true, navigateToUrl, }; diff --git a/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts b/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts index d269f5ca56ae5..889a374544429 100644 --- a/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts +++ b/packages/shared-ux/chrome/navigation/mocks/src/storybook.ts @@ -8,12 +8,19 @@ import { AbstractStorybookMock } from '@kbn/shared-ux-storybook-mock'; import { action } from '@storybook/addon-actions'; +import { BehaviorSubject } from 'rxjs'; import { ChromeNavigationViewModel, NavigationServices } from '../../types'; type Arguments = ChromeNavigationViewModel & NavigationServices; export type Params = Pick< Arguments, - 'activeNavItemId' | 'loadingCount' | 'navIsOpen' | 'platformConfig' | 'navigationTree' + | 'activeNavItemId' + | 'loadingCount$' + | 'navIsOpen' + | 'navigationTree' + | 'platformConfig' + | 'recentlyAccessed$' + | 'recentlyAccessedFilter' >; export class StorybookMock extends AbstractStorybookMock< @@ -27,17 +34,11 @@ export class StorybookMock extends AbstractStorybookMock< control: 'boolean', defaultValue: true, }, - loadingCount: { - control: 'number', - defaultValue: 0, - }, }; dependencies = []; getServices(params: Params): NavigationServices { - const { navIsOpen } = params; - const navAction = action('Navigate to'); const navigateToUrl = (url: string) => { navAction(url); @@ -48,7 +49,8 @@ export class StorybookMock extends AbstractStorybookMock< ...params, basePath: { prepend: (suffix: string) => `/basepath${suffix}` }, navigateToUrl, - navIsOpen, + loadingCount$: params.loadingCount$ ?? new BehaviorSubject(0), + recentlyAccessed$: params.recentlyAccessed$ ?? new BehaviorSubject([]), }; } @@ -57,6 +59,7 @@ export class StorybookMock extends AbstractStorybookMock< ...params, homeHref: '#', linkToCloud: 'projects', + recentlyAccessedFilter: params.recentlyAccessedFilter, }; } } diff --git a/packages/shared-ux/chrome/navigation/src/model/create_side_nav.ts b/packages/shared-ux/chrome/navigation/src/model/create_side_nav.ts index eafc3862a51c9..a4c26a675989f 100644 --- a/packages/shared-ux/chrome/navigation/src/model/create_side_nav.ts +++ b/packages/shared-ux/chrome/navigation/src/model/create_side_nav.ts @@ -12,8 +12,8 @@ import type { ChromeNavigationNodeViewModel, PlatformSectionConfig } from '../.. * Navigation node parser. It filers out the nodes disabled through config and * sets the `path` of each of the nodes. * - * @param items Navigation nodes - * @param platformConfig Configuration with flags to disable nodes in the navigation tree + * @param navItems Navigation nodes + * @param platformSectionConfig Configuration with flags to disable nodes in the navigation tree * * @returns The navigation tree filtered */ diff --git a/packages/shared-ux/chrome/navigation/src/model/index.ts b/packages/shared-ux/chrome/navigation/src/model/index.ts index 8e8d94e995019..db3e5a29951ac 100644 --- a/packages/shared-ux/chrome/navigation/src/model/index.ts +++ b/packages/shared-ux/chrome/navigation/src/model/index.ts @@ -21,7 +21,6 @@ export interface NavigationModelDeps { * @public */ export enum Platform { - Recents = 'recents', Analytics = 'analytics', MachineLearning = 'ml', DevTools = 'devTools', diff --git a/packages/shared-ux/chrome/navigation/src/services.tsx b/packages/shared-ux/chrome/navigation/src/services.tsx index 8235963c18681..0b6202591bb53 100644 --- a/packages/shared-ux/chrome/navigation/src/services.tsx +++ b/packages/shared-ux/chrome/navigation/src/services.tsx @@ -7,7 +7,6 @@ */ import React, { FC, useContext } from 'react'; -import useObservable from 'react-use/lib/useObservable'; import { NavigationKibanaDependencies, NavigationServices } from '../types'; const Context = React.createContext(null); @@ -27,15 +26,14 @@ export const NavigationKibanaProvider: FC = ({ ...dependencies }) => { const { core } = dependencies; - const { http } = core; + const { chrome, http } = core; const { basePath } = http; const { navigateToUrl } = core.application; - const loadingCount = useObservable(http.getLoadingCount$(), 0); - const value: NavigationServices = { basePath, - loadingCount, + loadingCount$: http.getLoadingCount$(), + recentlyAccessed$: chrome.recentlyAccessed.get$(), navigateToUrl, navIsOpen: true, }; diff --git a/packages/shared-ux/chrome/navigation/src/ui/i18n_strings.ts b/packages/shared-ux/chrome/navigation/src/ui/i18n_strings.ts index 9f6f3fbadca30..c268e7a42de10 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/i18n_strings.ts +++ b/packages/shared-ux/chrome/navigation/src/ui/i18n_strings.ts @@ -27,4 +27,10 @@ export const getI18nStrings = () => ({ defaultMessage: 'My deployments', } ), + recentlyAccessed: i18n.translate( + 'sharedUXPackages.chrome.sideNavigation.recentlyAccessed.title', + { + defaultMessage: 'Recent', + } + ), }); diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx index 9652d83597119..b666f4d409869 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx @@ -13,9 +13,10 @@ import { EuiPopover, EuiThemeProvider, } from '@elastic/eui'; +import { css } from '@emotion/react'; import { ComponentMeta, ComponentStory } from '@storybook/react'; import React, { useCallback, useState } from 'react'; -import { css } from '@emotion/react'; +import { BehaviorSubject } from 'rxjs'; import { getSolutionPropertiesMock, NavigationStorybookMock } from '../../mocks'; import mdx from '../../README.mdx'; import { ChromeNavigationViewModel, NavigationServices } from '../../types'; @@ -132,11 +133,25 @@ ReducedPlatformLinks.argTypes = storybookMock.getArgumentTypes(); export const WithRequestsLoading: ComponentStory = Template.bind({}); WithRequestsLoading.args = { activeNavItemId: 'example_project.root.get_started', - loadingCount: 1, + loadingCount$: new BehaviorSubject(1), navigationTree: [getSolutionPropertiesMock()], }; WithRequestsLoading.argTypes = storybookMock.getArgumentTypes(); +export const WithRecentlyAccessed: ComponentStory = Template.bind({}); +WithRecentlyAccessed.args = { + activeNavItemId: 'example_project.root.get_started', + loadingCount$: new BehaviorSubject(0), + recentlyAccessed$: new BehaviorSubject([ + { label: 'This is an example', link: '/app/example/39859', id: '39850' }, + { label: 'This is not an example', link: '/app/non-example/39458', id: '39458' }, // NOTE: this will be filtered out + ]), + recentlyAccessedFilter: (items) => + items.filter((item) => item.link.indexOf('/app/example') === 0), + navigationTree: [getSolutionPropertiesMock()], +}; +WithRecentlyAccessed.argTypes = storybookMock.getArgumentTypes(); + export const CustomElements: ComponentStory = Template.bind({}); CustomElements.args = { activeNavItemId: 'example_project.custom', diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation.test.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.test.tsx index 653b66887054b..4a39a4e651d7a 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/navigation.test.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation.test.tsx @@ -8,8 +8,9 @@ import { render } from '@testing-library/react'; import React from 'react'; +import { BehaviorSubject } from 'rxjs'; import { getServicesMock } from '../../mocks/src/jest'; -import { PlatformConfigSet, ChromeNavigationNodeViewModel } from '../../types'; +import { ChromeNavigationNodeViewModel, PlatformConfigSet } from '../../types'; import { Platform } from '../model'; import { NavigationProvider } from '../services'; import { Navigation } from './navigation'; @@ -27,7 +28,7 @@ describe('', () => { }); test('renders the header logo and top-level navigation buckets', async () => { - const { findByTestId, findByText } = render( + const { findByTestId, findByText, queryByTestId } = render( ', () => { expect(await findByTestId('nav-bucket-ml')).toBeVisible(); expect(await findByTestId('nav-bucket-devTools')).toBeVisible(); expect(await findByTestId('nav-bucket-management')).toBeVisible(); + + expect(queryByTestId('nav-bucket-recentlyAccessed')).not.toBeInTheDocument(); }); test('includes link to deployments', async () => { @@ -122,7 +125,7 @@ describe('', () => { }); test('shows loading state', async () => { - services.loadingCount = 5; + services.loadingCount$ = new BehaviorSubject(5); const { findByTestId } = render( @@ -136,4 +139,43 @@ describe('', () => { expect(await findByTestId('nav-header-loading-spinner')).toBeVisible(); }); + + describe('recent items', () => { + const recentlyAccessed = [ + { id: 'dashboard:234', label: 'Recently Accessed Test Item', link: '/app/dashboard/234' }, + ]; + + test('shows recent items', async () => { + services.recentlyAccessed$ = new BehaviorSubject(recentlyAccessed); + + const { findByTestId } = render( + + + + ); + + expect(await findByTestId('nav-bucket-recentlyAccessed')).toBeVisible(); + }); + + test('shows no recent items container when items are filtered', async () => { + services.recentlyAccessed$ = new BehaviorSubject(recentlyAccessed); + + const { queryByTestId } = render( + + []} + /> + + ); + + expect(queryByTestId('nav-bucket-recentlyAccessed')).not.toBeInTheDocument(); + }); + }); }); diff --git a/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx b/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx index ed58e9f57c4e5..776fe07297e64 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/navigation.tsx @@ -13,16 +13,20 @@ import { EuiHeaderLogo, EuiLink, EuiLoadingSpinner, + EuiSideNav, + EuiSideNavItemType, EuiSpacer, useEuiTheme, } from '@elastic/eui'; import React from 'react'; -import { getI18nStrings } from './i18n_strings'; +import useObservable from 'react-use/lib/useObservable'; import type { ChromeNavigationViewModel } from '../../types'; import { NavigationModel } from '../model'; import { useNavigation } from '../services'; +import { navigationStyles as styles } from '../styles'; import { ElasticMark } from './elastic_mark'; import './header_logo.scss'; +import { getI18nStrings } from './i18n_strings'; import { NavigationBucket, type Props as NavigationBucketProps } from './navigation_bucket'; interface Props extends ChromeNavigationViewModel { @@ -38,8 +42,9 @@ export const Navigation = ({ homeHref, linkToCloud, activeNavItemId: activeNavItemIdProps, + ...props }: Props) => { - const { loadingCount, activeNavItemId, basePath, navIsOpen, navigateToUrl } = useNavigation(); + const { activeNavItemId } = useNavigation(); const { euiTheme } = useEuiTheme(); const activeNav = activeNavItemId ?? activeNavItemIdProps; @@ -52,6 +57,8 @@ export const Navigation = ({ const strings = getI18nStrings(); const NavHeader = () => { + const { basePath, navIsOpen, navigateToUrl, loadingCount$ } = useNavigation(); + const loadingCount = useObservable(loadingCount$, 0); const homeUrl = basePath.prepend(homeHref); const navigateHome = (event: React.MouseEvent) => { event.preventDefault(); @@ -111,6 +118,45 @@ export const Navigation = ({ } }; + const RecentlyAccessed = () => { + const { recentlyAccessed$ } = useNavigation(); + const recentlyAccessed = useObservable(recentlyAccessed$, []); + + // consumer may filter objects from recent that are not applicable to the project + let filteredRecent = recentlyAccessed; + if (props.recentlyAccessedFilter) { + filteredRecent = props.recentlyAccessedFilter(recentlyAccessed); + } + + if (filteredRecent.length > 0) { + const navItems: Array> = [ + { + name: '', // no list header title + id: 'recents_root', + items: filteredRecent.map(({ id, label, link }) => ({ + id, + name: label, + href: link, + })), + }, + ]; + + return ( + + + + ); + } + + return null; + }; + // higher-order-component to keep the common props DRY const NavigationBucketHoc = (outerProps: Omit) => ( @@ -125,6 +171,8 @@ export const Navigation = ({ + + {solutions.map((navTree, idx) => { return ; })} diff --git a/packages/shared-ux/chrome/navigation/types/index.ts b/packages/shared-ux/chrome/navigation/types/index.ts index f58c9dc6836d1..a4ba5c42c39b8 100644 --- a/packages/shared-ux/chrome/navigation/types/index.ts +++ b/packages/shared-ux/chrome/navigation/types/index.ts @@ -17,7 +17,8 @@ import type { BasePathService, NavigateToUrlFn, RecentItem } from './internal'; export interface NavigationServices { activeNavItemId?: string; basePath: BasePathService; - loadingCount: number; + loadingCount$: Observable; + recentlyAccessed$: Observable; navIsOpen: boolean; navigateToUrl: NavigateToUrlFn; } @@ -111,6 +112,10 @@ export interface ChromeNavigation { * above. */ platformConfig?: Partial; + /** + * Filter function to allow consumer to remove items from the recently accessed section + */ + recentlyAccessedFilter?: (items: RecentItem[]) => RecentItem[]; } /** @@ -119,7 +124,7 @@ export interface ChromeNavigation { * @internal */ export interface ChromeNavigationViewModel - extends Pick { + extends Pick { /** * Target for the logo icon */ diff --git a/test/functional/apps/console/_context_menu.ts b/test/functional/apps/console/_context_menu.ts index 8114d4d05097e..1d7d4a07e3d4d 100644 --- a/test/functional/apps/console/_context_menu.ts +++ b/test/functional/apps/console/_context_menu.ts @@ -16,7 +16,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const browser = getService('browser'); const toasts = getService('toasts'); - describe('console context menu', function testContextMenu() { + // FLAKY: https://github.com/elastic/kibana/issues/155029 + describe.skip('console context menu', function testContextMenu() { before(async () => { await PageObjects.common.navigateToApp('console'); // Ensure that the text area can be interacted with diff --git a/test/interactive_setup_functional/tests/manual_configuration.ts b/test/interactive_setup_functional/tests/manual_configuration.ts index 3f41cf0659567..e1271ede84001 100644 --- a/test/interactive_setup_functional/tests/manual_configuration.ts +++ b/test/interactive_setup_functional/tests/manual_configuration.ts @@ -18,7 +18,8 @@ export default function ({ getService }: FtrProviderContext) { const retry = getService('retry'); const log = getService('log'); - describe('Interactive Setup Functional Tests (Manual configuration)', function () { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/157017 + describe.skip('Interactive Setup Functional Tests (Manual configuration)', function () { this.tags('skipCloud'); let verificationCode: string; diff --git a/test/interactive_setup_functional/tests/manual_configuration_without_tls.ts b/test/interactive_setup_functional/tests/manual_configuration_without_tls.ts index 23595150d55a1..bf83308677b40 100644 --- a/test/interactive_setup_functional/tests/manual_configuration_without_tls.ts +++ b/test/interactive_setup_functional/tests/manual_configuration_without_tls.ts @@ -18,7 +18,8 @@ export default function ({ getService }: FtrProviderContext) { const retry = getService('retry'); const log = getService('log'); - describe('Interactive Setup Functional Tests (Manual configuration without TLS)', function () { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/157018 + describe.skip('Interactive Setup Functional Tests (Manual configuration without TLS)', function () { this.tags('skipCloud'); let verificationCode: string; diff --git a/x-pack/examples/triggers_actions_ui_example/public/components/rules_list_notify_badge_sandbox.tsx b/x-pack/examples/triggers_actions_ui_example/public/components/rules_list_notify_badge_sandbox.tsx index 6ff26f7725f64..de09c88742a74 100644 --- a/x-pack/examples/triggers_actions_ui_example/public/components/rules_list_notify_badge_sandbox.tsx +++ b/x-pack/examples/triggers_actions_ui_example/public/components/rules_list_notify_badge_sandbox.tsx @@ -6,46 +6,15 @@ */ import React from 'react'; -import { - TriggersAndActionsUIPublicPluginStart, - RuleTableItem, -} from '@kbn/triggers-actions-ui-plugin/public'; +import type { RuleSnoozeSettings } from '@kbn/triggers-actions-ui-plugin/public/types'; +import { TriggersAndActionsUIPublicPluginStart } from '@kbn/triggers-actions-ui-plugin/public'; interface SandboxProps { triggersActionsUi: TriggersAndActionsUIPublicPluginStart; } -const mockRule: RuleTableItem = { - id: '1', - enabled: true, - name: 'test rule', - tags: ['tag1'], - ruleTypeId: 'test_rule_type', - consumer: 'rules', - schedule: { interval: '5d' }, - actions: [ - { id: 'test', actionTypeId: 'the_connector', group: 'rule', params: { message: 'test' } }, - ], - params: { name: 'test rule type name' }, - createdBy: null, - updatedBy: null, - createdAt: new Date(), - updatedAt: new Date(), - apiKeyOwner: null, - throttle: '1m', - notifyWhen: 'onActiveAlert', +const mockSnoozeSettings: RuleSnoozeSettings = { muteAll: true, - mutedInstanceIds: [], - executionStatus: { - status: 'active', - lastExecutionDate: new Date('2020-08-20T19:23:38Z'), - }, - actionsCount: 1, - index: 0, - ruleType: 'Test Rule Type', - isEditable: true, - enabledInLicense: true, - revision: 0, }; export const RulesListNotifyBadgeSandbox = ({ triggersActionsUi }: SandboxProps) => { @@ -53,8 +22,8 @@ export const RulesListNotifyBadgeSandbox = ({ triggersActionsUi }: SandboxProps) return (
    Promise.resolve()} />
    diff --git a/x-pack/plugins/actions/docs/openapi/bundled.json b/x-pack/plugins/actions/docs/openapi/bundled.json index 524b09c965130..53f48394d26bd 100644 --- a/x-pack/plugins/actions/docs/openapi/bundled.json +++ b/x-pack/plugins/actions/docs/openapi/bundled.json @@ -39,6 +39,16 @@ }, { "$ref": "#/components/parameters/space_id" + }, + { + "in": "path", + "name": "id", + "description": "An UUID v1 or v4 identifier for the connector. If you omit this parameter, an identifier is randomly generated.\n", + "required": true, + "schema": { + "type": "string", + "example": "ac4e6b90-6be7-11eb-ba0d-9b1c1f912d74" + } } ], "requestBody": { @@ -83,7 +93,10 @@ "$ref": "#/components/schemas/create_connector_request_servicenow_sir" }, { - "$ref": "#/components/schemas/create_connector_request_slack" + "$ref": "#/components/schemas/create_connector_request_slack_api" + }, + { + "$ref": "#/components/schemas/create_connector_request_slack_webhook" }, { "$ref": "#/components/schemas/create_connector_request_swimlane" @@ -318,7 +331,10 @@ "$ref": "#/components/schemas/update_connector_request_servicenow_itom" }, { - "$ref": "#/components/schemas/update_connector_request_slack" + "$ref": "#/components/schemas/update_connector_request_slack_api" + }, + { + "$ref": "#/components/schemas/update_connector_request_slack_webhook" }, { "$ref": "#/components/schemas/update_connector_request_swimlane" @@ -1948,37 +1964,63 @@ } } }, - "secrets_properties_slack": { - "title": "Connector secrets properties for a Slack connector", + "secrets_properties_slack_api": { + "title": "Connector secrets properties for a Web API Slack connector", "description": "Defines secrets for connectors when type is `.slack`.", - "oneOf": [ - { - "type": "object", - "required": [ - "token" + "required": [ + "token" + ], + "type": "object", + "properties": { + "token": { + "type": "string", + "description": "Slack bot user OAuth token." + } + } + }, + "create_connector_request_slack_api": { + "title": "Create Slack connector request", + "description": "The Slack connector uses Slack Incoming Webhooks.", + "type": "object", + "required": [ + "connector_type_id", + "name", + "secrets" + ], + "properties": { + "connector_type_id": { + "type": "string", + "description": "The type of connector.", + "enum": [ + ".slack_api" ], - "properties": { - "token": { - "type": "string", - "description": "The Slack bot user OAuth token." - } - } + "example": ".slack_api" }, - { - "type": "object", - "required": [ - "webhookUrl" - ], - "properties": { - "webhookUrl": { - "type": "string", - "description": "The Slack webhook url." - } - } + "name": { + "type": "string", + "description": "The display name for the connector.", + "example": "my-connector" + }, + "secrets": { + "$ref": "#/components/schemas/secrets_properties_slack_api" } - ] + } }, - "create_connector_request_slack": { + "secrets_properties_slack_webhook": { + "title": "Connector secrets properties for a Webhook Slack connector", + "description": "Defines secrets for connectors when type is `.slack`.", + "required": [ + "webhookUrl" + ], + "type": "object", + "properties": { + "webhookUrl": { + "type": "string", + "description": "Slack webhook url." + } + } + }, + "create_connector_request_slack_webhook": { "title": "Create Slack connector request", "description": "The Slack connector uses Slack Incoming Webhooks.", "type": "object", @@ -2002,7 +2044,7 @@ "example": "my-connector" }, "secrets": { - "$ref": "#/components/schemas/secrets_properties_slack" + "$ref": "#/components/schemas/secrets_properties_slack_webhook" } } }, @@ -2921,7 +2963,44 @@ } } }, - "connector_response_properties_slack": { + "connector_response_properties_slack_api": { + "title": "Connector response properties for a Slack connector", + "type": "object", + "required": [ + "connector_type_id", + "id", + "is_deprecated", + "is_preconfigured", + "name" + ], + "properties": { + "connector_type_id": { + "type": "string", + "description": "The type of connector.", + "enum": [ + ".slack_api" + ] + }, + "id": { + "type": "string", + "description": "The identifier for the connector." + }, + "is_deprecated": { + "$ref": "#/components/schemas/is_deprecated" + }, + "is_missing_secrets": { + "$ref": "#/components/schemas/is_missing_secrets" + }, + "is_preconfigured": { + "$ref": "#/components/schemas/is_preconfigured" + }, + "name": { + "type": "string", + "description": "The display name for the connector." + } + } + }, + "connector_response_properties_slack_webhook": { "title": "Connector response properties for a Slack connector", "type": "object", "required": [ @@ -3197,7 +3276,10 @@ "$ref": "#/components/schemas/connector_response_properties_servicenow_sir" }, { - "$ref": "#/components/schemas/connector_response_properties_slack" + "$ref": "#/components/schemas/connector_response_properties_slack_api" + }, + { + "$ref": "#/components/schemas/connector_response_properties_slack_webhook" }, { "$ref": "#/components/schemas/connector_response_properties_swimlane" @@ -3375,7 +3457,7 @@ } } }, - "update_connector_request_slack": { + "update_connector_request_slack_api": { "title": "Update Slack connector request", "type": "object", "required": [ @@ -3387,15 +3469,29 @@ "type": "string", "description": "The display name for the connector." }, - "config": { + "secrets": { + "type": "object", + "description": "The secrets object containing the necessary fields for authentication.", + "$ref": "#/components/schemas/secrets_properties_slack_api" + } + } + }, + "update_connector_request_slack_webhook": { + "title": "Update Slack connector request", + "type": "object", + "required": [ + "name", + "secrets" + ], + "properties": { + "name": { "type": "string", - "enum": [ - "web_api", - "webhook" - ] + "description": "The display name for the connector." }, "secrets": { - "$ref": "#/components/schemas/secrets_properties_slack" + "type": "object", + "description": "The secrets object containing the necessary fields for authentication.", + "$ref": "#/components/schemas/secrets_properties_slack_webhook" } } }, diff --git a/x-pack/plugins/actions/docs/openapi/bundled.yaml b/x-pack/plugins/actions/docs/openapi/bundled.yaml index 2d02e0dd2b046..8cd264536b8e7 100644 --- a/x-pack/plugins/actions/docs/openapi/bundled.yaml +++ b/x-pack/plugins/actions/docs/openapi/bundled.yaml @@ -26,6 +26,14 @@ paths: parameters: - $ref: '#/components/parameters/kbn_xsrf' - $ref: '#/components/parameters/space_id' + - in: path + name: id + description: | + An UUID v1 or v4 identifier for the connector. If you omit this parameter, an identifier is randomly generated. + required: true + schema: + type: string + example: ac4e6b90-6be7-11eb-ba0d-9b1c1f912d74 requestBody: required: true content: @@ -45,7 +53,8 @@ paths: - $ref: '#/components/schemas/create_connector_request_servicenow' - $ref: '#/components/schemas/create_connector_request_servicenow_itom' - $ref: '#/components/schemas/create_connector_request_servicenow_sir' - - $ref: '#/components/schemas/create_connector_request_slack' + - $ref: '#/components/schemas/create_connector_request_slack_api' + - $ref: '#/components/schemas/create_connector_request_slack_webhook' - $ref: '#/components/schemas/create_connector_request_swimlane' - $ref: '#/components/schemas/create_connector_request_teams' - $ref: '#/components/schemas/create_connector_request_tines' @@ -174,7 +183,8 @@ paths: - $ref: '#/components/schemas/update_connector_request_serverlog' - $ref: '#/components/schemas/update_connector_request_servicenow' - $ref: '#/components/schemas/update_connector_request_servicenow_itom' - - $ref: '#/components/schemas/update_connector_request_slack' + - $ref: '#/components/schemas/update_connector_request_slack_api' + - $ref: '#/components/schemas/update_connector_request_slack_webhook' - $ref: '#/components/schemas/update_connector_request_swimlane' examples: updateIndexConnectorRequest: @@ -1298,25 +1308,48 @@ components: example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_servicenow' - secrets_properties_slack: - title: Connector secrets properties for a Slack connector + secrets_properties_slack_api: + title: Connector secrets properties for a Web API Slack connector description: Defines secrets for connectors when type is `.slack`. - oneOf: - - type: object - required: - - token - properties: - token: - type: string - description: The Slack bot user OAuth token. - - type: object - required: - - webhookUrl - properties: - webhookUrl: - type: string - description: The Slack webhook url. - create_connector_request_slack: + required: + - token + type: object + properties: + token: + type: string + description: Slack bot user OAuth token. + create_connector_request_slack_api: + title: Create Slack connector request + description: The Slack connector uses Slack Incoming Webhooks. + type: object + required: + - connector_type_id + - name + - secrets + properties: + connector_type_id: + type: string + description: The type of connector. + enum: + - .slack_api + example: .slack_api + name: + type: string + description: The display name for the connector. + example: my-connector + secrets: + $ref: '#/components/schemas/secrets_properties_slack_api' + secrets_properties_slack_webhook: + title: Connector secrets properties for a Webhook Slack connector + description: Defines secrets for connectors when type is `.slack`. + required: + - webhookUrl + type: object + properties: + webhookUrl: + type: string + description: Slack webhook url. + create_connector_request_slack_webhook: title: Create Slack connector request description: The Slack connector uses Slack Incoming Webhooks. type: object @@ -1336,7 +1369,7 @@ components: description: The display name for the connector. example: my-connector secrets: - $ref: '#/components/schemas/secrets_properties_slack' + $ref: '#/components/schemas/secrets_properties_slack_webhook' config_properties_swimlane: title: Connector request properties for a Swimlane connector required: @@ -2024,7 +2057,7 @@ components: name: type: string description: The display name for the connector. - connector_response_properties_slack_webhook: + connector_response_properties_slack_api: title: Connector response properties for a Slack connector type: object required: @@ -2038,7 +2071,7 @@ components: type: string description: The type of connector. enum: - - .slack + - .slack_api id: type: string description: The identifier for the connector. @@ -2051,7 +2084,7 @@ components: name: type: string description: The display name for the connector. - connector_response_properties_slack_api: + connector_response_properties_slack_webhook: title: Connector response properties for a Slack connector type: object required: @@ -2065,7 +2098,7 @@ components: type: string description: The type of connector. enum: - - .slack_api + - .slack id: type: string description: The identifier for the connector. @@ -2240,7 +2273,8 @@ components: - $ref: '#/components/schemas/connector_response_properties_servicenow' - $ref: '#/components/schemas/connector_response_properties_servicenow_itom' - $ref: '#/components/schemas/connector_response_properties_servicenow_sir' - - $ref: '#/components/schemas/connector_response_properties_slack' + - $ref: '#/components/schemas/connector_response_properties_slack_api' + - $ref: '#/components/schemas/connector_response_properties_slack_webhook' - $ref: '#/components/schemas/connector_response_properties_swimlane' - $ref: '#/components/schemas/connector_response_properties_teams' - $ref: '#/components/schemas/connector_response_properties_tines' @@ -2359,7 +2393,7 @@ components: description: The display name for the connector. secrets: $ref: '#/components/schemas/secrets_properties_servicenow' - update_connector_request_slack: + update_connector_request_slack_api: title: Update Slack connector request type: object required: @@ -2369,13 +2403,24 @@ components: name: type: string description: The display name for the connector. - config: + secrets: + type: object + description: The secrets object containing the necessary fields for authentication. + $ref: '#/components/schemas/secrets_properties_slack_api' + update_connector_request_slack_webhook: + title: Update Slack connector request + type: object + required: + - name + - secrets + properties: + name: type: string - enum: - - web_api - - webhook + description: The display name for the connector. secrets: - $ref: '#/components/schemas/secrets_properties_slack' + type: object + description: The secrets object containing the necessary fields for authentication. + $ref: '#/components/schemas/secrets_properties_slack_webhook' update_connector_request_swimlane: title: Update Swimlane connector request type: object diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/connector_response_properties.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/connector_response_properties.yaml index b73584568df6b..ef72d88e31480 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/connector_response_properties.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/connector_response_properties.yaml @@ -12,7 +12,8 @@ oneOf: - $ref: 'connector_response_properties_servicenow.yaml' - $ref: 'connector_response_properties_servicenow_itom.yaml' - $ref: 'connector_response_properties_servicenow_sir.yaml' - - $ref: 'connector_response_properties_slack.yaml' + - $ref: 'connector_response_properties_slack_api.yaml' + - $ref: 'connector_response_properties_slack_webhook.yaml' - $ref: 'connector_response_properties_swimlane.yaml' - $ref: 'connector_response_properties_teams.yaml' - $ref: 'connector_response_properties_tines.yaml' diff --git a/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@connector.yaml b/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@connector.yaml index b3d42c3f47484..24fddf1a8b972 100644 --- a/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@connector.yaml +++ b/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@connector.yaml @@ -36,7 +36,8 @@ post: - $ref: '../components/schemas/create_connector_request_servicenow.yaml' - $ref: '../components/schemas/create_connector_request_servicenow_itom.yaml' - $ref: '../components/schemas/create_connector_request_servicenow_sir.yaml' - - $ref: '../components/schemas/create_connector_request_slack.yaml' + - $ref: '../components/schemas/create_connector_request_slack_api.yaml' + - $ref: '../components/schemas/create_connector_request_slack_webhook.yaml' - $ref: '../components/schemas/create_connector_request_swimlane.yaml' - $ref: '../components/schemas/create_connector_request_teams.yaml' - $ref: '../components/schemas/create_connector_request_tines.yaml' diff --git a/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@connector@{connectorid}.yaml b/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@connector@{connectorid}.yaml index 3cee979ed3e91..a417a457a9448 100644 --- a/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@connector@{connectorid}.yaml +++ b/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@connector@{connectorid}.yaml @@ -104,7 +104,8 @@ put: - $ref: '../components/schemas/update_connector_request_serverlog.yaml' - $ref: '../components/schemas/update_connector_request_servicenow.yaml' - $ref: '../components/schemas/update_connector_request_servicenow_itom.yaml' - - $ref: '../components/schemas/update_connector_request_slack.yaml' + - $ref: '../components/schemas/update_connector_request_slack_api.yaml' + - $ref: '../components/schemas/update_connector_request_slack_webhook.yaml' - $ref: '../components/schemas/update_connector_request_swimlane.yaml' # - $ref: '../components/schemas/update_connector_request_teams.yaml' # - $ref: '../components/schemas/update_connector_request_tines.yaml' diff --git a/x-pack/plugins/cloud_security_posture/public/components/no_findings_states.tsx b/x-pack/plugins/cloud_security_posture/public/components/no_findings_states.tsx index 897de7df552b9..773956b73c7a8 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/no_findings_states.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/no_findings_states.tsx @@ -125,7 +125,7 @@ const IndexTimeout = () => (

    diff --git a/x-pack/plugins/cloud_security_posture/public/components/no_vulnerabilities_states.tsx b/x-pack/plugins/cloud_security_posture/public/components/no_vulnerabilities_states.tsx index 705a0231fce10..cb11c456907cb 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/no_vulnerabilities_states.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/no_vulnerabilities_states.tsx @@ -11,7 +11,6 @@ import { EuiEmptyPrompt, EuiIcon, EuiMarkdownFormat, - EuiLink, EuiButton, EuiButtonEmpty, EuiFlexGroup, @@ -111,40 +110,6 @@ const VulnerabilitiesFindingsInstalledEmptyPrompt = ({ ); }; -const IndexTimeout = () => ( - } - title={ -

    - -

    - } - body={ -

    - - - - ), - }} - /> -

    - } - /> -); - const Unprivileged = ({ unprivilegedIndices }: { unprivilegedIndices: string[] }) => ( { .sort((a, b) => a.localeCompare(b)); const render = () => { - if (status === 'indexing' || status === 'waiting_for_results') - return ; // integration installed, but no agents added - if (status === 'index-timeout') return ; // agent added, index timeout has passed + if (status === 'indexing' || status === 'waiting_for_results' || status === 'index-timeout') + return ; // integration installed, but no agents added// agent added, index timeout has passed if (status === 'not-deployed' || status === 'not-installed') return ( ({ query, @@ -112,6 +113,8 @@ const VulnerabilitiesContent = ({ dataView }: { dataView: DataView }) => { enabled: !queryError, }); + const slicedPage = usePageSlice(data?.page, pageIndex, pageSize); + const invalidIndex = -1; const selectedVulnerability = data?.page[urlQuery.vulnerabilityIndex]; @@ -122,12 +125,21 @@ const VulnerabilitiesContent = ({ dataView }: { dataView: DataView }) => { }; const onOpenFlyout = useCallback( - (rowIndex: number) => { + (vulnerabilityRow: VulnerabilityRecord) => { + const vulnerabilityIndex = slicedPage.findIndex( + (vulnerabilityRecord: VulnerabilityRecord) => + vulnerabilityRecord.vulnerability?.id === vulnerabilityRow.vulnerability?.id && + vulnerabilityRecord.resource?.id === vulnerabilityRow.resource?.id && + vulnerabilityRecord.vulnerability.package.name === + vulnerabilityRow.vulnerability.package.name && + vulnerabilityRecord.vulnerability.package.version === + vulnerabilityRow.vulnerability.package.version + ); setUrlQuery({ - vulnerabilityIndex: rowIndex, + vulnerabilityIndex, }); }, - [setUrlQuery] + [setUrlQuery, slicedPage] ); const { isLastLimitedPage, limitedTotalItemCount } = useLimitProperties({ @@ -232,7 +244,7 @@ const VulnerabilitiesContent = ({ dataView }: { dataView: DataView }) => { iconType="expand" aria-label="View" onClick={() => { - onOpenFlyout(rowIndex); + onOpenFlyout(vulnerabilityRow); }} /> ); @@ -299,7 +311,6 @@ const VulnerabilitiesContent = ({ dataView }: { dataView: DataView }) => { ); const flyoutVulnerabilityIndex = urlQuery?.vulnerabilityIndex; - const error = queryError || null; if (error) { @@ -409,9 +420,9 @@ const VulnerabilitiesContent = ({ dataView }: { dataView: DataView }) => { {isLastLimitedPage && } {showVulnerabilityFlyout && ( diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities_finding_flyout/vulnerability_overview_tab.tsx b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities_finding_flyout/vulnerability_overview_tab.tsx index 033732cc4f64d..ba776c5f94ac2 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities_finding_flyout/vulnerability_overview_tab.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilities_finding_flyout/vulnerability_overview_tab.tsx @@ -59,7 +59,9 @@ const CVSScore = ({ vectorBaseScore, vendor }: CVSScoreProps) => { {vectorScores.length > 0 && - vectorScores.map((vectorScore) => )} + vectorScores.map((vectorScore, i) => ( + + ))} ); @@ -182,7 +184,7 @@ export const VulnerabilityOverviewTab = ({ vulnerability }: VulnerabilityTabProp ? Object.entries(vulnerability.cvss).map( ([vendor, vectorScoreBase]: [string, VectorScoreBase]) => { return ( - + ); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilties.test.tsx b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilties.test.tsx index 1b2a88446b8f9..1db9698c5dc1c 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilties.test.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilties.test.tsx @@ -133,10 +133,10 @@ describe('', () => { renderVulnerabilitiesPage(); expectIdsInDoc({ - be: [NO_VULNERABILITIES_STATUS_TEST_SUBJ.INDEX_TIMEOUT], + be: [NO_VULNERABILITIES_STATUS_TEST_SUBJ.SCANNING_VULNERABILITIES], notToBe: [ VULNERABILITIES_CONTAINER_TEST_SUBJ, - NO_VULNERABILITIES_STATUS_TEST_SUBJ.SCANNING_VULNERABILITIES, + NO_VULNERABILITIES_STATUS_TEST_SUBJ.INDEX_TIMEOUT, NO_VULNERABILITIES_STATUS_TEST_SUBJ.UNPRIVILEGED, ], }); diff --git a/x-pack/plugins/cloud_security_posture/server/routes/status/status.test.ts b/x-pack/plugins/cloud_security_posture/server/routes/status/status.test.ts index 00835a9520c60..ee7674bd7822b 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/status/status.test.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/status/status.test.ts @@ -248,6 +248,22 @@ describe('calculateIntegrationStatus for vul_mgmt', () => { [VULN_MGMT_POLICY_TEMPLATE] ); + expect(statusCode).toMatch('waiting_for_results'); + }); + + it('Verify status when there are installed policies, healthy agents and no vul_mgmt findings and been more than 1 hour', async () => { + const statusCode = calculateIntegrationStatus( + VULN_MGMT_POLICY_TEMPLATE, + { + latest: 'empty', + stream: 'empty', + score: 'empty', + }, + 1, + 61, + [VULN_MGMT_POLICY_TEMPLATE] + ); + expect(statusCode).toMatch('index-timeout'); }); diff --git a/x-pack/plugins/cloud_security_posture/server/routes/status/status.ts b/x-pack/plugins/cloud_security_posture/server/routes/status/status.ts index 69ae5eae2a414..4d394ced13eff 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/status/status.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/status/status.ts @@ -45,6 +45,7 @@ import { import { checkIndexStatus } from '../../lib/check_index_status'; export const INDEX_TIMEOUT_IN_MINUTES = 10; +export const INDEX_TIMEOUT_IN_MINUTES_CNVM = 60; interface CspStatusDependencies { logger: Logger; @@ -100,6 +101,7 @@ export const calculateIntegrationStatus = ( ): CspStatusCode => { // We check privileges only for the relevant indices for our pages to appear const postureTypeCheck: PostureTypes = POSTURE_TYPES[integration]; + if (indicesStatus.latest === 'unprivileged' || indicesStatus.score === 'unprivileged') return 'unprivileged'; if (indicesStatus.latest === 'not-empty') return 'indexed'; @@ -110,7 +112,10 @@ export const calculateIntegrationStatus = ( if ( indicesStatus.latest === 'empty' && indicesStatus.stream === 'empty' && - timeSinceInstallationInMinutes < INDEX_TIMEOUT_IN_MINUTES + timeSinceInstallationInMinutes < + (postureTypeCheck !== VULN_MGMT_POLICY_TEMPLATE + ? INDEX_TIMEOUT_IN_MINUTES + : INDEX_TIMEOUT_IN_MINUTES_CNVM) ) return 'waiting_for_results'; diff --git a/x-pack/plugins/osquery/common/translations/errors.ts b/x-pack/plugins/osquery/common/translations/errors.ts new file mode 100644 index 0000000000000..ac67ab46032b1 --- /dev/null +++ b/x-pack/plugins/osquery/common/translations/errors.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const LICENSE_TOO_LOW = i18n.translate( + 'xpack.osquery.liveQueryActions.error.licenseTooLow', + { + defaultMessage: 'At least Platinum license is required to use Response Actions.', + } +); + +export const PARAMETER_NOT_FOUND = i18n.translate( + 'xpack.osquery.liveQueryActions.error.notFoundParameters', + { + defaultMessage: + "This query hasn't been called due to parameter used and its value not found in the alert.", + } +); diff --git a/x-pack/plugins/osquery/public/action_results/action_results_summary.tsx b/x-pack/plugins/osquery/public/action_results/action_results_summary.tsx index d8299dd672d35..9bdd9bc5a4528 100644 --- a/x-pack/plugins/osquery/public/action_results/action_results_summary.tsx +++ b/x-pack/plugins/osquery/public/action_results/action_results_summary.tsx @@ -59,12 +59,7 @@ const ActionResultsSummaryComponent: React.FC = ({ if (error) { edges.forEach((edge) => { if (edge.fields) { - edge.fields['error.skipped'] = edge.fields.error = [ - i18n.translate('xpack.osquery.liveQueryActionResults.table.skippedErrorText', { - defaultMessage: - "This query hasn't been called due to parameter used and its value not found in the alert.", - }), - ]; + edge.fields['error.skipped'] = edge.fields.error = [error]; } }); } else if (expired) { diff --git a/x-pack/plugins/osquery/server/handlers/action/create_action_handler.ts b/x-pack/plugins/osquery/server/handlers/action/create_action_handler.ts index 3c776723a2da2..169ac4291a9df 100644 --- a/x-pack/plugins/osquery/server/handlers/action/create_action_handler.ts +++ b/x-pack/plugins/osquery/server/handlers/action/create_action_handler.ts @@ -31,6 +31,7 @@ interface CreateActionHandlerOptions { soClient?: SavedObjectsClientContract; metadata?: Metadata; alertData?: ParsedTechnicalFields; + error?: string; } export const createActionHandler = async ( @@ -43,7 +44,7 @@ export const createActionHandler = async ( const internalSavedObjectsClient = await getInternalSavedObjectsClient( osqueryContext.getStartServices ); - const { soClient, metadata, alertData } = options; + const { soClient, metadata, alertData, error } = options; const savedObjectsClient = soClient ?? coreStartServices.savedObjects.createInternalRepository(); // eslint-disable-next-line @typescript-eslint/naming-convention @@ -98,6 +99,7 @@ export const createActionHandler = async ( action_id: uuidv4(), id: packQueryId, ...replacedQuery, + ...(error ? { error } : {}), ecs_mapping: packQuery.ecs_mapping, version: packQuery.version, platform: packQuery.platform, @@ -106,22 +108,31 @@ export const createActionHandler = async ( (value) => !isEmpty(value) ); }) - : await createDynamicQueries({ params, alertData, agents: selectedAgents, osqueryContext }), + : await createDynamicQueries({ + params, + alertData, + agents: selectedAgents, + osqueryContext, + error, + }), }; - const fleetActions = map( - filter(osqueryAction.queries, (query) => !query.error), - (query) => ({ - action_id: query.action_id, - '@timestamp': moment().toISOString(), - expiration: moment().add(5, 'minutes').toISOString(), - type: 'INPUT_ACTION', - input_type: 'osquery', - agents: query.agents, - user_id: metadata?.currentUser, - data: pick(query, ['id', 'query', 'ecs_mapping', 'version', 'platform']), - }) - ); + const fleetActions = !error + ? map( + filter(osqueryAction.queries, (query) => !query.error), + (query) => ({ + action_id: query.action_id, + '@timestamp': moment().toISOString(), + expiration: moment().add(5, 'minutes').toISOString(), + type: 'INPUT_ACTION', + input_type: 'osquery', + agents: query.agents, + user_id: metadata?.currentUser, + data: pick(query, ['id', 'query', 'ecs_mapping', 'version', 'platform']), + }) + ) + : []; + if (fleetActions.length) { await esClientInternal.bulk({ refresh: 'wait_for', @@ -129,24 +140,24 @@ export const createActionHandler = async ( fleetActions.map((action) => [{ index: { _index: AGENT_ACTIONS_INDEX } }, action]) ), }); + } - const actionsComponentTemplateExists = await esClientInternal.indices.exists({ - index: `${ACTIONS_INDEX}*`, - }); - - if (actionsComponentTemplateExists) { - await esClientInternal.bulk({ - refresh: 'wait_for', - body: [{ index: { _index: `${ACTIONS_INDEX}-default` } }, osqueryAction], - }); - } + const actionsComponentTemplateExists = await esClientInternal.indices.exists({ + index: `${ACTIONS_INDEX}*`, + }); - osqueryContext.telemetryEventsSender.reportEvent(TELEMETRY_EBT_LIVE_QUERY_EVENT, { - ...omit(osqueryAction, ['type', 'input_type', 'user_id']), - agents: osqueryAction.agents.length, + if (actionsComponentTemplateExists) { + await esClientInternal.bulk({ + refresh: 'wait_for', + body: [{ index: { _index: `${ACTIONS_INDEX}-default` } }, osqueryAction], }); } + osqueryContext.telemetryEventsSender.reportEvent(TELEMETRY_EBT_LIVE_QUERY_EVENT, { + ...omit(osqueryAction, ['type', 'input_type', 'user_id', 'error']), + agents: osqueryAction.agents.length, + }); + return { response: osqueryAction, fleetActionsCount: fleetActions.length, diff --git a/x-pack/plugins/osquery/server/handlers/action/create_action_service.ts b/x-pack/plugins/osquery/server/handlers/action/create_action_service.ts new file mode 100644 index 0000000000000..e0e8098fd16bd --- /dev/null +++ b/x-pack/plugins/osquery/server/handlers/action/create_action_service.ts @@ -0,0 +1,43 @@ +/* + * 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 { ParsedTechnicalFields } from '@kbn/rule-registry-plugin/common'; +import type { Subscription } from 'rxjs'; +import type { OsqueryAppContext } from '../../lib/osquery_app_context_services'; +import type { CreateLiveQueryRequestBodySchema } from '../../../common/schemas/routes/live_query'; +import type { OsqueryActiveLicenses } from './validate_license'; +import { validateLicense } from './validate_license'; +import { createActionHandler } from './create_action_handler'; + +export const createActionService = (osqueryContext: OsqueryAppContext) => { + let licenseSubscription: Subscription | null = null; + const licenses: OsqueryActiveLicenses = { isActivePlatinumLicense: false }; + + licenseSubscription = osqueryContext.licensing.license$.subscribe((license) => { + licenses.isActivePlatinumLicense = license.isActive && license.hasAtLeast('platinum'); + }); + + const create = async ( + params: CreateLiveQueryRequestBodySchema, + alertData?: ParsedTechnicalFields + ) => { + const error = validateLicense(licenses); + + return createActionHandler(osqueryContext, params, { alertData, error }); + }; + + const stop = () => { + if (licenseSubscription) { + licenseSubscription.unsubscribe(); + } + }; + + return { + create, + stop, + }; +}; diff --git a/x-pack/plugins/osquery/server/handlers/action/create_queries.test.ts b/x-pack/plugins/osquery/server/handlers/action/create_queries.test.ts index 4956dfd1245bc..29386c3db90db 100644 --- a/x-pack/plugins/osquery/server/handlers/action/create_queries.test.ts +++ b/x-pack/plugins/osquery/server/handlers/action/create_queries.test.ts @@ -5,9 +5,10 @@ * 2.0. */ -import { createDynamicQueries, PARAMETER_NOT_FOUND } from './create_queries'; +import { createDynamicQueries } from './create_queries'; import type { ParsedTechnicalFields } from '@kbn/rule-registry-plugin/common'; import type { OsqueryAppContext } from '../../lib/osquery_app_context_services'; +import { PARAMETER_NOT_FOUND } from '../../../common/translations/errors'; describe('create queries', () => { const defualtQueryParams = { @@ -65,9 +66,7 @@ describe('create queries', () => { expect(queries[0].query).toBe(`SELECT * FROM processes where pid=${pid};`); expect(queries[0].error).toBe(undefined); expect(queries[1].query).toBe('SELECT * FROM processes where pid={{process.not-existing}};'); - expect(queries[1].error).toBe( - "This query hasn't been called due to parameter used and its value not found in the alert." - ); + expect(queries[1].error).toBe(PARAMETER_NOT_FOUND); expect(queries[2].query).toBe('SELECT * FROM processes;'); expect(queries[2].error).toBe(undefined); }); diff --git a/x-pack/plugins/osquery/server/handlers/action/create_queries.ts b/x-pack/plugins/osquery/server/handlers/action/create_queries.ts index f7d3601722189..4185353fdacff 100644 --- a/x-pack/plugins/osquery/server/handlers/action/create_queries.ts +++ b/x-pack/plugins/osquery/server/handlers/action/create_queries.ts @@ -7,33 +7,28 @@ import { isEmpty, map, pickBy } from 'lodash'; import { v4 as uuidv4 } from 'uuid'; -import { i18n } from '@kbn/i18n'; import type { ParsedTechnicalFields } from '@kbn/rule-registry-plugin/common'; +import { PARAMETER_NOT_FOUND } from '../../../common/translations/errors'; import type { OsqueryAppContext } from '../../lib/osquery_app_context_services'; import type { CreateLiveQueryRequestBodySchema } from '../../../common/schemas/routes/live_query'; import { replaceParamsQuery } from '../../../common/utils/replace_params_query'; import { isSavedQueryPrebuilt } from '../../routes/saved_query/utils'; -export const PARAMETER_NOT_FOUND = i18n.translate( - 'xpack.osquery.liveQueryActions.error.notFoundParameters', - { - defaultMessage: - "This query hasn't been called due to parameter used and its value not found in the alert.", - } -); - interface CreateDynamicQueriesParams { params: CreateLiveQueryRequestBodySchema; alertData?: ParsedTechnicalFields; agents: string[]; osqueryContext: OsqueryAppContext; + error?: string; } + export const createDynamicQueries = async ({ params, alertData, agents, osqueryContext, + error, }: CreateDynamicQueriesParams) => params.queries?.length ? map(params.queries, ({ query, ...restQuery }) => { @@ -43,6 +38,7 @@ export const createDynamicQueries = async ({ { ...replacedQuery, ...restQuery, + ...(error ? { error } : {}), action_id: uuidv4(), alert_ids: params.alert_ids, agents, @@ -66,6 +62,7 @@ export const createDynamicQueries = async ({ ecs_mapping: params.ecs_mapping, alert_ids: params.alert_ids, agents, + ...(error ? { error } : {}), }, (value) => !isEmpty(value) ), diff --git a/x-pack/plugins/osquery/server/handlers/action/validate_license.ts b/x-pack/plugins/osquery/server/handlers/action/validate_license.ts new file mode 100644 index 0000000000000..063683198acbe --- /dev/null +++ b/x-pack/plugins/osquery/server/handlers/action/validate_license.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 { LICENSE_TOO_LOW } from '../../../common/translations/errors'; + +export interface OsqueryActiveLicenses { + isActivePlatinumLicense: boolean; +} + +export const validateLicense = (license?: OsqueryActiveLicenses) => { + if (!license) { + return; + } + + const { isActivePlatinumLicense } = license; + + if (!isActivePlatinumLicense) { + return LICENSE_TOO_LOW; + } +}; diff --git a/x-pack/plugins/osquery/server/lib/osquery_app_context_services.ts b/x-pack/plugins/osquery/server/lib/osquery_app_context_services.ts index 19b5b13495718..25d668c16d93a 100644 --- a/x-pack/plugins/osquery/server/lib/osquery_app_context_services.ts +++ b/x-pack/plugins/osquery/server/lib/osquery_app_context_services.ts @@ -15,6 +15,7 @@ import type { PackagePolicyClient, } from '@kbn/fleet-plugin/server'; import type { RuleRegistryPluginStartContract } from '@kbn/rule-registry-plugin/server'; +import type { LicensingPluginSetup } from '@kbn/licensing-plugin/server'; import type { ConfigType } from '../../common/config'; import type { TelemetryEventsSender } from './telemetry/sender'; @@ -82,6 +83,7 @@ export interface OsqueryAppContext { security: SecurityPluginStart; getStartServices: CoreSetup['getStartServices']; telemetryEventsSender: TelemetryEventsSender; + licensing: LicensingPluginSetup; /** * Object readiness is tied to plugin start method */ diff --git a/x-pack/plugins/osquery/server/plugin.ts b/x-pack/plugins/osquery/server/plugin.ts index 9c74329ef2a69..49152eeba5cc6 100644 --- a/x-pack/plugins/osquery/server/plugin.ts +++ b/x-pack/plugins/osquery/server/plugin.ts @@ -17,12 +17,11 @@ import type { DataRequestHandlerContext } from '@kbn/data-plugin/server'; import type { DataViewsService } from '@kbn/data-views-plugin/common'; import type { NewPackagePolicy, UpdatePackagePolicy } from '@kbn/fleet-plugin/common'; -import type { ParsedTechnicalFields } from '@kbn/rule-registry-plugin/common'; +import type { Subscription } from 'rxjs'; import { upgradeIntegration } from './utils/upgrade_integration'; import type { PackSavedObjectAttributes } from './common/types'; import { updateGlobalPacksCreateCallback } from './lib/update_global_packs'; import { packSavedObjectType } from '../common/types'; -import type { CreateLiveQueryRequestBodySchema } from '../common/schemas/routes/live_query'; import { createConfig } from './create_config'; import type { OsqueryPluginSetup, OsqueryPluginStart, SetupPlugins, StartPlugins } from './types'; import { defineRoutes } from './routes'; @@ -39,10 +38,10 @@ import { TelemetryReceiver } from './lib/telemetry/receiver'; import { initializeTransformsIndices } from './create_indices/create_transforms_indices'; import { initializeTransforms } from './create_transforms/create_transforms'; import { createDataViews } from './create_data_views'; -import { createActionHandler } from './handlers/action'; import { registerFeatures } from './utils/register_features'; import { CASE_ATTACHMENT_TYPE_ID } from '../common/constants'; +import { createActionService } from './handlers/action/create_action_service'; export class OsqueryPlugin implements Plugin { private readonly logger: Logger; @@ -50,6 +49,8 @@ export class OsqueryPlugin implements Plugin | null = null; constructor(private readonly initializerContext: PluginInitializerContext) { this.context = initializerContext; @@ -73,6 +74,7 @@ export class OsqueryPlugin implements Plugin config, security: plugins.security, telemetryEventsSender: this.telemetryEventsSender, + licensing: plugins.licensing, }; initSavedObjects(core.savedObjects); @@ -82,6 +84,8 @@ export class OsqueryPlugin implements Plugin { const osquerySearchStrategy = osquerySearchStrategyProvider( depsStart.data, @@ -97,10 +101,7 @@ export class OsqueryPlugin implements Plugin createActionHandler(osqueryContext, params, { alertData }), + createActionService: this.createActionService, }; } @@ -180,6 +181,8 @@ export class OsqueryPlugin implements Plugin { diff --git a/x-pack/plugins/osquery/server/routes/live_query/create_live_query_route.ts b/x-pack/plugins/osquery/server/routes/live_query/create_live_query_route.ts index 05f857e320066..d9bb8eb8efb25 100644 --- a/x-pack/plugins/osquery/server/routes/live_query/create_live_query_route.ts +++ b/x-pack/plugins/osquery/server/routes/live_query/create_live_query_route.ts @@ -11,7 +11,7 @@ import markdown from 'remark-parse-no-trim'; import { some, filter } from 'lodash'; import deepEqual from 'fast-deep-equal'; import type { ECSMappingOrUndefined } from '@kbn/osquery-io-ts-types'; -import { PARAMETER_NOT_FOUND } from '../../handlers/action/create_queries'; +import { PARAMETER_NOT_FOUND } from '../../../common/translations/errors'; import { replaceParamsQuery } from '../../../common/utils/replace_params_query'; import { createLiveQueryRequestBodySchema } from '../../../common/schemas/routes/live_query'; import type { CreateLiveQueryRequestBodySchema } from '../../../common/schemas/routes/live_query'; diff --git a/x-pack/plugins/osquery/server/types.ts b/x-pack/plugins/osquery/server/types.ts index c620d6035f547..7a6353cccee40 100644 --- a/x-pack/plugins/osquery/server/types.ts +++ b/x-pack/plugins/osquery/server/types.ts @@ -22,15 +22,12 @@ import type { } from '@kbn/task-manager-plugin/server'; import type { PluginStart as DataViewsPluginStart } from '@kbn/data-views-plugin/server'; import type { RuleRegistryPluginStartContract } from '@kbn/rule-registry-plugin/server'; -import type { ParsedTechnicalFields } from '@kbn/rule-registry-plugin/common'; import type { CasesSetup } from '@kbn/cases-plugin/server'; -import type { CreateLiveQueryRequestBodySchema } from '../common/schemas/routes/live_query'; +import type { LicensingPluginSetup } from '@kbn/licensing-plugin/server'; +import type { createActionService } from './handlers/action/create_action_service'; export interface OsqueryPluginSetup { - osqueryCreateAction: ( - payload: CreateLiveQueryRequestBodySchema, - alertData?: ParsedTechnicalFields - ) => void; + createActionService: ReturnType; } // eslint-disable-next-line @typescript-eslint/no-empty-interface @@ -45,6 +42,7 @@ export interface SetupPlugins { security: SecurityPluginStart; taskManager?: TaskManagerPluginSetup; telemetry?: TelemetryPluginSetup; + licensing: LicensingPluginSetup; } export interface StartPlugins { diff --git a/x-pack/plugins/osquery/tsconfig.json b/x-pack/plugins/osquery/tsconfig.json index 6d67d852b6073..c71fbc2871f94 100644 --- a/x-pack/plugins/osquery/tsconfig.json +++ b/x-pack/plugins/osquery/tsconfig.json @@ -71,6 +71,7 @@ "@kbn/safer-lodash-set", "@kbn/shared-ux-router", "@kbn/securitysolution-ecs", + "@kbn/licensing-plugin", "@kbn/core-saved-objects-server" ] } diff --git a/x-pack/plugins/rule_registry/common/search_strategy/index.ts b/x-pack/plugins/rule_registry/common/search_strategy/index.ts index 213ab694eaa87..c38e5edc6d808 100644 --- a/x-pack/plugins/rule_registry/common/search_strategy/index.ts +++ b/x-pack/plugins/rule_registry/common/search_strategy/index.ts @@ -7,6 +7,7 @@ import { TechnicalRuleDataFieldName, ValidFeatureId } from '@kbn/rule-data-utils'; import { IEsSearchRequest, IEsSearchResponse } from '@kbn/data-plugin/common'; import type { + MappingRuntimeFields, QueryDslFieldAndFormat, QueryDslQueryContainer, SortCombinations, @@ -18,6 +19,7 @@ export type RuleRegistrySearchRequest = IEsSearchRequest & { query?: Pick; sort?: SortCombinations[]; pagination?: RuleRegistrySearchRequestPagination; + runtimeMappings?: MappingRuntimeFields; }; export interface RuleRegistrySearchRequestPagination { diff --git a/x-pack/plugins/rule_registry/server/search_strategy/search_strategy.ts b/x-pack/plugins/rule_registry/server/search_strategy/search_strategy.ts index e2c4e5d388758..d044e52154fd8 100644 --- a/x-pack/plugins/rule_registry/server/search_strategy/search_strategy.ts +++ b/x-pack/plugins/rule_registry/server/search_strategy/search_strategy.ts @@ -136,6 +136,7 @@ export const ruleRegistrySearchStrategyProvider = ( size, from: request.pagination ? request.pagination.pageIndex * size : 0, query, + ...(request.runtimeMappings ? { runtime_mappings: request.runtimeMappings } : {}), }, }; return (siemRequest ? requestUserEs : internalUserEs).search( diff --git a/x-pack/plugins/security_solution/common/endpoint/constants.ts b/x-pack/plugins/security_solution/common/endpoint/constants.ts index 2d7c5269737aa..97510abec6ba8 100644 --- a/x-pack/plugins/security_solution/common/endpoint/constants.ts +++ b/x-pack/plugins/security_solution/common/endpoint/constants.ts @@ -73,7 +73,7 @@ export const UNISOLATE_HOST_ROUTE = `${BASE_ENDPOINT_ROUTE}/unisolate`; /** Base Actions route. Used to get a list of all actions and is root to other action related routes */ export const BASE_ENDPOINT_ACTION_ROUTE = `${BASE_ENDPOINT_ROUTE}/action`; -export const BASE_ENDPOINT_ACTION_ALERTS_ROUTE = `${BASE_ENDPOINT_ROUTE}/alerts`; +export const BASE_ENDPOINT_ACTION_ALERTS_ROUTE = `${BASE_ENDPOINT_ACTION_ROUTE}/alerts`; export const ISOLATE_HOST_ROUTE_V2 = `${BASE_ENDPOINT_ACTION_ROUTE}/isolate`; export const UNISOLATE_HOST_ROUTE_V2 = `${BASE_ENDPOINT_ACTION_ROUTE}/unisolate`; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_table.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_table.test.ts.snap index c18b413cd2a57..6148e986fa5e2 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_table.test.ts.snap +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_table.test.ts.snap @@ -6,7 +6,7 @@ Object { "references": Array [ Object { "id": "security-solution-my-test", - "name": "indexpattern-datasource-layer-4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", + "name": "indexpattern-datasource-layer-mockLayerId", "type": "index-pattern", }, ], @@ -15,17 +15,28 @@ Object { "datasourceStates": Object { "formBased": Object { "layers": Object { - "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b": Object { + "mockLayerId": Object { "columnOrder": Array [ - "2881fedd-54b7-42ba-8c97-5175dec86166", - "75ce269b-ee9c-4c7d-a14e-9226ba0fe059", - "f04a71a3-399f-4d32-9efc-8a005e989991", + "mockTopValuesOfStackByFieldColumnId", + "mockTopValuesOfBreakdownFieldColumnId", + "mockCountColumnId", ], "columns": Object { - "2881fedd-54b7-42ba-8c97-5175dec86166": Object { + "mockCountColumnId": Object { + "dataType": "number", + "isBucketed": false, + "label": "Count of agent.type", + "operationType": "count", + "params": Object { + "emptyAsNull": true, + }, + "scale": "ratio", + "sourceField": "agent.type", + }, + "mockTopValuesOfBreakdownFieldColumnId": Object { "dataType": "string", "isBucketed": true, - "label": "Top values of event.category", + "label": "Top values of agent.type", "operationType": "terms", "params": Object { "exclude": Array [], @@ -34,7 +45,7 @@ Object { "includeIsRegex": false, "missingBucket": false, "orderBy": Object { - "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", + "columnId": "mockCountColumnId", "type": "column", }, "orderDirection": "desc", @@ -45,12 +56,12 @@ Object { "size": 1000, }, "scale": "ordinal", - "sourceField": "event.category", + "sourceField": "agent.type", }, - "75ce269b-ee9c-4c7d-a14e-9226ba0fe059": Object { + "mockTopValuesOfStackByFieldColumnId": Object { "dataType": "string", "isBucketed": true, - "label": "Top values of agent.type", + "label": "Top values of event.category", "operationType": "terms", "params": Object { "exclude": Array [], @@ -59,7 +70,7 @@ Object { "includeIsRegex": false, "missingBucket": false, "orderBy": Object { - "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", + "columnId": "mockCountColumnId", "type": "column", }, "orderDirection": "desc", @@ -70,18 +81,7 @@ Object { "size": 1000, }, "scale": "ordinal", - "sourceField": "agent.type", - }, - "f04a71a3-399f-4d32-9efc-8a005e989991": Object { - "dataType": "number", - "isBucketed": false, - "label": "Count of agent.type", - "operationType": "count", - "params": Object { - "emptyAsNull": true, - }, - "scale": "ratio", - "sourceField": "agent.type", + "sourceField": "event.category", }, }, "incompleteColumns": Object {}, @@ -144,20 +144,20 @@ Object { "visualization": Object { "columns": Array [ Object { - "columnId": "2881fedd-54b7-42ba-8c97-5175dec86166", + "columnId": "mockTopValuesOfStackByFieldColumnId", "isTransposed": false, "width": 362, }, Object { - "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", + "columnId": "mockCountColumnId", "isTransposed": false, }, Object { - "columnId": "75ce269b-ee9c-4c7d-a14e-9226ba0fe059", + "columnId": "mockTopValuesOfBreakdownFieldColumnId", "isTransposed": false, }, ], - "layerId": "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", + "layerId": "mockLayerId", "layerType": "data", }, }, @@ -172,7 +172,7 @@ Object { "references": Array [ Object { "id": "security-solution-my-test", - "name": "indexpattern-datasource-layer-4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", + "name": "indexpattern-datasource-layer-mockLayerId", "type": "index-pattern", }, ], @@ -181,42 +181,27 @@ Object { "datasourceStates": Object { "formBased": Object { "layers": Object { - "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b": Object { + "mockLayerId": Object { "columnOrder": Array [ - "2881fedd-54b7-42ba-8c97-5175dec86166", - "75ce269b-ee9c-4c7d-a14e-9226ba0fe059", - "f04a71a3-399f-4d32-9efc-8a005e989991", + "mockTopValuesOfStackByFieldColumnId", + "mockCountColumnId", ], "columns": Object { - "2881fedd-54b7-42ba-8c97-5175dec86166": Object { - "dataType": "string", - "isBucketed": true, - "label": "Top values of event.category", - "operationType": "terms", + "mockCountColumnId": Object { + "dataType": "number", + "isBucketed": false, + "label": "Count of event.category", + "operationType": "count", "params": Object { - "exclude": Array [], - "excludeIsRegex": false, - "include": Array [], - "includeIsRegex": false, - "missingBucket": false, - "orderBy": Object { - "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", - "type": "column", - }, - "orderDirection": "desc", - "otherBucket": true, - "parentFormat": Object { - "id": "terms", - }, - "size": 1000, + "emptyAsNull": true, }, - "scale": "ordinal", + "scale": "ratio", "sourceField": "event.category", }, - "75ce269b-ee9c-4c7d-a14e-9226ba0fe059": Object { + "mockTopValuesOfStackByFieldColumnId": Object { "dataType": "string", "isBucketed": true, - "label": "Top values of undefined", + "label": "Top values of event.category", "operationType": "terms", "params": Object { "exclude": Array [], @@ -225,7 +210,7 @@ Object { "includeIsRegex": false, "missingBucket": false, "orderBy": Object { - "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", + "columnId": "mockCountColumnId", "type": "column", }, "orderDirection": "desc", @@ -236,18 +221,7 @@ Object { "size": 1000, }, "scale": "ordinal", - "sourceField": undefined, - }, - "f04a71a3-399f-4d32-9efc-8a005e989991": Object { - "dataType": "number", - "isBucketed": false, - "label": "Count of undefined", - "operationType": "count", - "params": Object { - "emptyAsNull": true, - }, - "scale": "ratio", - "sourceField": undefined, + "sourceField": "event.category", }, }, "incompleteColumns": Object {}, @@ -334,20 +308,16 @@ Object { "visualization": Object { "columns": Array [ Object { - "columnId": "2881fedd-54b7-42ba-8c97-5175dec86166", + "columnId": "mockTopValuesOfStackByFieldColumnId", "isTransposed": false, "width": 362, }, Object { - "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", - "isTransposed": false, - }, - Object { - "columnId": "75ce269b-ee9c-4c7d-a14e-9226ba0fe059", + "columnId": "mockCountColumnId", "isTransposed": false, }, ], - "layerId": "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", + "layerId": "mockLayerId", "layerType": "data", }, }, @@ -362,7 +332,7 @@ Object { "references": Array [ Object { "id": "security-solution-my-test", - "name": "indexpattern-datasource-layer-4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", + "name": "indexpattern-datasource-layer-mockLayerId", "type": "index-pattern", }, ], @@ -371,42 +341,27 @@ Object { "datasourceStates": Object { "formBased": Object { "layers": Object { - "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b": Object { + "mockLayerId": Object { "columnOrder": Array [ - "2881fedd-54b7-42ba-8c97-5175dec86166", - "75ce269b-ee9c-4c7d-a14e-9226ba0fe059", - "f04a71a3-399f-4d32-9efc-8a005e989991", + "mockTopValuesOfStackByFieldColumnId", + "mockCountColumnId", ], "columns": Object { - "2881fedd-54b7-42ba-8c97-5175dec86166": Object { - "dataType": "string", - "isBucketed": true, - "label": "Top values of event.category", - "operationType": "terms", + "mockCountColumnId": Object { + "dataType": "number", + "isBucketed": false, + "label": "Count of event.category", + "operationType": "count", "params": Object { - "exclude": Array [], - "excludeIsRegex": false, - "include": Array [], - "includeIsRegex": false, - "missingBucket": false, - "orderBy": Object { - "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", - "type": "column", - }, - "orderDirection": "desc", - "otherBucket": true, - "parentFormat": Object { - "id": "terms", - }, - "size": 1000, + "emptyAsNull": true, }, - "scale": "ordinal", + "scale": "ratio", "sourceField": "event.category", }, - "75ce269b-ee9c-4c7d-a14e-9226ba0fe059": Object { + "mockTopValuesOfStackByFieldColumnId": Object { "dataType": "string", "isBucketed": true, - "label": "Top values of undefined", + "label": "Top values of event.category", "operationType": "terms", "params": Object { "exclude": Array [], @@ -415,7 +370,7 @@ Object { "includeIsRegex": false, "missingBucket": false, "orderBy": Object { - "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", + "columnId": "mockCountColumnId", "type": "column", }, "orderDirection": "desc", @@ -426,18 +381,7 @@ Object { "size": 1000, }, "scale": "ordinal", - "sourceField": undefined, - }, - "f04a71a3-399f-4d32-9efc-8a005e989991": Object { - "dataType": "number", - "isBucketed": false, - "label": "Count of undefined", - "operationType": "count", - "params": Object { - "emptyAsNull": true, - }, - "scale": "ratio", - "sourceField": undefined, + "sourceField": "event.category", }, }, "incompleteColumns": Object {}, @@ -500,20 +444,16 @@ Object { "visualization": Object { "columns": Array [ Object { - "columnId": "2881fedd-54b7-42ba-8c97-5175dec86166", + "columnId": "mockTopValuesOfStackByFieldColumnId", "isTransposed": false, "width": 362, }, Object { - "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991", - "isTransposed": false, - }, - Object { - "columnId": "75ce269b-ee9c-4c7d-a14e-9226ba0fe059", + "columnId": "mockCountColumnId", "isTransposed": false, }, ], - "layerId": "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b", + "layerId": "mockLayerId", "layerType": "data", }, }, diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.test.ts index e8457e8dfb533..82303e529db31 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.test.ts @@ -11,8 +11,20 @@ import { useLensAttributes } from '../../../use_lens_attributes'; import { getAlertsTableLensAttributes } from './alerts_table'; +interface VisualizationState { + visualization: { columns: {} }; + datasourceStates: { + formBased: { layers: Record }; + }; +} + jest.mock('uuid', () => ({ - v4: jest.fn().mockReturnValue('4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b'), + v4: jest + .fn() + .mockReturnValueOnce('mockLayerId') + .mockReturnValueOnce('mockTopValuesOfStackByFieldColumnId') + .mockReturnValueOnce('mockCountColumnId') + .mockReturnValueOnce('mockTopValuesOfBreakdownFieldColumnId'), })); jest.mock('../../../../../containers/sourcerer', () => ({ @@ -95,6 +107,49 @@ describe('getAlertsTableLensAttributes', () => { { wrapper } ); + const state = result?.current?.state as VisualizationState; expect(result?.current).toMatchSnapshot(); + + expect(state.datasourceStates.formBased.layers.mockLayerId.columnOrder).toMatchInlineSnapshot(` + Array [ + "mockTopValuesOfStackByFieldColumnId", + "mockTopValuesOfBreakdownFieldColumnId", + "mockCountColumnId", + ] + `); + }); + + it('should render Without extra options - breakdownField', () => { + const { result } = renderHook( + () => + useLensAttributes({ + extraOptions: { breakdownField: '' }, + getLensAttributes: getAlertsTableLensAttributes, + stackByField: 'event.category', + }), + { wrapper } + ); + + const state = result?.current?.state as VisualizationState; + expect(state.visualization?.columns).toMatchInlineSnapshot(` + Array [ + Object { + "columnId": "mockTopValuesOfStackByFieldColumnId", + "isTransposed": false, + "width": 362, + }, + Object { + "columnId": "mockCountColumnId", + "isTransposed": false, + }, + ] + `); + + expect(state.datasourceStates.formBased.layers.mockLayerId.columnOrder).toMatchInlineSnapshot(` + Array [ + "mockTopValuesOfStackByFieldColumnId", + "mockCountColumnId", + ] + `); }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.ts index 678179855557c..9358bcc5db810 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.ts @@ -5,35 +5,127 @@ * 2.0. */ import { v4 as uuidv4 } from 'uuid'; -import type { GetLensAttributes } from '../../../types'; + +import { isEmpty } from 'lodash'; +import type { GetLensAttributes, LensEmbeddableDataTableColumn } from '../../../types'; +import { COUNT_OF, TOP_VALUE } from '../../../translations'; const layerId = uuidv4(); +const topValuesOfStackByFieldColumnId = uuidv4(); +const countColumnId = uuidv4(); +const topValuesOfBreakdownFieldColumnId = uuidv4(); +const defaultColumns = [ + { + columnId: topValuesOfStackByFieldColumnId, + isTransposed: false, + width: 362, + }, + { + columnId: countColumnId, + isTransposed: false, + }, +]; +const breakdownFieldColumns = [ + { + columnId: topValuesOfBreakdownFieldColumnId, + isTransposed: false, + }, +]; +const defaultColumnOrder = [topValuesOfStackByFieldColumnId]; +const getTopValuesOfBreakdownFieldColumnSettings = ( + breakdownField: string +): Record => ({ + [topValuesOfBreakdownFieldColumnId]: { + label: TOP_VALUE(breakdownField), + dataType: 'string', + operationType: 'terms', + scale: 'ordinal', + sourceField: breakdownField, + isBucketed: true, + params: { + size: 1000, + orderBy: { + type: 'column', + columnId: countColumnId, + }, + orderDirection: 'desc', + otherBucket: true, + missingBucket: false, + parentFormat: { + id: 'terms', + }, + include: [], + exclude: [], + includeIsRegex: false, + excludeIsRegex: false, + }, + }, +}); export const getAlertsTableLensAttributes: GetLensAttributes = ( stackByField = 'kibana.alert.rule.name', extraOptions ) => { + const breakdownFieldProvided = !isEmpty(extraOptions?.breakdownField); + const countField = + extraOptions?.breakdownField && breakdownFieldProvided + ? extraOptions?.breakdownField + : stackByField; + const columnOrder = breakdownFieldProvided + ? [...defaultColumnOrder, topValuesOfBreakdownFieldColumnId, countColumnId] + : [...defaultColumnOrder, countColumnId]; + + const columnSettings: Record = { + [topValuesOfStackByFieldColumnId]: { + label: TOP_VALUE(stackByField), + dataType: 'string', + operationType: 'terms', + scale: 'ordinal', + sourceField: stackByField, + isBucketed: true, + params: { + size: 1000, + orderBy: { + type: 'column', + columnId: countColumnId, + }, + orderDirection: 'desc', + otherBucket: true, + missingBucket: false, + parentFormat: { + id: 'terms', + }, + include: [], + exclude: [], + includeIsRegex: false, + excludeIsRegex: false, + }, + }, + [countColumnId]: { + label: COUNT_OF(countField), + dataType: 'number', + operationType: 'count', + isBucketed: false, + scale: 'ratio', + sourceField: countField, + params: { + emptyAsNull: true, + }, + }, + ...(extraOptions?.breakdownField && breakdownFieldProvided + ? getTopValuesOfBreakdownFieldColumnSettings(extraOptions?.breakdownField) + : {}), + }; + return { title: 'Alerts', description: '', visualizationType: 'lnsDatatable', state: { visualization: { - columns: [ - { - columnId: '2881fedd-54b7-42ba-8c97-5175dec86166', - isTransposed: false, - width: 362, - }, - { - columnId: 'f04a71a3-399f-4d32-9efc-8a005e989991', - isTransposed: false, - }, - { - columnId: '75ce269b-ee9c-4c7d-a14e-9226ba0fe059', - isTransposed: false, - }, - ], + columns: breakdownFieldProvided + ? [...defaultColumns, ...breakdownFieldColumns] + : defaultColumns, layerId, layerType: 'data', }, @@ -46,74 +138,16 @@ export const getAlertsTableLensAttributes: GetLensAttributes = ( formBased: { layers: { [layerId]: { - columns: { - '2881fedd-54b7-42ba-8c97-5175dec86166': { - label: `Top values of ${stackByField}`, - dataType: 'string', - operationType: 'terms', - scale: 'ordinal', - sourceField: stackByField, - isBucketed: true, - params: { - size: 1000, - orderBy: { - type: 'column', - columnId: 'f04a71a3-399f-4d32-9efc-8a005e989991', - }, - orderDirection: 'desc', - otherBucket: true, - missingBucket: false, - parentFormat: { - id: 'terms', - }, - include: [], - exclude: [], - includeIsRegex: false, - excludeIsRegex: false, - }, - }, - 'f04a71a3-399f-4d32-9efc-8a005e989991': { - label: `Count of ${extraOptions?.breakdownField}`, - dataType: 'number', - operationType: 'count', - isBucketed: false, - scale: 'ratio', - sourceField: extraOptions?.breakdownField, - params: { - emptyAsNull: true, - }, - }, - '75ce269b-ee9c-4c7d-a14e-9226ba0fe059': { - label: `Top values of ${extraOptions?.breakdownField}`, - dataType: 'string', - operationType: 'terms', - scale: 'ordinal', - sourceField: extraOptions?.breakdownField, - isBucketed: true, - params: { - size: 1000, - orderBy: { - type: 'column', - columnId: 'f04a71a3-399f-4d32-9efc-8a005e989991', - }, - orderDirection: 'desc', - otherBucket: true, - missingBucket: false, - parentFormat: { - id: 'terms', - }, - include: [], - exclude: [], - includeIsRegex: false, - excludeIsRegex: false, - }, + columns: columnOrder.reduce>( + (acc, colId) => { + if (colId && columnSettings[colId]) { + acc[colId] = columnSettings[colId]; + } + return acc; }, - }, - columnOrder: [ - '2881fedd-54b7-42ba-8c97-5175dec86166', - '75ce269b-ee9c-4c7d-a14e-9226ba0fe059', - 'f04a71a3-399f-4d32-9efc-8a005e989991', - ], + {} + ), + columnOrder, sampling: 1, incompleteColumns: {}, }, diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/translations.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/translations.ts index 4dcceb29323b1..8f21d61bc869b 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/translations.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/translations.ts @@ -105,3 +105,9 @@ export const TOP_VALUE = (field: string) => export const COUNT = i18n.translate('xpack.securitySolution.visualizationActions.countLabel', { defaultMessage: 'Count of records', }); + +export const COUNT_OF = (field: string) => + i18n.translate('xpack.securitySolution.visualizationActions.countOfFieldLabel', { + values: { field }, + defaultMessage: 'Count of {field}', + }); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts index 71c7440d5f1e1..dd2c7f7cd6094 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts @@ -7,6 +7,7 @@ import type { DatatableVisualizationState, + FieldBasedIndexPatternColumn, FormBasedPersistedState, TypedLensByValueInput, } from '@kbn/lens-plugin/public'; @@ -181,3 +182,8 @@ export interface LensDataTableEmbeddable { id: string; timeRange: { from: string; to: string; fromStr: string; toStr: string }; } + +export interface LensEmbeddableDataTableColumn extends FieldBasedIndexPatternColumn { + operationType: string; + params?: unknown; +} diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx index 7276bb9d2e118..81c2708d961cb 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx @@ -81,10 +81,7 @@ export const useLensAttributes = ({ const lensAttrsWithInjectedData = useMemo(() => { if ( lensAttributes == null && - (getLensAttributes == null || - stackByField == null || - stackByField?.length === 0 || - (extraOptions?.breakdownField != null && extraOptions?.breakdownField.length === 0)) + (getLensAttributes == null || stackByField == null || stackByField?.length === 0) ) { return null; } @@ -113,7 +110,6 @@ export const useLensAttributes = ({ applyGlobalQueriesAndFilters, attrs, dataViewId, - extraOptions?.breakdownField, filters, getLensAttributes, hasAdHocDataViews, diff --git a/x-pack/plugins/security_solution/public/common/hooks/flyout/use_init_flyout_url_param.ts b/x-pack/plugins/security_solution/public/common/hooks/flyout/use_init_flyout_url_param.ts index a67b605841917..fbbf7ea453efd 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/flyout/use_init_flyout_url_param.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/flyout/use_init_flyout_url_param.ts @@ -42,7 +42,7 @@ export const useInitFlyoutFromUrlParam = () => { const { initialized, isLoading, totalCount, additionalFilters } = dataTableCurrent; const isTableLoaded = initialized && !isLoading && totalCount > 0; if (urlDetails) { - if (!additionalFilters.showBuildingBlockAlerts) { + if (!additionalFilters || !additionalFilters.showBuildingBlockAlerts) { // We want to show building block alerts when loading the flyout in case the alert is a building block alert dispatch( dataTableActions.updateShowBuildingBlockAlertsFilter({ diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.test.ts index 5e3248818bf4e..d1e67923d7b56 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.test.ts @@ -848,7 +848,7 @@ describe('Detections Rules API', () => { mute_all: false, }, { - id: '1', + id: '2', mute_all: false, active_snoozes: [], is_snoozed_until: '2023-04-24T19:31:46.765Z', @@ -856,21 +856,19 @@ describe('Detections Rules API', () => { ], }); - const result = await fetchRulesSnoozeSettings({ ids: ['id1'] }); + const result = await fetchRulesSnoozeSettings({ ids: ['1', '2'] }); - expect(result).toEqual([ - { - id: '1', + expect(result).toEqual({ + '1': { muteAll: false, activeSnoozes: [], }, - { - id: '1', + '2': { muteAll: false, activeSnoozes: [], isSnoozedUntil: new Date('2023-04-24T19:31:46.765Z'), }, - ]); + }); }); }); }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts index 24b66cada346c..74eb06bb373e9 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts @@ -57,8 +57,8 @@ import type { PrePackagedRulesStatusResponse, PreviewRulesProps, Rule, - RuleSnoozeSettings, RulesSnoozeSettingsBatchResponse, + RulesSnoozeSettingsMap, UpdateRulesProps, } from '../logic/types'; import { convertRulesFilterToKQL } from '../logic/utils'; @@ -198,7 +198,7 @@ export const fetchRuleById = async ({ id, signal }: FetchRuleProps): Promise => { +}: FetchRuleSnoozingProps): Promise => { const response = await KibanaServices.get().http.fetch( INTERNAL_ALERTING_API_FIND_RULES_PATH, { @@ -212,15 +212,18 @@ export const fetchRulesSnoozeSettings = async ({ } ); - return response.data?.map((snoozeSettings) => ({ - id: snoozeSettings?.id ?? '', - muteAll: snoozeSettings?.mute_all ?? false, - activeSnoozes: snoozeSettings?.active_snoozes ?? [], - isSnoozedUntil: snoozeSettings?.is_snoozed_until - ? new Date(snoozeSettings.is_snoozed_until) - : undefined, - snoozeSchedule: snoozeSettings?.snooze_schedule, - })); + return response.data?.reduce((result, { id, ...snoozeSettings }) => { + result[id] = { + muteAll: snoozeSettings.mute_all ?? false, + activeSnoozes: snoozeSettings.active_snoozes ?? [], + isSnoozedUntil: snoozeSettings.is_snoozed_until + ? new Date(snoozeSettings.is_snoozed_until) + : undefined, + snoozeSchedule: snoozeSettings.snooze_schedule, + }; + + return result; + }, {} as RulesSnoozeSettingsMap); }; export interface BulkActionSummary { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_fetch_rules_snooze_settings.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_fetch_rules_snooze_settings.ts index 8e0ef31871826..8963baab0362b 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_fetch_rules_snooze_settings.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_fetch_rules_snooze_settings.ts @@ -9,7 +9,7 @@ import { INTERNAL_ALERTING_API_FIND_RULES_PATH } from '@kbn/alerting-plugin/comm import type { UseQueryOptions } from '@tanstack/react-query'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useCallback } from 'react'; -import type { RuleSnoozeSettings } from '../../logic'; +import type { RulesSnoozeSettingsMap } from '../../logic'; import { fetchRulesSnoozeSettings } from '../api'; import { DEFAULT_QUERY_OPTIONS } from './constants'; @@ -25,7 +25,7 @@ const FETCH_RULE_SNOOZE_SETTINGS_QUERY_KEY = ['GET', INTERNAL_ALERTING_API_FIND_ */ export const useFetchRulesSnoozeSettings = ( ids: string[], - queryOptions?: UseQueryOptions + queryOptions?: UseQueryOptions ) => { return useQuery( [...FETCH_RULE_SNOOZE_SETTINGS_QUERY_KEY, ...ids], @@ -51,7 +51,7 @@ export const useInvalidateFetchRulesSnoozeSettingsQuery = () => { * Invalidate all queries that start with FIND_RULES_QUERY_KEY. This * includes the in-memory query cache and paged query cache. */ - queryClient.invalidateQueries(FETCH_RULE_SNOOZE_SETTINGS_QUERY_KEY, { + return queryClient.invalidateQueries(FETCH_RULE_SNOOZE_SETTINGS_QUERY_KEY, { refetchType: 'active', }); }, [queryClient]); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_snooze_badge/rule_snooze_badge.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_snooze_badge/rule_snooze_badge.tsx index e488127c25691..8808f6d3033b5 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_snooze_badge/rule_snooze_badge.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_snooze_badge/rule_snooze_badge.tsx @@ -6,7 +6,7 @@ */ import { EuiButtonIcon, EuiToolTip } from '@elastic/eui'; -import React, { useMemo } from 'react'; +import React from 'react'; import type { RuleObjectId } from '../../../../../common/detection_engine/rule_schema'; import { useUserData } from '../../../../detections/components/user_info'; import { hasUserCRUDPermission } from '../../../../common/utils/privileges'; @@ -31,20 +31,6 @@ export function RuleSnoozeBadge({ const [{ canUserCRUD }] = useUserData(); const hasCRUDPermissions = hasUserCRUDPermission(canUserCRUD); const invalidateFetchRuleSnoozeSettings = useInvalidateFetchRulesSnoozeSettingsQuery(); - const isLoading = !snoozeSettings; - const rule = useMemo( - () => ({ - id: snoozeSettings?.id ?? '', - muteAll: snoozeSettings?.muteAll ?? false, - activeSnoozes: snoozeSettings?.activeSnoozes ?? [], - isSnoozedUntil: snoozeSettings?.isSnoozedUntil - ? new Date(snoozeSettings.isSnoozedUntil) - : undefined, - snoozeSchedule: snoozeSettings?.snoozeSchedule, - isEditable: hasCRUDPermissions, - }), - [snoozeSettings, hasCRUDPermissions] - ); if (error) { return ( @@ -56,8 +42,10 @@ export function RuleSnoozeBadge({ return ( diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_snooze_badge/use_rule_snooze_settings.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_snooze_badge/use_rule_snooze_settings.ts index 94a857b1e9842..e31851a6a24b0 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_snooze_badge/use_rule_snooze_settings.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_snooze_badge/use_rule_snooze_settings.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { RuleSnoozeSettings } from '../../logic'; +import type { RuleSnoozeSettings } from '@kbn/triggers-actions-ui-plugin/public/types'; import { useFetchRulesSnoozeSettings } from '../../api/hooks/use_fetch_rules_snooze_settings'; import { useRulesTableContextOptional } from '../../../rule_management_ui/components/rules_table/rules_table/rules_table_context'; import * as i18n from './translations'; @@ -26,7 +26,7 @@ export function useRuleSnoozeSettings(id: string): UseRuleSnoozeSettingsResult { } = useFetchRulesSnoozeSettings([id], { enabled: !rulesTableSnoozeSettings?.data[id] && !rulesTableSnoozeSettings?.isFetching, }); - const snoozeSettings = rulesTableSnoozeSettings?.data[id] ?? rulesSnoozeSettings?.[0]; + const snoozeSettings = rulesTableSnoozeSettings?.data[id] ?? rulesSnoozeSettings?.[id]; const isFetching = rulesTableSnoozeSettings?.isFetching || isSingleSnoozeSettingsFetching; const isError = rulesTableSnoozeSettings?.isError || isSingleSnoozeSettingsError; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts index e22be9467c6a1..092bc81554016 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts @@ -28,6 +28,7 @@ import { type, } from '@kbn/securitysolution-io-ts-alerting-types'; import type { NamespaceType } from '@kbn/securitysolution-io-ts-list-types'; +import type { RuleSnoozeSettings } from '@kbn/triggers-actions-ui-plugin/public/types'; import { PositiveInteger } from '@kbn/securitysolution-io-ts-types'; import type { WarningSchema } from '../../../../common/detection_engine/schemas/response'; @@ -218,15 +219,13 @@ export interface FetchRulesProps { signal?: AbortSignal; } -export interface RuleSnoozeSettings { - id: string; - muteAll: boolean; - snoozeSchedule?: RuleSnooze; - activeSnoozes?: string[]; - isSnoozedUntil?: Date; -} +// Rule snooze settings map keyed by rule SO's id (not ruleId) and valued by rule snooze settings +export type RulesSnoozeSettingsMap = Record; interface RuleSnoozeSettingsResponse { + /** + * Rule's SO id + */ id: string; mute_all: boolean; snooze_schedule?: RuleSnooze; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/rules_table_context.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/rules_table_context.test.tsx index abc384cea3bfb..ad9ea30ed9f4d 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/rules_table_context.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/rules_table_context.test.tsx @@ -5,11 +5,11 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; -import type { PropsWithChildren } from 'react'; import React from 'react'; +import type { PropsWithChildren } from 'react'; +import { renderHook } from '@testing-library/react-hooks'; import { useUiSetting$ } from '../../../../../common/lib/kibana'; -import type { Rule, RuleSnoozeSettings } from '../../../../rule_management/logic/types'; +import type { Rule, RulesSnoozeSettingsMap } from '../../../../rule_management/logic'; import { useFindRules } from '../../../../rule_management/logic/use_find_rules'; import { useFetchRulesSnoozeSettings } from '../../../../rule_management/api/hooks/use_fetch_rules_snooze_settings'; import type { RulesTableState } from './rules_table_context'; @@ -34,7 +34,7 @@ function renderUseRulesTableContext({ savedState, }: { rules?: Rule[] | Error; - rulesSnoozeSettings?: RuleSnoozeSettings[] | Error; + rulesSnoozeSettings?: RulesSnoozeSettingsMap | Error; savedState?: ReturnType; }): RulesTableState { (useFindRules as jest.Mock).mockReturnValue({ @@ -189,10 +189,10 @@ describe('RulesTableContextProvider', () => { { id: '1', name: 'rule 1' }, { id: '2', name: 'rule 2' }, ] as Rule[], - rulesSnoozeSettings: [ - { id: '1', muteAll: true, snoozeSchedule: [] }, - { id: '2', muteAll: false, snoozeSchedule: [] }, - ], + rulesSnoozeSettings: { + '1': { muteAll: true, snoozeSchedule: [] }, + '2': { muteAll: false, snoozeSchedule: [] }, + }, }); expect(state.rules).toEqual([ @@ -215,20 +215,18 @@ describe('RulesTableContextProvider', () => { { id: '1', name: 'rule 1' }, { id: '2', name: 'rule 2' }, ] as Rule[], - rulesSnoozeSettings: [ - { id: '1', muteAll: true, snoozeSchedule: [] }, - { id: '2', muteAll: false, snoozeSchedule: [] }, - ], + rulesSnoozeSettings: { + '1': { muteAll: true, snoozeSchedule: [] }, + '2': { muteAll: false, snoozeSchedule: [] }, + }, }); expect(state.rulesSnoozeSettings.data).toEqual({ '1': { - id: '1', muteAll: true, snoozeSchedule: [], }, '2': { - id: '2', muteAll: false, snoozeSchedule: [], }, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/rules_table_context.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/rules_table_context.tsx index 938174d0c567d..0c5550a8c66dc 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/rules_table_context.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/rules_table_context.tsx @@ -25,7 +25,7 @@ import type { FilterOptions, PaginationOptions, Rule, - RuleSnoozeSettings, + RulesSnoozeSettingsMap, SortingOptions, } from '../../../../rule_management/logic/types'; import { useFindRules } from '../../../../rule_management/logic/use_find_rules'; @@ -39,11 +39,11 @@ import { import { RuleSource } from './rules_table_saved_state'; import { useRulesTableSavedState } from './use_rules_table_saved_state'; -interface RulesSnoozeSettings { +interface RulesSnoozeSettingsState { /** * A map object using rule SO's id (not ruleId) as keys and snooze settings as values */ - data: Record; + data: RulesSnoozeSettingsMap; /** * Sets to true during the first data loading */ @@ -127,7 +127,7 @@ export interface RulesTableState { /** * Rules snooze settings for the current rules */ - rulesSnoozeSettings: RulesSnoozeSettings; + rulesSnoozeSettings: RulesSnoozeSettingsState; } export type LoadingRuleAction = @@ -298,7 +298,7 @@ export const RulesTableContextProvider = ({ children }: RulesTableContextProvide // Fetch rules snooze settings const { - data: rulesSnoozeSettings, + data: rulesSnoozeSettingsMap, isLoading: isSnoozeSettingsLoading, isFetching: isSnoozeSettingsFetching, isError: isSnoozeSettingsFetchError, @@ -346,19 +346,12 @@ export const RulesTableContextProvider = ({ children }: RulesTableContextProvide ] ); - const providerValue = useMemo(() => { - const rulesSnoozeSettingsMap = - rulesSnoozeSettings?.reduce((map, snoozeSettings) => { - map[snoozeSettings.id] = snoozeSettings; - - return map; - }, {} as Record) ?? {}; - - return { + const providerValue = useMemo( + () => ({ state: { rules, rulesSnoozeSettings: { - data: rulesSnoozeSettingsMap, + data: rulesSnoozeSettingsMap ?? {}, isLoading: isSnoozeSettingsLoading, isFetching: isSnoozeSettingsFetching, isError: isSnoozeSettingsFetchError, @@ -389,32 +382,33 @@ export const RulesTableContextProvider = ({ children }: RulesTableContextProvide }), }, actions, - }; - }, [ - rules, - rulesSnoozeSettings, - isSnoozeSettingsLoading, - isSnoozeSettingsFetching, - isSnoozeSettingsFetchError, - page, - perPage, - total, - filterOptions, - isPreflightInProgress, - isActionInProgress, - isAllSelected, - isFetched, - isFetching, - isLoading, - isRefetching, - isRefreshOn, - dataUpdatedAt, - loadingRules.ids, - loadingRules.action, - selectedRuleIds, - sortingOptions, - actions, - ]); + }), + [ + rules, + rulesSnoozeSettingsMap, + isSnoozeSettingsLoading, + isSnoozeSettingsFetching, + isSnoozeSettingsFetchError, + page, + perPage, + total, + filterOptions, + isPreflightInProgress, + isActionInProgress, + isAllSelected, + isFetched, + isFetching, + isLoading, + isRefetching, + isRefreshOn, + dataUpdatedAt, + loadingRules.ids, + loadingRules.action, + selectedRuleIds, + sortingOptions, + actions, + ] + ); return {children}; }; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/chart_content.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/chart_content.tsx index 93b3f92642941..5e0077918b717 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/chart_content.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/chart_content.tsx @@ -37,7 +37,7 @@ const ChartContentComponent = ({ }: ChartContentProps) => { return isChartEmbeddablesEnabled ? ( = ({ enableIpDetailsFlyout: true, onRuleChange, }); - const { browserFields, indexPattern: indexPatterns } = useSourcererDataView(sourcererScope); + const { + browserFields, + indexPattern: indexPatterns, + runtimeMappings, + } = useSourcererDataView(sourcererScope); const license = useLicense(); const getGlobalFiltersQuerySelector = useMemo( @@ -265,23 +269,25 @@ export const AlertsTableComponent: FC = ({ columns: finalColumns, browserFields: finalBrowserFields, onUpdate: onAlertTableUpdate, + runtimeMappings, toolbarVisibility: { showColumnSelector: !isEventRenderedView, showSortSelector: !isEventRenderedView, }, }), [ - finalBoolQuery, - configId, triggersActionsUi.alertsTableConfigurationRegistry, + configId, + tableView, flyoutSize, + finalBoolQuery, gridStyle, rowHeightsOptions, finalColumns, finalBrowserFields, onAlertTableUpdate, + runtimeMappings, isEventRenderedView, - tableView, ] ); diff --git a/x-pack/plugins/security_solution/public/kubernetes/pages/constants.ts b/x-pack/plugins/security_solution/public/kubernetes/pages/constants.ts index 2435d97c148bc..4acc5bf038e75 100644 --- a/x-pack/plugins/security_solution/public/kubernetes/pages/constants.ts +++ b/x-pack/plugins/security_solution/public/kubernetes/pages/constants.ts @@ -13,7 +13,6 @@ import { COLUMN_EXECUTABLE, COLUMN_ENTRY_USER, COLUMN_INTERACTIVE, - COLUMN_ENTRY_TYPE, COLUMN_NODE, COLUMN_CONTAINER, COLUMN_POD, @@ -26,11 +25,6 @@ export const kubernetesSessionsHeaders: ColumnHeaderOptions[] = [ initialWidth: DEFAULT_DATE_COLUMN_MIN_WIDTH, display: COLUMN_SESSION_START, }, - { - columnHeaderType: defaultColumnHeaderType, - id: 'process.entry_leader.user.name', - display: COLUMN_ENTRY_USER, - }, { columnHeaderType: defaultColumnHeaderType, id: 'process.entry_leader.executable', @@ -38,13 +32,8 @@ export const kubernetesSessionsHeaders: ColumnHeaderOptions[] = [ }, { columnHeaderType: defaultColumnHeaderType, - id: 'cloud.instance.name', - display: COLUMN_NODE, - }, - { - columnHeaderType: defaultColumnHeaderType, - id: 'process.entry_leader.entry_meta.type', - display: COLUMN_ENTRY_TYPE, + id: 'process.entry_leader.user.id', + display: COLUMN_ENTRY_USER, }, { columnHeaderType: defaultColumnHeaderType, @@ -53,12 +42,17 @@ export const kubernetesSessionsHeaders: ColumnHeaderOptions[] = [ }, { columnHeaderType: defaultColumnHeaderType, - id: 'container.name', - display: COLUMN_CONTAINER, + id: 'cloud.instance.name', + display: COLUMN_NODE, }, { columnHeaderType: defaultColumnHeaderType, id: 'orchestrator.resource.name', display: COLUMN_POD, }, + { + columnHeaderType: defaultColumnHeaderType, + id: 'container.name', + display: COLUMN_CONTAINER, + }, ]; diff --git a/x-pack/plugins/security_solution/public/kubernetes/pages/translations.ts b/x-pack/plugins/security_solution/public/kubernetes/pages/translations.ts index 9b1024494f4f0..0a190e0a93d05 100644 --- a/x-pack/plugins/security_solution/public/kubernetes/pages/translations.ts +++ b/x-pack/plugins/security_solution/public/kubernetes/pages/translations.ts @@ -10,14 +10,14 @@ import { i18n } from '@kbn/i18n'; export const COLUMN_SESSION_START = i18n.translate( 'xpack.securitySolution.kubernetes.columnSessionStart', { - defaultMessage: 'Date connected', + defaultMessage: 'Date started', } ); export const COLUMN_EXECUTABLE = i18n.translate( 'xpack.securitySolution.kubernetes.columnExecutable', { - defaultMessage: 'Session leader', + defaultMessage: 'Executable', } ); @@ -28,21 +28,14 @@ export const COLUMN_NODE = i18n.translate('xpack.securitySolution.kubernetes.col export const COLUMN_ENTRY_USER = i18n.translate( 'xpack.securitySolution.kubernetes.columnEntryUser', { - defaultMessage: 'Session entry user', + defaultMessage: 'User ID', } ); export const COLUMN_INTERACTIVE = i18n.translate( 'xpack.securitySolution.kubernetes.columnInteractive', { - defaultMessage: 'Interactivity', - } -); - -export const COLUMN_ENTRY_TYPE = i18n.translate( - 'xpack.securitySolution.kubernetes.columnEntryType', - { - defaultMessage: 'Entry type', + defaultMessage: 'Interactive', } ); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/response_console.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/response_console.cy.ts index 882cf45465111..a2dc9cde510d0 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/response_console.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/response_console.cy.ts @@ -167,4 +167,50 @@ describe('Response console', () => { waitForCommandToBeExecuted(); }); }); + + describe('document signing', () => { + let response: IndexedFleetEndpointPolicyResponse; + let initialAgentData: Agent; + + before(() => { + getAgentByHostName(endpointHostname).then((agentData) => { + initialAgentData = agentData; + }); + + getEndpointIntegrationVersion().then((version) => + createAgentPolicyTask(version).then((data) => { + response = data; + }) + ); + }); + + after(() => { + if (initialAgentData?.policy_id) { + reassignAgentPolicy(initialAgentData.id, initialAgentData.policy_id); + } + if (response) { + cy.task('deleteIndexedFleetEndpointPolicies', response); + } + }); + + it('should fail if data tampered', () => { + waitForEndpointListPageToBeLoaded(endpointHostname); + checkEndpointListForOnlyUnIsolatedHosts(); + openResponseConsoleFromEndpointList(); + performCommandInputChecks('isolate'); + + // stop host so that we ensure tamper happens before endpoint processes the action + cy.task('stopEndpointHost'); + // get action doc before we submit command so we know when the new action doc is indexed + cy.task('getLatestActionDoc').then((previousActionDoc) => { + submitCommand(); + cy.task('tamperActionDoc', previousActionDoc); + }); + cy.task('startEndpointHost'); + + const actionValidationErrorMsg = + 'Fleet action response error: Failed to validate action signature; check Endpoint logs for details'; + cy.contains(actionValidationErrorMsg, { timeout: 120000 }).should('exist'); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/management/cypress/support/data_loaders.ts b/x-pack/plugins/security_solution/public/management/cypress/support/data_loaders.ts index bd0438542dc73..1af2aee01b4e2 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/support/data_loaders.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/support/data_loaders.ts @@ -46,8 +46,11 @@ import { indexEndpointRuleAlerts, } from '../../../../common/endpoint/data_loaders/index_endpoint_rule_alerts'; import { + startEndpointHost, createAndEnrollEndpointHost, destroyEndpointHost, + getEndpointHosts, + stopEndpointHost, } from '../../../../scripts/endpoint/common/endpoint_host_services'; /** @@ -220,5 +223,17 @@ export const dataLoadersForRealEndpoints = ( const { kbnClient } = await stackServicesPromise; return destroyEndpointHost(kbnClient, createdHost).then(() => null); }, + + stopEndpointHost: async () => { + const hosts = await getEndpointHosts(); + const hostName = hosts[0].name; + return stopEndpointHost(hostName); + }, + + startEndpointHost: async () => { + const hosts = await getEndpointHosts(); + const hostName = hosts[0].name; + return startEndpointHost(hostName); + }, }); }; diff --git a/x-pack/plugins/security_solution/public/management/cypress/support/response_actions.ts b/x-pack/plugins/security_solution/public/management/cypress/support/response_actions.ts new file mode 100644 index 0000000000000..f6fbbf7797f90 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/cypress/support/response_actions.ts @@ -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 { get } from 'lodash'; + +import { + getLatestActionDoc, + updateActionDoc, + waitForNewActionDoc, +} from '../../../../scripts/endpoint/common/response_actions'; +import { createRuntimeServices } from '../../../../scripts/endpoint/common/stack_services'; + +export const responseActionTasks = ( + on: Cypress.PluginEvents, + config: Cypress.PluginConfigOptions +): void => { + const stackServicesPromise = createRuntimeServices({ + kibanaUrl: config.env.KIBANA_URL, + elasticsearchUrl: config.env.ELASTICSEARCH_URL, + username: config.env.ELASTICSEARCH_USERNAME, + password: config.env.ELASTICSEARCH_PASSWORD, + asSuperuser: true, + }); + + on('task', { + getLatestActionDoc: async () => { + const { esClient } = await stackServicesPromise; + // cypress doesn't like resolved undefined values + return getLatestActionDoc(esClient).then((doc) => doc || null); + }, + + // previousActionDoc is used to determine when a new action doc is received + tamperActionDoc: async (previousActionDoc) => { + const { esClient } = await stackServicesPromise; + const newActionDoc = await waitForNewActionDoc(esClient, previousActionDoc); + + if (!newActionDoc) { + throw new Error('no action doc found'); + } + + const signed = get(newActionDoc, '_source.signed'); + const signedDataBuffer = Buffer.from(signed.data, 'base64'); + const signedDataJson = JSON.parse(signedDataBuffer.toString()); + const tamperedAgentsList = [...signedDataJson.agents, 'anotheragent']; + const tamperedData = { + ...signedDataJson, + agents: tamperedAgentsList, + }; + const tamperedDataString = Buffer.from(JSON.stringify(tamperedData), 'utf8').toString( + 'base64' + ); + const tamperedDoc = { + signed: { + ...signed, + data: tamperedDataString, + }, + }; + return updateActionDoc(esClient, newActionDoc._id, tamperedDoc); + }, + }); +}; diff --git a/x-pack/plugins/security_solution/public/management/cypress/tasks/response_actions.ts b/x-pack/plugins/security_solution/public/management/cypress/tasks/response_actions.ts index 13829f8d3378c..54a33ac863722 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/tasks/response_actions.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/tasks/response_actions.ts @@ -49,7 +49,17 @@ export const fillUpNewRule = (name = 'Test', description = 'Test') => { }; export const visitRuleActions = (ruleId: string) => { cy.visit(`app/security/rules/id/${ruleId}/edit`); - cy.getByTestSubj('edit-rule-actions-tab').wait(500).click(); + cy.getByTestSubj('edit-rule-actions-tab').should('exist'); + // strange rerendering behaviour. the following make sure the test doesn't fail + cy.get('body').then(($body) => { + if ($body.find('[data-test-subj="globalLoadingIndicator"]').length) { + cy.getByTestSubj('globalLoadingIndicator').should('exist'); + cy.getByTestSubj('globalLoadingIndicator').should('not.exist'); + } + cy.getByTestSubj('globalLoadingIndicator').should('not.exist'); + }); + + cy.getByTestSubj('edit-rule-actions-tab').click(); }; export const tryAddingDisabledResponseAction = (itemNumber = 0) => { cy.getByTestSubj('response-actions-wrapper').within(() => { diff --git a/x-pack/plugins/security_solution/public/management/cypress_endpoint.config.ts b/x-pack/plugins/security_solution/public/management/cypress_endpoint.config.ts index 50a9d8f1f5356..c707453ec4c99 100644 --- a/x-pack/plugins/security_solution/public/management/cypress_endpoint.config.ts +++ b/x-pack/plugins/security_solution/public/management/cypress_endpoint.config.ts @@ -8,6 +8,8 @@ import { defineCypressConfig } from '@kbn/cypress-config'; // eslint-disable-next-line @kbn/imports/no_boundary_crossing import { dataLoaders, dataLoadersForRealEndpoints } from './cypress/support/data_loaders'; +// eslint-disable-next-line @kbn/imports/no_boundary_crossing +import { responseActionTasks } from './cypress/support/response_actions'; // eslint-disable-next-line import/no-default-export export default defineCypressConfig({ @@ -43,6 +45,7 @@ export default defineCypressConfig({ dataLoaders(on, config); // Data loaders specific to "real" Endpoint testing dataLoadersForRealEndpoints(on, config); + responseActionTasks(on, config); }, }, }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx index a7368dee15ed2..60dbc4b790e41 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx @@ -116,7 +116,8 @@ describe('Timeline', () => { }; }); - describe('rendering', () => { + // FLAKY: https://github.com/elastic/kibana/issues/156797 + describe.skip('rendering', () => { let spyCombineQueries: jest.SpyInstance; beforeEach(() => { diff --git a/x-pack/plugins/security_solution/scripts/endpoint/common/endpoint_host_services.ts b/x-pack/plugins/security_solution/scripts/endpoint/common/endpoint_host_services.ts index 5b249ee238436..6add9eb06a39b 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/common/endpoint_host_services.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/common/endpoint_host_services.ts @@ -264,3 +264,18 @@ const enrollHostWithFleet = async ({ agentId: agent.id, }; }; + +export async function getEndpointHosts(): Promise< + Array<{ name: string; state: string; ipv4: string; image: string }> +> { + const output = await execa('multipass', ['list', '--format', 'json']); + return JSON.parse(output.stdout).list; +} + +export function stopEndpointHost(hostName: string) { + return execa('multipass', ['stop', hostName]); +} + +export function startEndpointHost(hostName: string) { + return execa('multipass', ['start', hostName]); +} diff --git a/x-pack/plugins/security_solution/scripts/endpoint/common/response_actions.ts b/x-pack/plugins/security_solution/scripts/endpoint/common/response_actions.ts index cac110ce0cde1..05fbd9c14695d 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/common/response_actions.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/common/response_actions.ts @@ -6,13 +6,15 @@ */ import type { Client } from '@elastic/elasticsearch'; +import type { SearchHit } from '@elastic/elasticsearch/lib/api/types'; import { basename } from 'path'; import * as cborx from 'cbor-x'; -import { AGENT_ACTIONS_RESULTS_INDEX } from '@kbn/fleet-plugin/common'; +import { AGENT_ACTIONS_INDEX, AGENT_ACTIONS_RESULTS_INDEX } from '@kbn/fleet-plugin/common'; import { FleetActionGenerator } from '../../../common/endpoint/data_generators/fleet_action_generator'; import { EndpointActionGenerator } from '../../../common/endpoint/data_generators/endpoint_action_generator'; import type { ActionDetails, + EndpointAction, EndpointActionData, EndpointActionResponse, FileUploadMetadata, @@ -309,3 +311,54 @@ const getOutputDataIfNeeded = (action: ActionDetails): ResponseOutput => { return { output: undefined }; } }; + +export async function getLatestActionDoc( + esClient: Client +): Promise | undefined> { + return ( + await esClient.search({ + index: AGENT_ACTIONS_INDEX, + ignore_unavailable: true, + query: { + match: { + type: 'INPUT_ACTION', + }, + }, + sort: { + '@timestamp': { + order: 'desc', + }, + }, + size: 1, + }) + ).hits.hits.at(0); +} + +export async function waitForNewActionDoc( + esClient: Client, + previousActionDoc?: SearchHit, + options: { + maxAttempts: number; + interval: number; + } = { maxAttempts: 3, interval: 10000 } +): Promise | undefined> { + const { maxAttempts, interval } = options; + let attempts = 1; + let latestDoc = await getLatestActionDoc(esClient); + while ((!latestDoc || latestDoc._id === previousActionDoc?._id) && attempts <= maxAttempts) { + await new Promise((res) => setTimeout(res, interval)); + latestDoc = await getLatestActionDoc(esClient); + attempts++; + } + + return latestDoc; +} + +export function updateActionDoc(esClient: Client, id: string, doc: T) { + return esClient.update({ + index: AGENT_ACTIONS_INDEX, + id, + doc, + refresh: true, + }); +} diff --git a/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts b/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts index eb04fd133a55e..73bd035aa6496 100644 --- a/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts +++ b/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts @@ -37,6 +37,7 @@ import type { EndpointAuthz } from '../../common/endpoint/types/authz'; import { calculateEndpointAuthz } from '../../common/endpoint/service/authz'; import type { FeatureUsageService } from './services/feature_usage/service'; import type { ExperimentalFeatures } from '../../common/experimental_features'; +import type { ActionCreateService } from './services'; import { doesArtifactHaveData } from './services'; import type { actionCreateService } from './services/actions'; @@ -237,7 +238,7 @@ export class EndpointAppContextService { return this.startDependencies.messageSigningService; } - public getActionCreateService(): ReturnType { + public getActionCreateService(): ActionCreateService { if (!this.startDependencies?.actionCreateService) { throw new EndpointAppContentServicesNotStartedError(); } diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/list.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/list.ts index 696fb9d654dbd..82ff69e4d4bda 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/list.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/list.ts @@ -41,17 +41,20 @@ export function registerActionListRoutes( actionListHandler(endpointContext) ) ); + // TODO: This route is a temporary solution until we decide on how RBAC should look like for Actions in Alerts - router.get( - { - path: BASE_ENDPOINT_ACTION_ALERTS_ROUTE, - validate: EndpointActionListRequestSchema, - options: { authRequired: true, tags: ['access:securitySolution'] }, - }, - withEndpointAuthz( - {}, - endpointContext.logFactory.get('endpointActionList'), - actionListHandler(endpointContext) - ) - ); + if (endpointContext.experimentalFeatures.endpointResponseActionsEnabled) { + router.get( + { + path: BASE_ENDPOINT_ACTION_ALERTS_ROUTE, + validate: EndpointActionListRequestSchema, + options: { authRequired: true, tags: ['access:securitySolution'] }, + }, + withEndpointAuthz( + {}, + endpointContext.logFactory.get('endpointActionList'), + actionListHandler(endpointContext) + ) + ); + } } diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts index 696222eab1ed7..b2bc7ea008b10 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts @@ -132,11 +132,7 @@ describe('Response actions', () => { endpointAppContextService.setup(createMockEndpointAppContextServiceSetupContract()); endpointAppContextService.start({ ...startContract, - actionCreateService: actionCreateService( - mockScopedClient.asInternalUser, - endpointContext, - licenseService - ), + actionCreateService: actionCreateService(mockScopedClient.asInternalUser, endpointContext), licenseService, }); diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/create/index.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/create/index.ts index 1b8629c0ca1d9..8ab1ec7994381 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/create/index.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/create/index.ts @@ -68,12 +68,19 @@ interface CreateActionMetadata { enableActionsWithErrors?: boolean; } +export interface ActionCreateService { + createActionFromAlert: (payload: CreateActionPayload) => Promise; + createAction: ( + payload: CreateActionPayload, + metadata: CreateActionMetadata + ) => Promise; +} + export const actionCreateService = ( esClient: ElasticsearchClient, - endpointContext: EndpointAppContext, - licenseService: LicenseService -) => { - const createActionFromAlert = async (payload: CreateActionPayload) => { + endpointContext: EndpointAppContext +): ActionCreateService => { + const createActionFromAlert = async (payload: CreateActionPayload): Promise => { return createAction({ ...payload }, { minimumLicenseRequired: 'enterprise' }); }; @@ -86,6 +93,8 @@ export const actionCreateService = ( endpointContext.service.getFeatureUsageService().notifyUsage(featureKey); } + const licenseService = endpointContext.service.getLicenseService(); + const logger = endpointContext.logFactory.get('hostIsolation'); // fetch the Agent IDs to send the commands to @@ -341,11 +350,12 @@ interface CheckForAlertsArgs { licenseService: LicenseService; minimumLicenseRequired: LicenseType; } + const checkForAlertErrors = ({ agents, licenseService, minimumLicenseRequired = 'basic', -}: CheckForAlertsArgs) => { +}: CheckForAlertsArgs): string | undefined => { const licenseError = validateEndpointLicense(licenseService, minimumLicenseRequired); const agentsError = validateAgents(agents); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/osquery_response_action.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/osquery_response_action.ts index 3312c0b42b10f..d9ef5ed1574c0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/osquery_response_action.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/osquery_response_action.ts @@ -13,7 +13,7 @@ import type { AlertsWithAgentType } from './types'; export const osqueryResponseAction = ( responseAction: RuleResponseOsqueryAction, - osqueryCreateAction: SetupPlugins['osquery']['osqueryCreateAction'], + osqueryCreateActionService: SetupPlugins['osquery']['createActionService'], { alerts, alertIds, agentIds }: AlertsWithAgentType ) => { const temporaryQueries = responseAction.params.queries?.length @@ -27,7 +27,7 @@ export const osqueryResponseAction = ( const { savedQueryId, packId, queries, ecsMapping, ...rest } = responseAction.params; if (!containsDynamicQueries) { - return osqueryCreateAction({ + return osqueryCreateActionService.create({ ...rest, queries, ecs_mapping: ecsMapping, @@ -37,7 +37,7 @@ export const osqueryResponseAction = ( }); } each(alerts, (alert) => { - return osqueryCreateAction( + return osqueryCreateActionService.create( { ...rest, queries, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/schedule_notification_response_actions.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/schedule_notification_response_actions.test.ts index d41e3a374ba75..0c59d720c4af2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/schedule_notification_response_actions.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/schedule_notification_response_actions.test.ts @@ -53,11 +53,14 @@ describe('ScheduleNotificationResponseActions', () => { saved_query_id: undefined, ecs_mapping: { testField: { field: 'testField', value: 'testValue' } }, }; - const osqueryActionMock = jest.fn(); + const osqueryActionMock = { + create: jest.fn(), + stop: jest.fn(), + }; const endpointActionMock = jest.fn(); const scheduleNotificationResponseActions = getScheduleNotificationResponseActionsService({ - osqueryCreateAction: osqueryActionMock, + osqueryCreateActionService: osqueryActionMock, endpointAppContextService: endpointActionMock as never, }); @@ -74,7 +77,7 @@ describe('ScheduleNotificationResponseActions', () => { ]; scheduleNotificationResponseActions({ signals, responseActions }); - expect(osqueryActionMock).toHaveBeenCalledWith({ + expect(osqueryActionMock.create).toHaveBeenCalledWith({ ...defaultQueryResultParams, query: simpleQuery, }); @@ -99,7 +102,7 @@ describe('ScheduleNotificationResponseActions', () => { ]; scheduleNotificationResponseActions({ signals, responseActions }); - expect(osqueryActionMock).toHaveBeenCalledWith({ + expect(osqueryActionMock.create).toHaveBeenCalledWith({ ...defaultPackResultParams, queries: [{ ...defaultQueries, id: 'query-1', query: simpleQuery }], }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/schedule_notification_response_actions.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/schedule_notification_response_actions.ts index 7db003c3b331b..da1bdf7022c37 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/schedule_notification_response_actions.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/schedule_notification_response_actions.ts @@ -20,12 +20,12 @@ type Alerts = Array; interface ScheduleNotificationResponseActionsService { endpointAppContextService: EndpointAppContextService; - osqueryCreateAction: SetupPlugins['osquery']['osqueryCreateAction']; + osqueryCreateActionService?: SetupPlugins['osquery']['createActionService']; } export const getScheduleNotificationResponseActionsService = ({ - osqueryCreateAction, + osqueryCreateActionService, endpointAppContextService, }: ScheduleNotificationResponseActionsService) => ({ signals, responseActions }: ScheduleNotificationActions) => { @@ -48,8 +48,11 @@ export const getScheduleNotificationResponseActionsService = ); each(responseActions, (responseAction) => { - if (responseAction.actionTypeId === RESPONSE_ACTION_TYPES.OSQUERY && osqueryCreateAction) { - osqueryResponseAction(responseAction, osqueryCreateAction, { + if ( + responseAction.actionTypeId === RESPONSE_ACTION_TYPES.OSQUERY && + osqueryCreateActionService + ) { + osqueryResponseAction(responseAction, osqueryCreateActionService, { alerts, alertIds, agentIds, diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index ce979737a98bf..b25b58c0045c2 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -57,7 +57,7 @@ import { registerEndpointRoutes } from './endpoint/routes/metadata'; import { registerPolicyRoutes } from './endpoint/routes/policy'; import { registerActionRoutes } from './endpoint/routes/actions'; import { registerEndpointSuggestionsRoutes } from './endpoint/routes/suggestions'; -import { actionCreateService, EndpointArtifactClient, ManifestManager } from './endpoint/services'; +import { EndpointArtifactClient, ManifestManager } from './endpoint/services'; import { EndpointAppContextService } from './endpoint/endpoint_app_context_services'; import type { EndpointAppContext } from './endpoint/types'; import { initUsageCollectors } from './usage'; @@ -101,6 +101,7 @@ import type { } from './plugin_contract'; import { EndpointFleetServicesFactory } from './endpoint/services/fleet'; import { featureUsageService } from './endpoint/services/feature_usage'; +import { actionCreateService } from './endpoint/services/actions'; import { setIsElasticCloudDeployment } from './lib/telemetry/helpers'; import { artifactService } from './lib/telemetry/artifact'; import { endpointFieldsProvider } from './search_strategy/endpoint_fields'; @@ -246,7 +247,7 @@ export class Plugin implements ISecuritySolutionPlugin { const queryRuleAdditionalOptions: CreateQueryRuleAdditionalOptions = { scheduleNotificationResponseActionsService: getScheduleNotificationResponseActionsService({ endpointAppContextService: this.endpointAppContextService, - osqueryCreateAction: plugins.osquery.osqueryCreateAction, + osqueryCreateActionService: plugins.osquery.createActionService, }), }; @@ -505,8 +506,7 @@ export class Plugin implements ISecuritySolutionPlugin { messageSigningService: plugins.fleet?.messageSigningService, actionCreateService: actionCreateService( core.elasticsearch.client.asInternalUser, - this.endpointContext, - licenseService + this.endpointContext ), }); diff --git a/x-pack/plugins/synthetics/common/formatters/browser/formatters.ts b/x-pack/plugins/synthetics/common/formatters/browser/formatters.ts index 9dfa027767851..22ce128fc2ab0 100644 --- a/x-pack/plugins/synthetics/common/formatters/browser/formatters.ts +++ b/x-pack/plugins/synthetics/common/formatters/browser/formatters.ts @@ -38,9 +38,9 @@ export const browserFormatters: BrowserFormatMap = { [ConfigKey.SCREENSHOTS]: null, [ConfigKey.IGNORE_HTTPS_ERRORS]: null, [ConfigKey.PLAYWRIGHT_OPTIONS]: null, - [ConfigKey.TEXT_ASSERTION]: null, - [ConfigKey.PORT]: null, - [ConfigKey.URLS]: null, + [ConfigKey.TEXT_ASSERTION]: stringToJsonFormatter, + [ConfigKey.PORT]: stringToJsonFormatter, + [ConfigKey.URLS]: stringToJsonFormatter, [ConfigKey.METADATA]: objectToJsonFormatter, [ConfigKey.SOURCE_INLINE]: stringToJsonFormatter, [ConfigKey.SYNTHETICS_ARGS]: arrayToJsonFormatter, diff --git a/x-pack/plugins/synthetics/common/formatters/common/formatters.ts b/x-pack/plugins/synthetics/common/formatters/common/formatters.ts index 3122bcdb46442..4db075420f859 100644 --- a/x-pack/plugins/synthetics/common/formatters/common/formatters.ts +++ b/x-pack/plugins/synthetics/common/formatters/common/formatters.ts @@ -6,30 +6,30 @@ */ import { CommonFields, ConfigKey, SourceType } from '../../runtime_types/monitor_management'; -import { arrayToJsonFormatter, FormatterFn } from '../formatting_utils'; +import { arrayToJsonFormatter, stringToJsonFormatter, FormatterFn } from '../formatting_utils'; export type Formatter = null | FormatterFn; export type CommonFormatMap = Record; export const commonFormatters: CommonFormatMap = { - [ConfigKey.APM_SERVICE_NAME]: null, - [ConfigKey.NAME]: null, + [ConfigKey.APM_SERVICE_NAME]: stringToJsonFormatter, + [ConfigKey.NAME]: stringToJsonFormatter, [ConfigKey.LOCATIONS]: null, [ConfigKey.MONITOR_TYPE]: null, [ConfigKey.ENABLED]: null, [ConfigKey.ALERT_CONFIG]: null, [ConfigKey.CONFIG_ID]: null, - [ConfigKey.NAMESPACE]: null, + [ConfigKey.NAMESPACE]: stringToJsonFormatter, [ConfigKey.REVISION]: null, [ConfigKey.MONITOR_SOURCE_TYPE]: null, [ConfigKey.FORM_MONITOR_TYPE]: null, - [ConfigKey.JOURNEY_ID]: null, - [ConfigKey.PROJECT_ID]: null, - [ConfigKey.CUSTOM_HEARTBEAT_ID]: null, - [ConfigKey.ORIGINAL_SPACE]: null, + [ConfigKey.JOURNEY_ID]: stringToJsonFormatter, + [ConfigKey.PROJECT_ID]: stringToJsonFormatter, + [ConfigKey.CUSTOM_HEARTBEAT_ID]: stringToJsonFormatter, + [ConfigKey.ORIGINAL_SPACE]: stringToJsonFormatter, [ConfigKey.CONFIG_HASH]: null, - [ConfigKey.MONITOR_QUERY_ID]: null, + [ConfigKey.MONITOR_QUERY_ID]: stringToJsonFormatter, [ConfigKey.SCHEDULE]: (fields) => JSON.stringify( `@every ${fields[ConfigKey.SCHEDULE]?.number}${fields[ConfigKey.SCHEDULE]?.unit}` diff --git a/x-pack/plugins/synthetics/common/formatters/format_synthetics_policy.test.ts b/x-pack/plugins/synthetics/common/formatters/format_synthetics_policy.test.ts index 2706972456acd..7c045f807e4ca 100644 --- a/x-pack/plugins/synthetics/common/formatters/format_synthetics_policy.test.ts +++ b/x-pack/plugins/synthetics/common/formatters/format_synthetics_policy.test.ts @@ -354,7 +354,7 @@ describe('formatSyntheticsPolicy', () => { }, id: { type: 'text', - value: '00bb3ceb-a242-4c7a-8405-8da963661374', + value: '"00bb3ceb-a242-4c7a-8405-8da963661374"', }, ignore_https_errors: { type: 'bool', @@ -372,7 +372,7 @@ describe('formatSyntheticsPolicy', () => { }, name: { type: 'text', - value: 'Test HTTP Monitor 03', + value: '"Test HTTP Monitor 03"', }, origin: { type: 'text', @@ -401,7 +401,7 @@ describe('formatSyntheticsPolicy', () => { }, 'service.name': { type: 'text', - value: '', + value: '"Local Service"', }, 'source.inline.script': { type: 'yaml', @@ -532,7 +532,7 @@ describe('formatSyntheticsPolicy', () => { }, id: { type: 'text', - value: '51ccd9d9-fc3f-4718-ba9d-b6ef80e73fc5', + value: '"51ccd9d9-fc3f-4718-ba9d-b6ef80e73fc5"', }, location_name: { type: 'text', @@ -550,7 +550,7 @@ describe('formatSyntheticsPolicy', () => { }, name: { type: 'text', - value: 'Test Monitor', + value: '"Test Monitor"', }, origin: { type: 'text', @@ -558,11 +558,11 @@ describe('formatSyntheticsPolicy', () => { }, password: { type: 'password', - value: 'changeme', + value: '"changeme"', }, proxy_url: { type: 'text', - value: 'https://proxy.com', + value: '"https://proxy.com"', }, 'response.include_body': { type: 'text', @@ -582,7 +582,7 @@ describe('formatSyntheticsPolicy', () => { }, 'service.name': { type: 'text', - value: 'LocalService', + value: '"LocalService"', }, 'ssl.certificate': { type: 'yaml', @@ -622,11 +622,11 @@ describe('formatSyntheticsPolicy', () => { }, urls: { type: 'text', - value: 'https://www.google.com', + value: '"https://www.google.com"', }, username: { type: 'text', - value: '', + value: '"admin"', }, }, }, @@ -1110,7 +1110,7 @@ const browserConfig: any = { enabled: true, alert: { status: { enabled: true } }, schedule: { number: '3', unit: 'm' }, - 'service.name': '', + 'service.name': 'Local Service', config_id: '00bb3ceb-a242-4c7a-8405-8da963661374', tags: ['cookie-test', 'browser'], timeout: '16', @@ -1198,7 +1198,7 @@ const httpPolicy: any = { 'check.request.body': { type: 'text', value: '' }, 'check.request.headers': {}, 'check.request.method': 'GET', - username: '', + username: 'admin', 'ssl.certificate_authorities': '', 'ssl.certificate': '', 'ssl.key': '', diff --git a/x-pack/plugins/synthetics/common/formatters/formatting_utils.ts b/x-pack/plugins/synthetics/common/formatters/formatting_utils.ts index 9013496b96eeb..2c69f551cbf7b 100644 --- a/x-pack/plugins/synthetics/common/formatters/formatting_utils.ts +++ b/x-pack/plugins/synthetics/common/formatters/formatting_utils.ts @@ -61,6 +61,15 @@ export const stringToJsonFormatter: FormatterFn = (fields, key) => { return value ? JSON.stringify(value) : null; }; +export const stringifyString = (value?: string) => { + if (!value) return value; + try { + return JSON.stringify(value); + } catch (e) { + return value; + } +}; + export const replaceStringWithParams = ( value: string | boolean | {} | [], params: Record, diff --git a/x-pack/plugins/synthetics/common/formatters/http/formatters.ts b/x-pack/plugins/synthetics/common/formatters/http/formatters.ts index 437112939f283..f485159d34596 100644 --- a/x-pack/plugins/synthetics/common/formatters/http/formatters.ts +++ b/x-pack/plugins/synthetics/common/formatters/http/formatters.ts @@ -9,7 +9,11 @@ import { tlsFormatters } from '../tls/formatters'; import { HTTPFields, ConfigKey } from '../../runtime_types/monitor_management'; import { Formatter, commonFormatters } from '../common/formatters'; -import { arrayToJsonFormatter, objectToJsonFormatter } from '../formatting_utils'; +import { + stringToJsonFormatter, + arrayToJsonFormatter, + objectToJsonFormatter, +} from '../formatting_utils'; export type HTTPFormatMap = Record; @@ -19,12 +23,12 @@ export const httpFormatters: HTTPFormatMap = { [ConfigKey.RESPONSE_BODY_INDEX]: null, [ConfigKey.RESPONSE_HEADERS_INDEX]: null, [ConfigKey.METADATA]: objectToJsonFormatter, - [ConfigKey.URLS]: null, - [ConfigKey.USERNAME]: null, - [ConfigKey.PASSWORD]: null, - [ConfigKey.PROXY_URL]: null, + [ConfigKey.URLS]: stringToJsonFormatter, + [ConfigKey.USERNAME]: stringToJsonFormatter, + [ConfigKey.PASSWORD]: stringToJsonFormatter, + [ConfigKey.PROXY_URL]: stringToJsonFormatter, [ConfigKey.PROXY_HEADERS]: objectToJsonFormatter, - [ConfigKey.PORT]: null, + [ConfigKey.PORT]: stringToJsonFormatter, [ConfigKey.RESPONSE_BODY_CHECK_NEGATIVE]: arrayToJsonFormatter, [ConfigKey.RESPONSE_BODY_CHECK_POSITIVE]: arrayToJsonFormatter, [ConfigKey.RESPONSE_JSON_CHECK]: arrayToJsonFormatter, diff --git a/x-pack/plugins/synthetics/common/formatters/icmp/formatters.ts b/x-pack/plugins/synthetics/common/formatters/icmp/formatters.ts index f58e15c86b3ad..acdccebfb4b57 100644 --- a/x-pack/plugins/synthetics/common/formatters/icmp/formatters.ts +++ b/x-pack/plugins/synthetics/common/formatters/icmp/formatters.ts @@ -9,11 +9,12 @@ import { secondsToCronFormatter } from '../formatting_utils'; import { ICMPFields, ConfigKey } from '../../runtime_types/monitor_management'; import { Formatter, commonFormatters } from '../common/formatters'; +import { stringToJsonFormatter } from '../formatting_utils'; export type ICMPFormatMap = Record; export const icmpFormatters: ICMPFormatMap = { - [ConfigKey.HOSTS]: null, + [ConfigKey.HOSTS]: stringToJsonFormatter, [ConfigKey.WAIT]: secondsToCronFormatter, [ConfigKey.MODE]: null, [ConfigKey.IPV4]: null, diff --git a/x-pack/plugins/synthetics/common/formatters/tcp/formatters.ts b/x-pack/plugins/synthetics/common/formatters/tcp/formatters.ts index 2d850e95ceaf1..a3127c1cb49b4 100644 --- a/x-pack/plugins/synthetics/common/formatters/tcp/formatters.ts +++ b/x-pack/plugins/synthetics/common/formatters/tcp/formatters.ts @@ -10,19 +10,19 @@ import { TCPFields, ConfigKey } from '../../runtime_types/monitor_management'; import { Formatter, commonFormatters } from '../common/formatters'; import { objectToJsonFormatter } from '../formatting_utils'; import { tlsFormatters } from '../tls/formatters'; +import { stringToJsonFormatter } from '../formatting_utils'; export type TCPFormatMap = Record; export const tcpFormatters: TCPFormatMap = { [ConfigKey.METADATA]: objectToJsonFormatter, - [ConfigKey.HOSTS]: null, + [ConfigKey.HOSTS]: stringToJsonFormatter, [ConfigKey.PROXY_USE_LOCAL_RESOLVER]: null, - [ConfigKey.RESPONSE_RECEIVE_CHECK]: null, - [ConfigKey.REQUEST_SEND_CHECK]: null, - [ConfigKey.PROXY_URL]: null, - [ConfigKey.PROXY_URL]: null, - [ConfigKey.PORT]: null, - [ConfigKey.URLS]: null, + [ConfigKey.RESPONSE_RECEIVE_CHECK]: stringToJsonFormatter, + [ConfigKey.REQUEST_SEND_CHECK]: stringToJsonFormatter, + [ConfigKey.PROXY_URL]: stringToJsonFormatter, + [ConfigKey.PORT]: stringToJsonFormatter, + [ConfigKey.URLS]: stringToJsonFormatter, [ConfigKey.MODE]: null, [ConfigKey.IPV4]: null, [ConfigKey.IPV6]: null, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/data_retention.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/data_retention.tsx index 8f998217117ed..851b64bbb7094 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/data_retention.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/data_retention.tsx @@ -6,14 +6,29 @@ */ import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiBasicTable, EuiCallOut, EuiLink, EuiSpacer } from '@elastic/eui'; +import { + EuiBasicTable, + EuiCallOut, + EuiEmptyPrompt, + EuiIcon, + EuiLink, + EuiMarkdownFormat, + EuiSpacer, +} from '@elastic/eui'; import React from 'react'; import { i18n } from '@kbn/i18n'; -import { PolicyLink } from './policy_link'; +import { css } from '@emotion/react'; +import { IHttpFetchError, ResponseErrorBody } from '@kbn/core-http-browser'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { PolicyLink, PolicyNameLabel } from './policy_link'; import { useGetIlmPolicies } from './hooks/use_get_ilm_policies'; export const DataRetentionTab = () => { - const { data, loading } = useGetIlmPolicies(); + const { data, loading, error } = useGetIlmPolicies(); + + if (error && (error as unknown as IHttpFetchError).body?.statusCode === 403) { + return ; + } const columns = [ { @@ -36,9 +51,7 @@ export const DataRetentionTab = () => { }, { field: 'policy.name', - name: i18n.translate('xpack.synthetics.settingsRoute.table.policy', { - defaultMessage: 'Policy', - }), + name: , render: (name: string, _item: typeof data[0]) => , }, ]; @@ -76,6 +89,65 @@ export const DataRetentionTab = () => { ); }; +const Unprivileged = () => { + const { + services: { docLinks }, + } = useKibana(); + return ( + } + title={ +

    + +

    + } + body={ +

    + + {i18n.translate('xpack.synthetics.monitorManagement.projectDelete.docsLink', { + defaultMessage: 'Learn more', + })} + + ), + }} + /> +

    + } + footer={ + + } + /> + ); +}; + +const INDEX_PRIVILEGES = i18n.translate('xpack.synthetics.dataRetention.unprivileged.index', { + defaultMessage: '`read`, `monitor` on the following Elasticsearch indices: `synthetics-*`', +}); + +const CLUSTER_PRIVILEGES = i18n.translate('xpack.synthetics.dataRetention.unprivileged.cluster', { + defaultMessage: + '`read_ilm`, `monitor` to view and `manage_ilm` to manage ILM policies on the Elasticsearch cluster.', +}); + const CALLOUT_TITLE = i18n.translate('xpack.synthetics.settingsRoute.retentionCalloutTitle', { defaultMessage: 'Synthetics data is configured by managed index lifecycle policies', }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/hooks/use_get_ilm_policies.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/hooks/use_get_ilm_policies.ts index bae80b567d339..0af2c3b0d4337 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/hooks/use_get_ilm_policies.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/hooks/use_get_ilm_policies.ts @@ -106,7 +106,7 @@ export const useGetIlmPolicies = () => { const formatAge = (age?: string) => { if (!age) { - return ''; + return '--'; } const [value] = age.split('d'); return i18n.translate('xpack.synthetics.settingsRoute.table.retentionPeriodValue', { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/policy_link.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/policy_link.tsx index 62833e1519fe9..34bb2e40f645a 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/policy_link.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/policy_link.tsx @@ -6,15 +6,17 @@ */ import React from 'react'; -import { EuiLink, EuiLoadingContent } from '@elastic/eui'; +import { EuiIconTip, EuiLink, EuiLoadingContent, EuiToolTip, EuiText } from '@elastic/eui'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { ILM_LOCATOR_ID } from '@kbn/index-lifecycle-management-plugin/public'; import { useFetcher } from '@kbn/observability-plugin/public'; +import { i18n } from '@kbn/i18n'; import { useSyntheticsSettingsContext } from '../../contexts'; import { ClientPluginsStart } from '../../../../plugin'; export const PolicyLink = ({ name }: { name: string }) => { - const { share } = useKibana().services; + const { share, application } = useKibana().services; + const canManageILM = application.capabilities.management?.data?.index_lifecycle_management; const ilmLocator = share.url.locators.get(ILM_LOCATOR_ID); @@ -28,6 +30,18 @@ export const PolicyLink = ({ name }: { name: string }) => { return ; } + if (!name) { + return <>--; + } + + if (!canManageILM) { + return ( + + {name} + + ); + } + return ( { ); }; + +export const PolicyNameLabel = () => { + const { application } = useKibana().services; + + const canManageILM = application.capabilities.management?.data?.index_lifecycle_management; + + if (canManageILM) { + return <>{POLICY_LABEL}; + } + + return ( + <> + {POLICY_LABEL} + + ); +}; + +const POLICY_LABEL = i18n.translate('xpack.synthetics.settingsRoute.table.policy', { + defaultMessage: 'Policy', +}); + +const PERMISSIONS_NEEDED = i18n.translate('xpack.synthetics.settingsRoute.policy.manageILM', { + defaultMessage: 'You need the "manage_ilm" cluster permission to manage ILM policies.', +}); diff --git a/x-pack/plugins/synthetics/server/synthetics_service/formatters/format_configs.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/formatters/format_configs.test.ts index 921fd737e5d3e..710946e520646 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/formatters/format_configs.test.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/formatters/format_configs.test.ts @@ -119,15 +119,15 @@ describe('formatMonitorConfig', () => { enabled: true, locations: [], max_redirects: '0', - name: 'Test', - password: '3z9SBOQWW5F0UrdqLVFqlF6z', + name: '"Test"', + password: '"3z9SBOQWW5F0UrdqLVFqlF6z"', 'response.include_body': 'on_error', 'response.include_headers': true, schedule: '@every 3m', timeout: '16s', type: 'http', - urls: 'https://www.google.com', - proxy_url: 'https://www.google.com', + urls: '"https://www.google.com"', + proxy_url: '"https://www.google.com"', }); }); @@ -158,15 +158,15 @@ describe('formatMonitorConfig', () => { enabled: true, locations: [], max_redirects: '0', - name: 'Test', - password: '3z9SBOQWW5F0UrdqLVFqlF6z', - proxy_url: 'https://www.google.com', + name: '"Test"', + password: '"3z9SBOQWW5F0UrdqLVFqlF6z"', + proxy_url: '"https://www.google.com"', 'response.include_body': 'on_error', 'response.include_headers': true, schedule: '@every 3m', timeout: '16s', type: 'http', - urls: 'https://www.google.com', + urls: '"https://www.google.com"', ...(isTLSEnabled ? { 'ssl.verification_mode': 'none' } : {}), }); } @@ -183,7 +183,7 @@ describe('browser fields', () => { enabled: true, 'filter_journeys.tags': ['dev'], ignore_https_errors: false, - name: 'Test', + name: '"Test"', locations: [], schedule: '@every 3m', screenshots: 'on', diff --git a/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.test.ts index 5b0b1895de3a2..fcab7e6cb06c4 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.test.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.test.ts @@ -31,7 +31,7 @@ describe('SyntheticsPrivateLocation', () => { type: 'http', enabled: true, schedule: '@every 3m', - 'service.name': '', + 'service.name': 'test service', locations: [mockPrivateLocation], tags: [], timeout: '16', @@ -226,7 +226,7 @@ describe('SyntheticsPrivateLocation', () => { }, name: { type: 'text', - value: 'Browser monitor', + value: '"Browser monitor"', }, params: { type: 'yaml', @@ -246,7 +246,7 @@ describe('SyntheticsPrivateLocation', () => { }, 'service.name': { type: 'text', - value: '', + value: '"test service"', }, 'source.inline.script': { type: 'yaml', @@ -286,7 +286,7 @@ const dummyBrowserConfig: Partial & { type: DataStream.BROWSER, enabled: true, schedule: { unit: ScheduleUnit.MINUTES, number: '10' }, - 'service.name': '', + 'service.name': 'test service', tags: [], timeout: null, name: 'Browser monitor', diff --git a/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.ts b/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.ts index 51545433b9450..9aef89a86a4a9 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.ts @@ -9,6 +9,7 @@ import { NewPackagePolicy } from '@kbn/fleet-plugin/common'; import { NewPackagePolicyWithId } from '@kbn/fleet-plugin/server/services/package_policy'; import { cloneDeep } from 'lodash'; import { SavedObjectError } from '@kbn/core-saved-objects-common'; +import { stringifyString } from '../../../common/formatters/formatting_utils'; import { formatSyntheticsPolicy } from '../../../common/formatters/format_synthetics_policy'; import { ConfigKey, @@ -86,9 +87,9 @@ export class SyntheticsPrivateLocation { { ...(config as Partial), config_id: config.fields?.config_id, - location_name: privateLocation.label, - 'monitor.project.id': config.fields?.['monitor.project.name'], - 'monitor.project.name': config.fields?.['monitor.project.name'], + location_name: stringifyString(privateLocation.label), + 'monitor.project.id': stringifyString(config.fields?.['monitor.project.name']), + 'monitor.project.name': stringifyString(config.fields?.['monitor.project.name']), }, globalParams ); diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 533f04aab9796..3e59548b6f3c8 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -25807,7 +25807,6 @@ "xpack.osquery.liveQueryActionResults.table.expiredStatusText": "expiré", "xpack.osquery.liveQueryActionResults.table.pendingStatusText": "en attente", "xpack.osquery.liveQueryActionResults.table.resultRowsNumberColumnTitle": "Nombre de lignes de résultats", - "xpack.osquery.liveQueryActionResults.table.skippedErrorText": "Cette requête n'a pas été appelée en raison du paramètre utilisé et de sa valeur non trouvée dans l'alerte.", "xpack.osquery.liveQueryActionResults.table.skippedStatusText": "ignoré", "xpack.osquery.liveQueryActionResults.table.statusColumnTitle": "Statut", "xpack.osquery.liveQueryActionResults.table.successStatusText": "réussite", @@ -30970,7 +30969,6 @@ "xpack.securitySolution.kpiUsers.totalUsers.errorSearchDescription": "Une erreur s'est produite sur la recherche du KPI du total des utilisateurs", "xpack.securitySolution.kpiUsers.totalUsers.title": "Utilisateurs", "xpack.securitySolution.kubernetes.columnContainer": "Conteneur", - "xpack.securitySolution.kubernetes.columnEntryType": "Type d’entrée", "xpack.securitySolution.kubernetes.columnExecutable": "Leader de session", "xpack.securitySolution.kubernetes.columnInteractive": "Interactivité", "xpack.securitySolution.kubernetes.columnNode": "Nœud", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 6aabae0e6ed5a..06d5e032cd802 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -25788,7 +25788,6 @@ "xpack.osquery.liveQueryActionResults.table.expiredStatusText": "期限切れ", "xpack.osquery.liveQueryActionResults.table.pendingStatusText": "保留中", "xpack.osquery.liveQueryActionResults.table.resultRowsNumberColumnTitle": "結果行数", - "xpack.osquery.liveQueryActionResults.table.skippedErrorText": "このクエリは、使用されているパラメーターとその値がアラートで見つからないため、呼び出されていません。", "xpack.osquery.liveQueryActionResults.table.skippedStatusText": "スキップ済み", "xpack.osquery.liveQueryActionResults.table.statusColumnTitle": "ステータス", "xpack.osquery.liveQueryActionResults.table.successStatusText": "成功", @@ -30949,7 +30948,6 @@ "xpack.securitySolution.kpiUsers.totalUsers.errorSearchDescription": "統計ユーザーKPI検索でエラーが発生しました", "xpack.securitySolution.kpiUsers.totalUsers.title": "ユーザー", "xpack.securitySolution.kubernetes.columnContainer": "コンテナー", - "xpack.securitySolution.kubernetes.columnEntryType": "エントリタイプ", "xpack.securitySolution.kubernetes.columnExecutable": "セッションリーダー", "xpack.securitySolution.kubernetes.columnInteractive": "インタラクティブ", "xpack.securitySolution.kubernetes.columnNode": "ノード", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 22fe6e5ab15df..1b3c8f4fbe94e 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -25805,7 +25805,6 @@ "xpack.osquery.liveQueryActionResults.table.expiredStatusText": "已过期", "xpack.osquery.liveQueryActionResults.table.pendingStatusText": "待处理", "xpack.osquery.liveQueryActionResults.table.resultRowsNumberColumnTitle": "结果行数", - "xpack.osquery.liveQueryActionResults.table.skippedErrorText": "尚未调用此查询,因为在告警中找不到所使用的参数及其值。", "xpack.osquery.liveQueryActionResults.table.skippedStatusText": "已跳过", "xpack.osquery.liveQueryActionResults.table.statusColumnTitle": "状态", "xpack.osquery.liveQueryActionResults.table.successStatusText": "成功", @@ -30966,7 +30965,6 @@ "xpack.securitySolution.kpiUsers.totalUsers.errorSearchDescription": "搜索总体用户 KPI 时发生错误", "xpack.securitySolution.kpiUsers.totalUsers.title": "用户", "xpack.securitySolution.kubernetes.columnContainer": "容器", - "xpack.securitySolution.kubernetes.columnEntryType": "条目类型", "xpack.securitySolution.kubernetes.columnExecutable": "会话 Leader", "xpack.securitySolution.kubernetes.columnInteractive": "交互性", "xpack.securitySolution.kubernetes.columnNode": "节点", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table_state.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table_state.tsx index 44c560a732b7a..fa224b0e98b55 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table_state.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table_state.tsx @@ -16,6 +16,7 @@ import { EuiDataGridProps, EuiDataGridToolBarVisibilityOptions, } from '@elastic/eui'; +import type { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { ALERT_CASE_IDS } from '@kbn/rule-data-utils'; import type { ValidFeatureId } from '@kbn/rule-data-utils'; import type { @@ -68,6 +69,7 @@ export type AlertsTableStateProps = { showExpandToDetails: boolean; browserFields?: BrowserFields; onUpdate?: (args: TableUpdateHandlerArgs) => void; + runtimeMappings?: MappingRuntimeFields; showAlertStatusWithFlapping?: boolean; toolbarVisibility?: EuiDataGridToolBarVisibilityOptions; /** @@ -140,6 +142,7 @@ const AlertsTableStateWithQueryProvider = ({ gridStyle, browserFields: propBrowserFields, onUpdate, + runtimeMappings, showAlertStatusWithFlapping, toolbarVisibility, shouldHighlightRow, @@ -233,6 +236,7 @@ const AlertsTableStateWithQueryProvider = ({ featureIds, query, pagination, + runtimeMappings, sort, skip: false, }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_fetch_alerts.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_fetch_alerts.tsx index ab49469477ba6..b6d725fc9c39e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_fetch_alerts.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_fetch_alerts.tsx @@ -18,6 +18,7 @@ import type { RuleRegistrySearchResponse, } from '@kbn/rule-registry-plugin/common/search_strategy'; import type { + MappingRuntimeFields, QueryDslFieldAndFormat, QueryDslQueryContainer, SortCombinations, @@ -35,6 +36,7 @@ export interface FetchAlertsArgs { pageIndex: number; pageSize: number; }; + runtimeMappings?: MappingRuntimeFields; sort: SortCombinations[]; skip: boolean; } @@ -144,6 +146,7 @@ export type UseFetchAlerts = ({ fields, query, pagination, + runtimeMappings, skip, sort, }: FetchAlertsArgs) => [boolean, FetchAlertResp]; @@ -152,6 +155,7 @@ const useFetchAlerts = ({ fields, query, pagination, + runtimeMappings, skip, sort, }: FetchAlertsArgs): [boolean, FetchAlertResp] => { @@ -284,6 +288,7 @@ const useFetchAlerts = ({ fields, pagination, query, + runtimeMappings, sort, }; if ( @@ -295,7 +300,7 @@ const useFetchAlerts = ({ request: newAlertRequest, }); } - }, [featureIds, fields, pagination, query, sort]); + }, [featureIds, fields, pagination, query, sort, runtimeMappings]); useEffect(() => { if (alertRequest.featureIds.length > 0 && !deepEqual(alertRequest, prevAlertRequest.current)) { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/logs_list/components/logs_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/logs_list/components/logs_list.tsx index c4fd40d11d1dd..d51b0e8d5d169 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/logs_list/components/logs_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/logs_list/components/logs_list.tsx @@ -24,7 +24,14 @@ export const LogsList = ({ 'xl' )({ ruleId: '*', - refreshToken: 0, + refreshToken: { + resolve: () => { + /* noop */ + }, + reject: () => { + /* noop */ + }, + }, initialPageSize: 50, hasRuleNames: true, hasAllSpaceSwitch: true, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule.tsx index 8b99cc910d8da..e520d222cec16 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule.tsx @@ -17,7 +17,7 @@ import { } from '../../common/components/with_bulk_rule_api_operations'; import './rule.scss'; import type { RuleEventLogListProps } from './rule_event_log_list'; -import { AlertListItem } from './types'; +import { AlertListItem, RefreshToken } from './types'; import { getIsExperimentalFeatureEnabled } from '../../../../common/get_experimental_features'; import { suspendedComponentWithProps } from '../../../lib/suspended_component_with_props'; import { @@ -41,7 +41,7 @@ type RuleProps = { readOnly: boolean; ruleSummary: RuleSummary; requestRefresh: () => Promise; - refreshToken?: number; + refreshToken?: RefreshToken; numberOfExecutions: number; onChangeDuration: (length: number) => void; durationEpoch?: number; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_action_error_log_flyout.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_action_error_log_flyout.tsx index aa914e2818c03..04d0ca41303f9 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_action_error_log_flyout.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_action_error_log_flyout.tsx @@ -23,10 +23,11 @@ import { import { IExecutionLog } from '@kbn/alerting-plugin/common'; import { RuleErrorLogWithApi } from './rule_error_log'; import { RuleActionErrorBadge } from './rule_action_error_badge'; +import { RefreshToken } from './types'; export interface RuleActionErrorLogFlyoutProps { runLog: IExecutionLog; - refreshToken?: number; + refreshToken?: RefreshToken; onClose: () => void; activeSpaceId?: string; } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.test.tsx index d07ee7460217c..ded0898639756 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.test.tsx @@ -63,7 +63,7 @@ const mockRuleApis = { muteRule: jest.fn(), unmuteRule: jest.fn(), requestRefresh: jest.fn(), - refreshToken: Date.now(), + refreshToken: { resolve: jest.fn(), reject: jest.fn() }, snoozeRule: jest.fn(), unsnoozeRule: jest.fn(), bulkEnableRules: jest.fn(), diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.tsx index fd125851c36b3..57da8f7d37594 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.tsx @@ -68,13 +68,14 @@ import { MULTIPLE_RULE_TITLE, } from '../../rules_list/translations'; import { useBulkOperationToast } from '../../../hooks/use_bulk_operation_toast'; +import { RefreshToken } from './types'; export type RuleDetailsProps = { rule: Rule; ruleType: RuleType; actionTypes: ActionType[]; requestRefresh: () => Promise; - refreshToken?: number; + refreshToken?: RefreshToken; } & Pick< BulkOperationsComponentOpts, 'bulkDisableRules' | 'bulkEnableRules' | 'bulkDeleteRules' | 'snoozeRule' | 'unsnoozeRule' diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details_route.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details_route.tsx index 30319ce13ae15..116c97ef905d7 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details_route.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details_route.tsx @@ -6,7 +6,7 @@ */ import { i18n } from '@kbn/i18n'; -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useCallback } from 'react'; import { RouteComponentProps } from 'react-router-dom'; import { ToastsApi } from '@kbn/core/public'; import { EuiSpacer } from '@elastic/eui'; @@ -49,18 +49,38 @@ export const RuleDetailsRoute: React.FunctionComponent = const [rule, setRule] = useState(null); const [ruleType, setRuleType] = useState(null); const [actionTypes, setActionTypes] = useState(null); - const [refreshToken, requestRefresh] = React.useState(); + const [refreshToken, setRefreshToken] = useState<{ + resolve: () => void; + reject: () => void; + }>(); + const requestRefresh = useCallback( + () => + new Promise((resolve, reject) => { + setRefreshToken({ + resolve, + reject, + }); + }), + [setRefreshToken] + ); + useEffect(() => { - getRuleData( - ruleId, - loadRuleTypes, - resolveRule, - loadActionTypes, - setRule, - setRuleType, - setActionTypes, - toasts - ); + const loadData = async () => { + await getRuleData( + ruleId, + loadRuleTypes, + resolveRule, + loadActionTypes, + setRule, + setRuleType, + setActionTypes, + toasts + ); + + refreshToken?.resolve(); + }; + + loadData(); }, [ruleId, http, loadActionTypes, loadRuleTypes, resolveRule, toasts, refreshToken]); useEffect(() => { @@ -117,7 +137,7 @@ export const RuleDetailsRoute: React.FunctionComponent = rule={rule} ruleType={ruleType} actionTypes={actionTypes} - requestRefresh={async () => requestRefresh(Date.now())} + requestRefresh={requestRefresh} refreshToken={refreshToken} /> diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_error_log.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_error_log.tsx index b5940e9d4b1e3..f489a40af478d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_error_log.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_error_log.tsx @@ -30,6 +30,7 @@ import { withBulkRuleOperations, } from '../../common/components/with_bulk_rule_api_operations'; import { EventLogListCellRenderer } from '../../common/components/event_log'; +import { RefreshToken } from './types'; const getParsedDate = (date: string) => { if (date.includes('now')) { @@ -62,7 +63,7 @@ const MAX_RESULTS = 1000; export type RuleErrorLogProps = { ruleId: string; runId?: string; - refreshToken?: number; + refreshToken?: RefreshToken; spaceId?: string; logFromDifferentSpace?: boolean; requestRefresh?: () => Promise; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list.tsx index aec6566d58668..75234562096d0 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list.tsx @@ -12,6 +12,7 @@ import { RuleExecutionSummaryAndChartWithApi } from './rule_execution_summary_an import { RuleSummary, RuleType } from '../../../../types'; import { ComponentOpts as RuleApis } from '../../common/components/with_bulk_rule_api_operations'; import { RuleEventLogListTableWithApi } from './rule_event_log_list_table'; +import { RefreshToken } from './types'; const RULE_EVENT_LOG_LIST_STORAGE_KEY = 'xpack.triggersActionsUI.ruleEventLogList.initialColumns'; @@ -23,7 +24,7 @@ export interface RuleEventLogListCommonProps { ruleId: string; ruleType: RuleType; localStorageKey?: string; - refreshToken?: number; + refreshToken?: RefreshToken; requestRefresh?: () => Promise; loadExecutionLogAggregations?: RuleApis['loadExecutionLogAggregations']; fetchRuleSummary?: boolean; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_kpi.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_kpi.tsx index 7ecf22c1b593a..2f0aa0460c754 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_kpi.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_kpi.tsx @@ -17,6 +17,7 @@ import { import { getIsExperimentalFeatureEnabled } from '../../../../common/get_experimental_features'; import { useKibana } from '../../../../common/lib/kibana'; import { EventLogListStatus, EventLogStat } from '../../common/components/event_log'; +import { RefreshToken } from './types'; const getParsedDate = (date: string) => { if (date.includes('now')) { @@ -59,7 +60,7 @@ export type RuleEventLogListKPIProps = { dateEnd: string; outcomeFilter?: string[]; message?: string; - refreshToken?: number; + refreshToken?: RefreshToken; namespaces?: Array; } & Pick; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_table.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_table.tsx index 7965f58fa8420..aecab951b3b18 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_table.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_table.tsx @@ -52,6 +52,7 @@ import { } from '../../common/components/with_bulk_rule_api_operations'; import { useMultipleSpaces } from '../../../hooks/use_multiple_spaces'; import { RulesSettingsLink } from '../../../components/rules_setting/rules_settings_link'; +import { RefreshToken } from './types'; const getEmptyFunctionComponent: React.FC = ({ children }) => <>{children}; @@ -102,7 +103,7 @@ export type RuleEventLogListOptions = 'stackManagement' | 'default'; export type RuleEventLogListCommonProps = { ruleId: string; localStorageKey?: string; - refreshToken?: number; + refreshToken?: RefreshToken; initialPageSize?: number; // Duplicating these properties is extremely silly but it's the only way to get Jest to cooperate with the way this component is structured overrideLoadExecutionLogAggregations?: RuleApis['loadExecutionLogAggregations']; @@ -142,7 +143,7 @@ export const RuleEventLogListTable = ( const [search, setSearch] = useState(''); const [isFlyoutOpen, setIsFlyoutOpen] = useState(false); const [selectedRunLog, setSelectedRunLog] = useState(); - const [internalRefreshToken, setInternalRefreshToken] = useState( + const [internalRefreshToken, setInternalRefreshToken] = useState( refreshToken ); const [showFromAllSpaces, setShowFromAllSpaces] = useState(false); @@ -298,7 +299,14 @@ export const RuleEventLogListTable = ( ); const onRefresh = () => { - setInternalRefreshToken(Date.now()); + setInternalRefreshToken({ + resolve: () => { + /* noop */ + }, + reject: () => { + /* noop */ + }, + }); loadEventLogs(); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_execution_summary_and_chart.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_execution_summary_and_chart.tsx index c3fb3abf58d0f..f3e7409b66d2b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_execution_summary_and_chart.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_execution_summary_and_chart.tsx @@ -20,6 +20,7 @@ import { ComponentOpts as RuleApis, withBulkRuleOperations, } from '../../common/components/with_bulk_rule_api_operations'; +import { RefreshToken } from './types'; export const DEFAULT_NUMBER_OF_EXECUTIONS = 60; @@ -29,7 +30,7 @@ type RuleExecutionSummaryAndChartProps = { ruleSummary?: RuleSummary; numberOfExecutions?: number; isLoadingRuleSummary?: boolean; - refreshToken?: number; + refreshToken?: RefreshToken; onChangeDuration?: (duration: number) => void; requestRefresh?: () => Promise; fetchRuleSummary?: boolean; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_route.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_route.tsx index 7b72f34d05bac..f1df6af277549 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_route.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_route.tsx @@ -16,13 +16,14 @@ import { import { RuleWithApi as Rules } from './rule'; import { useKibana } from '../../../../common/lib/kibana'; import { CenterJustifiedSpinner } from '../../../components/center_justified_spinner'; +import { RefreshToken } from './types'; type WithRuleSummaryProps = { rule: Rule; ruleType: RuleType; readOnly: boolean; requestRefresh: () => Promise; - refreshToken?: number; + refreshToken?: RefreshToken; } & Pick; export const RuleRoute: React.FunctionComponent = ({ diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_status_panel.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_status_panel.tsx index ae5e4251b87e2..3c02b7bcdbc9b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_status_panel.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_status_panel.tsx @@ -58,12 +58,8 @@ export const RuleStatusPanel: React.FC = ({ statusMessage, loadExecutionLogAggregations, }) => { - const [isSnoozeLoading, setIsSnoozeLoading] = useState(false); - const [isSnoozeOpen, setIsSnoozeOpen] = useState(false); const [lastNumberOfExecutions, setLastNumberOfExecutions] = useState(null); - const openSnooze = useCallback(() => setIsSnoozeOpen(true), [setIsSnoozeOpen]); - const closeSnooze = useCallback(() => setIsSnoozeOpen(false), [setIsSnoozeOpen]); const onSnoozeRule = useCallback( (snoozeSchedule) => snoozeRule(rule, snoozeSchedule), [rule, snoozeRule] @@ -187,12 +183,9 @@ export const RuleStatusPanel: React.FC = ({ void; + reject: () => void; +} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/collapsed_item_actions.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/collapsed_item_actions.tsx index ba36bea2eac9d..7df59161dffeb 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/collapsed_item_actions.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/collapsed_item_actions.tsx @@ -83,7 +83,7 @@ export const CollapsedItemActions: React.FunctionComponent = ({ try { onLoading(true); await snoozeRule(item, snoozeSchedule); - onRuleChanged(); + await onRuleChanged(); toasts.addSuccess(SNOOZE_SUCCESS_MESSAGE); } catch (e) { toasts.addDanger(SNOOZE_FAILED_MESSAGE); @@ -101,7 +101,7 @@ export const CollapsedItemActions: React.FunctionComponent = ({ try { onLoading(true); await unsnoozeRule(item, scheduleIds); - onRuleChanged(); + await onRuleChanged(); toasts.addSuccess(UNSNOOZE_SUCCESS_MESSAGE); } catch (e) { toasts.addDanger(SNOOZE_FAILED_MESSAGE); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/notify_badge/notify_badge.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/notify_badge/notify_badge.test.tsx index 35cddf249d8e7..3e7843f9ab143 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/notify_badge/notify_badge.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/notify_badge/notify_badge.test.tsx @@ -9,73 +9,27 @@ import { EuiButtonIcon, EuiButton } from '@elastic/eui'; import React from 'react'; import { act } from 'react-dom/test-utils'; import moment from 'moment'; - -import { RuleTableItem } from '../../../../../types'; import { mountWithIntl } from '@kbn/test-jest-helpers'; import { RulesListNotifyBadge } from './notify_badge'; jest.mock('../../../../../common/lib/kibana'); -const onClick = jest.fn(); -const onClose = jest.fn(); -const onLoading = jest.fn(); -const onRuleChanged = jest.fn(); -const snoozeRule = jest.fn(); -const unsnoozeRule = jest.fn(); - -const getRule = (overrides = {}): RuleTableItem => ({ - id: '1', - enabled: true, - name: 'test rule', - tags: ['tag1'], - ruleTypeId: 'test_rule_type', - consumer: 'rules', - schedule: { interval: '5d' }, - actions: [ - { id: 'test', actionTypeId: 'the_connector', group: 'rule', params: { message: 'test' } }, - ], - params: { name: 'test rule type name' }, - createdBy: null, - updatedBy: null, - createdAt: new Date(), - updatedAt: new Date(), - apiKeyOwner: null, - throttle: '1m', - notifyWhen: 'onActiveAlert', - muteAll: false, - mutedInstanceIds: [], - executionStatus: { - status: 'active', - lastExecutionDate: new Date('2020-08-20T19:23:38Z'), - }, - actionsCount: 1, - index: 0, - ruleType: 'Test Rule Type', - isEditable: true, - enabledInLicense: true, - revision: 0, - ...overrides, -}); - describe('RulesListNotifyBadge', () => { + const onRuleChanged = jest.fn(); + const snoozeRule = jest.fn(); + const unsnoozeRule = jest.fn(); + afterEach(() => { jest.clearAllMocks(); }); - it('renders the notify badge correctly', async () => { - jest.useFakeTimers().setSystemTime(moment('1990-01-01').toDate()); - + it('renders an unsnoozed badge', () => { const wrapper = mountWithIntl( { // Rule without snooze const badge = wrapper.find(EuiButtonIcon); expect(badge.first().props().iconType).toEqual('bell'); + }); + + it('renders a snoozed badge', () => { + jest.useFakeTimers().setSystemTime(moment('1990-01-01').toDate()); + + const wrapper = mountWithIntl( + + ); - // Rule with snooze - wrapper.setProps({ - rule: getRule({ - isSnoozedUntil: moment('1990-02-01').format(), - }), - }); const snoozeBadge = wrapper.find(EuiButton); + expect(snoozeBadge.first().props().iconType).toEqual('bellSlash'); expect(snoozeBadge.text()).toEqual('Feb 1'); + }); - // Rule with indefinite snooze - wrapper.setProps({ - rule: getRule({ - isSnoozedUntil: moment('1990-02-01').format(), - muteAll: true, - }), - }); + it('renders an indefinitely snoozed badge', () => { + jest.useFakeTimers().setSystemTime(moment('1990-01-01').toDate()); + + const wrapper = mountWithIntl( + + ); const indefiniteSnoozeBadge = wrapper.find(EuiButtonIcon); + expect(indefiniteSnoozeBadge.first().props().iconType).toEqual('bellSlash'); expect(indefiniteSnoozeBadge.text()).toEqual(''); }); @@ -113,24 +87,21 @@ describe('RulesListNotifyBadge', () => { jest.useFakeTimers().setSystemTime(moment('1990-01-01').toDate()); const wrapper = mountWithIntl( ); + // Open the popover + wrapper.find(EuiButtonIcon).first().simulate('click'); + // Snooze for 1 hour wrapper.find('button[data-test-subj="linkSnooze1h"]').first().simulate('click'); - expect(onLoading).toHaveBeenCalledWith(true); expect(snoozeRule).toHaveBeenCalledWith({ duration: 3600000, id: null, @@ -146,38 +117,31 @@ describe('RulesListNotifyBadge', () => { }); expect(onRuleChanged).toHaveBeenCalled(); - expect(onLoading).toHaveBeenCalledWith(false); - expect(onClose).toHaveBeenCalled(); }); it('should allow the user to unsnooze rules', async () => { jest.useFakeTimers().setSystemTime(moment('1990-01-01').toDate()); const wrapper = mountWithIntl( ); + // Open the popover + wrapper.find(EuiButtonIcon).first().simulate('click'); + // Unsnooze wrapper.find('[data-test-subj="ruleSnoozeCancel"] button').simulate('click'); - expect(onLoading).toHaveBeenCalledWith(true); await act(async () => { jest.runOnlyPendingTimers(); }); expect(unsnoozeRule).toHaveBeenCalled(); - expect(onLoading).toHaveBeenCalledWith(false); - expect(onClose).toHaveBeenCalled(); }); }); 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 92b8820775891..18076102c1fd9 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 @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import moment from 'moment'; import { EuiButton, @@ -30,35 +30,38 @@ import { } from './translations'; import { RulesListNotifyBadgeProps } from './types'; -export const RulesListNotifyBadge: React.FunctionComponent = (props) => { - const { - isLoading = false, - rule, - isOpen, - onClick, - onClose, - onLoading, - onRuleChanged, - snoozeRule, - unsnoozeRule, - showOnHover = false, - showTooltipInline = false, - } = props; - - const { isSnoozedUntil, muteAll, isEditable } = rule; - +export const RulesListNotifyBadge: React.FunctionComponent = ({ + snoozeSettings, + loading = false, + disabled = false, + onRuleChanged, + snoozeRule, + unsnoozeRule, + showOnHover = false, + showTooltipInline = false, +}) => { + const [requestInFlight, setRequestInFlightLoading] = useState(false); + const isLoading = loading || requestInFlight; + const isDisabled = Boolean(disabled) || !snoozeSettings; + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const openPopover = useCallback(() => setIsPopoverOpen(true), [setIsPopoverOpen]); + const closePopover = useCallback(() => setIsPopoverOpen(false), [setIsPopoverOpen]); + const isSnoozedUntil = snoozeSettings?.isSnoozedUntil; + const muteAll = snoozeSettings?.muteAll ?? false; const isSnoozedIndefinitely = muteAll; + const isSnoozed = useMemo( + () => (snoozeSettings ? isRuleSnoozed(snoozeSettings) : false), + [snoozeSettings] + ); + const nextScheduledSnooze = useMemo( + () => (snoozeSettings ? getNextRuleSnoozeSchedule(snoozeSettings) : null), + [snoozeSettings] + ); const { notifications: { toasts }, } = useKibana().services; - const isSnoozed = useMemo(() => { - return isRuleSnoozed(rule); - }, [rule]); - - const nextScheduledSnooze = useMemo(() => getNextRuleSnoozeSchedule(rule), [rule]); - const isScheduled = useMemo(() => { return !isSnoozed && Boolean(nextScheduledSnooze); }, [nextScheduledSnooze, isSnoozed]); @@ -124,18 +127,18 @@ export const RulesListNotifyBadge: React.FunctionComponent {formattedSnoozeText} ); - }, [formattedSnoozeText, isLoading, isEditable, onClick]); + }, [formattedSnoozeText, isLoading, isDisabled, openPopover]); const scheduledSnoozeButton = useMemo(() => { // TODO: Implement scheduled snooze button @@ -143,18 +146,18 @@ export const RulesListNotifyBadge: React.FunctionComponent {formattedSnoozeText} ); - }, [formattedSnoozeText, isLoading, isEditable, onClick]); + }, [formattedSnoozeText, isLoading, isDisabled, openPopover]); const unsnoozedButton = useMemo(() => { // This show on hover is needed because we need style sheets to achieve the @@ -165,32 +168,32 @@ export const RulesListNotifyBadge: React.FunctionComponent ); - }, [isOpen, isLoading, isEditable, showOnHover, onClick]); + }, [isPopoverOpen, isLoading, isDisabled, showOnHover, openPopover]); const indefiniteSnoozeButton = useMemo(() => { return ( ); - }, [isLoading, isEditable, onClick]); + }, [isLoading, isDisabled, openPopover]); const button = useMemo(() => { if (isScheduled) { @@ -214,57 +217,55 @@ export const RulesListNotifyBadge: React.FunctionComponent { - if (isOpen || showTooltipInline) { - return button; - } - return {button}; - }, [isOpen, button, snoozeTooltipText, showTooltipInline]); + const tooltipContent = + typeof disabled === 'string' + ? disabled + : isPopoverOpen || showTooltipInline + ? undefined + : snoozeTooltipText; - const onClosePopover = useCallback(() => { - onClose(); - // Set a timeout on closing the scheduler to avoid flicker - // setTimeout(onCloseScheduler, 1000); - }, [onClose]); + return {button}; + }, [disabled, isPopoverOpen, button, snoozeTooltipText, showTooltipInline]); const onApplySnooze = useCallback( async (schedule: SnoozeSchedule) => { try { - onLoading(true); - onClosePopover(); + setRequestInFlightLoading(true); + closePopover(); await snoozeRule(schedule); - onRuleChanged(); + await onRuleChanged(); toasts.addSuccess(SNOOZE_SUCCESS_MESSAGE); } catch (e) { toasts.addDanger(SNOOZE_FAILED_MESSAGE); } finally { - onLoading(false); + setRequestInFlightLoading(false); } }, - [onLoading, snoozeRule, onRuleChanged, toasts, onClosePopover] + [setRequestInFlightLoading, snoozeRule, onRuleChanged, toasts, closePopover] ); const onApplyUnsnooze = useCallback( async (scheduleIds?: string[]) => { try { - onLoading(true); - onClosePopover(); + setRequestInFlightLoading(true); + closePopover(); await unsnoozeRule(scheduleIds); - onRuleChanged(); + await onRuleChanged(); toasts.addSuccess(UNSNOOZE_SUCCESS_MESSAGE); } catch (e) { toasts.addDanger(SNOOZE_FAILED_MESSAGE); } finally { - onLoading(false); + setRequestInFlightLoading(false); } }, - [onLoading, unsnoozeRule, onRuleChanged, toasts, onClosePopover] + [setRequestInFlightLoading, unsnoozeRule, onRuleChanged, toasts, closePopover] ); const popover = ( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/notify_badge/notify_badge_with_api.stories.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/notify_badge/notify_badge_with_api.stories.tsx index 6fefd93632aaa..db2f8d2333c8c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/notify_badge/notify_badge_with_api.stories.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/notify_badge/notify_badge_with_api.stories.tsx @@ -27,7 +27,7 @@ export default { title: 'app/RulesListNotifyBadgeWithApi', component: RulesListNotifyBadgeWithApi, argTypes: { - rule: { + snoozeSettings: { defaultValue: rule, control: { type: 'object', @@ -54,7 +54,7 @@ export default { onRuleChanged: {}, }, args: { - rule, + snoozeSettings: rule, onRuleChanged: (...args: any) => action('onRuleChanged')(args), }, } as Meta; @@ -69,7 +69,7 @@ const IndefinitelyDate = new Date(); IndefinitelyDate.setDate(IndefinitelyDate.getDate() + 1); export const IndefinitelyRuleNotifyBadgeWithApi = Template.bind({}); IndefinitelyRuleNotifyBadgeWithApi.args = { - rule: { + snoozeSettings: { ...rule, muteAll: true, isSnoozedUntil: IndefinitelyDate, @@ -80,7 +80,7 @@ export const ActiveSnoozesRuleNotifyBadgeWithApi = Template.bind({}); const ActiveSnoozeDate = new Date(); ActiveSnoozeDate.setDate(ActiveSnoozeDate.getDate() + 2); ActiveSnoozesRuleNotifyBadgeWithApi.args = { - rule: { + snoozeSettings: { ...rule, activeSnoozes: ['24da3b26-bfa5-4317-b72f-4063dbea618e'], isSnoozedUntil: ActiveSnoozeDate, @@ -111,7 +111,7 @@ export const ScheduleSnoozesRuleNotifyBadgeWithApi: Story = (props) => { - const { onRuleChanged, rule, isLoading, showTooltipInline, showOnHover } = props; +> = ({ + ruleId, + snoozeSettings, + loading, + disabled, + showTooltipInline, + showOnHover, + onRuleChanged, +}) => { const { http } = useKibana().services; - const [currentlyOpenNotify, setCurrentlyOpenNotify] = useState(); - const [loadingSnoozeAction, setLoadingSnoozeAction] = useState(false); - const [ruleSnoozeInfo, setRuleSnoozeInfo] = - useState(rule); - - // This helps to fix problems related to rule prop updates. As component handles the loading state via isLoading prop - // rule prop is obviously not ready atm so when it's ready ruleSnoozeInfo won't be updated without useEffect so - // incorrect state will be shown. - useEffect(() => { - setRuleSnoozeInfo(rule); - }, [rule]); const onSnoozeRule = useCallback( - (snoozeSchedule: SnoozeSchedule) => { - return snoozeRuleApi({ http, id: ruleSnoozeInfo.id, snoozeSchedule }); - }, - [http, ruleSnoozeInfo.id] + (snoozeSchedule: SnoozeSchedule) => + ruleId ? snoozeRuleApi({ http, id: ruleId, snoozeSchedule }) : Promise.resolve(), + [http, ruleId] ); const onUnsnoozeRule = useCallback( - (scheduleIds?: string[]) => { - return unsnoozeRuleApi({ http, id: ruleSnoozeInfo.id, scheduleIds }); - }, - [http, ruleSnoozeInfo.id] + (scheduleIds?: string[]) => + ruleId ? unsnoozeRuleApi({ http, id: ruleId, scheduleIds }) : Promise.resolve(), + [http, ruleId] ); - const onRuleChangedCallback = useCallback(async () => { - const updatedRule = await loadRule({ - http, - ruleId: ruleSnoozeInfo.id, - }); - setLoadingSnoozeAction(false); - setRuleSnoozeInfo((prevRule) => ({ - ...prevRule, - activeSnoozes: updatedRule.activeSnoozes, - isSnoozedUntil: updatedRule.isSnoozedUntil, - muteAll: updatedRule.muteAll, - snoozeSchedule: updatedRule.snoozeSchedule, - })); - onRuleChanged(); - }, [http, ruleSnoozeInfo.id, onRuleChanged]); - - const openSnooze = useCallback(() => { - setCurrentlyOpenNotify(props.rule.id); - }, [props.rule.id]); - - const closeSnooze = useCallback(() => { - setCurrentlyOpenNotify(''); - }, []); - - const onLoading = useCallback((value: boolean) => { - if (value) { - setLoadingSnoozeAction(value); - } - }, []); - return ( ; - isOpen: boolean; - isLoading: boolean; - previousSnoozeInterval?: string | null; - onClick: React.MouseEventHandler; - onClose: () => void; - onLoading: (isLoading: boolean) => void; - onRuleChanged: () => void; + /** + * Rule's snooze settings + */ + snoozeSettings: RuleSnoozeSettings | undefined; + /** + * Displays the component in the loading state. If isLoading = false and snoozeSettings aren't set + * and the component is shown in disabled state. + */ + loading?: boolean; + /** + * Whether the component is disabled or not, string give a disabled reason displayed as a tooltip + */ + disabled?: boolean | string; + onRuleChanged: () => void | Promise; snoozeRule: (schedule: SnoozeSchedule, muteAll?: boolean) => Promise; unsnoozeRule: (scheduleIds?: string[]) => Promise; showTooltipInline?: boolean; @@ -27,5 +30,10 @@ export interface RulesListNotifyBadgeProps { export type RulesListNotifyBadgePropsWithApi = Pick< RulesListNotifyBadgeProps, - 'rule' | 'isLoading' | 'onRuleChanged' | 'showOnHover' | 'showTooltipInline' ->; + 'snoozeSettings' | 'loading' | 'disabled' | 'onRuleChanged' | 'showOnHover' | 'showTooltipInline' +> & { + /** + * Rule's SO id + */ + ruleId: string; +}; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_snooze/panel/index.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_snooze/panel/index.tsx index 25b97ec81bd91..9c1f1c844c699 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_snooze/panel/index.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_snooze/panel/index.tsx @@ -46,10 +46,12 @@ export const SnoozePanel: React.FC = ({ try { await snoozeRule(schedule); } finally { - setIsLoading(false); + if (!inPopover) { + setIsLoading(false); + } } }, - [setIsLoading, snoozeRule] + [inPopover, setIsLoading, snoozeRule] ); const onUnsnoozeRule = useCallback( @@ -58,10 +60,12 @@ export const SnoozePanel: React.FC = ({ try { await unsnoozeRule(scheduleIds); } finally { - setIsLoading(false); + if (!inPopover) { + setIsLoading(false); + } } }, - [setIsLoading, unsnoozeRule] + [inPopover, setIsLoading, unsnoozeRule] ); const saveSnoozeSchedule = useCallback( @@ -70,10 +74,12 @@ export const SnoozePanel: React.FC = ({ try { await snoozeRule(schedule); } finally { - setIsLoading(false); + if (!inPopover) { + setIsLoading(false); + } } }, - [snoozeRule, setIsLoading] + [inPopover, snoozeRule, setIsLoading] ); const cancelSnoozeSchedules = useCallback( @@ -82,10 +88,12 @@ export const SnoozePanel: React.FC = ({ try { await unsnoozeRule(scheduleIds); } finally { - setIsLoading(false); + if (!inPopover) { + setIsLoading(false); + } } }, - [unsnoozeRule, setIsLoading] + [inPopover, unsnoozeRule, setIsLoading] ); const onOpenScheduler = useCallback( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx index 5bb454c0c29c5..0548855629f48 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx @@ -847,7 +847,7 @@ export const RulesList = ({ itemIdToExpandedRowMap={itemIdToExpandedRowMap} onSort={setSort} onPage={setPage} - onRuleChanged={() => refreshRules()} + onRuleChanged={refreshRules} onRuleClick={(rule) => { const detailsRoute = ruleDetailsRoute ? ruleDetailsRoute : commonRuleDetailsRoute; history.push(detailsRoute.replace(`:ruleId`, rule.id)); @@ -883,7 +883,7 @@ export const RulesList = ({ key={rule.id} item={rule} onLoading={onLoading} - onRuleChanged={() => refreshRules()} + onRuleChanged={refreshRules} onDeleteRule={() => updateRulesToBulkEdit({ action: 'delete', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_table.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_table.tsx index a1aba92763b2d..34c1f83878ef6 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_table.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_table.tsx @@ -208,7 +208,6 @@ export const RulesListTable = (props: RulesListTableProps) => { } = props; const [tagPopoverOpenIndex, setTagPopoverOpenIndex] = useState(-1); - const [currentlyOpenNotify, setCurrentlyOpenNotify] = useState(); const [isLoadingMap, setIsLoadingMap] = useState>({}); const isRuleUsingExecutionStatus = getIsExperimentalFeatureEnabled('ruleUseExecutionStatus'); @@ -485,12 +484,9 @@ export const RulesListTable = (props: RulesListTableProps) => { return ( onLoading(rule.id, newIsLoading)} - isOpen={currentlyOpenNotify === rule.id} - onClick={() => setCurrentlyOpenNotify(rule.id)} - onClose={() => setCurrentlyOpenNotify('')} + snoozeSettings={rule} + loading={!!isLoadingMap[rule.id]} + disabled={!rule.isEditable} onRuleChanged={onRuleChanged} snoozeRule={async (snoozeSchedule) => { await onSnoozeRule(rule, snoozeSchedule); @@ -769,7 +765,6 @@ export const RulesListTable = (props: RulesListTableProps) => { ]; }, [ config.minimumScheduleInterval, - currentlyOpenNotify, isLoadingMap, isRuleTypeEditableInContext, onRuleChanged, diff --git a/x-pack/plugins/triggers_actions_ui/public/types.ts b/x-pack/plugins/triggers_actions_ui/public/types.ts index 4c7d743cb3085..7f411ce1f6600 100644 --- a/x-pack/plugins/triggers_actions_ui/public/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/types.ts @@ -343,6 +343,11 @@ export type SanitizedRuleType = Omit; export type RuleUpdates = Omit; +export type RuleSnoozeSettings = Pick< + Rule, + 'activeSnoozes' | 'isSnoozedUntil' | 'muteAll' | 'snoozeSchedule' +>; + export interface RuleTableItem extends Rule { ruleType: RuleType['name']; index: number; @@ -350,7 +355,6 @@ export interface RuleTableItem extends Rule { isEditable: boolean; enabledInLicense: boolean; showIntervalWarning?: boolean; - activeSnoozes?: string[]; } export interface RuleTypeParamsExpressionProps< diff --git a/x-pack/test/api_integration/apis/synthetics/add_monitor_private_location.ts b/x-pack/test/api_integration/apis/synthetics/add_monitor_private_location.ts index 149961f41e084..8a2da70a07009 100644 --- a/x-pack/test/api_integration/apis/synthetics/add_monitor_private_location.ts +++ b/x-pack/test/api_integration/apis/synthetics/add_monitor_private_location.ts @@ -90,7 +90,7 @@ export default function ({ getService }: FtrProviderContext) { it('does not add a monitor if there is an error in creating integration', async () => { const newMonitor = { ...httpMonitorJson }; - const invalidName = '[] - invalid name'; + const invalidName = '!@#$%^&*()_++[\\-\\]- wow'; newMonitor.locations.push({ id: testFleetPolicyID, @@ -109,7 +109,7 @@ export default function ({ getService }: FtrProviderContext) { expect(apiResponse.body).eql({ statusCode: 500, message: - 'YAMLException: end of the stream or a document separator is expected at line 3, column 10:\n name: [] - invalid name\n ^', + 'YAMLException: unknown escape sequence at line 3, column 34:\n name: "!@#$,%,^,&,*,(,),_,+,+,[,\\,\\,-,\\,\\,],-, ,w,o,w,"\n ^', error: 'Internal Server Error', }); diff --git a/x-pack/test/api_integration/apis/synthetics/add_monitor_project_private_location.ts b/x-pack/test/api_integration/apis/synthetics/add_monitor_project_private_location.ts index 7c6517d3cddf1..d53309c48e340 100644 --- a/x-pack/test/api_integration/apis/synthetics/add_monitor_project_private_location.ts +++ b/x-pack/test/api_integration/apis/synthetics/add_monitor_project_private_location.ts @@ -64,13 +64,13 @@ export default function ({ getService }: FtrProviderContext) { }; const testMonitors = [ projectMonitors.monitors[0], - { ...secondMonitor, name: '[] - invalid name' }, + { ...secondMonitor, name: '!@#$%^&*()_++[\\-\\]- wow name' }, ]; try { const body = await monitorTestService.addProjectMonitors(project, testMonitors); expect(body.createdMonitors.length).eql(1); expect(body.failedMonitors[0].reason).eql( - 'end of the stream or a document separator is expected at line 3, column 10:\n name: [] - invalid name\n ^' + 'unknown escape sequence at line 3, column 34:\n name: "!@#$,%,^,&,*,(,),_,+,+,[,\\,\\,-,\\,\\,],-, ,w,o,w, ,n,a,m,e,"\n ^' ); } finally { await Promise.all([ @@ -97,7 +97,7 @@ export default function ({ getService }: FtrProviderContext) { expect(editedBody.createdMonitors.length).eql(0); expect(editedBody.updatedMonitors.length).eql(2); - testMonitors[1].name = '[] - invalid name'; + testMonitors[1].name = '!@#$%^&*()_++[\\-\\]- wow name'; const editedBodyError = await monitorTestService.addProjectMonitors(project, testMonitors); expect(editedBodyError.createdMonitors.length).eql(0); @@ -107,7 +107,7 @@ export default function ({ getService }: FtrProviderContext) { 'Failed to update journey: test-id-2' ); expect(editedBodyError.failedMonitors[0].reason).eql( - 'end of the stream or a document separator is expected at line 3, column 10:\n name: [] - invalid name\n ^' + 'unknown escape sequence at line 3, column 34:\n name: "!@#$,%,^,&,*,(,),_,+,+,[,\\,\\,-,\\,\\,],-, ,w,o,w, ,n,a,m,e,"\n ^' ); } finally { await Promise.all([ diff --git a/x-pack/test/api_integration/apis/synthetics/sample_data/test_browser_policy.ts b/x-pack/test/api_integration/apis/synthetics/sample_data/test_browser_policy.ts index 5eec726cdb328..19cc3998bee0d 100644 --- a/x-pack/test/api_integration/apis/synthetics/sample_data/test_browser_policy.ts +++ b/x-pack/test/api_integration/apis/synthetics/sample_data/test_browser_policy.ts @@ -177,9 +177,9 @@ export const getTestBrowserSyntheticsPolicy = ({ }, enabled: { value: true, type: 'bool' }, type: { value: 'browser', type: 'text' }, - name: { value: 'Test HTTP Monitor 03', type: 'text' }, + name: { value: '"Test HTTP Monitor 03"', type: 'text' }, schedule: { value: '"@every 3m"', type: 'text' }, - 'service.name': { value: '', type: 'text' }, + 'service.name': { value: null, type: 'text' }, timeout: { value: '16s', type: 'text' }, tags: { value: '["cookie-test","browser"]', type: 'yaml' }, 'source.zip_url.url': { type: 'text' }, @@ -210,8 +210,8 @@ export const getTestBrowserSyntheticsPolicy = ({ 'source.zip_url.ssl.verification_mode': { type: 'text' }, 'source.zip_url.ssl.supported_protocols': { type: 'yaml' }, 'source.zip_url.proxy_url': { type: 'text' }, - location_name: { value: 'Test private location 0', type: 'text' }, - id: { value: id, type: 'text' }, + location_name: { value: JSON.stringify('Test private location 0'), type: 'text' }, + id: { value: JSON.stringify(id), type: 'text' }, config_id: { value: id, type: 'text' }, run_once: { value: false, type: 'bool' }, origin: { value: 'ui', type: 'text' }, diff --git a/x-pack/test/api_integration/apis/synthetics/sample_data/test_policy.ts b/x-pack/test/api_integration/apis/synthetics/sample_data/test_policy.ts index b0962e4d285e6..d59aa207c28a2 100644 --- a/x-pack/test/api_integration/apis/synthetics/sample_data/test_policy.ts +++ b/x-pack/test/api_integration/apis/synthetics/sample_data/test_policy.ts @@ -48,17 +48,23 @@ export const getTestSyntheticsPolicy = ( }, enabled: { value: true, type: 'bool' }, type: { value: 'http', type: 'text' }, - name: { value: name, type: 'text' }, + name: { value: `"${name}"`, type: 'text' }, schedule: { value: '"@every 5m"', type: 'text' }, - urls: { value: 'https://nextjs-test-synthetics.vercel.app/api/users', type: 'text' }, - 'service.name': { value: '', type: 'text' }, + urls: { + value: JSON.stringify('https://nextjs-test-synthetics.vercel.app/api/users'), + type: 'text', + }, + 'service.name': { value: null, type: 'text' }, timeout: { value: '3ms', type: 'text' }, max_redirects: { value: '3', type: 'integer' }, - proxy_url: { value: proxyUrl ?? 'http://proxy.com', type: 'text' }, + proxy_url: { + value: JSON.stringify(proxyUrl) ?? JSON.stringify('http://proxy.com'), + type: 'text', + }, proxy_headers: { value: null, type: 'yaml' }, tags: { value: '["tag1","tag2"]', type: 'yaml' }, - username: { value: 'test-username', type: 'text' }, - password: { value: 'test', type: 'password' }, + username: { value: '"test-username"', type: 'text' }, + password: { value: '"test"', type: 'password' }, 'response.include_headers': { value: true, type: 'bool' }, 'response.include_body': { value: 'never', type: 'text' }, 'response.include_body_max_bytes': { value: '1024', type: 'text' }, @@ -85,8 +91,11 @@ export const getTestSyntheticsPolicy = ( value: isTLSEnabled ? '["TLSv1.1","TLSv1.2"]' : null, type: 'yaml', }, - location_name: { value: locationName ?? 'Test private location 0', type: 'text' }, - id: { value: id, type: 'text' }, + location_name: { + value: JSON.stringify(locationName) ?? JSON.stringify('Test private location 0'), + type: 'text', + }, + id: { value: JSON.stringify(id), type: 'text' }, config_id: { value: id, type: 'text' }, run_once: { value: false, type: 'bool' }, origin: { value: 'ui', type: 'text' }, diff --git a/x-pack/test/api_integration/apis/synthetics/sample_data/test_project_monitor_policy.ts b/x-pack/test/api_integration/apis/synthetics/sample_data/test_project_monitor_policy.ts index bc015cb4bd77c..2289fb68d2565 100644 --- a/x-pack/test/api_integration/apis/synthetics/sample_data/test_project_monitor_policy.ts +++ b/x-pack/test/api_integration/apis/synthetics/sample_data/test_project_monitor_policy.ts @@ -202,9 +202,9 @@ export const getTestProjectSyntheticsPolicy = ( }, enabled: { value: true, type: 'bool' }, type: { value: 'browser', type: 'text' }, - name: { value: 'check if title is present', type: 'text' }, + name: { value: '"check if title is present"', type: 'text' }, schedule: { value: '"@every 10m"', type: 'text' }, - 'service.name': { value: '', type: 'text' }, + 'service.name': { value: null, type: 'text' }, timeout: { value: null, type: 'text' }, tags: { value: null, type: 'yaml' }, 'source.zip_url.url': { type: 'text' }, @@ -238,13 +238,13 @@ export const getTestProjectSyntheticsPolicy = ( 'source.zip_url.ssl.verification_mode': { type: 'text' }, 'source.zip_url.ssl.supported_protocols': { type: 'yaml' }, 'source.zip_url.proxy_url': { type: 'text' }, - location_name: { value: 'Test private location 0', type: 'text' }, - id: { value: id, type: 'text' }, + location_name: { value: '"Test private location 0"', type: 'text' }, + id: { value: `"${id}"`, type: 'text' }, config_id: { value: configId, type: 'text' }, run_once: { value: false, type: 'bool' }, origin: { value: 'project', type: 'text' }, - 'monitor.project.id': { value: projectId, type: 'text' }, - 'monitor.project.name': { value: projectId, type: 'text' }, + 'monitor.project.id': { value: JSON.stringify(projectId), type: 'text' }, + 'monitor.project.name': { value: JSON.stringify(projectId), type: 'text' }, ...inputs, }, id: `synthetics/browser-browser-4b6abc6c-118b-4d93-a489-1135500d09f1-${projectId}-default-d70a46e0-22ea-11ed-8c6b-09a2d21dfbc3`, diff --git a/x-pack/test/functional/apps/dashboard/group3/reporting/screenshots.ts b/x-pack/test/functional/apps/dashboard/group3/reporting/screenshots.ts index 8fb3c8c7a693b..12b8790d38324 100644 --- a/x-pack/test/functional/apps/dashboard/group3/reporting/screenshots.ts +++ b/x-pack/test/functional/apps/dashboard/group3/reporting/screenshots.ts @@ -191,7 +191,8 @@ export default function ({ }); }); - describe('Preserve Layout', () => { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/157023 + describe.skip('Preserve Layout', () => { before(async () => { await loadEcommerce(); }); diff --git a/x-pack/test/rule_registry/security_and_spaces/tests/basic/search_strategy.ts b/x-pack/test/rule_registry/security_and_spaces/tests/basic/search_strategy.ts index 3698839861031..c64f7c78650d2 100644 --- a/x-pack/test/rule_registry/security_and_spaces/tests/basic/search_strategy.ts +++ b/x-pack/test/rule_registry/security_and_spaces/tests/basic/search_strategy.ts @@ -173,6 +173,37 @@ export default ({ getService }: FtrProviderContext) => { `The privateRuleRegistryAlertsSearchStrategy search strategy is unable to accommodate requests containing multiple feature IDs and one of those IDs is SIEM.` ); }); + + it('should be able to handle runtime fields on alerts from siem rules', async () => { + const runtimeFieldValue = 'hello world'; + const runtimeFieldKey = 'hello_world'; + const result = await secureBsearch.send({ + supertestWithoutAuth, + auth: { + username: obsOnlySpacesAllEsRead.username, + password: obsOnlySpacesAllEsRead.password, + }, + referer: 'test', + kibanaVersion, + options: { + featureIds: [AlertConsumers.SIEM], + runtimeMappings: { + [runtimeFieldKey]: { + type: 'keyword', + script: { + source: `emit('${runtimeFieldValue}')`, + }, + }, + }, + }, + strategy: 'privateRuleRegistryAlertsSearchStrategy', + }); + expect(result.rawResponse.hits.total).to.eql(1); + const runtimeFields = result.rawResponse.hits.hits.map( + (hit) => hit.fields?.[runtimeFieldKey] + ); + expect(runtimeFields.every((field) => field === runtimeFieldValue)); + }); }); describe('apm', () => {