From d41f2dc57e92636f2e69444ca7dbe94878de88bb Mon Sep 17 00:00:00 2001 From: Zacqary Adam Xeper Date: Thu, 8 Jul 2021 15:33:25 -0500 Subject: [PATCH 01/49] [Fleet] Fix blank page when uninstalling outdated integration (#104938) * [Fleet] Fix blank page when uninstalling outdated integration * Make redirect conditional on version mismatch --- .../hooks/use_package_install.tsx | 15 +++++- .../detail/settings/installation_button.tsx | 7 +-- .../epm/screens/detail/settings/settings.tsx | 52 ++++++++++++++++++- 3 files changed, 67 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_package_install.tsx b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_package_install.tsx index 342d6b54c2613..edbe06f33b18e 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_package_install.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_package_install.tsx @@ -118,7 +118,12 @@ function usePackageInstall({ notifications }: { notifications: NotificationsStar ); const uninstallPackage = useCallback( - async ({ name, version, title }: Pick) => { + async ({ + name, + version, + title, + redirectToVersion, + }: Pick & { redirectToVersion: string }) => { setPackageInstallStatus({ name, status: InstallStatus.uninstalling, version }); const pkgkey = `${name}-${version}`; @@ -160,9 +165,15 @@ function usePackageInstall({ notifications }: { notifications: NotificationsStar /> ), }); + if (redirectToVersion !== version) { + const settingsPath = getPath('integration_details_settings', { + pkgkey: `${name}-${redirectToVersion}`, + }); + history.push(settingsPath); + } } }, - [notifications.toasts, setPackageInstallStatus] + [notifications.toasts, setPackageInstallStatus, getPath, history] ); return { diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/installation_button.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/installation_button.tsx index 8cf8466e6d9b0..eab28a051f061 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/installation_button.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/installation_button.tsx @@ -24,9 +24,10 @@ import { ConfirmPackageInstall } from './confirm_package_install'; type InstallationButtonProps = Pick & { disabled?: boolean; isUpdate?: boolean; + latestVersion?: string; }; export function InstallationButton(props: InstallationButtonProps) { - const { assets, name, title, version, disabled = true, isUpdate = false } = props; + const { assets, name, title, version, disabled = true, isUpdate = false, latestVersion } = props; const hasWriteCapabilites = useCapabilities().write; const installPackage = useInstallPackage(); const uninstallPackage = useUninstallPackage(); @@ -52,9 +53,9 @@ export function InstallationButton(props: InstallationButtonProps) { }, [installPackage, name, title, version]); const handleClickUninstall = useCallback(() => { - uninstallPackage({ name, version, title }); + uninstallPackage({ name, version, title, redirectToVersion: latestVersion ?? version }); toggleModal(); - }, [uninstallPackage, name, title, toggleModal, version]); + }, [uninstallPackage, name, title, toggleModal, version, latestVersion]); // counts the number of assets in the package const numOfAssets = useMemo( diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/settings.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/settings.tsx index 9e8d200344b01..14f378bc379a6 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/settings.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/settings.tsx @@ -10,11 +10,11 @@ import styled from 'styled-components'; import { FormattedMessage } from '@kbn/i18n/react'; import semverLt from 'semver/functions/lt'; -import { EuiTitle, EuiFlexGroup, EuiFlexItem, EuiText, EuiSpacer } from '@elastic/eui'; +import { EuiTitle, EuiFlexGroup, EuiFlexItem, EuiText, EuiSpacer, EuiLink } from '@elastic/eui'; import type { PackageInfo } from '../../../../../types'; import { InstallStatus } from '../../../../../types'; -import { useGetPackagePolicies, useGetPackageInstallStatus } from '../../../../../hooks'; +import { useGetPackagePolicies, useGetPackageInstallStatus, useLink } from '../../../../../hooks'; import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../../../constants'; import { UpdateIcon } from '../components'; @@ -47,6 +47,21 @@ const UpdatesAvailableMsg = () => ( ); +const LatestVersionLink = ({ name, version }: { name: string; version: string }) => { + const { getPath } = useLink(); + const settingsPath = getPath('integration_details_settings', { + pkgkey: `${name}-${version}`, + }); + return ( + + + + ); +}; + interface Props { packageInfo: PackageInfo; } @@ -72,6 +87,7 @@ export const SettingsPage: React.FC = memo(({ packageInfo }: Props) => { (installationStatus === InstallStatus.installed && installedVersion !== version); const isUpdating = installationStatus === InstallStatus.installing && installedVersion; + return ( @@ -206,6 +222,7 @@ export const SettingsPage: React.FC = memo(({ packageInfo }: Props) => {

@@ -244,6 +261,37 @@ export const SettingsPage: React.FC = memo(({ packageInfo }: Props) => { )} )} + {hideInstallOptions && isViewingOldPackage && !isUpdating && ( +
+ +
+ +

+ +

+
+ +

+ + , + }} + /> + +

+
+
+ )}
From 149d3c5685d7d39cc38b31a75111b56393a6bd1a Mon Sep 17 00:00:00 2001 From: Paul Tavares <56442535+paul-tavares@users.noreply.github.com> Date: Thu, 8 Jul 2021 19:29:25 -0400 Subject: [PATCH 02/49] [Security Solution][Endpoint] Fix permission checks for endpoint management funcionality (#104555) * refactor: move `UserPrivilegesContext` to `public/common` * Add endpointPrivileges to the UserPrivileges context * refactor `useUpgradeSecurityPackages` to use `useUserPrivileges()` * Refactor to use `useUserPrivileges()` instead of `useIngestEnabledCheck()` * Refactor Overview page to use `useUserPrivileges()` instead of `useIngestEnabledCheck()` * Delete `ingest_enabled` hook and refactor tests --- .../security_solution/public/app/app.tsx | 2 +- .../__mocks__/use_endpoint_privileges.ts | 17 +++ .../components/user_privileges/index.tsx | 17 ++- .../use_endpoint_privileges.test.ts | 120 ++++++++++++++++++ .../use_endpoint_privileges.ts | 77 +++++++++++ .../common/hooks/endpoint/ingest_enabled.ts | 35 ----- .../hooks/use_upgrade_security_packages.ts | 41 +----- .../public/common/mock/test_providers.tsx | 2 +- .../use_missing_privileges.ts | 2 +- .../components/user_info/index.test.tsx | 3 +- .../alerts/use_alerts_privileges.test.tsx | 5 +- .../alerts/use_alerts_privileges.tsx | 2 +- .../alerts/use_signal_index.test.tsx | 1 + .../lists/use_lists_privileges.tsx | 2 +- .../management/pages/endpoint_hosts/mocks.ts | 81 ++++++++---- .../public/management/pages/index.test.tsx | 18 ++- .../public/management/pages/index.tsx | 13 +- .../components/overview_empty/index.test.tsx | 14 +- .../components/overview_empty/index.tsx | 6 +- .../public/overview/pages/overview.test.tsx | 55 ++++++-- .../public/overview/pages/overview.tsx | 7 +- 21 files changed, 378 insertions(+), 142 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/components/user_privileges/__mocks__/use_endpoint_privileges.ts rename x-pack/plugins/security_solution/public/{detections => common}/components/user_privileges/index.tsx (59%) create mode 100644 x-pack/plugins/security_solution/public/common/components/user_privileges/use_endpoint_privileges.test.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/user_privileges/use_endpoint_privileges.ts delete mode 100644 x-pack/plugins/security_solution/public/common/hooks/endpoint/ingest_enabled.ts diff --git a/x-pack/plugins/security_solution/public/app/app.tsx b/x-pack/plugins/security_solution/public/app/app.tsx index c223570c77201..0cba9341cbce1 100644 --- a/x-pack/plugins/security_solution/public/app/app.tsx +++ b/x-pack/plugins/security_solution/public/app/app.tsx @@ -24,7 +24,7 @@ import { State } from '../common/store'; import { StartServices } from '../types'; import { PageRouter } from './routes'; import { EuiThemeProvider } from '../../../../../src/plugins/kibana_react/common'; -import { UserPrivilegesProvider } from '../detections/components/user_privileges'; +import { UserPrivilegesProvider } from '../common/components/user_privileges'; interface StartAppComponent { children: React.ReactNode; diff --git a/x-pack/plugins/security_solution/public/common/components/user_privileges/__mocks__/use_endpoint_privileges.ts b/x-pack/plugins/security_solution/public/common/components/user_privileges/__mocks__/use_endpoint_privileges.ts new file mode 100644 index 0000000000000..80cf11fecd847 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/user_privileges/__mocks__/use_endpoint_privileges.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EndpointPrivileges } from '../use_endpoint_privileges'; + +export const useEndpointPrivileges = jest.fn(() => { + const endpointPrivilegesMock: EndpointPrivileges = { + loading: false, + canAccessFleet: true, + canAccessEndpointManagement: true, + }; + return endpointPrivilegesMock; +}); diff --git a/x-pack/plugins/security_solution/public/detections/components/user_privileges/index.tsx b/x-pack/plugins/security_solution/public/common/components/user_privileges/index.tsx similarity index 59% rename from x-pack/plugins/security_solution/public/detections/components/user_privileges/index.tsx rename to x-pack/plugins/security_solution/public/common/components/user_privileges/index.tsx index bd6ff11b27f96..5a33297f04f9a 100644 --- a/x-pack/plugins/security_solution/public/detections/components/user_privileges/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/user_privileges/index.tsx @@ -6,19 +6,25 @@ */ import React, { createContext, useContext } from 'react'; -import { useFetchDetectionEnginePrivileges } from './use_fetch_detection_engine_privileges'; -import { useFetchListPrivileges } from './use_fetch_list_privileges'; +import { DeepReadonly } from 'utility-types'; +import { useFetchDetectionEnginePrivileges } from '../../../detections/components/user_privileges/use_fetch_detection_engine_privileges'; +import { useFetchListPrivileges } from '../../../detections/components/user_privileges/use_fetch_list_privileges'; +import { EndpointPrivileges, useEndpointPrivileges } from './use_endpoint_privileges'; export interface UserPrivilegesState { listPrivileges: ReturnType; detectionEnginePrivileges: ReturnType; + endpointPrivileges: EndpointPrivileges; } -const UserPrivilegesContext = createContext({ +export const initialUserPrivilegesState = (): UserPrivilegesState => ({ listPrivileges: { loading: false, error: undefined, result: undefined }, detectionEnginePrivileges: { loading: false, error: undefined, result: undefined }, + endpointPrivileges: { loading: true, canAccessEndpointManagement: false, canAccessFleet: false }, }); +const UserPrivilegesContext = createContext(initialUserPrivilegesState()); + interface UserPrivilegesProviderProps { children: React.ReactNode; } @@ -26,12 +32,14 @@ interface UserPrivilegesProviderProps { export const UserPrivilegesProvider = ({ children }: UserPrivilegesProviderProps) => { const listPrivileges = useFetchListPrivileges(); const detectionEnginePrivileges = useFetchDetectionEnginePrivileges(); + const endpointPrivileges = useEndpointPrivileges(); return ( {children} @@ -39,4 +47,5 @@ export const UserPrivilegesProvider = ({ children }: UserPrivilegesProviderProps ); }; -export const useUserPrivileges = () => useContext(UserPrivilegesContext); +export const useUserPrivileges = (): DeepReadonly => + useContext(UserPrivilegesContext); diff --git a/x-pack/plugins/security_solution/public/common/components/user_privileges/use_endpoint_privileges.test.ts b/x-pack/plugins/security_solution/public/common/components/user_privileges/use_endpoint_privileges.test.ts new file mode 100644 index 0000000000000..8e9dae9f12ad5 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/user_privileges/use_endpoint_privileges.test.ts @@ -0,0 +1,120 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { renderHook, RenderHookResult, RenderResult } from '@testing-library/react-hooks'; +import { useHttp, useCurrentUser } from '../../lib/kibana'; +import { EndpointPrivileges, useEndpointPrivileges } from './use_endpoint_privileges'; +import { fleetGetCheckPermissionsHttpMock } from '../../../management/pages/endpoint_hosts/mocks'; +import { securityMock } from '../../../../../security/public/mocks'; +import { appRoutesService } from '../../../../../fleet/common'; +import { AuthenticatedUser } from '../../../../../security/common'; + +jest.mock('../../lib/kibana'); + +describe('When using useEndpointPrivileges hook', () => { + let authenticatedUser: AuthenticatedUser; + let fleetApiMock: ReturnType; + let result: RenderResult; + let unmount: ReturnType['unmount']; + let waitForNextUpdate: ReturnType['waitForNextUpdate']; + let render: () => RenderHookResult; + + beforeEach(() => { + authenticatedUser = securityMock.createMockAuthenticatedUser({ + roles: ['superuser'], + }); + + (useCurrentUser as jest.Mock).mockReturnValue(authenticatedUser); + + fleetApiMock = fleetGetCheckPermissionsHttpMock( + useHttp() as Parameters[0] + ); + + render = () => { + const hookRenderResponse = renderHook(() => useEndpointPrivileges()); + ({ result, unmount, waitForNextUpdate } = hookRenderResponse); + return hookRenderResponse; + }; + }); + + afterEach(() => { + unmount(); + }); + + it('should return `loading: true` while retrieving privileges', async () => { + // Add a daly to the API response that we can control from the test + let releaseApiResponse: () => void; + fleetApiMock.responseProvider.checkPermissions.mockDelay.mockReturnValue( + new Promise((resolve) => { + releaseApiResponse = () => resolve(); + }) + ); + (useCurrentUser as jest.Mock).mockReturnValue(null); + + const { rerender } = render(); + expect(result.current).toEqual({ + canAccessEndpointManagement: false, + canAccessFleet: false, + loading: true, + }); + + // Make user service available + (useCurrentUser as jest.Mock).mockReturnValue(authenticatedUser); + rerender(); + expect(result.current).toEqual({ + canAccessEndpointManagement: false, + canAccessFleet: false, + loading: true, + }); + + // Release the API response + releaseApiResponse!(); + await fleetApiMock.waitForApi(); + expect(result.current).toEqual({ + canAccessEndpointManagement: true, + canAccessFleet: true, + loading: false, + }); + }); + + it('should call Fleet permissions api to determine user privilege to fleet', async () => { + render(); + await waitForNextUpdate(); + await fleetApiMock.waitForApi(); + expect(useHttp().get as jest.Mock).toHaveBeenCalledWith( + appRoutesService.getCheckPermissionsPath() + ); + }); + + it('should set privileges to false if user does not have superuser role', async () => { + authenticatedUser.roles = []; + render(); + await waitForNextUpdate(); + await fleetApiMock.waitForApi(); + expect(result.current).toEqual({ + canAccessEndpointManagement: false, + canAccessFleet: true, // this is only true here because I did not adjust the API mock + loading: false, + }); + }); + + it('should set privileges to false if fleet api check returns failure', async () => { + fleetApiMock.responseProvider.checkPermissions.mockReturnValue({ + error: 'MISSING_SECURITY', + success: false, + }); + + render(); + await waitForNextUpdate(); + await fleetApiMock.waitForApi(); + expect(result.current).toEqual({ + canAccessEndpointManagement: false, + canAccessFleet: false, + loading: false, + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/user_privileges/use_endpoint_privileges.ts b/x-pack/plugins/security_solution/public/common/components/user_privileges/use_endpoint_privileges.ts new file mode 100644 index 0000000000000..b8db0c5c0fbc9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/user_privileges/use_endpoint_privileges.ts @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useEffect, useMemo, useRef, useState } from 'react'; +import { useCurrentUser, useHttp } from '../../lib/kibana'; +import { appRoutesService, CheckPermissionsResponse } from '../../../../../fleet/common'; + +export interface EndpointPrivileges { + loading: boolean; + /** If user has permissions to access Fleet */ + canAccessFleet: boolean; + /** If user has permissions to access Endpoint management (includes check to ensure they also have access to fleet) */ + canAccessEndpointManagement: boolean; +} + +/** + * Retrieve the endpoint privileges for the current user. + * + * **NOTE:** Consider using `usePrivileges().endpointPrivileges` instead of this hook in order + * to keep API calls to a minimum. + */ +export const useEndpointPrivileges = (): EndpointPrivileges => { + const http = useHttp(); + const user = useCurrentUser(); + const isMounted = useRef(true); + const [canAccessFleet, setCanAccessFleet] = useState(false); + const [fleetCheckDone, setFleetCheckDone] = useState(false); + + // Check if user can access fleet + useEffect(() => { + (async () => { + try { + const fleetPermissionsResponse = await http.get( + appRoutesService.getCheckPermissionsPath() + ); + + if (isMounted.current) { + setCanAccessFleet(fleetPermissionsResponse.success); + } + } finally { + if (isMounted.current) { + setFleetCheckDone(true); + } + } + })(); + }, [http]); + + // Check if user has `superuser` role + const isSuperUser = useMemo(() => { + if (user?.roles) { + return user.roles.includes('superuser'); + } + return false; + }, [user?.roles]); + + const privileges = useMemo(() => { + return { + loading: !fleetCheckDone || !user, + canAccessFleet, + canAccessEndpointManagement: canAccessFleet && isSuperUser, + }; + }, [canAccessFleet, fleetCheckDone, isSuperUser, user]); + + // Capture if component is unmounted + useEffect( + () => () => { + isMounted.current = false; + }, + [] + ); + + return privileges; +}; diff --git a/x-pack/plugins/security_solution/public/common/hooks/endpoint/ingest_enabled.ts b/x-pack/plugins/security_solution/public/common/hooks/endpoint/ingest_enabled.ts deleted file mode 100644 index 18582e7064a7b..0000000000000 --- a/x-pack/plugins/security_solution/public/common/hooks/endpoint/ingest_enabled.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { ApplicationStart } from 'src/core/public'; -import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; -/** - * Returns an object which fleet permissions are allowed - */ -export const useIngestEnabledCheck = (): { - allEnabled: boolean; - show: boolean; - write: boolean; - read: boolean; -} => { - const { services } = useKibana<{ application: ApplicationStart }>(); - - // Check if Fleet is present in the configuration - const show = Boolean(services.application.capabilities.fleet?.show); - const write = Boolean(services.application.capabilities.fleet?.write); - const read = Boolean(services.application.capabilities.fleet?.read); - - // Check if all Fleet permissions are enabled - const allEnabled = show && read && write ? true : false; - - return { - allEnabled, - show, - write, - read, - }; -}; diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_upgrade_security_packages.ts b/x-pack/plugins/security_solution/public/common/hooks/use_upgrade_security_packages.ts index 6a3afccd8794d..ef1e658d349bf 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/use_upgrade_security_packages.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/use_upgrade_security_packages.ts @@ -8,14 +8,9 @@ import { useEffect } from 'react'; import { HttpFetchOptions, HttpStart } from 'kibana/public'; import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; -import { - epmRouteService, - appRoutesService, - CheckPermissionsResponse, - BulkInstallPackagesResponse, -} from '../../../../fleet/common'; +import { epmRouteService, BulkInstallPackagesResponse } from '../../../../fleet/common'; import { StartServices } from '../../types'; -import { useIngestEnabledCheck } from './endpoint/ingest_enabled'; +import { useUserPrivileges } from '../components/user_privileges'; /** * Requests that the endpoint and security_detection_engine package be upgraded to the latest version @@ -35,25 +30,9 @@ const sendUpgradeSecurityPackages = async ( }); }; -/** - * Checks with the ingest manager if the current user making these requests has the right permissions - * to install the endpoint package. - * - * @param http an http client for sending the request - * @param options an object containing options for the request - */ -const sendCheckPermissions = async ( - http: HttpStart, - options: HttpFetchOptions = {} -): Promise => { - return http.get(appRoutesService.getCheckPermissionsPath(), { - ...options, - }); -}; - export const useUpgradeSecurityPackages = () => { const context = useKibana(); - const { allEnabled: ingestEnabled } = useIngestEnabledCheck(); + const canAccessFleet = useUserPrivileges().endpointPrivileges.canAccessFleet; useEffect(() => { const abortController = new AbortController(); @@ -63,21 +42,11 @@ export const useUpgradeSecurityPackages = () => { abortController.abort(); }; - if (ingestEnabled) { + if (canAccessFleet) { const signal = abortController.signal; (async () => { try { - // make sure we're a privileged user before trying to install the package - const { success: hasPermissions } = await sendCheckPermissions(context.services.http, { - signal, - }); - - // if we're not a privileged user then return and don't try to check the status of the endpoint package - if (!hasPermissions) { - return abortRequests; - } - // ignore the response for now since we aren't notifying the user await sendUpgradeSecurityPackages(context.services.http, { signal }); } catch (error) { @@ -93,5 +62,5 @@ export const useUpgradeSecurityPackages = () => { return abortRequests; })(); } - }, [ingestEnabled, context.services.http]); + }, [canAccessFleet, context.services.http]); }; diff --git a/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx b/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx index 9ac7ae0f24322..d0755d05bdb5f 100644 --- a/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx +++ b/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx @@ -24,7 +24,7 @@ import { import { FieldHook } from '../../shared_imports'; import { SUB_PLUGINS_REDUCER } from './utils'; import { createSecuritySolutionStorageMock, localStorageMock } from './mock_local_storage'; -import { UserPrivilegesProvider } from '../../detections/components/user_privileges'; +import { UserPrivilegesProvider } from '../components/user_privileges'; const state: State = mockGlobalState; diff --git a/x-pack/plugins/security_solution/public/detections/components/callouts/missing_privileges_callout/use_missing_privileges.ts b/x-pack/plugins/security_solution/public/detections/components/callouts/missing_privileges_callout/use_missing_privileges.ts index dd139421e9ddd..73aa922251ee6 100644 --- a/x-pack/plugins/security_solution/public/detections/components/callouts/missing_privileges_callout/use_missing_privileges.ts +++ b/x-pack/plugins/security_solution/public/detections/components/callouts/missing_privileges_callout/use_missing_privileges.ts @@ -9,7 +9,7 @@ import { useMemo } from 'react'; import { SAVED_OBJECTS_MANAGEMENT_FEATURE_ID } from '../../../../../common/constants'; import { Privilege } from '../../../containers/detection_engine/alerts/types'; import { useUserData } from '../../user_info'; -import { useUserPrivileges } from '../../user_privileges'; +import { useUserPrivileges } from '../../../../common/components/user_privileges'; const REQUIRED_INDEX_PRIVILIGES = ['read', 'write', 'view_index_metadata', 'maintenance'] as const; diff --git a/x-pack/plugins/security_solution/public/detections/components/user_info/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/user_info/index.test.tsx index 83ca0026b8934..bb9ec01399f8d 100644 --- a/x-pack/plugins/security_solution/public/detections/components/user_info/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/user_info/index.test.tsx @@ -12,10 +12,11 @@ import { useUserInfo, ManageUserInfo } from './index'; import { useKibana } from '../../../common/lib/kibana'; import * as api from '../../containers/detection_engine/alerts/api'; import { TestProviders } from '../../../common/mock/test_providers'; -import { UserPrivilegesProvider } from '../user_privileges'; +import { UserPrivilegesProvider } from '../../../common/components/user_privileges'; jest.mock('../../../common/lib/kibana'); jest.mock('../../containers/detection_engine/alerts/api'); +jest.mock('../../../common/components/user_privileges/use_endpoint_privileges'); describe('useUserInfo', () => { beforeAll(() => { diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_alerts_privileges.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_alerts_privileges.test.tsx index 651d80f3165ab..f3afe83365286 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_alerts_privileges.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_alerts_privileges.test.tsx @@ -9,13 +9,13 @@ import { act, renderHook } from '@testing-library/react-hooks'; import produce from 'immer'; import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { useAppToastsMock } from '../../../../common/hooks/use_app_toasts.mock'; -import { useUserPrivileges } from '../../../components/user_privileges'; +import { useUserPrivileges } from '../../../../common/components/user_privileges'; import { Privilege } from './types'; import { UseAlertsPrivelegesReturn, useAlertsPrivileges } from './use_alerts_privileges'; jest.mock('./api'); jest.mock('../../../../common/hooks/use_app_toasts'); -jest.mock('../../../components/user_privileges'); +jest.mock('../../../../common/components/user_privileges'); const useUserPrivilegesMock = useUserPrivileges as jest.Mock>; @@ -86,6 +86,7 @@ const userPrivilegesInitial: ReturnType = { result: undefined, error: undefined, }, + endpointPrivileges: { loading: true, canAccessEndpointManagement: false, canAccessFleet: false }, }; describe('usePrivilegeUser', () => { diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_alerts_privileges.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_alerts_privileges.tsx index 08e28521e1473..005224a80c189 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_alerts_privileges.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_alerts_privileges.tsx @@ -6,7 +6,7 @@ */ import { useEffect, useState } from 'react'; -import { useUserPrivileges } from '../../../components/user_privileges'; +import { useUserPrivileges } from '../../../../common/components/user_privileges'; export interface UseAlertsPrivelegesReturn extends AlertsPrivelegesState { loading: boolean; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_signal_index.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_signal_index.test.tsx index ce262ce4f9a2e..ade83fed4fd6b 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_signal_index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_signal_index.test.tsx @@ -13,6 +13,7 @@ import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; jest.mock('./api'); jest.mock('../../../../common/hooks/use_app_toasts'); +jest.mock('../../../../common/components/user_privileges/use_endpoint_privileges'); describe('useSignalIndex', () => { let appToastsMock: jest.Mocked>; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/lists/use_lists_privileges.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/lists/use_lists_privileges.tsx index 0b7cd673c49f4..5f21f0287d7ea 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/lists/use_lists_privileges.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/lists/use_lists_privileges.tsx @@ -6,7 +6,7 @@ */ import { useEffect, useState } from 'react'; -import { useUserPrivileges } from '../../../components/user_privileges'; +import { useUserPrivileges } from '../../../../common/components/user_privileges'; import { Privilege } from '../alerts/types'; export interface UseListsPrivilegesState { diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/mocks.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/mocks.ts index 5498d139fd6e1..d6b24fa3cbdfc 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/mocks.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/mocks.ts @@ -25,6 +25,8 @@ import { } from '../../../../common/endpoint/constants'; import { AGENT_POLICY_API_ROUTES, + appRoutesService, + CheckPermissionsResponse, EPM_API_ROUTES, GetAgentPoliciesResponse, GetPackagesResponse, @@ -122,37 +124,66 @@ export const fleetGetPackageListHttpMock = httpHandlerMockFactory GetAgentPoliciesResponse; }>; -export const fleetGetAgentPolicyListHttpMock = httpHandlerMockFactory([ - { - id: 'agentPolicy', - path: AGENT_POLICY_API_ROUTES.LIST_PATTERN, - method: 'get', - handler: () => { - const generator = new EndpointDocGenerator('seed'); - const endpointMetadata = generator.generateHostMetadata(); - const agentPolicy = generator.generateAgentPolicy(); - - // Make sure that the Agent policy returned from the API has the Integration Policy ID that - // the endpoint metadata is using. This is needed especially when testing the Endpoint Details - // flyout where certain actions might be disabled if we know the endpoint integration policy no - // longer exists. - (agentPolicy.package_policies as string[]).push(endpointMetadata.Endpoint.policy.applied.id); - - return { - items: [agentPolicy], - perPage: 10, - total: 1, - page: 1, - }; +export const fleetGetAgentPolicyListHttpMock = httpHandlerMockFactory( + [ + { + id: 'agentPolicy', + path: AGENT_POLICY_API_ROUTES.LIST_PATTERN, + method: 'get', + handler: () => { + const generator = new EndpointDocGenerator('seed'); + const endpointMetadata = generator.generateHostMetadata(); + const agentPolicy = generator.generateAgentPolicy(); + + // Make sure that the Agent policy returned from the API has the Integration Policy ID that + // the endpoint metadata is using. This is needed especially when testing the Endpoint Details + // flyout where certain actions might be disabled if we know the endpoint integration policy no + // longer exists. + (agentPolicy.package_policies as string[]).push( + endpointMetadata.Endpoint.policy.applied.id + ); + + return { + items: [agentPolicy], + perPage: 10, + total: 1, + page: 1, + }; + }, }, - }, -]); + ] +); + +export type FleetGetCheckPermissionsInterface = ResponseProvidersInterface<{ + checkPermissions: () => CheckPermissionsResponse; +}>; + +export const fleetGetCheckPermissionsHttpMock = httpHandlerMockFactory( + [ + { + id: 'checkPermissions', + path: appRoutesService.getCheckPermissionsPath(), + method: 'get', + handler: () => { + return { + error: undefined, + success: true, + }; + }, + }, + ] +); type FleetApisHttpMockInterface = FleetGetPackageListHttpMockInterface & - FleetGetAgentPolicyListHttpMockInterface; + FleetGetAgentPolicyListHttpMockInterface & + FleetGetCheckPermissionsInterface; +/** + * Mocks all Fleet apis needed to render the Endpoint List/Details pages + */ export const fleetApisHttpMock = composeHttpHandlerMocks([ fleetGetPackageListHttpMock, fleetGetAgentPolicyListHttpMock, + fleetGetCheckPermissionsHttpMock, ]); type EndpointPageHttpMockInterface = EndpointMetadataHttpMocksInterface & diff --git a/x-pack/plugins/security_solution/public/management/pages/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/index.test.tsx index 9f2ed3618b06d..821e14edfda45 100644 --- a/x-pack/plugins/security_solution/public/management/pages/index.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/index.test.tsx @@ -10,11 +10,11 @@ import React from 'react'; import { ManagementContainer } from './index'; import '../../common/mock/match_media.ts'; import { AppContextTestRender, createAppRootMockRenderer } from '../../common/mock/endpoint'; -import { useIngestEnabledCheck } from '../../common/hooks/endpoint/ingest_enabled'; +import { useUserPrivileges } from '../../common/components/user_privileges'; -jest.mock('../../common/hooks/endpoint/ingest_enabled'); +jest.mock('../../common/components/user_privileges'); -describe('when in the Admistration tab', () => { +describe('when in the Administration tab', () => { let render: () => ReturnType; beforeEach(() => { @@ -23,14 +23,18 @@ describe('when in the Admistration tab', () => { mockedContext.history.push('/administration/endpoints'); }); - it('should display the No Permissions view when Ingest is OFF', async () => { - (useIngestEnabledCheck as jest.Mock).mockReturnValue({ allEnabled: false }); + it('should display the No Permissions if no sufficient privileges', async () => { + (useUserPrivileges as jest.Mock).mockReturnValue({ + endpointPrivileges: { loading: false, canAccessEndpointManagement: false }, + }); expect(await render().findByTestId('noIngestPermissions')).not.toBeNull(); }); - it('should display the Management view when Ingest is ON', async () => { - (useIngestEnabledCheck as jest.Mock).mockReturnValue({ allEnabled: true }); + it('should display the Management view if user has privileges', async () => { + (useUserPrivileges as jest.Mock).mockReturnValue({ + endpointPrivileges: { loading: false, canAccessEndpointManagement: true }, + }); expect(await render().findByTestId('endpointPage')).not.toBeNull(); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/index.tsx b/x-pack/plugins/security_solution/public/management/pages/index.tsx index 327dcd4458eeb..f348be6089923 100644 --- a/x-pack/plugins/security_solution/public/management/pages/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/index.tsx @@ -7,7 +7,7 @@ import React, { memo } from 'react'; import { Route, Switch, Redirect } from 'react-router-dom'; -import { EuiEmptyPrompt, EuiText } from '@elastic/eui'; +import { EuiEmptyPrompt, EuiLoadingSpinner, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { MANAGEMENT_ROUTING_ENDPOINTS_PATH, @@ -22,9 +22,9 @@ import { PolicyContainer } from './policy'; import { TrustedAppsContainer } from './trusted_apps'; import { MANAGEMENT_PATH, SecurityPageName } from '../../../common/constants'; import { SpyRoute } from '../../common/utils/route/spy_routes'; -import { useIngestEnabledCheck } from '../../common/hooks/endpoint/ingest_enabled'; import { EventFiltersContainer } from './event_filters'; import { getEndpointListPath } from '../common/routing'; +import { useUserPrivileges } from '../../common/components/user_privileges'; const NoPermissions = memo(() => { return ( @@ -80,9 +80,14 @@ const EventFilterTelemetry = () => ( ); export const ManagementContainer = memo(() => { - const { allEnabled: isIngestEnabled } = useIngestEnabledCheck(); + const { loading, canAccessEndpointManagement } = useUserPrivileges().endpointPrivileges; - if (!isIngestEnabled) { + // Lets wait until we can verify permissions + if (loading) { + return ; + } + + if (!canAccessEndpointManagement) { return ; } diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.test.tsx index f0198092ec1be..7af9f84ad0875 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { shallow, ShallowWrapper } from 'enzyme'; import { OverviewEmpty } from '.'; -import { useIngestEnabledCheck } from '../../../common/hooks/endpoint/ingest_enabled'; +import { useUserPrivileges } from '../../../common/components/user_privileges'; const endpointPackageVersion = '0.19.1'; @@ -20,8 +20,10 @@ jest.mock('../../../management/pages/endpoint_hosts/view/hooks', () => ({ useEndpointSelector: jest.fn().mockReturnValue({ endpointPackageVersion }), })); -jest.mock('../../../common/hooks/endpoint/ingest_enabled', () => ({ - useIngestEnabledCheck: jest.fn().mockReturnValue({ allEnabled: true }), +jest.mock('../../../common/components/user_privileges', () => ({ + useUserPrivileges: jest + .fn() + .mockReturnValue({ endpointPrivileges: { loading: false, canAccessFleet: true } }), })); jest.mock('../../../common/hooks/endpoint/use_navigate_to_app_event_handler', () => ({ @@ -36,7 +38,7 @@ describe('OverviewEmpty', () => { }); afterAll(() => { - (useIngestEnabledCheck as jest.Mock).mockReset(); + (useUserPrivileges as jest.Mock).mockReset(); }); test('render with correct actions ', () => { @@ -70,7 +72,9 @@ describe('OverviewEmpty', () => { describe('When isIngestEnabled = false', () => { let wrapper: ShallowWrapper; beforeAll(() => { - (useIngestEnabledCheck as jest.Mock).mockReturnValue({ allEnabled: false }); + (useUserPrivileges as jest.Mock).mockReturnValue({ + endpointPrivileges: { loading: false, canAccessFleet: false }, + }); wrapper = shallow(); }); diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.tsx index 028871d7be19d..c75438e18f5d5 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.tsx @@ -20,9 +20,9 @@ import { useIngestUrl, } from '../../../management/pages/endpoint_hosts/view/hooks'; import { useNavigateToAppEventHandler } from '../../../common/hooks/endpoint/use_navigate_to_app_event_handler'; -import { useIngestEnabledCheck } from '../../../common/hooks/endpoint/ingest_enabled'; import { CreateStructuredSelector } from '../../../common/store'; import { endpointPackageVersion as useEndpointPackageVersion } from '../../../management/pages/endpoint_hosts/store/selectors'; +import { useUserPrivileges } from '../../../common/components/user_privileges'; const OverviewEmptyComponent: React.FC = () => { const { http, docLinks } = useKibana().services; @@ -40,7 +40,7 @@ const OverviewEmptyComponent: React.FC = () => { const handleEndpointClick = useNavigateToAppEventHandler('fleet', { path: endpointIntegrationUrl, }); - const { allEnabled: isIngestEnabled } = useIngestEnabledCheck(); + const canAccessFleet = useUserPrivileges().endpointPrivileges.canAccessFleet; const emptyPageActions: EmptyPageActionsProps = useMemo( () => ({ @@ -72,7 +72,7 @@ const OverviewEmptyComponent: React.FC = () => { [emptyPageActions] ); - return isIngestEnabled === true ? ( + return canAccessFleet === true ? ( ({ jest.mock('../../common/components/query_bar', () => ({ QueryBar: () => null, })); -jest.mock('../../common/hooks/endpoint/ingest_enabled'); +jest.mock('../../common/components/user_privileges', () => { + return { + ...jest.requireActual('../../common/components/user_privileges'), + useUserPrivileges: jest.fn(() => { + return { + listPrivileges: { loading: false, error: undefined, result: undefined }, + detectionEnginePrivileges: { loading: false, error: undefined, result: undefined }, + endpointPrivileges: { + loading: false, + canAccessEndpointManagement: true, + canAccessFleet: true, + }, + }; + }), + }; +}); jest.mock('../../common/containers/local_storage/use_messages_storage'); jest.mock('../containers/overview_cti_links'); jest.mock('../containers/overview_cti_links/use_cti_event_counts'); jest.mock('../containers/overview_cti_links'); + const useCtiDashboardLinksMock = useCtiDashboardLinks as jest.Mock; useCtiDashboardLinksMock.mockReturnValue(mockCtiLinksResponse); @@ -74,12 +95,25 @@ const endpointNoticeMessage = (hasMessageValue: boolean) => { }; }; const mockUseSourcererScope = useSourcererScope as jest.Mock; -const mockUseIngestEnabledCheck = useIngestEnabledCheck as jest.Mock; +const mockUseUserPrivileges = useUserPrivileges as jest.Mock; const mockUseFetchIndex = useFetchIndex as jest.Mock; const mockUseMessagesStorage: jest.Mock = useMessagesStorage as jest.Mock; describe('Overview', () => { + const loadedUserPrivilegesState = ( + endpointOverrides: Partial = {} + ): ReturnType => + merge(initialUserPrivilegesState(), { + endpointPrivileges: { + loading: false, + canAccessFleet: true, + canAccessEndpointManagement: true, + ...endpointOverrides, + }, + }); + beforeEach(() => { + mockUseUserPrivileges.mockReturnValue(loadedUserPrivilegesState()); mockUseFetchIndex.mockReturnValue([ false, { @@ -88,6 +122,10 @@ describe('Overview', () => { ]); }); + afterAll(() => { + mockUseUserPrivileges.mockReset(); + }); + describe('rendering', () => { test('it DOES NOT render the Getting started text when an index is available', () => { mockUseSourcererScope.mockReturnValue({ @@ -97,7 +135,6 @@ describe('Overview', () => { }); mockUseMessagesStorage.mockImplementation(() => endpointNoticeMessage(false)); - mockUseIngestEnabledCheck.mockReturnValue({ allEnabled: true }); const wrapper = mount( @@ -125,7 +162,6 @@ describe('Overview', () => { }); mockUseMessagesStorage.mockImplementation(() => endpointNoticeMessage(false)); - mockUseIngestEnabledCheck.mockReturnValue({ allEnabled: true }); const wrapper = mount( @@ -153,7 +189,6 @@ describe('Overview', () => { }); mockUseMessagesStorage.mockImplementation(() => endpointNoticeMessage(true)); - mockUseIngestEnabledCheck.mockReturnValue({ allEnabled: true }); const wrapper = mount( @@ -175,7 +210,6 @@ describe('Overview', () => { }); mockUseMessagesStorage.mockImplementation(() => endpointNoticeMessage(true)); - mockUseIngestEnabledCheck.mockReturnValue({ allEnabled: true }); const wrapper = mount( @@ -197,7 +231,6 @@ describe('Overview', () => { }); mockUseMessagesStorage.mockImplementation(() => endpointNoticeMessage(false)); - mockUseIngestEnabledCheck.mockReturnValue({ allEnabled: true }); const wrapper = mount( @@ -219,7 +252,7 @@ describe('Overview', () => { }); mockUseMessagesStorage.mockImplementation(() => endpointNoticeMessage(true)); - mockUseIngestEnabledCheck.mockReturnValue({ allEnabled: false }); + mockUseUserPrivileges.mockReturnValue(loadedUserPrivilegesState({ canAccessFleet: false })); const wrapper = mount( @@ -239,7 +272,7 @@ describe('Overview', () => { selectedPatterns: [], indicesExist: false, }); - mockUseIngestEnabledCheck.mockReturnValue({ allEnabled: false }); + mockUseUserPrivileges.mockReturnValue(loadedUserPrivilegesState({ canAccessFleet: false })); mockUseMessagesStorage.mockImplementation(() => endpointNoticeMessage(false)); }); @@ -266,7 +299,7 @@ describe('Overview', () => { }); it('shows Endpoint get ready button when ingest is enabled', () => { - mockUseIngestEnabledCheck.mockReturnValue({ allEnabled: true }); + mockUseUserPrivileges.mockReturnValue(loadedUserPrivilegesState({ canAccessFleet: true })); const wrapper = mount( diff --git a/x-pack/plugins/security_solution/public/overview/pages/overview.tsx b/x-pack/plugins/security_solution/public/overview/pages/overview.tsx index 174141db9bfb1..ed12dce6db482 100644 --- a/x-pack/plugins/security_solution/public/overview/pages/overview.tsx +++ b/x-pack/plugins/security_solution/public/overview/pages/overview.tsx @@ -27,13 +27,13 @@ import { SecurityPageName } from '../../app/types'; import { EndpointNotice } from '../components/endpoint_notice'; import { useMessagesStorage } from '../../common/containers/local_storage/use_messages_storage'; import { ENDPOINT_METADATA_INDEX } from '../../../common/constants'; -import { useIngestEnabledCheck } from '../../common/hooks/endpoint/ingest_enabled'; import { useSourcererScope } from '../../common/containers/sourcerer'; import { Sourcerer } from '../../common/components/sourcerer'; import { SourcererScopeName } from '../../common/store/sourcerer/model'; import { useDeepEqualSelector } from '../../common/hooks/use_selector'; import { ThreatIntelLinkPanel } from '../components/overview_cti_links'; import { useIsThreatIntelModuleEnabled } from '../containers/overview_cti_links/use_is_threat_intel_module_enabled'; +import { useUserPrivileges } from '../../common/components/user_privileges'; const SidebarFlexItem = styled(EuiFlexItem)` margin-right: 24px; @@ -70,9 +70,8 @@ const OverviewComponent = () => { setDismissMessage(true); addMessage('management', 'dismissEndpointNotice'); }, [addMessage]); - const { allEnabled: isIngestEnabled } = useIngestEnabledCheck(); + const canAccessFleet = useUserPrivileges().endpointPrivileges.canAccessFleet; const isThreatIntelModuleEnabled = useIsThreatIntelModuleEnabled(); - return ( <> {indicesExist ? ( @@ -82,7 +81,7 @@ const OverviewComponent = () => { - {!dismissMessage && !metadataIndexExists && isIngestEnabled && ( + {!dismissMessage && !metadataIndexExists && canAccessFleet && ( <> From 151dafc2f2ad58cd1d77913a3cbd5154b6be7975 Mon Sep 17 00:00:00 2001 From: Yara Tercero Date: Thu, 8 Jul 2021 16:29:40 -0700 Subject: [PATCH 03/49] [Security Solution][Exceptions] - update exception lists page title and exceptions navigation title (#104803) ### Summary Updates exception lists page title and exceptions navigation title to "Exceptions" --- .../security_solution/public/app/deep_links/index.ts | 2 +- .../plugins/security_solution/public/app/translations.ts | 2 +- .../use_security_solution_navigation/index.test.tsx | 2 +- .../rules/all/exceptions/exceptions_table.tsx | 3 ++- .../rules/all/exceptions/translations.ts | 9 ++++++++- 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/security_solution/public/app/deep_links/index.ts b/x-pack/plugins/security_solution/public/app/deep_links/index.ts index e1c14f2a86380..f5cec592c7abf 100644 --- a/x-pack/plugins/security_solution/public/app/deep_links/index.ts +++ b/x-pack/plugins/security_solution/public/app/deep_links/index.ts @@ -165,7 +165,7 @@ const nestedDeepLinks: SecurityDeepLinks = { navLinkStatus: AppNavLinkStatus.hidden, keywords: [ i18n.translate('xpack.securitySolution.search.exceptions', { - defaultMessage: 'Exception list', + defaultMessage: 'Exceptions', }), ], searchable: true, diff --git a/x-pack/plugins/security_solution/public/app/translations.ts b/x-pack/plugins/security_solution/public/app/translations.ts index 847a9114d94bd..027789713a2ae 100644 --- a/x-pack/plugins/security_solution/public/app/translations.ts +++ b/x-pack/plugins/security_solution/public/app/translations.ts @@ -24,7 +24,7 @@ export const RULES = i18n.translate('xpack.securitySolution.navigation.rules', { }); export const EXCEPTIONS = i18n.translate('xpack.securitySolution.navigation.exceptions', { - defaultMessage: 'Exception list', + defaultMessage: 'Exceptions', }); export const ALERTS = i18n.translate('xpack.securitySolution.navigation.alerts', { diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx index e3549aa6ec047..af88aacb7602a 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx @@ -141,7 +141,7 @@ describe('useSecuritySolutionNavigation', () => { "href": "securitySolution/exceptions?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", "id": "exceptions", "isSelected": false, - "name": "Exception list", + "name": "Exceptions", "onClick": [Function], }, ], diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.tsx index b4f5efe2348bb..206976e6c0c1a 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.tsx @@ -336,7 +336,8 @@ export const ExceptionListsTable = React.memo(() => { <> <> diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/translations.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/translations.ts index 0dd016425f4e6..912f5bec4de35 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/translations.ts @@ -73,7 +73,14 @@ export const EXCEPTIONS_LISTS_SEARCH_PLACEHOLDER = i18n.translate( export const ALL_EXCEPTIONS = i18n.translate( 'xpack.securitySolution.detectionEngine.rules.allExceptions.tableTitle', { - defaultMessage: 'Exception Lists', + defaultMessage: 'Exceptions', + } +); + +export const ALL_EXCEPTIONS_DESCRIPTION = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allExceptions.tableTitleDescription', + { + defaultMessage: 'Exceptions are automatically grouped into exception lists.', } ); From 6d9d1db6ace763fb4336c16d7856d6ee14573186 Mon Sep 17 00:00:00 2001 From: Yara Tercero Date: Thu, 8 Jul 2021 16:30:00 -0700 Subject: [PATCH 04/49] [Security Solution] - Updates breadcrumbs text for returning to rules page (#104805) ### Summary Updates breadcrumbs text for returning to rules page from rule creation and rule details. --- .../pages/detection_engine/rules/create/translations.ts | 4 ++-- .../pages/detection_engine/rules/details/translations.ts | 4 ++-- x-pack/plugins/translations/translations/ja-JP.json | 2 -- x-pack/plugins/translations/translations/zh-CN.json | 2 -- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/translations.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/translations.ts index 5a49ab349c094..f4292f6c663cc 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/translations.ts @@ -15,9 +15,9 @@ export const PAGE_TITLE = i18n.translate( ); export const BACK_TO_RULES = i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.backToRulesDescription', + 'xpack.securitySolution.detectionEngine.createRule.backToRulesButton', { - defaultMessage: 'Back to detection rules', + defaultMessage: 'Rules', } ); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/translations.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/translations.ts index e42fde569bd67..ca3e5a4587a09 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/translations.ts @@ -15,9 +15,9 @@ export const PAGE_TITLE = i18n.translate( ); export const BACK_TO_RULES = i18n.translate( - 'xpack.securitySolution.detectionEngine.ruleDetails.backToRulesDescription', + 'xpack.securitySolution.detectionEngine.ruleDetails.backToRulesButton', { - defaultMessage: 'Back to detection rules', + defaultMessage: 'Rules', } ); diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index b21d4afbe0020..3809d55ff90be 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -19002,7 +19002,6 @@ "xpack.securitySolution.detectionEngine.components.importRuleModal.selectRuleDescription": "インポートするセキュリティルール (検出エンジンビューからエクスポートしたもの) を選択します", "xpack.securitySolution.detectionEngine.createRule. stepScheduleRule.completeWithActivatingTitle": "ルールの作成と有効化", "xpack.securitySolution.detectionEngine.createRule. stepScheduleRule.completeWithoutActivatingTitle": "有効化せずにルールを作成", - "xpack.securitySolution.detectionEngine.createRule.backToRulesDescription": "検出ルールに戻る", "xpack.securitySolution.detectionEngine.createRule.editRuleButton": "編集", "xpack.securitySolution.detectionEngine.createRule.eqlRuleTypeDescription": "イベント相関関係", "xpack.securitySolution.detectionEngine.createRule.filtersLabel": "フィルター", @@ -19744,7 +19743,6 @@ "xpack.securitySolution.detectionEngine.ruleDescription.thresholdResultsAggregatedByDescription": "結果集約条件", "xpack.securitySolution.detectionEngine.ruleDescription.thresholdResultsAllDescription": "すべての結果", "xpack.securitySolution.detectionEngine.ruleDetails.activatedRuleLabel": "有効化", - "xpack.securitySolution.detectionEngine.ruleDetails.backToRulesDescription": "検出ルールに戻る", "xpack.securitySolution.detectionEngine.ruleDetails.errorCalloutTitle": "ルール失敗", "xpack.securitySolution.detectionEngine.ruleDetails.exceptionsTab": "例外", "xpack.securitySolution.detectionEngine.ruleDetails.experimentalDescription": "実験的", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 27a5a06f9ca7c..6540d9f49399a 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -19281,7 +19281,6 @@ "xpack.securitySolution.detectionEngine.components.importRuleModal.successfullyImportedRulesTitle": "已成功导入 {totalRules} 个{totalRules, plural, other {规则}}", "xpack.securitySolution.detectionEngine.createRule. stepScheduleRule.completeWithActivatingTitle": "创建并激活规则", "xpack.securitySolution.detectionEngine.createRule. stepScheduleRule.completeWithoutActivatingTitle": "创建规则但不激活", - "xpack.securitySolution.detectionEngine.createRule.backToRulesDescription": "返回到检测规则", "xpack.securitySolution.detectionEngine.createRule.editRuleButton": "编辑", "xpack.securitySolution.detectionEngine.createRule.eqlRuleTypeDescription": "事件关联", "xpack.securitySolution.detectionEngine.createRule.filtersLabel": "筛选", @@ -20026,7 +20025,6 @@ "xpack.securitySolution.detectionEngine.ruleDescription.thresholdResultsAggregatedByDescription": "结果聚合依据", "xpack.securitySolution.detectionEngine.ruleDescription.thresholdResultsAllDescription": "所有结果", "xpack.securitySolution.detectionEngine.ruleDetails.activatedRuleLabel": "已激活", - "xpack.securitySolution.detectionEngine.ruleDetails.backToRulesDescription": "返回到检测规则", "xpack.securitySolution.detectionEngine.ruleDetails.errorCalloutTitle": "规则错误位置", "xpack.securitySolution.detectionEngine.ruleDetails.exceptionsTab": "例外", "xpack.securitySolution.detectionEngine.ruleDetails.experimentalDescription": "实验性", From fe6eb09936e671e1ecc80c5018bf97e2c412b1ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Kopyci=C5=84ski?= Date: Fri, 9 Jul 2021 02:39:46 +0300 Subject: [PATCH 05/49] [Osquery] Fix 7.14 UX issues (#104257) --- package.json | 2 +- .../action_results/action_agents_status.tsx | 91 +++++++++++ .../action_agents_status_badges.tsx | 50 ++++++ .../action_agents_status_bar.tsx | 46 ++++++ .../action_results/action_results_summary.tsx | 126 ++++----------- .../action_results/services/agent_status.tsx | 59 +++++++ .../osquery/public/action_results/types.ts | 8 + .../action_results/use_action_results.ts | 4 +- .../agent_policies/agents_policy_link.tsx | 4 +- .../agent_policies/use_agent_policies.ts | 1 - ...managed_policy_create_import_extension.tsx | 2 +- x-pack/plugins/osquery/public/index.ts | 2 +- .../live_queries/agent_results/index.tsx | 30 ---- .../public/live_queries/form/index.tsx | 149 +++++++++++------- x-pack/plugins/osquery/public/plugin.ts | 10 +- .../osquery/public/queries/edit/tabs.tsx | 78 --------- .../osquery/public/results/results_table.tsx | 74 ++++++--- .../osquery/public/results/translations.ts | 2 +- .../osquery/public/results/use_all_results.ts | 2 +- .../routes/live_queries/details/index.tsx | 92 +---------- .../routes/saved_queries/edit/index.tsx | 2 +- .../public/routes/saved_queries/edit/tabs.tsx | 27 ++-- .../public/saved_queries/form/index.tsx | 8 +- .../public/saved_queries/use_saved_queries.ts | 2 +- .../queries/query_flyout.tsx | 27 +++- .../scheduled_query_groups/queries/schema.tsx | 10 +- .../public/shared_components/index.tsx | 8 + .../shared_components/lazy_osquery_action.tsx | 18 +++ .../osquery_action/index.tsx | 99 ++++++++++++ x-pack/plugins/osquery/public/types.ts | 6 +- .../factory/results/query.all_results.dsl.ts | 7 +- .../translations/translations/ja-JP.json | 3 - .../translations/translations/zh-CN.json | 3 - yarn.lock | 8 +- 34 files changed, 625 insertions(+), 435 deletions(-) create mode 100644 x-pack/plugins/osquery/public/action_results/action_agents_status.tsx create mode 100644 x-pack/plugins/osquery/public/action_results/action_agents_status_badges.tsx create mode 100644 x-pack/plugins/osquery/public/action_results/action_agents_status_bar.tsx create mode 100644 x-pack/plugins/osquery/public/action_results/services/agent_status.tsx create mode 100644 x-pack/plugins/osquery/public/action_results/types.ts delete mode 100644 x-pack/plugins/osquery/public/live_queries/agent_results/index.tsx delete mode 100644 x-pack/plugins/osquery/public/queries/edit/tabs.tsx create mode 100644 x-pack/plugins/osquery/public/shared_components/index.tsx create mode 100644 x-pack/plugins/osquery/public/shared_components/lazy_osquery_action.tsx create mode 100644 x-pack/plugins/osquery/public/shared_components/osquery_action/index.tsx diff --git a/package.json b/package.json index 688c73f44a4ef..8f56d80c584ea 100644 --- a/package.json +++ b/package.json @@ -351,7 +351,7 @@ "react-moment-proptypes": "^1.7.0", "react-monaco-editor": "^0.41.2", "react-popper-tooltip": "^2.10.1", - "react-query": "^3.13.10", + "react-query": "^3.18.1", "react-redux": "^7.2.0", "react-resizable": "^1.7.5", "react-resize-detector": "^4.2.0", diff --git a/x-pack/plugins/osquery/public/action_results/action_agents_status.tsx b/x-pack/plugins/osquery/public/action_results/action_agents_status.tsx new file mode 100644 index 0000000000000..2de5ab11664ae --- /dev/null +++ b/x-pack/plugins/osquery/public/action_results/action_agents_status.tsx @@ -0,0 +1,91 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import React, { useEffect, useMemo, useState } from 'react'; + +import { Direction } from '../../common/search_strategy'; +import { AgentStatusBar } from './action_agents_status_bar'; +import { ActionAgentsStatusBadges } from './action_agents_status_badges'; +import { useActionResults } from './use_action_results'; + +interface ActionAgentsStatusProps { + actionId: string; + expirationDate?: string; + agentIds?: string[]; +} + +const ActionAgentsStatusComponent: React.FC = ({ + actionId, + expirationDate, + agentIds, +}) => { + const [isLive, setIsLive] = useState(true); + const expired = useMemo(() => (!expirationDate ? false : new Date(expirationDate) < new Date()), [ + expirationDate, + ]); + const { + // @ts-expect-error update types + data: { aggregations }, + } = useActionResults({ + actionId, + activePage: 0, + agentIds, + limit: 0, + direction: Direction.asc, + sortField: '@timestamp', + isLive, + }); + + const agentStatus = useMemo(() => { + const notRespondedCount = !agentIds?.length ? 0 : agentIds.length - aggregations.totalResponded; + + return { + success: aggregations.successful, + pending: notRespondedCount, + failed: aggregations.failed, + }; + }, [agentIds?.length, aggregations.failed, aggregations.successful, aggregations.totalResponded]); + + useEffect( + () => + setIsLive(() => { + if (!agentIds?.length || expired) return false; + + return !!(aggregations.totalResponded !== agentIds?.length); + }), + [agentIds?.length, aggregations.totalResponded, expired] + ); + + return ( + <> + + + + + + + + + + + + + + + + + ); +}; + +export const ActionAgentsStatus = React.memo(ActionAgentsStatusComponent); diff --git a/x-pack/plugins/osquery/public/action_results/action_agents_status_badges.tsx b/x-pack/plugins/osquery/public/action_results/action_agents_status_badges.tsx new file mode 100644 index 0000000000000..95b96ca454610 --- /dev/null +++ b/x-pack/plugins/osquery/public/action_results/action_agents_status_badges.tsx @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiFlexGroup, EuiHealth, EuiNotificationBadge, EuiFlexItem } from '@elastic/eui'; +import React, { memo } from 'react'; + +import { + AGENT_STATUSES, + getColorForAgentStatus, + getLabelForAgentStatus, +} from './services/agent_status'; +import type { ActionAgentStatus } from './types'; + +export const ActionAgentsStatusBadges = memo<{ + agentStatus: { [k in ActionAgentStatus]: number }; + expired: boolean; +}>(({ agentStatus, expired }) => ( + + {AGENT_STATUSES.map((status) => ( + + + + ))} + +)); + +ActionAgentsStatusBadges.displayName = 'ActionAgentsStatusBadges'; + +const AgentStatusBadge = memo<{ expired: boolean; status: ActionAgentStatus; count: number }>( + ({ expired, status, count }) => ( + <> + + + {getLabelForAgentStatus(status, expired)} + + + {count} + + + + + + ) +); + +AgentStatusBadge.displayName = 'AgentStatusBadge'; diff --git a/x-pack/plugins/osquery/public/action_results/action_agents_status_bar.tsx b/x-pack/plugins/osquery/public/action_results/action_agents_status_bar.tsx new file mode 100644 index 0000000000000..21866566cb7e3 --- /dev/null +++ b/x-pack/plugins/osquery/public/action_results/action_agents_status_bar.tsx @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import styled from 'styled-components'; +import { EuiColorPaletteDisplay } from '@elastic/eui'; +import React, { useMemo } from 'react'; + +import { AGENT_STATUSES, getColorForAgentStatus } from './services/agent_status'; +import type { ActionAgentStatus } from './types'; + +const StyledEuiColorPaletteDisplay = styled(EuiColorPaletteDisplay)` + &.osquery-action-agent-status-bar { + border: none; + border-radius: 0; + &:after { + border: none; + } + } +`; + +export const AgentStatusBar: React.FC<{ + agentStatus: { [k in ActionAgentStatus]: number }; +}> = ({ agentStatus }) => { + const palette = useMemo(() => { + let stop = 0; + return AGENT_STATUSES.reduce((acc, status) => { + stop += agentStatus[status] || 0; + acc.push({ + stop, + color: getColorForAgentStatus(status), + }); + return acc; + }, [] as Array<{ stop: number; color: string }>); + }, [agentStatus]); + return ( + + ); +}; 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 257c89047aab0..d3b0e38a5e033 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 @@ -8,20 +8,8 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import { i18n } from '@kbn/i18n'; -import { - EuiLink, - EuiFlexGroup, - EuiFlexItem, - EuiCard, - EuiTextColor, - EuiSpacer, - EuiDescriptionList, - EuiInMemoryTable, - EuiCodeBlock, - EuiProgress, -} from '@elastic/eui'; -import React, { useCallback, useMemo, useState } from 'react'; -import styled from 'styled-components'; +import { EuiLink, EuiInMemoryTable, EuiCodeBlock } from '@elastic/eui'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { PLUGIN_ID } from '../../../fleet/common'; import { pagePathGetters } from '../../../fleet/public'; @@ -30,15 +18,10 @@ import { useAllResults } from '../results/use_all_results'; import { Direction } from '../../common/search_strategy'; import { useKibana } from '../common/lib/kibana'; -const StyledEuiCard = styled(EuiCard)` - position: relative; -`; - interface ActionResultsSummaryProps { actionId: string; - expirationDate: Date; + expirationDate?: string; agentIds?: string[]; - isLive?: boolean; } const renderErrorMessage = (error: string) => ( @@ -51,14 +34,16 @@ const ActionResultsSummaryComponent: React.FC = ({ actionId, expirationDate, agentIds, - isLive, }) => { const getUrlForApp = useKibana().services.application.getUrlForApp; // @ts-expect-error update types const [pageIndex, setPageIndex] = useState(0); // @ts-expect-error update types const [pageSize, setPageSize] = useState(50); - const expired = useMemo(() => expirationDate < new Date(), [expirationDate]); + const expired = useMemo(() => (!expirationDate ? false : new Date(expirationDate) < new Date()), [ + expirationDate, + ]); + const [isLive, setIsLive] = useState(true); const { // @ts-expect-error update types data: { aggregations, edges }, @@ -69,7 +54,7 @@ const ActionResultsSummaryComponent: React.FC = ({ limit: pageSize, direction: Direction.asc, sortField: '@timestamp', - isLive: !expired && isLive, + isLive, }); const { data: logsResults } = useAllResults({ @@ -82,64 +67,15 @@ const ActionResultsSummaryComponent: React.FC = ({ direction: Direction.asc, }, ], - isLive: !expired && isLive, + isLive, }); - const notRespondedCount = useMemo(() => { - if (!agentIds || !aggregations.totalResponded) { - return '-'; - } - - return agentIds.length - aggregations.totalResponded; - }, [aggregations.totalResponded, agentIds]); - - const listItems = useMemo( - () => [ - { - title: i18n.translate( - 'xpack.osquery.liveQueryActionResults.summary.agentsQueriedLabelText', - { - defaultMessage: 'Agents queried', - } - ), - description: agentIds?.length, - }, - { - title: i18n.translate('xpack.osquery.liveQueryActionResults.summary.successfulLabelText', { - defaultMessage: 'Successful', - }), - description: aggregations.successful, - }, - { - title: expired - ? i18n.translate('xpack.osquery.liveQueryActionResults.summary.expiredLabelText', { - defaultMessage: 'Expired', - }) - : i18n.translate('xpack.osquery.liveQueryActionResults.summary.pendingLabelText', { - defaultMessage: 'Not yet responded', - }), - description: notRespondedCount, - }, - { - title: i18n.translate('xpack.osquery.liveQueryActionResults.summary.failedLabelText', { - defaultMessage: 'Failed', - }), - description: ( - - {aggregations.failed} - - ), - }, - ], - [agentIds, aggregations.failed, aggregations.successful, notRespondedCount, expired] - ); - const renderAgentIdColumn = useCallback( (agentId) => ( @@ -236,30 +172,26 @@ const ActionResultsSummaryComponent: React.FC = ({ [] ); - return ( - <> - - - - {!expired && notRespondedCount ? : null} - - - - + useEffect(() => { + setIsLive(() => { + if (!agentIds?.length || expired) return false; - {edges.length ? ( - <> - - - - ) : null} - - ); + const uniqueAgentsRepliedCount = + // @ts-expect-error update types + logsResults?.rawResponse.aggregations?.unique_agents.value ?? 0; + + return !!(uniqueAgentsRepliedCount !== agentIds?.length - aggregations.failed); + }); + }, [ + agentIds?.length, + aggregations.failed, + expired, + logsResults?.rawResponse.aggregations?.unique_agents, + ]); + + return edges.length ? ( + + ) : null; }; export const ActionResultsSummary = React.memo(ActionResultsSummaryComponent); diff --git a/x-pack/plugins/osquery/public/action_results/services/agent_status.tsx b/x-pack/plugins/osquery/public/action_results/services/agent_status.tsx new file mode 100644 index 0000000000000..39a033f49ec90 --- /dev/null +++ b/x-pack/plugins/osquery/public/action_results/services/agent_status.tsx @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { euiPaletteColorBlindBehindText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import type { ActionAgentStatus } from '../types'; + +const visColors = euiPaletteColorBlindBehindText(); +const colorToHexMap = { + default: '#d3dae6', + primary: visColors[1], + secondary: visColors[0], + accent: visColors[2], + warning: visColors[5], + danger: visColors[9], +}; + +export const AGENT_STATUSES: ActionAgentStatus[] = ['success', 'pending', 'failed']; + +export function getColorForAgentStatus(agentStatus: ActionAgentStatus): string { + switch (agentStatus) { + case 'success': + return colorToHexMap.secondary; + case 'pending': + return colorToHexMap.default; + case 'failed': + return colorToHexMap.danger; + default: + throw new Error(`Unsupported action agent status ${agentStatus}`); + } +} + +export function getLabelForAgentStatus(agentStatus: ActionAgentStatus, expired: boolean): string { + switch (agentStatus) { + case 'success': + return i18n.translate('xpack.osquery.liveQueryActionResults.summary.successfulLabelText', { + defaultMessage: 'Successful', + }); + case 'pending': + return expired + ? i18n.translate('xpack.osquery.liveQueryActionResults.summary.expiredLabelText', { + defaultMessage: 'Expired', + }) + : i18n.translate('xpack.osquery.liveQueryActionResults.summary.pendingLabelText', { + defaultMessage: 'Not yet responded', + }); + case 'failed': + return i18n.translate('xpack.osquery.liveQueryActionResults.summary.failedLabelText', { + defaultMessage: 'Failed', + }); + default: + throw new Error(`Unsupported action agent status ${agentStatus}`); + } +} diff --git a/x-pack/plugins/osquery/public/action_results/types.ts b/x-pack/plugins/osquery/public/action_results/types.ts new file mode 100644 index 0000000000000..ce9415986ba02 --- /dev/null +++ b/x-pack/plugins/osquery/public/action_results/types.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export type ActionAgentStatus = 'success' | 'pending' | 'failed'; diff --git a/x-pack/plugins/osquery/public/action_results/use_action_results.ts b/x-pack/plugins/osquery/public/action_results/use_action_results.ts index ab69bf86dc326..29bff0819956a 100644 --- a/x-pack/plugins/osquery/public/action_results/use_action_results.ts +++ b/x-pack/plugins/osquery/public/action_results/use_action_results.ts @@ -83,7 +83,7 @@ export const useActionResults = ({ const totalResponded = // @ts-expect-error update types - responseData.rawResponse?.aggregations?.aggs.responses_by_action_id?.doc_count; + responseData.rawResponse?.aggregations?.aggs.responses_by_action_id?.doc_count ?? 0; const aggsBuckets = // @ts-expect-error update types responseData.rawResponse?.aggregations?.aggs.responses_by_action_id?.responses.buckets; @@ -120,7 +120,7 @@ export const useActionResults = ({ failed: 0, }, }, - refetchInterval: isLive ? 1000 : false, + refetchInterval: isLive ? 5000 : false, keepPreviousData: true, enabled: !skip && !!agentIds?.length, onSuccess: () => setErrorToast(), diff --git a/x-pack/plugins/osquery/public/agent_policies/agents_policy_link.tsx b/x-pack/plugins/osquery/public/agent_policies/agents_policy_link.tsx index 98402e349c77d..81953135b5321 100644 --- a/x-pack/plugins/osquery/public/agent_policies/agents_policy_link.tsx +++ b/x-pack/plugins/osquery/public/agent_policies/agents_policy_link.tsx @@ -27,7 +27,7 @@ const AgentsPolicyLinkComponent: React.FC = ({ policyId } const href = useMemo( () => getUrlForApp(PLUGIN_ID, { - path: `#` + pagePathGetters.policy_details({ policyId }), + path: `#` + pagePathGetters.policy_details({ policyId })[1], }), [getUrlForApp, policyId] ); @@ -38,7 +38,7 @@ const AgentsPolicyLinkComponent: React.FC = ({ policyId } event.preventDefault(); return navigateToApp(PLUGIN_ID, { - path: `#` + pagePathGetters.policy_details({ policyId }), + path: `#` + pagePathGetters.policy_details({ policyId })[1], }); } }, diff --git a/x-pack/plugins/osquery/public/agent_policies/use_agent_policies.ts b/x-pack/plugins/osquery/public/agent_policies/use_agent_policies.ts index 6f87610667198..c51f2d2f44a5c 100644 --- a/x-pack/plugins/osquery/public/agent_policies/use_agent_policies.ts +++ b/x-pack/plugins/osquery/public/agent_policies/use_agent_policies.ts @@ -30,7 +30,6 @@ export const useAgentPolicies = () => { }), { initialData: { items: [], total: 0, page: 1, perPage: 100 }, - placeholderData: [], keepPreviousData: true, select: (response) => response.items, onSuccess: () => setErrorToast(), diff --git a/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx b/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx index 28d69a6a7b15a..63036f5f693f7 100644 --- a/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx +++ b/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx @@ -57,7 +57,7 @@ export const OsqueryManagedPolicyCreateImportExtension = React.memo< return getUrlForApp(PLUGIN_ID, { path: `#` + - pagePathGetters.policy_details({ policyId: policy?.policy_id }) + + pagePathGetters.policy_details({ policyId: policy?.policy_id })[1] + '?openEnrollmentFlyout=true', }); }, [getUrlForApp, policy?.policy_id]); diff --git a/x-pack/plugins/osquery/public/index.ts b/x-pack/plugins/osquery/public/index.ts index f0e956b64ee06..fadd61cce85ef 100644 --- a/x-pack/plugins/osquery/public/index.ts +++ b/x-pack/plugins/osquery/public/index.ts @@ -13,4 +13,4 @@ import { OsqueryPlugin } from './plugin'; export function plugin(initializerContext: PluginInitializerContext) { return new OsqueryPlugin(initializerContext); } -export { OsqueryPluginSetup, OsqueryPluginStart } from './types'; +export type { OsqueryPluginSetup, OsqueryPluginStart } from './types'; diff --git a/x-pack/plugins/osquery/public/live_queries/agent_results/index.tsx b/x-pack/plugins/osquery/public/live_queries/agent_results/index.tsx deleted file mode 100644 index d1ef18e2e12ea..0000000000000 --- a/x-pack/plugins/osquery/public/live_queries/agent_results/index.tsx +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { EuiCodeBlock, EuiSpacer } from '@elastic/eui'; -import React from 'react'; -import { useParams } from 'react-router-dom'; - -import { useActionDetails } from '../../actions/use_action_details'; -import { ResultsTable } from '../../results/results_table'; - -const QueryAgentResultsComponent = () => { - const { actionId, agentId } = useParams<{ actionId: string; agentId: string }>(); - const { data } = useActionDetails({ actionId }); - - return ( - <> - - {data?.actionDetails._source?.data?.query} - - - - - ); -}; - -export const QueryAgentResults = React.memo(QueryAgentResultsComponent); diff --git a/x-pack/plugins/osquery/public/live_queries/form/index.tsx b/x-pack/plugins/osquery/public/live_queries/form/index.tsx index 9e952810e3352..8654a74fecfb4 100644 --- a/x-pack/plugins/osquery/public/live_queries/form/index.tsx +++ b/x-pack/plugins/osquery/public/live_queries/form/index.tsx @@ -18,6 +18,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { useCallback, useMemo, useState } from 'react'; import { useMutation } from 'react-query'; +import deepMerge from 'deepmerge'; import { UseField, Form, FormData, useForm, useFormData, FIELD_TYPES } from '../../shared_imports'; import { AgentsTableField } from './agents_table_field'; @@ -33,12 +34,19 @@ const FORM_ID = 'liveQueryForm'; export const MAX_QUERY_LENGTH = 2000; +const GhostFormField = () => <>; + interface LiveQueryFormProps { + agentId?: string | undefined; defaultValue?: Partial | undefined; onSuccess?: () => void; } -const LiveQueryFormComponent: React.FC = ({ defaultValue, onSuccess }) => { +const LiveQueryFormComponent: React.FC = ({ + agentId, + defaultValue, + onSuccess, +}) => { const { http } = useKibana().services; const [showSavedQueryFlyout, setShowSavedQueryFlyout] = useState(false); const setErrorToast = useErrorToast(); @@ -71,8 +79,6 @@ const LiveQueryFormComponent: React.FC = ({ defaultValue, on } ); - const expirationDate = useMemo(() => new Date(data?.actions[0].expiration), [data?.actions]); - const formSchema = { query: { type: FIELD_TYPES.TEXT, @@ -100,9 +106,18 @@ const LiveQueryFormComponent: React.FC = ({ defaultValue, on options: { stripEmptyFields: false, }, - defaultValue: defaultValue ?? { - query: '', - }, + defaultValue: deepMerge( + { + agentSelection: { + agents: [], + allAgentsSelected: false, + platformsSelected: [], + policiesSelected: [], + }, + query: '', + }, + defaultValue ?? {} + ), }); const { submit } = form; @@ -147,6 +162,59 @@ const LiveQueryFormComponent: React.FC = ({ defaultValue, on const flyoutFormDefaultValue = useMemo(() => ({ query }), [query]); + const queryFieldStepContent = useMemo( + () => ( + <> + + + + {!agentId && ( + + + + + + )} + + + + + + + + ), + [ + agentId, + agentSelected, + handleShowSaveQueryFlout, + queryComponentProps, + queryValueProvided, + resultsStatus, + submit, + ] + ); + + const resultsStepContent = useMemo( + () => + actionId ? ( + + ) : null, + [actionId, agentIds, data?.actions] + ); + const formSteps: EuiContainedStepProps[] = useMemo( () => [ { @@ -160,73 +228,34 @@ const LiveQueryFormComponent: React.FC = ({ defaultValue, on title: i18n.translate('xpack.osquery.liveQueryForm.steps.queryStepHeading', { defaultMessage: 'Enter query', }), - children: ( - <> - - - - - - - - - - - - - - - - ), + children: queryFieldStepContent, status: queryStatus, }, { title: i18n.translate('xpack.osquery.liveQueryForm.steps.resultsStepHeading', { defaultMessage: 'Check results', }), - children: actionId ? ( - - ) : null, + children: resultsStepContent, status: resultsStatus, }, ], - [ - actionId, - agentIds, - agentSelected, - handleShowSaveQueryFlout, - queryComponentProps, - queryStatus, - queryValueProvided, - expirationDate, - resultsStatus, - submit, - ] + [agentSelected, queryFieldStepContent, queryStatus, resultsStepContent, resultsStatus] + ); + + const singleAgentForm = useMemo( + () => ( + + + {queryFieldStepContent} + {resultsStepContent} + + ), + [queryFieldStepContent, resultsStepContent] ); return ( <> -
- - +
{agentId ? singleAgentForm : } {showSavedQueryFlyout ? ( , @@ -160,7 +161,14 @@ export class OsqueryPlugin implements Plugin = ({ - actionId, - agentIds, - expirationDate, - endDate, - isLive, - startDate, -}) => { - const tabs = useMemo( - () => [ - { - id: 'status', - name: 'Status', - content: ( - <> - - - - ), - }, - { - id: 'results', - name: 'Results', - content: ( - <> - - - - ), - }, - ], - [actionId, agentIds, endDate, isLive, startDate, expirationDate] - ); - - return ( - - ); -}; - -export const ResultTabs = React.memo(ResultTabsComponent); diff --git a/x-pack/plugins/osquery/public/results/results_table.tsx b/x-pack/plugins/osquery/public/results/results_table.tsx index 6ff60d30d23bf..d82737ab51e7c 100644 --- a/x-pack/plugins/osquery/public/results/results_table.tsx +++ b/x-pack/plugins/osquery/public/results/results_table.tsx @@ -14,6 +14,8 @@ import { EuiDataGridColumn, EuiLink, EuiLoadingContent, + EuiProgress, + EuiSpacer, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { createContext, useEffect, useState, useCallback, useContext, useMemo } from 'react'; @@ -37,17 +39,16 @@ interface ResultsTableComponentProps { selectedAgent?: string; agentIds?: string[]; endDate?: string; - isLive?: boolean; startDate?: string; } const ResultsTableComponent: React.FC = ({ actionId, agentIds, - isLive, startDate, endDate, }) => { + const [isLive, setIsLive] = useState(true); const { // @ts-expect-error update types data: { aggregations }, @@ -60,13 +61,13 @@ const ResultsTableComponent: React.FC = ({ sortField: '@timestamp', isLive, }); - + const expired = useMemo(() => (!endDate ? false : new Date(endDate) < new Date()), [endDate]); const { getUrlForApp } = useKibana().services.application; const getFleetAppUrl = useCallback( (agentId) => getUrlForApp('fleet', { - path: `#` + pagePathGetters.agent_details({ agentId }), + path: `#` + pagePathGetters.agent_details({ agentId })[1], }), [getUrlForApp] ); @@ -216,29 +217,56 @@ const ResultsTableComponent: React.FC = ({ [actionId, endDate, startDate] ); - if (!aggregations.totalResponded) { - return ; - } + useEffect( + () => + setIsLive(() => { + if (!agentIds?.length || expired) return false; + + const uniqueAgentsRepliedCount = + // @ts-expect-error-type + allResultsData?.rawResponse.aggregations?.unique_agents.value ?? 0; + + return !!(uniqueAgentsRepliedCount !== agentIds?.length - aggregations.failed); + }), + [ + agentIds?.length, + aggregations.failed, + // @ts-expect-error-type + allResultsData?.rawResponse.aggregations?.unique_agents.value, + expired, + ] + ); - if (aggregations.totalResponded && isFetched && !allResultsData?.edges.length) { - return ; + if (!isFetched) { + return ; } return ( - // @ts-expect-error update types - - - + <> + {isLive && } + + {isFetched && !allResultsData?.edges.length ? ( + <> + + + + ) : ( + // @ts-expect-error update types + + + + )} + ); }; diff --git a/x-pack/plugins/osquery/public/results/translations.ts b/x-pack/plugins/osquery/public/results/translations.ts index 8e77e78ec76e2..e4f71d818f01d 100644 --- a/x-pack/plugins/osquery/public/results/translations.ts +++ b/x-pack/plugins/osquery/public/results/translations.ts @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; export const generateEmptyDataMessage = (agentsResponded: number): string => { return i18n.translate('xpack.osquery.results.multipleAgentsResponded', { defaultMessage: - '{agentsResponded, plural, one {# agent has} other {# agents have}} responded, but no osquery data has been reported.', + '{agentsResponded, plural, one {# agent has} other {# agents have}} responded, no osquery data has been reported.', values: { agentsResponded }, }); }; diff --git a/x-pack/plugins/osquery/public/results/use_all_results.ts b/x-pack/plugins/osquery/public/results/use_all_results.ts index 1121898410278..a13fceedfa07a 100644 --- a/x-pack/plugins/osquery/public/results/use_all_results.ts +++ b/x-pack/plugins/osquery/public/results/use_all_results.ts @@ -78,7 +78,7 @@ export const useAllResults = ({ }; }, { - refetchInterval: isLive ? 1000 : false, + refetchInterval: isLive ? 5000 : false, enabled: !skip, onSuccess: () => setErrorToast(), onError: (error: Error) => diff --git a/x-pack/plugins/osquery/public/routes/live_queries/details/index.tsx b/x-pack/plugins/osquery/public/routes/live_queries/details/index.tsx index e4f1bb447a15a..02f5c8b6fb2a5 100644 --- a/x-pack/plugins/osquery/public/routes/live_queries/details/index.tsx +++ b/x-pack/plugins/osquery/public/routes/live_queries/details/index.tsx @@ -6,54 +6,24 @@ */ import { get } from 'lodash'; -import { - EuiButtonEmpty, - EuiTextColor, - EuiFlexGroup, - EuiFlexItem, - EuiCodeBlock, - EuiSpacer, - EuiDescriptionList, - EuiDescriptionListTitle, - EuiDescriptionListDescription, -} from '@elastic/eui'; +import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiCodeBlock, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { useMemo } from 'react'; import { useParams } from 'react-router-dom'; -import styled from 'styled-components'; -import { Direction } from '../../../../common/search_strategy'; import { useRouterNavigate } from '../../../common/lib/kibana'; import { WithHeaderLayout } from '../../../components/layouts'; -import { useActionResults } from '../../../action_results/use_action_results'; import { useActionDetails } from '../../../actions/use_action_details'; import { ResultTabs } from '../../saved_queries/edit/tabs'; import { useBreadcrumbs } from '../../../common/hooks/use_breadcrumbs'; import { BetaBadge, BetaBadgeRowWrapper } from '../../../components/beta_badge'; -const Divider = styled.div` - width: 0; - height: 100%; - border-left: ${({ theme }) => theme.eui.euiBorderThin}; -`; - const LiveQueryDetailsPageComponent = () => { const { actionId } = useParams<{ actionId: string }>(); useBreadcrumbs('live_query_details', { liveQueryId: actionId }); const liveQueryListProps = useRouterNavigate('live_queries'); const { data } = useActionDetails({ actionId }); - const expirationDate = useMemo(() => new Date(data?.actionDetails._source.expiration), [ - data?.actionDetails, - ]); - const expired = useMemo(() => expirationDate < new Date(), [expirationDate]); - const { data: actionResultsData } = useActionResults({ - actionId, - activePage: 0, - limit: 0, - direction: Direction.asc, - sortField: '@timestamp', - }); const LeftColumn = useMemo( () => ( @@ -82,72 +52,14 @@ const LiveQueryDetailsPageComponent = () => { [liveQueryListProps] ); - const failed = useMemo(() => { - let result = actionResultsData?.aggregations.failed; - if (expired) { - result = '-'; - if (data?.actionDetails?.fields?.agents && actionResultsData?.aggregations) { - result = - data.actionDetails.fields.agents.length - actionResultsData.aggregations.successful; - } - } - return result; - }, [expired, actionResultsData?.aggregations, data?.actionDetails?.fields?.agents]); - - const RightColumn = useMemo( - () => ( - - - <> - - - - - - {/* eslint-disable-next-line react-perf/jsx-no-new-object-as-prop */} - - - - - - {data?.actionDetails?.fields?.agents?.length ?? '0'} - - - - - - - - {/* eslint-disable-next-line react-perf/jsx-no-new-object-as-prop */} - - - - - - {failed} - - - - - ), - [data?.actionDetails?.fields?.agents?.length, failed] - ); - return ( - + {data?.actionDetails._source?.data?.query} { const updateSavedQueryMutation = useUpdateSavedQuery({ savedQueryId }); const deleteSavedQueryMutation = useDeleteSavedQuery({ savedQueryId }); - useBreadcrumbs('saved_query_edit', { savedQueryId: savedQueryDetails?.attributes?.id ?? '' }); + useBreadcrumbs('saved_query_edit', { savedQueryName: savedQueryDetails?.attributes?.id ?? '' }); const handleCloseDeleteConfirmationModal = useCallback(() => { setIsDeleteModalVisible(false); diff --git a/x-pack/plugins/osquery/public/routes/saved_queries/edit/tabs.tsx b/x-pack/plugins/osquery/public/routes/saved_queries/edit/tabs.tsx index 1946cd6dd3450..1f56daaa3bdb5 100644 --- a/x-pack/plugins/osquery/public/routes/saved_queries/edit/tabs.tsx +++ b/x-pack/plugins/osquery/public/routes/saved_queries/edit/tabs.tsx @@ -10,12 +10,11 @@ import React, { useMemo } from 'react'; import { ResultsTable } from '../../../results/results_table'; import { ActionResultsSummary } from '../../../action_results/action_results_summary'; +import { ActionAgentsStatus } from '../../../action_results/action_agents_status'; interface ResultTabsProps { actionId: string; agentIds?: string[]; - expirationDate: Date; - isLive?: boolean; startDate?: string; endDate?: string; } @@ -24,8 +23,6 @@ const ResultTabsComponent: React.FC = ({ actionId, agentIds, endDate, - expirationDate, - isLive, startDate, }) => { const tabs = useMemo( @@ -39,7 +36,6 @@ const ResultTabsComponent: React.FC = ({ @@ -55,23 +51,26 @@ const ResultTabsComponent: React.FC = ({ ), }, ], - [actionId, agentIds, endDate, expirationDate, isLive, startDate] + [actionId, agentIds, endDate, startDate] ); return ( - + <> + + + + ); }; diff --git a/x-pack/plugins/osquery/public/saved_queries/form/index.tsx b/x-pack/plugins/osquery/public/saved_queries/form/index.tsx index 174227eb5e6e5..9bbf847c4d2a0 100644 --- a/x-pack/plugins/osquery/public/saved_queries/form/index.tsx +++ b/x-pack/plugins/osquery/public/saved_queries/form/index.tsx @@ -7,6 +7,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle, EuiText } from '@elastic/eui'; import React from 'react'; +import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { ALL_OSQUERY_VERSIONS_OPTIONS } from '../../scheduled_query_groups/queries/constants'; @@ -57,7 +58,12 @@ const SavedQueryFormComponent = () => ( euiFieldProps={{ noSuggestions: false, singleSelection: { asPlainText: true }, - placeholder: ALL_OSQUERY_VERSIONS_OPTIONS[0].label, + placeholder: i18n.translate( + 'xpack.osquery.scheduledQueryGroup.queriesTable.osqueryVersionAllLabel', + { + defaultMessage: 'ALL', + } + ), options: ALL_OSQUERY_VERSIONS_OPTIONS, onCreateOption: undefined, }} diff --git a/x-pack/plugins/osquery/public/saved_queries/use_saved_queries.ts b/x-pack/plugins/osquery/public/saved_queries/use_saved_queries.ts index 324d4aace1647..bb5a73d9d50fa 100644 --- a/x-pack/plugins/osquery/public/saved_queries/use_saved_queries.ts +++ b/x-pack/plugins/osquery/public/saved_queries/use_saved_queries.ts @@ -40,7 +40,7 @@ export const useSavedQueries = ({ { keepPreviousData: true, // Refetch the data every 10 seconds - refetchInterval: isLive ? 10000 : false, + refetchInterval: isLive ? 5000 : false, } ); }; diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/queries/query_flyout.tsx b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/query_flyout.tsx index b37c315849f60..32547bc5dd2d0 100644 --- a/x-pack/plugins/osquery/public/scheduled_query_groups/queries/query_flyout.tsx +++ b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/query_flyout.tsx @@ -18,9 +18,10 @@ import { EuiFlexItem, EuiButtonEmpty, EuiButton, - EuiDescribedFormGroup, + EuiText, } from '@elastic/eui'; import React, { useCallback, useMemo, useState } from 'react'; +import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { satisfies } from 'semver'; @@ -128,10 +129,7 @@ const QueryFlyoutComponent: React.FC = ({ - Set heading level based on context} - description={'Will be wrapped in a small, subdued EuiText block.'} - > + = ({ + + + + + } // eslint-disable-next-line react-perf/jsx-no-new-object-as-prop euiFieldProps={{ isDisabled: !isFieldSupported, noSuggestions: false, singleSelection: { asPlainText: true }, - placeholder: ALL_OSQUERY_VERSIONS_OPTIONS[0].label, + placeholder: i18n.translate( + 'xpack.osquery.scheduledQueryGroup.queriesTable.osqueryVersionAllLabel', + { + defaultMessage: 'ALL', + } + ), options: ALL_OSQUERY_VERSIONS_OPTIONS, onCreateOption: undefined, }} @@ -160,7 +173,7 @@ const QueryFlyoutComponent: React.FC = ({ euiFieldProps={{ disabled: !isFieldSupported }} /> - + {!isFieldSupported ? ( diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/queries/schema.tsx b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/schema.tsx index d8dbaad2f17e8..0b23ce924f930 100644 --- a/x-pack/plugins/osquery/public/scheduled_query_groups/queries/schema.tsx +++ b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/schema.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import React from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -65,14 +65,6 @@ export const formSchema = { defaultMessage="Minimum Osquery version" /> - - - - - ) as unknown) as string, validations: [], diff --git a/x-pack/plugins/osquery/public/shared_components/index.tsx b/x-pack/plugins/osquery/public/shared_components/index.tsx new file mode 100644 index 0000000000000..0e0ab4e5c045b --- /dev/null +++ b/x-pack/plugins/osquery/public/shared_components/index.tsx @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { getLazyOsqueryAction } from './lazy_osquery_action'; diff --git a/x-pack/plugins/osquery/public/shared_components/lazy_osquery_action.tsx b/x-pack/plugins/osquery/public/shared_components/lazy_osquery_action.tsx new file mode 100644 index 0000000000000..9cc55a65ce2bc --- /dev/null +++ b/x-pack/plugins/osquery/public/shared_components/lazy_osquery_action.tsx @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { lazy, Suspense } from 'react'; + +// @ts-expect-error update types +export const getLazyOsqueryAction = (services) => (props) => { + const OsqueryAction = lazy(() => import('./osquery_action')); + return ( + + + + ); +}; diff --git a/x-pack/plugins/osquery/public/shared_components/osquery_action/index.tsx b/x-pack/plugins/osquery/public/shared_components/osquery_action/index.tsx new file mode 100644 index 0000000000000..cf8a85cea244c --- /dev/null +++ b/x-pack/plugins/osquery/public/shared_components/osquery_action/index.tsx @@ -0,0 +1,99 @@ +/* + * 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 { EuiErrorBoundary, EuiLoadingContent } from '@elastic/eui'; +import React, { useEffect, useState } from 'react'; +import { QueryClientProvider } from 'react-query'; +import { KibanaContextProvider, useKibana } from '../../common/lib/kibana'; + +import { LiveQueryForm } from '../../live_queries/form'; +import { queryClient } from '../../query_client'; + +interface OsqueryActionProps { + hostId?: string | undefined; +} + +const OsqueryActionComponent: React.FC = ({ hostId }) => { + const [agentId, setAgentId] = useState(); + const { indexPatterns, search } = useKibana().services.data; + + useEffect(() => { + if (hostId) { + const findAgent = async () => { + const searchSource = await search.searchSource.create(); + const indexPattern = await indexPatterns.find('.fleet-agents'); + + searchSource.setField('index', indexPattern[0]); + searchSource.setField('filter', [ + { + meta: { + alias: null, + disabled: false, + negate: false, + key: 'local_metadata.host.id', + value: hostId, + }, + query: { + match_phrase: { + 'local_metadata.host.id': hostId, + }, + }, + }, + { + meta: { + alias: null, + disabled: false, + negate: false, + key: 'active', + value: 'true', + }, + query: { + match_phrase: { + active: 'true', + }, + }, + }, + ]); + + const response = await searchSource.fetch$().toPromise(); + + if (response.rawResponse.hits.hits.length && response.rawResponse.hits.hits[0]._id) { + setAgentId(response.rawResponse.hits.hits[0]._id); + } + }; + + findAgent(); + } + }); + + if (!agentId) { + return ; + } + + return ( + // eslint-disable-next-line react-perf/jsx-no-new-object-as-prop + + ); +}; + +export const OsqueryAction = React.memo(OsqueryActionComponent); + +// @ts-expect-error update types +const OsqueryActionWrapperComponent = ({ services, ...props }) => ( + + + + + + + +); + +const OsqueryActionWrapper = React.memo(OsqueryActionWrapperComponent); + +// eslint-disable-next-line import/no-default-export +export { OsqueryActionWrapper as default }; diff --git a/x-pack/plugins/osquery/public/types.ts b/x-pack/plugins/osquery/public/types.ts index 9a466dfc619b6..fd21b39d25504 100644 --- a/x-pack/plugins/osquery/public/types.ts +++ b/x-pack/plugins/osquery/public/types.ts @@ -16,11 +16,13 @@ import { TriggersAndActionsUIPublicPluginSetup, TriggersAndActionsUIPublicPluginStart, } from '../../triggers_actions_ui/public'; +import { getLazyOsqueryAction } from './shared_components'; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface OsqueryPluginSetup {} -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface OsqueryPluginStart {} +export interface OsqueryPluginStart { + OsqueryAction?: ReturnType; +} export interface AppPluginStartDependencies { navigation: NavigationPublicPluginStart; diff --git a/x-pack/plugins/osquery/server/search_strategy/osquery/factory/results/query.all_results.dsl.ts b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/results/query.all_results.dsl.ts index b560fd3c364e9..406ff26991f0e 100644 --- a/x-pack/plugins/osquery/server/search_strategy/osquery/factory/results/query.all_results.dsl.ts +++ b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/results/query.all_results.dsl.ts @@ -47,12 +47,17 @@ export const buildResultsQuery = ({ size: 10000, }, }, + unique_agents: { + cardinality: { + field: 'elastic_agent.id', + }, + }, }, query: { bool: { filter } }, from: activePage * querySize, size: querySize, track_total_hits: true, - fields: agentId ? ['osquery.*'] : ['agent.*', 'osquery.*'], + fields: ['elastic_agent.*', 'agent.*', 'osquery.*'], sort: sort?.map((sortConfig) => ({ [sortConfig.field]: { diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 3809d55ff90be..11770d2d2f386 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -17301,7 +17301,6 @@ "xpack.osquery.fleetIntegration.scheduleQueryGroupsButtonText": "クエリグループをスケジュール", "xpack.osquery.liveQueriesHistory.newLiveQueryButtonLabel": "新しいライブクエリ", "xpack.osquery.liveQueriesHistory.pageTitle": "ライブクエリ履歴", - "xpack.osquery.liveQueryActionResults.summary.agentsQueriedLabelText": "エージェントがクエリされました", "xpack.osquery.liveQueryActionResults.summary.failedLabelText": "失敗", "xpack.osquery.liveQueryActionResults.summary.pendingLabelText": "未応答", "xpack.osquery.liveQueryActionResults.summary.successfulLabelText": "成功", @@ -17316,8 +17315,6 @@ "xpack.osquery.liveQueryActions.table.createdAtColumnTitle": "作成日時:", "xpack.osquery.liveQueryActions.table.queryColumnTitle": "クエリ", "xpack.osquery.liveQueryActions.table.viewDetailsColumnTitle": "詳細を表示", - "xpack.osquery.liveQueryDetails.kpis.agentsFailedCountLabelText": "エージェントが失敗しました", - "xpack.osquery.liveQueryDetails.kpis.agentsQueriedLabelText": "エージェントがクエリされました", "xpack.osquery.liveQueryDetails.pageTitle": "ライブクエリ詳細", "xpack.osquery.liveQueryDetails.viewLiveQueriesHistoryTitle": "ライブクエリ履歴を表示", "xpack.osquery.liveQueryForm.form.submitButtonLabel": "送信", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 6540d9f49399a..9704070feb8ab 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -17539,7 +17539,6 @@ "xpack.osquery.fleetIntegration.scheduleQueryGroupsButtonText": "计划查询组", "xpack.osquery.liveQueriesHistory.newLiveQueryButtonLabel": "新建实时查询", "xpack.osquery.liveQueriesHistory.pageTitle": "实时查询历史记录", - "xpack.osquery.liveQueryActionResults.summary.agentsQueriedLabelText": "查询的代理", "xpack.osquery.liveQueryActionResults.summary.failedLabelText": "失败", "xpack.osquery.liveQueryActionResults.summary.pendingLabelText": "尚未响应", "xpack.osquery.liveQueryActionResults.summary.successfulLabelText": "成功", @@ -17554,8 +17553,6 @@ "xpack.osquery.liveQueryActions.table.createdAtColumnTitle": "创建于", "xpack.osquery.liveQueryActions.table.queryColumnTitle": "查询", "xpack.osquery.liveQueryActions.table.viewDetailsColumnTitle": "查看详情", - "xpack.osquery.liveQueryDetails.kpis.agentsFailedCountLabelText": "失败的代理", - "xpack.osquery.liveQueryDetails.kpis.agentsQueriedLabelText": "查询的代理", "xpack.osquery.liveQueryDetails.pageTitle": "实时查询详情", "xpack.osquery.liveQueryDetails.viewLiveQueriesHistoryTitle": "查看实时查询历史记录", "xpack.osquery.liveQueryForm.form.submitButtonLabel": "提交", diff --git a/yarn.lock b/yarn.lock index 51df4a1e72b90..c64f4e1c4b281 100644 --- a/yarn.lock +++ b/yarn.lock @@ -23088,10 +23088,10 @@ react-popper@^2.2.4: react-fast-compare "^3.0.1" warning "^4.0.2" -react-query@^3.13.10: - version "3.13.10" - resolved "https://registry.yarnpkg.com/react-query/-/react-query-3.13.10.tgz#b6a05e22a5debb6e2df79ada588179771cbd7df8" - integrity sha512-wFvKhEDnOVL5bFL+9KPgNsiOOei1Ad+l6l1awCBuoX7xMG+SXXKDOF2uuZFsJe0w6gdthdWN+00021yepTR31g== +react-query@^3.18.1: + version "3.18.1" + resolved "https://registry.yarnpkg.com/react-query/-/react-query-3.18.1.tgz#893b5475a7b4add099e007105317446f7a2cd310" + integrity sha512-17lv3pQxU9n+cB5acUv0/cxNTjo9q8G+RsedC6Ax4V9D8xEM7Q5xf9xAbCPdEhDrrnzPjTls9fQEABKRSi7OJA== dependencies: "@babel/runtime" "^7.5.5" broadcast-channel "^3.4.1" From 94a0b23486fc5e68b0c14a3de8d943f111ad674d Mon Sep 17 00:00:00 2001 From: Spencer Date: Thu, 8 Jul 2021 17:11:42 -0700 Subject: [PATCH 06/49] [reporting] remove outdated todo comment (#104959) Co-authored-by: spalger --- src/dev/build/tasks/install_chromium.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/dev/build/tasks/install_chromium.js b/src/dev/build/tasks/install_chromium.js index 37abcbad4466e..95e0df8984f9d 100644 --- a/src/dev/build/tasks/install_chromium.js +++ b/src/dev/build/tasks/install_chromium.js @@ -19,7 +19,6 @@ export const InstallChromium = { log.info(`Installing Chromium for ${platform.getName()}-${platform.getArchitecture()}`); const { binaryPath$ } = installBrowser( - // TODO: https://github.com/elastic/kibana/issues/72496 log, build.resolvePathForPlatform(platform, 'x-pack/plugins/reporting/chromium'), platform.getName(), From c6916eacc2ca391ee4e832e4f173f453fa70fb75 Mon Sep 17 00:00:00 2001 From: Davis Plumlee <56367316+dplumlee@users.noreply.github.com> Date: Thu, 8 Jul 2021 18:12:04 -0600 Subject: [PATCH 07/49] [Security Solution][Exceptions] Gets rid of rule exception comma delimiter for "is one of" operator (#104960) --- .../components/autocomplete/field_value_match_any.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/x-pack/plugins/lists/public/exceptions/components/autocomplete/field_value_match_any.tsx b/x-pack/plugins/lists/public/exceptions/components/autocomplete/field_value_match_any.tsx index 82347f6212442..e5a5e76f8cc5d 100644 --- a/x-pack/plugins/lists/public/exceptions/components/autocomplete/field_value_match_any.tsx +++ b/x-pack/plugins/lists/public/exceptions/components/autocomplete/field_value_match_any.tsx @@ -164,7 +164,6 @@ export const AutocompleteFieldMatchAnyComponent: React.FC From fcb0de47f1ea94254b2d8e6ca83f1cc88b8f760a Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Thu, 8 Jul 2021 19:55:18 -0500 Subject: [PATCH 08/49] [paths] Add default lookup for kibana.yml at /etc/kibana (#103934) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- packages/kbn-utils/src/path/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/kbn-utils/src/path/index.ts b/packages/kbn-utils/src/path/index.ts index 2d6d0e9e919eb..9835179a61e9d 100644 --- a/packages/kbn-utils/src/path/index.ts +++ b/packages/kbn-utils/src/path/index.ts @@ -18,6 +18,7 @@ const CONFIG_PATHS = [ process.env.KIBANA_PATH_CONF && join(process.env.KIBANA_PATH_CONF, 'kibana.yml'), process.env.CONFIG_PATH, // deprecated join(REPO_ROOT, 'config/kibana.yml'), + '/etc/kibana/kibana.yml', ].filter(isString); const CONFIG_DIRECTORIES = [ From 1d2811e6036a383975ed71ddf67c92c44c8da2b3 Mon Sep 17 00:00:00 2001 From: Kevin Logan <56395104+kevinlog@users.noreply.github.com> Date: Thu, 8 Jul 2021 22:45:01 -0400 Subject: [PATCH 09/49] [Security Solution] add advanced policy option for Linux Malware quarantine (#104984) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../pages/policy/models/advanced_policy_schema.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/models/advanced_policy_schema.ts b/x-pack/plugins/security_solution/public/management/pages/policy/models/advanced_policy_schema.ts index 62d51c3630db7..db998b871cd93 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/models/advanced_policy_schema.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/models/advanced_policy_schema.ts @@ -691,4 +691,15 @@ export const AdvancedPolicySchema: AdvancedPolicySchemaType[] = [ } ), }, + { + key: 'linux.advanced.malware.quarantine', + first_supported_version: '7.14', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.linux.advanced.malware.quarantine', + { + defaultMessage: + 'Whether quarantine should be enabled when malware prevention is enabled. Default: true.', + } + ), + }, ]; From d4e46c52a274671fb8cef7521cbeae749bcb59a9 Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Fri, 9 Jul 2021 08:13:36 +0100 Subject: [PATCH 10/49] [ML] Fixing annotations table loading hang (#104825) --- .../annotations/annotations_table/annotations_table.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/x-pack/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js b/x-pack/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js index 7d461c4ec8572..ed603357206ad 100644 --- a/x-pack/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js +++ b/x-pack/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js @@ -128,6 +128,12 @@ class AnnotationsTableUI extends Component { jobId: undefined, }); }); + } else { + this.setState({ + annotations: [], + isLoading: false, + jobId: undefined, + }); } } From 8dd08529ae764d9691869625720c29d4a2feafa3 Mon Sep 17 00:00:00 2001 From: Dmitry Shevchenko Date: Fri, 9 Jul 2021 10:24:36 +0200 Subject: [PATCH 11/49] Fix stale validation messages on the action edit form (#104868) --- .../detection_rules/custom_query_rule.spec.ts | 18 ++++++++++++++ .../cypress/objects/connector.ts | 24 +++++++++++++++++++ .../cypress/screens/create_new_rule.ts | 24 +++++++++++++++++++ .../cypress/tasks/create_new_rule.ts | 16 +++++++++++++ .../rules/rule_actions_field/index.tsx | 8 ++++++- 5 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 x-pack/plugins/security_solution/cypress/objects/connector.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/custom_query_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/custom_query_rule.spec.ts index 5f9175476795c..218b1f7745d94 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/custom_query_rule.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/custom_query_rule.spec.ts @@ -48,6 +48,12 @@ import { SEVERITY_DROPDOWN, TAGS_CLEAR_BUTTON, TAGS_FIELD, + EMAIL_ACTION_BTN, + CREATE_ACTION_CONNECTOR_BTN, + SAVE_ACTION_CONNECTOR_BTN, + FROM_VALIDATION_ERROR, + EMAIL_ACTION_TO_INPUT, + EMAIL_ACTION_SUBJECT_INPUT, } from '../../screens/create_new_rule'; import { ADDITIONAL_LOOK_BACK_DETAILS, @@ -99,6 +105,7 @@ import { fillAboutRule, fillAboutRuleAndContinue, fillDefineCustomRuleWithImportedQueryAndContinue, + fillEmailConnectorForm, fillScheduleRuleAndContinue, goToAboutStepTab, goToActionsStepTab, @@ -360,6 +367,17 @@ describe('Custom detection rules deletion and edition', () => { cy.get(ACTIONS_THROTTLE_INPUT).invoke('val').should('eql', 'no_actions'); + cy.get(ACTIONS_THROTTLE_INPUT).select('Weekly'); + cy.get(EMAIL_ACTION_BTN).click(); + cy.get(CREATE_ACTION_CONNECTOR_BTN).click(); + fillEmailConnectorForm(); + cy.get(SAVE_ACTION_CONNECTOR_BTN).click(); + + cy.get(EMAIL_ACTION_TO_INPUT).type('test@example.com'); + cy.get(EMAIL_ACTION_SUBJECT_INPUT).type('Subject'); + + cy.get(FROM_VALIDATION_ERROR).should('not.exist'); + goToAboutStepTab(); cy.get(TAGS_CLEAR_BUTTON).click({ force: true }); fillAboutRule(editedRule); diff --git a/x-pack/plugins/security_solution/cypress/objects/connector.ts b/x-pack/plugins/security_solution/cypress/objects/connector.ts new file mode 100644 index 0000000000000..2a0f1cc43eff0 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/objects/connector.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. + */ + +export interface EmailConnector { + name: string; + from: string; + host: string; + port: string; + user: string; + password: string; +} + +export const emailConnector: EmailConnector = { + name: 'Test connector', + from: 'test@example.com', + host: 'example.com', + port: '80', + user: 'username', + password: 'password', +}; diff --git a/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts index a580068b636e4..551857ca3bfca 100644 --- a/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts +++ b/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts @@ -16,6 +16,30 @@ export const ACTIONS_EDIT_TAB = '[data-test-subj="edit-rule-actions-tab"]'; export const ACTIONS_THROTTLE_INPUT = '[data-test-subj="stepRuleActions"] [data-test-subj="select"]'; +export const EMAIL_ACTION_BTN = '[data-test-subj=".email-ActionTypeSelectOption"]'; + +export const CREATE_ACTION_CONNECTOR_BTN = '[data-test-subj="createActionConnectorButton-0"]'; + +export const SAVE_ACTION_CONNECTOR_BTN = '[data-test-subj="saveActionButtonModal"]'; + +export const EMAIL_ACTION_TO_INPUT = '[data-test-subj="toEmailAddressInput"]'; + +export const EMAIL_ACTION_SUBJECT_INPUT = '[data-test-subj="subjectInput"]'; + +export const FROM_VALIDATION_ERROR = '.euiFormErrorText'; + +export const CONNECTOR_NAME_INPUT = '[data-test-subj="nameInput"]'; + +export const EMAIL_CONNECTOR_FROM_INPUT = '[data-test-subj="emailFromInput"]'; + +export const EMAIL_CONNECTOR_HOST_INPUT = '[data-test-subj="emailHostInput"]'; + +export const EMAIL_CONNECTOR_PORT_INPUT = '[data-test-subj="emailPortInput"]'; + +export const EMAIL_CONNECTOR_USER_INPUT = '[data-test-subj="emailUserInput"]'; + +export const EMAIL_CONNECTOR_PASSWORD_INPUT = '[data-test-subj="emailPasswordInput"]'; + export const ADD_FALSE_POSITIVE_BTN = '[data-test-subj="detectionEngineStepAboutRuleFalsePositives"] .euiButtonEmpty__text'; diff --git a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts index 9c15b1f03932d..9b74110f0ef77 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { emailConnector, EmailConnector } from '../objects/connector'; import { CustomRule, MachineLearningRule, @@ -85,6 +86,12 @@ import { THRESHOLD_FIELD_SELECTION, THRESHOLD_INPUT_AREA, THRESHOLD_TYPE, + CONNECTOR_NAME_INPUT, + EMAIL_CONNECTOR_FROM_INPUT, + EMAIL_CONNECTOR_HOST_INPUT, + EMAIL_CONNECTOR_PORT_INPUT, + EMAIL_CONNECTOR_USER_INPUT, + EMAIL_CONNECTOR_PASSWORD_INPUT, } from '../screens/create_new_rule'; import { TOAST_ERROR } from '../screens/shared'; import { SERVER_SIDE_EVENT_COUNT } from '../screens/timeline'; @@ -390,6 +397,15 @@ export const fillIndexAndIndicatorIndexPattern = ( getIndicatorIndicatorIndex().type(`${indicatorIndex}{enter}`); }; +export const fillEmailConnectorForm = (connector: EmailConnector = emailConnector) => { + cy.get(CONNECTOR_NAME_INPUT).type(connector.name); + cy.get(EMAIL_CONNECTOR_FROM_INPUT).type(connector.from); + cy.get(EMAIL_CONNECTOR_HOST_INPUT).type(connector.host); + cy.get(EMAIL_CONNECTOR_PORT_INPUT).type(connector.port); + cy.get(EMAIL_CONNECTOR_USER_INPUT).type(connector.user); + cy.get(EMAIL_CONNECTOR_PASSWORD_INPUT).type(connector.password); +}; + /** Returns the indicator index drop down field. Pass in row number, default is 1 */ export const getIndicatorIndexComboField = (row = 1) => cy.get(THREAT_COMBO_BOX_INPUT).eq(row * 2 - 2); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.tsx index 9ecf3333279eb..2206960f6bcd3 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.tsx @@ -122,7 +122,13 @@ export const RuleActionsField: React.FC = ({ // eslint-disable-next-line @typescript-eslint/no-explicit-any (key: string, value: any, index: number) => { const updatedActions = [...actions]; - updatedActions[index].params[key] = value; + updatedActions[index] = { + ...updatedActions[index], + params: { + ...updatedActions[index].params, + [key]: value, + }, + }; field.setValue(updatedActions); }, // eslint-disable-next-line react-hooks/exhaustive-deps From 7ddab693ae149390196bf0e3a6c24b11f8360288 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ester=20Mart=C3=AD=20Vilaseca?= Date: Fri, 9 Jul 2021 10:35:24 +0200 Subject: [PATCH 12/49] [Monitoring] Add rules modal to listing page (#104328) * Add rules modal to listing page * Fix tests * minor fixes Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../public/alerts/enable_alerts_modal.tsx | 14 ++++---- .../public/services/enable_alerts_modal.js | 6 ++-- .../public/views/cluster/listing/index.js | 32 ++++++++++++------- .../public/views/cluster/overview/index.js | 2 +- .../apps/monitoring/cluster/list.js | 4 +++ .../test/functional/apps/monitoring/index.js | 2 +- .../services/monitoring/cluster_list.js | 5 +++ 7 files changed, 41 insertions(+), 24 deletions(-) diff --git a/x-pack/plugins/monitoring/public/alerts/enable_alerts_modal.tsx b/x-pack/plugins/monitoring/public/alerts/enable_alerts_modal.tsx index 914446c42aaa7..fadf4c5872507 100644 --- a/x-pack/plugins/monitoring/public/alerts/enable_alerts_modal.tsx +++ b/x-pack/plugins/monitoring/public/alerts/enable_alerts_modal.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useEffect, useState, useContext } from 'react'; +import React, { useEffect, useState } from 'react'; import { EuiButton, @@ -22,14 +22,16 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { AlertsContext } from './context'; import { Legacy } from '../legacy_shims'; -export const EnableAlertsModal: React.FC<{}> = () => { +interface Props { + alerts: {}; +} + +export const EnableAlertsModal: React.FC = ({ alerts }: Props) => { const [isModalVisible, setIsModalVisible] = useState(false); const $injector = Legacy.shims.getAngularInjector(); const alertsEnableModalProvider: any = $injector.get('enableAlertsModal'); - const alertsContext = useContext(AlertsContext); const closeModal = () => { setIsModalVisible(false); @@ -58,10 +60,10 @@ export const EnableAlertsModal: React.FC<{}> = () => { }; useEffect(() => { - if (alertsEnableModalProvider.shouldShowAlertsModal(alertsContext)) { + if (alertsEnableModalProvider.shouldShowAlertsModal(alerts)) { setIsModalVisible(true); } - }, [alertsEnableModalProvider, alertsContext]); + }, [alertsEnableModalProvider, alerts]); const confirmButtonClick = () => { if (radioIdSelected === 'create-alerts') { diff --git a/x-pack/plugins/monitoring/public/services/enable_alerts_modal.js b/x-pack/plugins/monitoring/public/services/enable_alerts_modal.js index 0232e302517af..438c5ab83f5e3 100644 --- a/x-pack/plugins/monitoring/public/services/enable_alerts_modal.js +++ b/x-pack/plugins/monitoring/public/services/enable_alerts_modal.js @@ -12,12 +12,10 @@ export function enableAlertsModalProvider($http, $window, $injector) { const modalHasBeenShown = $window.sessionStorage.getItem('ALERTS_MODAL_HAS_BEEN_SHOWN'); const decisionMade = $window.localStorage.getItem('ALERTS_MODAL_DECISION_MADE'); - if (Object.keys(alerts.allAlerts).length > 0) { + if (Object.keys(alerts).length > 0) { $window.localStorage.setItem('ALERTS_MODAL_DECISION_MADE', true); return false; - } - - if (!modalHasBeenShown && !decisionMade) { + } else if (!modalHasBeenShown && !decisionMade) { return true; } diff --git a/x-pack/plugins/monitoring/public/views/cluster/listing/index.js b/x-pack/plugins/monitoring/public/views/cluster/listing/index.js index 9f9eec3848604..8b365292aeb13 100644 --- a/x-pack/plugins/monitoring/public/views/cluster/listing/index.js +++ b/x-pack/plugins/monitoring/public/views/cluster/listing/index.js @@ -13,6 +13,7 @@ import { MonitoringViewBaseEuiTableController } from '../../'; import template from './index.html'; import { Listing } from '../../../components/cluster/listing'; import { CODE_PATH_ALL } from '../../../../common/constants'; +import { EnableAlertsModal } from '../../../alerts/enable_alerts_modal.tsx'; const CODE_PATHS = [CODE_PATH_ALL]; @@ -21,6 +22,10 @@ const getPageData = ($injector) => { return monitoringClusters(undefined, undefined, CODE_PATHS); }; +const getAlerts = (clusters) => { + return clusters.reduce((alerts, cluster) => ({ ...alerts, ...cluster.alerts.list }), {}); +}; + uiRoutes .when('/home', { template, @@ -71,18 +76,21 @@ uiRoutes () => this.data, (data) => { this.renderReact( - + <> + + + ); } ); diff --git a/x-pack/plugins/monitoring/public/views/cluster/overview/index.js b/x-pack/plugins/monitoring/public/views/cluster/overview/index.js index bf34650bdb700..20e694ad8548f 100644 --- a/x-pack/plugins/monitoring/public/views/cluster/overview/index.js +++ b/x-pack/plugins/monitoring/public/views/cluster/overview/index.js @@ -83,7 +83,7 @@ uiRoutes.when('/overview', { setupMode={setupMode} showLicenseExpiration={showLicenseExpiration} /> - + {bottomBarComponent} )} diff --git a/x-pack/test/functional/apps/monitoring/cluster/list.js b/x-pack/test/functional/apps/monitoring/cluster/list.js index f88e30f717141..09361f88f5652 100644 --- a/x-pack/test/functional/apps/monitoring/cluster/list.js +++ b/x-pack/test/functional/apps/monitoring/cluster/list.js @@ -26,6 +26,8 @@ export default function ({ getService, getPageObjects }) { to: 'Aug 16, 2017 @ 00:00:00.000', }); + await clusterList.closeAlertsModal(); + await clusterList.assertDefaults(); }); @@ -83,6 +85,8 @@ export default function ({ getService, getPageObjects }) { to: 'Sep 7, 2017 @ 20:18:55.733', }); + await clusterList.closeAlertsModal(); + await clusterList.assertDefaults(); }); diff --git a/x-pack/test/functional/apps/monitoring/index.js b/x-pack/test/functional/apps/monitoring/index.js index 24ace88f334f0..213007c7b71df 100644 --- a/x-pack/test/functional/apps/monitoring/index.js +++ b/x-pack/test/functional/apps/monitoring/index.js @@ -46,7 +46,7 @@ export default function ({ loadTestFile }) { loadTestFile(require.resolve('./beats/listing')); loadTestFile(require.resolve('./beats/beat_detail')); - loadTestFile(require.resolve('./time_filter')); + // loadTestFile(require.resolve('./time_filter')); loadTestFile(require.resolve('./enable_monitoring')); loadTestFile(require.resolve('./setup/metricbeat_migration')); diff --git a/x-pack/test/functional/services/monitoring/cluster_list.js b/x-pack/test/functional/services/monitoring/cluster_list.js index aea82fbb6b793..f63e7b6cd125e 100644 --- a/x-pack/test/functional/services/monitoring/cluster_list.js +++ b/x-pack/test/functional/services/monitoring/cluster_list.js @@ -15,6 +15,7 @@ export function MonitoringClusterListProvider({ getService, getPageObjects }) { const SUBJ_SEARCH_BAR = `${SUBJ_TABLE_CONTAINER} > monitoringTableToolBar`; const SUBJ_CLUSTER_ROW_PREFIX = `${SUBJ_TABLE_CONTAINER} > clusterRow_`; + const ALERTS_MODAL_BUTTON = 'alerts-modal-button'; return new (class ClusterList { async assertDefaults() { @@ -41,6 +42,10 @@ export function MonitoringClusterListProvider({ getService, getPageObjects }) { return PageObjects.monitoring.tableClearFilter(SUBJ_SEARCH_BAR); } + closeAlertsModal() { + return testSubjects.click(ALERTS_MODAL_BUTTON); + } + getClusterLink(clusterUuid) { return testSubjects.find(`${SUBJ_CLUSTER_ROW_PREFIX}${clusterUuid} > clusterLink`); } From ffc37406bd8985402f434be86d7bd181d7c26a3c Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Fri, 9 Jul 2021 12:13:00 +0200 Subject: [PATCH 13/49] [APM] Get service name from context for alert flyout (#104103) --- .../alerting/alerting_flyout/index.tsx | 13 ++- .../error_count_alert_trigger/index.tsx | 57 +++++++------ .../alerting/service_alert_trigger/index.tsx | 4 - .../index.tsx | 85 +++++++++---------- .../index.tsx | 55 ++++++------ .../index.tsx | 70 +++++++-------- .../shared/apm_header_action_menu/index.tsx | 4 +- .../apm_service/apm_service_context.tsx | 12 ++- .../use_service_agent_name_fetcher.ts | 4 +- .../use_service_alerts_fetcher.tsx | 10 ++- .../use_service_transaction_types_fetcher.tsx | 4 +- .../apm/public/hooks/use_service_name.tsx | 16 ++++ 12 files changed, 184 insertions(+), 150 deletions(-) create mode 100644 x-pack/plugins/apm/public/hooks/use_service_name.tsx diff --git a/x-pack/plugins/apm/public/components/alerting/alerting_flyout/index.tsx b/x-pack/plugins/apm/public/components/alerting/alerting_flyout/index.tsx index b87298c5fe8a0..eef3271d5932d 100644 --- a/x-pack/plugins/apm/public/components/alerting/alerting_flyout/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/alerting_flyout/index.tsx @@ -6,7 +6,6 @@ */ import React, { useCallback, useMemo } from 'react'; -import { useParams } from 'react-router-dom'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; import { AlertType, @@ -14,6 +13,8 @@ import { } from '../../../../common/alert_types'; import { getInitialAlertValues } from '../get_initial_alert_values'; import { ApmPluginStartDeps } from '../../../plugin'; +import { useServiceName } from '../../../hooks/use_service_name'; +import { ApmServiceContextProvider } from '../../../context/apm_service/apm_service_context'; interface Props { addFlyoutVisible: boolean; setAddFlyoutVisibility: React.Dispatch>; @@ -22,7 +23,7 @@ interface Props { export function AlertingFlyout(props: Props) { const { addFlyoutVisible, setAddFlyoutVisibility, alertType } = props; - const { serviceName } = useParams<{ serviceName?: string }>(); + const serviceName = useServiceName(); const { services } = useKibana(); const initialValues = getInitialAlertValues(alertType, serviceName); @@ -43,5 +44,11 @@ export function AlertingFlyout(props: Props) { /* eslint-disable-next-line react-hooks/exhaustive-deps */ [alertType, onCloseAddFlyout, services.triggersActionsUi] ); - return <>{addFlyoutVisible && addAlertFlyout}; + return ( + <> + {addFlyoutVisible && ( + {addAlertFlyout} + )} + + ); } diff --git a/x-pack/plugins/apm/public/components/alerting/error_count_alert_trigger/index.tsx b/x-pack/plugins/apm/public/components/alerting/error_count_alert_trigger/index.tsx index fdfed6eb0d685..811353067ab60 100644 --- a/x-pack/plugins/apm/public/components/alerting/error_count_alert_trigger/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/error_count_alert_trigger/index.tsx @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; -import { useParams } from 'react-router-dom'; +import { defaults } from 'lodash'; import { ForLastExpression } from '../../../../../triggers_actions_ui/public'; import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; import { asInteger } from '../../../../common/utils/formatters'; @@ -18,6 +18,7 @@ import { ChartPreview } from '../chart_preview'; import { EnvironmentField, IsAboveField, ServiceField } from '../fields'; import { getAbsoluteTimeRange } from '../helper'; import { ServiceAlertTrigger } from '../service_alert_trigger'; +import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; export interface AlertParams { windowSize: number; @@ -35,49 +36,55 @@ interface Props { export function ErrorCountAlertTrigger(props: Props) { const { setAlertParams, setAlertProperty, alertParams } = props; - const { serviceName } = useParams<{ serviceName?: string }>(); + + const { serviceName: serviceNameFromContext } = useApmServiceContext(); + const { urlParams } = useUrlParams(); - const { start, end } = urlParams; + const { start, end, environment: environmentFromUrl } = urlParams; const { environmentOptions } = useEnvironmentsFetcher({ - serviceName, + serviceName: serviceNameFromContext, start, end, }); - const { threshold, windowSize, windowUnit, environment } = alertParams; + const params = defaults( + { + ...alertParams, + }, + { + threshold: 25, + windowSize: 1, + windowUnit: 'm', + environment: environmentFromUrl || ENVIRONMENT_ALL.value, + serviceName: serviceNameFromContext, + } + ); const { data } = useFetcher( (callApmApi) => { - if (windowSize && windowUnit) { + if (params.windowSize && params.windowUnit) { return callApmApi({ endpoint: 'GET /api/apm/alerts/chart_preview/transaction_error_count', params: { query: { - ...getAbsoluteTimeRange(windowSize, windowUnit), - environment, - serviceName, + ...getAbsoluteTimeRange(params.windowSize, params.windowUnit), + environment: params.environment, + serviceName: params.serviceName, }, }, }); } }, - [windowSize, windowUnit, environment, serviceName] + [ + params.windowSize, + params.windowUnit, + params.environment, + params.serviceName, + ] ); - const defaults = { - threshold: 25, - windowSize: 1, - windowUnit: 'm', - environment: urlParams.environment || ENVIRONMENT_ALL.value, - }; - - const params = { - ...defaults, - ...alertParams, - }; - const fields = [ - , + , ); return ( void; @@ -18,13 +17,10 @@ interface Props { } export function ServiceAlertTrigger(props: Props) { - const { serviceName } = useParams<{ serviceName?: string }>(); - const { fields, setAlertParams, defaults, chartPreview } = props; const params: Record = { ...defaults, - serviceName, }; useEffect(() => { diff --git a/x-pack/plugins/apm/public/components/alerting/transaction_duration_alert_trigger/index.tsx b/x-pack/plugins/apm/public/components/alerting/transaction_duration_alert_trigger/index.tsx index b4c78b54f329b..8f2713685127e 100644 --- a/x-pack/plugins/apm/public/components/alerting/transaction_duration_alert_trigger/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/transaction_duration_alert_trigger/index.tsx @@ -7,9 +7,8 @@ import { EuiSelect } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { map } from 'lodash'; +import { map, defaults } from 'lodash'; import React from 'react'; -import { useParams } from 'react-router-dom'; import { ForLastExpression } from '../../../../../triggers_actions_ui/public'; import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; import { getDurationFormatter } from '../../../../common/utils/formatters'; @@ -72,46 +71,60 @@ interface Props { export function TransactionDurationAlertTrigger(props: Props) { const { setAlertParams, alertParams, setAlertProperty } = props; const { urlParams } = useUrlParams(); - const { transactionTypes, transactionType } = useApmServiceContext(); - const { serviceName } = useParams<{ serviceName?: string }>(); - const { start, end } = urlParams; + + const { start, end, environment: environmentFromUrl } = urlParams; + + const { + transactionTypes, + transactionType: transactionTypeFromContext, + serviceName: serviceNameFromContext, + } = useApmServiceContext(); + + const params = defaults( + { + ...alertParams, + }, + { + aggregationType: 'avg', + environment: environmentFromUrl || ENVIRONMENT_ALL.value, + threshold: 1500, + windowSize: 5, + windowUnit: 'm', + transactionType: transactionTypeFromContext, + serviceName: serviceNameFromContext, + } + ); + const { environmentOptions } = useEnvironmentsFetcher({ - serviceName, + serviceName: params.serviceName, start, end, }); - const { - aggregationType, - environment, - threshold, - windowSize, - windowUnit, - } = alertParams; const { data } = useFetcher( (callApmApi) => { - if (windowSize && windowUnit) { + if (params.windowSize && params.windowUnit) { return callApmApi({ endpoint: 'GET /api/apm/alerts/chart_preview/transaction_duration', params: { query: { - ...getAbsoluteTimeRange(windowSize, windowUnit), - aggregationType, - environment, - serviceName, - transactionType: alertParams.transactionType, + ...getAbsoluteTimeRange(params.windowSize, params.windowUnit), + aggregationType: params.aggregationType, + environment: params.environment, + serviceName: params.serviceName, + transactionType: params.transactionType, }, }, }); } }, [ - aggregationType, - environment, - serviceName, - alertParams.transactionType, - windowSize, - windowUnit, + params.aggregationType, + params.environment, + params.serviceName, + params.transactionType, + params.windowSize, + params.windowUnit, ] ); @@ -122,7 +135,7 @@ export function TransactionDurationAlertTrigger(props: Props) { const yTickFormat = getResponseTimeTickFormatter(formatter); // The threshold from the form is in ms. Convert to µs. - const thresholdMs = threshold * 1000; + const thresholdMs = params.threshold * 1000; const chartPreview = ( ); - if (!transactionTypes.length || !serviceName) { + if (!transactionTypes.length || !params.serviceName) { return null; } - const defaults = { - threshold: 1500, - aggregationType: 'avg', - windowSize: 5, - windowUnit: 'm', - transactionType, - environment: urlParams.environment || ENVIRONMENT_ALL.value, - }; - - const params = { - ...defaults, - ...alertParams, - }; - const fields = [ - , + , ({ text: key, value: key }))} @@ -206,7 +205,7 @@ export function TransactionDurationAlertTrigger(props: Props) { return ( void; setAlertProperty: (key: string, value: any) => void; } @@ -47,35 +47,36 @@ interface Props { export function TransactionDurationAnomalyAlertTrigger(props: Props) { const { setAlertParams, alertParams, setAlertProperty } = props; const { urlParams } = useUrlParams(); - const { transactionTypes } = useApmServiceContext(); - const { serviceName } = useParams<{ serviceName?: string }>(); - const { start, end, transactionType } = urlParams; + const { + serviceName: serviceNameFromContext, + transactionType: transactionTypeFromContext, + transactionTypes, + } = useApmServiceContext(); + + const { start, end, environment: environmentFromUrl } = urlParams; + + const params = defaults( + { + ...alertParams, + }, + { + windowSize: 15, + windowUnit: 'm', + transactionType: transactionTypeFromContext, + environment: environmentFromUrl || ENVIRONMENT_ALL.value, + anomalySeverityType: ANOMALY_SEVERITY.CRITICAL, + serviceName: serviceNameFromContext, + } + ); + const { environmentOptions } = useEnvironmentsFetcher({ - serviceName, + serviceName: params.serviceName, start, end, }); - if (serviceName && !transactionTypes.length) { - return null; - } - - const defaults: Params = { - windowSize: 15, - windowUnit: 'm', - transactionType: transactionType || transactionTypes[0], - serviceName, - environment: urlParams.environment || ENVIRONMENT_ALL.value, - anomalySeverityType: ANOMALY_SEVERITY.CRITICAL, - }; - - const params = { - ...defaults, - ...alertParams, - }; - const fields = [ - , + , ({ text: key, value: key }))} @@ -107,7 +108,7 @@ export function TransactionDurationAnomalyAlertTrigger(props: Props) { return ( diff --git a/x-pack/plugins/apm/public/components/alerting/transaction_error_rate_alert_trigger/index.tsx b/x-pack/plugins/apm/public/components/alerting/transaction_error_rate_alert_trigger/index.tsx index c6f9c4efd98b6..4eb0b0e797571 100644 --- a/x-pack/plugins/apm/public/components/alerting/transaction_error_rate_alert_trigger/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/transaction_error_rate_alert_trigger/index.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { useParams } from 'react-router-dom'; +import { defaults } from 'lodash'; import { ForLastExpression } from '../../../../../triggers_actions_ui/public'; import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; import { asPercent } from '../../../../common/utils/formatters'; @@ -42,63 +42,65 @@ interface Props { export function TransactionErrorRateAlertTrigger(props: Props) { const { setAlertParams, alertParams, setAlertProperty } = props; const { urlParams } = useUrlParams(); - const { transactionTypes } = useApmServiceContext(); - const { serviceName } = useParams<{ serviceName?: string }>(); - const { start, end, transactionType } = urlParams; + const { + transactionType: transactionTypeFromContext, + transactionTypes, + serviceName: serviceNameFromContext, + } = useApmServiceContext(); + + const { start, end, environment: environmentFromUrl } = urlParams; + + const params = defaults, AlertParams>( + { + threshold: 30, + windowSize: 5, + windowUnit: 'm', + transactionType: transactionTypeFromContext, + environment: environmentFromUrl || ENVIRONMENT_ALL.value, + serviceName: serviceNameFromContext, + }, + alertParams + ); + const { environmentOptions } = useEnvironmentsFetcher({ - serviceName, + serviceName: serviceNameFromContext, start, end, }); - const { threshold, windowSize, windowUnit, environment } = alertParams; - - const thresholdAsPercent = (threshold ?? 0) / 100; + const thresholdAsPercent = (params.threshold ?? 0) / 100; const { data } = useFetcher( (callApmApi) => { - if (windowSize && windowUnit) { + if (params.windowSize && params.windowUnit) { return callApmApi({ endpoint: 'GET /api/apm/alerts/chart_preview/transaction_error_rate', params: { query: { - ...getAbsoluteTimeRange(windowSize, windowUnit), - environment, - serviceName, - transactionType: alertParams.transactionType, + ...getAbsoluteTimeRange(params.windowSize, params.windowUnit), + environment: params.environment, + serviceName: params.serviceName, + transactionType: params.transactionType, }, }, }); } }, [ - alertParams.transactionType, - environment, - serviceName, - windowSize, - windowUnit, + params.transactionType, + params.environment, + params.serviceName, + params.windowSize, + params.windowUnit, ] ); - if (serviceName && !transactionTypes.length) { + if (params.serviceName && !transactionTypes.length) { return null; } - const defaultParams = { - threshold: 30, - windowSize: 5, - windowUnit: 'm', - transactionType: transactionType || transactionTypes[0], - environment: urlParams.environment || ENVIRONMENT_ALL.value, - }; - - const params = { - ...defaultParams, - ...alertParams, - }; - const fields = [ - , + , ({ text: key, value: key }))} @@ -141,7 +143,7 @@ export function TransactionErrorRateAlertTrigger(props: Props) { return ( (); + const serviceName = useServiceName(); const { search } = window.location; const { application, http } = core; const { basePath } = http; diff --git a/x-pack/plugins/apm/public/context/apm_service/apm_service_context.tsx b/x-pack/plugins/apm/public/context/apm_service/apm_service_context.tsx index 54914580aefbd..cb826763425c2 100644 --- a/x-pack/plugins/apm/public/context/apm_service/apm_service_context.tsx +++ b/x-pack/plugins/apm/public/context/apm_service/apm_service_context.tsx @@ -18,6 +18,7 @@ import { useServiceAgentNameFetcher } from './use_service_agent_name_fetcher'; import { IUrlParams } from '../url_params_context/types'; import { APIReturnType } from '../../services/rest/createCallApmApi'; import { useServiceAlertsFetcher } from './use_service_alerts_fetcher'; +import { useServiceName } from '../../hooks/use_service_name'; export type APMServiceAlert = ValuesType< APIReturnType<'GET /api/apm/services/{serviceName}/alerts'>['alerts'] @@ -28,6 +29,7 @@ export const APMServiceContext = createContext<{ transactionType?: string; transactionTypes: string[]; alerts: APMServiceAlert[]; + serviceName?: string; }>({ transactionTypes: [], alerts: [] }); export function ApmServiceContextProvider({ @@ -36,9 +38,12 @@ export function ApmServiceContextProvider({ children: ReactNode; }) { const { urlParams } = useUrlParams(); - const { agentName } = useServiceAgentNameFetcher(); - const transactionTypes = useServiceTransactionTypesFetcher(); + const serviceName = useServiceName(); + + const { agentName } = useServiceAgentNameFetcher(serviceName); + + const transactionTypes = useServiceTransactionTypesFetcher(serviceName); const transactionType = getTransactionType({ urlParams, @@ -46,7 +51,7 @@ export function ApmServiceContextProvider({ agentName, }); - const { alerts } = useServiceAlertsFetcher(transactionType); + const { alerts } = useServiceAlertsFetcher({ serviceName, transactionType }); return ( diff --git a/x-pack/plugins/apm/public/context/apm_service/use_service_agent_name_fetcher.ts b/x-pack/plugins/apm/public/context/apm_service/use_service_agent_name_fetcher.ts index ceb6767898f06..82198eb73b3cb 100644 --- a/x-pack/plugins/apm/public/context/apm_service/use_service_agent_name_fetcher.ts +++ b/x-pack/plugins/apm/public/context/apm_service/use_service_agent_name_fetcher.ts @@ -5,12 +5,10 @@ * 2.0. */ -import { useParams } from 'react-router-dom'; import { useFetcher } from '../../hooks/use_fetcher'; import { useUrlParams } from '../url_params_context/use_url_params'; -export function useServiceAgentNameFetcher() { - const { serviceName } = useParams<{ serviceName?: string }>(); +export function useServiceAgentNameFetcher(serviceName?: string) { const { urlParams } = useUrlParams(); const { start, end } = urlParams; const { data, error, status } = useFetcher( diff --git a/x-pack/plugins/apm/public/context/apm_service/use_service_alerts_fetcher.tsx b/x-pack/plugins/apm/public/context/apm_service/use_service_alerts_fetcher.tsx index b07e6562a2154..54c95319afea2 100644 --- a/x-pack/plugins/apm/public/context/apm_service/use_service_alerts_fetcher.tsx +++ b/x-pack/plugins/apm/public/context/apm_service/use_service_alerts_fetcher.tsx @@ -5,13 +5,18 @@ * 2.0. */ -import { useParams } from 'react-router-dom'; import { useApmPluginContext } from '../apm_plugin/use_apm_plugin_context'; import { useUrlParams } from '../url_params_context/use_url_params'; import { useFetcher } from '../../hooks/use_fetcher'; import type { APMServiceAlert } from './apm_service_context'; -export function useServiceAlertsFetcher(transactionType?: string) { +export function useServiceAlertsFetcher({ + serviceName, + transactionType, +}: { + serviceName?: string; + transactionType?: string; +}) { const { plugins: { observability }, } = useApmPluginContext(); @@ -19,7 +24,6 @@ export function useServiceAlertsFetcher(transactionType?: string) { const { urlParams: { start, end, environment }, } = useUrlParams(); - const { serviceName } = useParams<{ serviceName?: string }>(); const experimentalAlertsEnabled = observability.isAlertingExperienceEnabled(); diff --git a/x-pack/plugins/apm/public/context/apm_service/use_service_transaction_types_fetcher.tsx b/x-pack/plugins/apm/public/context/apm_service/use_service_transaction_types_fetcher.tsx index ba70295ae70ca..b22c233b0c24b 100644 --- a/x-pack/plugins/apm/public/context/apm_service/use_service_transaction_types_fetcher.tsx +++ b/x-pack/plugins/apm/public/context/apm_service/use_service_transaction_types_fetcher.tsx @@ -5,14 +5,12 @@ * 2.0. */ -import { useParams } from 'react-router-dom'; import { useFetcher } from '../../hooks/use_fetcher'; import { useUrlParams } from '../url_params_context/use_url_params'; const INITIAL_DATA = { transactionTypes: [] }; -export function useServiceTransactionTypesFetcher() { - const { serviceName } = useParams<{ serviceName?: string }>(); +export function useServiceTransactionTypesFetcher(serviceName?: string) { const { urlParams } = useUrlParams(); const { start, end } = urlParams; const { data = INITIAL_DATA } = useFetcher( diff --git a/x-pack/plugins/apm/public/hooks/use_service_name.tsx b/x-pack/plugins/apm/public/hooks/use_service_name.tsx new file mode 100644 index 0000000000000..c003bf5223a32 --- /dev/null +++ b/x-pack/plugins/apm/public/hooks/use_service_name.tsx @@ -0,0 +1,16 @@ +/* + * 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 { useRouteMatch } from 'react-router-dom'; + +export function useServiceName(): string | undefined { + const match = useRouteMatch<{ serviceName?: string }>( + '/services/:serviceName' + ); + + return match ? match.params.serviceName : undefined; +} From b1f2f1395ddb7c0046d7f591f7345aa05c3e0cc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20S=C3=A1nchez?= Date: Fri, 9 Jul 2021 12:28:41 +0200 Subject: [PATCH 14/49] [Security Solution][Endpoint] Enrich events with ES data before rendering event filters modal (#104703) * Enrich events with ES data before rendering event filters modal * Add unmounted component control check * Fix error when closing and opening modal/flyout twice --- .../pages/event_filters/test_utils/index.ts | 101 ++++++++++++ .../view/components/flyout/index.tsx | 9 ++ .../view/components/modal/index.test.tsx | 153 +++++++++++------- .../view/components/modal/index.tsx | 78 +++++++-- 4 files changed, 275 insertions(+), 66 deletions(-) diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/test_utils/index.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/test_utils/index.ts index c45d0f88927be..fb2251072f03d 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/test_utils/index.ts +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/test_utils/index.ts @@ -106,6 +106,107 @@ export type EventFiltersListQueryHttpMockProviders = ResponseProvidersInterface< eventFiltersCreateList: () => ExceptionListItemSchema; }>; +export const esResponseData = () => ({ + rawResponse: { + took: 0, + timed_out: false, + _shards: { + total: 1, + successful: 1, + skipped: 0, + failed: 0, + }, + hits: { + total: 1, + max_score: 1, + hits: [ + { + _index: '.ds-logs-endpoint.events.process-default-2021.07.06-000001', + _id: 'ZihXfHoBP7UhLrksX9-B', + _score: 1, + _source: { + agent: { + id: '9b5fad11-6cd9-401b-afc1-1c2b0c8a2603', + type: 'endpoint', + version: '7.12.2', + }, + process: { + args: '"C:\\lsass.exe" \\d6e', + Ext: { + ancestry: ['wm6pfs8yo3', 'd0zpkp91jx'], + }, + parent: { + pid: 2356, + entity_id: 'wm6pfs8yo3', + }, + code_signature: { + subject_name: 'Microsoft', + status: 'trusted', + }, + name: 'lsass.exe', + pid: 2522, + entity_id: 'hmmlst1ewe', + executable: 'C:\\lsass.exe', + hash: { + md5: 'de8c03a1-099f-4d9b-9a5e-1961c18af19f', + }, + }, + network: { + forwarded_ip: '10.105.19.209', + direction: 'inbound', + }, + '@timestamp': 1625694621727, + ecs: { + version: '1.4.0', + }, + data_stream: { + namespace: 'default', + type: 'logs', + dataset: 'endpoint.events.process', + }, + host: { + hostname: 'Host-15ofk0qkwk', + os: { + Ext: { + variant: 'Windows Pro', + }, + name: 'Linux', + family: 'Debian OS', + version: '10.0', + platform: 'Windows', + full: 'Windows 10', + }, + ip: ['10.133.4.77', '10.135.101.75', '10.137.102.119'], + name: 'Host-15ofk0qkwk', + id: 'bae7a849-1ce9-421a-a879-5fee5dcd1fb9', + mac: ['ad-65-2d-17-aa-95', '63-4-33-c5-c6-90'], + architecture: 'uwp8xmxk1f', + }, + event: { + agent_id_status: 'auth_metadata_missing', + sequence: 36, + ingested: '2021-07-06T15:02:18.746828Z', + kind: 'event', + id: '02057ac0-0ae5-442c-9082-c5a7489dde09', + category: 'network', + type: 'start', + }, + user: { + domain: '22bk8yptgw', + name: 'dlkfiz43rh', + }, + }, + }, + ], + }, + }, + isPartial: false, + isRunning: false, + total: 1, + loaded: 1, + isRestored: false, +}); + /** * Mock `core.http` methods used by Event Filters List page */ diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/flyout/index.tsx b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/flyout/index.tsx index 9f81d25520524..48523ce45c3f9 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/flyout/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/flyout/index.tsx @@ -70,6 +70,15 @@ export const EventFiltersFlyout: React.FC = memo( payload: { entry: getInitialExceptionFromEvent() }, }); } + + return () => { + dispatch({ + type: 'eventFiltersFormStateChanged', + payload: { + type: 'UninitialisedResourceState', + }, + }); + }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/modal/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/modal/index.test.tsx index 178b774e91635..c77188694f507 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/modal/index.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/modal/index.test.tsx @@ -6,18 +6,24 @@ */ import React from 'react'; import { EventFiltersModal } from '.'; -import { RenderResult, act, render } from '@testing-library/react'; +import { RenderResult, act } from '@testing-library/react'; import { fireEvent } from '@testing-library/dom'; -import { Provider } from 'react-redux'; -import { ThemeProvider } from 'styled-components'; -import { createGlobalNoMiddlewareStore, ecsEventMock } from '../../../test_utils'; -import { getMockTheme } from '../../../../../../common/lib/kibana/kibana_react.mock'; +import { ecsEventMock, esResponseData } from '../../../test_utils'; +import { + AppContextTestRender, + createAppRootMockRenderer, +} from '../../../../../../common/mock/endpoint'; +import { MiddlewareActionSpyHelper } from '../../../../../../common/store/test_utils'; + import { MODAL_TITLE, MODAL_SUBTITLE, ACTIONS_CONFIRM, ACTIONS_CANCEL } from './translations'; import type { CreateExceptionListItemSchema, ExceptionListItemSchema, } from '@kbn/securitysolution-io-ts-list-types'; +import { useKibana } from '../../../../../../common/lib/kibana'; +import { EventFiltersListPageState } from '../../../types'; +jest.mock('../../../../../../common/lib/kibana'); jest.mock('../form'); jest.mock('../../hooks', () => { const originalModule = jest.requireActual('../../hooks'); @@ -29,67 +35,88 @@ jest.mock('../../hooks', () => { }; }); -const mockTheme = getMockTheme({ - eui: { - paddingSizes: { m: '2' }, - euiBreakpoints: { l: '2' }, - }, -}); - describe('Event filter modal', () => { let component: RenderResult; - let store: ReturnType; let onCancelMock: jest.Mock; - - const renderForm = () => { - const Wrapper: React.FC = ({ children }) => ( - - {children} - - ); - - return render(, { - wrapper: Wrapper, - }); - }; + let mockedContext: AppContextTestRender; + let waitForAction: MiddlewareActionSpyHelper['waitForAction']; + let render: () => ReturnType; + let getState: () => EventFiltersListPageState; beforeEach(() => { - store = createGlobalNoMiddlewareStore(); + mockedContext = createAppRootMockRenderer(); + waitForAction = mockedContext.middlewareSpy.waitForAction; onCancelMock = jest.fn(); + getState = () => mockedContext.store.getState().management.eventFilters; + render = () => + mockedContext.render(); + (useKibana as jest.Mock).mockReturnValue({ + services: { + http: {}, + data: { + search: { + search: jest.fn().mockImplementation(() => ({ toPromise: () => esResponseData() })), + }, + }, + notifications: {}, + }, + }); }); - it('should renders correctly', () => { - component = renderForm(); + it('should renders correctly', async () => { + await act(async () => { + component = render(); + await waitForAction('eventFiltersInitForm'); + }); + expect(component.getAllByText(MODAL_TITLE)).not.toBeNull(); expect(component.getByText(MODAL_SUBTITLE)).not.toBeNull(); expect(component.getAllByText(ACTIONS_CONFIRM)).not.toBeNull(); expect(component.getByText(ACTIONS_CANCEL)).not.toBeNull(); }); - it('should dispatch action to init form store on mount', () => { - component = renderForm(); - expect(store.getState()!.management!.eventFilters!.form!.entry).not.toBeNull(); + it('should dispatch action to init form store on mount', async () => { + await act(async () => { + component = render(); + await waitForAction('eventFiltersInitForm'); + }); + + expect(getState().form!.entry).not.toBeUndefined(); + }); + + it('should set OS with the enriched data', async () => { + await act(async () => { + component = render(); + await waitForAction('eventFiltersInitForm'); + }); + + expect(getState().form!.entry?.os_types).toContain('linux'); }); - it('should confirm form when button is disabled', () => { - component = renderForm(); + it('should confirm form when button is disabled', async () => { + await act(async () => { + component = render(); + await waitForAction('eventFiltersInitForm'); + }); + const confirmButton = component.getByTestId('add-exception-confirm-button'); act(() => { fireEvent.click(confirmButton); }); - expect(store.getState()!.management!.eventFilters!.form!.submissionResourceState.type).toBe( - 'UninitialisedResourceState' - ); + expect(getState().form!.submissionResourceState.type).toBe('UninitialisedResourceState'); }); - it('should confirm form when button is enabled', () => { - component = renderForm(); - store.dispatch({ + it('should confirm form when button is enabled', async () => { + await act(async () => { + component = render(); + await waitForAction('eventFiltersInitForm'); + }); + + mockedContext.store.dispatch({ type: 'eventFiltersChangeForm', payload: { entry: { - ...(store.getState()!.management!.eventFilters!.form! - .entry as CreateExceptionListItemSchema), + ...(getState().form!.entry as CreateExceptionListItemSchema), name: 'test', }, hasNameError: false, @@ -99,22 +126,24 @@ describe('Event filter modal', () => { act(() => { fireEvent.click(confirmButton); }); - expect(store.getState()!.management!.eventFilters!.form!.submissionResourceState.type).toBe( - 'UninitialisedResourceState' - ); - expect(confirmButton.hasAttribute('disabled')).toBeFalsy(); + expect(getState().form!.submissionResourceState.type).toBe('LoadingResourceState'); + expect(confirmButton.hasAttribute('disabled')).toBeTruthy(); }); - it('should close when exception has been submitted correctly', () => { - component = renderForm(); + it('should close when exception has been submitted correctly', async () => { + await act(async () => { + component = render(); + await waitForAction('eventFiltersInitForm'); + }); + expect(onCancelMock).toHaveBeenCalledTimes(0); act(() => { - store.dispatch({ + mockedContext.store.dispatch({ type: 'eventFiltersFormStateChanged', payload: { type: 'LoadedResourceState', - data: store.getState()!.management!.eventFilters!.form!.entry as ExceptionListItemSchema, + data: getState().form!.entry as ExceptionListItemSchema, }, }); }); @@ -122,8 +151,12 @@ describe('Event filter modal', () => { expect(onCancelMock).toHaveBeenCalledTimes(1); }); - it('should close when click on cancel button', () => { - component = renderForm(); + it('should close when click on cancel button', async () => { + await act(async () => { + component = render(); + await waitForAction('eventFiltersInitForm'); + }); + const cancelButton = component.getByText(ACTIONS_CANCEL); expect(onCancelMock).toHaveBeenCalledTimes(0); @@ -134,8 +167,12 @@ describe('Event filter modal', () => { expect(onCancelMock).toHaveBeenCalledTimes(1); }); - it('should close when close modal', () => { - component = renderForm(); + it('should close when close modal', async () => { + await act(async () => { + component = render(); + await waitForAction('eventFiltersInitForm'); + }); + const modalCloseButton = component.getByLabelText('Closes this modal window'); expect(onCancelMock).toHaveBeenCalledTimes(0); @@ -146,10 +183,14 @@ describe('Event filter modal', () => { expect(onCancelMock).toHaveBeenCalledTimes(1); }); - it('should prevent close when is loading action', () => { - component = renderForm(); + it('should prevent close when is loading action', async () => { + await act(async () => { + component = render(); + await waitForAction('eventFiltersInitForm'); + }); + act(() => { - store.dispatch({ + mockedContext.store.dispatch({ type: 'eventFiltersFormStateChanged', payload: { type: 'LoadingResourceState', diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/modal/index.tsx b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/modal/index.tsx index 50102d09248b1..dabf68ffed394 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/modal/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/modal/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { memo, useMemo, useEffect, useCallback } from 'react'; +import React, { memo, useMemo, useEffect, useCallback, useState, useRef } from 'react'; import { useDispatch } from 'react-redux'; import { Dispatch } from 'redux'; import styled, { css } from 'styled-components'; @@ -20,6 +20,7 @@ import { import { AppAction } from '../../../../../../common/store/actions'; import { Ecs } from '../../../../../../../common/ecs'; import { EventFiltersForm } from '../form'; +import { useKibana } from '../../../../../../common/lib/kibana'; import { useEventFiltersSelector, useEventFiltersNotification } from '../../hooks'; import { getFormHasError, @@ -61,10 +62,76 @@ const ModalBodySection = styled.section` export const EventFiltersModal: React.FC = memo(({ data, onCancel }) => { useEventFiltersNotification(); + const [enrichedData, setEnrichedData] = useState(); + const { + data: { search }, + } = useKibana().services; const dispatch = useDispatch>(); const formHasError = useEventFiltersSelector(getFormHasError); const creationInProgress = useEventFiltersSelector(isCreationInProgress); const creationSuccessful = useEventFiltersSelector(isCreationSuccessful); + const isMounted = useRef(false); + + // Enrich the event with missing ECS data from ES source + useEffect(() => { + isMounted.current = true; + + const enrichEvent = async () => { + if (!data._index) return; + const searchResponse = await search + .search({ + params: { + index: data._index, + body: { + query: { + match: { + _id: data._id, + }, + }, + }, + }, + }) + .toPromise(); + + if (!isMounted.current) return; + + setEnrichedData({ + ...data, + host: { + ...data.host, + os: { + ...(data?.host?.os || {}), + name: [searchResponse.rawResponse.hits.hits[0]._source.host.os.name], + }, + }, + }); + }; + + enrichEvent(); + + return () => { + dispatch({ + type: 'eventFiltersFormStateChanged', + payload: { + type: 'UninitialisedResourceState', + }, + }); + isMounted.current = false; + }; + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + // Initialize the store with the enriched event to allow render the form + useEffect(() => { + if (enrichedData) { + dispatch({ + type: 'eventFiltersInitForm', + payload: { entry: getInitialExceptionFromEvent(enrichedData) }, + }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [enrichedData]); useEffect(() => { if (creationSuccessful) { @@ -78,15 +145,6 @@ export const EventFiltersModal: React.FC = memo(({ data, } }, [creationSuccessful, onCancel, dispatch]); - // Initialize the store with the event passed as prop to allow render the form. It acts as componentDidMount - useEffect(() => { - dispatch({ - type: 'eventFiltersInitForm', - payload: { entry: getInitialExceptionFromEvent(data) }, - }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - const handleOnCancel = useCallback(() => { if (creationInProgress) return; onCancel(); From 01005289ae6c45e3cd49a61ced61349657df9cd2 Mon Sep 17 00:00:00 2001 From: Dmitry Shevchenko Date: Fri, 9 Jul 2021 12:43:40 +0200 Subject: [PATCH 15/49] Fix incorrect tags after rule duplication (#104948) --- .../server/lib/detection_engine/rules/add_tags.test.ts | 9 +++++++++ .../server/lib/detection_engine/rules/add_tags.ts | 4 +++- .../lib/detection_engine/rules/duplicate_rule.test.ts | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/add_tags.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/add_tags.test.ts index a871c7157d5e8..93fddc06b8068 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/add_tags.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/add_tags.test.ts @@ -33,8 +33,17 @@ describe('add_tags', () => { const tags2 = addTags(tags1, 'rule-1', false); expect(tags2).toEqual([ 'tag-1', + `${INTERNAL_RULE_ID_KEY}:rule-1`, `${INTERNAL_IMMUTABLE_KEY}:false`, + ]); + }); + + test('it should overwrite existing immutable tag if it exists', () => { + const tags1 = addTags(['tag-1', `${INTERNAL_IMMUTABLE_KEY}:true`], 'rule-1', false); + expect(tags1).toEqual([ + 'tag-1', `${INTERNAL_RULE_ID_KEY}:rule-1`, + `${INTERNAL_IMMUTABLE_KEY}:false`, ]); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/add_tags.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/add_tags.ts index 6ff4a54ad8e54..d66f961b38598 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/add_tags.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/add_tags.ts @@ -10,7 +10,9 @@ import { INTERNAL_RULE_ID_KEY, INTERNAL_IMMUTABLE_KEY } from '../../../../common export const addTags = (tags: string[], ruleId: string, immutable: boolean): string[] => { return Array.from( new Set([ - ...tags.filter((tag) => !tag.startsWith(INTERNAL_RULE_ID_KEY)), + ...tags.filter( + (tag) => !(tag.startsWith(INTERNAL_RULE_ID_KEY) || tag.startsWith(INTERNAL_IMMUTABLE_KEY)) + ), `${INTERNAL_RULE_ID_KEY}:${ruleId}`, `${INTERNAL_IMMUTABLE_KEY}:${immutable}`, ]) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/duplicate_rule.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/duplicate_rule.test.ts index 3046999a632c6..92b4dcff61b35 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/duplicate_rule.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/duplicate_rule.test.ts @@ -123,8 +123,8 @@ describe('duplicateRule', () => { }, "tags": Array [ "test", - "__internal_immutable:false", "__internal_rule_id:newId", + "__internal_immutable:false", ], "throttle": null, } From 3e118c4e23d716d71bbbccd63beeab7da0c7a577 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Fri, 9 Jul 2021 15:04:37 +0200 Subject: [PATCH 16/49] [Reporting] First accessibility test (#104410) * makeAllReportingPoliciesUnmanaged -> makeAllReportingIndicesUnmanaged * expose the reporting services on the functional services object shared with a11y * added data-test-subjs for a11y test * added reporting a11y test * updated jest test Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../report_listing.test.tsx.snap | 9 +++ .../public/management/report_listing.tsx | 3 +- x-pack/test/accessibility/apps/reporting.ts | 76 +++++++++++++++++++ x-pack/test/accessibility/config.ts | 1 + x-pack/test/functional/services/index.ts | 2 + .../ilm_migration_apis.ts | 4 +- .../services/scenarios.ts | 6 +- 7 files changed, 95 insertions(+), 6 deletions(-) create mode 100644 x-pack/test/accessibility/apps/reporting.ts diff --git a/x-pack/plugins/reporting/public/management/__snapshots__/report_listing.test.tsx.snap b/x-pack/plugins/reporting/public/management/__snapshots__/report_listing.test.tsx.snap index 9ce249aa32a1d..744a3b2d405c3 100644 --- a/x-pack/plugins/reporting/public/management/__snapshots__/report_listing.test.tsx.snap +++ b/x-pack/plugins/reporting/public/management/__snapshots__/report_listing.test.tsx.snap @@ -425,6 +425,7 @@ exports[`ReportListing Report job listing with some items 1`] = ` >
@@ -1434,6 +1435,7 @@ exports[`ReportListing Report job listing with some items 1`] = ` >
@@ -2457,6 +2459,7 @@ exports[`ReportListing Report job listing with some items 1`] = ` >
@@ -3527,6 +3530,7 @@ exports[`ReportListing Report job listing with some items 1`] = ` >
@@ -4630,6 +4634,7 @@ exports[`ReportListing Report job listing with some items 1`] = ` >
@@ -5700,6 +5705,7 @@ exports[`ReportListing Report job listing with some items 1`] = ` >
@@ -6770,6 +6776,7 @@ exports[`ReportListing Report job listing with some items 1`] = ` >
@@ -7840,6 +7847,7 @@ exports[`ReportListing Report job listing with some items 1`] = ` >
@@ -8910,6 +8918,7 @@ exports[`ReportListing Report job listing with some items 1`] = ` >
diff --git a/x-pack/plugins/reporting/public/management/report_listing.tsx b/x-pack/plugins/reporting/public/management/report_listing.tsx index 749e42de526d3..dd41314b4883f 100644 --- a/x-pack/plugins/reporting/public/management/report_listing.tsx +++ b/x-pack/plugins/reporting/public/management/report_listing.tsx @@ -151,6 +151,7 @@ class ReportListingUi extends Component { return ( <> @@ -375,7 +376,7 @@ class ReportListingUi extends Component { }), render: (objectTitle: string, record: Job) => { return ( -
+
{objectTitle}
{record.object_type} diff --git a/x-pack/test/accessibility/apps/reporting.ts b/x-pack/test/accessibility/apps/reporting.ts new file mode 100644 index 0000000000000..bccb650fa08ca --- /dev/null +++ b/x-pack/test/accessibility/apps/reporting.ts @@ -0,0 +1,76 @@ +/* + * 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 { FtrProviderContext } from '../ftr_provider_context'; + +import { JOB_PARAMS_RISON_CSV_DEPRECATED } from '../../reporting_api_integration/services/fixtures'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const { common } = getPageObjects(['common']); + const retry = getService('retry'); + const a11y = getService('a11y'); + const testSubjects = getService('testSubjects'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + const reporting = getService('reporting'); + const esArchiver = getService('esArchiver'); + const security = getService('security'); + + describe('Reporting', () => { + const createReportingUser = async () => { + await security.user.create(reporting.REPORTING_USER_USERNAME, { + password: reporting.REPORTING_USER_PASSWORD, + roles: ['reporting_user', 'data_analyst', 'kibana_user'], // Deprecated: using built-in `reporting_user` role grants all Reporting privileges + full_name: 'a reporting user', + }); + }; + + const deleteReportingUser = async () => { + await security.user.delete(reporting.REPORTING_USER_USERNAME); + }; + + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/reporting/logs'); + await esArchiver.load('x-pack/test/functional/es_archives/logstash_functional'); + + await createReportingUser(); + await reporting.loginReportingUser(); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/reporting/logs'); + await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional'); + + await deleteReportingUser(); + }); + + beforeEach(async () => { + // Add one report + await supertestWithoutAuth + .post(`/api/reporting/generate/csv`) + .auth(reporting.REPORTING_USER_USERNAME, reporting.REPORTING_USER_PASSWORD) + .set('kbn-xsrf', 'xxx') + .send({ jobParams: JOB_PARAMS_RISON_CSV_DEPRECATED }) + .expect(200); + + await retry.waitFor('Reporting app', async () => { + await common.navigateToApp('reporting'); + return testSubjects.exists('reportingPageHeader'); + }); + }); + + afterEach(async () => { + await reporting.deleteAllReports(); + }); + + it('List reports view', async () => { + await retry.waitForWithTimeout('A reporting list item', 5000, () => { + return testSubjects.exists('reportingListItemObjectTitle'); + }); + await a11y.testAppSnapshot(); + }); + }); +} diff --git a/x-pack/test/accessibility/config.ts b/x-pack/test/accessibility/config.ts index 81cfd70a23956..e79bbdb86a88a 100644 --- a/x-pack/test/accessibility/config.ts +++ b/x-pack/test/accessibility/config.ts @@ -37,6 +37,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { require.resolve('./apps/security_solution'), require.resolve('./apps/ml_embeddables_in_dashboard'), require.resolve('./apps/remote_clusters'), + require.resolve('./apps/reporting'), ], pageObjects, diff --git a/x-pack/test/functional/services/index.ts b/x-pack/test/functional/services/index.ts index 99293c71676b4..273db212400ab 100644 --- a/x-pack/test/functional/services/index.ts +++ b/x-pack/test/functional/services/index.ts @@ -9,6 +9,7 @@ import { services as kibanaFunctionalServices } from '../../../../test/functiona import { services as kibanaApiIntegrationServices } from '../../../../test/api_integration/services'; import { services as kibanaXPackApiIntegrationServices } from '../../api_integration/services'; import { services as commonServices } from '../../common/services'; +import { ReportingFunctionalProvider } from '../../reporting_functional/services'; import { MonitoringNoDataProvider, @@ -107,5 +108,6 @@ export const services = { dashboardDrilldownPanelActions: DashboardDrilldownPanelActionsProvider, dashboardDrilldownsManage: DashboardDrilldownsManageProvider, dashboardPanelTimeRange: DashboardPanelTimeRangeProvider, + reporting: ReportingFunctionalProvider, searchSessions: SearchSessionsService, }; diff --git a/x-pack/test/reporting_api_integration/reporting_without_security/ilm_migration_apis.ts b/x-pack/test/reporting_api_integration/reporting_without_security/ilm_migration_apis.ts index a0f4a3f91fe32..a9b6798a0224f 100644 --- a/x-pack/test/reporting_api_integration/reporting_without_security/ilm_migration_apis.ts +++ b/x-pack/test/reporting_api_integration/reporting_without_security/ilm_migration_apis.ts @@ -47,7 +47,7 @@ export default function ({ getService }: FtrProviderContext) { }); it('detects when reporting indices should be migrated due to missing ILM policy', async () => { - await reportingAPI.makeAllReportingPoliciesUnmanaged(); + await reportingAPI.makeAllReportingIndicesUnmanaged(); // TODO: Remove "any" when no longer through type issue "policy_id" missing await es.ilm.deleteLifecycle({ policy: ILM_POLICY_NAME } as any); @@ -63,7 +63,7 @@ export default function ({ getService }: FtrProviderContext) { }); it('detects when reporting indices should be migrated due to unmanaged indices', async () => { - await reportingAPI.makeAllReportingPoliciesUnmanaged(); + await reportingAPI.makeAllReportingIndicesUnmanaged(); await supertestNoAuth .post(`/api/reporting/generate/csv`) .set('kbn-xsrf', 'xxx') diff --git a/x-pack/test/reporting_api_integration/services/scenarios.ts b/x-pack/test/reporting_api_integration/services/scenarios.ts index eb32de9d0dc9c..08c07e0e257ed 100644 --- a/x-pack/test/reporting_api_integration/services/scenarios.ts +++ b/x-pack/test/reporting_api_integration/services/scenarios.ts @@ -181,8 +181,8 @@ export function createScenarios({ getService }: Pick { - log.debug('ReportingAPI.makeAllReportingPoliciesUnmanaged'); + const makeAllReportingIndicesUnmanaged = async () => { + log.debug('ReportingAPI.makeAllReportingIndicesUnmanaged'); const settings: any = { 'index.lifecycle.name': null, }; @@ -214,6 +214,6 @@ export function createScenarios({ getService }: Pick Date: Fri, 9 Jul 2021 16:18:38 +0300 Subject: [PATCH 17/49] [Alerting UI] Fixed bug when rule state was updated on Edit flyout opening: Index action set empty documents property for non history index. (#105014) --- .../builtin_action_types/es_index/es_index_params.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_params.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_params.tsx index 5d526e74564c5..56f333396908b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_params.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_params.tsx @@ -43,6 +43,7 @@ export const IndexParamsFields = ({ ALERT_HISTORY_PREFIX, '' ); + const [isActionConnectorChanged, setIsActionConnectorChanged] = useState(false); const getDocumentToIndex = (doc: Array> | undefined) => doc && doc.length > 0 ? ((doc[0] as unknown) as string) : undefined; @@ -67,11 +68,12 @@ export const IndexParamsFields = ({ setUsePreconfiguredSchema(true); editAction('documents', [JSON.stringify(AlertHistoryDocumentTemplate)], index); setDocumentToIndex(JSON.stringify(AlertHistoryDocumentTemplate)); - } else { + } else if (isActionConnectorChanged) { setUsePreconfiguredSchema(false); editAction('documents', undefined, index); setDocumentToIndex(undefined); } + setIsActionConnectorChanged(true); // eslint-disable-next-line react-hooks/exhaustive-deps }, [actionConnector?.id]); From 4806cf3fd6055ab0be9cec6c9711ad8104bf49d6 Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Fri, 9 Jul 2021 16:46:56 +0300 Subject: [PATCH 18/49] [TSVB] Table view - fix display of item urls (#105051) --- .../components/panel_config/table.tsx | 1 + .../components/vis_types/table/vis.js | 8 ++++++-- test/functional/apps/visualize/_tsvb_table.ts | 17 ++++++++++++++++- .../page_objects/visual_builder_page.ts | 7 +++++++ 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/plugins/vis_type_timeseries/public/application/components/panel_config/table.tsx b/src/plugins/vis_type_timeseries/public/application/components/panel_config/table.tsx index 9ba0822402562..3633f8add7457 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/panel_config/table.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/panel_config/table.tsx @@ -207,6 +207,7 @@ export class TablePanelConfig extends Component< diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/vis.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/vis.js index 4dd8f672c9ea3..4db038de912f5 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/vis.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/vis.js @@ -58,7 +58,11 @@ class TableVis extends Component { renderRow = (row) => { const { model } = this.props; - let rowDisplay = model.pivot_type === 'date' ? this.dateFormatter.convert(row.key) : row.key; + + let rowDisplay = getValueOrEmpty( + model.pivot_type === 'date' ? this.dateFormatter.convert(row.key) : row.key + ); + if (model.drilldown_url) { const url = replaceVars(model.drilldown_url, {}, { key: row.key }); rowDisplay = {rowDisplay}; @@ -98,7 +102,7 @@ class TableVis extends Component { }); return ( - {getValueOrEmpty(rowDisplay)} + {rowDisplay} {columns} ); diff --git a/test/functional/apps/visualize/_tsvb_table.ts b/test/functional/apps/visualize/_tsvb_table.ts index abe3b799e4711..de0771d3c8ec5 100644 --- a/test/functional/apps/visualize/_tsvb_table.ts +++ b/test/functional/apps/visualize/_tsvb_table.ts @@ -10,12 +10,14 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; -export default function ({ getPageObjects }: FtrProviderContext) { +export default function ({ getPageObjects, getService }: FtrProviderContext) { const { visualBuilder, visualize, visChart } = getPageObjects([ 'visualBuilder', 'visualize', 'visChart', ]); + const findService = getService('find'); + const retry = getService('retry'); describe('visual builder', function describeIndexTests() { before(async () => { @@ -43,6 +45,19 @@ export default function ({ getPageObjects }: FtrProviderContext) { expect(tableData).to.be(EXPECTED); }); + it('should display drilldown urls', async () => { + const baseURL = 'http://elastic.co/foo/'; + + await visualBuilder.clickPanelOptions('table'); + await visualBuilder.setDrilldownUrl(`${baseURL}{{key}}`); + + await retry.try(async () => { + const links = await findService.allByCssSelector(`a[href="${baseURL}ios"]`); + + expect(links.length).to.be(1); + }); + }); + it('should display correct values on changing metrics aggregation', async () => { const EXPECTED = 'OS Cardinality\nwin 8 12\nwin xp 9\nwin 7 8\nios 5\nosx 3'; diff --git a/test/functional/page_objects/visual_builder_page.ts b/test/functional/page_objects/visual_builder_page.ts index ea11560e37b6f..0f6c09f6ee464 100644 --- a/test/functional/page_objects/visual_builder_page.ts +++ b/test/functional/page_objects/visual_builder_page.ts @@ -277,6 +277,13 @@ export class VisualBuilderPageObject extends FtrService { await this.comboBox.setElement(formatterEl, formatter, { clickWithMouse: true }); } + public async setDrilldownUrl(value: string) { + const drilldownEl = await this.testSubjects.find('drilldownUrl'); + + await drilldownEl.clearValue(); + await drilldownEl.type(value); + } + /** * set duration formatter additional settings * From ef991b7c2b93c53c1ccf1db0a3ba7506fa549f23 Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Fri, 9 Jul 2021 16:53:48 +0300 Subject: [PATCH 19/49] [TSVB] fix include/exclude fields appear to migrated TSVB visualization when using Group by Terms (#104848) * [TSVB] Include/exclude fields appear to migrated TSVB visualization when using Group by Terms Closes: #104829 * add functional test * fix JEST --- .../components/aggs/cumulative_sum.js | 3 ++- .../application/components/aggs/derivative.js | 3 ++- .../components/aggs/moving_average.js | 3 ++- .../components/aggs/positive_only.js | 3 ++- .../components/aggs/serial_diff.js | 3 ++- .../components/aggs/std_sibling.js | 3 ++- .../application/components/aggs/vars.js | 3 ++- .../splits/__snapshots__/terms.test.js.snap | 2 ++ .../application/components/splits/terms.js | 8 +++++-- .../apps/visualize/_tsvb_time_series.ts | 5 ++++- .../page_objects/visual_builder_page.ts | 21 ++++++++++++++++++- 11 files changed, 46 insertions(+), 11 deletions(-) diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/cumulative_sum.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/cumulative_sum.js index f167bc35c06e9..a232a1dc03ae3 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/cumulative_sum.js +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/cumulative_sum.js @@ -23,6 +23,7 @@ import { EuiFormRow, EuiSpacer, } from '@elastic/eui'; +import { getIndexPatternKey } from '../../../../common/index_patterns_utils'; export function CumulativeSumAgg(props) { const { model, siblings, fields, indexPattern } = props; @@ -70,7 +71,7 @@ export function CumulativeSumAgg(props) { onChange={handleSelectChange('field')} metrics={siblings} metric={model} - fields={fields[indexPattern]} + fields={fields[getIndexPatternKey(indexPattern)]} value={model.field} exclude={[METRIC_TYPES.TOP_HIT]} /> diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/derivative.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/derivative.js index 9bed7015b0245..616f40128ff22 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/derivative.js +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/derivative.js @@ -25,6 +25,7 @@ import { EuiSpacer, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; +import { getIndexPatternKey } from '../../../../common/index_patterns_utils'; export const DerivativeAgg = (props) => { const { siblings, fields, indexPattern } = props; @@ -80,7 +81,7 @@ export const DerivativeAgg = (props) => { onChange={handleSelectChange('field')} metrics={siblings} metric={model} - fields={fields[indexPattern]} + fields={fields[getIndexPatternKey(indexPattern)]} value={model.field} exclude={[METRIC_TYPES.TOP_HIT]} fullWidth diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/moving_average.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/moving_average.js index 79f70f45d6256..a3ce43f97a36a 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/moving_average.js +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/moving_average.js @@ -26,6 +26,7 @@ import { EuiFieldNumber, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { getIndexPatternKey } from '../../../../common/index_patterns_utils'; const DEFAULTS = { model_type: MODEL_TYPES.UNWEIGHTED, @@ -141,7 +142,7 @@ export const MovingAverageAgg = (props) => { onChange={handleSelectChange('field')} metrics={siblings} metric={model} - fields={fields[indexPattern]} + fields={fields[getIndexPatternKey(indexPattern)]} value={model.field} exclude={[METRIC_TYPES.TOP_HIT]} /> diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/positive_only.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/positive_only.js index 156a042abb4e2..c974f5d5f05f5 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/positive_only.js +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/positive_only.js @@ -23,6 +23,7 @@ import { EuiSpacer, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; +import { getIndexPatternKey } from '../../../../common/index_patterns_utils'; export const PositiveOnlyAgg = (props) => { const { siblings, fields, indexPattern } = props; @@ -74,7 +75,7 @@ export const PositiveOnlyAgg = (props) => { onChange={handleSelectChange('field')} metrics={siblings} metric={model} - fields={fields[indexPattern]} + fields={fields[getIndexPatternKey(indexPattern)]} value={model.field} exclude={[METRIC_TYPES.TOP_HIT]} /> diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/serial_diff.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/serial_diff.js index a553b1a4c6671..efc2a72c3dd67 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/serial_diff.js +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/serial_diff.js @@ -24,6 +24,7 @@ import { EuiSpacer, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; +import { getIndexPatternKey } from '../../../../common/index_patterns_utils'; export const SerialDiffAgg = (props) => { const { siblings, fields, indexPattern, model } = props; @@ -74,7 +75,7 @@ export const SerialDiffAgg = (props) => { onChange={handleSelectChange('field')} metrics={siblings} metric={model} - fields={fields[indexPattern]} + fields={fields[getIndexPatternKey(indexPattern)]} value={model.field} exclude={[METRIC_TYPES.TOP_HIT]} /> diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/std_sibling.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/std_sibling.js index 9a30988d252e5..d2b3f45a70164 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/std_sibling.js +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/std_sibling.js @@ -27,6 +27,7 @@ import { EuiSpacer, } from '@elastic/eui'; import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; +import { getIndexPatternKey } from '../../../../common/index_patterns_utils'; const StandardSiblingAggUi = (props) => { const { siblings, intl, fields, indexPattern } = props; @@ -147,7 +148,7 @@ const StandardSiblingAggUi = (props) => { onChange={handleSelectChange('field')} exclude={[METRIC_TYPES.PERCENTILE, METRIC_TYPES.TOP_HIT]} metrics={siblings} - fields={fields[indexPattern]} + fields={fields[getIndexPatternKey(indexPattern)]} metric={model} value={model.field} /> diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/vars.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/vars.js index b9d554e254bcc..ba06b0fffd307 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/vars.js +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/vars.js @@ -15,6 +15,7 @@ import { AddDeleteButtons } from '../add_delete_buttons'; import { collectionActions } from '../lib/collection_actions'; import { MetricSelect } from './metric_select'; import { EuiFlexGroup, EuiFlexItem, EuiFieldText } from '@elastic/eui'; +import { getIndexPatternKey } from '../../../../common/index_patterns_utils'; export const newVariable = (opts) => ({ id: uuid.v1(), name: '', field: '', ...opts }); @@ -59,7 +60,7 @@ export class CalculationVars extends Component { metrics={this.props.metrics} metric={this.props.model} value={row.field} - fields={this.props.fields[this.props.indexPattern]} + fields={this.props.fields[getIndexPatternKey(this.props.indexPattern)]} includeSiblings={this.props.includeSiblings} exclude={this.props.exclude} /> diff --git a/src/plugins/vis_type_timeseries/public/application/components/splits/__snapshots__/terms.test.js.snap b/src/plugins/vis_type_timeseries/public/application/components/splits/__snapshots__/terms.test.js.snap index 562c463f6c83c..ce381a0e539d0 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/splits/__snapshots__/terms.test.js.snap +++ b/src/plugins/vis_type_timeseries/public/application/components/splits/__snapshots__/terms.test.js.snap @@ -78,6 +78,7 @@ exports[`src/legacy/core_plugins/metrics/public/components/splits/terms.test.js labelType="label" > @@ -100,6 +101,7 @@ exports[`src/legacy/core_plugins/metrics/public/components/splits/terms.test.js labelType="label" > diff --git a/src/plugins/vis_type_timeseries/public/application/components/splits/terms.js b/src/plugins/vis_type_timeseries/public/application/components/splits/terms.js index 7db6a75e2392c..9c097de38d56a 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/splits/terms.js +++ b/src/plugins/vis_type_timeseries/public/application/components/splits/terms.js @@ -27,6 +27,7 @@ import { import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; import { KBN_FIELD_TYPES } from '../../../../../data/public'; import { STACKED_OPTIONS } from '../../visualizations/constants'; +import { getIndexPatternKey } from '../../../../common/index_patterns_utils'; const DEFAULTS = { terms_direction: 'desc', terms_size: 10, terms_order_by: '_count' }; @@ -75,10 +76,11 @@ export const SplitByTermsUI = ({ }), }, ]; + const fieldsSelector = getIndexPatternKey(indexPattern); const selectedDirectionOption = dirOptions.find((option) => { return model.terms_direction === option.value; }); - const selectedField = find(fields[indexPattern], ({ name }) => name === model.terms_field); + const selectedField = find(fields[fieldsSelector], ({ name }) => name === model.terms_field); const selectedFieldType = get(selectedField, 'type'); if ( @@ -144,6 +146,7 @@ export const SplitByTermsUI = ({ @@ -160,6 +163,7 @@ export const SplitByTermsUI = ({ @@ -198,7 +202,7 @@ export const SplitByTermsUI = ({ metrics={metrics} clearable={false} additionalOptions={[defaultCount, terms]} - fields={fields[indexPattern]} + fields={fields[fieldsSelector]} onChange={handleSelectChange('terms_order_by')} restrict="basic" value={model.terms_order_by} diff --git a/test/functional/apps/visualize/_tsvb_time_series.ts b/test/functional/apps/visualize/_tsvb_time_series.ts index a0c9d806facc6..cc57d58348180 100644 --- a/test/functional/apps/visualize/_tsvb_time_series.ts +++ b/test/functional/apps/visualize/_tsvb_time_series.ts @@ -155,7 +155,10 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { describe('Clicking on the chart', () => { it(`should create a filter`, async () => { - await visualBuilder.setMetricsGroupByTerms('machine.os.raw'); + await visualBuilder.setMetricsGroupByTerms('machine.os.raw', { + include: 'win 7', + exclude: 'ios', + }); await visualBuilder.clickSeriesOption(); await testSubjects.click('visualizeSaveButton'); diff --git a/test/functional/page_objects/visual_builder_page.ts b/test/functional/page_objects/visual_builder_page.ts index 0f6c09f6ee464..8e28ffab6c9c3 100644 --- a/test/functional/page_objects/visual_builder_page.ts +++ b/test/functional/page_objects/visual_builder_page.ts @@ -635,7 +635,10 @@ export class VisualBuilderPageObject extends FtrService { return await this.find.allByCssSelector('.tvbSeriesEditor'); } - public async setMetricsGroupByTerms(field: string) { + public async setMetricsGroupByTerms( + field: string, + filtering: { include?: string; exclude?: string } = {} + ) { const groupBy = await this.find.byCssSelector( '.tvbAggRow--split [data-test-subj="comboBoxInput"]' ); @@ -643,6 +646,22 @@ export class VisualBuilderPageObject extends FtrService { await this.common.sleep(1000); const byField = await this.testSubjects.find('groupByField'); await this.comboBox.setElement(byField, field); + + await this.setMetricsGroupByFiltering(filtering.include, filtering.exclude); + } + + public async setMetricsGroupByFiltering(include?: string, exclude?: string) { + const setFilterValue = async (value: string | undefined, subjectKey: string) => { + if (typeof value === 'string') { + const valueSubject = await this.testSubjects.find(subjectKey); + + await valueSubject.clearValue(); + await valueSubject.type(value); + } + }; + + await setFilterValue(include, 'groupByInclude'); + await setFilterValue(exclude, 'groupByExclude'); } public async checkSelectedMetricsGroupByValue(value: string) { From efa4ce22faa06a7a0df288ead1e4b905939fe416 Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Fri, 9 Jul 2021 08:54:48 -0500 Subject: [PATCH 20/49] Storybook EUI theme decorator (#103582) Add an `EuiThemeProviderDecorator` to kibanaReact which uses the Storybook globals to set the EUI theme. Add global decorators to the APM and Observability plugins so all stories are wrapped in the `EuiThemeProviderDecorator`, and they don't need to specify it in the stories. Add [jest setup helpers recommended by @storybook/testing-react](https://github.com/storybookjs/testing-react#global-config). --- .../common/eui_styled_components.tsx | 17 +++- x-pack/plugins/apm/.storybook/jest_setup.js | 11 +++ x-pack/plugins/apm/.storybook/preview.js | 10 +++ x-pack/plugins/apm/jest.config.js | 2 + .../index.stories.tsx | 23 ++--- .../__stories__/MapTooltip.stories.tsx | 85 +++++++++---------- .../index.stories.tsx | 14 ++- .../Distribution/index.stories.tsx | 13 ++- .../exception_stacktrace.stories.tsx | 8 -- .../service_map/Popover/Popover.stories.tsx | 17 ++-- .../Popover/service_stats_list.stories.tsx | 10 +-- .../__stories__/Cytoscape.stories.tsx | 10 +-- .../cytoscape_example_data.stories.tsx | 10 +-- .../WaterfallContainer.stories.tsx | 9 +- .../shared/agent_icon/agent_icon.stories.tsx | 18 ++-- .../custom_tooltip.stories.tsx | 10 +-- ...ces_latency_distribution_chart.stories.tsx | 10 +-- .../latency_chart/latency_chart.stories.tsx | 27 +++--- .../shared/span_icon/span_icon.stories.tsx | 17 +--- .../observability/.storybook/jest_setup.js | 11 +++ .../observability/.storybook/preview.js | 10 +++ x-pack/plugins/observability/jest.config.js | 1 + .../__stories__/core_vitals.stories.tsx | 5 +- .../field_value_selection.stories.tsx | 19 ++--- .../public/pages/landing/landing.stories.tsx | 5 +- .../pages/overview/overview.stories.tsx | 5 +- 26 files changed, 165 insertions(+), 212 deletions(-) create mode 100644 x-pack/plugins/apm/.storybook/jest_setup.js create mode 100644 x-pack/plugins/apm/.storybook/preview.js create mode 100644 x-pack/plugins/observability/.storybook/jest_setup.js create mode 100644 x-pack/plugins/observability/.storybook/preview.js diff --git a/src/plugins/kibana_react/common/eui_styled_components.tsx b/src/plugins/kibana_react/common/eui_styled_components.tsx index 10cd168da6faa..62876a03c7d83 100644 --- a/src/plugins/kibana_react/common/eui_styled_components.tsx +++ b/src/plugins/kibana_react/common/eui_styled_components.tsx @@ -6,15 +6,14 @@ * Side Public License, v 1. */ +import type { DecoratorFn } from '@storybook/react'; import React from 'react'; import * as styledComponents from 'styled-components'; import { ThemedStyledComponentsModule, ThemeProvider, ThemeProviderProps } from 'styled-components'; - -import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; -import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; +import { euiThemeVars, euiLightVars, euiDarkVars } from '@kbn/ui-shared-deps/theme'; export interface EuiTheme { - eui: typeof euiLightVars | typeof euiDarkVars; + eui: typeof euiThemeVars; darkMode: boolean; } @@ -36,6 +35,16 @@ const EuiThemeProvider = < /> ); +/** + * Storybook decorator using the EUI theme provider. Uses the value from + * `globals` provided by the Storybook theme switcher. + */ +export const EuiThemeProviderDecorator: DecoratorFn = (storyFn, { globals }) => { + const darkMode = globals.euiTheme === 'v8.dark' || globals.euiTheme === 'v7.dark'; + + return {storyFn()}; +}; + const { default: euiStyled, css, diff --git a/x-pack/plugins/apm/.storybook/jest_setup.js b/x-pack/plugins/apm/.storybook/jest_setup.js new file mode 100644 index 0000000000000..32071b8aa3f62 --- /dev/null +++ b/x-pack/plugins/apm/.storybook/jest_setup.js @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { setGlobalConfig } from '@storybook/testing-react'; +import * as globalStorybookConfig from './preview'; + +setGlobalConfig(globalStorybookConfig); diff --git a/x-pack/plugins/apm/.storybook/preview.js b/x-pack/plugins/apm/.storybook/preview.js new file mode 100644 index 0000000000000..18343c15a6465 --- /dev/null +++ b/x-pack/plugins/apm/.storybook/preview.js @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiThemeProviderDecorator } from '../../../../src/plugins/kibana_react/common'; + +export const decorators = [EuiThemeProviderDecorator]; diff --git a/x-pack/plugins/apm/jest.config.js b/x-pack/plugins/apm/jest.config.js index caa8256cdb7ea..5bce9bbfb5b1b 100644 --- a/x-pack/plugins/apm/jest.config.js +++ b/x-pack/plugins/apm/jest.config.js @@ -11,4 +11,6 @@ module.exports = { preset: '@kbn/test', rootDir: path.resolve(__dirname, '../../..'), roots: ['/x-pack/plugins/apm'], + setupFiles: ['/x-pack/plugins/apm/.storybook/jest_setup.js'], + testPathIgnorePatterns: ['/x-pack/plugins/apm/e2e/'], }; diff --git a/x-pack/plugins/apm/public/components/alerting/error_count_alert_trigger/index.stories.tsx b/x-pack/plugins/apm/public/components/alerting/error_count_alert_trigger/index.stories.tsx index 83874e9584510..23afb9646dea7 100644 --- a/x-pack/plugins/apm/public/components/alerting/error_count_alert_trigger/index.stories.tsx +++ b/x-pack/plugins/apm/public/components/alerting/error_count_alert_trigger/index.stories.tsx @@ -8,7 +8,6 @@ import React from 'react'; import { MemoryRouter } from 'react-router-dom'; import { ErrorCountAlertTrigger } from '.'; -import { EuiThemeProvider } from '../../../../../../../src/plugins/kibana_react/common'; import { ApmPluginContextValue } from '../../../context/apm_plugin/apm_plugin_context'; import { mockApmPluginContextValue, @@ -20,19 +19,15 @@ export default { component: ErrorCountAlertTrigger, decorators: [ (Story: React.ComponentClass) => ( - - - -
- -
-
-
-
+ + +
+ +
+
+
), ], }; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/__stories__/MapTooltip.stories.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/__stories__/MapTooltip.stories.tsx index 1aad25fc89c0b..8263db648cd39 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/__stories__/MapTooltip.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/__stories__/MapTooltip.stories.tsx @@ -7,53 +7,50 @@ import { storiesOf } from '@storybook/react'; import React from 'react'; -import { EuiThemeProvider } from '../../../../../../../../../src/plugins/kibana_react/common'; import { MapToolTip } from '../MapToolTip'; import { COUNTRY_NAME, TRANSACTION_DURATION_COUNTRY } from '../useLayerList'; -storiesOf('app/RumDashboard/VisitorsRegionMap', module) - .addDecorator((storyFn) => {storyFn()}) - .add( - 'Tooltip', - () => { - const loadFeatureProps = async () => { - return [ +storiesOf('app/RumDashboard/VisitorsRegionMap', module).add( + 'Tooltip', + () => { + const loadFeatureProps = async () => { + return [ + { + getPropertyKey: () => COUNTRY_NAME, + getRawValue: () => 'United States', + }, + { + getPropertyKey: () => TRANSACTION_DURATION_COUNTRY, + getRawValue: () => 2434353, + }, + ]; + }; + return ( + COUNTRY_NAME, - getRawValue: () => 'United States', - }, - { - getPropertyKey: () => TRANSACTION_DURATION_COUNTRY, - getRawValue: () => 2434353, - }, - ]; - }; - return ( - - ); + actions: [], + }, + ]} + /> + ); + }, + { + info: { + propTables: false, + source: false, }, - { - info: { - propTables: false, - source: false, - }, - } - ); + } +); diff --git a/x-pack/plugins/apm/public/components/app/Settings/agent_configurations/AgentConfigurationCreateEdit/index.stories.tsx b/x-pack/plugins/apm/public/components/app/Settings/agent_configurations/AgentConfigurationCreateEdit/index.stories.tsx index cd5fa5db89a31..02ecf902f00a3 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/agent_configurations/AgentConfigurationCreateEdit/index.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/agent_configurations/AgentConfigurationCreateEdit/index.stories.tsx @@ -8,7 +8,6 @@ import { storiesOf } from '@storybook/react'; import React from 'react'; import { CoreStart } from 'kibana/public'; -import { EuiThemeProvider } from '../../../../../../../../../src/plugins/kibana_react/common'; import { AgentConfiguration } from '../../../../../../common/agent_configuration/configuration_types'; import { FETCH_STATUS } from '../../../../../hooks/use_fetcher'; import { createCallApmApi } from '../../../../../services/rest/createCallApmApi'; @@ -37,13 +36,11 @@ storiesOf( }; return ( - - - {storyFn()} - - + + {storyFn()} + ); }) .add( @@ -67,7 +64,6 @@ storiesOf( propTablesExclude: [ AgentConfigurationCreateEdit, ApmPluginContext.Provider, - EuiThemeProvider, ], source: false, }, diff --git a/x-pack/plugins/apm/public/components/app/error_group_details/Distribution/index.stories.tsx b/x-pack/plugins/apm/public/components/app/error_group_details/Distribution/index.stories.tsx index 8cc16dd801c25..d434a155c9cf4 100644 --- a/x-pack/plugins/apm/public/components/app/error_group_details/Distribution/index.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/error_group_details/Distribution/index.stories.tsx @@ -11,7 +11,6 @@ import { ApmPluginContext, ApmPluginContextValue, } from '../../../../context/apm_plugin/apm_plugin_context'; -import { EuiThemeProvider } from '../../../../../../../../src/plugins/kibana_react/common'; import { ErrorDistribution } from './'; export default { @@ -28,13 +27,11 @@ export default { }; return ( - - - - - - - + + + + + ); }, ], diff --git a/x-pack/plugins/apm/public/components/app/error_group_details/detail_view/exception_stacktrace.stories.tsx b/x-pack/plugins/apm/public/components/app/error_group_details/detail_view/exception_stacktrace.stories.tsx index f21c189584d31..9468202edf4d6 100644 --- a/x-pack/plugins/apm/public/components/app/error_group_details/detail_view/exception_stacktrace.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/error_group_details/detail_view/exception_stacktrace.stories.tsx @@ -7,7 +7,6 @@ import { Story } from '@storybook/react'; import React, { ComponentProps, ComponentType } from 'react'; -import { EuiThemeProvider } from '../../../../../../../../src/plugins/kibana_react/common'; import { ExceptionStacktrace } from './exception_stacktrace'; type Args = ComponentProps; @@ -15,13 +14,6 @@ type Args = ComponentProps; export default { title: 'app/ErrorGroupDetails/DetailView/ExceptionStacktrace', component: ExceptionStacktrace, - decorators: [ - (StoryComponent: ComponentType) => ( - - - - ), - ], }; export const JavaWithLongLines: Story = (args) => ( diff --git a/x-pack/plugins/apm/public/components/app/service_map/Popover/Popover.stories.tsx b/x-pack/plugins/apm/public/components/app/service_map/Popover/Popover.stories.tsx index 6b7626514d03f..324a38ea5db39 100644 --- a/x-pack/plugins/apm/public/components/app/service_map/Popover/Popover.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/service_map/Popover/Popover.stories.tsx @@ -8,7 +8,6 @@ import cytoscape from 'cytoscape'; import { CoreStart } from 'kibana/public'; import React, { ComponentType } from 'react'; -import { EuiThemeProvider } from '../../../../../../../../src/plugins/kibana_react/common'; import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context'; import { MockUrlParamsContextProvider } from '../../../../context/url_params_context/mock_url_params_context_provider'; import { createCallApmApi } from '../../../../services/rest/createCallApmApi'; @@ -38,15 +37,13 @@ export default { createCallApmApi(coreMock); return ( - - - -
- -
-
-
-
+ + +
+ +
+
+
); }, ], diff --git a/x-pack/plugins/apm/public/components/app/service_map/Popover/service_stats_list.stories.tsx b/x-pack/plugins/apm/public/components/app/service_map/Popover/service_stats_list.stories.tsx index a8f004a7295d9..f1a89043f826e 100644 --- a/x-pack/plugins/apm/public/components/app/service_map/Popover/service_stats_list.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/service_map/Popover/service_stats_list.stories.tsx @@ -5,20 +5,12 @@ * 2.0. */ -import React, { ComponentType } from 'react'; -import { EuiThemeProvider } from '../../../../../../../../src/plugins/kibana_react/common'; +import React from 'react'; import { ServiceStatsList } from './ServiceStatsList'; export default { title: 'app/ServiceMap/Popover/ServiceStatsList', component: ServiceStatsList, - decorators: [ - (Story: ComponentType) => ( - - - - ), - ], }; export function Example() { diff --git a/x-pack/plugins/apm/public/components/app/service_map/__stories__/Cytoscape.stories.tsx b/x-pack/plugins/apm/public/components/app/service_map/__stories__/Cytoscape.stories.tsx index 8bc0d7239e9c5..7ce9c3e943613 100644 --- a/x-pack/plugins/apm/public/components/app/service_map/__stories__/Cytoscape.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/service_map/__stories__/Cytoscape.stories.tsx @@ -6,21 +6,13 @@ */ import cytoscape from 'cytoscape'; -import React, { ComponentType } from 'react'; -import { EuiThemeProvider } from '../../../../../../../../src/plugins/kibana_react/common'; +import React from 'react'; import { Cytoscape } from '../Cytoscape'; import { Centerer } from './centerer'; export default { title: 'app/ServiceMap/Cytoscape', component: Cytoscape, - decorators: [ - (Story: ComponentType) => ( - - - - ), - ], }; export function Example() { diff --git a/x-pack/plugins/apm/public/components/app/service_map/__stories__/cytoscape_example_data.stories.tsx b/x-pack/plugins/apm/public/components/app/service_map/__stories__/cytoscape_example_data.stories.tsx index 45de632a152d4..192447ef7591a 100644 --- a/x-pack/plugins/apm/public/components/app/service_map/__stories__/cytoscape_example_data.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/service_map/__stories__/cytoscape_example_data.stories.tsx @@ -16,8 +16,7 @@ import { EuiSpacer, EuiToolTip, } from '@elastic/eui'; -import React, { ComponentType, useEffect, useState } from 'react'; -import { EuiThemeProvider } from '../../../../../../../../src/plugins/kibana_react/common'; +import React, { useEffect, useState } from 'react'; import { Cytoscape } from '../Cytoscape'; import { Centerer } from './centerer'; import exampleResponseHipsterStore from './example_response_hipster_store.json'; @@ -42,13 +41,6 @@ function getHeight() { export default { title: 'app/ServiceMap/Example data', component: Cytoscape, - decorators: [ - (Story: ComponentType) => ( - - - - ), - ], }; export function GenerateMap() { diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/WaterfallContainer.stories.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/WaterfallContainer.stories.tsx index 5ea2fca2dfa32..20ca3194fbfdf 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/WaterfallContainer.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/WaterfallContainer.stories.tsx @@ -7,7 +7,6 @@ import React, { ComponentType } from 'react'; import { MemoryRouter } from 'react-router-dom'; -import { EuiThemeProvider } from '../../../../../../../../../src/plugins/kibana_react/common'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { TraceAPIResponse } from '../../../../../../server/lib/traces/get_trace'; import { MockApmPluginContextWrapper } from '../../../../../context/apm_plugin/mock_apm_plugin_context'; @@ -27,11 +26,9 @@ export default { decorators: [ (Story: ComponentType) => ( - - - - - + + + ), ], diff --git a/x-pack/plugins/apm/public/components/shared/agent_icon/agent_icon.stories.tsx b/x-pack/plugins/apm/public/components/shared/agent_icon/agent_icon.stories.tsx index bc41fd58ea5d2..68c3edabfa44e 100644 --- a/x-pack/plugins/apm/public/components/shared/agent_icon/agent_icon.stories.tsx +++ b/x-pack/plugins/apm/public/components/shared/agent_icon/agent_icon.stories.tsx @@ -7,30 +7,22 @@ import { EuiCard, + EuiCodeBlock, EuiFlexGroup, - EuiImage, EuiFlexItem, + EuiImage, EuiSpacer, EuiToolTip, - EuiCodeBlock, } from '@elastic/eui'; -import React, { ComponentType } from 'react'; -import { AgentIcon } from './index'; -import { EuiThemeProvider } from '../../../../../../../src/plugins/kibana_react/common'; +import React from 'react'; import { AGENT_NAMES } from '../../../../common/agent_name'; -import { getAgentIcon } from './get_agent_icon'; import { useTheme } from '../../../hooks/use_theme'; +import { getAgentIcon } from './get_agent_icon'; +import { AgentIcon } from './index'; export default { title: 'shared/icons', component: AgentIcon, - decorators: [ - (Story: ComponentType) => ( - - - - ), - ], }; export function AgentIcons() { diff --git a/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/custom_tooltip.stories.tsx b/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/custom_tooltip.stories.tsx index 0eb5b0e84ff39..6128526c577e4 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/custom_tooltip.stories.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/custom_tooltip.stories.tsx @@ -6,8 +6,7 @@ */ import { TooltipInfo } from '@elastic/charts'; -import React, { ComponentType } from 'react'; -import { EuiThemeProvider } from '../../../../../../../../src/plugins/kibana_react/common'; +import React from 'react'; import { getDurationFormatter } from '../../../../../common/utils/formatters'; import { MainStatsServiceInstanceItem } from '../../../app/service_overview/service_overview_instances_chart_and_table'; import { CustomTooltip } from './custom_tooltip'; @@ -25,13 +24,6 @@ function getLatencyFormatter(props: TooltipInfo) { export default { title: 'shared/charts/InstancesLatencyDistributionChart/CustomTooltip', component: CustomTooltip, - decorators: [ - (Story: ComponentType) => ( - - - - ), - ], }; export function Example(props: TooltipInfo) { diff --git a/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/instances_latency_distribution_chart.stories.tsx b/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/instances_latency_distribution_chart.stories.tsx index c574645d485d5..80bfcca05aabc 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/instances_latency_distribution_chart.stories.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/instances_latency_distribution_chart.stories.tsx @@ -5,8 +5,7 @@ * 2.0. */ -import React, { ComponentType } from 'react'; -import { EuiThemeProvider } from '../../../../../../../../src/plugins/kibana_react/common'; +import React from 'react'; import { FETCH_STATUS } from '../../../../hooks/use_fetcher'; import { InstancesLatencyDistributionChart, @@ -16,13 +15,6 @@ import { export default { title: 'shared/charts/InstancesLatencyDistributionChart', component: InstancesLatencyDistributionChart, - decorators: [ - (Story: ComponentType) => ( - - - - ), - ], }; export function Example({ items }: InstancesLatencyDistributionChartProps) { diff --git a/x-pack/plugins/apm/public/components/shared/charts/latency_chart/latency_chart.stories.tsx b/x-pack/plugins/apm/public/components/shared/charts/latency_chart/latency_chart.stories.tsx index d1dcd831eadd7..ff2b95667a63a 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/latency_chart/latency_chart.stories.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/latency_chart/latency_chart.stories.tsx @@ -8,7 +8,6 @@ import { StoryContext } from '@storybook/react'; import React, { ComponentType } from 'react'; import { MemoryRouter, Route } from 'react-router-dom'; -import { EuiThemeProvider } from '../../../../../../../../src/plugins/kibana_react/common'; import { KibanaContextProvider } from '../../../../../../../../src/plugins/kibana_react/public'; import { LatencyAggregationType } from '../../../../../common/latency_aggregation_types'; import { @@ -76,20 +75,18 @@ export default { - - - - - - - - - + + + + + + + diff --git a/x-pack/plugins/apm/public/components/shared/span_icon/span_icon.stories.tsx b/x-pack/plugins/apm/public/components/shared/span_icon/span_icon.stories.tsx index b053f441e9632..7d2e2fbefc359 100644 --- a/x-pack/plugins/apm/public/components/shared/span_icon/span_icon.stories.tsx +++ b/x-pack/plugins/apm/public/components/shared/span_icon/span_icon.stories.tsx @@ -6,32 +6,23 @@ */ import { - EuiImage, EuiCard, + EuiCodeBlock, EuiFlexGroup, EuiFlexItem, + EuiImage, EuiSpacer, - EuiCodeBlock, EuiToolTip, } from '@elastic/eui'; -import React, { ComponentType } from 'react'; -import { EuiThemeProvider } from '../../../../../../../src/plugins/kibana_react/common'; +import React from 'react'; +import { getSpanIcon, spanTypeIcons } from './get_span_icon'; import { SpanIcon } from './index'; -import { getSpanIcon } from './get_span_icon'; -import { spanTypeIcons } from './get_span_icon'; const spanTypes = Object.keys(spanTypeIcons); export default { title: 'shared/icons', component: SpanIcon, - decorators: [ - (Story: ComponentType) => ( - - - - ), - ], }; export function SpanIcons() { diff --git a/x-pack/plugins/observability/.storybook/jest_setup.js b/x-pack/plugins/observability/.storybook/jest_setup.js new file mode 100644 index 0000000000000..32071b8aa3f62 --- /dev/null +++ b/x-pack/plugins/observability/.storybook/jest_setup.js @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { setGlobalConfig } from '@storybook/testing-react'; +import * as globalStorybookConfig from './preview'; + +setGlobalConfig(globalStorybookConfig); diff --git a/x-pack/plugins/observability/.storybook/preview.js b/x-pack/plugins/observability/.storybook/preview.js new file mode 100644 index 0000000000000..18343c15a6465 --- /dev/null +++ b/x-pack/plugins/observability/.storybook/preview.js @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiThemeProviderDecorator } from '../../../../src/plugins/kibana_react/common'; + +export const decorators = [EuiThemeProviderDecorator]; diff --git a/x-pack/plugins/observability/jest.config.js b/x-pack/plugins/observability/jest.config.js index 66d42122382f3..6fdeab06df053 100644 --- a/x-pack/plugins/observability/jest.config.js +++ b/x-pack/plugins/observability/jest.config.js @@ -9,4 +9,5 @@ module.exports = { preset: '@kbn/test', rootDir: '../../..', roots: ['/x-pack/plugins/observability'], + setupFiles: ['/x-pack/plugins/observability/.storybook/jest_setup.js'], }; diff --git a/x-pack/plugins/observability/public/components/shared/core_web_vitals/__stories__/core_vitals.stories.tsx b/x-pack/plugins/observability/public/components/shared/core_web_vitals/__stories__/core_vitals.stories.tsx index 5f5cf2cb4da21..5c07b4626cf19 100644 --- a/x-pack/plugins/observability/public/components/shared/core_web_vitals/__stories__/core_vitals.stories.tsx +++ b/x-pack/plugins/observability/public/components/shared/core_web_vitals/__stories__/core_vitals.stories.tsx @@ -9,7 +9,6 @@ import React, { ComponentType } from 'react'; import { __IntlProvider as IntlProvider } from '@kbn/i18n/react'; import { Observable } from 'rxjs'; import { CoreStart } from 'src/core/public'; -import { EuiThemeProvider } from '../../../../../../../../src/plugins/kibana_react/common'; import { createKibanaReactContext } from '../../../../../../../../src/plugins/kibana_react/public'; import { CoreVitalItem } from '../core_vital_item'; import { LCP_HELP_LABEL, LCP_LABEL } from '../translations'; @@ -25,9 +24,7 @@ export default { (Story: ComponentType) => ( - - - + ), diff --git a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/__stories__/field_value_selection.stories.tsx b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/__stories__/field_value_selection.stories.tsx index 80a25b82eb8cb..1152ba32960ed 100644 --- a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/__stories__/field_value_selection.stories.tsx +++ b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/__stories__/field_value_selection.stories.tsx @@ -10,7 +10,6 @@ import { __IntlProvider as IntlProvider } from '@kbn/i18n/react'; import { Observable } from 'rxjs'; import { CoreStart } from 'src/core/public'; import { text } from '@storybook/addon-knobs'; -import { EuiThemeProvider } from '../../../../../../../../src/plugins/kibana_react/common'; import { createKibanaReactContext } from '../../../../../../../../src/plugins/kibana_react/public'; import { FieldValueSelectionProps } from '../types'; import { FieldValueSelection } from '../field_value_selection'; @@ -31,16 +30,14 @@ export default { (Story: ComponentType) => ( - - {}} - selectedValue={[]} - loading={false} - setQuery={() => {}} - /> - + {}} + selectedValue={[]} + loading={false} + setQuery={() => {}} + /> ), diff --git a/x-pack/plugins/observability/public/pages/landing/landing.stories.tsx b/x-pack/plugins/observability/public/pages/landing/landing.stories.tsx index 86922b045c742..ef3ded61492c7 100644 --- a/x-pack/plugins/observability/public/pages/landing/landing.stories.tsx +++ b/x-pack/plugins/observability/public/pages/landing/landing.stories.tsx @@ -6,7 +6,6 @@ */ import React, { ComponentType } from 'react'; -import { EuiThemeProvider } from '../../../../../../src/plugins/kibana_react/common'; import { PluginContext, PluginContextValue } from '../../context/plugin_context'; import { LandingPage } from './'; @@ -27,9 +26,7 @@ export default { } as unknown) as PluginContextValue; return ( - - - + ); }, diff --git a/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx b/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx index dd424cf221d15..2982333235331 100644 --- a/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx +++ b/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx @@ -10,7 +10,6 @@ import { storiesOf } from '@storybook/react'; import { AppMountParameters, CoreStart } from 'kibana/public'; import React from 'react'; import { MemoryRouter } from 'react-router-dom'; -import { EuiThemeProvider } from '../../../../../../src/plugins/kibana_react/common'; import { UI_SETTINGS } from '../../../../../../src/plugins/data/public'; import { HasDataContextProvider } from '../../context/has_data_context'; import { PluginContext } from '../../context/plugin_context'; @@ -65,9 +64,7 @@ const withCore = makeDecorator({ ObservabilityPageTemplate: KibanaPageTemplate, }} > - - {storyFn(context)} - + {storyFn(context)} ); From 583b867d44b926fc1f19fa02f76bc2f83f99440b Mon Sep 17 00:00:00 2001 From: "Devin W. Hurley" Date: Fri, 9 Jul 2021 10:15:04 -0400 Subject: [PATCH 21/49] [RAC] [RBAC] Fix hyperlinks in typedocs for alerts client (#104975) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/rule_registry/docs/README.md | 2 +- .../alerts_client/classes/alertsclient.md | 32 +++++++++---------- .../interfaces/constructoroptions.md | 8 ++--- .../alerts_client/interfaces/updateoptions.md | 8 ++--- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/x-pack/plugins/rule_registry/docs/README.md b/x-pack/plugins/rule_registry/docs/README.md index a22dc1ab7e864..0eb2463005193 100644 --- a/x-pack/plugins/rule_registry/docs/README.md +++ b/x-pack/plugins/rule_registry/docs/README.md @@ -19,7 +19,7 @@ yarn global add typedoc typedoc-plugin-markdown ```bash cd x-pack/plugins/rule_registry/docs -npx typedoc --options alerts_client_typedoc.json +npx typedoc --gitRemote upstream --options alerts_client_typedoc.json ``` After running the above commands the files in the `server` directory will be updated to match the new tsdocs. diff --git a/x-pack/plugins/rule_registry/docs/alerts_client/classes/alertsclient.md b/x-pack/plugins/rule_registry/docs/alerts_client/classes/alertsclient.md index 9b639829a9f5f..359834bf9c2e7 100644 --- a/x-pack/plugins/rule_registry/docs/alerts_client/classes/alertsclient.md +++ b/x-pack/plugins/rule_registry/docs/alerts_client/classes/alertsclient.md @@ -41,7 +41,7 @@ on alerts as data. #### Defined in -[rule_registry/server/alert_data_client/alerts_client.ts:59](https://github.com/dhurley14/kibana/blob/d2173f5090e/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L59) +[rule_registry/server/alert_data_client/alerts_client.ts:59](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L59) ## Properties @@ -51,7 +51,7 @@ on alerts as data. #### Defined in -[rule_registry/server/alert_data_client/alerts_client.ts:57](https://github.com/dhurley14/kibana/blob/d2173f5090e/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L57) +[rule_registry/server/alert_data_client/alerts_client.ts:57](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L57) ___ @@ -61,7 +61,7 @@ ___ #### Defined in -[rule_registry/server/alert_data_client/alerts_client.ts:58](https://github.com/dhurley14/kibana/blob/d2173f5090e/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L58) +[rule_registry/server/alert_data_client/alerts_client.ts:58](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L58) ___ @@ -71,7 +71,7 @@ ___ #### Defined in -[rule_registry/server/alert_data_client/alerts_client.ts:59](https://github.com/dhurley14/kibana/blob/d2173f5090e/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L59) +[rule_registry/server/alert_data_client/alerts_client.ts:59](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L59) ___ @@ -81,13 +81,13 @@ ___ #### Defined in -[rule_registry/server/alert_data_client/alerts_client.ts:56](https://github.com/dhurley14/kibana/blob/d2173f5090e/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L56) +[rule_registry/server/alert_data_client/alerts_client.ts:56](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L56) ## Methods ### fetchAlert -▸ `Private` **fetchAlert**(`__namedParameters`): `Promise` +▸ `Private` **fetchAlert**(`__namedParameters`): `Promise`\>, ``"kibana.rac.alert.owner"`` \| ``"rule.id"``\> & { `kibana.rac.alert.owner`: `string` ; `rule.id`: `string` } & { `_version`: `undefined` \| `string` }\> #### Parameters @@ -97,17 +97,17 @@ ___ #### Returns -`Promise` +`Promise`\>, ``"kibana.rac.alert.owner"`` \| ``"rule.id"``\> & { `kibana.rac.alert.owner`: `string` ; `rule.id`: `string` } & { `_version`: `undefined` \| `string` }\> #### Defined in -[rule_registry/server/alert_data_client/alerts_client.ts:79](https://github.com/dhurley14/kibana/blob/d2173f5090e/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L79) +[rule_registry/server/alert_data_client/alerts_client.ts:79](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L79) ___ ### get -▸ **get**(`__namedParameters`): `Promise`\>\> +▸ **get**(`__namedParameters`): `Promise`\>\> #### Parameters @@ -117,11 +117,11 @@ ___ #### Returns -`Promise`\>\> +`Promise`\>\> #### Defined in -[rule_registry/server/alert_data_client/alerts_client.ts:108](https://github.com/dhurley14/kibana/blob/d2173f5090e/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L108) +[rule_registry/server/alert_data_client/alerts_client.ts:115](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L115) ___ @@ -142,7 +142,7 @@ ___ #### Defined in -[rule_registry/server/alert_data_client/alerts_client.ts:68](https://github.com/dhurley14/kibana/blob/d2173f5090e/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L68) +[rule_registry/server/alert_data_client/alerts_client.ts:68](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L68) ___ @@ -162,13 +162,13 @@ ___ #### Defined in -[rule_registry/server/alert_data_client/alerts_client.ts:200](https://github.com/dhurley14/kibana/blob/d2173f5090e/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L200) +[rule_registry/server/alert_data_client/alerts_client.ts:219](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L219) ___ ### update -▸ **update**(`__namedParameters`): `Promise`<`Object`\> +▸ **update**(`__namedParameters`): `Promise` #### Type parameters @@ -184,8 +184,8 @@ ___ #### Returns -`Promise`<`Object`\> +`Promise` #### Defined in -[rule_registry/server/alert_data_client/alerts_client.ts:146](https://github.com/dhurley14/kibana/blob/d2173f5090e/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L146) +[rule_registry/server/alert_data_client/alerts_client.ts:160](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L160) diff --git a/x-pack/plugins/rule_registry/docs/alerts_client/interfaces/constructoroptions.md b/x-pack/plugins/rule_registry/docs/alerts_client/interfaces/constructoroptions.md index e3dbc6b2c2354..051a5affc0379 100644 --- a/x-pack/plugins/rule_registry/docs/alerts_client/interfaces/constructoroptions.md +++ b/x-pack/plugins/rule_registry/docs/alerts_client/interfaces/constructoroptions.md @@ -19,7 +19,7 @@ #### Defined in -[rule_registry/server/alert_data_client/alerts_client.ts:34](https://github.com/dhurley14/kibana/blob/d2173f5090e/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L34) +[rule_registry/server/alert_data_client/alerts_client.ts:34](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L34) ___ @@ -29,7 +29,7 @@ ___ #### Defined in -[rule_registry/server/alert_data_client/alerts_client.ts:33](https://github.com/dhurley14/kibana/blob/d2173f5090e/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L33) +[rule_registry/server/alert_data_client/alerts_client.ts:33](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L33) ___ @@ -39,7 +39,7 @@ ___ #### Defined in -[rule_registry/server/alert_data_client/alerts_client.ts:35](https://github.com/dhurley14/kibana/blob/d2173f5090e/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L35) +[rule_registry/server/alert_data_client/alerts_client.ts:35](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L35) ___ @@ -49,4 +49,4 @@ ___ #### Defined in -[rule_registry/server/alert_data_client/alerts_client.ts:32](https://github.com/dhurley14/kibana/blob/d2173f5090e/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L32) +[rule_registry/server/alert_data_client/alerts_client.ts:32](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L32) diff --git a/x-pack/plugins/rule_registry/docs/alerts_client/interfaces/updateoptions.md b/x-pack/plugins/rule_registry/docs/alerts_client/interfaces/updateoptions.md index fbc0991635000..10e793155c196 100644 --- a/x-pack/plugins/rule_registry/docs/alerts_client/interfaces/updateoptions.md +++ b/x-pack/plugins/rule_registry/docs/alerts_client/interfaces/updateoptions.md @@ -25,7 +25,7 @@ #### Defined in -[rule_registry/server/alert_data_client/alerts_client.ts:41](https://github.com/dhurley14/kibana/blob/d2173f5090e/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L41) +[rule_registry/server/alert_data_client/alerts_client.ts:41](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L41) ___ @@ -35,7 +35,7 @@ ___ #### Defined in -[rule_registry/server/alert_data_client/alerts_client.ts:39](https://github.com/dhurley14/kibana/blob/d2173f5090e/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L39) +[rule_registry/server/alert_data_client/alerts_client.ts:39](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L39) ___ @@ -45,7 +45,7 @@ ___ #### Defined in -[rule_registry/server/alert_data_client/alerts_client.ts:42](https://github.com/dhurley14/kibana/blob/d2173f5090e/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L42) +[rule_registry/server/alert_data_client/alerts_client.ts:42](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L42) ___ @@ -55,4 +55,4 @@ ___ #### Defined in -[rule_registry/server/alert_data_client/alerts_client.ts:40](https://github.com/dhurley14/kibana/blob/d2173f5090e/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L40) +[rule_registry/server/alert_data_client/alerts_client.ts:40](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L40) From 3cbea1bcaabeaaf3399047b3da12cc6533f12a47 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Fri, 9 Jul 2021 15:19:27 +0100 Subject: [PATCH 22/49] skip flaky suite (#105016) --- x-pack/test/functional/apps/lens/formula.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/functional/apps/lens/formula.ts b/x-pack/test/functional/apps/lens/formula.ts index 8b87db21a1ffe..6148215d8b6d2 100644 --- a/x-pack/test/functional/apps/lens/formula.ts +++ b/x-pack/test/functional/apps/lens/formula.ts @@ -16,7 +16,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const fieldEditor = getService('fieldEditor'); - describe('lens formula', () => { + // FLAKY: https://github.com/elastic/kibana/issues/105016 + describe.skip('lens formula', () => { it('should transition from count to formula', async () => { await PageObjects.visualize.gotoVisualizationLandingPage(); await listingTable.searchForItemWithName('lnsXYvis'); From 8c366faf8f5e729216ea4b5f9b4c2d774d664742 Mon Sep 17 00:00:00 2001 From: Yaroslav Kuznietsov Date: Fri, 9 Jul 2021 18:32:34 +0300 Subject: [PATCH 23/49] Fix of the `ja-JP.json not found` error. (#105023) * Fixed `i18nrc not found ja-JP.json file` at expression_reveal_image. * Fixed `i18nrc not found ja-JP.json file` at `screenshotMode`. * Fixed `i18nrc not found ja-JP.json file` at `x-pack/plugins/timelines`. --- src/plugins/expression_reveal_image/.i18nrc.json | 7 ------- src/plugins/screenshot_mode/.i18nrc.json | 7 ------- x-pack/plugins/timelines/.i18nrc.json | 7 ------- 3 files changed, 21 deletions(-) delete mode 100755 src/plugins/expression_reveal_image/.i18nrc.json delete mode 100644 src/plugins/screenshot_mode/.i18nrc.json delete mode 100644 x-pack/plugins/timelines/.i18nrc.json diff --git a/src/plugins/expression_reveal_image/.i18nrc.json b/src/plugins/expression_reveal_image/.i18nrc.json deleted file mode 100755 index 5b073e4374519..0000000000000 --- a/src/plugins/expression_reveal_image/.i18nrc.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "prefix": "expressionRevealImage", - "paths": { - "expressionRevealImage": "." - }, - "translations": ["translations/ja-JP.json"] -} diff --git a/src/plugins/screenshot_mode/.i18nrc.json b/src/plugins/screenshot_mode/.i18nrc.json deleted file mode 100644 index 79643fbb63d30..0000000000000 --- a/src/plugins/screenshot_mode/.i18nrc.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "prefix": "screenshotMode", - "paths": { - "screenshotMode": "." - }, - "translations": ["translations/ja-JP.json"] -} diff --git a/x-pack/plugins/timelines/.i18nrc.json b/x-pack/plugins/timelines/.i18nrc.json deleted file mode 100644 index 4fe01ccc7bc69..0000000000000 --- a/x-pack/plugins/timelines/.i18nrc.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "prefix": "timelines", - "paths": { - "timelines": "." - }, - "translations": ["translations/ja-JP.json"] -} From dacb5949a2639932296347a09a14388c9e340af9 Mon Sep 17 00:00:00 2001 From: Thomas Watson Date: Fri, 9 Jul 2021 18:03:52 +0200 Subject: [PATCH 24/49] Bump fast-safe-stringify to v2.0.8 (#105066) --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index c64f4e1c4b281..227301eed8dd6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13489,9 +13489,9 @@ fast-redact@^3.0.0: integrity sha512-a/S/Hp6aoIjx7EmugtzLqXmcNsyFszqbt6qQ99BdG61QjBZF6shNis0BYR6TsZOQ1twYc0FN2Xdhwwbv6+KD0w== fast-safe-stringify@2.x.x, fast-safe-stringify@^2.0.4, fast-safe-stringify@^2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743" - integrity sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA== + version "2.0.8" + resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.8.tgz#dc2af48c46cf712b683e849b2bbd446b32de936f" + integrity sha512-lXatBjf3WPjmWD6DpIZxkeSsCOwqI0maYMpgDlx8g4U2qi4lbjA9oH/HD2a87G+KfsUmo5WbJFmqBZlPxtptag== fast-shallow-equal@^1.0.0: version "1.0.0" From 48fa754042e7597b65b69b2c98fb2c6aa2057c4d Mon Sep 17 00:00:00 2001 From: Vadim Yakhin Date: Fri, 9 Jul 2021 13:11:14 -0300 Subject: [PATCH 25/49] Replace cmd with bash as EuiCodeBlock language (#105065) cmd is no longer supported by 3rd party library used by EuiCodeBlock --- .../components/shared/status_item/status_item.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/status_item/status_item.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/status_item/status_item.tsx index 79455ccc1d90d..35ac8f1b85c05 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/status_item/status_item.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/status_item/status_item.tsx @@ -44,7 +44,7 @@ export const StatusItem: React.FC = ({ details }) => { const infoPopover = ( Date: Fri, 9 Jul 2021 09:23:18 -0700 Subject: [PATCH 26/49] [App Search] Relevance Tuning: Fix unsaved changes bug (#104951) * Fix unsavedChanges false positive when MultiInputRows is present - The fix for this is to change MultiInputRows from useEffect to useUpdateEffect, which prevents onChange from firing on initial mount/render (triggering updateBoostValue->unsavedChanges) @see https://github.com/streamich/react-use/blob/master/docs/useUpdateEffect.md * Fix precision tuner not triggering unsavedChanges --- .../multi_input_rows/multi_input_rows.test.tsx | 12 ++++++++++-- .../components/multi_input_rows/multi_input_rows.tsx | 5 +++-- .../relevance_tuning/relevance_tuning_logic.test.ts | 3 ++- .../relevance_tuning/relevance_tuning_logic.ts | 1 + 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/multi_input_rows.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/multi_input_rows.test.tsx index 3b8e1c96ff504..63952bc2a6de7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/multi_input_rows.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/multi_input_rows.test.tsx @@ -10,7 +10,7 @@ import '../../../__mocks__/shallow_useeffect.mock'; import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import { rerender } from '../../../test_helpers'; @@ -162,10 +162,18 @@ describe('MultiInputRows', () => { }); describe('onChange', () => { + let wrapper: ShallowWrapper; const onChange = jest.fn(); + beforeEach(() => { + wrapper = shallow(); + }); + + it('does not call on change on mount', () => { + expect(onChange).not.toHaveBeenCalled(); + }); + it('returns the current values dynamically on change', () => { - const wrapper = shallow(); setMockValues({ ...values, values: ['updated'] }); rerender(wrapper); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/multi_input_rows.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/multi_input_rows.tsx index ac61e69eb44c4..257f4b637f3e0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/multi_input_rows.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/multi_input_rows.tsx @@ -5,9 +5,10 @@ * 2.0. */ -import React, { useEffect } from 'react'; +import React from 'react'; import { useValues, useActions } from 'kea'; +import useUpdateEffect from 'react-use/lib/useUpdateEffect'; import { EuiForm, EuiButton, EuiButtonEmpty, EuiSpacer } from '@elastic/eui'; @@ -49,7 +50,7 @@ export const MultiInputRows: React.FC = ({ const { values, addedNewRow, hasEmptyValues, hasOnlyOneValue } = useValues(logic); const { addValue, editValue, deleteValue } = useActions(logic); - useEffect(() => { + useUpdateEffect(() => { if (onChange) { onChange(filterEmptyValues(values)); } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts index 4233a7b300d15..e2493b6404f7d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts @@ -229,7 +229,7 @@ describe('RelevanceTuningLogic', () => { }); describe('updatePrecision', () => { - it('should set precision inside search settings', () => { + it('should set precision inside search settings and set unsavedChanges to true', () => { mount(); RelevanceTuningLogic.actions.updatePrecision(9); @@ -239,6 +239,7 @@ describe('RelevanceTuningLogic', () => { ...DEFAULT_VALUES.searchSettings, precision: 9, }, + unsavedChanges: true, }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.ts index 743bb1aa1502b..02903b4588ed4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.ts @@ -191,6 +191,7 @@ export const RelevanceTuningLogic = kea< unsavedChanges: [ false, { + updatePrecision: () => true, setSearchSettings: () => true, setSearchSettingsResponse: () => false, }, From 81f09a863de65a16d88bbc8763957190ae4165c8 Mon Sep 17 00:00:00 2001 From: Ashokaditya Date: Fri, 9 Jul 2021 18:24:18 +0200 Subject: [PATCH 27/49] [Security Solution] [Endpoint] Allow filtering activity log with date range (#104085) * use date range in search query fixes elastic/security-team/issues/1137 * make any date selection fetch matching log fixes elastic/security-team/issues/1137 * use a single action for updating paging info and fetching data fixes elastic/security-team/issues/1137 * use consistent types for some reason TS was complaining earlier with `undefined` * reset date picker on tab load fixes elastic/security-team/issues/1137 * refactor date pickers into a component refs elastic/security-team/issues/1137 * clear dates on change of endpoint fixes elastic/security-team/issues/1137 * do not show empty state if date filtering results return empty data fixes elastic/security-team/issues/1137 * add tests fixes elastic/security-team/issues/1137 * review changes * update comment refs f551b67d661a330621fb77c435db26ec90288b81 * store invalidDateRange on redux store and decouple logic from the component review changes * fix test * fix lint * review changes * expand date picker to use the full width of the flyout review changes Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../common/endpoint/schema/actions.ts | 2 + .../common/endpoint/types/actions.ts | 2 + .../pages/endpoint_hosts/store/action.ts | 20 +-- .../pages/endpoint_hosts/store/builders.ts | 3 + .../pages/endpoint_hosts/store/index.test.ts | 1 + .../pages/endpoint_hosts/store/middleware.ts | 56 ++++++-- .../pages/endpoint_hosts/store/reducer.ts | 25 ++-- .../management/pages/endpoint_hosts/types.ts | 5 +- .../pages/endpoint_hosts/utils.test.ts | 30 +++++ .../management/pages/endpoint_hosts/utils.ts | 16 +++ .../activity_log_date_range_picker/index.tsx | 127 ++++++++++++++++++ .../components/endpoint_details_tabs.tsx | 13 +- .../view/details/endpoint_activity_log.tsx | 34 +++-- .../pages/endpoint_hosts/view/index.test.tsx | 21 +++ .../pages/endpoint_hosts/view/translations.ts | 14 ++ .../endpoint/routes/actions/audit_log.test.ts | 46 +++++++ .../routes/actions/audit_log_handler.ts | 12 +- .../server/endpoint/services/actions.ts | 56 ++++++-- 18 files changed, 416 insertions(+), 67 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/utils.test.ts create mode 100644 x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/activity_log_date_range_picker/index.tsx diff --git a/x-pack/plugins/security_solution/common/endpoint/schema/actions.ts b/x-pack/plugins/security_solution/common/endpoint/schema/actions.ts index fd4d89540f0ce..98cb7729c9440 100644 --- a/x-pack/plugins/security_solution/common/endpoint/schema/actions.ts +++ b/x-pack/plugins/security_solution/common/endpoint/schema/actions.ts @@ -23,6 +23,8 @@ export const EndpointActionLogRequestSchema = { query: schema.object({ page: schema.number({ defaultValue: 1, min: 1 }), page_size: schema.number({ defaultValue: 10, min: 1, max: 100 }), + start_date: schema.maybe(schema.string()), + end_date: schema.maybe(schema.string()), }), params: schema.object({ agent_id: schema.string(), diff --git a/x-pack/plugins/security_solution/common/endpoint/types/actions.ts b/x-pack/plugins/security_solution/common/endpoint/types/actions.ts index dfaad68e295eb..25fc831ca0aa4 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types/actions.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types/actions.ts @@ -60,6 +60,8 @@ export type ActivityLogEntry = ActivityLogAction | ActivityLogActionResponse; export interface ActivityLog { page: number; pageSize: number; + startDate?: string; + endDate?: string; data: ActivityLogEntry[]; } diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts index 42c16e151c45d..3fe6821abbcbe 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts @@ -146,13 +146,6 @@ export type EndpointIsolationRequestStateChange = Action<'endpointIsolationReque payload: EndpointState['isolationRequestState']; }; -export interface AppRequestedEndpointActivityLog { - type: 'appRequestedEndpointActivityLog'; - payload: { - page: number; - pageSize: number; - }; -} export type EndpointDetailsActivityLogChanged = Action<'endpointDetailsActivityLogChanged'> & { payload: EndpointState['endpointDetails']['activityLog']['logData']; }; @@ -165,9 +158,18 @@ export interface EndpointDetailsActivityLogUpdatePaging { type: 'endpointDetailsActivityLogUpdatePaging'; payload: { // disable paging when no more data after paging - disabled: boolean; + disabled?: boolean; page: number; pageSize: number; + startDate?: string; + endDate?: string; + }; +} + +export interface EndpointDetailsActivityLogUpdateIsInvalidDateRange { + type: 'endpointDetailsActivityLogUpdateIsInvalidDateRange'; + payload: { + isInvalidDateRange?: boolean; }; } @@ -181,8 +183,8 @@ export type EndpointAction = | ServerFailedToReturnEndpointList | ServerReturnedEndpointDetails | ServerFailedToReturnEndpointDetails - | AppRequestedEndpointActivityLog | EndpointDetailsActivityLogUpdatePaging + | EndpointDetailsActivityLogUpdateIsInvalidDateRange | EndpointDetailsFlyoutTabChanged | EndpointDetailsActivityLogChanged | ServerReturnedEndpointPolicyResponse diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/builders.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/builders.ts index 5db861d18cd69..2a869095cac81 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/builders.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/builders.ts @@ -25,6 +25,9 @@ export const initialEndpointPageState = (): Immutable => { disabled: false, page: 1, pageSize: 50, + startDate: undefined, + endDate: undefined, + isInvalidDateRange: false, }, logData: createUninitialisedResourceState(), }, diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts index 3bf625d726e5f..a9c65c74015c6 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts @@ -48,6 +48,7 @@ describe('EndpointList store concerns', () => { disabled: false, page: 1, pageSize: 50, + isInvalidDateRange: false, }, logData: { type: 'UninitialisedResourceState' }, }, diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts index e34e9cf5a83f3..f233fbdec5415 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts @@ -65,6 +65,7 @@ import { resolvePathVariables } from '../../../../common/utils/resolve_path_vari import { EndpointPackageInfoStateChanged } from './action'; import { fetchPendingActionsByAgentId } from '../../../../common/lib/endpoint_pending_actions'; import { EndpointDetailsTabsTypes } from '../view/details/components/endpoint_details_tabs'; +import { getIsInvalidDateRange } from '../utils'; type EndpointPageStore = ImmutableMiddlewareAPI; @@ -400,21 +401,50 @@ export const endpointMiddlewareFactory: ImmutableMiddlewareFactory(getActivityLogData(getState())), - }); - + if ( + action.type === 'endpointDetailsActivityLogUpdatePaging' && + hasSelectedEndpoint(getState()) + ) { try { - const { page, pageSize } = getActivityLogDataPaging(getState()); + const { disabled, page, pageSize, startDate, endDate } = getActivityLogDataPaging( + getState() + ); + // don't page when paging is disabled or when date ranges are invalid + if (disabled) { + return; + } + if (getIsInvalidDateRange({ startDate, endDate })) { + dispatch({ + type: 'endpointDetailsActivityLogUpdateIsInvalidDateRange', + payload: { + isInvalidDateRange: true, + }, + }); + return; + } + + dispatch({ + type: 'endpointDetailsActivityLogUpdateIsInvalidDateRange', + payload: { + isInvalidDateRange: false, + }, + }); + dispatch({ + type: 'endpointDetailsActivityLogChanged', + // ts error to be fixed when AsyncResourceState is refactored (#830) + // @ts-expect-error + payload: createLoadingResourceState(getActivityLogData(getState())), + }); const route = resolvePathVariables(ENDPOINT_ACTION_LOG_ROUTE, { agent_id: selectedAgent(getState()), }); const activityLog = await coreStart.http.get(route, { - query: { page, page_size: pageSize }, + query: { + page, + page_size: pageSize, + start_date: startDate, + end_date: endDate, + }, }); const lastLoadedLogData = getLastLoadedActivityLogData(getState()); @@ -428,6 +458,8 @@ export const endpointMiddlewareFactory: ImmutableMiddlewareFactory 1 ? activityLog.page - 1 : 1, pageSize: activityLog.pageSize, + startDate: activityLog.startDate, + endDate: activityLog.endDate, }, }); } diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts index 0981d621f26f3..1498ce08db8ab 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts @@ -41,6 +41,8 @@ const handleEndpointDetailsActivityLogChanged: CaseReducer; }; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/utils.test.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/utils.test.ts new file mode 100644 index 0000000000000..fa2aaaa16ae37 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/utils.test.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import moment from 'moment'; +import { getIsInvalidDateRange } from './utils'; + +describe('utils', () => { + describe('getIsInvalidDateRange', () => { + it('should return FALSE when either dates are undefined', () => { + expect(getIsInvalidDateRange({})).toBe(false); + expect(getIsInvalidDateRange({ startDate: moment().subtract(1, 'd').toISOString() })).toBe( + false + ); + expect(getIsInvalidDateRange({ endDate: moment().toISOString() })).toBe(false); + }); + + it('should return TRUE when startDate is after endDate', () => { + expect( + getIsInvalidDateRange({ + startDate: moment().toISOString(), + endDate: moment().subtract(1, 'd').toISOString(), + }) + ).toBe(true); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/utils.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/utils.ts index 3e17992dd975f..e2d619743c83b 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/utils.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/utils.ts @@ -5,6 +5,7 @@ * 2.0. */ +import moment from 'moment'; import { HostInfo, HostMetadata } from '../../../../common/endpoint/types'; export const isPolicyOutOfDate = ( @@ -23,3 +24,18 @@ export const isPolicyOutOfDate = ( reported.endpoint_policy_version >= current.endpoint.revision ); }; + +export const getIsInvalidDateRange = ({ + startDate, + endDate, +}: { + startDate?: string; + endDate?: string; +}) => { + if (startDate && endDate) { + const start = moment(startDate); + const end = moment(endDate); + return start.isAfter(end); + } + return false; +}; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/activity_log_date_range_picker/index.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/activity_log_date_range_picker/index.tsx new file mode 100644 index 0000000000000..f11d2872e3d26 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/activity_log_date_range_picker/index.tsx @@ -0,0 +1,127 @@ +/* + * 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 { useDispatch } from 'react-redux'; +import React, { memo, useCallback } from 'react'; +import styled from 'styled-components'; +import moment from 'moment'; +import { EuiFlexGroup, EuiFlexItem, EuiDatePicker, EuiDatePickerRange } from '@elastic/eui'; + +import * as i18 from '../../../translations'; +import { useEndpointSelector } from '../../../hooks'; +import { getActivityLogDataPaging } from '../../../../store/selectors'; + +const DatePickerWrapper = styled.div` + width: ${(props) => props.theme.eui.fractions.single.percentage}; + background: white; +`; +const StickyFlexItem = styled(EuiFlexItem)` + position: sticky; + top: ${(props) => props.theme.eui.euiSizeM}; + z-index: 1; +`; + +export const DateRangePicker = memo(() => { + const dispatch = useDispatch(); + const { page, pageSize, startDate, endDate, isInvalidDateRange } = useEndpointSelector( + getActivityLogDataPaging + ); + + const onClear = useCallback( + ({ clearStart = false, clearEnd = false }: { clearStart?: boolean; clearEnd?: boolean }) => { + dispatch({ + type: 'endpointDetailsActivityLogUpdatePaging', + payload: { + disabled: false, + page, + pageSize, + startDate: clearStart ? undefined : startDate, + endDate: clearEnd ? undefined : endDate, + }, + }); + }, + [dispatch, endDate, startDate, page, pageSize] + ); + + const onChangeStartDate = useCallback( + (date) => { + dispatch({ + type: 'endpointDetailsActivityLogUpdatePaging', + payload: { + disabled: false, + page, + pageSize, + startDate: date ? date?.toISOString() : undefined, + endDate: endDate ? endDate : undefined, + }, + }); + }, + [dispatch, endDate, page, pageSize] + ); + + const onChangeEndDate = useCallback( + (date) => { + dispatch({ + type: 'endpointDetailsActivityLogUpdatePaging', + payload: { + disabled: false, + page, + pageSize, + startDate: startDate ? startDate : undefined, + endDate: date ? date.toISOString() : undefined, + }, + }); + }, + [dispatch, startDate, page, pageSize] + ); + + return ( + + + + + onClear({ clearStart: true })} + placeholderText={i18.ACTIVITY_LOG.datePicker.startDate} + selected={startDate ? moment(startDate) : undefined} + showTimeSelect + startDate={startDate ? moment(startDate) : undefined} + /> + } + endDateControl={ + onClear({ clearEnd: true })} + placeholderText={i18.ACTIVITY_LOG.datePicker.endDate} + selected={endDate ? moment(endDate) : undefined} + showTimeSelect + startDate={startDate ? moment(startDate) : undefined} + /> + } + /> + + + + + ); +}); + +DateRangePicker.displayName = 'DateRangePicker'; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/endpoint_details_tabs.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/endpoint_details_tabs.tsx index aa1f56529657e..73a3734e4ca88 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/endpoint_details_tabs.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/endpoint_details_tabs.tsx @@ -56,19 +56,14 @@ export const EndpointDetailsFlyoutTabs = memo( }, }); if (tab.id === EndpointDetailsTabsTypes.activityLog) { - const paging = { - page: 1, - pageSize, - }; - dispatch({ - type: 'appRequestedEndpointActivityLog', - payload: paging, - }); dispatch({ type: 'endpointDetailsActivityLogUpdatePaging', payload: { disabled: false, - ...paging, + page: 1, + pageSize, + startDate: undefined, + endDate: undefined, }, }); } diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_activity_log.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_activity_log.tsx index 360d6e3842816..121f23fdb3a9e 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_activity_log.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_activity_log.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { memo, useCallback, useEffect, useRef } from 'react'; +import React, { memo, useCallback, useEffect, useMemo, useRef } from 'react'; import styled from 'styled-components'; import { @@ -17,6 +17,7 @@ import { } from '@elastic/eui'; import { useDispatch } from 'react-redux'; import { LogEntry } from './components/log_entry'; +import { DateRangePicker } from './components/activity_log_date_range_picker'; import * as i18 from '../translations'; import { Immutable, ActivityLog } from '../../../../../../common/endpoint/types'; import { AsyncResourceState } from '../../../../state'; @@ -31,12 +32,12 @@ import { getActivityLogRequestLoading, } from '../../store/selectors'; -const StyledEuiFlexGroup = styled(EuiFlexGroup)` - height: 85vh; +const StyledEuiFlexGroup = styled(EuiFlexGroup)<{ isShorter: boolean }>` + height: ${({ isShorter }) => (isShorter ? '25vh' : '85vh')}; `; const LoadMoreTrigger = styled.div` - height: 6px; - width: 100%; + height: ${(props) => props.theme.eui.euiSizeXS}; + width: ${(props) => props.theme.eui.fractions.single.percentage}; `; export const EndpointActivityLog = memo( @@ -48,25 +49,37 @@ export const EndpointActivityLog = memo( const activityLogSize = activityLogData.length; const activityLogError = useEndpointSelector(getActivityLogError); const dispatch = useDispatch<(action: EndpointAction) => void>(); - const { page, pageSize, disabled: isPagingDisabled } = useEndpointSelector( + const { page, pageSize, startDate, endDate, disabled: isPagingDisabled } = useEndpointSelector( getActivityLogDataPaging ); + const hasActiveDateRange = useMemo(() => !!startDate || !!endDate, [startDate, endDate]); + const showEmptyState = useMemo( + () => (activityLogLoaded && !activityLogSize && !hasActiveDateRange) || activityLogError, + [activityLogLoaded, activityLogSize, hasActiveDateRange, activityLogError] + ); + const isShorter = useMemo( + () => !!(hasActiveDateRange && isPagingDisabled && !activityLogLoading && !activityLogSize), + [hasActiveDateRange, isPagingDisabled, activityLogLoading, activityLogSize] + ); + const loadMoreTrigger = useRef(null); const getActivityLog = useCallback( (entries: IntersectionObserverEntry[]) => { const isTargetIntersecting = entries.some((entry) => entry.isIntersecting); if (isTargetIntersecting && activityLogLoaded && !isPagingDisabled) { dispatch({ - type: 'appRequestedEndpointActivityLog', + type: 'endpointDetailsActivityLogUpdatePaging', payload: { page: page + 1, pageSize, + startDate, + endDate, }, }); } }, - [activityLogLoaded, dispatch, isPagingDisabled, page, pageSize] + [activityLogLoaded, dispatch, isPagingDisabled, page, pageSize, startDate, endDate] ); useEffect(() => { @@ -82,8 +95,8 @@ export const EndpointActivityLog = memo( return ( <> - - {(activityLogLoaded && !activityLogSize) || activityLogError ? ( + + {showEmptyState ? ( ) : ( <> + {activityLogLoaded && activityLogData.map((logEntry) => ( diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx index ee5ef52d00f18..aafac38accd89 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx @@ -889,6 +889,27 @@ describe('when on the endpoint list page', () => { const emptyState = await renderResult.queryByTestId('activityLogEmpty'); expect(emptyState).not.toBe(null); }); + + it('should not display empty state with no log data while date range filter is active', async () => { + const activityLogTab = await renderResult.findByTestId('activity_log'); + reactTestingLibrary.act(() => { + reactTestingLibrary.fireEvent.click(activityLogTab); + }); + await middlewareSpy.waitForAction('endpointDetailsActivityLogChanged'); + reactTestingLibrary.act(() => { + dispatchEndpointDetailsActivityLogChanged('success', { + page: 1, + pageSize: 50, + startDate: new Date().toISOString(), + data: [], + }); + }); + + const emptyState = await renderResult.queryByTestId('activityLogEmpty'); + const dateRangePicker = await renderResult.queryByTestId('activityLogDateRangePicker'); + expect(emptyState).toBe(null); + expect(dateRangePicker).not.toBe(null); + }); }); describe('when showing host Policy Response panel', () => { diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/translations.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/translations.ts index 89ffd2d23807e..7759935aa840a 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/translations.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/translations.ts @@ -15,6 +15,20 @@ export const ACTIVITY_LOG = { tabTitle: i18n.translate('xpack.securitySolution.endpointDetails.activityLog', { defaultMessage: 'Activity Log', }), + datePicker: { + startDate: i18n.translate( + 'xpack.securitySolution.endpointDetails.activityLog.datePicker.startDate', + { + defaultMessage: 'Pick a start date', + } + ), + endDate: i18n.translate( + 'xpack.securitySolution.endpointDetails.activityLog.datePicker.endDate', + { + defaultMessage: 'Pick an end date', + } + ), + }, LogEntry: { endOfLog: i18n.translate( 'xpack.securitySolution.endpointDetails.activityLog.logEntry.action.endOfLog', diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/audit_log.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/audit_log.test.ts index c7f07151f8724..d9069444a10d7 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/audit_log.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/audit_log.test.ts @@ -60,6 +60,37 @@ describe('Action Log API', () => { }).not.toThrow(); }); + it('should work with all query params', () => { + expect(() => { + EndpointActionLogRequestSchema.query.validate({ + page: 10, + page_size: 100, + start_date: new Date(new Date().setDate(new Date().getDate() - 1)).toISOString(), // yesterday + end_date: new Date().toISOString(), // today + }); + }).not.toThrow(); + }); + + it('should work with just startDate', () => { + expect(() => { + EndpointActionLogRequestSchema.query.validate({ + page: 1, + page_size: 100, + start_date: new Date(new Date().setDate(new Date().getDate() - 1)).toISOString(), // yesterday + }); + }).not.toThrow(); + }); + + it('should work with just endDate', () => { + expect(() => { + EndpointActionLogRequestSchema.query.validate({ + page: 1, + page_size: 100, + end_date: new Date().toISOString(), // today + }); + }).not.toThrow(); + }); + it('should not work without allowed page and page_size params', () => { expect(() => { EndpointActionLogRequestSchema.query.validate({ page_size: 101 }); @@ -176,5 +207,20 @@ describe('Action Log API', () => { expect(error.message).toEqual(`Error fetching actions log for agent_id ${mockID}`); } }); + + it('should return date ranges if present in the query', async () => { + havingActionsAndResponses([], []); + const startDate = new Date(new Date().setDate(new Date().getDate() - 1)).toISOString(); + const endDate = new Date().toISOString(); + const response = await getActivityLog({ + page: 1, + page_size: 50, + start_date: startDate, + end_date: endDate, + }); + expect(response.ok).toBeCalled(); + expect((response.ok.mock.calls[0][0]?.body as ActivityLog).startDate).toEqual(startDate); + expect((response.ok.mock.calls[0][0]?.body as ActivityLog).endDate).toEqual(endDate); + }); }); }); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/audit_log_handler.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/audit_log_handler.ts index 5e9594f478b31..716c1ab833559 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/audit_log_handler.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/audit_log_handler.ts @@ -27,10 +27,18 @@ export const actionsLogRequestHandler = ( return async (context, req, res) => { const { params: { agent_id: elasticAgentId }, - query: { page, page_size: pageSize }, + query: { page, page_size: pageSize, start_date: startDate, end_date: endDate }, } = req; - const body = await getAuditLogResponse({ elasticAgentId, page, pageSize, context, logger }); + const body = await getAuditLogResponse({ + elasticAgentId, + page, + pageSize, + startDate, + endDate, + context, + logger, + }); return res.ok({ body, }); diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions.ts index 9d8db5b9a2154..89f088e322ffa 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions.ts @@ -19,28 +19,37 @@ export const getAuditLogResponse = async ({ elasticAgentId, page, pageSize, + startDate, + endDate, context, logger, }: { elasticAgentId: string; page: number; pageSize: number; + startDate?: string; + endDate?: string; context: SecuritySolutionRequestHandlerContext; logger: Logger; -}): Promise<{ - page: number; - pageSize: number; - data: ActivityLog['data']; -}> => { +}): Promise => { const size = Math.floor(pageSize / 2); const from = page <= 1 ? 0 : page * size - size + 1; const esClient = context.core.elasticsearch.client.asCurrentUser; - - const data = await getActivityLog({ esClient, from, size, elasticAgentId, logger }); + const data = await getActivityLog({ + esClient, + from, + size, + startDate, + endDate, + elasticAgentId, + logger, + }); return { page, pageSize, + startDate, + endDate, data, }; }; @@ -49,6 +58,8 @@ const getActivityLog = async ({ esClient, size, from, + startDate, + endDate, elasticAgentId, logger, }: { @@ -56,6 +67,8 @@ const getActivityLog = async ({ elasticAgentId: string; size: number; from: number; + startDate?: string; + endDate?: string; logger: Logger; }) => { const options = { @@ -67,8 +80,22 @@ const getActivityLog = async ({ let actionsResult; let responsesResult; + const dateFilters = []; + if (startDate) { + dateFilters.push({ range: { '@timestamp': { gte: startDate } } }); + } + if (endDate) { + dateFilters.push({ range: { '@timestamp': { lte: endDate } } }); + } try { + // fetch actions with matching agent_id + const baseActionFilters = [ + { term: { agents: elasticAgentId } }, + { term: { input_type: 'endpoint' } }, + { term: { type: 'INPUT_ACTION' } }, + ]; + const actionsFilters = [...baseActionFilters, ...dateFilters]; actionsResult = await esClient.search( { index: AGENT_ACTIONS_INDEX, @@ -77,11 +104,8 @@ const getActivityLog = async ({ body: { query: { bool: { - filter: [ - { term: { agents: elasticAgentId } }, - { term: { input_type: 'endpoint' } }, - { term: { type: 'INPUT_ACTION' } }, - ], + // @ts-ignore + filter: actionsFilters, }, }, sort: [ @@ -99,6 +123,12 @@ const getActivityLog = async ({ (e) => (e._source as EndpointAction).action_id ); + // fetch responses with matching `action_id`s + const baseResponsesFilter = [ + { term: { agent_id: elasticAgentId } }, + { terms: { action_id: actionIds } }, + ]; + const responsesFilters = [...baseResponsesFilter, ...dateFilters]; responsesResult = await esClient.search( { index: AGENT_ACTIONS_RESULTS_INDEX, @@ -106,7 +136,7 @@ const getActivityLog = async ({ body: { query: { bool: { - filter: [{ term: { agent_id: elasticAgentId } }, { terms: { action_id: actionIds } }], + filter: responsesFilters, }, }, }, From 357264db09fd9c6ce1e5440a52837cd0da798220 Mon Sep 17 00:00:00 2001 From: gchaps <33642766+gchaps@users.noreply.github.com> Date: Fri, 9 Jul 2021 09:34:27 -0700 Subject: [PATCH 28/49] [DOCS] Fixes formatting in search sessions doc (#105077) --- docs/discover/search-sessions.asciidoc | 31 ++++++++++++++++++-------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/docs/discover/search-sessions.asciidoc b/docs/discover/search-sessions.asciidoc index b503e8cfba3b4..652583db785ad 100644 --- a/docs/discover/search-sessions.asciidoc +++ b/docs/discover/search-sessions.asciidoc @@ -72,15 +72,28 @@ behaves differently: [float] ==== Limitations -Certain visualization features do not fully support background search sessions yet. If a dashboard using these features gets restored, -all panels using unsupported features won't load immediately, but instead send out additional data requests which can take a while to complete. -In this case a warning *Your search session is still running* will be shown. +Certain visualization features do not fully support background search sessions. If a dashboard +using these features is restored, +all panels using unsupported features won't load immediately, but instead send out additional +data requests, which can take a while to complete. +The warning *Your search session is still running* is shown. -You can either wait for these additional requests to complete or come back to the dashboard later when all data requests have been finished. +You can either wait for these additional requests to complete or come back to the dashboard later +when all data requests have finished. A panel on a dashboard can behave like this if one of the following features is used: -* *Lens* - A *top values* dimension with an enabled setting *Group other values as "Other"* (configurable in the *Advanced* section of the dimension) -* *Lens* - An *intervals* dimension is used -* *Aggregation based* visualizations - A *terms* aggregation is used with an enabled setting *Group other values in separate bucket* -* *Aggregation based* visualizations - A *histogram* aggregation is used -* *Maps* - Layers using joins, blended layers or tracks layers are used + +**Lens** + +* A *top values* dimension with an enabled *Group other values as "Other"* setting. +This is configurable in the *Advanced* section of the dimension. +* An *intervals* dimension. + +**Aggregation based** visualizations + +* A *terms* aggregation with an enabled *Group other values in separate bucket* setting. +* A *histogram* aggregation. + +**Maps** + +* Layers using joins, blended layers, or tracks layers. From 0809d5d15fab7a3013b89f4e8cdb571385aa1ee6 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Fri, 9 Jul 2021 18:53:20 +0200 Subject: [PATCH 29/49] [ML] Add integration tests for `trained_models` API (#104819) * [ML] api integration tests for get trained models endpoint * [ML] delete ingest pipelines after tests execution * [ML] deleteIngestPipeline method * [ML] test for unauthorized user * [ML] tests for model stats * [ML] delete trained model tests * [ML] fix typo * [ML] fix expect package path * [ML] get model pipelines tests * [ML] test for aliases * [ML] add tests for a 404 response * [ML] fix typo * [ML] fix typo --- x-pack/test/api_integration/apis/ml/index.ts | 1 + .../apis/ml/trained_models/delete_model.ts | 67 ++++++++++++++ .../ml/trained_models/get_model_pipelines.ts | 51 ++++++++++ .../apis/ml/trained_models/get_model_stats.ts | 54 +++++++++++ .../apis/ml/trained_models/get_models.ts | 88 ++++++++++++++++++ .../apis/ml/trained_models/index.ts | 17 ++++ x-pack/test/functional/services/ml/api.ts | 92 +++++++++++++++++++ .../functional/services/ml/trained_models.ts | 36 +------- 8 files changed, 371 insertions(+), 35 deletions(-) create mode 100644 x-pack/test/api_integration/apis/ml/trained_models/delete_model.ts create mode 100644 x-pack/test/api_integration/apis/ml/trained_models/get_model_pipelines.ts create mode 100644 x-pack/test/api_integration/apis/ml/trained_models/get_model_stats.ts create mode 100644 x-pack/test/api_integration/apis/ml/trained_models/get_models.ts create mode 100644 x-pack/test/api_integration/apis/ml/trained_models/index.ts diff --git a/x-pack/test/api_integration/apis/ml/index.ts b/x-pack/test/api_integration/apis/ml/index.ts index 7154debc3e195..394672ac07fc5 100644 --- a/x-pack/test/api_integration/apis/ml/index.ts +++ b/x-pack/test/api_integration/apis/ml/index.ts @@ -82,5 +82,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./results')); loadTestFile(require.resolve('./saved_objects')); loadTestFile(require.resolve('./system')); + loadTestFile(require.resolve('./trained_models')); }); } diff --git a/x-pack/test/api_integration/apis/ml/trained_models/delete_model.ts b/x-pack/test/api_integration/apis/ml/trained_models/delete_model.ts new file mode 100644 index 0000000000000..3848330a95fb9 --- /dev/null +++ b/x-pack/test/api_integration/apis/ml/trained_models/delete_model.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 expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; +import { USER } from '../../../../functional/services/ml/security_common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; + +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertestWithoutAuth'); + const ml = getService('ml'); + + describe('DELETE trained_models', () => { + before(async () => { + await ml.testResources.setKibanaTimeZoneToUTC(); + await ml.api.createdTestTrainedModels('regression', 2); + }); + + after(async () => { + await ml.api.cleanMlIndices(); + }); + + it('deletes trained model by id', async () => { + const { body: deleteResponseBody } = await supertest + .delete(`/api/ml/trained_models/dfa_regression_model_n_0`) + .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) + .set(COMMON_REQUEST_HEADERS) + .expect(200); + + expect(deleteResponseBody).to.eql({ acknowledged: true }); + + // verify that model is actually deleted + await supertest + .get(`/api/ml/trained_models/dfa_regression_model_n_0`) + .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) + .set(COMMON_REQUEST_HEADERS) + .expect(404); + }); + + it('returns 404 if requested trained model does not exist', async () => { + await supertest + .delete(`/api/ml/trained_models/not_existing_model`) + .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) + .set(COMMON_REQUEST_HEADERS) + .expect(404); + }); + + it('does not allow to delete trained model if the user does not have required permissions', async () => { + await supertest + .delete(`/api/ml/trained_models/dfa_regression_model_n_1`) + .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) + .set(COMMON_REQUEST_HEADERS) + .expect(403); + + // verify that model has not been deleted + await supertest + .get(`/api/ml/trained_models/dfa_regression_model_n_1`) + .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) + .set(COMMON_REQUEST_HEADERS) + .expect(200); + }); + }); +}; diff --git a/x-pack/test/api_integration/apis/ml/trained_models/get_model_pipelines.ts b/x-pack/test/api_integration/apis/ml/trained_models/get_model_pipelines.ts new file mode 100644 index 0000000000000..cc347056f02a3 --- /dev/null +++ b/x-pack/test/api_integration/apis/ml/trained_models/get_model_pipelines.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; +import { USER } from '../../../../functional/services/ml/security_common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; + +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertestWithoutAuth'); + const ml = getService('ml'); + + describe('GET trained_models/pipelines', () => { + let testModelIds: string[] = []; + + before(async () => { + await ml.testResources.setKibanaTimeZoneToUTC(); + testModelIds = await ml.api.createdTestTrainedModels('regression', 2, true); + }); + + after(async () => { + // delete all created ingest pipelines + await Promise.all(testModelIds.map((modelId) => ml.api.deleteIngestPipeline(modelId))); + await ml.api.cleanMlIndices(); + }); + + it('returns trained model pipelines by id', async () => { + const { body } = await supertest + .get(`/api/ml/trained_models/dfa_regression_model_n_0/pipelines`) + .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) + .set(COMMON_REQUEST_HEADERS) + .expect(200); + + expect(body.length).to.eql(1); + expect(body[0].model_id).to.eql('dfa_regression_model_n_0'); + expect(Object.keys(body[0].pipelines).length).to.eql(1); + }); + + it('returns an error in case user does not have required permission', async () => { + await supertest + .get(`/api/ml/trained_models/dfa_regression_model_n_0/pipelines`) + .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) + .set(COMMON_REQUEST_HEADERS) + .expect(403); + }); + }); +}; diff --git a/x-pack/test/api_integration/apis/ml/trained_models/get_model_stats.ts b/x-pack/test/api_integration/apis/ml/trained_models/get_model_stats.ts new file mode 100644 index 0000000000000..76f108836996f --- /dev/null +++ b/x-pack/test/api_integration/apis/ml/trained_models/get_model_stats.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; +import { USER } from '../../../../functional/services/ml/security_common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; + +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertestWithoutAuth'); + const ml = getService('ml'); + + describe('GET trained_models/_stats', () => { + before(async () => { + await ml.testResources.setKibanaTimeZoneToUTC(); + await ml.api.createdTestTrainedModels('regression', 2); + }); + + after(async () => { + await ml.api.cleanMlIndices(); + }); + + it('returns trained model stats by id', async () => { + const { body } = await supertest + .get(`/api/ml/trained_models/dfa_regression_model_n_0/_stats`) + .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) + .set(COMMON_REQUEST_HEADERS) + .expect(200); + + expect(body.count).to.eql(1); + expect(body.trained_model_stats[0].model_id).to.eql('dfa_regression_model_n_0'); + }); + + it('returns 404 if requested trained model does not exist', async () => { + await supertest + .get(`/api/ml/trained_models/not_existing_model/_stats`) + .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) + .set(COMMON_REQUEST_HEADERS) + .expect(404); + }); + + it('returns an error for unauthorized user', async () => { + await supertest + .get(`/api/ml/trained_models/dfa_regression_model_n_0/_stats`) + .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) + .set(COMMON_REQUEST_HEADERS) + .expect(403); + }); + }); +}; diff --git a/x-pack/test/api_integration/apis/ml/trained_models/get_models.ts b/x-pack/test/api_integration/apis/ml/trained_models/get_models.ts new file mode 100644 index 0000000000000..604dff6a98a9a --- /dev/null +++ b/x-pack/test/api_integration/apis/ml/trained_models/get_models.ts @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; +import { USER } from '../../../../functional/services/ml/security_common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; + +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertestWithoutAuth'); + const ml = getService('ml'); + + describe('GET trained_models', () => { + let testModelIds: string[] = []; + + before(async () => { + await ml.testResources.setKibanaTimeZoneToUTC(); + testModelIds = await ml.api.createdTestTrainedModels('regression', 5, true); + await ml.api.createModelAlias('dfa_regression_model_n_0', 'dfa_regression_model_alias'); + await ml.api.createIngestPipeline('dfa_regression_model_alias'); + }); + + after(async () => { + // delete created ingest pipelines + await Promise.all( + ['dfa_regression_model_alias', ...testModelIds].map((modelId) => + ml.api.deleteIngestPipeline(modelId) + ) + ); + await ml.api.cleanMlIndices(); + }); + + it('returns all trained models with associated pipelines including aliases', async () => { + const { body } = await supertest + .get(`/api/ml/trained_models?with_pipelines=true`) + .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) + .set(COMMON_REQUEST_HEADERS) + .expect(200); + // Created models + system model + expect(body.length).to.eql(6); + + const sampleModel = body.find((v: any) => v.model_id === 'dfa_regression_model_n_0'); + expect(Object.keys(sampleModel.pipelines).length).to.eql(2); + }); + + it('returns models without pipeline in case user does not have required permission', async () => { + const { body } = await supertest + .get(`/api/ml/trained_models?with_pipelines=true`) + .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) + .set(COMMON_REQUEST_HEADERS) + .expect(200); + // Created models + system model + expect(body.length).to.eql(6); + const sampleModel = body.find((v: any) => v.model_id === 'dfa_regression_model_n_0'); + expect(sampleModel.pipelines).to.eql(undefined); + }); + + it('returns trained model by id', async () => { + const { body } = await supertest + .get(`/api/ml/trained_models/dfa_regression_model_n_1`) + .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) + .set(COMMON_REQUEST_HEADERS) + .expect(200); + expect(body.length).to.eql(1); + expect(body[0].model_id).to.eql('dfa_regression_model_n_1'); + }); + + it('returns 404 if requested trained model does not exist', async () => { + await supertest + .get(`/api/ml/trained_models/not_existing_model`) + .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) + .set(COMMON_REQUEST_HEADERS) + .expect(404); + }); + + it('returns an error for unauthorized user', async () => { + await supertest + .get(`/api/ml/trained_models/dfa_regression_model_n_1`) + .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) + .set(COMMON_REQUEST_HEADERS) + .expect(403); + }); + }); +}; diff --git a/x-pack/test/api_integration/apis/ml/trained_models/index.ts b/x-pack/test/api_integration/apis/ml/trained_models/index.ts new file mode 100644 index 0000000000000..d1812dc188b00 --- /dev/null +++ b/x-pack/test/api_integration/apis/ml/trained_models/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('trained models', function () { + loadTestFile(require.resolve('./get_models')); + loadTestFile(require.resolve('./get_model_stats')); + loadTestFile(require.resolve('./get_model_pipelines')); + loadTestFile(require.resolve('./delete_model')); + }); +} diff --git a/x-pack/test/functional/services/ml/api.ts b/x-pack/test/functional/services/ml/api.ts index 728e3ff8fc8e6..ec5ca4c661157 100644 --- a/x-pack/test/functional/services/ml/api.ts +++ b/x-pack/test/functional/services/ml/api.ts @@ -8,6 +8,8 @@ import { estypes } from '@elastic/elasticsearch'; import expect from '@kbn/expect'; import { ProvidedType } from '@kbn/test'; +import fs from 'fs'; +import path from 'path'; import { Calendar } from '../../../../plugins/ml/server/models/calendar/index'; import { Annotation } from '../../../../plugins/ml/common/types/annotations'; import { DataFrameAnalyticsConfig } from '../../../../plugins/ml/public/application/data_frame_analytics/common'; @@ -25,6 +27,8 @@ import { import { COMMON_REQUEST_HEADERS } from '../../../functional/services/ml/common_api'; import { PutTrainedModelConfig } from '../../../../plugins/ml/common/types/trained_models'; +type ModelType = 'regression' | 'classification'; + export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { const es = getService('es'); const log = getService('log'); @@ -943,5 +947,93 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { log.debug('> Trained model crated'); return model; }, + + async createdTestTrainedModels( + modelType: ModelType, + count: number = 10, + withIngestPipelines = false + ) { + const compressedDefinition = this.getCompressedModelDefinition(modelType); + + const modelIds = new Array(count).fill(null).map((v, i) => `dfa_${modelType}_model_n_${i}`); + + const models = modelIds.map((id) => { + return { + model_id: id, + body: { + compressed_definition: compressedDefinition, + inference_config: { + [modelType]: {}, + }, + input: { + field_names: ['common_field'], + }, + } as PutTrainedModelConfig, + }; + }); + + for (const model of models) { + await this.createTrainedModel(model.model_id, model.body); + if (withIngestPipelines) { + await this.createIngestPipeline(model.model_id); + } + } + + return modelIds; + }, + + /** + * Retrieves compressed model definition from the test resources. + * @param modelType + */ + getCompressedModelDefinition(modelType: ModelType) { + return fs.readFileSync( + path.resolve( + __dirname, + 'resources', + 'trained_model_definitions', + `minimum_valid_config_${modelType}.json.gz.b64` + ), + 'utf-8' + ); + }, + + async createModelAlias(modelId: string, modelAlias: string) { + log.debug(`Creating alias for model "${modelId}"`); + await esSupertest + .put(`/_ml/trained_models/${modelId}/model_aliases/${modelAlias}`) + .expect(200); + log.debug('> Model alias created'); + }, + + /** + * Creates ingest pipelines for trained model + * @param modelId + */ + async createIngestPipeline(modelId: string) { + log.debug(`Creating ingest pipeline for trained model with id "${modelId}"`); + const ingestPipeline = await esSupertest + .put(`/_ingest/pipeline/pipeline_${modelId}`) + .send({ + processors: [ + { + inference: { + model_id: modelId, + }, + }, + ], + }) + .expect(200) + .then((res) => res.body); + + log.debug('> Ingest pipeline crated'); + return ingestPipeline; + }, + + async deleteIngestPipeline(modelId: string) { + log.debug(`Deleting ingest pipeline for trained model with id "${modelId}"`); + await esSupertest.delete(`/_ingest/pipeline/pipeline_${modelId}`).expect(200); + log.debug('> Ingest pipeline deleted'); + }, }; } diff --git a/x-pack/test/functional/services/ml/trained_models.ts b/x-pack/test/functional/services/ml/trained_models.ts index ae799efbbd30c..7a1fa1714ca14 100644 --- a/x-pack/test/functional/services/ml/trained_models.ts +++ b/x-pack/test/functional/services/ml/trained_models.ts @@ -5,12 +5,9 @@ * 2.0. */ -import fs from 'fs'; -import path from 'path'; import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; import { MlApi } from './api'; -import { PutTrainedModelConfig } from '../../../../plugins/ml/common/types/trained_models'; import { MlCommonUI } from './common_ui'; type ModelType = 'regression' | 'classification'; @@ -24,38 +21,7 @@ export function TrainedModelsProvider( return { async createdTestTrainedModels(modelType: ModelType, count: number = 10) { - const compressedDefinition = this.getCompressedModelDefinition(modelType); - - const models = new Array(count).fill(null).map((v, i) => { - return { - model_id: `dfa_${modelType}_model_n_${i}`, - body: { - compressed_definition: compressedDefinition, - inference_config: { - [modelType]: {}, - }, - input: { - field_names: ['common_field'], - }, - } as PutTrainedModelConfig, - }; - }); - - for (const model of models) { - await mlApi.createTrainedModel(model.model_id, model.body); - } - }, - - getCompressedModelDefinition(modelType: ModelType) { - return fs.readFileSync( - path.resolve( - __dirname, - 'resources', - 'trained_model_definitions', - `minimum_valid_config_${modelType}.json.gz.b64` - ), - 'utf-8' - ); + await mlApi.createdTestTrainedModels(modelType, count); }, async assertStats(expectedTotalCount: number) { From 95008cdb61b16090166115be8e6e906b4bd47242 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Fri, 9 Jul 2021 13:04:52 -0400 Subject: [PATCH 30/49] [Fleet] Fix add host url validation in fleet server setup (#105072) --- .../components/fleet_server_on_prem_instructions.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/fleet_server_on_prem_instructions.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/fleet_server_on_prem_instructions.tsx index 48ff51f1a25e8..0fc3821d2e3f7 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/fleet_server_on_prem_instructions.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/fleet_server_on_prem_instructions.tsx @@ -452,6 +452,8 @@ export const AddFleetServerHostStepContent = ({ await addFleetServerHost(fleetServerHost); setCalloutHost(fleetServerHost); setFleetServerHost(''); + } else { + setCalloutHost(''); } } finally { setIsLoading(false); From 5b207d8484cee16375068791eb4e81b914d9022e Mon Sep 17 00:00:00 2001 From: Tim Sullivan Date: Fri, 9 Jul 2021 10:18:25 -0700 Subject: [PATCH 31/49] [Reporting] Add `handleSIGHUP: false` to puppeteer LaunchOptions (#104992) --- .../browsers/chromium/driver_factory/index.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.ts b/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.ts index 1141437eae0ef..eb2abf4036c03 100644 --- a/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.ts +++ b/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.ts @@ -26,6 +26,18 @@ import { HeadlessChromiumDriver } from '../driver'; import { args } from './args'; import { Metrics, getMetrics } from './metrics'; +// Puppeteer type definitions do not match the documentation. +// See https://pptr.dev/#?product=Puppeteer&version=v8.0.0&show=api-puppeteerlaunchoptions +interface ReportingLaunchOptions extends puppeteer.LaunchOptions { + userDataDir?: string; + ignoreHTTPSErrors?: boolean; + args?: string[]; +} + +declare module 'puppeteer' { + function launch(options: ReportingLaunchOptions): Promise; +} + type BrowserConfig = CaptureConfig['browser']['chromium']; type ViewportConfig = CaptureConfig['viewport']; @@ -85,11 +97,12 @@ export class HeadlessChromiumDriverFactory { userDataDir: this.userDataDir, executablePath: this.binaryPath, ignoreHTTPSErrors: true, + handleSIGHUP: false, args: chromiumArgs, env: { TZ: browserTimezone, }, - } as puppeteer.LaunchOptions); + }); page = await browser.newPage(); devTools = await page.target().createCDPSession(); From d2ce8d52235248b03e69531c23a72f376e082263 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Fri, 9 Jul 2021 13:46:52 -0400 Subject: [PATCH 32/49] [APM] Make fleet plugin dependency optional (#104967) * fixing tutorial when fleet plugin is disabled * addressing PR comments --- x-pack/plugins/apm/kibana.json | 11 +- x-pack/plugins/apm/public/plugin.ts | 33 +- .../config_agent/config_agent.stories.tsx | 7 + .../config_agent/get_policy_options.test.ts | 8 + .../tutorial/config_agent/index.test.tsx | 346 ++++++++++++++---- .../public/tutorial/config_agent/index.tsx | 51 ++- .../tutorial/config_agent/policy_selector.tsx | 10 +- .../tutorial_fleet_instructions/index.tsx | 1 + x-pack/plugins/apm/server/plugin.ts | 33 +- x-pack/plugins/apm/server/routes/fleet.ts | 10 +- .../apm/server/tutorial/envs/on_prem.ts | 19 +- x-pack/plugins/apm/server/tutorial/index.ts | 4 +- 12 files changed, 384 insertions(+), 149 deletions(-) diff --git a/x-pack/plugins/apm/kibana.json b/x-pack/plugins/apm/kibana.json index ae4510b10acd4..9f661f13a491e 100644 --- a/x-pack/plugins/apm/kibana.json +++ b/x-pack/plugins/apm/kibana.json @@ -7,7 +7,6 @@ "data", "embeddable", "features", - "fleet", "infra", "licensing", "observability", @@ -24,11 +23,15 @@ "security", "spaces", "taskManager", - "usageCollection" + "usageCollection", + "fleet" ], "server": true, "ui": true, - "configPath": ["xpack", "apm"], + "configPath": [ + "xpack", + "apm" + ], "requiredBundles": [ "fleet", "home", @@ -38,4 +41,4 @@ "ml", "observability" ] -} +} \ No newline at end of file diff --git a/x-pack/plugins/apm/public/plugin.ts b/x-pack/plugins/apm/public/plugin.ts index 0cd5009570613..91b045b8db46f 100644 --- a/x-pack/plugins/apm/public/plugin.ts +++ b/x-pack/plugins/apm/public/plugin.ts @@ -74,7 +74,7 @@ export interface ApmPluginStartDeps { ml?: MlPluginStart; triggersActionsUi: TriggersAndActionsUIPublicPluginStart; observability: ObservabilityPublicStart; - fleet: FleetStart; + fleet?: FleetStart; } export class ApmPlugin implements Plugin { @@ -311,20 +311,21 @@ export class ApmPlugin implements Plugin { } public start(core: CoreStart, plugins: ApmPluginStartDeps) { const { fleet } = plugins; - - const agentEnrollmentExtensionData = getApmEnrollmentFlyoutData(); - - fleet.registerExtension({ - package: 'apm', - view: 'agent-enrollment-flyout', - title: agentEnrollmentExtensionData.title, - Component: agentEnrollmentExtensionData.Component, - }); - - fleet.registerExtension({ - package: 'apm', - view: 'package-detail-assets', - Component: LazyApmCustomAssetsExtension, - }); + if (fleet) { + const agentEnrollmentExtensionData = getApmEnrollmentFlyoutData(); + + fleet.registerExtension({ + package: 'apm', + view: 'agent-enrollment-flyout', + title: agentEnrollmentExtensionData.title, + Component: agentEnrollmentExtensionData.Component, + }); + + fleet.registerExtension({ + package: 'apm', + view: 'package-detail-assets', + Component: LazyApmCustomAssetsExtension, + }); + } } } diff --git a/x-pack/plugins/apm/public/tutorial/config_agent/config_agent.stories.tsx b/x-pack/plugins/apm/public/tutorial/config_agent/config_agent.stories.tsx index 33f171ab88247..0d4d3748422ea 100644 --- a/x-pack/plugins/apm/public/tutorial/config_agent/config_agent.stories.tsx +++ b/x-pack/plugins/apm/public/tutorial/config_agent/config_agent.stories.tsx @@ -18,6 +18,7 @@ interface Args { onPrem: boolean; hasFleetPoliciesWithApmIntegration: boolean; hasCloudPolicyWithApmIntegration: boolean; + isFleetEnabled: boolean; } const policyElasticAgentOnCloudAgent: APIResponseType['fleetAgents'][0] = { @@ -47,6 +48,7 @@ function Wrapper({ apmAgent, onPrem, hasCloudPolicyWithApmIntegration, + isFleetEnabled, }: Args) { const http = ({ get: () => ({ @@ -56,6 +58,7 @@ function Wrapper({ ? [policyElasticAgentOnCloudAgent] : []), ], + isFleetEnabled, cloudStandaloneSetup: { apmServerUrl: 'cloud_url', secretToken: 'foo', @@ -80,6 +83,7 @@ Integration.args = { onPrem: true, hasFleetPoliciesWithApmIntegration: false, hasCloudPolicyWithApmIntegration: false, + isFleetEnabled: true, }; export default { @@ -113,5 +117,8 @@ export default { hasCloudPolicyWithApmIntegration: { control: { type: 'boolean', options: [true, false] }, }, + isFleetEnabled: { + control: { type: 'boolean', options: [true, false], defaultValue: true }, + }, }, }; diff --git a/x-pack/plugins/apm/public/tutorial/config_agent/get_policy_options.test.ts b/x-pack/plugins/apm/public/tutorial/config_agent/get_policy_options.test.ts index 90c9aab80f6f5..c6dc7265f3d3e 100644 --- a/x-pack/plugins/apm/public/tutorial/config_agent/get_policy_options.test.ts +++ b/x-pack/plugins/apm/public/tutorial/config_agent/get_policy_options.test.ts @@ -41,6 +41,7 @@ describe('getPolicyOptions', () => { apmServerUrl: 'cloud_url', secretToken: 'cloud_token', }, + isFleetEnabled: true, }; const options = getPolicyOptions({ isCloudEnabled: true, @@ -65,6 +66,7 @@ describe('getPolicyOptions', () => { apmServerUrl: 'cloud_url', secretToken: 'cloud_token', }, + isFleetEnabled: true, }; const options = getPolicyOptions({ isCloudEnabled: true, @@ -109,6 +111,7 @@ describe('getPolicyOptions', () => { apmServerUrl: 'cloud_url', secretToken: 'cloud_token', }, + isFleetEnabled: true, }; const options = getPolicyOptions({ isCloudEnabled: true, @@ -151,6 +154,7 @@ describe('getPolicyOptions', () => { const data: APIResponseType = { fleetAgents: [], cloudStandaloneSetup: undefined, + isFleetEnabled: true, }; const options = getPolicyOptions({ isCloudEnabled: true, @@ -173,6 +177,7 @@ describe('getPolicyOptions', () => { const data: APIResponseType = { fleetAgents, cloudStandaloneSetup: undefined, + isFleetEnabled: true, }; const options = getPolicyOptions({ isCloudEnabled: true, @@ -213,6 +218,7 @@ describe('getPolicyOptions', () => { const data: APIResponseType = { fleetAgents: [policyElasticAgentOnCloudAgent, ...fleetAgents], cloudStandaloneSetup: undefined, + isFleetEnabled: true, }; const options = getPolicyOptions({ isCloudEnabled: true, @@ -256,6 +262,7 @@ describe('getPolicyOptions', () => { const data: APIResponseType = { fleetAgents: [], cloudStandaloneSetup: undefined, + isFleetEnabled: true, }; const options = getPolicyOptions({ isCloudEnabled: false, @@ -278,6 +285,7 @@ describe('getPolicyOptions', () => { const data: APIResponseType = { fleetAgents, cloudStandaloneSetup: undefined, + isFleetEnabled: true, }; const options = getPolicyOptions({ isCloudEnabled: false, diff --git a/x-pack/plugins/apm/public/tutorial/config_agent/index.test.tsx b/x-pack/plugins/apm/public/tutorial/config_agent/index.test.tsx index 8f8afe58506a6..cb49cee108bd1 100644 --- a/x-pack/plugins/apm/public/tutorial/config_agent/index.test.tsx +++ b/x-pack/plugins/apm/public/tutorial/config_agent/index.test.tsx @@ -7,6 +7,10 @@ import { fireEvent, render, screen } from '@testing-library/react'; import { HttpStart } from 'kibana/public'; import React from 'react'; +import { + expectTextsInDocument, + expectTextsNotInDocument, +} from '../../utils/testHelpers'; import TutorialConfigAgent from './'; const policyElasticAgentOnCloudAgent = { @@ -32,68 +36,32 @@ const fleetAgents = [ ]; describe('TutorialConfigAgent', () => { - it('renders loading component while API is being called', () => { - const component = render( - - ); - expect(component.getByTestId('loading')).toBeInTheDocument(); + beforeAll(() => { + // Mocks console.error so it won't polute tests output when testing the api throwing error + jest.spyOn(console, 'error').mockImplementation(() => null); }); - it('updates commands when a different policy is selected', async () => { - const component = render( - - ); - expect( - await screen.findByText('Default Standalone configuration') - ).toBeInTheDocument(); - let commands = component.getByTestId('commands').innerHTML; - expect(commands).not.toEqual(''); - expect(commands).toMatchInlineSnapshot(` - "java -javaagent:/path/to/elastic-apm-agent-<version>.jar \\\\ - -Delastic.apm.service_name=my-application \\\\ - -Delastic.apm.server_urls=http://localhost:8200 \\\\ - -Delastic.apm.secret_token= \\\\ - -Delastic.apm.environment=production \\\\ - -Delastic.apm.application_packages=org.example \\\\ - -jar my-application.jar" - `); - fireEvent.click(component.getByTestId('comboBoxToggleListButton')); - fireEvent.click(component.getByText('agent foo')); - commands = component.getByTestId('commands').innerHTML; - expect(commands).not.toEqual(''); - expect(commands).toMatchInlineSnapshot(` - "java -javaagent:/path/to/elastic-apm-agent-<version>.jar \\\\ - -Delastic.apm.service_name=my-application \\\\ - -Delastic.apm.server_urls=foo \\\\ - -Delastic.apm.secret_token=foo \\\\ - -Delastic.apm.environment=production \\\\ - -Delastic.apm.application_packages=org.example \\\\ - -jar my-application.jar" - `); + afterAll(() => { + jest.restoreAllMocks(); }); - describe('running on prem', () => { - it('selects defaul standalone by defauls', async () => { + + describe('when fleet plugin is enabled', () => { + it('renders loading component while API is being called', () => { + const component = render( + + ); + expect(component.getByTestId('loading')).toBeInTheDocument(); + }); + it('updates commands when a different policy is selected', async () => { const component = render( { get: jest.fn().mockReturnValue({ cloudStandaloneSetup: undefined, fleetAgents, + isFleetEnabled: true, }), } as unknown) as HttpStart } @@ -112,10 +81,7 @@ describe('TutorialConfigAgent', () => { expect( await screen.findByText('Default Standalone configuration') ).toBeInTheDocument(); - expect( - component.getByTestId('policySelector_onPrem') - ).toBeInTheDocument(); - const commands = component.getByTestId('commands').innerHTML; + let commands = component.getByTestId('commands').innerHTML; expect(commands).not.toEqual(''); expect(commands).toMatchInlineSnapshot(` "java -javaagent:/path/to/elastic-apm-agent-<version>.jar \\\\ @@ -126,21 +92,238 @@ describe('TutorialConfigAgent', () => { -Delastic.apm.application_packages=org.example \\\\ -jar my-application.jar" `); + + fireEvent.click(component.getByTestId('comboBoxToggleListButton')); + fireEvent.click(component.getByText('agent foo')); + commands = component.getByTestId('commands').innerHTML; + expect(commands).not.toEqual(''); + expect(commands).toMatchInlineSnapshot(` + "java -javaagent:/path/to/elastic-apm-agent-<version>.jar \\\\ + -Delastic.apm.service_name=my-application \\\\ + -Delastic.apm.server_urls=foo \\\\ + -Delastic.apm.secret_token=foo \\\\ + -Delastic.apm.environment=production \\\\ + -Delastic.apm.application_packages=org.example \\\\ + -jar my-application.jar" + `); + }); + describe('running on prem', () => { + it('selects defaul standalone by defauls', async () => { + const component = render( + + ); + expect( + await screen.findByText('Default Standalone configuration') + ).toBeInTheDocument(); + expect( + component.getByTestId('policySelector_onPrem') + ).toBeInTheDocument(); + const commands = component.getByTestId('commands').innerHTML; + expect(commands).not.toEqual(''); + expect(commands).toMatchInlineSnapshot(` + "java -javaagent:/path/to/elastic-apm-agent-<version>.jar \\\\ + -Delastic.apm.service_name=my-application \\\\ + -Delastic.apm.server_urls=http://localhost:8200 \\\\ + -Delastic.apm.secret_token= \\\\ + -Delastic.apm.environment=production \\\\ + -Delastic.apm.application_packages=org.example \\\\ + -jar my-application.jar" + `); + }); + it('shows get started with fleet link when there are no fleet agents', async () => { + const component = render( + + ); + expect( + await screen.findByText('Default Standalone configuration') + ).toBeInTheDocument(); + expect( + component.getByTestId('policySelector_onPrem') + ).toBeInTheDocument(); + const commands = component.getByTestId('commands').innerHTML; + expect(commands).not.toEqual(''); + expect(commands).toMatchInlineSnapshot(` + "java -javaagent:/path/to/elastic-apm-agent-<version>.jar \\\\ + -Delastic.apm.service_name=my-application \\\\ + -Delastic.apm.server_urls=http://localhost:8200 \\\\ + -Delastic.apm.secret_token= \\\\ + -Delastic.apm.environment=production \\\\ + -Delastic.apm.application_packages=org.example \\\\ + -jar my-application.jar" + `); + expectTextsInDocument(component, ['Get started with fleet']); + }); + }); + describe('running on cloud', () => { + it('selects defaul standalone by defauls', async () => { + const component = render( + + ); + expect( + await screen.findByText('Default Standalone configuration') + ).toBeInTheDocument(); + expect( + component.getByTestId('policySelector_cloud') + ).toBeInTheDocument(); + const commands = component.getByTestId('commands').innerHTML; + expect(commands).not.toEqual(''); + expect(commands).toMatchInlineSnapshot(` + "java -javaagent:/path/to/elastic-apm-agent-<version>.jar \\\\ + -Delastic.apm.service_name=my-application \\\\ + -Delastic.apm.server_urls=cloud_url \\\\ + -Delastic.apm.secret_token=cloud_token \\\\ + -Delastic.apm.environment=production \\\\ + -Delastic.apm.application_packages=org.example \\\\ + -jar my-application.jar" + `); + }); + it('selects policy elastic agent on cloud when available by default', async () => { + const component = render( + + ); + expect( + await screen.findByText('Elastic Cloud agent policy') + ).toBeInTheDocument(); + expect( + component.getByTestId('policySelector_policy-elastic-agent-on-cloud') + ).toBeInTheDocument(); + const commands = component.getByTestId('commands').innerHTML; + expect(commands).not.toEqual(''); + expect(commands).toMatchInlineSnapshot(` + "java -javaagent:/path/to/elastic-apm-agent-<version>.jar \\\\ + -Delastic.apm.service_name=my-application \\\\ + -Delastic.apm.server_urls=apm_cloud_url \\\\ + -Delastic.apm.secret_token=apm_cloud_token \\\\ + -Delastic.apm.environment=production \\\\ + -Delastic.apm.application_packages=org.example \\\\ + -jar my-application.jar" + `); + }); + + it('shows default standalone option when api throws an error', async () => { + const component = render( + { + throw new Error('Boom'); + }, + } as unknown) as HttpStart + } + basePath="http://localhost:5601" + isCloudEnabled + /> + ); + expect( + await screen.findByText('Default Standalone configuration') + ).toBeInTheDocument(); + const commands = component.getByTestId('commands').innerHTML; + expect(commands).not.toEqual(''); + expect(commands).toMatchInlineSnapshot(` + "java -javaagent:/path/to/elastic-apm-agent-<version>.jar \\\\ + -Delastic.apm.service_name=my-application \\\\ + -Delastic.apm.server_urls=http://localhost:8200 \\\\ + -Delastic.apm.secret_token= \\\\ + -Delastic.apm.environment=production \\\\ + -Delastic.apm.application_packages=org.example \\\\ + -jar my-application.jar" + `); + }); }); }); - describe('running on cloud', () => { - it('selects defaul standalone by defauls', async () => { + describe('when fleet plugin is disabled', () => { + it('hides fleet links', async () => { const component = render( + ); + + expectTextsNotInDocument(component, [ + 'Get started with fleet', + 'Manage fleet policies', + ]); + }); + it('shows default standalone on prem', async () => { + const component = render( + { expect( await screen.findByText('Default Standalone configuration') ).toBeInTheDocument(); - expect(component.getByTestId('policySelector_cloud')).toBeInTheDocument(); + expect( + component.getByTestId('policySelector_onPrem') + ).toBeInTheDocument(); const commands = component.getByTestId('commands').innerHTML; expect(commands).not.toEqual(''); expect(commands).toMatchInlineSnapshot(` "java -javaagent:/path/to/elastic-apm-agent-<version>.jar \\\\ -Delastic.apm.service_name=my-application \\\\ - -Delastic.apm.server_urls=cloud_url \\\\ - -Delastic.apm.secret_token=cloud_token \\\\ + -Delastic.apm.server_urls=http://localhost:8200 \\\\ + -Delastic.apm.secret_token= \\\\ -Delastic.apm.environment=production \\\\ -Delastic.apm.application_packages=org.example \\\\ -jar my-application.jar" `); }); - it('selects policy elastic agent on cloud when available by default', async () => { + it('shows default standalone on cloud', async () => { const component = render( { apmServerUrl: 'cloud_url', secretToken: 'cloud_token', }, - fleetAgents: [...fleetAgents, policyElasticAgentOnCloudAgent], + fleetAgents: [], + isFleetEnabled: false, }), } as unknown) as HttpStart } @@ -184,18 +370,16 @@ describe('TutorialConfigAgent', () => { /> ); expect( - await screen.findByText('Elastic Cloud agent policy') - ).toBeInTheDocument(); - expect( - component.getByTestId('policySelector_policy-elastic-agent-on-cloud') + await screen.findByText('Default Standalone configuration') ).toBeInTheDocument(); + expect(component.getByTestId('policySelector_cloud')).toBeInTheDocument(); const commands = component.getByTestId('commands').innerHTML; expect(commands).not.toEqual(''); expect(commands).toMatchInlineSnapshot(` "java -javaagent:/path/to/elastic-apm-agent-<version>.jar \\\\ -Delastic.apm.service_name=my-application \\\\ - -Delastic.apm.server_urls=apm_cloud_url \\\\ - -Delastic.apm.secret_token=apm_cloud_token \\\\ + -Delastic.apm.server_urls=cloud_url \\\\ + -Delastic.apm.secret_token=cloud_token \\\\ -Delastic.apm.environment=production \\\\ -Delastic.apm.application_packages=org.example \\\\ -jar my-application.jar" diff --git a/x-pack/plugins/apm/public/tutorial/config_agent/index.tsx b/x-pack/plugins/apm/public/tutorial/config_agent/index.tsx index 755c3eca55868..d38d51f01c67b 100644 --- a/x-pack/plugins/apm/public/tutorial/config_agent/index.tsx +++ b/x-pack/plugins/apm/public/tutorial/config_agent/index.tsx @@ -46,16 +46,43 @@ interface Props { isCloudEnabled: boolean; } +const INITIAL_STATE = { + fleetAgents: [], + cloudStandaloneSetup: undefined, + isFleetEnabled: false, +}; + +function getFleetLink({ + isFleetEnabled, + hasFleetAgents, + basePath, +}: { + isFleetEnabled: boolean; + hasFleetAgents: boolean; + basePath: string; +}) { + if (!isFleetEnabled) { + return; + } + + return hasFleetAgents + ? { + label: MANAGE_FLEET_POLICIES_LABEL, + href: `${basePath}/app/fleet#/policies`, + } + : { + label: GET_STARTED_WITH_FLEET_LABEL, + href: `${basePath}/app/integrations#/detail/apm-0.3.0/overview`, + }; +} + function TutorialConfigAgent({ variantId, http, basePath, isCloudEnabled, }: Props) { - const [data, setData] = useState({ - fleetAgents: [], - cloudStandaloneSetup: undefined, - }); + const [data, setData] = useState(INITIAL_STATE); const [isLoading, setIsLoading] = useState(true); const [selectedOption, setSelectedOption] = useState(); @@ -68,6 +95,7 @@ function TutorialConfigAgent({ setData(response as APIResponseType); } } catch (e) { + setIsLoading(false); console.error('Error while fetching fleet agents.', e); } } @@ -105,15 +133,6 @@ function TutorialConfigAgent({ }); const hasFleetAgents = !!data.fleetAgents.length; - const fleetLink = hasFleetAgents - ? { - label: MANAGE_FLEET_POLICIES_LABEL, - href: `${basePath}/app/fleet#/policies`, - } - : { - label: GET_STARTED_WITH_FLEET_LABEL, - href: `${basePath}/app/integrations#/detail/apm-0.3.0/overview`, - }; return ( <> @@ -125,7 +144,11 @@ function TutorialConfigAgent({ onChange={(newSelectedOption) => setSelectedOption(newSelectedOption) } - fleetLink={fleetLink} + fleetLink={getFleetLink({ + isFleetEnabled: data.isFleetEnabled, + hasFleetAgents, + basePath, + })} /> diff --git a/x-pack/plugins/apm/public/tutorial/config_agent/policy_selector.tsx b/x-pack/plugins/apm/public/tutorial/config_agent/policy_selector.tsx index 3a0c6d70db82b..25ce7042c4c97 100644 --- a/x-pack/plugins/apm/public/tutorial/config_agent/policy_selector.tsx +++ b/x-pack/plugins/apm/public/tutorial/config_agent/policy_selector.tsx @@ -21,7 +21,7 @@ interface Props { options: PolicyOption[]; selectedOption?: PolicyOption; onChange: (selectedOption?: PolicyOption) => void; - fleetLink: { + fleetLink?: { label: string; href: string; }; @@ -58,9 +58,11 @@ export function PolicySelector({ { defaultMessage: 'Choose policy' } )} labelAppend={ - - {fleetLink.label} - + fleetLink && ( + + {fleetLink.label} + + ) } helpText={i18n.translate( 'xpack.apm.tutorial.agent_config.choosePolicy.helper', diff --git a/x-pack/plugins/apm/public/tutorial/tutorial_fleet_instructions/index.tsx b/x-pack/plugins/apm/public/tutorial/tutorial_fleet_instructions/index.tsx index 8a81b7a994e76..6fcf13345538f 100644 --- a/x-pack/plugins/apm/public/tutorial/tutorial_fleet_instructions/index.tsx +++ b/x-pack/plugins/apm/public/tutorial/tutorial_fleet_instructions/index.tsx @@ -42,6 +42,7 @@ function TutorialFleetInstructions({ http, basePath, isDarkTheme }: Props) { const response = await http.get('/api/apm/fleet/has_data'); setData(response as APIResponseType); } catch (e) { + setIsLoading(false); console.error('Error while fetching fleet details.', e); } setIsLoading(false); diff --git a/x-pack/plugins/apm/server/plugin.ts b/x-pack/plugins/apm/server/plugin.ts index f260971c3bdcb..3a7eb738dd3b2 100644 --- a/x-pack/plugins/apm/server/plugin.ts +++ b/x-pack/plugins/apm/server/plugin.ts @@ -15,7 +15,7 @@ import { Plugin, PluginInitializerContext, } from 'src/core/server'; -import { mapValues, once } from 'lodash'; +import { isEmpty, mapValues, once } from 'lodash'; import { TECHNICAL_COMPONENT_TEMPLATE_NAME } from '../../rule_registry/common/assets'; import { mappingFromFieldMap } from '../../rule_registry/common/mapping_from_field_map'; import { APMConfig, APMXPackConfig, APM_SERVER_FEATURE_ID } from '.'; @@ -104,21 +104,6 @@ export class APMPlugin }); } - plugins.home?.tutorials.registerTutorial( - tutorialProvider({ - isEnabled: this.currentConfig['xpack.apm.ui.enabled'], - indexPatternTitle: this.currentConfig['apm_oss.indexPattern'], - cloud: plugins.cloud, - indices: { - errorIndices: this.currentConfig['apm_oss.errorIndices'], - metricsIndices: this.currentConfig['apm_oss.metricsIndices'], - onboardingIndices: this.currentConfig['apm_oss.onboardingIndices'], - sourcemapIndices: this.currentConfig['apm_oss.sourcemapIndices'], - transactionIndices: this.currentConfig['apm_oss.transactionIndices'], - }, - }) - ); - plugins.features.registerKibanaFeature(APM_FEATURE); registerFeaturesUsage({ licensingPlugin: plugins.licensing }); @@ -206,6 +191,22 @@ export class APMPlugin }; }) as APMRouteHandlerResources['plugins']; + plugins.home?.tutorials.registerTutorial( + tutorialProvider({ + isEnabled: this.currentConfig['xpack.apm.ui.enabled'], + indexPatternTitle: this.currentConfig['apm_oss.indexPattern'], + cloud: plugins.cloud, + isFleetPluginEnabled: !isEmpty(resourcePlugins.fleet), + indices: { + errorIndices: this.currentConfig['apm_oss.errorIndices'], + metricsIndices: this.currentConfig['apm_oss.metricsIndices'], + onboardingIndices: this.currentConfig['apm_oss.onboardingIndices'], + sourcemapIndices: this.currentConfig['apm_oss.sourcemapIndices'], + transactionIndices: this.currentConfig['apm_oss.transactionIndices'], + }, + }) + ); + const telemetryUsageCounter = resourcePlugins.usageCollection?.setup.createUsageCounter( APM_SERVER_FEATURE_ID ); diff --git a/x-pack/plugins/apm/server/routes/fleet.ts b/x-pack/plugins/apm/server/routes/fleet.ts index b83bfd54b93cd..6628d29b256f7 100644 --- a/x-pack/plugins/apm/server/routes/fleet.ts +++ b/x-pack/plugins/apm/server/routes/fleet.ts @@ -32,7 +32,7 @@ const hasFleetDataRoute = createApmServerRoute({ handler: async ({ core, plugins }) => { const fleetPluginStart = await plugins.fleet?.start(); if (!fleetPluginStart) { - throw Boom.internal(FLEET_REQUIRED_MESSAGE); + return { hasData: false }; } const packagePolicies = await getApmPackgePolicies({ core, @@ -56,7 +56,7 @@ const fleetAgentsRoute = createApmServerRoute({ const fleetPluginStart = await plugins.fleet?.start(); if (!fleetPluginStart) { - throw Boom.internal(FLEET_REQUIRED_MESSAGE); + return { cloudStandaloneSetup, fleetAgents: [], isFleetEnabled: false }; } // fetches package policies that contains APM integrations const packagePolicies = await getApmPackgePolicies({ @@ -75,6 +75,7 @@ const fleetAgentsRoute = createApmServerRoute({ return { cloudStandaloneSetup, + isFleetEnabled: true, fleetAgents: fleetAgents.map((agent) => { const packagePolicy = policiesGroupedById[agent.id]; const packagePolicyVars = packagePolicy.inputs[0]?.vars; @@ -190,11 +191,6 @@ export const apmFleetRouteRepository = createApmServerRouteRepository() .add(getMigrationCheckRoute) .add(createCloudApmPackagePolicyRoute); -const FLEET_REQUIRED_MESSAGE = i18n.translate( - 'xpack.apm.fleet_has_data.fleetRequired', - { defaultMessage: `Fleet plugin is required` } -); - const FLEET_SECURITY_REQUIRED_MESSAGE = i18n.translate( 'xpack.apm.api.fleet.fleetSecurityRequired', { defaultMessage: `Fleet and Security plugins are required` } diff --git a/x-pack/plugins/apm/server/tutorial/envs/on_prem.ts b/x-pack/plugins/apm/server/tutorial/envs/on_prem.ts index 882d45c4c21db..400da79e3d2d0 100644 --- a/x-pack/plugins/apm/server/tutorial/envs/on_prem.ts +++ b/x-pack/plugins/apm/server/tutorial/envs/on_prem.ts @@ -38,12 +38,14 @@ export function onPremInstructions({ metricsIndices, sourcemapIndices, onboardingIndices, + isFleetPluginEnabled, }: { errorIndices: string; transactionIndices: string; metricsIndices: string; sourcemapIndices: string; onboardingIndices: string; + isFleetPluginEnabled: boolean; }): InstructionsSchema { const EDIT_CONFIG = createEditConfig(); const START_SERVER_UNIX = createStartServerUnix(); @@ -69,12 +71,17 @@ export function onPremInstructions({ iconType: 'alert', }, instructionVariants: [ - { - id: INSTRUCTION_VARIANT.FLEET, - instructions: [ - { customComponentName: 'TutorialFleetInstructions' }, - ], - }, + // hides fleet section when plugin is disabled + ...(isFleetPluginEnabled + ? [ + { + id: INSTRUCTION_VARIANT.FLEET, + instructions: [ + { customComponentName: 'TutorialFleetInstructions' }, + ], + }, + ] + : []), { id: INSTRUCTION_VARIANT.OSX, instructions: [ diff --git a/x-pack/plugins/apm/server/tutorial/index.ts b/x-pack/plugins/apm/server/tutorial/index.ts index 9118c30b845d0..edf056a6d1be4 100644 --- a/x-pack/plugins/apm/server/tutorial/index.ts +++ b/x-pack/plugins/apm/server/tutorial/index.ts @@ -28,6 +28,7 @@ export const tutorialProvider = ({ indexPatternTitle, indices, cloud, + isFleetPluginEnabled, }: { isEnabled: boolean; indexPatternTitle: string; @@ -39,6 +40,7 @@ export const tutorialProvider = ({ sourcemapIndices: string; onboardingIndices: string; }; + isFleetPluginEnabled: boolean; }) => () => { const savedObjects = [ { @@ -104,7 +106,7 @@ It allows you to monitor the performance of thousands of applications in real ti euiIconType: 'apmApp', artifacts, customStatusCheckName: 'apm_fleet_server_status_check', - onPrem: onPremInstructions(indices), + onPrem: onPremInstructions({ ...indices, isFleetPluginEnabled }), elasticCloud: createElasticCloudInstructions(cloud), previewImagePath: '/plugins/apm/assets/apm.png', savedObjects, From fa03028688aa145bb1483ed5be812e3d7a139353 Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Fri, 9 Jul 2021 13:42:50 -0500 Subject: [PATCH 33/49] Enable CSS-in-JS styling with `emotion` (#98157) * emotion deps * kbn-babel * kbn-test * examples * babel-plugin-styled-components config * css prop type fixes * type context * declaration location * some emotion types resolved * clean up * emotion v10 accomodations * types * kbn-crypto * kbn-telemetry-tools * bazel * eslint rule; shared file regex array * update paths * Update packages/kbn-eslint-plugin-eslint/rules/module_migration.js Co-authored-by: Spencer * remove placeholder styles * doc api changes * snapshot updates * storybook comments * use constant * bump new deps * condense versions Co-authored-by: Spencer --- package.json | 3 + .../elastic-eslint-config-kibana/.eslintrc.js | 10 +- packages/kbn-babel-preset/BUILD.bazel | 1 + packages/kbn-babel-preset/webpack_preset.js | 34 +++- packages/kbn-crypto/BUILD.bazel | 3 +- packages/kbn-dev-utils/src/babel.ts | 11 ++ .../rules/module_migration.js | 23 ++- packages/kbn-storybook/lib/default_config.ts | 20 +- packages/kbn-storybook/lib/theme_switcher.tsx | 1 + packages/kbn-telemetry-tools/BUILD.bazel | 3 +- packages/kbn-test/jest-preset.js | 1 + packages/kbn-ui-shared-deps/BUILD.bazel | 1 + packages/kbn-ui-shared-deps/src/entry.js | 1 + packages/kbn-ui-shared-deps/src/index.js | 1 + .../collapsible_nav.test.tsx.snap | 20 +- src/plugins/data/public/public.api.md | 4 +- .../__snapshots__/data_view.test.tsx.snap | 2 - src/plugins/embeddable/public/public.api.md | 5 +- .../tsconfig.json | 3 +- test/tsconfig.json | 2 +- tsconfig.base.json | 8 +- typings/@emotion/index.d.ts | 12 ++ .../report_listing.test.tsx.snap | 27 --- ...screen_capture_panel_content.test.tsx.snap | 12 +- yarn.lock | 187 ++++++++++++++---- 25 files changed, 280 insertions(+), 115 deletions(-) create mode 100644 typings/@emotion/index.d.ts diff --git a/package.json b/package.json index 8f56d80c584ea..22eedde59c5e7 100644 --- a/package.json +++ b/package.json @@ -114,6 +114,7 @@ "@elastic/safer-lodash-set": "link:bazel-bin/packages/elastic-safer-lodash-set", "@elastic/search-ui-app-search-connector": "^1.6.0", "@elastic/ui-ace": "0.2.3", + "@emotion/react": "^11.4.0", "@hapi/accept": "^5.0.2", "@hapi/boom": "^9.1.1", "@hapi/cookie": "^11.0.2", @@ -454,6 +455,8 @@ "@elastic/eslint-plugin-eui": "0.0.2", "@elastic/github-checks-reporter": "0.0.20b3", "@elastic/makelogs": "^6.0.0", + "@emotion/babel-preset-css-prop": "^11.2.0", + "@emotion/jest": "^11.3.0", "@istanbuljs/schema": "^0.1.2", "@jest/reporters": "^26.6.2", "@kbn/babel-code-parser": "link:bazel-bin/packages/kbn-babel-code-parser", diff --git a/packages/elastic-eslint-config-kibana/.eslintrc.js b/packages/elastic-eslint-config-kibana/.eslintrc.js index 3220a01184004..d3cf7cf964a60 100644 --- a/packages/elastic-eslint-config-kibana/.eslintrc.js +++ b/packages/elastic-eslint-config-kibana/.eslintrc.js @@ -1,3 +1,5 @@ +const { USES_STYLED_COMPONENTS } = require('@kbn/dev-utils'); + module.exports = { extends: [ './javascript.js', @@ -79,7 +81,13 @@ module.exports = { from: 'react-intl', to: '@kbn/i18n/react', disallowedMessage: `import from @kbn/i18n/react instead` - } + }, + { + from: 'styled-components', + to: false, + exclude: USES_STYLED_COMPONENTS, + disallowedMessage: `Prefer using @emotion/react instead. To use styled-components, ensure you plugin is enabled in @kbn/dev-utils/src/babel.ts.` + }, ], ], }, diff --git a/packages/kbn-babel-preset/BUILD.bazel b/packages/kbn-babel-preset/BUILD.bazel index f5ebc153b9e1a..11eae8bc55ca9 100644 --- a/packages/kbn-babel-preset/BUILD.bazel +++ b/packages/kbn-babel-preset/BUILD.bazel @@ -32,6 +32,7 @@ DEPS = [ "@npm//@babel/preset-env", "@npm//@babel/preset-react", "@npm//@babel/preset-typescript", + "@npm//@emotion/babel-preset-css-prop", "@npm//babel-plugin-add-module-exports", "@npm//babel-plugin-styled-components", "@npm//babel-plugin-transform-react-remove-prop-types", diff --git a/packages/kbn-babel-preset/webpack_preset.js b/packages/kbn-babel-preset/webpack_preset.js index ca7ea40ff0fe1..186ce87478828 100644 --- a/packages/kbn-babel-preset/webpack_preset.js +++ b/packages/kbn-babel-preset/webpack_preset.js @@ -6,6 +6,8 @@ * Side Public License, v 1. */ +const { USES_STYLED_COMPONENTS } = require.resolve('@kbn/dev-utils'); + module.exports = () => { return { presets: [ @@ -21,14 +23,6 @@ module.exports = () => { ], require('./common_preset'), ], - plugins: [ - [ - require.resolve('babel-plugin-styled-components'), - { - fileName: false, - }, - ], - ], env: { production: { plugins: [ @@ -42,5 +36,29 @@ module.exports = () => { ], }, }, + overrides: [ + { + include: USES_STYLED_COMPONENTS, + plugins: [ + [ + require.resolve('babel-plugin-styled-components'), + { + fileName: false, + }, + ], + ], + }, + { + exclude: USES_STYLED_COMPONENTS, + presets: [ + [ + require.resolve('@emotion/babel-preset-css-prop'), + { + labelFormat: '[local]', + }, + ], + ], + }, + ], }; }; diff --git a/packages/kbn-crypto/BUILD.bazel b/packages/kbn-crypto/BUILD.bazel index 20793e27de629..bf1ed3f778975 100644 --- a/packages/kbn-crypto/BUILD.bazel +++ b/packages/kbn-crypto/BUILD.bazel @@ -38,7 +38,8 @@ TYPES_DEPS = [ "@npm//@types/node", "@npm//@types/node-forge", "@npm//@types/testing-library__jest-dom", - "@npm//resize-observer-polyfill" + "@npm//resize-observer-polyfill", + "@npm//@emotion/react", ] DEPS = SRC_DEPS + TYPES_DEPS diff --git a/packages/kbn-dev-utils/src/babel.ts b/packages/kbn-dev-utils/src/babel.ts index 9daa7d9fe8d7a..5570055a21d15 100644 --- a/packages/kbn-dev-utils/src/babel.ts +++ b/packages/kbn-dev-utils/src/babel.ts @@ -46,3 +46,14 @@ export async function transformFileWithBabel(file: File) { file.extname = '.js'; transformedFiles.add(file); } + +/** + * Synchronized regex list of files that use `styled-components`. + * Used by `kbn-babel-preset` and `elastic-eslint-config-kibana`. + */ +export const USES_STYLED_COMPONENTS = [ + /packages[\/\\]kbn-ui-shared-deps[\/\\]/, + /src[\/\\]plugins[\/\\](data|kibana_react)[\/\\]/, + /x-pack[\/\\]plugins[\/\\](apm|beats_management|cases|fleet|infra|lists|observability|osquery|security_solution|timelines|uptime)[\/\\]/, + /x-pack[\/\\]test[\/\\]plugin_functional[\/\\]plugins[\/\\]resolver_test[\/\\]/, +]; diff --git a/packages/kbn-eslint-plugin-eslint/rules/module_migration.js b/packages/kbn-eslint-plugin-eslint/rules/module_migration.js index 87a1bae8eac1a..3175210eccb10 100644 --- a/packages/kbn-eslint-plugin-eslint/rules/module_migration.js +++ b/packages/kbn-eslint-plugin-eslint/rules/module_migration.js @@ -78,6 +78,12 @@ module.exports = { disallowedMessage: { type: 'string', }, + include: { + type: 'array', + }, + exclude: { + type: 'array', + }, }, anyOf: [ { @@ -95,7 +101,22 @@ module.exports = { ], }, create: (context) => { - const mappings = context.options[0]; + const filename = path.relative(KIBANA_ROOT, context.getFilename()); + + const mappings = context.options[0].filter((mapping) => { + // exclude mapping rule if it is explicitly excluded from this file + if (mapping.exclude && mapping.exclude.some((p) => p.test(filename))) { + return false; + } + + // if this mapping rule is only included in specific files, optionally include it + if (mapping.include) { + return mapping.include.some((p) => p.test(filename)); + } + + // include all mapping rules by default + return true; + }); return { ImportDeclaration(node) { diff --git a/packages/kbn-storybook/lib/default_config.ts b/packages/kbn-storybook/lib/default_config.ts index e194c9789daab..989f707b06fed 100644 --- a/packages/kbn-storybook/lib/default_config.ts +++ b/packages/kbn-storybook/lib/default_config.ts @@ -6,8 +6,11 @@ * Side Public License, v 1. */ +import * as path from 'path'; import { StorybookConfig } from '@storybook/core/types'; +import { REPO_ROOT } from './constants'; +const toPath = (_path: string) => path.join(REPO_ROOT, _path); export const defaultConfig: StorybookConfig = { addons: ['@kbn/storybook/preset', '@storybook/addon-a11y', '@storybook/addon-essentials'], stories: ['../**/*.stories.tsx'], @@ -22,6 +25,21 @@ export const defaultConfig: StorybookConfig = { config.node = { fs: 'empty' }; - return config; + // Remove when @storybook has moved to @emotion v11 + // https://github.com/storybookjs/storybook/issues/13145 + const emotion11CompatibleConfig = { + ...config, + resolve: { + ...config.resolve, + alias: { + ...config.resolve?.alias, + '@emotion/core': toPath('node_modules/@emotion/react'), + '@emotion/styled': toPath('node_modules/@emotion/styled'), + 'emotion-theming': toPath('node_modules/@emotion/react'), + }, + }, + }; + + return emotion11CompatibleConfig; }, }; diff --git a/packages/kbn-storybook/lib/theme_switcher.tsx b/packages/kbn-storybook/lib/theme_switcher.tsx index da62bc7010c4b..24ddec1fdf51c 100644 --- a/packages/kbn-storybook/lib/theme_switcher.tsx +++ b/packages/kbn-storybook/lib/theme_switcher.tsx @@ -54,6 +54,7 @@ export function ThemeSwitcher() { closeOnClick tooltip={({ onHide }) => } > + {/* @ts-ignore Remove when @storybook has moved to @emotion v11 */} diff --git a/packages/kbn-telemetry-tools/BUILD.bazel b/packages/kbn-telemetry-tools/BUILD.bazel index d394b0c93d45f..ef1316cec75a3 100644 --- a/packages/kbn-telemetry-tools/BUILD.bazel +++ b/packages/kbn-telemetry-tools/BUILD.bazel @@ -47,7 +47,8 @@ TYPES_DEPS = [ "@npm//@types/node", "@npm//@types/normalize-path", "@npm//@types/testing-library__jest-dom", - "@npm//resize-observer-polyfill" + "@npm//resize-observer-polyfill", + "@npm//@emotion/react", ] DEPS = SRC_DEPS + TYPES_DEPS diff --git a/packages/kbn-test/jest-preset.js b/packages/kbn-test/jest-preset.js index c84fe3f7a55b0..abc5cfa8efaa8 100644 --- a/packages/kbn-test/jest-preset.js +++ b/packages/kbn-test/jest-preset.js @@ -66,6 +66,7 @@ module.exports = { snapshotSerializers: [ '/src/plugins/kibana_react/public/util/test_helpers/react_mount_serializer.ts', '/node_modules/enzyme-to-json/serializer', + '/node_modules/@emotion/jest/serializer', ], // The test environment that will be used for testing diff --git a/packages/kbn-ui-shared-deps/BUILD.bazel b/packages/kbn-ui-shared-deps/BUILD.bazel index 9096905a2586b..f92049292f373 100644 --- a/packages/kbn-ui-shared-deps/BUILD.bazel +++ b/packages/kbn-ui-shared-deps/BUILD.bazel @@ -40,6 +40,7 @@ SRC_DEPS = [ "@npm//@elastic/charts", "@npm//@elastic/eui", "@npm//@elastic/numeral", + "@npm//@emotion/react", "@npm//abortcontroller-polyfill", "@npm//angular", "@npm//babel-loader", diff --git a/packages/kbn-ui-shared-deps/src/entry.js b/packages/kbn-ui-shared-deps/src/entry.js index 0e91c45ae6392..20e26ca6a2864 100644 --- a/packages/kbn-ui-shared-deps/src/entry.js +++ b/packages/kbn-ui-shared-deps/src/entry.js @@ -18,6 +18,7 @@ export const KbnI18n = require('@kbn/i18n'); export const KbnI18nAngular = require('@kbn/i18n/angular'); export const KbnI18nReact = require('@kbn/i18n/react'); export const Angular = require('angular'); +export const EmotionReact = require('@emotion/react'); export const Moment = require('moment'); export const MomentTimezone = require('moment-timezone/moment-timezone'); export const KbnMonaco = require('@kbn/monaco'); diff --git a/packages/kbn-ui-shared-deps/src/index.js b/packages/kbn-ui-shared-deps/src/index.js index 36c2e6b02879e..291c7c471d27c 100644 --- a/packages/kbn-ui-shared-deps/src/index.js +++ b/packages/kbn-ui-shared-deps/src/index.js @@ -57,6 +57,7 @@ exports.externals = { '@kbn/i18n': '__kbnSharedDeps__.KbnI18n', '@kbn/i18n/angular': '__kbnSharedDeps__.KbnI18nAngular', '@kbn/i18n/react': '__kbnSharedDeps__.KbnI18nReact', + '@emotion/react': '__kbnSharedDeps__.EmotionReact', jquery: '__kbnSharedDeps__.Jquery', moment: '__kbnSharedDeps__.Moment', 'moment-timezone': '__kbnSharedDeps__.MomentTimezone', diff --git a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap index 82353a96dc33c..6e33e39b148c4 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap @@ -746,9 +746,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` onResize={[Function]} >
-
+
@@ -1021,9 +1019,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` onResize={[Function]} >
-
+
@@ -1315,9 +1311,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` onResize={[Function]} >
-
+
@@ -1570,9 +1564,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` onResize={[Function]} >
-
+
@@ -1786,9 +1778,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` onResize={[Function]} >
-
+
diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 66d81d058fc77..b8af7c12d57fc 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -17,7 +17,6 @@ import { CoreSetup } from 'src/core/public'; import { CoreSetup as CoreSetup_2 } from 'kibana/public'; import { CoreStart } from 'kibana/public'; import { CoreStart as CoreStart_2 } from 'src/core/public'; -import * as CSS from 'csstype'; import { Datatable as Datatable_2 } from 'src/plugins/expressions'; import { Datatable as Datatable_3 } from 'src/plugins/expressions/common'; import { DatatableColumn as DatatableColumn_2 } from 'src/plugins/expressions'; @@ -72,13 +71,12 @@ import { Plugin } from 'src/core/public'; import { PluginInitializerContext as PluginInitializerContext_2 } from 'src/core/public'; import { PluginInitializerContext as PluginInitializerContext_3 } from 'kibana/public'; import { PopoverAnchorPosition } from '@elastic/eui'; -import * as PropTypes from 'prop-types'; import { PublicContract } from '@kbn/utility-types'; import { PublicMethodsOf } from '@kbn/utility-types'; import { PublicUiSettingsParams } from 'src/core/server/types'; import { RangeFilter as RangeFilter_2 } from 'src/plugins/data/public'; import React from 'react'; -import * as React_3 from 'react'; +import * as React_2 from 'react'; import { RecursiveReadonly } from '@kbn/utility-types'; import { Request as Request_2 } from '@hapi/hapi'; import { RequestAdapter } from 'src/plugins/inspector/common'; diff --git a/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap b/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap index 0ab3f8a4e3466..1e7b59d8a9e76 100644 --- a/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap +++ b/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap @@ -1169,7 +1169,6 @@ exports[`Inspector Data View component should render single table without select > { - // Warning: (ae-forgotten-export) The symbol "React" needs to be exported by the entry point index.d.ts - constructor(getFactory: EmbeddableStart_2['getEmbeddableFactory'], getAllFactories: EmbeddableStart_2['getEmbeddableFactories'], overlays: OverlayStart_2, notifications: NotificationsStart_2, SavedObjectFinder: React_2.ComponentType, reportUiCounter?: ((appName: string, type: import("@kbn/analytics").UiCounterMetricType, eventNames: string | string[], count?: number | undefined) => void) | undefined); + constructor(getFactory: EmbeddableStart_2['getEmbeddableFactory'], getAllFactories: EmbeddableStart_2['getEmbeddableFactories'], overlays: OverlayStart_2, notifications: NotificationsStart_2, SavedObjectFinder: React.ComponentType, reportUiCounter?: ((appName: string, type: import("@kbn/analytics").UiCounterMetricType, eventNames: string | string[], count?: number | undefined) => void) | undefined); // (undocumented) execute(context: ActionExecutionContext_2): Promise; // (undocumented) diff --git a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/tsconfig.json b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/tsconfig.json index b704274a58aa4..e92dc717ae25e 100644 --- a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/tsconfig.json +++ b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/tsconfig.json @@ -6,7 +6,8 @@ "types": [ "node", "jest", - "react" + "react", + "@emotion/react/types/css-prop" ] }, "include": [ diff --git a/test/tsconfig.json b/test/tsconfig.json index 8cf33d93a4067..dccbe8d715c51 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -6,7 +6,7 @@ "emitDeclarationOnly": true, "declaration": true, "declarationMap": true, - "types": ["node", "resize-observer-polyfill"] + "types": ["node", "resize-observer-polyfill", "@emotion/react/types/css-prop"] }, "include": [ "**/*", diff --git a/tsconfig.base.json b/tsconfig.base.json index cc8b66848a394..0c8fec7c88cda 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -12,7 +12,10 @@ // Allows for importing from `kibana` package for the exported types. "kibana": ["./kibana"], "kibana/public": ["src/core/public"], - "kibana/server": ["src/core/server"] + "kibana/server": ["src/core/server"], + "@emotion/core": [ + "typings/@emotion" + ], }, // Support .tsx files and transform JSX into calls to React.createElement "jsx": "react", @@ -62,7 +65,8 @@ "flot", "jest-styled-components", "@testing-library/jest-dom", - "resize-observer-polyfill" + "resize-observer-polyfill", + "@emotion/react/types/css-prop" ] } } diff --git a/typings/@emotion/index.d.ts b/typings/@emotion/index.d.ts new file mode 100644 index 0000000000000..2a5e63a3e29ef --- /dev/null +++ b/typings/@emotion/index.d.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +// Stub @emotion/core +// Remove when @storybook has moved to @emotion v11 +// https://github.com/storybookjs/storybook/issues/13145 +export {}; diff --git a/x-pack/plugins/reporting/public/management/__snapshots__/report_listing.test.tsx.snap b/x-pack/plugins/reporting/public/management/__snapshots__/report_listing.test.tsx.snap index 744a3b2d405c3..8007acad93e4b 100644 --- a/x-pack/plugins/reporting/public/management/__snapshots__/report_listing.test.tsx.snap +++ b/x-pack/plugins/reporting/public/management/__snapshots__/report_listing.test.tsx.snap @@ -424,7 +424,6 @@ exports[`ReportListing Report job listing with some items 1`] = ` className="euiTableCellContent euiTableCellContent--overflowingContent" >
@@ -481,7 +480,6 @@ exports[`ReportListing Report job listing with some items 1`] = ` className="euiTableCellContent euiTableCellContent--overflowingContent" >
@@ -523,7 +521,6 @@ exports[`ReportListing Report job listing with some items 1`] = ` className="euiTableCellContent euiTableCellContent--overflowingContent" >
@@ -1491,7 +1487,6 @@ exports[`ReportListing Report job listing with some items 1`] = ` className="euiTableCellContent euiTableCellContent--overflowingContent" >
@@ -1533,7 +1528,6 @@ exports[`ReportListing Report job listing with some items 1`] = ` className="euiTableCellContent euiTableCellContent--overflowingContent" >
@@ -2515,7 +2508,6 @@ exports[`ReportListing Report job listing with some items 1`] = ` className="euiTableCellContent euiTableCellContent--overflowingContent" >
@@ -2557,7 +2549,6 @@ exports[`ReportListing Report job listing with some items 1`] = ` className="euiTableCellContent euiTableCellContent--overflowingContent" >
@@ -3586,7 +3576,6 @@ exports[`ReportListing Report job listing with some items 1`] = ` className="euiTableCellContent euiTableCellContent--overflowingContent" >
@@ -3628,7 +3617,6 @@ exports[`ReportListing Report job listing with some items 1`] = ` className="euiTableCellContent euiTableCellContent--overflowingContent" >
@@ -4690,7 +4677,6 @@ exports[`ReportListing Report job listing with some items 1`] = ` className="euiTableCellContent euiTableCellContent--overflowingContent" >
@@ -4732,7 +4718,6 @@ exports[`ReportListing Report job listing with some items 1`] = ` className="euiTableCellContent euiTableCellContent--overflowingContent" >
@@ -5761,7 +5745,6 @@ exports[`ReportListing Report job listing with some items 1`] = ` className="euiTableCellContent euiTableCellContent--overflowingContent" >
@@ -5803,7 +5786,6 @@ exports[`ReportListing Report job listing with some items 1`] = ` className="euiTableCellContent euiTableCellContent--overflowingContent" >
@@ -6832,7 +6813,6 @@ exports[`ReportListing Report job listing with some items 1`] = ` className="euiTableCellContent euiTableCellContent--overflowingContent" >
@@ -6874,7 +6854,6 @@ exports[`ReportListing Report job listing with some items 1`] = ` className="euiTableCellContent euiTableCellContent--overflowingContent" >
@@ -7903,7 +7881,6 @@ exports[`ReportListing Report job listing with some items 1`] = ` className="euiTableCellContent euiTableCellContent--overflowingContent" >
@@ -7945,7 +7922,6 @@ exports[`ReportListing Report job listing with some items 1`] = ` className="euiTableCellContent euiTableCellContent--overflowingContent" >
@@ -8974,7 +8949,6 @@ exports[`ReportListing Report job listing with some items 1`] = ` className="euiTableCellContent euiTableCellContent--overflowingContent" >
@@ -9016,7 +8990,6 @@ exports[`ReportListing Report job listing with some items 1`] = ` className="euiTableCellContent euiTableCellContent--overflowingContent" >
-
+
@@ -752,9 +750,7 @@ exports[`ScreenCapturePanelContent properly renders a view with "print" layout o onResize={[Function]} >
-
+
@@ -1064,9 +1060,7 @@ exports[`ScreenCapturePanelContent renders the default view properly 1`] = ` onResize={[Function]} >
-
+
diff --git a/yarn.lock b/yarn.lock index 227301eed8dd6..dccbd8f91a429 100644 --- a/yarn.lock +++ b/yarn.lock @@ -209,6 +209,13 @@ dependencies: "@babel/types" "^7.12.5" +"@babel/helper-module-imports@^7.7.0": + version "7.13.12" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz#c6a369a6f3621cb25da014078684da9196b61977" + integrity sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA== + dependencies: + "@babel/types" "^7.13.12" + "@babel/helper-module-transforms@^7.12.1": version "7.12.1" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.12.1.tgz#7954fec71f5b32c48e4b303b437c34453fd7247c" @@ -548,6 +555,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.4" +"@babel/plugin-syntax-jsx@^7.2.0": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.13.tgz#044fb81ebad6698fe62c478875575bcbb9b70f15" + integrity sha512-d4HM23Q1K7oq/SLNmG6mRt85l2csmQ0cHRaxRXjKW0YFdEXqlZ5kzFQKH5Uc3rDJECgu+yCRgPkG04Mm98R/1g== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + "@babel/plugin-syntax-logical-assignment-operators@^7.10.4", "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" @@ -1154,6 +1168,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.13.10": + version "7.13.17" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.17.tgz#8966d1fc9593bf848602f0662d6b4d0069e3a7ec" + integrity sha512-NCdgJEelPTSh+FEFylhnP1ylq848l1z9t9N0j1Lfbcw0+KXGjsTvUmkxy+voLLXB5SOKMbLLx4jxYliGrYQseA== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/template@^7.10.4", "@babel/template@^7.12.13", "@babel/template@^7.12.7", "@babel/template@^7.3.3", "@babel/template@^7.4.4": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.12.13.tgz#530265be8a2589dbb37523844c5bcb55947fb327" @@ -1187,6 +1208,15 @@ lodash "^4.17.19" to-fast-properties "^2.0.0" +"@babel/types@^7.13.12": + version "7.13.14" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.13.14.tgz#c35a4abb15c7cd45a2746d78ab328e362cbace0d" + integrity sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ== + dependencies: + "@babel/helper-validator-identifier" "^7.12.11" + lodash "^4.17.19" + to-fast-properties "^2.0.0" + "@base2/pretty-print-object@1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@base2/pretty-print-object/-/pretty-print-object-1.0.0.tgz#860ce718b0b73f4009e153541faff2cb6b85d047" @@ -1592,6 +1622,41 @@ resolved "https://registry.yarnpkg.com/@elastic/ui-ace/-/ui-ace-0.2.3.tgz#5281aed47a79b7216c55542b0675e435692f20cd" integrity sha512-Nti5s2dplBPhSKRwJxG9JXTMOev4jVOWcnTJD1TOkJr1MUBYKVZcNcJtIVMSvahWGmP0B/UfO9q9lyRqdivkvQ== +"@emotion/babel-plugin-jsx-pragmatic@^0.1.5": + version "0.1.5" + resolved "https://registry.yarnpkg.com/@emotion/babel-plugin-jsx-pragmatic/-/babel-plugin-jsx-pragmatic-0.1.5.tgz#27debfe9c27c4d83574d509787ae553bf8a34d7e" + integrity sha512-y+3AJ0SItMDaAgGPVkQBC/S/BaqaPACkQ6MyCI2CUlrjTxKttTVfD3TMtcs7vLEcLxqzZ1xiG0vzwCXjhopawQ== + dependencies: + "@babel/plugin-syntax-jsx" "^7.2.0" + +"@emotion/babel-plugin@^11.2.0": + version "11.2.0" + resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.2.0.tgz#f25c6df8ec045dad5ae6ca63df0791673b98c920" + integrity sha512-lsnQBnl3l4wu/FJoyHnYRpHJeIPNkOBMbtDUIXcO8luulwRKZXPvA10zd2eXVN6dABIWNX4E34en/jkejIg/yA== + dependencies: + "@babel/helper-module-imports" "^7.7.0" + "@babel/plugin-syntax-jsx" "^7.12.1" + "@babel/runtime" "^7.7.2" + "@emotion/hash" "^0.8.0" + "@emotion/memoize" "^0.7.5" + "@emotion/serialize" "^1.0.0" + babel-plugin-macros "^2.6.1" + convert-source-map "^1.5.0" + escape-string-regexp "^4.0.0" + find-root "^1.1.0" + source-map "^0.5.7" + stylis "^4.0.3" + +"@emotion/babel-preset-css-prop@^11.2.0": + version "11.2.0" + resolved "https://registry.yarnpkg.com/@emotion/babel-preset-css-prop/-/babel-preset-css-prop-11.2.0.tgz#c7e945f56b2610b438f0dc8ae5253fc55488de0e" + integrity sha512-9XLQm2eLPYTho+Cx1LQTDA1rATjoAaB4O+ds55XDvoAa+Z16Hhg8y5Vihj3C8E6+ilDM8SV5A9Z6z+yj0YIRBg== + dependencies: + "@babel/plugin-transform-react-jsx" "^7.12.1" + "@babel/runtime" "^7.7.2" + "@emotion/babel-plugin" "^11.2.0" + "@emotion/babel-plugin-jsx-pragmatic" "^0.1.5" + "@emotion/babel-utils@^0.6.4": version "0.6.10" resolved "https://registry.yarnpkg.com/@emotion/babel-utils/-/babel-utils-0.6.10.tgz#83dbf3dfa933fae9fc566e54fbb45f14674c6ccc" @@ -1614,6 +1679,17 @@ "@emotion/utils" "0.11.3" "@emotion/weak-memoize" "0.2.5" +"@emotion/cache@^11.4.0": + version "11.4.0" + resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.4.0.tgz#293fc9d9a7a38b9aad8e9337e5014366c3b09ac0" + integrity sha512-Zx70bjE7LErRO9OaZrhf22Qye1y4F7iDl+ITjet0J+i+B88PrAOBkKvaAWhxsZf72tDLajwCgfCjJ2dvH77C3g== + dependencies: + "@emotion/memoize" "^0.7.4" + "@emotion/sheet" "^1.0.0" + "@emotion/utils" "^1.0.0" + "@emotion/weak-memoize" "^0.2.5" + stylis "^4.0.3" + "@emotion/core@^10.0.9", "@emotion/core@^10.1.1": version "10.1.1" resolved "https://registry.yarnpkg.com/@emotion/core/-/core-10.1.1.tgz#c956c1365f2f2481960064bcb8c4732e5fb612c3" @@ -1626,6 +1702,14 @@ "@emotion/sheet" "0.9.4" "@emotion/utils" "0.11.3" +"@emotion/css-prettifier@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@emotion/css-prettifier/-/css-prettifier-1.0.0.tgz#3ed4240d93c9798c001cedf27dd0aa960bdddd1a" + integrity sha512-efxSrRTiTqHTQVKW15Gz5H4pNAw8OqcG8NaiwkJIkqIdNXTD4Qr1zC1Ou6r2acd1oJJ2s56nb1ClnXMiWoj6gQ== + dependencies: + "@emotion/memoize" "^0.7.4" + stylis "^4.0.3" + "@emotion/css@^10.0.27", "@emotion/css@^10.0.9": version "10.0.27" resolved "https://registry.yarnpkg.com/@emotion/css/-/css-10.0.27.tgz#3a7458198fbbebb53b01b2b87f64e5e21241e14c" @@ -1635,7 +1719,7 @@ "@emotion/utils" "0.11.3" babel-plugin-emotion "^10.0.27" -"@emotion/hash@0.8.0": +"@emotion/hash@0.8.0", "@emotion/hash@^0.8.0": version "0.8.0" resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413" integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow== @@ -1652,6 +1736,17 @@ dependencies: "@emotion/memoize" "0.7.4" +"@emotion/jest@^11.3.0": + version "11.3.0" + resolved "https://registry.yarnpkg.com/@emotion/jest/-/jest-11.3.0.tgz#43bed6dcb47c8691b346cee231861ebc8f9b0016" + integrity sha512-LZqYc3yerhic1IvAcEwBLRs1DsUt3oY7Oz6n+e+HU32iYOK/vpfzlhgmQURE94BHfv6eCOj6DV38f3jSnIkBkQ== + dependencies: + "@babel/runtime" "^7.13.10" + "@emotion/css-prettifier" "^1.0.0" + chalk "^4.1.0" + specificity "^0.4.1" + stylis "^4.0.3" + "@emotion/memoize@0.7.4": version "0.7.4" resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb" @@ -1662,6 +1757,24 @@ resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.6.6.tgz#004b98298d04c7ca3b4f50ca2035d4f60d2eed1b" integrity sha512-h4t4jFjtm1YV7UirAFuSuFGyLa+NNxjdkq6DpFLANNQY5rHueFZHVY+8Cu1HYVP6DrheB0kv4m5xPjo7eKT7yQ== +"@emotion/memoize@^0.7.4", "@emotion/memoize@^0.7.5": + version "0.7.5" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.5.tgz#2c40f81449a4e554e9fc6396910ed4843ec2be50" + integrity sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ== + +"@emotion/react@^11.4.0": + version "11.4.0" + resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.4.0.tgz#2465ad7b073a691409b88dfd96dc17097ddad9b7" + integrity sha512-4XklWsl9BdtatLoJpSjusXhpKv9YVteYKh9hPKP1Sxl+mswEFoUe0WtmtWjxEjkA51DQ2QRMCNOvKcSlCQ7ivg== + dependencies: + "@babel/runtime" "^7.13.10" + "@emotion/cache" "^11.4.0" + "@emotion/serialize" "^1.0.2" + "@emotion/sheet" "^1.0.1" + "@emotion/utils" "^1.0.0" + "@emotion/weak-memoize" "^0.2.5" + hoist-non-react-statics "^3.3.1" + "@emotion/serialize@^0.11.15", "@emotion/serialize@^0.11.16": version "0.11.16" resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-0.11.16.tgz#dee05f9e96ad2fb25a5206b6d759b2d1ed3379ad" @@ -1683,11 +1796,27 @@ "@emotion/unitless" "^0.6.7" "@emotion/utils" "^0.8.2" +"@emotion/serialize@^1.0.0", "@emotion/serialize@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.0.2.tgz#77cb21a0571c9f68eb66087754a65fa97bfcd965" + integrity sha512-95MgNJ9+/ajxU7QIAruiOAdYNjxZX7G2mhgrtDWswA21VviYIRP1R5QilZ/bDY42xiKsaktP4egJb3QdYQZi1A== + dependencies: + "@emotion/hash" "^0.8.0" + "@emotion/memoize" "^0.7.4" + "@emotion/unitless" "^0.7.5" + "@emotion/utils" "^1.0.0" + csstype "^3.0.2" + "@emotion/sheet@0.9.4": version "0.9.4" resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-0.9.4.tgz#894374bea39ec30f489bbfc3438192b9774d32e5" integrity sha512-zM9PFmgVSqBw4zL101Q0HrBVTGmpAxFZH/pYx/cjJT5advXguvcgjHFTCaIO3enL/xr89vK2bh0Mfyj9aa0ANA== +"@emotion/sheet@^1.0.0", "@emotion/sheet@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.0.1.tgz#245f54abb02dfd82326e28689f34c27aa9b2a698" + integrity sha512-GbIvVMe4U+Zc+929N1V7nW6YYJtidj31lidSmdYcWozwoBIObXBnaJkKNDjZrLm9Nc0BR+ZyHNaRZxqNZbof5g== + "@emotion/styled-base@^10.0.27": version "10.0.31" resolved "https://registry.yarnpkg.com/@emotion/styled-base/-/styled-base-10.0.31.tgz#940957ee0aa15c6974adc7d494ff19765a2f742a" @@ -1716,7 +1845,7 @@ resolved "https://registry.yarnpkg.com/@emotion/stylis/-/stylis-0.7.1.tgz#50f63225e712d99e2b2b39c19c70fff023793ca5" integrity sha512-/SLmSIkN13M//53TtNxgxo57mcJk/UJIDFRKwOiLIBEyBHEcipgR6hNMQ/59Sl4VjCJ0Z/3zeAZyvnSLPG/1HQ== -"@emotion/unitless@0.7.5", "@emotion/unitless@^0.7.4": +"@emotion/unitless@0.7.5", "@emotion/unitless@^0.7.4", "@emotion/unitless@^0.7.5": version "0.7.5" resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed" integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== @@ -1736,7 +1865,12 @@ resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-0.8.2.tgz#576ff7fb1230185b619a75d258cbc98f0867a8dc" integrity sha512-rLu3wcBWH4P5q1CGoSSH/i9hrXs7SlbRLkoq9IGuoPYNGQvDJ3pt/wmOM+XgYjIDRMVIdkUWt0RsfzF50JfnCw== -"@emotion/weak-memoize@0.2.5": +"@emotion/utils@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.0.0.tgz#abe06a83160b10570816c913990245813a2fd6af" + integrity sha512-mQC2b3XLDs6QCW+pDQDiyO/EdGZYOygE8s5N5rrzjSI4M3IejPE/JPndCBwRT9z982aqQNi6beWs1UeayrQxxA== + +"@emotion/weak-memoize@0.2.5", "@emotion/weak-memoize@^0.2.5": version "0.2.5" resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46" integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA== @@ -7790,7 +7924,7 @@ babel-plugin-jest-hoist@^26.6.2: "@types/babel__core" "^7.0.0" "@types/babel__traverse" "^7.0.6" -babel-plugin-macros@^2.0.0, babel-plugin-macros@^2.8.0: +babel-plugin-macros@^2.0.0, babel-plugin-macros@^2.6.1, babel-plugin-macros@^2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz#0f958a7cc6556b1e65344465d99111a1e5e10138" integrity sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg== @@ -9487,15 +9621,6 @@ cli-width@^3.0.0: resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== -clipboard@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.4.tgz#836dafd66cf0fea5d71ce5d5b0bf6e958009112d" - integrity sha512-Vw26VSLRpJfBofiVaFb/I8PVfdI1OxKcYShe6fm0sP/DtmiWQNCjhM/okTvdCo0G+lMMm1rMYbk4IK4x1X+kgQ== - dependencies: - good-listener "^1.2.2" - select "^1.1.2" - tiny-emitter "^2.0.0" - cliui@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" @@ -10718,6 +10843,11 @@ csstype@^2.5.2: resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.14.tgz#004822a4050345b55ad4dcc00be1d9cf2f4296de" integrity sha512-2mSc+VEpGPblzAxyeR+vZhJKgYg0Og0nnRi7pmRXFYYxSfnOnW8A5wwQb4n4cE2nIOzqKOAzLCaEX6aBmNEv8A== +csstype@^3.0.2: + version "3.0.7" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.7.tgz#2a5fb75e1015e84dd15692f71e89a1450290950b" + integrity sha512-KxnUB0ZMlnUWCsx2Z8MUsr6qV6ja1w9ArPErJaJaF8a5SOWoHLIszeCTKGRGRgtLgYrs1E8CHkNSP1VZTTPc9g== + cucumber-expressions@^5.0.13: version "5.0.18" resolved "https://registry.yarnpkg.com/cucumber-expressions/-/cucumber-expressions-5.0.18.tgz#6c70779efd3aebc5e9e7853938b1110322429596" @@ -11589,11 +11719,6 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= -delegate@^3.1.2: - version "3.2.0" - resolved "https://registry.yarnpkg.com/delegate/-/delegate-3.2.0.tgz#b66b71c3158522e8ab5744f720d8ca0c2af59166" - integrity sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw== - delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" @@ -14789,13 +14914,6 @@ gonzales-pe@^4.3.0: dependencies: minimist "^1.2.5" -good-listener@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50" - integrity sha1-1TswzfkxPf+33JoNR3CWqm0UXFA= - dependencies: - delegate "^3.1.2" - got@5.6.0: version "5.6.0" resolved "https://registry.yarnpkg.com/got/-/got-5.6.0.tgz#bb1d7ee163b78082bbc8eb836f3f395004ea6fbf" @@ -15410,7 +15528,7 @@ hmac-drbg@^1.0.1: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -hoist-non-react-statics@^2.3.1, hoist-non-react-statics@^2.5.5, hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: +hoist-non-react-statics@^2.3.1, hoist-non-react-statics@^2.5.5, hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -22172,8 +22290,6 @@ prismjs@1.24.0, prismjs@^1.22.0, prismjs@~1.23.0: version "1.24.0" resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.24.0.tgz#0409c30068a6c52c89ef7f1089b3ca4de56be2ac" integrity sha512-SqV5GRsNqnzCL8k5dfAjCNhUrF3pR0A9lTDSCUZeh/LIshheXJEaP0hwLz2t4XHivd2J/v2HR+gRnigzeKe3cQ== - optionalDependencies: - clipboard "^2.0.0" private@^0.1.8, private@~0.1.5: version "0.1.8" @@ -24791,11 +24907,6 @@ select-hose@^2.0.0: resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= -select@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d" - integrity sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0= - selenium-webdriver@^4.0.0-alpha.7: version "4.0.0-alpha.7" resolved "https://registry.yarnpkg.com/selenium-webdriver/-/selenium-webdriver-4.0.0-alpha.7.tgz#e3879d8457fd7ad8e4424094b7dc0540d99e6797" @@ -26207,6 +26318,11 @@ stylis@^3.5.0: resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.5.4.tgz#f665f25f5e299cf3d64654ab949a57c768b73fbe" integrity sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q== +stylis@^4.0.3: + version "4.0.7" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.7.tgz#412a90c28079417f3d27c028035095e4232d2904" + integrity sha512-OFFeUXFgwnGOKvEXaSv0D0KQ5ADP0n6g3SVONx6I/85JzNZ3u50FRwB3lVIk1QO2HNdI75tbVzc4Z66Gdp9voA== + subarg@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/subarg/-/subarg-1.0.0.tgz#f62cf17581e996b48fc965699f54c06ae268b8d2" @@ -26828,11 +26944,6 @@ timsort@^0.3.0, timsort@~0.3.0: resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= -tiny-emitter@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.0.2.tgz#82d27468aca5ade8e5fd1e6d22b57dd43ebdfb7c" - integrity sha512-2NM0auVBGft5tee/OxP4PI3d8WItkDM+fPnaRAVo6xTDI2knbz9eC5ArWGqtGlYqiH3RU5yMpdyTTO7MguC4ow== - tiny-inflate@^1.0.0, tiny-inflate@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/tiny-inflate/-/tiny-inflate-1.0.3.tgz#122715494913a1805166aaf7c93467933eea26c4" From 65bc4a9c0e486f14b40b99d961644b2dd501ac16 Mon Sep 17 00:00:00 2001 From: Scotty Bollinger Date: Fri, 9 Jul 2021 14:53:15 -0500 Subject: [PATCH 34/49] [Workplace Search] Remove users from groups views (#105108) * Remove user list from groups table * Remove users table from group overview * Lint --- .../groups/components/group_overview.tsx | 25 ++++++++----------- .../groups/components/group_row.test.tsx | 15 +---------- .../views/groups/components/group_row.tsx | 19 +------------- .../views/groups/components/groups_table.tsx | 7 ------ .../translations/translations/ja-JP.json | 2 -- .../translations/translations/zh-CN.json | 2 -- 6 files changed, 13 insertions(+), 57 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.tsx index e7dfd6ddf1389..5714cc965827e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.tsx @@ -33,24 +33,23 @@ import { NAV, CANCEL_BUTTON } from '../../../constants'; import { USERS_AND_ROLES_PATH } from '../../../routes'; import { GroupLogic, MAX_NAME_LENGTH } from '../group_logic'; -import { GroupUsersTable } from './group_users_table'; - export const EMPTY_SOURCES_DESCRIPTION = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.groups.overview.emptySourcesDescription', { defaultMessage: 'No content sources are shared with this group.', } ); -const GROUP_USERS_DESCRIPTION = i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.groups.overview.groupUsersDescription', +const USERS_SECTION_TITLE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.groups.overview.usersSectionTitle', { - defaultMessage: 'Members will be able to search over the group’s sources.', + defaultMessage: 'Group users', } ); -export const EMPTY_USERS_DESCRIPTION = i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.groups.overview.emptyUsersDescription', +const GROUP_USERS_DESCRIPTION = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.groups.overview.groupUsersDescription', { - defaultMessage: 'There are no users in this group.', + defaultMessage: + "Users assigned to this group gain access to the sources' data and content defined above. User assignments for this group can be managed in the Users and Roles area.", } ); const MANAGE_SOURCES_BUTTON_TEXT = i18n.translate( @@ -118,7 +117,7 @@ export const GroupOverview: React.FC = () => { onGroupNameInputChange, } = useActions(GroupLogic); const { - group: { name, contentSources, users, canDeleteGroup }, + group: { name, contentSources, canDeleteGroup }, groupNameInputValue, dataLoading, confirmDeleteModalVisible, @@ -158,7 +157,6 @@ export const GroupOverview: React.FC = () => { ); const hasContentSources = contentSources?.length > 0; - const hasUsers = users?.length > 0; const manageSourcesButton = ( @@ -199,12 +197,11 @@ export const GroupOverview: React.FC = () => { const usersSection = ( - {hasUsers && } + {manageUsersButton} ); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_row.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_row.test.tsx index f98b873aed5bb..770bf8a51efd3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_row.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_row.test.tsx @@ -14,8 +14,7 @@ import moment from 'moment'; import { EuiTableRow } from '@elastic/eui'; -import { GroupRow, NO_USERS_MESSAGE, NO_SOURCES_MESSAGE } from './group_row'; -import { GroupUsers } from './group_users'; +import { GroupRow, NO_SOURCES_MESSAGE } from './group_row'; describe('GroupRow', () => { it('renders', () => { @@ -24,12 +23,6 @@ describe('GroupRow', () => { expect(wrapper.find(EuiTableRow)).toHaveLength(1); }); - it('renders group users', () => { - const wrapper = shallow(); - - expect(wrapper.find(GroupUsers)).toHaveLength(1); - }); - it('renders fromNow date string when in range', () => { const wrapper = shallow( @@ -44,12 +37,6 @@ describe('GroupRow', () => { expect(wrapper.find('small').text()).toEqual('Last updated January 1, 2020.'); }); - it('renders empty users message when no users present', () => { - const wrapper = shallow(); - - expect(wrapper.find('.user-group__accounts').text()).toEqual(NO_USERS_MESSAGE); - }); - it('renders empty sources message when no sources present', () => { const wrapper = shallow(); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_row.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_row.tsx index 94d44fde57aed..d079eb34fbf89 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_row.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_row.tsx @@ -19,7 +19,6 @@ import { Group } from '../../../types'; import { MAX_NAME_LENGTH } from '../group_logic'; import { GroupSources } from './group_sources'; -import { GroupUsers } from './group_users'; const DAYS_CUTOFF = 8; export const NO_SOURCES_MESSAGE = i18n.translate( @@ -40,14 +39,7 @@ const dateDisplay = (date: string) => ? moment(date).fromNow() : moment(date).format('MMMM D, YYYY'); -export const GroupRow: React.FC = ({ - id, - name, - updatedAt, - contentSources, - users, - usersCount, -}) => { +export const GroupRow: React.FC = ({ id, name, updatedAt, contentSources }) => { const GROUP_UPDATED_TEXT = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.groups.groupUpdatedText', { @@ -76,15 +68,6 @@ export const GroupRow: React.FC = ({ )}
- -
- {usersCount > 0 ? ( - - ) : ( - NO_USERS_MESSAGE - )} -
-
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/groups_table.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/groups_table.tsx index cfb3ed8044235..45175e489f94a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/groups_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/groups_table.tsx @@ -36,12 +36,6 @@ const SOURCES_TABLE_HEADER = i18n.translate( defaultMessage: 'Content sources', } ); -const USERS_TABLE_HEADER = i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.groups.groupsTable.usersTableHeader', - { - defaultMessage: 'Users', - } -); export const GroupsTable: React.FC<{}> = () => { const { setActivePage } = useActions(GroupsLogic); @@ -77,7 +71,6 @@ export const GroupsTable: React.FC<{}> = () => { {GROUP_TABLE_HEADER} {SOURCES_TABLE_HEADER} - {USERS_TABLE_HEADER} diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 11770d2d2f386..b7cde09da6319 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -8105,7 +8105,6 @@ "xpack.enterpriseSearch.workplaceSearch.groups.groupSourcesUpdated": "共有コンテンツソースが正常に更新されました。", "xpack.enterpriseSearch.workplaceSearch.groups.groupsTable.groupTableHeader": "グループ", "xpack.enterpriseSearch.workplaceSearch.groups.groupsTable.sourcesTableHeader": "コンテンツソース", - "xpack.enterpriseSearch.workplaceSearch.groups.groupsTable.usersTableHeader": "ユーザー", "xpack.enterpriseSearch.workplaceSearch.groups.groupUpdatedText": "前回更新日時{updatedAt}。", "xpack.enterpriseSearch.workplaceSearch.groups.heading": "グループを管理", "xpack.enterpriseSearch.workplaceSearch.groups.inviteUsers.action": "ユーザーを招待", @@ -8118,7 +8117,6 @@ "xpack.enterpriseSearch.workplaceSearch.groups.overview.confirmRemoveDescription": "グループはWorkplace Searchから削除されます。{name}を削除してよろしいですか?", "xpack.enterpriseSearch.workplaceSearch.groups.overview.confirmTitleText": "確認", "xpack.enterpriseSearch.workplaceSearch.groups.overview.emptySourcesDescription": "コンテンツソースはこのグループと共有されていません。", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.emptyUsersDescription": "このグループにはユーザーがありません。", "xpack.enterpriseSearch.workplaceSearch.groups.overview.groupSourcesDescription": "「{name}」グループのすべてのユーザーによって検索可能です。", "xpack.enterpriseSearch.workplaceSearch.groups.overview.groupSourcesTitle": "グループコンテンツソース", "xpack.enterpriseSearch.workplaceSearch.groups.overview.groupUsersDescription": "メンバーはグループのソースを検索できます。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 9704070feb8ab..ac43983a75ccf 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -8173,7 +8173,6 @@ "xpack.enterpriseSearch.workplaceSearch.groups.groupSourcesUpdated": "已成功更新共享内容源。", "xpack.enterpriseSearch.workplaceSearch.groups.groupsTable.groupTableHeader": "组", "xpack.enterpriseSearch.workplaceSearch.groups.groupsTable.sourcesTableHeader": "内容源", - "xpack.enterpriseSearch.workplaceSearch.groups.groupsTable.usersTableHeader": "用户", "xpack.enterpriseSearch.workplaceSearch.groups.groupUpdatedText": "上次更新于 {updatedAt}。", "xpack.enterpriseSearch.workplaceSearch.groups.heading": "管理组", "xpack.enterpriseSearch.workplaceSearch.groups.inviteUsers.action": "邀请用户", @@ -8186,7 +8185,6 @@ "xpack.enterpriseSearch.workplaceSearch.groups.overview.confirmRemoveDescription": "您的组将从 Workplace Search 中删除。确定要移除 {name}?", "xpack.enterpriseSearch.workplaceSearch.groups.overview.confirmTitleText": "确认", "xpack.enterpriseSearch.workplaceSearch.groups.overview.emptySourcesDescription": "未与此组共享任何内容源。", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.emptyUsersDescription": "此组中没有用户。", "xpack.enterpriseSearch.workplaceSearch.groups.overview.groupSourcesDescription": "可按“{name}”组中的所有用户搜索。", "xpack.enterpriseSearch.workplaceSearch.groups.overview.groupSourcesTitle": "组内容源", "xpack.enterpriseSearch.workplaceSearch.groups.overview.groupUsersDescription": "成员将可以对该组的源进行搜索。", From f3228a38f41e7f946883030f2a718637ffd3c514 Mon Sep 17 00:00:00 2001 From: Spencer Date: Fri, 9 Jul 2021 13:26:26 -0700 Subject: [PATCH 35/49] [kbn/client/ui-settings] support using uiSettings in a specific space (#105116) Co-authored-by: spalger --- .../kbn_client/kbn_client_requester.test.ts | 35 +++++++++++++++++++ .../src/kbn_client/kbn_client_requester.ts | 13 +++++++ .../src/kbn_client/kbn_client_ui_settings.ts | 25 +++++++------ 3 files changed, 62 insertions(+), 11 deletions(-) create mode 100644 packages/kbn-test/src/kbn_client/kbn_client_requester.test.ts diff --git a/packages/kbn-test/src/kbn_client/kbn_client_requester.test.ts b/packages/kbn-test/src/kbn_client/kbn_client_requester.test.ts new file mode 100644 index 0000000000000..bb2f923ad1f01 --- /dev/null +++ b/packages/kbn-test/src/kbn_client/kbn_client_requester.test.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { pathWithSpace } from './kbn_client_requester'; + +describe('pathWithSpace()', () => { + it('adds a space to the path', () => { + expect(pathWithSpace('hello')`/foo/bar`).toMatchInlineSnapshot(`"/s/hello/foo/bar"`); + }); + + it('ignores the space when it is empty', () => { + expect(pathWithSpace(undefined)`/foo/bar`).toMatchInlineSnapshot(`"/foo/bar"`); + expect(pathWithSpace('')`/foo/bar`).toMatchInlineSnapshot(`"/foo/bar"`); + }); + + it('ignores the space when it is the default space', () => { + expect(pathWithSpace('default')`/foo/bar`).toMatchInlineSnapshot(`"/foo/bar"`); + }); + + it('uriencodes variables in the path', () => { + expect(pathWithSpace('space')`hello/${'funky/username🏴‍☠️'}`).toMatchInlineSnapshot( + `"/s/space/hello/funky%2Fusername%F0%9F%8F%B4%E2%80%8D%E2%98%A0%EF%B8%8F"` + ); + }); + + it('ensures the path always starts with a slash', () => { + expect(pathWithSpace('foo')`hello/world`).toMatchInlineSnapshot(`"/s/foo/hello/world"`); + expect(pathWithSpace()`hello/world`).toMatchInlineSnapshot(`"/hello/world"`); + }); +}); diff --git a/packages/kbn-test/src/kbn_client/kbn_client_requester.ts b/packages/kbn-test/src/kbn_client/kbn_client_requester.ts index a194b593b3863..c2e4247df1ab0 100644 --- a/packages/kbn-test/src/kbn_client/kbn_client_requester.ts +++ b/packages/kbn-test/src/kbn_client/kbn_client_requester.ts @@ -23,6 +23,19 @@ const isIgnorableError = (error: any, ignorableErrors: number[] = []) => { return isAxiosResponseError(error) && ignorableErrors.includes(error.response.status); }; +/** + * Creates a template literal tag which will uriencode the variables in a template literal + * as well as prefix the path with a specific space if one is defined + */ +export const pathWithSpace = (space?: string) => { + const prefix = !space || space === 'default' ? '' : uriencode`/s/${space}`; + + return (strings: TemplateStringsArray, ...args: Array) => { + const path = uriencode(strings, ...args); + return path.startsWith('/') || path === '' ? `${prefix}${path}` : `${prefix}/${path}`; + }; +}; + export const uriencode = ( strings: TemplateStringsArray, ...values: Array diff --git a/packages/kbn-test/src/kbn_client/kbn_client_ui_settings.ts b/packages/kbn-test/src/kbn_client/kbn_client_ui_settings.ts index 78155098ef038..7ea685667d48b 100644 --- a/packages/kbn-test/src/kbn_client/kbn_client_ui_settings.ts +++ b/packages/kbn-test/src/kbn_client/kbn_client_ui_settings.ts @@ -8,7 +8,7 @@ import { ToolingLog } from '@kbn/dev-utils'; -import { KbnClientRequester, uriencode } from './kbn_client_requester'; +import { KbnClientRequester, pathWithSpace } from './kbn_client_requester'; export type UiSettingValues = Record; interface UiSettingsApiResponse { @@ -27,8 +27,8 @@ export class KbnClientUiSettings { private readonly defaults?: UiSettingValues ) {} - async get(setting: string) { - const all = await this.getAll(); + async get(setting: string, { space }: { space?: string } = {}) { + const all = await this.getAll({ space }); const value = all[setting]?.userValue; this.log.verbose('uiSettings.value: %j', value); @@ -45,9 +45,9 @@ export class KbnClientUiSettings { /** * Unset a uiSetting */ - async unset(setting: string) { + async unset(setting: string, { space }: { space?: string } = {}) { const { data } = await this.requester.request({ - path: uriencode`/api/kibana/settings/${setting}`, + path: pathWithSpace(space)`/api/kibana/settings/${setting}`, method: 'DELETE', }); return data; @@ -57,7 +57,10 @@ export class KbnClientUiSettings { * Replace all uiSettings with the `doc` values, `doc` is merged * with some defaults */ - async replace(doc: UiSettingValues, { retries = 5 }: { retries?: number } = {}) { + async replace( + doc: UiSettingValues, + { retries = 5, space }: { retries?: number; space?: string } = {} + ) { this.log.debug('replacing kibana config doc: %j', doc); const changes: Record = { @@ -73,7 +76,7 @@ export class KbnClientUiSettings { await this.requester.request({ method: 'POST', - path: '/api/kibana/settings', + path: pathWithSpace(space)`/api/kibana/settings`, body: { changes }, retries, }); @@ -82,11 +85,11 @@ export class KbnClientUiSettings { /** * Add fields to the config doc (like setting timezone and defaultIndex) */ - async update(updates: UiSettingValues) { + async update(updates: UiSettingValues, { space }: { space?: string } = {}) { this.log.debug('applying update to kibana config: %j', updates); await this.requester.request({ - path: '/api/kibana/settings', + path: pathWithSpace(space)`/api/kibana/settings`, method: 'POST', body: { changes: updates, @@ -95,9 +98,9 @@ export class KbnClientUiSettings { }); } - private async getAll() { + private async getAll({ space }: { space?: string } = {}) { const { data } = await this.requester.request({ - path: '/api/kibana/settings', + path: pathWithSpace(space)`/api/kibana/settings`, method: 'GET', }); From 36bf7f71208d1c03eb4f06fa61aa75f0095fc298 Mon Sep 17 00:00:00 2001 From: Aaron Caldwell Date: Fri, 9 Jul 2021 14:28:02 -0600 Subject: [PATCH 36/49] [Maps] Fix tracking threshold alerts improper handling of elasticsearch epoch millis strings (#105010) --- .../alert_types/geo_containment/es_query_builder.ts | 9 ++++++++- .../alert_types/geo_containment/geo_containment.ts | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/es_query_builder.ts b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/es_query_builder.ts index 1e26ea09618d5..37e0a293b03a0 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/es_query_builder.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/es_query_builder.ts @@ -151,7 +151,14 @@ export async function executeEsQueryFactory( }, }, ], - docvalue_fields: [entity, dateField, geoField], + docvalue_fields: [ + entity, + { + field: dateField, + format: 'strict_date_optional_time', + }, + geoField, + ], _source: false, }, }, diff --git a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/geo_containment.ts b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/geo_containment.ts index 754af920b009e..21a536dd474ba 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/geo_containment.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/geo_containment.ts @@ -103,7 +103,7 @@ export function getActiveEntriesAndGenerateAlerts( locationsArr.forEach(({ location, shapeLocationId, dateInShape, docId }) => { const context = { entityId: entityName, - entityDateTime: dateInShape ? new Date(dateInShape).toISOString() : null, + entityDateTime: dateInShape || null, entityDocumentId: docId, detectionDateTime: new Date(currIntervalEndTime).toISOString(), entityLocation: `POINT (${location[0]} ${location[1]})`, From f7b87a5f65da7ed8538a962ccc8f2ca48aa18799 Mon Sep 17 00:00:00 2001 From: Quynh Nguyen <43350163+qn895@users.noreply.github.com> Date: Fri, 9 Jul 2021 15:33:20 -0500 Subject: [PATCH 37/49] [ML] Fix Single Metric Viewer & Explorer annotation table actions overflow and annotations count not matching (#104955) * Fix annotations * Fix translations * Fix onclick open * Fix label/aggregations mismatch * Fix title --- .../annotations_table/annotations_table.js | 136 ++++++++---------- .../public/application/explorer/explorer.js | 36 +++-- .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 4 files changed, 89 insertions(+), 85 deletions(-) diff --git a/x-pack/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js b/x-pack/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js index ed603357206ad..39d4dd1a71dd9 100644 --- a/x-pack/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js +++ b/x-pack/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js @@ -18,13 +18,13 @@ import React, { Component, Fragment, useContext } from 'react'; import memoizeOne from 'memoize-one'; import { EuiBadge, - EuiButtonEmpty, EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiInMemoryTable, EuiLink, EuiLoadingSpinner, + EuiToolTip, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -52,6 +52,19 @@ import { timeFormatter } from '../../../../../common/util/date_utils'; import { MlAnnotationUpdatesContext } from '../../../contexts/ml/ml_annotation_updates_context'; import { DatafeedChartFlyout } from '../../../jobs/jobs_list/components/datafeed_chart_flyout'; +const editAnnotationsText = ( + +); +const viewDataFeedText = ( + +); + const CURRENT_SERIES = 'current_series'; /** * Table component for rendering the lists of annotations for an ML job. @@ -463,82 +476,67 @@ class AnnotationsTableUI extends Component { const actions = []; actions.push({ - render: (annotation) => { - // find the original annotation because the table might not show everything + name: editAnnotationsText, + description: editAnnotationsText, + icon: 'pencil', + type: 'icon', + onClick: (annotation) => { const annotationId = annotation._id; const originalAnnotation = annotations.find((d) => d._id === annotationId); - const editAnnotationsText = ( - - ); - const editAnnotationsAriaLabelText = i18n.translate( - 'xpack.ml.annotationsTable.editAnnotationsTooltipAriaLabel', - { defaultMessage: 'Edit annotation' } - ); - return ( - annotationUpdatesService.setValue(originalAnnotation ?? annotation)} - > - {editAnnotationsText} - - ); + + annotationUpdatesService.setValue(originalAnnotation ?? annotation); }, }); if (this.state.jobId && this.props.jobs[0].analysis_config.bucket_span) { // add datafeed modal action actions.push({ - render: (annotation) => { - const viewDataFeedText = ( - - ); - const viewDataFeedTooltipAriaLabelText = i18n.translate( - 'xpack.ml.annotationsTable.datafeedChartTooltipAriaLabel', - { defaultMessage: 'Datafeed chart' } - ); - return ( - - this.setState({ - datafeedFlyoutVisible: true, - datafeedEnd: annotation.end_timestamp, - }) - } - > - {viewDataFeedText} - - ); + name: viewDataFeedText, + description: viewDataFeedText, + icon: 'visAreaStacked', + type: 'icon', + onClick: (annotation) => { + this.setState({ + datafeedFlyoutVisible: true, + datafeedEnd: annotation.end_timestamp, + }); }, }); } if (isSingleMetricViewerLinkVisible) { actions.push({ - render: (annotation) => { + name: (annotation) => { const isDrillDownAvailable = isTimeSeriesViewJob(this.getJob(annotation.job_id)); - const openInSingleMetricViewerTooltipText = isDrillDownAvailable ? ( - - ) : ( - + + if (isDrillDownAvailable) { + return ( + + ); + } + return ( + + } + > + + ); - const openInSingleMetricViewerAriaLabelText = isDrillDownAvailable + }, + description: (annotation) => { + const isDrillDownAvailable = isTimeSeriesViewJob(this.getJob(annotation.job_id)); + + return isDrillDownAvailable ? i18n.translate('xpack.ml.annotationsTable.openInSingleMetricViewerAriaLabel', { defaultMessage: 'Open in Single Metric Viewer', }) @@ -546,19 +544,11 @@ class AnnotationsTableUI extends Component { 'xpack.ml.annotationsTable.jobConfigurationNotSupportedInSingleMetricViewerAriaLabel', { defaultMessage: 'Job configuration not supported in Single Metric Viewer' } ); - - return ( - this.openSingleMetricView(annotation)} - > - {openInSingleMetricViewerTooltipText} - - ); }, + enabled: (annotation) => isTimeSeriesViewJob(this.getJob(annotation.job_id)), + icon: 'visLine', + type: 'icon', + onClick: (annotation) => this.openSingleMetricView(annotation), }); } diff --git a/x-pack/plugins/ml/public/application/explorer/explorer.js b/x-pack/plugins/ml/public/application/explorer/explorer.js index a11a42b9b65b2..31058b62af7fe 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer.js @@ -261,6 +261,30 @@ export class ExplorerUI extends React.Component { } = this.props.explorerState; const { annotationsData, aggregations, error: annotationsError } = annotations; + const annotationsCnt = Array.isArray(annotationsData) ? annotationsData.length : 0; + const allAnnotationsCnt = Array.isArray(aggregations?.event?.buckets) + ? aggregations.event.buckets.reduce((acc, v) => acc + v.doc_count, 0) + : annotationsCnt; + + const badge = + allAnnotationsCnt > annotationsCnt ? ( + + + + ) : ( + + + + ); + const jobSelectorProps = { dateFormatTz: getDateFormatTz(), }; @@ -404,7 +428,7 @@ export class ExplorerUI extends React.Component { {loading === false && tableData.anomalies?.length ? ( ) : null} - {annotationsData.length > 0 && ( + {annotationsCnt > 0 && ( <> - - - ), + badge, }} /> diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index b7cde09da6319..387547295f047 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -13639,7 +13639,6 @@ "xpack.ml.annotationsTable.byColumnSMVName": "グループ基準", "xpack.ml.annotationsTable.detectorColumnName": "検知器", "xpack.ml.annotationsTable.editAnnotationsTooltip": "注釈を編集します", - "xpack.ml.annotationsTable.editAnnotationsTooltipAriaLabel": "注釈を編集します", "xpack.ml.annotationsTable.eventColumnName": "イベント", "xpack.ml.annotationsTable.fromColumnName": "開始:", "xpack.ml.annotationsTable.howToCreateAnnotationDescription": "注釈を作成するには、{linkToSingleMetricView} を開きます", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index ac43983a75ccf..d142969022c81 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -13818,7 +13818,6 @@ "xpack.ml.annotationsTable.byColumnSMVName": "依据", "xpack.ml.annotationsTable.detectorColumnName": "检测工具", "xpack.ml.annotationsTable.editAnnotationsTooltip": "编辑注释", - "xpack.ml.annotationsTable.editAnnotationsTooltipAriaLabel": "编辑注释", "xpack.ml.annotationsTable.eventColumnName": "事件", "xpack.ml.annotationsTable.fromColumnName": "自", "xpack.ml.annotationsTable.howToCreateAnnotationDescription": "要创建注释,请打开 {linkToSingleMetricView}", From ce48b73dc8f215eb7761f896b1bdaa4d584e5121 Mon Sep 17 00:00:00 2001 From: spalger Date: Fri, 9 Jul 2021 13:59:56 -0700 Subject: [PATCH 38/49] skip suites failing es promotion (#104466) --- test/functional/apps/discover/_data_grid_field_data.ts | 3 ++- test/functional/apps/discover/_field_data.ts | 7 ++++--- .../apps/discover/_field_data_with_fields_api.ts | 3 ++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/test/functional/apps/discover/_data_grid_field_data.ts b/test/functional/apps/discover/_data_grid_field_data.ts index 94e8e942f86ba..cf8a6fc1bd60f 100644 --- a/test/functional/apps/discover/_data_grid_field_data.ts +++ b/test/functional/apps/discover/_data_grid_field_data.ts @@ -19,7 +19,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const defaultSettings = { defaultIndex: 'logstash-*', 'doc_table:legacy': false }; const dataGrid = getService('dataGrid'); - describe('discover data grid field data tests', function describeIndexTests() { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/104466 + describe.skip('discover data grid field data tests', function describeIndexTests() { this.tags('includeFirefox'); before(async function () { await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); diff --git a/test/functional/apps/discover/_field_data.ts b/test/functional/apps/discover/_field_data.ts index 5ab6495686726..6471b751945a8 100644 --- a/test/functional/apps/discover/_field_data.ts +++ b/test/functional/apps/discover/_field_data.ts @@ -20,7 +20,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['common', 'header', 'discover', 'visualize', 'timePicker']); const find = getService('find'); - describe('discover tab', function describeIndexTests() { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/104466 + describe.skip('discover tab', function describeIndexTests() { this.tags('includeFirefox'); before(async function () { await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); @@ -33,8 +34,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); await PageObjects.common.navigateToApp('discover'); }); - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/104466 - describe.skip('field data', function () { + + describe('field data', function () { it('search php should show the correct hit count', async function () { const expectedHitCount = '445'; await retry.try(async function () { diff --git a/test/functional/apps/discover/_field_data_with_fields_api.ts b/test/functional/apps/discover/_field_data_with_fields_api.ts index 110e255d18c75..7c6867e935063 100644 --- a/test/functional/apps/discover/_field_data_with_fields_api.ts +++ b/test/functional/apps/discover/_field_data_with_fields_api.ts @@ -20,7 +20,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['common', 'header', 'discover', 'visualize', 'timePicker']); const find = getService('find'); - describe('discover tab with new fields API', function describeIndexTests() { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/104466 + describe.skip('discover tab with new fields API', function describeIndexTests() { this.tags('includeFirefox'); before(async function () { await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); From fdd37e3cfb0116772c48fc45c75e8fe61cd55579 Mon Sep 17 00:00:00 2001 From: Jason Stoltzfus Date: Fri, 9 Jul 2021 17:14:37 -0400 Subject: [PATCH 39/49] Fixed description on Ent Home (#105122) --- .../product_selector/product_selector.tsx | 10 ++++++---- .../applications/enterprise_search/index.scss | 14 -------------- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/product_selector.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/product_selector.tsx index a7eb2424e797a..0dd2b0988b3f4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/product_selector.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/product_selector.tsx @@ -19,6 +19,7 @@ import { EuiFlexItem, EuiSpacer, EuiTitle, + EuiText, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -59,14 +60,15 @@ export const ProductSelector: React.FC = ({ access }) => { -

+

{i18n.translate('xpack.enterpriseSearch.overview.heading', { defaultMessage: 'Welcome to Elastic Enterprise Search', })}

- -

+ + +

{config.host ? i18n.translate('xpack.enterpriseSearch.overview.subheading', { defaultMessage: 'Select a product to get started.', @@ -75,7 +77,7 @@ export const ProductSelector: React.FC = ({ access }) => { defaultMessage: 'Choose a product to set up and get started.', })}

-
+
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.scss b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.scss index 45bf37def1121..4be8d7322b4c8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.scss +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.scss @@ -21,21 +21,7 @@ &__header { text-align: center; margin: auto; - } - - &__heading { - @include euiBreakpoint('xs', 's') { - font-size: $euiFontSizeXL; - line-height: map-get(map-get($euiTitles, 'm'), 'line-height'); - } - } - - &__subheading { - color: $euiColorMediumShade; - font-size: $euiFontSize; - @include euiBreakpoint('m', 'l', 'xl') { - font-size: $euiFontSizeL; margin-bottom: $euiSizeL; } } From b7f50c279bf9e348096f2d7b9e289ec1acb5ae47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ece=20=C3=96zalp?= Date: Fri, 9 Jul 2021 17:17:48 -0400 Subject: [PATCH 40/49] [CTI] updates overview cti panel copy (#105074) --- .../overview/components/overview_cti_links/translations.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/translations.ts b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/translations.ts index 663ec3a75c902..91abd48eb2b7e 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/translations.ts +++ b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/translations.ts @@ -51,9 +51,10 @@ export const DANGER_TITLE = i18n.translate( ); export const DANGER_BODY = i18n.translate( - 'xpack.securitySolution.overview.ctiDashboardDangerPanelBody', + 'xpack.securitySolution.overview.ctiDashboardEnableThreatIntel', { - defaultMessage: 'You need to enable module in order to view data from different sources.', + defaultMessage: + 'You need to enable the filebeat threatintel module in order to view data from different sources.', } ); From bcc8ee2532133c39e663f73303187a2345ef24a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ece=20=C3=96zalp?= Date: Fri, 9 Jul 2021 17:28:15 -0400 Subject: [PATCH 41/49] [CTI] makes dashboard links external (#104979) * [CTI] makes dashboard links external --- .../cti_disabled_module.tsx | 2 +- .../threat_intel_panel_view.tsx | 28 ++++++++++--------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_disabled_module.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_disabled_module.tsx index e22fec1861f8b..21a4beca72f3b 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_disabled_module.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_disabled_module.tsx @@ -25,7 +25,7 @@ export const CtiDisabledModuleComponent = () => { title={i18n.DANGER_TITLE} body={i18n.DANGER_BODY} button={ - + {i18n.DANGER_BUTTON} } diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/threat_intel_panel_view.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/threat_intel_panel_view.tsx index 4565c16bc2bf6..b34f6e657d39a 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/threat_intel_panel_view.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/threat_intel_panel_view.tsx @@ -22,7 +22,6 @@ import { InspectButtonContainer } from '../../../common/components/inspect'; import { HeaderSection } from '../../../common/components/header_section'; import { ID as CTIEventCountQueryId } from '../../containers/overview_cti_links/use_cti_event_counts'; import { CtiListItem } from '../../containers/overview_cti_links/helpers'; -import { LinkButton } from '../../../common/components/links'; import { useKibana } from '../../../common/lib/kibana'; import { CtiInnerPanel } from './cti_inner_panel'; import * as i18n from './translations'; @@ -36,7 +35,7 @@ const DashboardLinkItems = styled(EuiFlexGroup)` `; const Title = styled(EuiFlexItem)` - min-width: 140px; + min-width: 110px; `; const List = styled.ul` @@ -45,12 +44,11 @@ const List = styled.ul` const DashboardRightSideElement = styled(EuiFlexItem)` align-items: flex-end; - max-width: 160px; `; const RightSideLink = styled(EuiLink)` text-align: right; - min-width: 140px; + min-width: 180px; `; interface ThreatIntelPanelViewProps { @@ -96,12 +94,12 @@ export const ThreatIntelPanelView: React.FC = ({ const button = useMemo( () => ( - + - + ), [buttonHref] ); @@ -117,7 +115,11 @@ export const ThreatIntelPanelView: React.FC = ({ color={'primary'} title={i18n.INFO_TITLE} body={i18n.INFO_BODY} - button={{i18n.INFO_BUTTON}} + button={ + + {i18n.INFO_BUTTON} + + } /> ) : null, [isDashboardPluginDisabled, threatIntelDashboardDocLink] @@ -149,9 +151,7 @@ export const ThreatIntelPanelView: React.FC = ({ gutterSize="l" justifyContent="spaceBetween" > - - {title} - + {title} = ({ alignItems="center" justifyContent="flexEnd" > - + {count} - + {path ? ( - {linkCopy} + + {linkCopy} + ) : ( {linkCopy} From 1739b55f94df04b4b99fbc11233cc16d5cef863b Mon Sep 17 00:00:00 2001 From: Constance Date: Fri, 9 Jul 2021 14:31:01 -0700 Subject: [PATCH 42/49] [Enterprise Search] Fix Error Connecting view not displaying for auth issues (#105125) * Fix ent-search authentication to show the error connecting screen Missed this in #103555 * [Misc] updoot handleConnectionError order/spacing to match - why? because i've lost control of my life, probably --- .../server/lib/enterprise_search_request_handler.test.ts | 2 +- .../server/lib/enterprise_search_request_handler.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.test.ts b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.test.ts index d0e74f3234c14..f9756119b336c 100644 --- a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.test.ts @@ -403,7 +403,7 @@ describe('EnterpriseSearchRequestHandler', () => { expect(responseMock.customError).toHaveBeenCalledWith({ statusCode: 502, body: 'Cannot authenticate Enterprise Search user', - headers: mockExpectedResponseHeaders, + headers: { ...mockExpectedResponseHeaders, [ERROR_CONNECTING_HEADER]: 'true' }, }); expect(mockLogger.error).toHaveBeenCalled(); }); diff --git a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.ts b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.ts index 8031fc724f7b3..57b91c2b30c73 100644 --- a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.ts +++ b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.ts @@ -283,12 +283,11 @@ export class EnterpriseSearchRequestHandler { handleConnectionError(response: KibanaResponseFactory, e: Error) { const errorMessage = `Error connecting to Enterprise Search: ${e?.message || e.toString()}`; + const headers = { ...this.headers, [ERROR_CONNECTING_HEADER]: 'true' }; this.log.error(errorMessage); if (e instanceof Error) this.log.debug(e.stack as string); - const headers = { ...this.headers, [ERROR_CONNECTING_HEADER]: 'true' }; - return response.customError({ statusCode: 502, headers, body: errorMessage }); } @@ -298,9 +297,10 @@ export class EnterpriseSearchRequestHandler { */ handleAuthenticationError(response: KibanaResponseFactory) { const errorMessage = 'Cannot authenticate Enterprise Search user'; + const headers = { ...this.headers, [ERROR_CONNECTING_HEADER]: 'true' }; this.log.error(errorMessage); - return response.customError({ statusCode: 502, headers: this.headers, body: errorMessage }); + return response.customError({ statusCode: 502, headers, body: errorMessage }); } /** From a05853ab2ad26e5f8fe8654a138c805ba45422be Mon Sep 17 00:00:00 2001 From: Oliver Gupte Date: Fri, 9 Jul 2021 17:52:17 -0400 Subject: [PATCH 43/49] [APM] Support for data streams index patterns for cloud migration (#105015) * [APM] Support for data streams index patterns for cloud migration (#101095) * [APM] Update apm package version to 0.3.0 (elastic/apm-server/#5579) --- x-pack/plugins/apm/server/index.test.ts | 2 +- x-pack/plugins/apm/server/index.ts | 6 ++- .../get_apm_package_policy_definition.ts | 2 +- .../create_static_index_pattern.ts | 5 ++- x-pack/plugins/apm/server/routes/fleet.ts | 41 ++++++++++++++----- 5 files changed, 41 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/apm/server/index.test.ts b/x-pack/plugins/apm/server/index.test.ts index 6052ec921f9f9..56c9825db5a5c 100644 --- a/x-pack/plugins/apm/server/index.test.ts +++ b/x-pack/plugins/apm/server/index.test.ts @@ -30,7 +30,7 @@ describe('mergeConfigs', () => { expect(mergeConfigs(apmOssConfig, apmConfig)).toEqual({ 'apm_oss.errorIndices': 'logs-apm*,apm-*-error-*', - 'apm_oss.indexPattern': 'apm-*', + 'apm_oss.indexPattern': 'traces-apm*,logs-apm*,metrics-apm*,apm-*', 'apm_oss.metricsIndices': 'metrics-apm*,apm-*-metric-*', 'apm_oss.spanIndices': 'traces-apm*,apm-*-span-*', 'apm_oss.transactionIndices': 'traces-apm*,apm-*-transaction-*', diff --git a/x-pack/plugins/apm/server/index.ts b/x-pack/plugins/apm/server/index.ts index f14894a76edb4..9031f454f4a7f 100644 --- a/x-pack/plugins/apm/server/index.ts +++ b/x-pack/plugins/apm/server/index.ts @@ -74,7 +74,7 @@ export function mergeConfigs( 'apm_oss.metricsIndices': apmOssConfig.metricsIndices, 'apm_oss.sourcemapIndices': apmOssConfig.sourcemapIndices, 'apm_oss.onboardingIndices': apmOssConfig.onboardingIndices, - 'apm_oss.indexPattern': apmOssConfig.indexPattern, // TODO: add data stream indices: traces-apm*,logs-apm*,metrics-apm*. Blocked by https://github.com/elastic/kibana/issues/87851 + 'apm_oss.indexPattern': apmOssConfig.indexPattern, /* eslint-enable @typescript-eslint/naming-convention */ 'xpack.apm.serviceMapEnabled': apmConfig.serviceMapEnabled, 'xpack.apm.serviceMapFingerprintBucketSize': @@ -119,6 +119,10 @@ export function mergeConfigs( 'apm_oss.metricsIndices' ] = `metrics-apm*,${mergedConfig['apm_oss.metricsIndices']}`; + mergedConfig[ + 'apm_oss.indexPattern' + ] = `traces-apm*,logs-apm*,metrics-apm*,${mergedConfig['apm_oss.indexPattern']}`; + return mergedConfig; } diff --git a/x-pack/plugins/apm/server/lib/fleet/get_apm_package_policy_definition.ts b/x-pack/plugins/apm/server/lib/fleet/get_apm_package_policy_definition.ts index 82e85e7da9bb3..291b2fa2af99d 100644 --- a/x-pack/plugins/apm/server/lib/fleet/get_apm_package_policy_definition.ts +++ b/x-pack/plugins/apm/server/lib/fleet/get_apm_package_policy_definition.ts @@ -34,7 +34,7 @@ export function getApmPackagePolicyDefinition( ], package: { name: APM_PACKAGE_NAME, - version: '0.3.0-dev.1', + version: '0.3.0', title: 'Elastic APM', }, }; diff --git a/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts b/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts index 607a7e6227a9d..a2944d6241d2d 100644 --- a/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts +++ b/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts @@ -19,7 +19,8 @@ export async function createStaticIndexPattern( setup: Setup, config: APMRouteHandlerResources['config'], savedObjectsClient: InternalSavedObjectsClient, - spaceId: string | undefined + spaceId: string | undefined, + overwrite = false ): Promise { return withApmSpan('create_static_index_pattern', async () => { // don't autocreate APM index pattern if it's been disabled via the config @@ -45,7 +46,7 @@ export async function createStaticIndexPattern( }, { id: APM_STATIC_INDEX_PATTERN_ID, - overwrite: false, + overwrite, namespace: spaceId, } ) diff --git a/x-pack/plugins/apm/server/routes/fleet.ts b/x-pack/plugins/apm/server/routes/fleet.ts index 6628d29b256f7..b760014d6af89 100644 --- a/x-pack/plugins/apm/server/routes/fleet.ts +++ b/x-pack/plugins/apm/server/routes/fleet.ts @@ -25,6 +25,8 @@ import { createCloudApmPackgePolicy } from '../lib/fleet/create_cloud_apm_packag import { getUnsupportedApmServerSchema } from '../lib/fleet/get_unsupported_apm_server_schema'; import { isSuperuser } from '../lib/fleet/is_superuser'; import { getInternalSavedObjectsClient } from '../lib/helpers/get_internal_saved_objects_client'; +import { setupRequest } from '../lib/helpers/setup_request'; +import { createStaticIndexPattern } from '../lib/index_pattern/create_static_index_pattern'; const hasFleetDataRoute = createApmServerRoute({ endpoint: 'GET /api/apm/fleet/has_data', @@ -154,7 +156,7 @@ const createCloudApmPackagePolicyRoute = createApmServerRoute({ endpoint: 'POST /api/apm/fleet/cloud_apm_package_policy', options: { tags: ['access:apm', 'access:apm_write'] }, handler: async (resources) => { - const { plugins, context, config, request, logger } = resources; + const { plugins, context, config, request, logger, core } = resources; const cloudApmMigrationEnabled = config['xpack.apm.agent.migrations.enabled']; if (!plugins.fleet || !plugins.security) { @@ -171,15 +173,34 @@ const createCloudApmPackagePolicyRoute = createApmServerRoute({ if (!hasRequiredRole || !cloudApmMigrationEnabled) { throw Boom.forbidden(CLOUD_SUPERUSER_REQUIRED_MESSAGE); } - return { - cloud_apm_package_policy: await createCloudApmPackgePolicy({ - cloudPluginSetup, - fleetPluginStart, - savedObjectsClient, - esClient, - logger, - }), - }; + + const cloudApmAackagePolicy = await createCloudApmPackgePolicy({ + cloudPluginSetup, + fleetPluginStart, + savedObjectsClient, + esClient, + logger, + }); + + const [setup, internalSavedObjectsClient] = await Promise.all([ + setupRequest(resources), + core + .start() + .then(({ savedObjects }) => savedObjects.createInternalRepository()), + ]); + + const spaceId = plugins.spaces?.setup.spacesService.getSpaceId(request); + + // force update the index pattern title with data streams + await createStaticIndexPattern( + setup, + config, + internalSavedObjectsClient, + spaceId, + true + ); + + return { cloud_apm_package_policy: cloudApmAackagePolicy }; }, }); From 937a4f381a592f605b1edc6a898de02e4831aadf Mon Sep 17 00:00:00 2001 From: Ryland Herrick Date: Fri, 9 Jul 2021 17:20:10 -0500 Subject: [PATCH 44/49] [Security Solution] Enrichment details UI cleanup (#104995) * Remove the "view threat intel data" button from the alert summary This can be accomplished by clicking the tab itself; there's no real need for this button. * Remove section title from alert summary view This made sense when we had both alert and threat sections, but we no longer do. Removes the corresponding translation, and the analogously unused title from the defunct threat summary view. * Smaller spacer on alert summary tab This is distractingly large as compared to other tabs. * Move "no enrichments" panel below our threat details table * Remove old import * Move inspect button inline with rest of header * Add HR separator to top of NoEnrichmentsPanel This should arguably be added a level above so as to keep this panel context-agnostic, but it's currently only used in one place and will always require the HR, so YAGNI for now. * Adds more space between title and description on "no data" panel It has been suggested that the NoEnrichmentsPanel should be following the guidelines of the EuiEmptyPrompt. If we end up needing e.g. centered text, we're better off rewriting NoEnrichmentsPanelView in terms of an EuiEmptyPrompt. * StyledEuiInMemoryTable has no header row height We have never provided column names to this component. However, there is default padding on the thead tds such that even without content they take up vertical height. This has resulted in some extra top-margin on historical uses of this table (which are just the Alert Details views). However, the addition of a sibling table (ThreatSummaryView) made the extra margin noticable, since it made the two tables appear disjointed even though they're right up against each other. This fixes the issue by removing the padding, allowing the thead to take no height. And now that that space isn't taken up by the table header, we need to add a little bit of space between the header and table on the Threat Details view. * Move test to appropriate location The ThreatDetailsView is no longer responsible for displaying the "no data" components, that's now a level above in EventDetails. * Prune unused translations These have been changed in the latest designs. * Only add HR if panel is preceded by enrichments We do not want an HR if there's nothing above the panel. --- .../event_details/alert_summary_view.tsx | 2 +- .../empty_threat_details_view.test.tsx | 48 ---------- .../cti_details/empty_threat_details_view.tsx | 51 ----------- .../cti_details/no_enrichments_panel.test.tsx | 57 ++++++++++++ .../cti_details/no_enrichments_panel.tsx | 88 +++++++++++++++++++ .../cti_details/threat_details_view.test.tsx | 9 -- .../cti_details/threat_details_view.tsx | 18 ++-- .../event_details/cti_details/translations.ts | 34 ++++++- .../event_details/event_details.test.tsx | 7 ++ .../event_details/event_details.tsx | 53 +++++------ .../components/event_details/summary_view.tsx | 7 +- .../components/event_details/translations.ts | 12 --- .../translations/translations/ja-JP.json | 3 - .../translations/translations/zh-CN.json | 3 - 14 files changed, 222 insertions(+), 170 deletions(-) delete mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/cti_details/empty_threat_details_view.test.tsx delete mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/cti_details/empty_threat_details_view.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/cti_details/no_enrichments_panel.test.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/cti_details/no_enrichments_panel.tsx diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx index 3a1a29b63eadf..329b8e32f057d 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx @@ -213,7 +213,7 @@ const AlertSummaryViewComponent: React.FC<{ return ( <> - + { - const mount = useMountAppended(); - const mockTheme = getMockTheme({ - eui: { - euiBreakpoints: { - l: '1200px', - }, - paddingSizes: { - m: '8px', - xl: '32px', - }, - }, - }); - - test('renders correct items', () => { - const wrapper = mount( - - - - ); - expect(wrapper.find('[data-test-subj="empty-threat-details-view"]').exists()).toEqual(true); - }); - - test('renders link to docs', () => { - const wrapper = mount( - - - - ); - expect(wrapper.find('a').exists()).toEqual(true); - }); -}); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/empty_threat_details_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/empty_threat_details_view.tsx deleted file mode 100644 index d7e1c4d7754ec..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/empty_threat_details_view.tsx +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { EuiLink, EuiSpacer, EuiTitle } from '@elastic/eui'; -import React from 'react'; -import styled from 'styled-components'; - -import { useKibana } from '../../../lib/kibana'; -import * as i18n from './translations'; - -const EmptyThreatDetailsViewContainer = styled.div` - display: flex; - flex-direction: column; - align-items: center; -`; - -const Span = styled.span` - color: ${({ theme }) => theme.eui.euiColorDarkShade}; - line-height: 1.8em; - text-align: center; - padding: ${({ theme }) => `${theme.eui.paddingSizes.m} ${theme.eui.paddingSizes.xl}`}; -`; - -const EmptyThreatDetailsViewComponent: React.FC<{}> = () => { - const threatIntelDocsUrl = `${ - useKibana().services.docLinks.links.filebeat.base - }/filebeat-module-threatintel.html`; - - return ( - - - -

{i18n.NO_ENRICHMENT_FOUND}

-
- - {i18n.IF_CTI_NOT_ENABLED} - - {i18n.CHECK_DOCS} - - -
- ); -}; - -EmptyThreatDetailsViewComponent.displayName = 'EmptyThreatDetailsView'; - -export const EmptyThreatDetailsView = React.memo(EmptyThreatDetailsViewComponent); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/no_enrichments_panel.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/no_enrichments_panel.test.tsx new file mode 100644 index 0000000000000..819c666bd7267 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/no_enrichments_panel.test.tsx @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { mount } from 'enzyme'; + +import { NoEnrichmentsPanel } from './no_enrichments_panel'; +import * as i18n from './translations'; + +jest.mock('../../../lib/kibana'); + +describe('NoEnrichmentsPanelView', () => { + it('renders a qualified container', () => { + const wrapper = mount( + + ); + expect(wrapper.find('[data-test-subj="no-enrichments-panel"]').exists()).toEqual(true); + }); + + it('renders nothing when all enrichments are present', () => { + const wrapper = mount( + + ); + expect(wrapper.find('[data-test-subj="no-enrichments-panel"]').exists()).toEqual(false); + }); + + it('renders expected text when no enrichments are present', () => { + const wrapper = mount( + + ); + expect(wrapper.find('[data-test-subj="no-enrichments-panel"]').hostNodes().text()).toContain( + i18n.NO_ENRICHMENTS_FOUND_TITLE + ); + }); + + it('renders expected text when existing enrichments are absent', () => { + const wrapper = mount( + + ); + expect(wrapper.find('[data-test-subj="no-enrichments-panel"]').hostNodes().text()).toContain( + i18n.NO_INDICATOR_ENRICHMENTS_TITLE + ); + }); + + it('renders expected text when investigation enrichments are absent', () => { + const wrapper = mount( + + ); + expect(wrapper.find('[data-test-subj="no-enrichments-panel"]').hostNodes().text()).toContain( + i18n.NO_INVESTIGATION_ENRICHMENTS_TITLE + ); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/no_enrichments_panel.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/no_enrichments_panel.tsx new file mode 100644 index 0000000000000..b521c3ba92c4d --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/no_enrichments_panel.tsx @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiHorizontalRule, EuiLink, EuiPanel, EuiSpacer, EuiText } from '@elastic/eui'; +import React from 'react'; +import styled from 'styled-components'; +import { useKibana } from '../../../lib/kibana'; + +import * as i18n from './translations'; + +const Container = styled(EuiPanel)` + display: flex; + flex-direction: column; +`; + +const NoEnrichmentsPanelView: React.FC<{ + title: React.ReactNode; + description: React.ReactNode; +}> = ({ title, description }) => { + return ( + + {title} + + + {description} + + + ); +}; + +NoEnrichmentsPanelView.displayName = 'NoEnrichmentsPanelView'; + +export const NoEnrichmentsPanel: React.FC<{ + existingEnrichmentsCount: number; + investigationEnrichmentsCount: number; +}> = ({ existingEnrichmentsCount, investigationEnrichmentsCount }) => { + const threatIntelDocsUrl = `${ + useKibana().services.docLinks.links.filebeat.base + }/filebeat-module-threatintel.html`; + const noIndicatorEnrichmentsDescription = ( + <> + {i18n.IF_CTI_NOT_ENABLED} + + {i18n.CHECK_DOCS} + + + ); + + if (existingEnrichmentsCount === 0 && investigationEnrichmentsCount === 0) { + return ( + {i18n.NO_ENRICHMENTS_FOUND_TITLE}} + description={ + <> +

{noIndicatorEnrichmentsDescription}

+

{i18n.NO_INVESTIGATION_ENRICHMENTS_DESCRIPTION}

+ + } + /> + ); + } else if (existingEnrichmentsCount === 0) { + return ( + <> + + {i18n.NO_INDICATOR_ENRICHMENTS_TITLE}} + description={noIndicatorEnrichmentsDescription} + /> + + ); + } else if (investigationEnrichmentsCount === 0) { + return ( + <> + + {i18n.NO_INVESTIGATION_ENRICHMENTS_TITLE}} + description={i18n.NO_INVESTIGATION_ENRICHMENTS_DESCRIPTION} + /> + + ); + } else { + return null; + } +}; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_details_view.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_details_view.test.tsx index 0113dde96a4b6..c25457a5e5e88 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_details_view.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_details_view.test.tsx @@ -31,15 +31,6 @@ describe('ThreatDetailsView', () => { ); }); - it('renders an empty view if there are no enrichments', () => { - const wrapper = mount( - - - - ); - expect(wrapper.find('[data-test-subj="empty-threat-details-view"]').exists()).toEqual(true); - }); - it('renders anchor links for event.url and event.reference', () => { const enrichments = [ buildEventEnrichmentMock({ diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_details_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_details_view.tsx index d5e985c5757a6..b6b8a47c1dd8c 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_details_view.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_details_view.tsx @@ -20,7 +20,6 @@ import React, { Fragment } from 'react'; import { StyledEuiInMemoryTable } from '../summary_view'; import { getSummaryColumns, SummaryRow, ThreatDetailsRow } from '../helpers'; -import { EmptyThreatDetailsView } from './empty_threat_details_view'; import { FIRSTSEEN, EVENT_URL, EVENT_REFERENCE } from '../../../../../common/cti/constants'; import { DEFAULT_INDICATOR_SOURCE_PATH } from '../../../../../common/constants'; import { getFirstElement } from '../../../../../common/utils/data_retrieval'; @@ -70,15 +69,13 @@ const ThreatDetailsHeader: React.FC<{ + {isInvestigationTimeEnrichment(type) && ( + + + + )}
- {isInvestigationTimeEnrichment(type) && ( - - - - - - )} ); @@ -131,10 +128,6 @@ const buildThreatDetailsItems = (enrichment: CtiEnrichment) => const ThreatDetailsViewComponent: React.FC<{ enrichments: CtiEnrichment[]; }> = ({ enrichments }) => { - if (enrichments.length < 1) { - return ; - } - const sortedEnrichments = enrichments.sort((a, b) => getFirstSeen(b) - getFirstSeen(a)); return ( @@ -146,6 +139,7 @@ const ThreatDetailsViewComponent: React.FC<{ return ( + { ).toEqual('Summary'); }); }); + + describe('threat intel tab', () => { + it('renders a "no enrichments" panel view if there are no enrichments', () => { + alertsWrapper.find('[data-test-subj="threatIntelTab"]').first().simulate('click'); + expect(alertsWrapper.find('[data-test-subj="no-enrichments-panel"]').exists()).toEqual(true); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx index 9afaaef61b17a..7074212dcdb4c 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx @@ -9,9 +9,6 @@ import { EuiTabbedContent, EuiTabbedContentTab, EuiSpacer, - EuiButton, - EuiFlexGroup, - EuiFlexItem, EuiLoadingContent, EuiLoadingSpinner, } from '@elastic/eui'; @@ -34,6 +31,7 @@ import { parseExistingEnrichments, timelineDataToEnrichment, } from './cti_details/helpers'; +import { NoEnrichmentsPanel } from './cti_details/no_enrichments_panel'; type EventViewTab = EuiTabbedContentTab; @@ -100,9 +98,6 @@ const EventDetailsComponent: React.FC = ({ (tab: EuiTabbedContentTab) => setSelectedTabId(tab.id as EventViewId), [setSelectedTabId] ); - const viewThreatIntelTab = useCallback(() => setSelectedTabId(EventsViewType.threatIntelView), [ - setSelectedTabId, - ]); const eventFields = useMemo(() => getEnrichmentFields(data), [data]); const existingEnrichments = useMemo( @@ -118,6 +113,9 @@ const EventDetailsComponent: React.FC = ({ loading: enrichmentsLoading, result: enrichmentsResponse, } = useInvestigationTimeEnrichment(eventFields); + const investigationEnrichments = useMemo(() => enrichmentsResponse?.enrichments ?? [], [ + enrichmentsResponse?.enrichments, + ]); const allEnrichments = useMemo(() => { if (enrichmentsLoading || !enrichmentsResponse?.enrichments) { return existingEnrichments; @@ -140,29 +138,20 @@ const EventDetailsComponent: React.FC = ({ eventId: id, browserFields, timelineId, - title: i18n.ALERT_SUMMARY, }} /> + {enrichmentCount > 0 && ( + + )} {enrichmentsLoading && ( <> )} - {enrichmentCount > 0 && ( - <> - - - - - {i18n.VIEW_CTI_DATA} - - - - )} ), } @@ -176,7 +165,6 @@ const EventDetailsComponent: React.FC = ({ enrichmentsLoading, enrichmentCount, allEnrichments, - viewThreatIntelTab, ] ); @@ -192,10 +180,25 @@ const EventDetailsComponent: React.FC = ({ {enrichmentsLoading ? : `(${enrichmentCount})`} ), - content: , + content: ( + <> + + + + ), } : undefined, - [allEnrichments, enrichmentCount, enrichmentsLoading, isAlert] + [ + allEnrichments, + enrichmentCount, + enrichmentsLoading, + existingEnrichments.length, + investigationEnrichments.length, + isAlert, + ] ); const tableTab = useMemo( diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx index 0e846f3f6f699..961860ed6d8b9 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx @@ -13,12 +13,13 @@ import { SummaryRow } from './helpers'; // eslint-disable-next-line @typescript-eslint/no-explicit-any export const StyledEuiInMemoryTable = styled(EuiInMemoryTable as any)` - .euiTableHeaderCell { - border: none; - } + .euiTableHeaderCell, .euiTableRowCell { border: none; } + .euiTableHeaderCell .euiTableCellContent { + padding: 0; + } `; const StyledEuiTitle = styled(EuiTitle)` diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/translations.ts b/x-pack/plugins/security_solution/public/common/components/event_details/translations.ts index a17ca5e434ace..c632f5d6332e0 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/translations.ts +++ b/x-pack/plugins/security_solution/public/common/components/event_details/translations.ts @@ -11,22 +11,10 @@ export const SUMMARY = i18n.translate('xpack.securitySolution.alertDetails.summa defaultMessage: 'Summary', }); -export const ALERT_SUMMARY = i18n.translate('xpack.securitySolution.alertDetails.alertSummary', { - defaultMessage: 'Alert Summary', -}); - export const THREAT_INTEL = i18n.translate('xpack.securitySolution.alertDetails.threatIntel', { defaultMessage: 'Threat Intel', }); -export const THREAT_SUMMARY = i18n.translate('xpack.securitySolution.alertDetails.threatSummary', { - defaultMessage: 'Threat Summary', -}); - -export const VIEW_CTI_DATA = i18n.translate('xpack.securitySolution.alertDetails.threatIntelCta', { - defaultMessage: 'View threat intel data', -}); - export const INVESTIGATION_GUIDE = i18n.translate( 'xpack.securitySolution.alertDetails.summary.investigationGuide', { diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 387547295f047..2b1088d8c11ae 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -18562,16 +18562,13 @@ "xpack.securitySolution.administration.os.linux": "Linux", "xpack.securitySolution.administration.os.macos": "Mac", "xpack.securitySolution.administration.os.windows": "Windows", - "xpack.securitySolution.alertDetails.alertSummary": "アラート概要", "xpack.securitySolution.alertDetails.checkDocs": "マニュアルをご確認ください。", "xpack.securitySolution.alertDetails.ifCtiNotEnabled": "脅威インテリジェンスソースを有効にしていない場合で、この機能について関心がある場合は、", - "xpack.securitySolution.alertDetails.noEnrichmentFound": "Threat Intel Enrichmentが見つかりません", "xpack.securitySolution.alertDetails.summary": "まとめ", "xpack.securitySolution.alertDetails.summary.investigationGuide": "調査ガイド", "xpack.securitySolution.alertDetails.summary.readLess": "表示を減らす", "xpack.securitySolution.alertDetails.summary.readMore": "続きを読む", "xpack.securitySolution.alertDetails.threatIntel": "Threat Intel", - "xpack.securitySolution.alertDetails.threatSummary": "脅威概要", "xpack.securitySolution.alerts.riskScoreMapping.defaultDescriptionLabel": "このルールで生成されたすべてのアラートのリスクスコアを選択します。", "xpack.securitySolution.alerts.riskScoreMapping.defaultRiskScoreTitle": "デフォルトリスクスコア", "xpack.securitySolution.alerts.riskScoreMapping.mappingDescriptionLabel": "ソースイベント値を使用して、デフォルトリスクスコアを上書きします。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index d142969022c81..04394a1ac1704 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -18826,16 +18826,13 @@ "xpack.securitySolution.administration.os.linux": "Linux", "xpack.securitySolution.administration.os.macos": "Mac", "xpack.securitySolution.administration.os.windows": "Windows", - "xpack.securitySolution.alertDetails.alertSummary": "告警摘要", "xpack.securitySolution.alertDetails.checkDocs": "请查看我们的文档。", "xpack.securitySolution.alertDetails.ifCtiNotEnabled": "如果尚未启用任何威胁情报来源,并希望更多了解此功能,", - "xpack.securitySolution.alertDetails.noEnrichmentFound": "未找到威胁情报扩充", "xpack.securitySolution.alertDetails.summary": "摘要", "xpack.securitySolution.alertDetails.summary.investigationGuide": "调查指南", "xpack.securitySolution.alertDetails.summary.readLess": "阅读更少内容", "xpack.securitySolution.alertDetails.summary.readMore": "阅读更多内容", "xpack.securitySolution.alertDetails.threatIntel": "威胁情报", - "xpack.securitySolution.alertDetails.threatSummary": "威胁摘要", "xpack.securitySolution.alerts.riskScoreMapping.defaultDescriptionLabel": "选择此规则生成的所有告警的风险分数。", "xpack.securitySolution.alerts.riskScoreMapping.defaultRiskScoreTitle": "默认风险分数", "xpack.securitySolution.alerts.riskScoreMapping.mappingDescriptionLabel": "使用源事件值覆盖默认风险分数。", From c07f51e5be9b16c58a4988491d8ae2ce021b4aba Mon Sep 17 00:00:00 2001 From: Frank Hassanabad Date: Fri, 9 Jul 2021 16:23:00 -0600 Subject: [PATCH 45/49] [Security Detections] Fixes ip on threshold preview button when selecting an ip data type such as source.ip (#105126) ## Summary See https://github.com/elastic/kibana/issues/100433 for details and test instructions. This is considered critical and a small fix for 7.14.0 has been requested. * Wrote Cypress test that exercises the bug * Fixed mutation in one part of the Cypress Test * Decided to remove the "missing" that we were telling users was "others" since missing is not the same as others. It no longer errors, but some users might be asking why we don't show "others" anymore. The reality is that we only showed "missing" which isn't adding value to the preview of what detections will end up looking like. * Later if we want a true "others" we should implement it as a larger feature request and not a bug fix IMHO Before you would get errors in your network panel: ![errors_threshold](https://user-images.githubusercontent.com/1151048/125126681-b0380e00-e0b8-11eb-9f2c-a75e2909754c.png) After you now get the `source.ip` without errors: Screen Shot 2021-07-09 at 1 28 24 PM ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../detection_rules/threshold_rule.spec.ts | 27 ++++++++++++++++--- .../cypress/tasks/create_new_rule.ts | 2 +- .../components/rules/query_preview/index.tsx | 1 + 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts index ad71d54eb2a7a..ce00c9b40aead 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts @@ -6,7 +6,7 @@ */ import { formatMitreAttackDescription } from '../../helpers/rules'; -import { indexPatterns, newRule, newThresholdRule } from '../../objects/rule'; +import { indexPatterns, newRule, newThresholdRule, ThresholdRule } from '../../objects/rule'; import { ALERT_RULE_METHOD, @@ -180,9 +180,9 @@ describe('Detection rules, threshold', () => { cy.get(ALERT_RULE_RISK_SCORE).first().should('have.text', rule.riskScore); }); - it('Preview results', () => { - const previewRule = { ...newThresholdRule }; - previewRule.index!.push('.siem-signals*'); + it('Preview results of keyword using "host.name"', () => { + const previewRule: ThresholdRule = { ...newThresholdRule }; + previewRule.index = [...previewRule.index, '.siem-signals*']; createCustomRuleActivated(newRule); goToManageAlertsDetectionRules(); @@ -194,4 +194,23 @@ describe('Detection rules, threshold', () => { cy.get(PREVIEW_HEADER_SUBTITLE).should('have.text', '3 unique hits'); }); + + it('Preview results of "ip" using "source.ip"', () => { + const previewRule: ThresholdRule = { + ...newThresholdRule, + thresholdField: 'source.ip', + threshold: '1', + }; + previewRule.index = [...previewRule.index, '.siem-signals*']; + + createCustomRuleActivated(newRule); + goToManageAlertsDetectionRules(); + waitForRulesTableToBeLoaded(); + goToCreateNewRule(); + selectThresholdRuleType(); + fillDefineThresholdRule(previewRule); + previewResults(); + + cy.get(PREVIEW_HEADER_SUBTITLE).should('have.text', '10 unique hits'); + }); }); diff --git a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts index 9b74110f0ef77..1b420cd6d1520 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts @@ -275,7 +275,7 @@ export const fillDefineThresholdRule = (rule: ThresholdRule) => { cy.get(TIMELINE(rule.timeline.id!)).click(); cy.get(COMBO_BOX_CLEAR_BTN).click(); - rule.index!.forEach((index) => { + rule.index.forEach((index) => { cy.get(COMBO_BOX_INPUT).first().type(`${index}{enter}`); }); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/index.tsx index 6342d468f5962..45b66058a04fb 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/index.tsx @@ -118,6 +118,7 @@ export const PreviewQuery = ({ startDate: toTime, filterQuery: queryFilter, indexNames: index, + includeMissingData: false, histogramType: MatrixHistogramType.events, stackByField: 'event.category', threshold: ruleType === 'threshold' ? threshold : undefined, From b40fc09dfca3c2e7072ba33f65c9c4ee7474764c Mon Sep 17 00:00:00 2001 From: spalger Date: Fri, 9 Jul 2021 16:40:13 -0700 Subject: [PATCH 46/49] skip another suite blocking es promotion (#104466) --- test/functional/apps/discover/_large_string.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/functional/apps/discover/_large_string.ts b/test/functional/apps/discover/_large_string.ts index de3f0f2c40ae1..ea219881c7a95 100644 --- a/test/functional/apps/discover/_large_string.ts +++ b/test/functional/apps/discover/_large_string.ts @@ -19,7 +19,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const security = getService('security'); const PageObjects = getPageObjects(['common', 'home', 'settings', 'discover']); - describe('test large strings', function () { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/104466 + describe.skip('test large strings', function () { before(async function () { await security.testUser.setRoles(['kibana_admin', 'kibana_large_strings']); From d776c0940ee47098bef21a129a03445e5fbf3eda Mon Sep 17 00:00:00 2001 From: spalger Date: Fri, 9 Jul 2021 18:01:11 -0700 Subject: [PATCH 47/49] skip all discover functional tests to unblock es promotion (#104466) --- test/functional/apps/discover/_data_grid_field_data.ts | 3 +-- test/functional/apps/discover/_field_data.ts | 3 +-- test/functional/apps/discover/_field_data_with_fields_api.ts | 3 +-- test/functional/apps/discover/_large_string.ts | 3 +-- test/functional/apps/discover/index.ts | 3 ++- 5 files changed, 6 insertions(+), 9 deletions(-) diff --git a/test/functional/apps/discover/_data_grid_field_data.ts b/test/functional/apps/discover/_data_grid_field_data.ts index cf8a6fc1bd60f..94e8e942f86ba 100644 --- a/test/functional/apps/discover/_data_grid_field_data.ts +++ b/test/functional/apps/discover/_data_grid_field_data.ts @@ -19,8 +19,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const defaultSettings = { defaultIndex: 'logstash-*', 'doc_table:legacy': false }; const dataGrid = getService('dataGrid'); - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/104466 - describe.skip('discover data grid field data tests', function describeIndexTests() { + describe('discover data grid field data tests', function describeIndexTests() { this.tags('includeFirefox'); before(async function () { await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); diff --git a/test/functional/apps/discover/_field_data.ts b/test/functional/apps/discover/_field_data.ts index 6471b751945a8..ec9f9cf65e0fa 100644 --- a/test/functional/apps/discover/_field_data.ts +++ b/test/functional/apps/discover/_field_data.ts @@ -20,8 +20,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['common', 'header', 'discover', 'visualize', 'timePicker']); const find = getService('find'); - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/104466 - describe.skip('discover tab', function describeIndexTests() { + describe('discover tab', function describeIndexTests() { this.tags('includeFirefox'); before(async function () { await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); diff --git a/test/functional/apps/discover/_field_data_with_fields_api.ts b/test/functional/apps/discover/_field_data_with_fields_api.ts index 7c6867e935063..110e255d18c75 100644 --- a/test/functional/apps/discover/_field_data_with_fields_api.ts +++ b/test/functional/apps/discover/_field_data_with_fields_api.ts @@ -20,8 +20,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['common', 'header', 'discover', 'visualize', 'timePicker']); const find = getService('find'); - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/104466 - describe.skip('discover tab with new fields API', function describeIndexTests() { + describe('discover tab with new fields API', function describeIndexTests() { this.tags('includeFirefox'); before(async function () { await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); diff --git a/test/functional/apps/discover/_large_string.ts b/test/functional/apps/discover/_large_string.ts index ea219881c7a95..de3f0f2c40ae1 100644 --- a/test/functional/apps/discover/_large_string.ts +++ b/test/functional/apps/discover/_large_string.ts @@ -19,8 +19,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const security = getService('security'); const PageObjects = getPageObjects(['common', 'home', 'settings', 'discover']); - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/104466 - describe.skip('test large strings', function () { + describe('test large strings', function () { before(async function () { await security.testUser.setRoles(['kibana_admin', 'kibana_large_strings']); diff --git a/test/functional/apps/discover/index.ts b/test/functional/apps/discover/index.ts index b396f172f6961..a17bf53e7f478 100644 --- a/test/functional/apps/discover/index.ts +++ b/test/functional/apps/discover/index.ts @@ -12,7 +12,8 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const browser = getService('browser'); - describe('discover app', function () { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/104466 + describe.skip('discover app', function () { this.tags('ciGroup6'); before(function () { From c0fec48104a7d026802673723ffb8796211bc2e0 Mon Sep 17 00:00:00 2001 From: Frank Hassanabad Date: Fri, 9 Jul 2021 20:55:20 -0600 Subject: [PATCH 48/49] [Security Solutions][Detection Engine] Fixes button group alignments in machine learning and tags (#105166) ## Summary See: https://github.com/elastic/kibana/issues/104055 For more issue details. This is deemed embarrassing enough to be critical for a fix for 7.14.0 before shipping. EUI looks to have updated its self and added a new attribute that it wants us to use called `numFIlters` which when set will show the total number of filter items before they are selected. Once selected the number and look and feel change. ```ts numFilters ``` Before: Screen Shot 2021-07-09 at 5 45 08 PM After before selections: Screen Shot 2021-07-09 at 5 48 43 PM After once you have selections: Screen Shot 2021-07-09 at 5 49 44 PM Before: Screen Shot 2021-07-09 at 5 42 01 PM After before selections: Screen Shot 2021-07-09 at 5 42 27 PM After once you have selections: Screen Shot 2021-07-09 at 5 49 36 PM ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../filters/__snapshots__/groups_filter_popover.test.tsx.snap | 1 + .../ml_popover/jobs_table/filters/groups_filter_popover.tsx | 1 + .../rules/all/rules_table_filters/tags_filter_popover.tsx | 2 ++ 3 files changed, 4 insertions(+) diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/filters/__snapshots__/groups_filter_popover.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/filters/__snapshots__/groups_filter_popover.test.tsx.snap index 22805d34d2ee1..410fb7f3ae793 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/filters/__snapshots__/groups_filter_popover.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/filters/__snapshots__/groups_filter_popover.test.tsx.snap @@ -10,6 +10,7 @@ exports[`GroupsFilterPopover renders correctly against snapshot 1`] = ` iconType="arrowDown" isSelected={false} numActiveFilters={0} + numFilters={3} onClick={[Function]} > Groups diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/filters/groups_filter_popover.tsx b/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/filters/groups_filter_popover.tsx index b7425a62f6773..249dc0dfccdbb 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/filters/groups_filter_popover.tsx +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/filters/groups_filter_popover.tsx @@ -59,6 +59,7 @@ export const GroupsFilterPopoverComponent = ({ iconType="arrowDown" onClick={() => setIsGroupPopoverOpen(!isGroupPopoverOpen)} isSelected={isGroupPopoverOpen} + numFilters={uniqueGroups.length} hasActiveFilters={selectedGroups.length > 0} numActiveFilters={selectedGroups.length} > diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/tags_filter_popover.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/tags_filter_popover.tsx index 45ce5bc18361c..c5262caf6c776 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/tags_filter_popover.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/tags_filter_popover.tsx @@ -102,9 +102,11 @@ const TagsFilterPopoverComponent = ({ ownFocus button={ setIsTagPopoverOpen(!isTagPopoverOpen)} + numFilters={tags.length} isSelected={isTagPopoverOpen} hasActiveFilters={selectedTags.length > 0} numActiveFilters={selectedTags.length} From 857dc9f2e1e1edd86c9cf7cd5f157e3e8b82a14a Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Sat, 10 Jul 2021 09:27:07 +0200 Subject: [PATCH 49/49] [APM] Don't log error if request was aborted (#105024) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../routes/register_routes/index.test.ts | 23 ++++-- .../server/routes/register_routes/index.ts | 72 ++++++++++++------- 2 files changed, 64 insertions(+), 31 deletions(-) diff --git a/x-pack/plugins/apm/server/routes/register_routes/index.test.ts b/x-pack/plugins/apm/server/routes/register_routes/index.test.ts index 158d7ee7e76a3..b9dece866fae5 100644 --- a/x-pack/plugins/apm/server/routes/register_routes/index.test.ts +++ b/x-pack/plugins/apm/server/routes/register_routes/index.test.ts @@ -109,6 +109,11 @@ const initApi = ( params: {}, query: {}, body: null, + events: { + aborted$: { + toPromise: () => new Promise(() => {}), + }, + }, ...request, }, responseMock @@ -202,7 +207,7 @@ describe('createApi', () => { describe('when validating', () => { describe('_inspect', () => { it('allows _inspect=true', async () => { - const handlerMock = jest.fn(); + const handlerMock = jest.fn().mockResolvedValue({}); const { simulateRequest, mocks: { response }, @@ -234,7 +239,7 @@ describe('createApi', () => { }); it('rejects _inspect=1', async () => { - const handlerMock = jest.fn(); + const handlerMock = jest.fn().mockResolvedValue({}); const { simulateRequest, @@ -267,7 +272,7 @@ describe('createApi', () => { }); it('allows omitting _inspect', async () => { - const handlerMock = jest.fn(); + const handlerMock = jest.fn().mockResolvedValue({}); const { simulateRequest, @@ -297,7 +302,11 @@ describe('createApi', () => { simulateRequest, mocks: { response }, } = initApi([ - { endpoint: 'GET /foo', options: { tags: [] }, handler: jest.fn() }, + { + endpoint: 'GET /foo', + options: { tags: [] }, + handler: jest.fn().mockResolvedValue({}), + }, ]); await simulateRequest({ @@ -328,7 +337,7 @@ describe('createApi', () => { }); it('validates path parameters', async () => { - const handlerMock = jest.fn(); + const handlerMock = jest.fn().mockResolvedValue({}); const { simulateRequest, mocks: { response }, @@ -402,7 +411,7 @@ describe('createApi', () => { }); it('validates body parameters', async () => { - const handlerMock = jest.fn(); + const handlerMock = jest.fn().mockResolvedValue({}); const { simulateRequest, mocks: { response }, @@ -448,7 +457,7 @@ describe('createApi', () => { }); it('validates query parameters', async () => { - const handlerMock = jest.fn(); + const handlerMock = jest.fn().mockResolvedValue({}); const { simulateRequest, mocks: { response }, diff --git a/x-pack/plugins/apm/server/routes/register_routes/index.ts b/x-pack/plugins/apm/server/routes/register_routes/index.ts index 136f3c73d8046..8e6070de722be 100644 --- a/x-pack/plugins/apm/server/routes/register_routes/index.ts +++ b/x-pack/plugins/apm/server/routes/register_routes/index.ts @@ -29,6 +29,13 @@ const inspectRt = t.exact( }) ); +const CLIENT_CLOSED_REQUEST = { + statusCode: 499, + body: { + message: 'Client closed request', + }, +}; + export const inspectableEsQueriesMap = new WeakMap< KibanaRequest, InspectResponse @@ -89,23 +96,40 @@ export function registerRoutes({ runtimeType ); - const data: Record | undefined | null = (await handler({ - request, - context, - config, - logger, - core, - plugins, - params: merge( - { - query: { - _inspect: false, + const { aborted, data } = await Promise.race([ + handler({ + request, + context, + config, + logger, + core, + plugins, + params: merge( + { + query: { + _inspect: false, + }, }, - }, - validatedParams - ), - ruleDataClient, - })) as any; + validatedParams + ), + ruleDataClient, + }).then((value) => { + return { + aborted: false, + data: value as Record | undefined | null, + }; + }), + request.events.aborted$.toPromise().then(() => { + return { + aborted: true, + data: undefined, + }; + }), + ]); + + if (aborted) { + return response.custom(CLIENT_CLOSED_REQUEST); + } if (Array.isArray(data)) { throw new Error('Return type cannot be an array'); @@ -118,9 +142,6 @@ export function registerRoutes({ } : { ...data }; - // cleanup - inspectableEsQueriesMap.delete(request); - if (!options.disableTelemetry && telemetryUsageCounter) { telemetryUsageCounter.incrementCounter({ counterName: `${method.toUpperCase()} ${pathname}`, @@ -131,6 +152,7 @@ export function registerRoutes({ return response.ok({ body }); } catch (error) { logger.error(error); + if (!options.disableTelemetry && telemetryUsageCounter) { telemetryUsageCounter.incrementCounter({ counterName: `${method.toUpperCase()} ${pathname}`, @@ -147,16 +169,18 @@ export function registerRoutes({ }, }; - if (Boom.isBoom(error)) { - opts.statusCode = error.output.statusCode; + if (error instanceof RequestAbortedError) { + return response.custom(merge(opts, CLIENT_CLOSED_REQUEST)); } - if (error instanceof RequestAbortedError) { - opts.statusCode = 499; - opts.body.message = 'Client closed request'; + if (Boom.isBoom(error)) { + opts.statusCode = error.output.statusCode; } return response.custom(opts); + } finally { + // cleanup + inspectableEsQueriesMap.delete(request); } } );