@@ -59,7 +59,7 @@ export const PersonalDashboardLayout: React.FC = ({ diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_sidebar/private_sources_sidebar.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_sidebar/private_sources_sidebar.test.tsx index 9fa4d4dd1b237..8f2ecf012e1ea 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_sidebar/private_sources_sidebar.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_sidebar/private_sources_sidebar.test.tsx @@ -30,7 +30,7 @@ import { PrivateSourcesSidebar } from './private_sources_sidebar'; describe('PrivateSourcesSidebar', () => { const mockValues = { - account: { canCreatePersonalSources: true }, + account: { canCreatePrivateSources: true }, contentSource: {}, }; @@ -55,7 +55,7 @@ describe('PrivateSourcesSidebar', () => { }); it('uses correct title and description when private sources are disabled', () => { - setMockValues({ ...mockValues, account: { canCreatePersonalSources: false } }); + setMockValues({ ...mockValues, account: { canCreatePrivateSources: false } }); const wrapper = shallow(); expect(wrapper.find(ViewContentHeader).prop('title')).toEqual(PRIVATE_VIEW_ONLY_PAGE_TITLE); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_sidebar/private_sources_sidebar.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_sidebar/private_sources_sidebar.tsx index ac497f5ae3a28..6cd7a10fc7ade 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_sidebar/private_sources_sidebar.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/personal_dashboard_sidebar/private_sources_sidebar.tsx @@ -24,13 +24,13 @@ import { ViewContentHeader } from '../../shared/view_content_header'; export const PrivateSourcesSidebar = () => { const { - account: { canCreatePersonalSources }, + account: { canCreatePrivateSources }, } = useValues(AppLogic); - const PAGE_TITLE = canCreatePersonalSources + const PAGE_TITLE = canCreatePrivateSources ? PRIVATE_CAN_CREATE_PAGE_TITLE : PRIVATE_VIEW_ONLY_PAGE_TITLE; - const PAGE_DESCRIPTION = canCreatePersonalSources + const PAGE_DESCRIPTION = canCreatePrivateSources ? PRIVATE_CAN_CREATE_PAGE_DESCRIPTION : PRIVATE_VIEW_ONLY_PAGE_DESCRIPTION; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/credential_item/credential_item.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/credential_item/credential_item.tsx index 61eaa4e42f8dd..e4067e1f98e5d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/credential_item/credential_item.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/credential_item/credential_item.tsx @@ -19,6 +19,21 @@ import { EuiFieldPassword, EuiToolTip, } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +export const COPY_TOOLTIP = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.credentialItem.copy.tooltip', + { + defaultMessage: 'Copy to clipboard', + } +); + +export const COPIED_TOOLTIP = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.credentialItem.copied.tooltip', + { + defaultMessage: 'Copied!', + } +); interface CredentialItemProps { label: string; @@ -37,6 +52,14 @@ export const CredentialItem: React.FC = ({ }) => { const [isVisible, setIsVisible] = useState(false); + const SHOW_CREDENTIAL_TOOLTIP = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.credentialItem.show.tooltip', + { + defaultMessage: 'Show {credential}.', + values: { credential: label }, + } + ); + return ( = ({ {!hideCopy && ( - + {(copy) => ( = ({ )} - + setIsVisible(!isVisible)} iconType={isVisible ? 'eyeClosed' : 'eye'} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/license_callout/license_callout.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/license_callout/license_callout.tsx index f278cda96ae83..270daf195bd38 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/license_callout/license_callout.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/license_callout/license_callout.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { EuiLink, EuiFlexItem, EuiFlexGroup, EuiText } from '@elastic/eui'; +import { EXPLORE_PLATINUM_FEATURES_LINK } from '../../../constants'; import { ENT_SEARCH_LICENSE_MANAGEMENT } from '../../../routes'; interface LicenseCalloutProps { @@ -20,7 +21,7 @@ export const LicenseCallout: React.FC = ({ message }) => { <> {message}{' '} - Explore Platinum features + {EXPLORE_PLATINUM_FEATURES_LINK} ); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts index d4fa2059f62fb..3666a794617ac 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts @@ -583,8 +583,8 @@ export const NON_PLATINUM_OAUTH_DESCRIPTION = i18n.translate( } ); -export const NON_PLATINUM_OAUTH_LINK = i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.nonPlatinumOauthLinkLabel', +export const EXPLORE_PLATINUM_FEATURES_LINK = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.explorePlatinumFeatures.link', { defaultMessage: 'Explore Platinum features', } @@ -734,6 +734,10 @@ export const FIELD_LABEL = i18n.translate('xpack.enterpriseSearch.workplaceSearc defaultMessage: 'Field', }); +export const LABEL_LABEL = i18n.translate('xpack.enterpriseSearch.workplaceSearch.label.label', { + defaultMessage: 'Label', +}); + export const DESCRIPTION_LABEL = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.description.label', { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx index 26f82ca5371d6..1ed77ea0fb1fd 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx @@ -23,7 +23,7 @@ import { SOURCES_PATH, SOURCE_ADDED_PATH, OAUTH_AUTHORIZE_PATH, - PERSONAL_SOURCES_PATH, + PRIVATE_SOURCES_PATH, ORG_SETTINGS_PATH, USERS_AND_ROLES_PATH, SECURITY_PATH, @@ -94,8 +94,8 @@ export const WorkplaceSearchConfigured: React.FC = (props) => { - - + + diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.test.tsx index b89a1451f7e57..b69303aae2106 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.test.tsx @@ -19,7 +19,7 @@ import { getSourcesPath, GROUPS_PATH, SOURCES_PATH, - PERSONAL_SOURCES_PATH, + PRIVATE_SOURCES_PATH, SOURCE_DETAILS_PATH, } from './routes'; @@ -40,7 +40,7 @@ describe('getContentSourcePath', () => { const wrapper = shallow(); const path = wrapper.find(EuiLink).prop('href'); - expect(path).toEqual(`${PERSONAL_SOURCES_PATH}/123`); + expect(path).toEqual(`${PRIVATE_SOURCES_PATH}/123`); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts index edd33ac885569..cd699f7df86cb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts @@ -60,7 +60,7 @@ export const GROUP_PATH = `${GROUPS_PATH}/:groupId`; export const GROUP_SOURCE_PRIORITIZATION_PATH = `${GROUPS_PATH}/:groupId/source_prioritization`; export const SOURCES_PATH = '/sources'; -export const PERSONAL_SOURCES_PATH = `${PERSONAL_PATH}${SOURCES_PATH}`; +export const PRIVATE_SOURCES_PATH = `${PERSONAL_PATH}${SOURCES_PATH}`; export const SOURCE_ADDED_PATH = `${SOURCES_PATH}/added`; export const ADD_SOURCE_PATH = `${SOURCES_PATH}/add`; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/read_uploaded_file_as_base64.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/read_uploaded_file_as_base64.test.ts index 9f612a7432ec5..2aedbbb798f58 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/read_uploaded_file_as_base64.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/read_uploaded_file_as_base64.test.ts @@ -15,7 +15,7 @@ describe('readUploadedFileAsBase64', () => { }); it('throws an error if the file cannot be read', async () => { - const badFile = ('causes an error' as unknown) as File; + const badFile = 'causes an error' as unknown as File; await expect(readUploadedFileAsBase64(badFile)).rejects.toThrow(); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/account_settings/account_settings.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/account_settings/account_settings.test.tsx index 5ff80a7683db6..c675a57ab18d8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/account_settings/account_settings.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/account_settings/account_settings.test.tsx @@ -27,6 +27,9 @@ describe('AccountSettings', () => { const mockCurrentUser = (user?: unknown) => (getCurrentUser as jest.Mock).mockReturnValue(Promise.resolve(user)); + const mockCurrentUserError = () => + (getCurrentUser as jest.Mock).mockReturnValue(Promise.reject()); + beforeAll(() => { mockCurrentUser(); }); @@ -44,6 +47,13 @@ describe('AccountSettings', () => { expect(wrapper.isEmptyRender()).toBe(true); }); + it('does not render if the getCurrentUser promise returns error', async () => { + mockCurrentUserError(); + const wrapper = await shallow(); + + expect(wrapper.isEmptyRender()).toBe(true); + }); + it('renders the security UI components when the user exists', async () => { mockCurrentUser({ username: 'mock user' }); (getPersonalInfo as jest.Mock).mockReturnValue(
); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/account_settings/account_settings.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/account_settings/account_settings.tsx index 2e92a00e3aa12..b26650875f3ef 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/account_settings/account_settings.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/account_settings/account_settings.tsx @@ -20,13 +20,19 @@ export const AccountSettings: React.FC = () => { const [currentUser, setCurrentUser] = useState(null); useEffect(() => { - security.authc.getCurrentUser().then(setCurrentUser); + security.authc + .getCurrentUser() + .then(setCurrentUser) + .catch(() => { + setCurrentUser(null); + }); }, [security.authc]); const PersonalInfo = useMemo(() => security.uiApi.components.getPersonalInfo, [security.uiApi]); - const ChangePassword = useMemo(() => security.uiApi.components.getChangePassword, [ - security.uiApi, - ]); + const ChangePassword = useMemo( + () => security.uiApi.components.getChangePassword, + [security.uiApi] + ); if (!currentUser) { return null; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_list.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_list.tsx index 165586dcc3903..08e002ee432a9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_list.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_list.tsx @@ -20,7 +20,7 @@ import { } from '@elastic/eui'; import { AppLogic } from '../../../../app_logic'; -import noSharedSourcesIcon from '../../../../assets/share_circle.svg'; +import noOrgSourcesIcon from '../../../../assets/share_circle.svg'; import { WorkplaceSearchPageTemplate, PersonalDashboardLayout, @@ -46,9 +46,8 @@ import { } from './constants'; export const AddSourceList: React.FC = () => { - const { contentSources, dataLoading, availableSources, configuredSources } = useValues( - SourcesLogic - ); + const { contentSources, dataLoading, availableSources, configuredSources } = + useValues(SourcesLogic); const { initializeSources, resetSourcesState } = useActions(SourcesLogic); @@ -143,7 +142,7 @@ export const AddSourceList: React.FC = () => { {ADD_SOURCE_EMPTY_TITLE}} body={

{ADD_SOURCE_EMPTY_BODY}

} /> diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts index 63eba575f0c37..5ff3964b8f83a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts @@ -23,7 +23,7 @@ import { AppLogic } from '../../../../app_logic'; import { ADD_GITHUB_PATH, SOURCES_PATH, - PERSONAL_SOURCES_PATH, + PRIVATE_SOURCES_PATH, getSourcesPath, } from '../../../../routes'; import { CustomSource } from '../../../../types'; @@ -321,7 +321,7 @@ describe('AddSourceLogic', () => { expect(navigateToUrl).toHaveBeenCalledWith(getSourcesPath(SOURCES_PATH, true)); }); - it('redirects to private dashboard when account context', async () => { + it('redirects to personal dashboard when account context', async () => { const accountQueryString = '?state=%7B%22action%22:%22create%22,%22context%22:%22account%22,%22service_type%22:%22gmail%22,%22csrf_token%22:%22token%3D%3D%22,%22index_permissions%22:false%7D&code=code'; @@ -379,7 +379,7 @@ describe('AddSourceLogic', () => { const githubQueryString = getGithubQueryString('account'); AddSourceLogic.actions.saveSourceParams(githubQueryString, errorParams, false); - expect(navigateToUrl).toHaveBeenCalledWith(PERSONAL_SOURCES_PATH); + expect(navigateToUrl).toHaveBeenCalledWith(PRIVATE_SOURCES_PATH); expect(setErrorMessage).toHaveBeenCalledWith( PERSONAL_DASHBOARD_SOURCE_ERROR(GITHUB_ERROR) ); @@ -569,9 +569,7 @@ describe('AddSourceLogic', () => { expect(clearFlashMessages).toHaveBeenCalled(); expect(AddSourceLogic.values.buttonLoading).toEqual(true); - expect( - http.put - ).toHaveBeenCalledWith( + expect(http.put).toHaveBeenCalledWith( `/internal/workplace_search/org/settings/connectors/${sourceConfigData.serviceType}`, { body: JSON.stringify(params) } ); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts index e63e58adb38e2..6f09cbb15c7db 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts @@ -25,7 +25,7 @@ import { CUSTOM_SERVICE_TYPE, WORKPLACE_SEARCH_URL_PREFIX } from '../../../../co import { SOURCES_PATH, ADD_GITHUB_PATH, - PERSONAL_SOURCES_PATH, + PRIVATE_SOURCES_PATH, getSourcesPath, } from '../../../../routes'; import { CustomSource } from '../../../../types'; @@ -62,11 +62,7 @@ export interface OauthParams { export interface AddSourceActions { initializeAddSource: (addSourceProps: AddSourceProps) => { addSourceProps: AddSourceProps }; - setAddSourceProps: ({ - addSourceProps, - }: { - addSourceProps: AddSourceProps; - }) => { + setAddSourceProps: ({ addSourceProps }: { addSourceProps: AddSourceProps }) => { addSourceProps: AddSourceProps; }; setAddSourceStep(addSourceCurrentStep: AddSourceSteps): AddSourceSteps; @@ -521,7 +517,7 @@ export const AddSourceLogic = kea = ({ values={{ securityLink: ( - enable private source connection + {CONFIG_COMPLETED_PRIVATE_SOURCES_DISABLED_LINK} ), }} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configuration_intro.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configuration_intro.tsx index d17e8b84efb2b..15941f14d5ab1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configuration_intro.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configuration_intro.tsx @@ -28,6 +28,7 @@ import { CONFIG_INTRO_STEPS_TEXT, CONFIG_INTRO_STEP1_HEADING, CONFIG_INTRO_STEP1_TEXT, + CONFIG_INTRO_STEP1_BADGE, CONFIG_INTRO_STEP2_HEADING, CONFIG_INTRO_STEP2_TITLE, CONFIG_INTRO_STEP2_TEXT, @@ -108,7 +109,9 @@ export const ConfigurationIntro: React.FC = ({ id="xpack.enterpriseSearch.workplaceSearch.contentSource.configIntro.step1.title" defaultMessage="Configure an OAuth application {badge}" values={{ - badge: One-Time Action, + badge: ( + {CONFIG_INTRO_STEP1_BADGE} + ), }} /> diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configure_custom.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configure_custom.tsx index b142ddcf4937e..1529875e66af5 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configure_custom.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configure_custom.tsx @@ -22,8 +22,10 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { CUSTOM_SOURCE_DOCS_URL } from '../../../../routes'; +import { SOURCE_NAME_LABEL } from '../../constants'; + import { AddSourceLogic } from './add_source_logic'; -import { CONFIG_CUSTOM_BUTTON } from './constants'; +import { CONFIG_CUSTOM_BUTTON, CONFIG_CUSTOM_LINK_TEXT } from './constants'; interface ConfigureCustomProps { header: React.ReactNode; @@ -57,12 +59,12 @@ export const ConfigureCustom: React.FC = ({

{helpText}

- Read the documentation + {CONFIG_CUSTOM_LINK_TEXT} ), }} @@ -70,7 +72,7 @@ export const ConfigureCustom: React.FC = ({

- + = ({ name, onFormCreated, header }) => { const [formLoading, setFormLoading] = useState(false); - const { - getPreContentSourceConfigData, - setSelectedGithubOrganizations, - createContentSource, - } = useActions(AddSourceLogic); + const { getPreContentSourceConfigData, setSelectedGithubOrganizations, createContentSource } = + useActions(AddSourceLogic); const { currentServiceType, githubOrganizations, diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.tsx index 4b8b2437788e8..5428e63a101b4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.tsx @@ -30,6 +30,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { LicensingLogic } from '../../../../../shared/licensing'; import { AppLogic } from '../../../../app_logic'; +import { EXPLORE_PLATINUM_FEATURES_LINK } from '../../../../constants'; import { DOCUMENT_PERMISSIONS_DOCS_URL, ENT_SEARCH_LICENSE_MANAGEMENT } from '../../../../routes'; import { FeatureIds, Configuration, Features } from '../../../../types'; import { LEARN_MORE_LINK } from '../../constants'; @@ -44,7 +45,6 @@ import { CONNECT_NOT_SYNCED_TEXT, SOURCE_FEATURES_DOCUMENT_LEVEL_PERMISSIONS_FEATURE, SOURCE_FEATURES_DOCUMENT_LEVEL_PERMISSIONS_TITLE, - SOURCE_FEATURES_EXPLORE_BUTTON, } from './constants'; import { SourceFeatures } from './source_features'; @@ -82,9 +82,8 @@ export const ConnectInstance: React.FC = ({ setSourceIndexPermissionsValue, } = useActions(AddSourceLogic); - const { loginValue, passwordValue, indexPermissionsValue, subdomainValue } = useValues( - AddSourceLogic - ); + const { loginValue, passwordValue, indexPermissionsValue, subdomainValue } = + useValues(AddSourceLogic); const { isOrganization } = useValues(AppLogic); @@ -230,7 +229,7 @@ export const ConnectInstance: React.FC = ({ - {SOURCE_FEATURES_EXPLORE_BUTTON} + {EXPLORE_PLATINUM_FEATURES_LINK} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/constants.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/constants.ts index 2bf185ee048bd..079cb5e1a5a3d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/constants.ts @@ -19,7 +19,7 @@ export const ADD_SOURCE_ORG_SOURCE_DESCRIPTION = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.contentSource.addSourceList.orgSourceDescription', { defaultMessage: - 'Shared content sources are available to your entire organization or can be assigned to specific user groups.', + 'Organizational content sources are available to your entire organization or can be assigned to specific user groups.', } ); @@ -41,7 +41,7 @@ export const ADD_SOURCE_NO_SOURCES_TITLE = i18n.translate( export const ADD_SOURCE_ORG_SOURCES_TITLE = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.contentSource.addSourceList.orgSourcesTitle', { - defaultMessage: 'Add a shared content source', + defaultMessage: 'Add an organizational content source', } ); @@ -101,6 +101,13 @@ export const AVAILABLE_SOURCE_CUSTOM_SOURCE_BUTTON = i18n.translate( } ); +export const CONFIG_COMPLETED_PRIVATE_SOURCES_DISABLED_LINK = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSource.configCompleted.privateDisabled.link', + { + defaultMessage: 'enable private source connection', + } +); + export const CONFIG_COMPLETED_PRIVATE_SOURCES_DOCS_LINK = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.contentSource.configCompleted.privateDisabled.button', { @@ -136,6 +143,13 @@ export const CONFIG_INTRO_STEP1_HEADING = i18n.translate( } ); +export const CONFIG_INTRO_STEP1_BADGE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSource.configIntro.step1.badge', + { + defaultMessage: 'One-Time Action', + } +); + export const CONFIG_INTRO_STEP1_TEXT = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.contentSource.configIntro.step1.text', { @@ -173,6 +187,13 @@ export const CONFIG_CUSTOM_BUTTON = i18n.translate( } ); +export const CONFIG_CUSTOM_LINK_TEXT = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSource.configCustom.docs.link.text', + { + defaultMessage: 'Read the documentation', + } +); + export const CONFIG_OAUTH_LABEL = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.contentSource.configOauth.label', { @@ -293,6 +314,13 @@ export const SAVE_CUSTOM_VISUAL_WALKTHROUGH_TITLE = i18n.translate( } ); +export const SAVE_CUSTOM_VISUAL_WALKTHROUGH_LINK = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSource.saveCustom.visualWalkthrough.link', + { + defaultMessage: 'Check out the documentation', + } +); + export const SAVE_CUSTOM_STYLING_RESULTS_TITLE = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.contentSource.saveCustom.stylingResults.title', { @@ -300,6 +328,13 @@ export const SAVE_CUSTOM_STYLING_RESULTS_TITLE = i18n.translate( } ); +export const SAVE_CUSTOM_STYLING_RESULTS_LINK = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSource.saveCustom.stylingResults.link', + { + defaultMessage: 'Display Settings', + } +); + export const SAVE_CUSTOM_DOC_PERMISSIONS_TITLE = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.contentSource.saveCustom.docPermissions.title', { @@ -307,6 +342,13 @@ export const SAVE_CUSTOM_DOC_PERMISSIONS_TITLE = i18n.translate( } ); +export const SAVE_CUSTOM_DOC_PERMISSIONS_LINK = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSource.saveCustom.docPermissions.link', + { + defaultMessage: 'Document-level permissions', + } +); + export const INCLUDED_FEATURES_TITLE = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.contentSource.includedFeaturesTitle', { @@ -314,31 +356,80 @@ export const INCLUDED_FEATURES_TITLE = i18n.translate( } ); -export const SOURCE_FEATURES_SEARCHABLE = i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.searchable.text', +export const SOURCE_FEATURES_SYNC_FREQUENCY_TITLE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.syncFrequency.title', + { + defaultMessage: 'Syncs every 2 hours', + } +); + +export const SOURCE_FEATURES_SYNC_FREQUENCY_TIME = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.syncFrequency.time', + { + defaultMessage: '2 hours', + } +); + +export const SOURCE_FEATURES_SYNCED_ITEMS_TITLE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.syncedItems.title', + { + defaultMessage: 'Synced items', + } +); + +export const SOURCE_FEATURES_SEARCHABLE_TITLE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.searchable.title', + { + defaultMessage: 'Searchable content', + } +); + +export const SOURCE_FEATURES_SEARCHABLE_DESCRIPTION = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.searchable.description', { defaultMessage: 'The following items are searchable:', } ); -export const SOURCE_FEATURES_REMOTE_FEATURE = i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.remote.text', +export const SOURCE_FEATURES_REMOTE_FEATURE_TITLE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.remote.title', + { + defaultMessage: 'Always up-to-date', + } +); + +export const SOURCE_FEATURES_REMOTE_FEATURE_DESCRIPTION = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.remote.description', { defaultMessage: 'Message data and other information is searchable in real-time from the Workplace Search experience.', } ); -export const SOURCE_FEATURES_PRIVATE_FEATURE = i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.private.text', +export const SOURCE_FEATURES_PRIVATE_FEATURE_TITLE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.private.title', + { + defaultMessage: 'Always private', + } +); + +export const SOURCE_FEATURES_PRIVATE_FEATURE_DESCRIPTION = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.private.description', { defaultMessage: 'Results returned are specific and relevant to you. Connecting this source does not expose your personal data to other search users - only you.', } ); -export const SOURCE_FEATURES_GLOBAL_ACCESS_PERMISSIONS_FEATURE = i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.globalAccessPermissions.text', +export const SOURCE_FEATURES_GLOBAL_ACCESS_PERMISSIONS_FEATURE_TITLE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.globalAccessPermissions.title', + { + defaultMessage: 'Global access permissions', + } +); + +export const SOURCE_FEATURES_GLOBAL_ACCESS_PERMISSIONS_FEATURE_DESCRIPTION = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.globalAccessPermissions.description', { defaultMessage: 'All documents accessible to the connecting service user will be synchronized and made available to the organization’s users, or group’s users. Documents are immediately available for search', @@ -360,12 +451,6 @@ export const SOURCE_FEATURES_DOCUMENT_LEVEL_PERMISSIONS_FEATURE = i18n.translate } ); -export const SOURCE_FEATURES_EXPLORE_BUTTON = i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.explore.button', - { - defaultMessage: 'Explore Platinum features', - } -); export const CONNECT_WHICH_OPTION_LINK = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.contentSource.connect.whichOption.link', { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/reauthenticate.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/reauthenticate.tsx index fa604ef758a44..f6c34a3eec4ca 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/reauthenticate.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/reauthenticate.tsx @@ -30,7 +30,7 @@ interface ReauthenticateProps { export const Reauthenticate: React.FC = ({ name, header }) => { const { search } = useLocation() as Location; - const { sourceId } = (parseQueryParams(search) as unknown) as SourceQueryParams; + const { sourceId } = parseQueryParams(search) as unknown as SourceQueryParams; const [formLoading, setFormLoading] = useState(false); const { getSourceReConnectData } = useActions(AddSourceLogic); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_config.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_config.tsx index 053a3b6b0e6bb..3320f43ed771a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_config.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_config.tsx @@ -67,13 +67,8 @@ export const SaveConfig: React.FC = ({ const { setClientIdValue, setClientSecretValue, setBaseUrlValue } = useActions(AddSourceLogic); - const { - sourceConfigData, - buttonLoading, - clientIdValue, - clientSecretValue, - baseUrlValue, - } = useValues(AddSourceLogic); + const { sourceConfigData, buttonLoading, clientIdValue, clientSecretValue, baseUrlValue } = + useValues(AddSourceLogic); const { accountContextOnly, diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.tsx index bbece2f68c717..dbec5d1808167 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.tsx @@ -46,8 +46,11 @@ import { SAVE_CUSTOM_API_KEYS_TITLE, SAVE_CUSTOM_API_KEYS_BODY, SAVE_CUSTOM_VISUAL_WALKTHROUGH_TITLE, + SAVE_CUSTOM_VISUAL_WALKTHROUGH_LINK, SAVE_CUSTOM_STYLING_RESULTS_TITLE, + SAVE_CUSTOM_STYLING_RESULTS_LINK, SAVE_CUSTOM_DOC_PERMISSIONS_TITLE, + SAVE_CUSTOM_DOC_PERMISSIONS_LINK, } from './constants'; interface SaveCustomProps { @@ -140,7 +143,7 @@ export const SaveCustom: React.FC = ({ values={{ link: ( - Check out the documentation + {SAVE_CUSTOM_VISUAL_WALKTHROUGH_LINK} ), }} @@ -168,7 +171,7 @@ export const SaveCustom: React.FC = ({ isOrganization )} > - Display Settings + {SAVE_CUSTOM_STYLING_RESULTS_LINK} ), }} @@ -193,7 +196,7 @@ export const SaveCustom: React.FC = ({ values={{ link: ( - Document-level permissions + {SAVE_CUSTOM_DOC_PERMISSIONS_LINK} ), }} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/source_features.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/source_features.tsx index 02856320aa535..940cb9cb7372a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/source_features.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/source_features.tsx @@ -26,10 +26,17 @@ import { Features, FeatureIds } from '../../../../types'; import { INCLUDED_FEATURES_TITLE, - SOURCE_FEATURES_SEARCHABLE, - SOURCE_FEATURES_REMOTE_FEATURE, - SOURCE_FEATURES_PRIVATE_FEATURE, - SOURCE_FEATURES_GLOBAL_ACCESS_PERMISSIONS_FEATURE, + SOURCE_FEATURES_SYNC_FREQUENCY_TITLE, + SOURCE_FEATURES_SYNC_FREQUENCY_TIME, + SOURCE_FEATURES_SYNCED_ITEMS_TITLE, + SOURCE_FEATURES_SEARCHABLE_TITLE, + SOURCE_FEATURES_SEARCHABLE_DESCRIPTION, + SOURCE_FEATURES_REMOTE_FEATURE_TITLE, + SOURCE_FEATURES_REMOTE_FEATURE_DESCRIPTION, + SOURCE_FEATURES_PRIVATE_FEATURE_TITLE, + SOURCE_FEATURES_PRIVATE_FEATURE_DESCRIPTION, + SOURCE_FEATURES_GLOBAL_ACCESS_PERMISSIONS_FEATURE_TITLE, + SOURCE_FEATURES_GLOBAL_ACCESS_PERMISSIONS_FEATURE_DESCRIPTION, } from './constants'; interface ConnectInstanceProps { @@ -76,7 +83,7 @@ export const SourceFeatures: React.FC = ({ features, objTy }; const SyncFrequencyFeature = ( - +

= ({ features, objTy defaultMessage="This source gets new content from {name} every {duration} (following the initial sync)." values={{ name, - duration: 2 hours, + duration: {SOURCE_FEATURES_SYNC_FREQUENCY_TIME}, }} />

@@ -93,10 +100,10 @@ export const SourceFeatures: React.FC = ({ features, objTy ); const SyncedItemsFeature = ( - + <> -

{SOURCE_FEATURES_SEARCHABLE}

+

{SOURCE_FEATURES_SEARCHABLE_DESCRIPTION}

@@ -111,10 +118,10 @@ export const SourceFeatures: React.FC = ({ features, objTy ); const SearchableContentFeature = ( - + -

{SOURCE_FEATURES_SEARCHABLE}

+

{SOURCE_FEATURES_SEARCHABLE_DESCRIPTION}

    @@ -127,25 +134,25 @@ export const SourceFeatures: React.FC = ({ features, objTy ); const RemoteFeature = ( - + -

    {SOURCE_FEATURES_REMOTE_FEATURE}

    +

    {SOURCE_FEATURES_REMOTE_FEATURE_DESCRIPTION}

    ); const PrivateFeature = ( - + -

    {SOURCE_FEATURES_PRIVATE_FEATURE}

    +

    {SOURCE_FEATURES_PRIVATE_FEATURE_DESCRIPTION}

    ); const GlobalAccessPermissionsFeature = ( - + -

    {SOURCE_FEATURES_GLOBAL_ACCESS_PERMISSIONS_FEATURE}

    +

    {SOURCE_FEATURES_GLOBAL_ACCESS_PERMISSIONS_FEATURE_DESCRIPTION}

    ); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/constants.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/constants.ts index d6ee25da6e2b1..bd86eb31005af 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/constants.ts @@ -80,6 +80,55 @@ export const TITLE_LABEL = i18n.translate( } ); +export const URL_LABEL = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.url.label', + { + defaultMessage: 'URL', + } +); + +export const COLOR_LABEL = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.color.label', + { + defaultMessage: 'Color', + } +); + +export const TYPE_LABEL = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.type.label', + { + defaultMessage: 'Type', + } +); + +export const MEDIA_TYPE_LABEL = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.mediaType.label', + { + defaultMessage: 'Media Type', + } +); + +export const CREATED_BY_LABEL = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.createdBy.label', + { + defaultMessage: 'Created By', + } +); + +export const UPDATED_BY_LABEL = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.updatedBy.label', + { + defaultMessage: 'Updated By', + } +); + +export const OPTIONAL_AREA_TEXT = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.optionalArea.text', + { + defaultMessage: 'This area is optional', + } +); + export const EMPTY_FIELDS_DESCRIPTION = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.emptyFields.description', { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings.tsx index ae47e20026b68..c5836dc939f2a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings.tsx @@ -44,9 +44,8 @@ interface DisplaySettingsProps { } export const DisplaySettings: React.FC = ({ tabId }) => { - const { initializeDisplaySettings, setServerData, handleSelectedTabChanged } = useActions( - DisplaySettingsLogic - ); + const { initializeDisplaySettings, setServerData, handleSelectedTabChanged } = + useActions(DisplaySettingsLogic); const { dataLoading, diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/field_editor_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/field_editor_modal.tsx index 717eebf5cf873..794b54eb59f36 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/field_editor_modal.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/field_editor_modal.tsx @@ -23,16 +23,21 @@ import { EuiSelect, } from '@elastic/eui'; -import { CANCEL_BUTTON, FIELD_LABEL, UPDATE_LABEL, ADD_LABEL } from '../../../../constants'; +import { + CANCEL_BUTTON, + FIELD_LABEL, + LABEL_LABEL, + UPDATE_LABEL, + ADD_LABEL, +} from '../../../../constants'; import { DisplaySettingsLogic } from './display_settings_logic'; const emptyField = { fieldName: '', label: '' }; export const FieldEditorModal: React.FC = () => { - const { toggleFieldEditorModal, addDetailField, updateDetailField } = useActions( - DisplaySettingsLogic - ); + const { toggleFieldEditorModal, addDetailField, updateDetailField } = + useActions(DisplaySettingsLogic); const { searchResultConfig: { detailFields }, @@ -67,7 +72,7 @@ export const FieldEditorModal: React.FC = () => { - + { onChange={(e) => setName(e.target.value)} /> - + { - const { - toggleFieldEditorModal, - setDetailFields, - openEditDetailField, - removeDetailField, - } = useActions(DisplaySettingsLogic); + const { toggleFieldEditorModal, setDetailFields, openEditDetailField, removeDetailField } = + useActions(DisplaySettingsLogic); const { searchResultConfig: { detailFields }, diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/search_results.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/search_results.tsx index 6515c8740f7a3..b0b047705182f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/search_results.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/search_results.tsx @@ -28,6 +28,14 @@ import { SEARCH_RESULTS_TITLE, SEARCH_RESULTS_ROW_HELP_TEXT, PREVIEW_TITLE, + TITLE_LABEL, + URL_LABEL, + COLOR_LABEL, + TYPE_LABEL, + MEDIA_TYPE_LABEL, + CREATED_BY_LABEL, + UPDATED_BY_LABEL, + OPTIONAL_AREA_TEXT, FEATURED_RESULTS_TITLE, FEATURED_RESULTS_DESCRIPTION, STANDARD_RESULTS_TITLE, @@ -85,7 +93,7 @@ export const SearchResults: React.FC = () => { { onChange={(e) => setTitleField(e.target.value)} /> - + { onChange={(e) => setUrlField(e.target.value)} /> - + { /> { /> { /> { /> {

    {CONTENT_SUMMARY_TITLE}

    - {!summary && } + {!summary && } {!!summary && (totalDocuments === 0 ? ( emptyState @@ -233,22 +235,18 @@ export const Overview: React.FC = () => {
    {GROUP_ACCESS_TITLE}
    - - {groups.map((group, index) => ( - - + + {groups.map((group, index) => ( + - - {group.name} - - - - ))} - + /> + ))} + + ); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_logic.test.ts index 549ffe0546d7c..af9d85237335c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_logic.test.ts @@ -40,12 +40,8 @@ import { SchemaLogic, dataTypeOptions } from './schema_logic'; describe('SchemaLogic', () => { const { http } = mockHttpValues; - const { - clearFlashMessages, - flashAPIErrors, - flashSuccessToast, - setErrorMessage, - } = mockFlashMessageHelpers; + const { clearFlashMessages, flashAPIErrors, flashSuccessToast, setErrorMessage } = + mockFlashMessageHelpers; const { mount } = new LogicMounter(SchemaLogic); const defaultValues = { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_logic.ts index 173f5df162caa..d664197afaa26 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_logic.ts @@ -44,13 +44,10 @@ interface SchemaActions { onSchemaSetSuccess(schemaProps: SchemaResponseProps): SchemaResponseProps; onSchemaSetFormErrors(errors: string[]): string[]; updateNewFieldType(newFieldType: SchemaType): SchemaType; - onFieldUpdate({ - schema, - formUnchanged, - }: { + onFieldUpdate({ schema, formUnchanged }: { schema: Schema; formUnchanged: boolean }): { schema: Schema; formUnchanged: boolean; - }): { schema: Schema; formUnchanged: boolean }; + }; onIndexingComplete(numDocumentsWithErrors: number): number; resetMostRecentIndexJob(emptyReindexJob: IndexJob): IndexJob; setFieldName(rawFieldName: string): string; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_added.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_added.tsx index 77b3931050483..c4f97eb718452 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_added.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_added.tsx @@ -26,7 +26,7 @@ import { AddSourceLogic, OauthParams } from './add_source/add_source_logic'; */ export const SourceAdded: React.FC = () => { const { search } = useLocation() as Location; - const params = (parseQueryParams(search) as unknown) as OauthParams; + const params = parseQueryParams(search) as unknown as OauthParams; const state = JSON.parse(params.state); const isOrganization = state.context !== 'account'; const { setChromeIsVisible } = useValues(KibanaLogic); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_content.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_content.tsx index a97cc85cb822a..fdeb325730470 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_content.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_content.tsx @@ -46,6 +46,8 @@ import { GO_BUTTON, RESET_BUTTON, SOURCE_CONTENT_TITLE, + SEARCH_CONTENT_PLACEHOLDER, + FILTER_CONTENT_PLACEHOLDER, CONTENT_LOADING_TEXT, } from '../constants'; import { SourceLogic } from '../source_logic'; @@ -57,9 +59,8 @@ const MAX_LENGTH = 28; export const SourceContent: React.FC = () => { const [searchTerm, setSearchTerm] = useState(''); - const { setActivePage, searchContentSourceDocuments, setContentFilterValue } = useActions( - SourceLogic - ); + const { setActivePage, searchContentSourceDocuments, setContentFilterValue } = + useActions(SourceLogic); const { contentSource: { id, serviceType, urlField, titleField, urlFieldIsLinkable, isFederatedSource }, @@ -197,13 +198,9 @@ export const SourceContent: React.FC = () => { { {showConfig && ( - + { const mockValues = { - account: { canCreatePersonalSources: false, groups: [] }, + account: { canCreatePrivateSources: false, groups: [] }, dataLoading: false, contentSources: [], privateContentSources: [], @@ -42,15 +42,15 @@ describe('PrivateSources', () => { expect(wrapper.find(SourcesView)).toHaveLength(1); }); - it('renders only shared sources section when canCreatePersonalSources is false', () => { + it('renders only organizational sources section when canCreatePrivateSources is false', () => { setMockValues({ ...mockValues }); const wrapper = shallow(); expect(wrapper.find(ContentSection)).toHaveLength(1); }); - it('renders both shared and private sources sections when canCreatePersonalSources is true', () => { - setMockValues({ ...mockValues, account: { canCreatePersonalSources: true, groups: [] } }); + it('renders both organizational and private sources sections when canCreatePrivateSources is true', () => { + setMockValues({ ...mockValues, account: { canCreatePrivateSources: true, groups: [] } }); const wrapper = shallow(); expect(wrapper.find(ContentSection)).toHaveLength(2); @@ -61,7 +61,7 @@ describe('PrivateSources', () => { ...mockValues, privateContentSources: ['source1', 'source2'], hasPlatinumLicense: false, - account: { canCreatePersonalSources: true, groups: [] }, + account: { canCreatePrivateSources: true, groups: [] }, }); const wrapper = shallow(); @@ -71,7 +71,7 @@ describe('PrivateSources', () => { it('renders an action button when user can add private sources', () => { setMockValues({ ...mockValues, - account: { canCreatePersonalSources: true, groups: [] }, + account: { canCreatePrivateSources: true, groups: [] }, serviceTypes: [{ configured: true }], }); const wrapper = shallow(); @@ -82,7 +82,7 @@ describe('PrivateSources', () => { it('renders empty prompts if no sources are available', () => { setMockValues({ ...mockValues, - account: { canCreatePersonalSources: true, groups: [] }, + account: { canCreatePrivateSources: true, groups: [] }, }); const wrapper = shallow(); @@ -92,7 +92,7 @@ describe('PrivateSources', () => { it('renders SourcesTable if sources are available', () => { setMockValues({ ...mockValues, - account: { canCreatePersonalSources: true, groups: [] }, + account: { canCreatePrivateSources: true, groups: [] }, contentSources: ['1', '2'], privateContentSources: ['1', '2'], }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/private_sources.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/private_sources.tsx index 57574ce14df67..c9f55ff656561 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/private_sources.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/private_sources.tsx @@ -15,7 +15,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { LicensingLogic } from '../../../shared/licensing'; import { EuiButtonTo } from '../../../shared/react_router_helpers'; import { AppLogic } from '../../app_logic'; -import noSharedSourcesIcon from '../../assets/share_circle.svg'; +import noOrgSourcesIcon from '../../assets/share_circle.svg'; import { PersonalDashboardLayout } from '../../components/layout'; import { ContentSection } from '../../components/shared/content_section'; import { SourcesTable } from '../../components/shared/sources_table'; @@ -27,10 +27,10 @@ import { PRIVATE_LINK_TITLE, PRIVATE_HEADER_TITLE, PRIVATE_HEADER_DESCRIPTION, - PRIVATE_SHARED_SOURCES_TITLE, + PRIVATE_ORG_SOURCES_TITLE, PRIVATE_EMPTY_TITLE, - SHARED_EMPTY_TITLE, - SHARED_EMPTY_DESCRIPTION, + ORG_SOURCES_EMPTY_TITLE, + ORG_SOURCES_EMPTY_DESCRIPTION, LICENSE_CALLOUT_TITLE, LICENSE_CALLOUT_DESCRIPTION, } from './constants'; @@ -46,18 +46,17 @@ export const PrivateSources: React.FC = () => { return resetSourcesState; }, []); - const { dataLoading, contentSources, serviceTypes, privateContentSources } = useValues( - SourcesLogic - ); + const { dataLoading, contentSources, serviceTypes, privateContentSources } = + useValues(SourcesLogic); const { - account: { canCreatePersonalSources, groups }, + account: { canCreatePrivateSources, groups }, } = useValues(AppLogic); const hasConfiguredConnectors = serviceTypes.some(({ configured }) => configured); - const canAddSources = canCreatePersonalSources && hasConfiguredConnectors; + const canAddSources = canCreatePrivateSources && hasConfiguredConnectors; const hasPrivateSources = privateContentSources?.length > 0; - const hasSharedSources = contentSources.length > 0; + const hasOrgSources = contentSources.length > 0; const licenseCallout = ( <> @@ -106,30 +105,30 @@ export const PrivateSources: React.FC = () => { ); - const sharedSourcesEmptyState = ( + const orgSourcesEmptyState = ( {SHARED_EMPTY_TITLE}} - body={

    {SHARED_EMPTY_DESCRIPTION}

    } + iconType={noOrgSourcesIcon} + title={

    {ORG_SOURCES_EMPTY_TITLE}

    } + body={

    {ORG_SOURCES_EMPTY_DESCRIPTION}

    } />
    ); - const sharedSourcesTable = ( + const orgSourcesTable = ( ); - const sharedSourcesSection = ( + const orgSourcesSection = ( { ) } > - {hasSharedSources ? sharedSourcesTable : sharedSourcesEmptyState} + {hasOrgSources ? orgSourcesTable : orgSourcesEmptyState} ); @@ -148,8 +147,8 @@ export const PrivateSources: React.FC = () => { {hasPrivateSources && !hasPlatinumLicense && licenseCallout} - {canCreatePersonalSources && privateSourcesSection} - {sharedSourcesSection} + {canCreatePrivateSources && privateSourcesSection} + {orgSourcesSection} ); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.test.ts index 94ec730ef256c..0d8d5684c4a4c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.test.ts @@ -26,12 +26,8 @@ import { SourceLogic } from './source_logic'; describe('SourceLogic', () => { const { http } = mockHttpValues; - const { - clearFlashMessages, - flashAPIErrors, - flashSuccessToast, - setErrorMessage, - } = mockFlashMessageHelpers; + const { clearFlashMessages, flashAPIErrors, flashSuccessToast, setErrorMessage } = + mockFlashMessageHelpers; const { navigateToUrl } = mockKibanaValues; const { mount, getListeners } = new LogicMounter(SourceLogic); @@ -202,7 +198,7 @@ describe('SourceLogic', () => { AppLogic.values.isOrganization = false; http.get.mockReturnValue(mock404); - SourceLogic.actions.initializeSource('404ing_personal_source'); + SourceLogic.actions.initializeSource('404ing_private_source'); await expectedAsyncError(mock404); expect(navigateToUrl).toHaveBeenCalledWith('/p/sources'); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts index 5c947b68f2333..c31eacda69515 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts @@ -19,7 +19,7 @@ import { import { HttpLogic } from '../../../shared/http'; import { KibanaLogic } from '../../../shared/kibana'; import { AppLogic } from '../../app_logic'; -import { PERSONAL_SOURCES_PATH, SOURCES_PATH, getSourcesPath } from '../../routes'; +import { PRIVATE_SOURCES_PATH, SOURCES_PATH, getSourcesPath } from '../../routes'; import { ContentSourceFullData, Meta, DocumentSummaryItem, SourceContentItem } from '../../types'; export interface SourceActions { @@ -169,7 +169,7 @@ export const SourceLogic = kea>({ } } catch (e) { if (e?.response?.status === 404) { - const redirect = isOrganization ? SOURCES_PATH : PERSONAL_SOURCES_PATH; + const redirect = isOrganization ? SOURCES_PATH : PRIVATE_SOURCES_PATH; KibanaLogic.values.navigateToUrl(redirect); setErrorMessage( i18n.translate('xpack.enterpriseSearch.workplaceSearch.sources.notFoundErrorMessage', { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.test.tsx index eb6a9949eb07c..00cf56001f73b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.test.tsx @@ -14,14 +14,14 @@ import { Route, Switch, Redirect } from 'react-router-dom'; import { shallow } from 'enzyme'; -import { ADD_SOURCE_PATH, PERSONAL_SOURCES_PATH, SOURCES_PATH, getSourcesPath } from '../../routes'; +import { ADD_SOURCE_PATH, PRIVATE_SOURCES_PATH, SOURCES_PATH, getSourcesPath } from '../../routes'; import { SourcesRouter } from './sources_router'; describe('SourcesRouter', () => { const resetSourcesState = jest.fn(); const mockValues = { - account: { canCreatePersonalSources: true }, + account: { canCreatePrivateSources: true }, isOrganization: true, hasPlatinumLicense: true, }; @@ -50,17 +50,17 @@ describe('SourcesRouter', () => { }); it('redirects when cannot create sources', () => { - setMockValues({ ...mockValues, account: { canCreatePersonalSources: false } }); + setMockValues({ ...mockValues, account: { canCreatePrivateSources: false } }); const wrapper = shallow(); expect(wrapper.find(Redirect).last().prop('from')).toEqual( getSourcesPath(ADD_SOURCE_PATH, false) ); - expect(wrapper.find(Redirect).last().prop('to')).toEqual(PERSONAL_SOURCES_PATH); + expect(wrapper.find(Redirect).last().prop('to')).toEqual(PRIVATE_SOURCES_PATH); }); - it('does not render the router until canCreatePersonalSources is fetched', () => { - setMockValues({ ...mockValues, account: {} }); // canCreatePersonalSources is undefined + it('does not render the router until canCreatePrivateSources is fetched', () => { + setMockValues({ ...mockValues, account: {} }); // canCreatePrivateSources is undefined const wrapper = shallow(); expect(wrapper.html()).toEqual(null); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.tsx index 0171717490c9b..2d47bab00810c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.tsx @@ -16,7 +16,7 @@ import { AppLogic } from '../../app_logic'; import { ADD_SOURCE_PATH, SOURCE_DETAILS_PATH, - PERSONAL_SOURCES_PATH, + PRIVATE_SOURCES_PATH, SOURCES_PATH, getSourcesPath, } from '../../routes'; @@ -35,7 +35,7 @@ export const SourcesRouter: React.FC = () => { const { hasPlatinumLicense } = useValues(LicensingLogic); const { resetSourcesState } = useActions(SourcesLogic); const { - account: { canCreatePersonalSources }, + account: { canCreatePrivateSources }, isOrganization, } = useValues(AppLogic); @@ -49,18 +49,18 @@ export const SourcesRouter: React.FC = () => { /** * When opening `workplace_search/p/sources/add` as a bookmark or reloading this page, - * Sources router first get rendered *before* it receives the canCreatePersonalSources value. - * This results in canCreatePersonalSources always being undefined on the first render, + * Sources router first get rendered *before* it receives the canCreatePrivateSources value. + * This results in canCreatePrivateSources always being undefined on the first render, * and user always getting redirected to `workplace_search/p/sources`. * Here we check for this value being present before we render any routes. */ - if (canCreatePersonalSources === undefined) { + if (canCreatePrivateSources === undefined) { return null; } return ( - + @@ -93,12 +93,12 @@ export const SourcesRouter: React.FC = () => { ); })} - {canCreatePersonalSources ? ( + {canCreatePrivateSources ? ( ) : ( - + )} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/__mocks__/group_logic.mock.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/__mocks__/group_logic.mock.ts index d4c1ce9dc8a29..d4d66005ecdc5 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/__mocks__/group_logic.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/__mocks__/group_logic.mock.ts @@ -11,7 +11,7 @@ export const mockGroupValues = { group: {} as GroupDetails, dataLoading: true, managerModalFormErrors: [], - sharedSourcesModalVisible: false, + orgSourcesModalVisible: false, confirmDeleteModalVisible: false, groupNameInputValue: '', selectedGroupSources: [], diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/add_group_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/add_group_modal.tsx index 2c5732b4b7157..d87991626ef3f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/add_group_modal.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/add_group_modal.tsx @@ -37,6 +37,12 @@ const ADD_GROUP_SUBMIT = i18n.translate( defaultMessage: 'Add Group', } ); +const ADD_GROUP_LABEL = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.groups.addGroup.label', + { + defaultMessage: 'Group name', + } +); export const AddGroupModal: React.FC<{}> = () => { const { closeNewGroupModal, saveNewGroup, setNewGroupName } = useActions(GroupsLogic); @@ -55,7 +61,7 @@ export const AddGroupModal: React.FC<{}> = () => { - + , - label: 'shared content sources', + label: 'organizational content sources', allItems: [], numSelected: 1, hideModal, diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_manager_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_manager_modal.tsx index af76bfc8a9396..b5d23895e9efd 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_manager_modal.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_manager_modal.tsx @@ -26,7 +26,7 @@ import { import { i18n } from '@kbn/i18n'; import { EuiButtonTo } from '../../../../shared/react_router_helpers'; -import noSharedSourcesIcon from '../../../assets/share_circle.svg'; +import noOrgSourcesIcon from '../../../assets/share_circle.svg'; import { UPDATE_BUTTON, CANCEL_BUTTON } from '../../../constants'; import { SOURCES_PATH } from '../../../routes'; import { Group } from '../../../types'; @@ -36,7 +36,7 @@ import { GroupsLogic } from '../groups_logic'; const ADD_SOURCE_BUTTON_TEXT = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.groups.groupManagerUpdateAddSourceButton', { - defaultMessage: 'Add a Shared Source', + defaultMessage: 'Add an organizational source', } ); const EMPTY_STATE_TITLE = i18n.translate( @@ -48,7 +48,21 @@ const EMPTY_STATE_TITLE = i18n.translate( const EMPTY_STATE_BODY = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.groups.groupManagerSourceEmpty.body', { - defaultMessage: 'Looks like you have not added any shared content sources yet.', + defaultMessage: 'Looks like you have not added any organizational content sources yet.', + } +); + +const ADD_SOURCE_MODAL_SELECT_BUTTON = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.groups.groupManagerSourceModal.selectButton.text', + { + defaultMessage: 'Select all', + } +); + +const ADD_SOURCE_MODAL_DESELECT_BUTTON = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.groups.groupManagerSourceModal.deselectButton.text', + { + defaultMessage: 'Deselect all', } ); @@ -75,8 +89,7 @@ export const GroupManagerModal: React.FC = ({ const { contentSources } = useValues(GroupsLogic); const allSelected = numSelected === allItems.length; - const isSources = label === 'shared content sources'; - const showEmptyState = isSources && contentSources.length < 1; + const showEmptyState = contentSources.length < 1; const handleClose = () => hideModal(group); const handleSelectAll = () => selectAll(allSelected ? [] : allItems); @@ -90,7 +103,7 @@ export const GroupManagerModal: React.FC = ({ {EMPTY_STATE_TITLE}} body={EMPTY_STATE_BODY} actions={sourcesButton} @@ -125,13 +138,7 @@ export const GroupManagerModal: React.FC = ({ - {i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.groups.groupManagerSelectAllToggle', - { - defaultMessage: '{action} All', - values: { action: allSelected ? 'Deselect' : 'Select' }, - } - )} + {allSelected ? ADD_SOURCE_MODAL_DESELECT_BUTTON : ADD_SOURCE_MODAL_SELECT_BUTTON} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.test.tsx index ed0246702d7a5..e1ecc47f02669 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.test.tsx @@ -20,7 +20,7 @@ import { SourcesTable } from '../../../components/shared/sources_table'; import { GroupOverview } from './group_overview'; const deleteGroup = jest.fn(); -const showSharedSourcesModal = jest.fn(); +const showOrgSourcesModal = jest.fn(); const showConfirmDeleteModal = jest.fn(); const hideConfirmDeleteModal = jest.fn(); const updateGroupName = jest.fn(); @@ -37,7 +37,7 @@ describe('GroupOverview', () => { beforeEach(() => { setMockActions({ deleteGroup, - showSharedSourcesModal, + showOrgSourcesModal, showConfirmDeleteModal, hideConfirmDeleteModal, updateGroupName, 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 5714cc965827e..c5febf37e1fbb 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 @@ -25,7 +25,7 @@ import { i18n } from '@kbn/i18n'; import { EuiButtonTo } from '../../../../shared/react_router_helpers'; import { TruncatedContent } from '../../../../shared/truncate'; -import noSharedSourcesIcon from '../../../assets/share_circle.svg'; +import noOrgSourcesIcon from '../../../assets/share_circle.svg'; import { WorkplaceSearchPageTemplate } from '../../../components/layout'; import { ContentSection } from '../../../components/shared/content_section'; import { SourcesTable } from '../../../components/shared/sources_table'; @@ -55,7 +55,7 @@ const GROUP_USERS_DESCRIPTION = i18n.translate( const MANAGE_SOURCES_BUTTON_TEXT = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.groups.overview.manageSourcesButtonText', { - defaultMessage: 'Manage shared content sources', + defaultMessage: 'Manage organizational content sources', } ); const MANAGE_USERS_BUTTON_TEXT = i18n.translate( @@ -110,7 +110,7 @@ const CONFIRM_TITLE_TEXT = i18n.translate( export const GroupOverview: React.FC = () => { const { deleteGroup, - showSharedSourcesModal, + showOrgSourcesModal, showConfirmDeleteModal, hideConfirmDeleteModal, updateGroupName, @@ -159,7 +159,7 @@ export const GroupOverview: React.FC = () => { const hasContentSources = contentSources?.length > 0; const manageSourcesButton = ( - + {MANAGE_SOURCES_BUTTON_TEXT} ); @@ -185,7 +185,7 @@ export const GroupOverview: React.FC = () => { <> {GROUP_SOURCES_TITLE}} body={

    {EMPTY_SOURCES_DESCRIPTION}

    } actions={manageSourcesButton} 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 8d3c7dfa7622b..effacfa3aa4f8 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 @@ -25,7 +25,7 @@ const DAYS_CUTOFF = 8; export const NO_SOURCES_MESSAGE = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.groups.noSourcesMessage', { - defaultMessage: 'No shared content sources', + defaultMessage: 'No organizational content sources', } ); export const NO_USERS_MESSAGE = i18n.translate( diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_row_sources_dropdown.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_row_sources_dropdown.tsx index 77d7de91caf7c..aa25deff49e3d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_row_sources_dropdown.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_row_sources_dropdown.tsx @@ -37,7 +37,7 @@ export const GroupRowSourcesDropdown: React.FC = ( const contentSourceCountHeading = ( {i18n.translate('xpack.enterpriseSearch.workplaceSearch.groups.contentSourceCountHeading', { - defaultMessage: '{numSources} shared content sources', + defaultMessage: '{numSources} organizational content sources', values: { numSources: groupSources.length }, })} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_source_prioritization.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_source_prioritization.test.tsx index ba667c6d8152d..49dc7223e54e6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_source_prioritization.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_source_prioritization.test.tsx @@ -18,7 +18,7 @@ import { GroupSourcePrioritization } from './group_source_prioritization'; const updatePriority = jest.fn(); const saveGroupSourcePrioritization = jest.fn(); -const showSharedSourcesModal = jest.fn(); +const showOrgSourcesModal = jest.fn(); const mockValues = { group: groups[0], @@ -36,7 +36,7 @@ describe('GroupSourcePrioritization', () => { setMockActions({ updatePriority, saveGroupSourcePrioritization, - showSharedSourcesModal, + showOrgSourcesModal, }); setMockValues(mockValues); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_source_prioritization.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_source_prioritization.tsx index 0fdaf2adde869..615e7b22b343f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_source_prioritization.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_source_prioritization.tsx @@ -36,7 +36,7 @@ import { GroupLogic } from '../group_logic'; const HEADER_TITLE = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.groups.sourceProioritization.headerTitle', { - defaultMessage: 'Shared content source prioritization', + defaultMessage: 'Organizational content source prioritization', } ); const HEADER_DESCRIPTION = i18n.translate( @@ -54,7 +54,7 @@ const ZERO_STATE_TITLE = i18n.translate( const ZERO_STATE_BUTTON_TEXT = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.groups.sourceProioritization.zeroStateButtonText', { - defaultMessage: 'Add shared content sources', + defaultMessage: 'Add organizational content sources', } ); const SOURCE_TABLE_HEADER = i18n.translate( @@ -71,9 +71,8 @@ const PRIORITY_TABLE_HEADER = i18n.translate( ); export const GroupSourcePrioritization: React.FC = () => { - const { updatePriority, saveGroupSourcePrioritization, showSharedSourcesModal } = useActions( - GroupLogic - ); + const { updatePriority, saveGroupSourcePrioritization, showOrgSourcesModal } = + useActions(GroupLogic); const { group: { contentSources = [], name: groupName }, @@ -118,7 +117,7 @@ export const GroupSourcePrioritization: React.FC = () => { )} } - actions={{ZERO_STATE_BUTTON_TEXT}} + actions={{ZERO_STATE_BUTTON_TEXT}} />
    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 3c44261cc911f..41a1c92107661 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 @@ -31,6 +31,14 @@ const GROUP_TABLE_HEADER = i18n.translate( defaultMessage: 'Group', } ); + +const GROUPS_PAGINATION_LABEL = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.groups.groupsTable.groupPagination.label', + { + defaultMessage: 'Groups', + } +); + const SOURCES_TABLE_HEADER = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.groups.groupsTable.sourcesTableHeader', { @@ -51,7 +59,7 @@ export const GroupsTable: React.FC<{}> = () => { const clearFiltersLink = hasFiltersSet ? : undefined; const paginationOptions = { - itemLabel: 'Groups', + itemLabel: GROUPS_PAGINATION_LABEL, totalPages, totalItems, activePage, diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/shared_sources_modal.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/org_sources_modal.test.tsx similarity index 83% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/shared_sources_modal.test.tsx rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/org_sources_modal.test.tsx index 02f0b53af97cc..3059274686045 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/shared_sources_modal.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/org_sources_modal.test.tsx @@ -13,23 +13,23 @@ import React from 'react'; import { shallow } from 'enzyme'; import { GroupManagerModal } from './group_manager_modal'; -import { SharedSourcesModal } from './shared_sources_modal'; +import { OrgSourcesModal } from './org_sources_modal'; import { SourcesList } from './sources_list'; const group = groups[0]; const addGroupSource = jest.fn(); const selectAllSources = jest.fn(); -const hideSharedSourcesModal = jest.fn(); +const hideOrgSourcesModal = jest.fn(); const removeGroupSource = jest.fn(); const saveGroupSources = jest.fn(); -describe('SharedSourcesModal', () => { +describe('OrgSourcesModal', () => { it('renders', () => { setMockActions({ addGroupSource, selectAllSources, - hideSharedSourcesModal, + hideOrgSourcesModal, removeGroupSource, saveGroupSources, }); @@ -40,7 +40,7 @@ describe('SharedSourcesModal', () => { contentSources: group.contentSources, }); - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper.find(SourcesList)).toHaveLength(1); expect(wrapper.find(GroupManagerModal)).toHaveLength(1); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/shared_sources_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/org_sources_modal.tsx similarity index 91% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/shared_sources_modal.tsx rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/org_sources_modal.tsx index 631c4f80f36b0..23598433d426b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/shared_sources_modal.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/org_sources_modal.tsx @@ -21,15 +21,15 @@ import { SourcesList } from './sources_list'; const MODAL_LABEL = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.groups.sourcesModalLabel', { - defaultMessage: 'shared content sources', + defaultMessage: 'organizational content sources', } ); -export const SharedSourcesModal: React.FC = () => { +export const OrgSourcesModal: React.FC = () => { const { addGroupSource, selectAllSources, - hideSharedSourcesModal, + hideOrgSourcesModal, removeGroupSource, saveGroupSources, } = useActions(GroupLogic); @@ -43,7 +43,7 @@ export const SharedSourcesModal: React.FC = () => { label={MODAL_LABEL} allItems={contentSources} numSelected={selectedGroupSources.length} - hideModal={hideSharedSourcesModal} + hideModal={hideOrgSourcesModal} selectAll={selectAllSources} saveItems={saveGroupSources} > diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_logic.test.ts index 088936443ccd3..6f811ce364290 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_logic.test.ts @@ -24,12 +24,8 @@ describe('GroupLogic', () => { const { mount } = new LogicMounter(GroupLogic); const { http } = mockHttpValues; const { navigateToUrl } = mockKibanaValues; - const { - clearFlashMessages, - flashAPIErrors, - flashSuccessToast, - setQueuedErrorMessage, - } = mockFlashMessageHelpers; + const { clearFlashMessages, flashAPIErrors, flashSuccessToast, setQueuedErrorMessage } = + mockFlashMessageHelpers; const group = groups[0]; const sourceIds = ['123', '124']; @@ -114,7 +110,7 @@ describe('GroupLogic', () => { GroupLogic.actions.onGroupSourcesSaved(group); expect(GroupLogic.values.group).toEqual(group); - expect(GroupLogic.values.sharedSourcesModalVisible).toEqual(false); + expect(GroupLogic.values.orgSourcesModalVisible).toEqual(false); expect(GroupLogic.values.selectedGroupSources).toEqual(sourceIds); expect(GroupLogic.values.cachedSourcePriorities).toEqual(sourcePriorities); expect(GroupLogic.values.activeSourcePriorities).toEqual(sourcePriorities); @@ -130,11 +126,11 @@ describe('GroupLogic', () => { }); }); - describe('hideSharedSourcesModal', () => { + describe('hideOrgSourcesModal', () => { it('sets reducers', () => { - GroupLogic.actions.hideSharedSourcesModal(group); + GroupLogic.actions.hideOrgSourcesModal(group); - expect(GroupLogic.values.sharedSourcesModalVisible).toEqual(false); + expect(GroupLogic.values.orgSourcesModalVisible).toEqual(false); expect(GroupLogic.values.selectedGroupSources).toEqual(sourceIds); }); }); @@ -284,7 +280,7 @@ describe('GroupLogic', () => { await nextTick(); expect(onGroupSourcesSavedSpy).toHaveBeenCalledWith(group); expect(flashSuccessToast).toHaveBeenCalledWith( - 'Successfully updated shared content sources.' + 'Successfully updated organizational content sources.' ); }); @@ -321,7 +317,7 @@ describe('GroupLogic', () => { await nextTick(); expect(flashSuccessToast).toHaveBeenCalledWith( - 'Successfully updated shared source prioritization.' + 'Successfully updated organizational source prioritization.' ); expect(onGroupPrioritiesChangedSpy).toHaveBeenCalledWith(group); }); @@ -345,11 +341,11 @@ describe('GroupLogic', () => { }); }); - describe('showSharedSourcesModal', () => { + describe('showOrgSourcesModal', () => { it('sets reducer and clears flash messages', () => { - GroupLogic.actions.showSharedSourcesModal(); + GroupLogic.actions.showOrgSourcesModal(); - expect(GroupLogic.values.sharedSourcesModalVisible).toEqual(true); + expect(GroupLogic.values.orgSourcesModalVisible).toEqual(true); expect(clearFlashMessages).toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_logic.ts index 96b9213d30afc..3ba7d68d0b3e9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_logic.ts @@ -32,13 +32,13 @@ interface GroupActions { removeGroupSource(sourceId: string): string; onGroupSourcesSaved(group: GroupDetails): GroupDetails; setGroupModalErrors(errors: string[]): string[]; - hideSharedSourcesModal(group: GroupDetails): GroupDetails; + hideOrgSourcesModal(group: GroupDetails): GroupDetails; selectAllSources(contentSources: ContentSourceDetails[]): ContentSourceDetails[]; updatePriority(id: string, boost: number): { id: string; boost: number }; resetGroup(): void; showConfirmDeleteModal(): void; hideConfirmDeleteModal(): void; - showSharedSourcesModal(): void; + showOrgSourcesModal(): void; resetFlashMessages(): void; initializeGroup(groupId: string): { groupId: string }; deleteGroup(): void; @@ -51,7 +51,7 @@ interface GroupValues { group: GroupDetails; dataLoading: boolean; managerModalFormErrors: string[]; - sharedSourcesModalVisible: boolean; + orgSourcesModalVisible: boolean; confirmDeleteModalVisible: boolean; groupNameInputValue: string; selectedGroupSources: string[]; @@ -71,13 +71,13 @@ export const GroupLogic = kea>({ removeGroupSource: (sourceId) => sourceId, onGroupSourcesSaved: (group) => group, setGroupModalErrors: (errors) => errors, - hideSharedSourcesModal: (group) => group, + hideOrgSourcesModal: (group) => group, selectAllSources: (contentSources) => contentSources, updatePriority: (id, boost) => ({ id, boost }), resetGroup: () => true, showConfirmDeleteModal: () => true, hideConfirmDeleteModal: () => true, - showSharedSourcesModal: () => true, + showOrgSourcesModal: () => true, resetFlashMessages: () => true, initializeGroup: (groupId) => ({ groupId }), deleteGroup: () => true, @@ -109,11 +109,11 @@ export const GroupLogic = kea>({ setGroupModalErrors: (_, errors) => errors, }, ], - sharedSourcesModalVisible: [ + orgSourcesModalVisible: [ false, { - showSharedSourcesModal: () => true, - hideSharedSourcesModal: () => false, + showOrgSourcesModal: () => true, + hideOrgSourcesModal: () => false, onGroupSourcesSaved: () => false, }, ], @@ -138,7 +138,7 @@ export const GroupLogic = kea>({ onInitializeGroup: (_, { contentSources }) => contentSources.map(({ id }) => id), onGroupSourcesSaved: (_, { contentSources }) => contentSources.map(({ id }) => id), selectAllSources: (_, contentSources) => contentSources.map(({ id }) => id), - hideSharedSourcesModal: (_, { contentSources }) => contentSources.map(({ id }) => id), + hideOrgSourcesModal: (_, { contentSources }) => contentSources.map(({ id }) => id), addGroupSource: (state, sourceId) => [...state, sourceId].sort(), removeGroupSource: (state, sourceId) => state.filter((id) => id !== sourceId), }, @@ -257,7 +257,7 @@ export const GroupLogic = kea>({ const GROUP_SOURCES_UPDATED_MESSAGE = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.groups.groupSourcesUpdated', { - defaultMessage: 'Successfully updated shared content sources.', + defaultMessage: 'Successfully updated organizational content sources.', } ); flashSuccessToast(GROUP_SOURCES_UPDATED_MESSAGE); @@ -289,7 +289,7 @@ export const GroupLogic = kea>({ const GROUP_PRIORITIZATION_UPDATED_MESSAGE = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.groups.groupPrioritizationUpdated', { - defaultMessage: 'Successfully updated shared source prioritization.', + defaultMessage: 'Successfully updated organizational source prioritization.', } ); @@ -302,7 +302,7 @@ export const GroupLogic = kea>({ showConfirmDeleteModal: () => { clearFlashMessages(); }, - showSharedSourcesModal: () => { + showOrgSourcesModal: () => { clearFlashMessages(); }, resetFlashMessages: () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_router.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_router.test.tsx index 9ea0908a0f014..393b5a7e194de 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_router.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_router.test.tsx @@ -17,7 +17,7 @@ import { shallow } from 'enzyme'; import { GroupOverview } from './components/group_overview'; import { GroupSourcePrioritization } from './components/group_source_prioritization'; -import { SharedSourcesModal } from './components/shared_sources_modal'; +import { OrgSourcesModal } from './components/org_sources_modal'; import { GroupRouter } from './group_router'; describe('GroupRouter', () => { @@ -26,7 +26,7 @@ describe('GroupRouter', () => { beforeEach(() => { setMockValues({ - sharedSourcesModalVisible: false, + orgSourcesModalVisible: false, group: groups[0], }); @@ -47,12 +47,12 @@ describe('GroupRouter', () => { it('renders modal', () => { setMockValues({ - sharedSourcesModalVisible: true, + orgSourcesModalVisible: true, group: groups[0], }); const wrapper = shallow(); - expect(wrapper.find(SharedSourcesModal)).toHaveLength(1); + expect(wrapper.find(OrgSourcesModal)).toHaveLength(1); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_router.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_router.tsx index 341e20099c6c2..ffdfb9c378614 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_router.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_router.tsx @@ -14,14 +14,14 @@ import { GROUP_SOURCE_PRIORITIZATION_PATH, GROUP_PATH } from '../../routes'; import { GroupOverview } from './components/group_overview'; import { GroupSourcePrioritization } from './components/group_source_prioritization'; -import { SharedSourcesModal } from './components/shared_sources_modal'; +import { OrgSourcesModal } from './components/org_sources_modal'; import { GroupLogic } from './group_logic'; export const GroupRouter: React.FC = () => { const { groupId } = useParams() as { groupId: string }; const { initializeGroup, resetGroup } = useActions(GroupLogic); - const { sharedSourcesModalVisible } = useValues(GroupLogic); + const { orgSourcesModalVisible } = useValues(GroupLogic); useEffect(() => { initializeGroup(groupId); @@ -38,7 +38,7 @@ export const GroupRouter: React.FC = () => {
    - {sharedSourcesModalVisible && } + {orgSourcesModalVisible && } ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.tsx index 1a4c4f51e93ea..5ecaef95c8be1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.tsx @@ -112,7 +112,7 @@ export const Groups: React.FC = () => { }), description: i18n.translate('xpack.enterpriseSearch.workplaceSearch.groups.description', { defaultMessage: - 'Assign shared content sources and users to groups to create relevant search experiences for various internal teams.', + 'Assign organizational content sources and users to groups to create relevant search experiences for various internal teams.', }), rightSideItems: headerActions, }} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/not_found/not_found.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/not_found/not_found.tsx index ef55668775513..e5a162baa2871 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/not_found/not_found.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/not_found/not_found.tsx @@ -12,7 +12,7 @@ import { PageTemplateProps } from '../../../shared/layout'; import { NotFoundPrompt } from '../../../shared/not_found'; import { SendWorkplaceSearchTelemetry } from '../../../shared/telemetry'; import { WorkplaceSearchPageTemplate, PersonalDashboardLayout } from '../../components/layout'; -import { PERSONAL_SOURCES_PATH } from '../../routes'; +import { PRIVATE_SOURCES_PATH } from '../../routes'; interface Props { isOrganization?: boolean; @@ -25,7 +25,7 @@ export const NotFound: React.FC = ({ isOrganization = true, pageChrome = diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/oauth_authorize/oauth_authorize.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/oauth_authorize/oauth_authorize.tsx index 972f1951d47b8..5c3114ae3199e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/oauth_authorize/oauth_authorize.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/oauth_authorize/oauth_authorize.tsx @@ -48,9 +48,8 @@ import { OAuthAuthorizeLogic } from './oauth_authorize_logic'; export const OAuthAuthorize: React.FC = () => { const { search } = useLocation() as Location; - const { initializeOAuthPreAuth, allowOAuthAuthorization, denyOAuthAuthorization } = useActions( - OAuthAuthorizeLogic - ); + const { initializeOAuthPreAuth, allowOAuthAuthorization, denyOAuthAuthorization } = + useActions(OAuthAuthorizeLogic); const { buttonLoading, dataLoading, cachedPreAuth, hasError } = useValues(OAuthAuthorizeLogic); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/__mocks__/overview_logic.mock.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/__mocks__/overview_logic.mock.ts index 7e30826c5e7ec..72ad157dd06bf 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/__mocks__/overview_logic.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/__mocks__/overview_logic.mock.ts @@ -17,7 +17,7 @@ export const mockOverviewValues = { hasUsers: false, isOldAccount: false, pendingInvitationsCount: 0, - personalSourcesCount: 0, + privateSourcesCount: 0, sourcesCount: 0, dataLoading: true, }; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.test.tsx index b62f022d85e38..f4f87f48f6395 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.test.tsx @@ -21,19 +21,19 @@ import { OnboardingSteps, OrgNameOnboarding } from './onboarding_steps'; const account = { id: '1', isAdmin: true, - canCreatePersonalSources: true, + canCreatePrivateSources: true, groups: [], }; describe('OnboardingSteps', () => { - describe('Shared Sources', () => { + describe('Organizational Sources', () => { it('renders 0 sources state', () => { const wrapper = shallow(); expect(wrapper.find(OnboardingCard)).toHaveLength(2); expect(wrapper.find(OnboardingCard).first().prop('actionPath')).toBe(ADD_SOURCE_PATH); expect(wrapper.find(OnboardingCard).first().prop('description')).toBe( - 'Add shared sources for your organization to start searching.' + 'Add organizational sources for your organization to start searching.' ); }); @@ -42,7 +42,7 @@ describe('OnboardingSteps', () => { const wrapper = shallow(); expect(wrapper.find(OnboardingCard).first().prop('description')).toEqual( - 'You have added 2 shared sources. Happy searching.' + 'You have added 2 organizational sources. Happy searching.' ); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.tsx index b0cc35fe10dff..13288bce98200 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.tsx @@ -24,7 +24,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiButtonTo } from '../../../shared/react_router_helpers'; import { TelemetryLogic } from '../../../shared/telemetry'; import { AppLogic } from '../../app_logic'; -import sharedSourcesIcon from '../../components/shared/assets/source_icons/share_circle.svg'; +import orgSourcesIcon from '../../components/shared/assets/source_icons/share_circle.svg'; import { ContentSection } from '../../components/shared/content_section'; import { ADD_SOURCE_PATH, USERS_AND_ROLES_PATH, ORG_SETTINGS_PATH } from '../../routes'; @@ -33,7 +33,7 @@ import { OverviewLogic } from './overview_logic'; const SOURCES_TITLE = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.overviewOnboardingSourcesCard.title', - { defaultMessage: 'Shared sources' } + { defaultMessage: 'Organizational sources' } ); const USERS_TITLE = i18n.translate( @@ -41,9 +41,29 @@ const USERS_TITLE = i18n.translate( { defaultMessage: 'Users & invitations' } ); +const INVITE_FIRST_USERS_BUTTON = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.overviewOnboardingUsersCard.inviteFirstUsers.button', + { defaultMessage: 'Invite users' } +); + +const INVITE_MORE_USERS_BUTTON = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.overviewOnboardingUsersCard.inviteMoreUsers.button', + { defaultMessage: 'Invite more users' } +); + const ONBOARDING_SOURCES_CARD_DESCRIPTION = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.overviewOnboardingSourcesCard.description', - { defaultMessage: 'Add shared sources for your organization to start searching.' } + { defaultMessage: 'Add organizational sources for your organization to start searching.' } +); + +const ADD_FIRST_SOURCES_BUTTON = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sourcesOnboardingCard.addFirstSources.button', + { defaultMessage: 'Add sources' } +); + +const ADD_MORE_SOURCES_BUTTON = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sourcesOnboardingCard.addMoreSources.button', + { defaultMessage: 'Add more sources' } ); const USERS_CARD_DESCRIPTION = i18n.translate( @@ -67,7 +87,7 @@ export const OnboardingSteps: React.FC = () => { 'xpack.enterpriseSearch.workplaceSearch.sourcesOnboardingCard.description', { defaultMessage: - 'You have added {sourcesCount, number} shared {sourcesCount, plural, one {source} other {sources}}. Happy searching.', + 'You have added {sourcesCount, number} organizational {sourcesCount, plural, one {source} other {sources}}. Happy searching.', values: { sourcesCount }, } ); @@ -77,18 +97,12 @@ export const OnboardingSteps: React.FC = () => { 0 ? 'more' : '' }, - } - )} + actionTitle={sourcesCount > 0 ? ADD_MORE_SOURCES_BUTTON : ADD_FIRST_SOURCES_BUTTON} actionPath={ADD_SOURCE_PATH} complete={hasOrgSources} /> @@ -97,13 +111,7 @@ export const OnboardingSteps: React.FC = () => { testSubj="usersButton" icon="user" description={hasUsers ? USERS_CARD_DESCRIPTION : ONBOARDING_USERS_CARD_DESCRIPTION} - actionTitle={i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.usersOnboardingCard.buttonLabel', - { - defaultMessage: 'Invite {label} users', - values: { label: accountsCount > 0 ? 'more' : '' }, - } - )} + actionTitle={accountsCount > 0 ? INVITE_MORE_USERS_BUTTON : INVITE_FIRST_USERS_BUTTON} actionPath={USERS_AND_ROLES_PATH} complete={hasUsers} /> diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/organization_stats.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/organization_stats.tsx index 5608032f8bdd9..00ad69fbdf372 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/organization_stats.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/organization_stats.tsx @@ -20,9 +20,8 @@ import { OverviewLogic } from './overview_logic'; import { StatisticCard } from './statistic_card'; export const OrganizationStats: React.FC = () => { - const { sourcesCount, pendingInvitationsCount, accountsCount, personalSourcesCount } = useValues( - OverviewLogic - ); + const { sourcesCount, pendingInvitationsCount, accountsCount, privateSourcesCount } = + useValues(OverviewLogic); return ( { { 'xpack.enterpriseSearch.workplaceSearch.organizationStats.privateSources', { defaultMessage: 'Private sources' } )} - count={personalSourcesCount} + count={privateSourcesCount} /> diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview_logic.test.ts index f10918a1afe2d..64e87b77bacef 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview_logic.test.ts @@ -33,7 +33,7 @@ describe('OverviewLogic', () => { hasUsers: true, isOldAccount: true, pendingInvitationsCount: 1, - personalSourcesCount: 1, + privateSourcesCount: 1, sourcesCount: 1, }; @@ -52,7 +52,7 @@ describe('OverviewLogic', () => { expect(OverviewLogic.values.sourcesCount).toEqual(1); expect(OverviewLogic.values.pendingInvitationsCount).toEqual(1); expect(OverviewLogic.values.accountsCount).toEqual(1); - expect(OverviewLogic.values.personalSourcesCount).toEqual(1); + expect(OverviewLogic.values.privateSourcesCount).toEqual(1); expect(OverviewLogic.values.activityFeed).toEqual(feed); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview_logic.ts index 196de02646804..1d79e66e778fb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview_logic.ts @@ -19,7 +19,7 @@ interface OverviewServerData { sourcesCount: number; pendingInvitationsCount: number; accountsCount: number; - personalSourcesCount: number; + privateSourcesCount: number; activityFeed: FeedActivity[]; } @@ -75,10 +75,10 @@ export const OverviewLogic = kea> setServerData: (_, { accountsCount }) => accountsCount, }, ], - personalSourcesCount: [ + privateSourcesCount: [ 0, { - setServerData: (_, { personalSourcesCount }) => personalSourcesCount, + setServerData: (_, { privateSourcesCount }) => privateSourcesCount, }, ], activityFeed: [ diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/group_assignment_selector.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/group_assignment_selector.test.tsx index 94c21f3b5524f..289a5d9212284 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/group_assignment_selector.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/group_assignment_selector.test.tsx @@ -102,9 +102,11 @@ describe('GroupAssignmentSelector', () => { it('handles group checkbox click', async () => { const wrapper = shallow(); await waitFor(() => - ((wrapper.find(EuiComboBox).props() as unknown) as { - onChange: (a: EuiComboBoxOptionOption[]) => void; - }).onChange([{ label: groups[0].name, value: groups[0].name }]) + ( + wrapper.find(EuiComboBox).props() as unknown as { + onChange: (a: EuiComboBoxOptionOption[]) => void; + } + ).onChange([{ label: groups[0].name, value: groups[0].name }]) ); wrapper.update(); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/group_assignment_selector.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/group_assignment_selector.tsx index b53b092eced2d..5d8ef7ababc8c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/group_assignment_selector.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/group_assignment_selector.tsx @@ -25,13 +25,11 @@ import { import { RoleMappingsLogic } from './role_mappings_logic'; export const GroupAssignmentSelector: React.FC = () => { - const { handleAllGroupsSelectionChange, handleGroupSelectionChange } = useActions( - RoleMappingsLogic - ); + const { handleAllGroupsSelectionChange, handleGroupSelectionChange } = + useActions(RoleMappingsLogic); - const { includeInAllGroups, availableGroups, selectedGroups, selectedOptions } = useValues( - RoleMappingsLogic - ); + const { includeInAllGroups, availableGroups, selectedGroups, selectedOptions } = + useValues(RoleMappingsLogic); const hasGroupAssignment = selectedGroups.size > 0 || includeInAllGroups; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.ts index 55f82a07bf405..29103aa0c39af 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.ts @@ -47,11 +47,9 @@ interface RoleMappingsActions extends RoleMappingsBaseActions { setDefaultGroup(availableGroups: RoleGroup[]): { availableGroups: RoleGroup[] }; setRoleMapping(roleMapping: WSRoleMapping): { roleMapping: WSRoleMapping }; setSingleUserRoleMapping(data?: UserMapping): { singleUserRoleMapping: UserMapping }; - setRoleMappings({ - roleMappings, - }: { + setRoleMappings({ roleMappings }: { roleMappings: WSRoleMapping[] }): { roleMappings: WSRoleMapping[]; - }): { roleMappings: WSRoleMapping[] }; + }; setRoleMappingsData(data: RoleMappingsServerDetails): RoleMappingsServerDetails; handleAllGroupsSelectionChange(selected: boolean): { selected: boolean }; handleGroupSelectionChange(groupIds: string[]): { groupIds: string[] }; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/user.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/user.tsx index 6447f43e6ed4d..33785b4a4ce0e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/user.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/user.tsx @@ -24,7 +24,7 @@ import { Role } from '../../types'; import { GroupAssignmentSelector } from './group_assignment_selector'; import { RoleMappingsLogic } from './role_mappings_logic'; -const roleTypes = (['admin', 'user'] as unknown) as Role[]; +const roleTypes = ['admin', 'user'] as unknown as Role[]; export const User: React.FC = () => { const { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/components/oauth_application.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/components/oauth_application.tsx index 075d95f726030..b55828f78344c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/components/oauth_application.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/components/oauth_application.tsx @@ -47,7 +47,7 @@ import { SAVE_CHANGES_BUTTON, NON_PLATINUM_OAUTH_TITLE, NON_PLATINUM_OAUTH_DESCRIPTION, - NON_PLATINUM_OAUTH_LINK, + EXPLORE_PLATINUM_FEATURES_LINK, } from '../../../constants'; import { ENT_SEARCH_LICENSE_MANAGEMENT } from '../../../routes'; import { SettingsLogic } from '../settings_logic'; @@ -101,7 +101,7 @@ export const OauthApplication: React.FC = () => { {NON_PLATINUM_OAUTH_DESCRIPTION} - {NON_PLATINUM_OAUTH_LINK} + {EXPLORE_PLATINUM_FEATURES_LINK} ); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/setup_guide/setup_guide.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/setup_guide/setup_guide.tsx index 05692a0ecab8c..61300bac47734 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/setup_guide/setup_guide.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/setup_guide/setup_guide.tsx @@ -55,7 +55,10 @@ export const SetupGuide: React.FC = () => { - Get started with Workplace Search + diff --git a/x-pack/plugins/enterprise_search/server/index.ts b/x-pack/plugins/enterprise_search/server/index.ts index ecd068c8bdbd9..dae584a883bd7 100644 --- a/x-pack/plugins/enterprise_search/server/index.ts +++ b/x-pack/plugins/enterprise_search/server/index.ts @@ -37,4 +37,5 @@ export const config: PluginConfigDescriptor = { exposeToBrowser: { host: true, }, + deprecations: ({ deprecate }) => [deprecate('enabled', '8.0.0')], }; diff --git a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts index d656c0f889635..3f2a038d8bff3 100644 --- a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts @@ -100,7 +100,7 @@ describe('callEnterpriseSearchConfigAPI', () => { id: 'some-id-string', groups: ['Default', 'Cats'], is_admin: true, - can_create_personal_sources: true, + can_create_private_sources: true, can_create_invitations: true, is_curated: false, viewed_onboarding_page: true, @@ -114,7 +114,7 @@ describe('callEnterpriseSearchConfigAPI', () => { }); it('calls the config API endpoint', async () => { - ((fetch as unknown) as jest.Mock).mockImplementationOnce((url: string) => { + (fetch as unknown as jest.Mock).mockImplementationOnce((url: string) => { expect(url).toEqual('http://localhost:3002/api/ent/v2/internal/client_config'); return Promise.resolve(new Response(JSON.stringify(mockResponse))); }); @@ -130,7 +130,7 @@ describe('callEnterpriseSearchConfigAPI', () => { }); it('falls back without error when data is unavailable', async () => { - ((fetch as unknown) as jest.Mock).mockReturnValueOnce(Promise.resolve(new Response('{}'))); + (fetch as unknown as jest.Mock).mockReturnValueOnce(Promise.resolve(new Response('{}'))); expect(await callEnterpriseSearchConfigAPI(mockDependencies)).toEqual({ access: { @@ -183,7 +183,7 @@ describe('callEnterpriseSearchConfigAPI', () => { id: undefined, groups: [], isAdmin: false, - canCreatePersonalSources: false, + canCreatePrivateSources: false, viewedOnboardingPage: false, }, }, @@ -198,13 +198,13 @@ describe('callEnterpriseSearchConfigAPI', () => { }); it('handles server errors', async () => { - ((fetch as unknown) as jest.Mock).mockReturnValueOnce(Promise.reject('500')); + (fetch as unknown as jest.Mock).mockReturnValueOnce(Promise.reject('500')); expect(await callEnterpriseSearchConfigAPI(mockDependencies)).toEqual({}); expect(mockDependencies.log.error).toHaveBeenCalledWith( 'Could not perform access check to Enterprise Search: 500' ); - ((fetch as unknown) as jest.Mock).mockReturnValueOnce(Promise.resolve('Bad Data')); + (fetch as unknown as jest.Mock).mockReturnValueOnce(Promise.resolve('Bad Data')); expect(await callEnterpriseSearchConfigAPI(mockDependencies)).toEqual({}); expect(mockDependencies.log.error).toHaveBeenCalledWith( 'Could not perform access check to Enterprise Search: TypeError: response.json is not a function' @@ -222,7 +222,7 @@ describe('callEnterpriseSearchConfigAPI', () => { ); // Timeout - ((fetch as unknown) as jest.Mock).mockImplementationOnce(async () => { + (fetch as unknown as jest.Mock).mockImplementationOnce(async () => { jest.advanceTimersByTime(250); return Promise.reject({ name: 'AbortError' }); }); diff --git a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.ts b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.ts index fb3c3b14911a5..146b06e4d9a4c 100644 --- a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.ts +++ b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.ts @@ -124,10 +124,10 @@ export const callEnterpriseSearchConfigAPI = async ({ id: data?.current_user?.workplace_search?.account?.id, groups: data?.current_user?.workplace_search?.account?.groups || [], isAdmin: !!data?.current_user?.workplace_search?.account?.is_admin, - canCreatePersonalSources: !!data?.current_user?.workplace_search?.account - ?.can_create_personal_sources, - viewedOnboardingPage: !!data?.current_user?.workplace_search?.account - ?.viewed_onboarding_page, + canCreatePrivateSources: + !!data?.current_user?.workplace_search?.account?.can_create_private_sources, + viewedOnboardingPage: + !!data?.current_user?.workplace_search?.account?.viewed_onboarding_page, }, }, }; diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_crawl_rules.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_crawl_rules.test.ts index db554d1cec93f..018ab433536b2 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_crawl_rules.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_crawl_rules.test.ts @@ -60,8 +60,7 @@ describe('crawler crawl rules routes', () => { jest.clearAllMocks(); mockRouter = new MockRouter({ method: 'put', - path: - '/internal/app_search/engines/{engineName}/crawler/domains/{domainId}/crawl_rules/{crawlRuleId}', + path: '/internal/app_search/engines/{engineName}/crawler/domains/{domainId}/crawl_rules/{crawlRuleId}', }); registerCrawlerCrawlRulesRoutes({ @@ -105,8 +104,7 @@ describe('crawler crawl rules routes', () => { jest.clearAllMocks(); mockRouter = new MockRouter({ method: 'delete', - path: - '/internal/app_search/engines/{engineName}/crawler/domains/{domainId}/crawl_rules/{crawlRuleId}', + path: '/internal/app_search/engines/{engineName}/crawler/domains/{domainId}/crawl_rules/{crawlRuleId}', }); registerCrawlerCrawlRulesRoutes({ diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_crawl_rules.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_crawl_rules.ts index a360129b07523..7c82c73db7263 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_crawl_rules.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_crawl_rules.ts @@ -38,8 +38,7 @@ export function registerCrawlerCrawlRulesRoutes({ router.put( { - path: - '/internal/app_search/engines/{engineName}/crawler/domains/{domainId}/crawl_rules/{crawlRuleId}', + path: '/internal/app_search/engines/{engineName}/crawler/domains/{domainId}/crawl_rules/{crawlRuleId}', validate: { params: schema.object({ engineName: schema.string(), @@ -64,8 +63,7 @@ export function registerCrawlerCrawlRulesRoutes({ router.delete( { - path: - '/internal/app_search/engines/{engineName}/crawler/domains/{domainId}/crawl_rules/{crawlRuleId}', + path: '/internal/app_search/engines/{engineName}/crawler/domains/{domainId}/crawl_rules/{crawlRuleId}', validate: { params: schema.object({ engineName: schema.string(), diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_entry_points.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_entry_points.test.ts index d59a8e32045ec..6fb7e99400877 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_entry_points.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_entry_points.test.ts @@ -58,8 +58,7 @@ describe('crawler entry point routes', () => { jest.clearAllMocks(); mockRouter = new MockRouter({ method: 'put', - path: - '/internal/app_search/engines/{engineName}/crawler/domains/{domainId}/entry_points/{entryPointId}', + path: '/internal/app_search/engines/{engineName}/crawler/domains/{domainId}/entry_points/{entryPointId}', }); registerCrawlerEntryPointRoutes({ @@ -100,8 +99,7 @@ describe('crawler entry point routes', () => { jest.clearAllMocks(); mockRouter = new MockRouter({ method: 'delete', - path: - '/internal/app_search/engines/{engineName}/crawler/domains/{domainId}/entry_points/{entryPointId}', + path: '/internal/app_search/engines/{engineName}/crawler/domains/{domainId}/entry_points/{entryPointId}', }); registerCrawlerEntryPointRoutes({ diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_entry_points.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_entry_points.ts index 224a79bbf6cb8..a6d6fdb24b41f 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_entry_points.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_entry_points.ts @@ -36,8 +36,7 @@ export function registerCrawlerEntryPointRoutes({ router.put( { - path: - '/internal/app_search/engines/{engineName}/crawler/domains/{domainId}/entry_points/{entryPointId}', + path: '/internal/app_search/engines/{engineName}/crawler/domains/{domainId}/entry_points/{entryPointId}', validate: { params: schema.object({ engineName: schema.string(), @@ -59,8 +58,7 @@ export function registerCrawlerEntryPointRoutes({ router.delete( { - path: - '/internal/app_search/engines/{engineName}/crawler/domains/{domainId}/entry_points/{entryPointId}', + path: '/internal/app_search/engines/{engineName}/crawler/domains/{domainId}/entry_points/{entryPointId}', validate: { params: schema.object({ engineName: schema.string(), diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_sitemaps.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_sitemaps.test.ts index 88fef87e60470..a37a8311093c7 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_sitemaps.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_sitemaps.test.ts @@ -58,8 +58,7 @@ describe('crawler sitemap routes', () => { jest.clearAllMocks(); mockRouter = new MockRouter({ method: 'put', - path: - '/internal/app_search/engines/{engineName}/crawler/domains/{domainId}/sitemaps/{sitemapId}', + path: '/internal/app_search/engines/{engineName}/crawler/domains/{domainId}/sitemaps/{sitemapId}', }); registerCrawlerSitemapRoutes({ @@ -100,8 +99,7 @@ describe('crawler sitemap routes', () => { jest.clearAllMocks(); mockRouter = new MockRouter({ method: 'delete', - path: - '/internal/app_search/engines/{engineName}/crawler/domains/{domainId}/sitemaps/{sitemapId}', + path: '/internal/app_search/engines/{engineName}/crawler/domains/{domainId}/sitemaps/{sitemapId}', }); registerCrawlerSitemapRoutes({ diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_sitemaps.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_sitemaps.ts index c3ae90349b95a..b63473888eecc 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_sitemaps.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_sitemaps.ts @@ -36,8 +36,7 @@ export function registerCrawlerSitemapRoutes({ router.put( { - path: - '/internal/app_search/engines/{engineName}/crawler/domains/{domainId}/sitemaps/{sitemapId}', + path: '/internal/app_search/engines/{engineName}/crawler/domains/{domainId}/sitemaps/{sitemapId}', validate: { params: schema.object({ engineName: schema.string(), @@ -59,8 +58,7 @@ export function registerCrawlerSitemapRoutes({ router.delete( { - path: - '/internal/app_search/engines/{engineName}/crawler/domains/{domainId}/sitemaps/{sitemapId}', + path: '/internal/app_search/engines/{engineName}/crawler/domains/{domainId}/sitemaps/{sitemapId}', validate: { params: schema.object({ engineName: schema.string(), diff --git a/x-pack/plugins/event_log/server/es/init.ts b/x-pack/plugins/event_log/server/es/init.ts index bc1b36ab3e375..f15b9f89887c5 100644 --- a/x-pack/plugins/event_log/server/es/init.ts +++ b/x-pack/plugins/event_log/server/es/init.ts @@ -96,8 +96,9 @@ class EsInitializationSteps { } asyncForEach(Object.keys(indices), async (indexName: string) => { try { - const hidden: string | boolean | undefined = (indices[indexName] - ?.settings as IndicesIndexStatePrefixedSettings)?.index?.hidden; + const hidden: string | boolean | undefined = ( + indices[indexName]?.settings as IndicesIndexStatePrefixedSettings + )?.index?.hidden; // Check to see if this index template is hidden if (hidden !== true && hidden !== 'true') { diff --git a/x-pack/plugins/event_log/server/event_log_client.test.ts b/x-pack/plugins/event_log/server/event_log_client.test.ts index 6babfef9bd8a8..0acb53e93b81a 100644 --- a/x-pack/plugins/event_log/server/event_log_client.test.ts +++ b/x-pack/plugins/event_log/server/event_log_client.test.ts @@ -322,7 +322,7 @@ function fakeEvent(overrides = {}) { function FakeRequest(): KibanaRequest { const savedObjectGetter = jest.fn(); - return ({ + return { headers: {}, getBasePath: () => '', path: '/', @@ -336,5 +336,5 @@ function FakeRequest(): KibanaRequest { }, }, getSavedObjectsClient: () => savedObjectGetter, - } as unknown) as KibanaRequest; + } as unknown as KibanaRequest; } diff --git a/x-pack/plugins/event_log/server/event_log_start_service.test.ts b/x-pack/plugins/event_log/server/event_log_start_service.test.ts index 0815673d546b2..1534623100f5d 100644 --- a/x-pack/plugins/event_log/server/event_log_start_service.test.ts +++ b/x-pack/plugins/event_log/server/event_log_start_service.test.ts @@ -44,7 +44,7 @@ describe('EventLogClientService', () => { function fakeRequest(): KibanaRequest { const savedObjectsClient = savedObjectsClientMock.create(); - return ({ + return { headers: {}, getBasePath: () => '', path: '/', @@ -58,5 +58,5 @@ function fakeRequest(): KibanaRequest { }, }, getSavedObjectsClient: () => savedObjectsClient, - } as unknown) as KibanaRequest; + } as unknown as KibanaRequest; } diff --git a/x-pack/plugins/event_log/server/event_logger.test.ts b/x-pack/plugins/event_log/server/event_logger.test.ts index 9dcfa37ae76b1..d90fd93c60043 100644 --- a/x-pack/plugins/event_log/server/event_logger.test.ts +++ b/x-pack/plugins/event_log/server/event_logger.test.ts @@ -188,7 +188,7 @@ describe('EventLogger', () => { service.registerProviderActions('provider', ['action-a']); eventLogger = service.getLogger({}); - eventLogger.logEvent(({ event: { PROVIDER: 'provider' } } as unknown) as IEvent); + eventLogger.logEvent({ event: { PROVIDER: 'provider' } } as unknown as IEvent); let message = await waitForLogMessage(systemLogger); expect(message).toMatch(/invalid event logged.*provider.*undefined.*/); diff --git a/x-pack/plugins/event_log/server/routes/_mock_handler_arguments.ts b/x-pack/plugins/event_log/server/routes/_mock_handler_arguments.ts index 7563359e2dd16..3cd8e5eaae2ba 100644 --- a/x-pack/plugins/event_log/server/routes/_mock_handler_arguments.ts +++ b/x-pack/plugins/event_log/server/routes/_mock_handler_arguments.ts @@ -18,13 +18,13 @@ export function mockHandlerArguments( res?: Array> ): [RequestHandlerContext, KibanaRequest, KibanaResponseFactory] { return [ - ({ + { eventLog: { getEventLogClient() { return eventLogClient; }, }, - } as unknown) as RequestHandlerContext, + } as unknown as RequestHandlerContext, req as KibanaRequest, mockResponseFactory(res), ]; @@ -39,7 +39,7 @@ export const mockResponseFactory = (resToMock: Array { - return ({ + return { registerProvider: jest.fn(), registerDefaultProvider: jest.fn(), getProvidersClient: jest.fn(), - } as unknown) as jest.Mocked; + } as unknown as jest.Mocked; }; export const savedObjectProviderRegistryMock = { diff --git a/x-pack/plugins/event_log/server/saved_object_provider_registry.test.ts b/x-pack/plugins/event_log/server/saved_object_provider_registry.test.ts index c9a7d4f855153..d3b4ec4648b9f 100644 --- a/x-pack/plugins/event_log/server/saved_object_provider_registry.test.ts +++ b/x-pack/plugins/event_log/server/saved_object_provider_registry.test.ts @@ -83,7 +83,7 @@ describe('SavedObjectProviderRegistry', () => { function fakeRequest(): KibanaRequest { const savedObjectsClient = savedObjectsClientMock.create(); - return ({ + return { headers: {}, getBasePath: () => '', path: '/', @@ -97,5 +97,5 @@ function fakeRequest(): KibanaRequest { }, }, getSavedObjectsClient: () => savedObjectsClient, - } as unknown) as KibanaRequest; + } as unknown as KibanaRequest; } diff --git a/x-pack/plugins/features/server/feature_privilege_iterator/sub_feature_privilege_iterator.ts b/x-pack/plugins/features/server/feature_privilege_iterator/sub_feature_privilege_iterator.ts index 4e43643338830..8dffd553bfe14 100644 --- a/x-pack/plugins/features/server/feature_privilege_iterator/sub_feature_privilege_iterator.ts +++ b/x-pack/plugins/features/server/feature_privilege_iterator/sub_feature_privilege_iterator.ts @@ -19,17 +19,18 @@ export type SubFeaturePrivilegeIterator = ( licenseHasAtLeast: (licenseType: LicenseType) => boolean | undefined ) => IterableIterator; -const subFeaturePrivilegeIterator: SubFeaturePrivilegeIterator = function* subFeaturePrivilegeIterator( - feature: KibanaFeature, - licenseHasAtLeast: (licenseType: LicenseType) => boolean | undefined -): IterableIterator { - for (const subFeature of feature.subFeatures) { - for (const group of subFeature.privilegeGroups) { - yield* group.privileges.filter( - (privilege) => !privilege.minimumLicense || licenseHasAtLeast(privilege.minimumLicense) - ); +const subFeaturePrivilegeIterator: SubFeaturePrivilegeIterator = + function* subFeaturePrivilegeIterator( + feature: KibanaFeature, + licenseHasAtLeast: (licenseType: LicenseType) => boolean | undefined + ): IterableIterator { + for (const subFeature of feature.subFeatures) { + for (const group of subFeature.privilegeGroups) { + yield* group.privileges.filter( + (privilege) => !privilege.minimumLicense || licenseHasAtLeast(privilege.minimumLicense) + ); + } } - } -}; + }; export { subFeaturePrivilegeIterator }; diff --git a/x-pack/plugins/features/server/feature_schema.ts b/x-pack/plugins/features/server/feature_schema.ts index 96d79982afcf1..2694620c62d3f 100644 --- a/x-pack/plugins/features/server/feature_schema.ts +++ b/x-pack/plugins/features/server/feature_schema.ts @@ -152,11 +152,10 @@ const kibanaIndependentSubFeaturePrivilegeSchema = schema.object({ ui: listOfCapabilitiesSchema, }); -const kibanaMutuallyExclusiveSubFeaturePrivilegeSchema = kibanaIndependentSubFeaturePrivilegeSchema.extends( - { +const kibanaMutuallyExclusiveSubFeaturePrivilegeSchema = + kibanaIndependentSubFeaturePrivilegeSchema.extends({ minimumLicense: schema.never(), - } -); + }); const kibanaSubFeatureSchema = schema.object({ name: schema.string(), diff --git a/x-pack/plugins/features/server/plugin.ts b/x-pack/plugins/features/server/plugin.ts index c1fbc4b8a5edb..b11c77b60e66d 100644 --- a/x-pack/plugins/features/server/plugin.ts +++ b/x-pack/plugins/features/server/plugin.ts @@ -85,8 +85,8 @@ export interface PluginStartContract { * Represents Features Plugin instance that will be managed by the Kibana plugin system. */ export class FeaturesPlugin - implements - Plugin, RecursiveReadonly> { + implements Plugin, RecursiveReadonly> +{ private readonly logger: Logger; private readonly featureRegistry: FeatureRegistry = new FeatureRegistry(); private isReportingEnabled: boolean = false; diff --git a/x-pack/plugins/file_upload/public/components/json_upload_and_parse.tsx b/x-pack/plugins/file_upload/public/components/json_upload_and_parse.tsx index 28e99e7ffb18b..1fd96544918c8 100644 --- a/x-pack/plugins/file_upload/public/components/json_upload_and_parse.tsx +++ b/x-pack/plugins/file_upload/public/components/json_upload_and_parse.tsx @@ -100,9 +100,9 @@ export class JsonUploadAndParse extends Component { + > +{ public setup() {} public start(core: CoreStart, plugins: FileUploadStartDependencies): FileUploadStartApi { diff --git a/x-pack/plugins/file_upload/server/capabilities.test.ts b/x-pack/plugins/file_upload/server/capabilities.test.ts index 2fc666c837961..53f9da26060af 100644 --- a/x-pack/plugins/file_upload/server/capabilities.test.ts +++ b/x-pack/plugins/file_upload/server/capabilities.test.ts @@ -61,7 +61,7 @@ describe('setupCapabilities', () => { const security = securityMock.createStart(); security.authz.mode.useRbacForRequest.mockReturnValue(true); coreSetup.getStartServices.mockResolvedValue([ - (undefined as unknown) as CoreStart, + undefined as unknown as CoreStart, { security }, undefined, ]); @@ -104,7 +104,7 @@ describe('setupCapabilities', () => { const mockCheckPrivileges = jest.fn().mockResolvedValue({ hasAllRequested: false }); security.authz.checkPrivilegesDynamicallyWithRequest.mockReturnValue(mockCheckPrivileges); coreSetup.getStartServices.mockResolvedValue([ - (undefined as unknown) as CoreStart, + undefined as unknown as CoreStart, { security }, undefined, ]); @@ -146,7 +146,7 @@ describe('setupCapabilities', () => { const mockCheckPrivileges = jest.fn().mockResolvedValue({ hasAllRequested: true }); security.authz.checkPrivilegesDynamicallyWithRequest.mockReturnValue(mockCheckPrivileges); coreSetup.getStartServices.mockResolvedValue([ - (undefined as unknown) as CoreStart, + undefined as unknown as CoreStart, { security }, undefined, ]); @@ -192,7 +192,7 @@ describe('setupCapabilities', () => { .mockRejectedValue(new Error('this should not have been called')); security.authz.checkPrivilegesDynamicallyWithRequest.mockReturnValue(mockCheckPrivileges); coreSetup.getStartServices.mockResolvedValue([ - (undefined as unknown) as CoreStart, + undefined as unknown as CoreStart, { security }, undefined, ]); @@ -229,7 +229,7 @@ describe('setupCapabilities', () => { const security = securityMock.createStart(); security.authz.mode.useRbacForRequest.mockReturnValue(false); coreSetup.getStartServices.mockResolvedValue([ - (undefined as unknown) as CoreStart, + undefined as unknown as CoreStart, { security }, undefined, ]); diff --git a/x-pack/plugins/file_upload/server/routes.ts b/x-pack/plugins/file_upload/server/routes.ts index c957916e7f321..cb7c37a586b64 100644 --- a/x-pack/plugins/file_upload/server/routes.ts +++ b/x-pack/plugins/file_upload/server/routes.ts @@ -187,9 +187,8 @@ export function fileUploadRoutes(coreSetup: CoreSetup, logge }, async (context, request, response) => { try { - const { - body: indexExists, - } = await context.core.elasticsearch.client.asCurrentUser.indices.exists(request.body); + const { body: indexExists } = + await context.core.elasticsearch.client.asCurrentUser.indices.exists(request.body); return response.ok({ body: { exists: indexExists } }); } catch (e) { return response.customError(wrapError(e)); diff --git a/x-pack/plugins/file_upload/server/utils/runtime_field_utils.ts b/x-pack/plugins/file_upload/server/utils/runtime_field_utils.ts index 921741110d2a3..aba6effe175c0 100644 --- a/x-pack/plugins/file_upload/server/utils/runtime_field_utils.ts +++ b/x-pack/plugins/file_upload/server/utils/runtime_field_utils.ts @@ -6,7 +6,7 @@ */ import { estypes } from '@elastic/elasticsearch'; -import { RUNTIME_FIELD_TYPES } from '../../../../../src/plugins/data/common/index_patterns'; +import { RUNTIME_FIELD_TYPES } from '../../../../../src/plugins/data/common'; type RuntimeType = typeof RUNTIME_FIELD_TYPES[number]; diff --git a/x-pack/plugins/fleet/common/constants/output.ts b/x-pack/plugins/fleet/common/constants/output.ts index 80c7e56dbb52f..9a236001aca25 100644 --- a/x-pack/plugins/fleet/common/constants/output.ts +++ b/x-pack/plugins/fleet/common/constants/output.ts @@ -13,8 +13,10 @@ export const outputType = { Elasticsearch: 'elasticsearch', } as const; +export const DEFAULT_OUTPUT_ID = 'default'; + export const DEFAULT_OUTPUT: NewOutput = { - name: 'default', + name: DEFAULT_OUTPUT_ID, is_default: true, type: outputType.Elasticsearch, hosts: [''], diff --git a/x-pack/plugins/fleet/common/constants/package_policy.ts b/x-pack/plugins/fleet/common/constants/package_policy.ts index f1d6f00d05773..42bbe5bb5b79a 100644 --- a/x-pack/plugins/fleet/common/constants/package_policy.ts +++ b/x-pack/plugins/fleet/common/constants/package_policy.ts @@ -6,3 +6,5 @@ */ export const PACKAGE_POLICY_SAVED_OBJECT_TYPE = 'ingest-package-policies'; + +export const PACKAGE_POLICY_DEFAULT_INDEX_PRIVILEGES = ['auto_configure', 'create_doc']; diff --git a/x-pack/plugins/fleet/common/openapi/bundled.json b/x-pack/plugins/fleet/common/openapi/bundled.json index 4dc67a6771531..c58d064006a2b 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.json +++ b/x-pack/plugins/fleet/common/openapi/bundled.json @@ -2410,6 +2410,16 @@ }, "description": { "type": "string" + }, + "monitoring_enabled": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "metrics", + "logs" + ] + } } } }, diff --git a/x-pack/plugins/fleet/common/openapi/bundled.yaml b/x-pack/plugins/fleet/common/openapi/bundled.yaml index f2a12c0edb8a6..5a5b9838885ae 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.yaml +++ b/x-pack/plugins/fleet/common/openapi/bundled.yaml @@ -1513,6 +1513,13 @@ components: type: string description: type: string + monitoring_enabled: + type: array + items: + type: string + enum: + - metrics + - logs new_package_policy: title: New package policy type: object diff --git a/x-pack/plugins/fleet/common/openapi/components/schemas/new_agent_policy.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/new_agent_policy.yaml index 06048c81d979a..1184e41c5fded 100644 --- a/x-pack/plugins/fleet/common/openapi/components/schemas/new_agent_policy.yaml +++ b/x-pack/plugins/fleet/common/openapi/components/schemas/new_agent_policy.yaml @@ -7,3 +7,10 @@ properties: type: string description: type: string + monitoring_enabled: + type: array + items: + type: string + enum: + - metrics + - logs diff --git a/x-pack/plugins/fleet/common/services/decode_cloud_id.ts b/x-pack/plugins/fleet/common/services/decode_cloud_id.ts index e4952e6b22356..2afc15e8f71b5 100644 --- a/x-pack/plugins/fleet/common/services/decode_cloud_id.ts +++ b/x-pack/plugins/fleet/common/services/decode_cloud_id.ts @@ -6,9 +6,7 @@ */ // decodeCloudId decodes the c.id into c.esURL and c.kibURL -export function decodeCloudId( - cid: string -): +export function decodeCloudId(cid: string): | { host: string; defaultPort: string; diff --git a/x-pack/plugins/fleet/common/services/package_policies_to_agent_inputs.ts b/x-pack/plugins/fleet/common/services/package_policies_to_agent_inputs.ts index f262521461b98..119bb04af5ca8 100644 --- a/x-pack/plugins/fleet/common/services/package_policies_to_agent_inputs.ts +++ b/x-pack/plugins/fleet/common/services/package_policies_to_agent_inputs.ts @@ -11,7 +11,8 @@ import type { PackagePolicy, FullAgentPolicyInput, FullAgentPolicyInputStream } import { DEFAULT_OUTPUT } from '../constants'; export const storedPackagePoliciesToAgentInputs = ( - packagePolicies: PackagePolicy[] + packagePolicies: PackagePolicy[], + outputId: string = DEFAULT_OUTPUT.name ): FullAgentPolicyInput[] => { const fullInputs: FullAgentPolicyInput[] = []; @@ -32,7 +33,7 @@ export const storedPackagePoliciesToAgentInputs = ( data_stream: { namespace: packagePolicy.namespace || 'default', }, - use_output: DEFAULT_OUTPUT.name, + use_output: outputId, ...(input.compiled_input || {}), ...(input.streams.length ? { diff --git a/x-pack/plugins/fleet/common/services/package_to_package_policy.test.ts b/x-pack/plugins/fleet/common/services/package_to_package_policy.test.ts index 0e5cab08edfb7..275cf237a9621 100644 --- a/x-pack/plugins/fleet/common/services/package_to_package_policy.test.ts +++ b/x-pack/plugins/fleet/common/services/package_to_package_policy.test.ts @@ -58,25 +58,25 @@ describe('Fleet - packageToPackagePolicy', () => { it('returns empty array for packages with a config template but no inputs', () => { expect( - packageToPackagePolicyInputs(({ + packageToPackagePolicyInputs({ ...mockPackage, policy_templates: [{ name: 'test_template', inputs: [] }], - } as unknown) as PackageInfo) + } as unknown as PackageInfo) ).toEqual([]); }); it('returns inputs with no streams for packages with no streams', () => { expect( - packageToPackagePolicyInputs(({ + packageToPackagePolicyInputs({ ...mockPackage, policy_templates: [{ name: 'test_template', inputs: [{ type: 'foo' }] }], - } as unknown) as PackageInfo) + } as unknown as PackageInfo) ).toEqual([{ type: 'foo', enabled: true, policy_template: 'test_template', streams: [] }]); expect( - packageToPackagePolicyInputs(({ + packageToPackagePolicyInputs({ ...mockPackage, policy_templates: [{ name: 'test_template', inputs: [{ type: 'foo' }, { type: 'bar' }] }], - } as unknown) as PackageInfo) + } as unknown as PackageInfo) ).toEqual([ { type: 'foo', enabled: true, policy_template: 'test_template', streams: [] }, { type: 'bar', enabled: true, policy_template: 'test_template', streams: [] }, @@ -85,7 +85,7 @@ describe('Fleet - packageToPackagePolicy', () => { it('returns inputs with streams for packages with streams', () => { expect( - packageToPackagePolicyInputs(({ + packageToPackagePolicyInputs({ ...mockPackage, data_streams: [ { type: 'logs', dataset: 'foo', streams: [{ input: 'foo' }] }, @@ -93,7 +93,7 @@ describe('Fleet - packageToPackagePolicy', () => { { type: 'logs', dataset: 'bar2', streams: [{ input: 'bar' }] }, ], policy_templates: [{ name: 'test_template', inputs: [{ type: 'foo' }, { type: 'bar' }] }], - } as unknown) as PackageInfo) + } as unknown as PackageInfo) ).toEqual([ { type: 'foo', @@ -127,7 +127,7 @@ describe('Fleet - packageToPackagePolicy', () => { it('returns inputs with streams configurations for packages with stream vars', () => { expect( - packageToPackagePolicyInputs(({ + packageToPackagePolicyInputs({ ...mockPackage, data_streams: [ { @@ -157,7 +157,7 @@ describe('Fleet - packageToPackagePolicy', () => { }, ], policy_templates: [{ name: 'test_template', inputs: [{ type: 'foo' }, { type: 'bar' }] }], - } as unknown) as PackageInfo) + } as unknown as PackageInfo) ).toEqual([ { type: 'foo', @@ -193,7 +193,7 @@ describe('Fleet - packageToPackagePolicy', () => { it('returns inputs with streams configurations for packages with stream and input vars', () => { expect( - packageToPackagePolicyInputs(({ + packageToPackagePolicyInputs({ ...mockPackage, data_streams: [ { @@ -268,7 +268,7 @@ describe('Fleet - packageToPackagePolicy', () => { ], }, ], - } as unknown) as PackageInfo) + } as unknown as PackageInfo) ).toEqual([ { type: 'foo', @@ -399,10 +399,10 @@ describe('Fleet - packageToPackagePolicy', () => { }); it('returns package policy with inputs', () => { - const mockPackageWithPolicyTemplates = ({ + const mockPackageWithPolicyTemplates = { ...mockPackage, policy_templates: [{ inputs: [{ type: 'foo' }] }], - } as unknown) as PackageInfo; + } as unknown as PackageInfo; expect( packageToPackagePolicy(mockPackageWithPolicyTemplates, '1', '2', 'default', 'pkgPolicy-1') @@ -424,7 +424,7 @@ describe('Fleet - packageToPackagePolicy', () => { it('returns package policy with multiple policy templates (aka has integrations', () => { expect( packageToPackagePolicy( - (AWS_PACKAGE as unknown) as PackageInfo, + AWS_PACKAGE as unknown as PackageInfo, 'some-agent-policy-id', 'some-output-id', 'default', diff --git a/x-pack/plugins/fleet/common/services/validate_package_policy.test.ts b/x-pack/plugins/fleet/common/services/validate_package_policy.test.ts index 30bd9f071feb8..306d31879e0c6 100644 --- a/x-pack/plugins/fleet/common/services/validate_package_policy.test.ts +++ b/x-pack/plugins/fleet/common/services/validate_package_policy.test.ts @@ -15,7 +15,7 @@ import { AWS_PACKAGE, INVALID_AWS_POLICY, VALID_AWS_POLICY } from './fixtures/aw describe('Fleet - validatePackagePolicy()', () => { describe('works for packages with single policy template (aka no integrations)', () => { - const mockPackage = ({ + const mockPackage = { name: 'mock-package', title: 'Mock package', version: '0.0.0', @@ -152,7 +152,7 @@ describe('Fleet - validatePackagePolicy()', () => { ], }, ], - } as unknown) as PackageInfo; + } as unknown as PackageInfo; const validPackagePolicy: NewPackagePolicy = { name: 'pkgPolicy1-1', @@ -544,7 +544,7 @@ describe('Fleet - validatePackagePolicy()', () => { validPackagePolicy, { ...mockPackage, - policy_templates: [({ inputs: [] } as unknown) as RegistryPolicyTemplate], + policy_templates: [{ inputs: [] } as unknown as RegistryPolicyTemplate], }, safeLoad ) @@ -562,7 +562,7 @@ describe('Fleet - validatePackagePolicy()', () => { expect( validatePackagePolicy( INVALID_AWS_POLICY as NewPackagePolicy, - (AWS_PACKAGE as unknown) as PackageInfo, + AWS_PACKAGE as unknown as PackageInfo, safeLoad ) ).toMatchSnapshot(); @@ -573,7 +573,7 @@ describe('Fleet - validatePackagePolicy()', () => { validationHasErrors( validatePackagePolicy( VALID_AWS_POLICY as NewPackagePolicy, - (AWS_PACKAGE as unknown) as PackageInfo, + AWS_PACKAGE as unknown as PackageInfo, safeLoad ) ) diff --git a/x-pack/plugins/fleet/common/types/index.ts b/x-pack/plugins/fleet/common/types/index.ts index 0deda3bf32657..bd970fc2cd83e 100644 --- a/x-pack/plugins/fleet/common/types/index.ts +++ b/x-pack/plugins/fleet/common/types/index.ts @@ -8,7 +8,11 @@ export * from './models'; export * from './rest_spec'; -import type { PreconfiguredAgentPolicy, PreconfiguredPackage } from './models/preconfiguration'; +import type { + PreconfiguredAgentPolicy, + PreconfiguredPackage, + PreconfiguredOutput, +} from './models/preconfiguration'; export interface FleetConfigType { enabled: boolean; @@ -26,6 +30,7 @@ export interface FleetConfigType { }; agentPolicies?: PreconfiguredAgentPolicy[]; packages?: PreconfiguredPackage[]; + outputs?: PreconfiguredOutput[]; agentIdVerificationEnabled?: boolean; } diff --git a/x-pack/plugins/fleet/common/types/models/agent_policy.ts b/x-pack/plugins/fleet/common/types/models/agent_policy.ts index f64467ca674fb..3f9e43e72c51d 100644 --- a/x-pack/plugins/fleet/common/types/models/agent_policy.ts +++ b/x-pack/plugins/fleet/common/types/models/agent_policy.ts @@ -23,6 +23,8 @@ export interface NewAgentPolicy { monitoring_enabled?: MonitoringType; unenroll_timeout?: number; is_preconfigured?: boolean; + data_output_id?: string; + monitoring_output_id?: string; } export interface AgentPolicy extends NewAgentPolicy { @@ -71,12 +73,14 @@ export interface FullAgentPolicyOutputPermissions { }; } +export type FullAgentPolicyOutput = Pick & { + [key: string]: any; +}; + export interface FullAgentPolicy { id: string; outputs: { - [key: string]: Pick & { - [key: string]: any; - }; + [key: string]: FullAgentPolicyOutput; }; output_permissions?: { [output: string]: FullAgentPolicyOutputPermissions; diff --git a/x-pack/plugins/fleet/common/types/models/epm.ts b/x-pack/plugins/fleet/common/types/models/epm.ts index bbb571f963dc9..66852bc965b07 100644 --- a/x-pack/plugins/fleet/common/types/models/epm.ts +++ b/x-pack/plugins/fleet/common/types/models/epm.ts @@ -281,7 +281,6 @@ export enum RegistryDataStreamKeys { ingest_pipeline = 'ingest_pipeline', elasticsearch = 'elasticsearch', dataset_is_prefix = 'dataset_is_prefix', - permissions = 'permissions', } export interface RegistryDataStream { @@ -297,15 +296,15 @@ export interface RegistryDataStream { [RegistryDataStreamKeys.ingest_pipeline]?: string; [RegistryDataStreamKeys.elasticsearch]?: RegistryElasticsearch; [RegistryDataStreamKeys.dataset_is_prefix]?: boolean; - [RegistryDataStreamKeys.permissions]?: RegistryDataStreamPermissions; } export interface RegistryElasticsearch { + privileges?: RegistryDataStreamPrivileges; 'index_template.settings'?: estypes.IndicesIndexSettings; 'index_template.mappings'?: estypes.MappingTypeMapping; } -export interface RegistryDataStreamPermissions { +export interface RegistryDataStreamPrivileges { cluster?: string[]; indices?: string[]; } diff --git a/x-pack/plugins/fleet/common/types/models/output.ts b/x-pack/plugins/fleet/common/types/models/output.ts index c1dc2a4b4e058..4f70460e89ff8 100644 --- a/x-pack/plugins/fleet/common/types/models/output.ts +++ b/x-pack/plugins/fleet/common/types/models/output.ts @@ -17,11 +17,13 @@ export interface NewOutput { hosts?: string[]; ca_sha256?: string; api_key?: string; - config?: Record; config_yaml?: string; + is_preconfigured?: boolean; } -export type OutputSOAttributes = NewOutput; +export type OutputSOAttributes = NewOutput & { + output_id?: string; +}; export type Output = NewOutput & { id: string; diff --git a/x-pack/plugins/fleet/common/types/models/package_policy.ts b/x-pack/plugins/fleet/common/types/models/package_policy.ts index ab977c5d67d0d..aca537ae31b52 100644 --- a/x-pack/plugins/fleet/common/types/models/package_policy.ts +++ b/x-pack/plugins/fleet/common/types/models/package_policy.ts @@ -25,6 +25,11 @@ export interface NewPackagePolicyInputStream { data_stream: { dataset: string; type: string; + elasticsearch?: { + privileges?: { + indices?: string[]; + }; + }; }; vars?: PackagePolicyConfigRecord; config?: PackagePolicyConfigRecord; diff --git a/x-pack/plugins/fleet/common/types/models/preconfiguration.ts b/x-pack/plugins/fleet/common/types/models/preconfiguration.ts index 6087c910510cc..17f9b946885b1 100644 --- a/x-pack/plugins/fleet/common/types/models/preconfiguration.ts +++ b/x-pack/plugins/fleet/common/types/models/preconfiguration.ts @@ -11,6 +11,7 @@ import type { NewPackagePolicyInput, } from './package_policy'; import type { NewAgentPolicy } from './agent_policy'; +import type { Output } from './output'; export type InputsOverride = Partial & { vars?: Array; @@ -29,3 +30,7 @@ export interface PreconfiguredAgentPolicy extends Omit; + +export interface PreconfiguredOutput extends Omit { + config?: Record; +} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx index f89bd5ef48d72..8229aced234fa 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx @@ -420,14 +420,14 @@ export const AgentPolicyForm: React.FunctionComponent = ({ <> {' '} packageInputStreams.length > 0, [ - packageInputStreams.length, - ]); + const hasInputStreams = useMemo( + () => packageInputStreams.length > 0, + [packageInputStreams.length] + ); const inputStreams = useMemo( () => packageInputStreams diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx index 1f7fa0ceb354b..a83f6902ed056 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx @@ -20,6 +20,7 @@ import { EuiFlexItem, EuiSpacer, EuiLink, + EuiErrorBoundary, } from '@elastic/eui'; import type { EuiStepProps } from '@elastic/eui/src/components/steps/step'; import type { ApplicationStart } from 'kibana/public'; @@ -90,9 +91,10 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => { const { search } = useLocation(); const queryParams = useMemo(() => new URLSearchParams(search), [search]); - const queryParamsPolicyId = useMemo(() => queryParams.get('policyId') ?? undefined, [ - queryParams, - ]); + const queryParamsPolicyId = useMemo( + () => queryParams.get('policyId') ?? undefined, + [queryParams] + ); /** * Please note: policyId can come from one of two sources. The URL param (in the URL path) or @@ -503,70 +505,75 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => { return ( - {formState === 'CONFIRM' && agentPolicy && ( - setFormState('VALID')} - /> - )} - {packageInfo && ( - - )} - - - - - - - {!isLoadingAgentPolicyStep && agentPolicy && packageInfo && formState === 'INVALID' ? ( - - ) : null} - - - - - {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} - - - - - - - - - - - - - + + {formState === 'CONFIRM' && agentPolicy && ( + setFormState('VALID')} + /> + )} + {packageInfo && ( + + )} + + + + + + + {!isLoadingAgentPolicyStep && + agentPolicy && + packageInfo && + formState === 'INVALID' ? ( + + ) : null} + + + + + {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} + + + + + + + + + + + + + + ); }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_select_agent_policy.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_select_agent_policy.tsx index e2f5a7249ff0a..63cf1a0c87b29 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_select_agent_policy.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_select_agent_policy.tsx @@ -65,9 +65,8 @@ export const StepSelectAgentPolicy: React.FunctionComponent<{ // Create new agent policy flyout state const hasWriteCapabilites = useCapabilities().write; - const [isCreateAgentPolicyFlyoutOpen, setIsCreateAgentPolicyFlyoutOpen] = useState( - false - ); + const [isCreateAgentPolicyFlyoutOpen, setIsCreateAgentPolicyFlyoutOpen] = + useState(false); // Fetch package info const { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/index.tsx index 9d3fe64b6639d..eec7d80b75c4b 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/index.tsx @@ -312,7 +312,7 @@ export const AgentPolicyDetailsPage: React.FunctionComponent = () => { {content} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx index a36bc988da89f..35092cb67f7ef 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx @@ -25,6 +25,7 @@ import { EuiFlyoutBody, EuiFlyoutHeader, EuiTitle, + EuiErrorBoundary, } from '@elastic/eui'; import styled from 'styled-components'; @@ -96,9 +97,8 @@ export const EditPackagePolicyForm = memo<{ inputs: [], version: '', }); - const [originalPackagePolicy, setOriginalPackagePolicy] = useState< - GetOnePackagePolicyResponse['item'] - >(); + const [originalPackagePolicy, setOriginalPackagePolicy] = + useState(); const [dryRunData, setDryRunData] = useState(); const policyId = agentPolicy?.id ?? ''; @@ -109,10 +109,8 @@ export const EditPackagePolicyForm = memo<{ setIsLoadingData(true); setLoadingError(undefined); try { - const { - data: packagePolicyData, - error: packagePolicyError, - } = await sendGetOnePackagePolicy(packagePolicyId); + const { data: packagePolicyData, error: packagePolicyError } = + await sendGetOnePackagePolicy(packagePolicyId); if (packagePolicyError) { throw packagePolicyError; @@ -510,93 +508,95 @@ export const EditPackagePolicyForm = memo<{ return ( - {isLoadingData ? ( - - ) : loadingError || !agentPolicy || !packageInfo ? ( - - } - error={ - loadingError || - i18n.translate('xpack.fleet.editPackagePolicy.errorLoadingDataMessage', { - defaultMessage: 'There was an error loading this integration information', - }) - } - /> - ) : ( - <> - + {isLoadingData ? ( + + ) : loadingError || !agentPolicy || !packageInfo ? ( + + } + error={ + loadingError || + i18n.translate('xpack.fleet.editPackagePolicy.errorLoadingDataMessage', { + defaultMessage: 'There was an error loading this integration information', + }) + } /> - {formState === 'CONFIRM' && ( - setFormState('VALID')} + ) : ( + <> + - )} - {isUpgrade && dryRunData && ( - <> - - - - )} - {configurePackage} - {/* Extra space to accomodate the EuiBottomBar height */} - - - - - - {agentPolicy && packageInfo && formState === 'INVALID' ? ( - - ) : null} - - - - - - - - - - - - - - - - - - - )} + {formState === 'CONFIRM' && ( + setFormState('VALID')} + /> + )} + {isUpgrade && dryRunData && ( + <> + + + + )} + {configurePackage} + {/* Extra space to accomodate the EuiBottomBar height */} + + + + + + {agentPolicy && packageInfo && formState === 'INVALID' ? ( + + ) : null} + + + + + + + + + + + + + + + + + + + )} + ); }); @@ -726,7 +726,7 @@ const UpgradeStatusCallout: React.FunctionComponent<{ > = () => { ); // Fetch agent policies - const { isLoading, data: agentPolicyData, resendRequest } = useGetAgentPolicies({ + const { + isLoading, + data: agentPolicyData, + resendRequest, + } = useGetAgentPolicies({ page: pagination.currentPage, perPage: pagination.pageSize, sortField: sorting?.field, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/agent_logs.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/agent_logs.tsx index f927bdcc2e518..783a960aff120 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/agent_logs.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/agent_logs.tsx @@ -245,9 +245,10 @@ export const AgentLogsUI: React.FunctionComponent = memo( // Set absolute height on logs component (needed to render correctly in Safari) // based on available height, or 600px, whichever is greater const [logsPanelRef, { height: measuredlogPanelHeight }] = useMeasure(); - const logPanelHeight = useMemo(() => Math.max(measuredlogPanelHeight, 600), [ - measuredlogPanelHeight, - ]); + const logPanelHeight = useMemo( + () => Math.max(measuredlogPanelHeight, 600), + [measuredlogPanelHeight] + ); if (!isLogFeatureAvailable) { return ( diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/index.tsx index c138d23cce93b..5411e6313ebb7 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/index.tsx @@ -22,59 +22,58 @@ import { DEFAULT_LOGS_STATE, STATE_STORAGE_KEY } from './constants'; import type { AgentLogsProps, AgentLogsState } from './agent_logs'; import { AgentLogsUI, AgentLogsUrlStateHelper } from './agent_logs'; -export const AgentLogs: React.FunctionComponent< - Pick -> = memo(({ agent, agentPolicy }) => { - const stateContainer = useMemo( - () => - createStateContainer< - AgentLogsState, - { - update: PureTransition]>; - } - >( - { - ...DEFAULT_LOGS_STATE, - ...getStateFromKbnUrl(STATE_STORAGE_KEY, window.location.href, { - getFromHashQuery: false, - }), - }, - { - update: (state) => (updatedState) => ({ ...state, ...updatedState }), - } - ), - [] - ); +export const AgentLogs: React.FunctionComponent> = + memo(({ agent, agentPolicy }) => { + const stateContainer = useMemo( + () => + createStateContainer< + AgentLogsState, + { + update: PureTransition]>; + } + >( + { + ...DEFAULT_LOGS_STATE, + ...getStateFromKbnUrl(STATE_STORAGE_KEY, window.location.href, { + getFromHashQuery: false, + }), + }, + { + update: (state) => (updatedState) => ({ ...state, ...updatedState }), + } + ), + [] + ); - const AgentLogsConnected = useMemo( - () => - AgentLogsUrlStateHelper.connect((state) => ({ - state: state || DEFAULT_LOGS_STATE, - }))(AgentLogsUI), - [] - ); + const AgentLogsConnected = useMemo( + () => + AgentLogsUrlStateHelper.connect((state) => ({ + state: state || DEFAULT_LOGS_STATE, + }))(AgentLogsUI), + [] + ); - const [isSyncReady, setIsSyncReady] = useState(false); + const [isSyncReady, setIsSyncReady] = useState(false); - useEffect(() => { - const stateStorage = createKbnUrlStateStorage(); - const { start, stop } = syncState({ - storageKey: STATE_STORAGE_KEY, - stateContainer: stateContainer as INullableBaseStateContainer, - stateStorage, - }); - start(); - setIsSyncReady(true); + useEffect(() => { + const stateStorage = createKbnUrlStateStorage(); + const { start, stop } = syncState({ + storageKey: STATE_STORAGE_KEY, + stateContainer: stateContainer as INullableBaseStateContainer, + stateStorage, + }); + start(); + setIsSyncReady(true); - return () => { - stop(); - stateContainer.set(DEFAULT_LOGS_STATE); - }; - }, [stateContainer]); + return () => { + stop(); + stateContainer.set(DEFAULT_LOGS_STATE); + }; + }, [stateContainer]); - return ( - - {isSyncReady ? : null} - - ); -}); + return ( + + {isSyncReady ? : null} + + ); + }); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/index.tsx index d2342dafca2eb..f3430110ba283 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/index.tsx @@ -254,7 +254,7 @@ export const AgentDetailsPage: React.FunctionComponent = () => { {isLoading && isInitialRequest ? ( diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.tsx index 494541a00fc0a..18a684baad60d 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.tsx @@ -39,9 +39,10 @@ export const AgentsApp: React.FunctionComponent = () => { perPage: 1000, }); - const agentPolicies = useMemo(() => agentPoliciesRequest.data?.items || [], [ - agentPoliciesRequest.data, - ]); + const agentPolicies = useMemo( + () => agentPoliciesRequest.data?.items || [], + [agentPoliciesRequest.data] + ); const fleetStatus = useFleetStatus(); diff --git a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_local_search.tsx b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_local_search.tsx index fc2966697418a..06c4bac5c7e8e 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_local_search.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_local_search.tsx @@ -11,7 +11,7 @@ import { useEffect, useRef } from 'react'; import type { PackageList } from '../../../types'; export const searchIdField = 'id'; -export const fieldsToSearch = ['name', 'title']; +export const fieldsToSearch = ['name', 'title', 'description']; export function useLocalSearch(packageList: PackageList) { const localSearchRef = useRef(null); 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 edbe06f33b18e..a59d4422348fb 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 @@ -96,6 +96,7 @@ function usePackageInstall({ notifications }: { notifications: NotificationsStar }); history.push(settingsPath); } + notifications.toasts.addSuccess({ title: toMountPoint( { + return ( +
    + +
    + ); +}; + +AssetsFacetGroup.args = args; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.stories.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.stories.tsx new file mode 100644 index 0000000000000..e8814b8b8c877 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.stories.tsx @@ -0,0 +1,79 @@ +/* + * 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 type { SavedObject } from 'src/core/public'; + +import type { Installation } from '../../../../../../common'; + +import type { PackageCardProps } from './package_card'; +import { PackageCard } from './package_card'; + +export default { + title: 'Sections/EPM/Package Card', + description: 'A card representing a package available in Fleet', +}; + +type Args = Omit & { width: number }; + +const args: Args = { + width: 250, + title: 'Title', + description: 'Description', + name: 'beats', + release: 'ga', + id: 'id', + version: '1.0.0', + download: '/', + path: 'path', +}; + +const argTypes = { + release: { + control: { + type: 'radio', + options: ['ga', 'beta', 'experimental'], + }, + }, +}; + +export const NotInstalled = ({ width, ...props }: Args) => ( +
    + +
    +); + +export const Installed = ({ width, ...props }: Args) => { + const savedObject: SavedObject = { + id: props.id, + type: props.type || '', + attributes: { + name: props.name, + version: props.version, + install_version: props.version, + es_index_patterns: {}, + installed_kibana: [], + installed_es: [], + install_status: 'installed', + install_source: 'registry', + install_started_at: '2020-01-01T00:00:00.000Z', + }, + references: [], + }; + + return ( +
    + +
    + ); +}; + +NotInstalled.args = args; +NotInstalled.argTypes = argTypes; +Installed.args = args; +Installed.argTypes = argTypes; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx index c12e67fdb5718..c2d6d0f1e028b 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx @@ -15,7 +15,7 @@ import { PackageIcon } from '../../../components'; import { RELEASE_BADGE_LABEL, RELEASE_BADGE_DESCRIPTION } from './release_badge'; -type PackageCardProps = PackageListItem; +export type PackageCardProps = PackageListItem; // adding the `href` causes EuiCard to use a `a` instead of a `button` // `a` tags use `euiLinkColor` which results in blueish Badge text diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.stories.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.stories.tsx new file mode 100644 index 0000000000000..d84e286b6f560 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.stories.tsx @@ -0,0 +1,138 @@ +/* + * 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 { action } from '@storybook/addon-actions'; + +import type { SavedObject } from 'src/core/public'; + +import type { Installation } from '../../../../../../common'; + +import type { ListProps } from './package_list_grid'; +import { PackageListGrid } from './package_list_grid'; + +export default { + component: PackageListGrid, + title: 'Sections/EPM/Package List Grid', +}; + +type Args = Pick; + +const args: Args = { + title: 'Installed integrations', + isLoading: false, + showMissingIntegrationMessage: false, +}; + +const savedObject: SavedObject = { + id: 'id', + type: 'integration', + attributes: { + name: 'savedObject', + version: '1.2.3', + install_version: '1.2.3', + es_index_patterns: {}, + installed_kibana: [], + installed_es: [], + install_status: 'installed', + install_source: 'registry', + install_started_at: '2020-01-01T00:00:00.000Z', + }, + references: [], +}; + +export const EmptyList = (props: Args) => ( + +); + +export const List = (props: Args) => ( + +); + +EmptyList.args = args; +List.args = args; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.tsx index 6bbd479c5c2ba..db63c5c7dd832 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.tsx @@ -28,7 +28,7 @@ import { useLocalSearch, searchIdField } from '../../../hooks'; import { PackageCard } from './package_card'; -interface ListProps { +export interface ListProps { isLoading?: boolean; controls?: ReactNode; title: string; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/requirements.stories.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/requirements.stories.tsx new file mode 100644 index 0000000000000..205d739d48696 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/requirements.stories.tsx @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { Requirements as Component } from './requirements'; + +export default { + component: Component, + title: 'Sections/EPM/Requirements', +}; + +interface Args { + width: number; +} + +const args: Args = { + width: 250, +}; + +export const Requirements = ({ width }: Args) => { + return ( +
    + +
    + ); +}; + +Requirements.args = args; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx index 26869f8fea574..82436eb4d3f51 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx @@ -79,6 +79,13 @@ const FlexItemWithMinWidth = styled(EuiFlexItem)` min-width: 0px; `; +// to limit size of iconpanel, making the header too big +const FlexItemWithMaxHeight = styled(EuiFlexItem)` + @media (min-width: 768px) { + max-height: 60px; + } +`; + function Breadcrumbs({ packageTitle }: { packageTitle: string }) { useBreadcrumbs('integration_details_overview', { pkgTitle: packageTitle }); return null; @@ -117,9 +124,11 @@ export function Detail() { semverLt(packageInfo.savedObject.attributes.version, packageInfo.latestVersion); // Fetch package info - const { data: packageInfoData, error: packageInfoError, isLoading } = useGetPackageInfoByKey( - pkgkey - ); + const { + data: packageInfoData, + error: packageInfoError, + isLoading, + } = useGetPackageInfoByKey(pkgkey); const showCustomTab = useUIExtension(packageInfoData?.response.name ?? '', 'package-detail-custom') !== undefined; @@ -173,7 +182,7 @@ export function Detail() {
    - + {isLoading || !packageInfo ? ( ) : ( @@ -184,7 +193,7 @@ export function Detail() { icons={integrationInfo?.icons || packageInfo.icons} /> )} - + @@ -296,7 +305,7 @@ export function Detail() { {packageInfo.version} {updateAvailable ? ( - + ) : null} diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/overview.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/overview.tsx index 945859ac81ffd..0b477fee2ba77 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/overview.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/overview.tsx @@ -27,10 +27,10 @@ const LeftColumn = styled(EuiFlexItem)` `; export const OverviewPage: React.FC = memo(({ packageInfo, integrationInfo }) => { - const screenshots = useMemo(() => integrationInfo?.screenshots || packageInfo.screenshots || [], [ - integrationInfo, - packageInfo.screenshots, - ]); + const screenshots = useMemo( + () => integrationInfo?.screenshots || packageInfo.screenshots || [], + [integrationInfo, packageInfo.screenshots] + ); return ( diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx index 07a61410e9a6b..92b4012011fc8 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx @@ -84,9 +84,10 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps const { search } = useLocation(); const history = useHistory(); const queryParams = useMemo(() => new URLSearchParams(search), [search]); - const agentPolicyIdFromParams = useMemo(() => queryParams.get('addAgentToPolicyId'), [ - queryParams, - ]); + const agentPolicyIdFromParams = useMemo( + () => queryParams.get('addAgentToPolicyId'), + [queryParams] + ); const [flyoutOpenForPolicyId, setFlyoutOpenForPolicyId] = useState( agentPolicyIdFromParams ); @@ -94,7 +95,11 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps const getPackageInstallStatus = useGetPackageInstallStatus(); const packageInstallStatus = getPackageInstallStatus(name); const { pagination, pageSizeOptions, setPagination } = useUrlPagination(); - const { data, isLoading, resendRequest: refreshPolicies } = usePackagePoliciesWithAgentPolicy({ + const { + data, + isLoading, + resendRequest: refreshPolicies, + } = usePackagePoliciesWithAgentPolicy({ page: pagination.currentPage, perPage: pagination.pageSize, kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name: ${name}`, diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/use_package_policies_with_agent_policy.ts b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/use_package_policies_with_agent_policy.ts index 33c1d3ff77302..4f6f456bf82cd 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/use_package_policies_with_agent_policy.ts +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/use_package_policies_with_agent_policy.ts @@ -74,18 +74,16 @@ export const usePackagePoliciesWithAgentPolicy = ( .join(' or ')}) `; }, [packagePoliciesData]); - const { - data: agentPoliciesData, - isLoading: isLoadingAgentPolicies, - } = useConditionalRequest({ - path: agentPolicyRouteService.getListPath(), - method: 'get', - query: { - perPage: 100, - kuery: agentPoliciesFilter, - }, - shouldSendRequest: !!packagePoliciesData?.items.length, - } as SendConditionalRequestConfig); + const { data: agentPoliciesData, isLoading: isLoadingAgentPolicies } = + useConditionalRequest({ + path: agentPolicyRouteService.getListPath(), + method: 'get', + query: { + perPage: 100, + kuery: agentPoliciesFilter, + }, + shouldSendRequest: !!packagePoliciesData?.items.length, + } as SendConditionalRequestConfig); const [enrichedData, setEnrichedData] = useState(); diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/install_button.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/install_button.tsx new file mode 100644 index 0000000000000..f2813058afe5a --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/install_button.tsx @@ -0,0 +1,84 @@ +/* + * 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 { EuiButton } from '@elastic/eui'; +import React, { Fragment, useCallback, useState } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import type { PackageInfo, UpgradePackagePolicyDryRunResponse } from '../../../../../types'; +import { InstallStatus } from '../../../../../types'; +import { + useCapabilities, + useGetPackageInstallStatus, + useInstallPackage, +} from '../../../../../hooks'; + +import { ConfirmPackageInstall } from './confirm_package_install'; + +type InstallationButtonProps = Pick & { + disabled?: boolean; + dryRunData?: UpgradePackagePolicyDryRunResponse | null; + isUpgradingPackagePolicies?: boolean; + latestVersion?: string; + numOfAssets: number; + packagePolicyIds?: string[]; + setIsUpgradingPackagePolicies?: React.Dispatch>; +}; +export function InstallButton(props: InstallationButtonProps) { + const { name, numOfAssets, title, version } = props; + const hasWriteCapabilites = useCapabilities().write; + const installPackage = useInstallPackage(); + const getPackageInstallStatus = useGetPackageInstallStatus(); + const { status: installationStatus } = getPackageInstallStatus(name); + + const isInstalling = installationStatus === InstallStatus.installing; + const [isInstallModalVisible, setIsInstallModalVisible] = useState(false); + + const toggleInstallModal = useCallback(() => { + setIsInstallModalVisible(!isInstallModalVisible); + }, [isInstallModalVisible]); + + const handleClickInstall = useCallback(() => { + installPackage({ name, version, title }); + toggleInstallModal(); + }, [installPackage, name, title, toggleInstallModal, version]); + + const installModal = ( + + ); + + return hasWriteCapabilites ? ( + + + {isInstalling ? ( + + ) : ( + + )} + + + {isInstallModalVisible && installModal} + + ) : null; +} 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 deleted file mode 100644 index eab28a051f061..0000000000000 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/installation_button.tsx +++ /dev/null @@ -1,161 +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 { EuiButton } from '@elastic/eui'; -import React, { Fragment, useCallback, useMemo, useState } from 'react'; -import { FormattedMessage } from '@kbn/i18n/react'; - -import type { PackageInfo } from '../../../../../types'; -import { InstallStatus } from '../../../../../types'; -import { - useCapabilities, - useUninstallPackage, - useGetPackageInstallStatus, - useInstallPackage, -} from '../../../../../hooks'; - -import { ConfirmPackageUninstall } from './confirm_package_uninstall'; -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, latestVersion } = props; - const hasWriteCapabilites = useCapabilities().write; - const installPackage = useInstallPackage(); - const uninstallPackage = useUninstallPackage(); - const getPackageInstallStatus = useGetPackageInstallStatus(); - const { status: installationStatus } = getPackageInstallStatus(name); - - const isInstalling = installationStatus === InstallStatus.installing; - const isRemoving = installationStatus === InstallStatus.uninstalling; - const isInstalled = installationStatus === InstallStatus.installed; - const showUninstallButton = isInstalled || isRemoving; - const [isModalVisible, setModalVisible] = useState(false); - const toggleModal = useCallback(() => { - setModalVisible(!isModalVisible); - }, [isModalVisible]); - - const handleClickInstall = useCallback(() => { - installPackage({ name, version, title }); - toggleModal(); - }, [installPackage, name, title, toggleModal, version]); - - const handleClickUpdate = useCallback(() => { - installPackage({ name, version, title, fromUpdate: true }); - }, [installPackage, name, title, version]); - - const handleClickUninstall = useCallback(() => { - uninstallPackage({ name, version, title, redirectToVersion: latestVersion ?? version }); - toggleModal(); - }, [uninstallPackage, name, title, toggleModal, version, latestVersion]); - - // counts the number of assets in the package - const numOfAssets = useMemo( - () => - Object.entries(assets).reduce( - (acc, [serviceName, serviceNameValue]) => - acc + - Object.entries(serviceNameValue).reduce( - (acc2, [assetName, assetNameValue]) => acc2 + assetNameValue.length, - 0 - ), - 0 - ), - [assets] - ); - - const installButton = ( - - {isInstalling ? ( - - ) : ( - - )} - - ); - - const updateButton = ( - - - - ); - - const uninstallButton = ( - - {isRemoving ? ( - - ) : ( - - )} - - ); - - const uninstallModal = ( - - ); - - const installModal = ( - - ); - - return hasWriteCapabilites ? ( - - {isUpdate ? updateButton : showUninstallButton ? uninstallButton : installButton} - {isModalVisible && (isInstalled ? uninstallModal : installModal)} - - ) : null; -} 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 98cc172197d44..07c95e0d77ec7 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 @@ -5,30 +5,42 @@ * 2.0. */ -import React, { memo } from 'react'; +import React, { memo, useEffect, useMemo, useState } from 'react'; import styled from 'styled-components'; import { FormattedMessage } from '@kbn/i18n/react'; import semverLt from 'semver/functions/lt'; -import { EuiTitle, EuiFlexGroup, EuiFlexItem, EuiText, EuiSpacer, EuiLink } from '@elastic/eui'; +import { + EuiCallOut, + EuiTitle, + EuiFlexGroup, + EuiFlexItem, + EuiText, + EuiSpacer, + EuiLink, +} from '@elastic/eui'; -import type { PackageInfo } from '../../../../../types'; +import { i18n } from '@kbn/i18n'; + +import type { PackageInfo, UpgradePackagePolicyDryRunResponse } from '../../../../../types'; import { InstallStatus } from '../../../../../types'; -import { useGetPackagePolicies, useGetPackageInstallStatus, useLink } from '../../../../../hooks'; +import { + useGetPackagePolicies, + useGetPackageInstallStatus, + useLink, + sendUpgradePackagePolicyDryRun, +} from '../../../../../hooks'; import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../../../constants'; -import { UpdateIcon } from '../components'; -import { InstallationButton } from './installation_button'; +import { InstallButton } from './install_button'; +import { UpdateButton } from './update_button'; +import { UninstallButton } from './uninstall_button'; const SettingsTitleCell = styled.td` padding-right: ${(props) => props.theme.eui.spacerSizes.xl}; padding-bottom: ${(props) => props.theme.eui.spacerSizes.m}; `; -const UpdatesAvailableMsgContainer = styled.span` - padding-left: ${(props) => props.theme.eui.spacerSizes.s}; -`; - const NoteLabel = () => ( ( /> ); -const UpdatesAvailableMsg = () => ( - - +const UpdatesAvailableMsg = ({ latestVersion }: { latestVersion: string }) => ( + - + ); const LatestVersionLink = ({ name, version }: { name: string; version: string }) => { @@ -68,14 +86,35 @@ interface Props { export const SettingsPage: React.FC = memo(({ packageInfo }: Props) => { const { name, title, removable, latestVersion, version } = packageInfo; + const [dryRunData, setDryRunData] = useState(); + const [isUpgradingPackagePolicies, setIsUpgradingPackagePolicies] = useState(false); const getPackageInstallStatus = useGetPackageInstallStatus(); const { data: packagePoliciesData } = useGetPackagePolicies({ - perPage: 0, + perPage: 1000, page: 1, kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${name}`, }); + const { status: installationStatus, version: installedVersion } = getPackageInstallStatus(name); const packageHasUsages = !!packagePoliciesData?.total; + + const packagePolicyIds = useMemo( + () => packagePoliciesData?.items.map(({ id }) => id), + [packagePoliciesData] + ); + + useEffect(() => { + const fetchDryRunData = async () => { + if (packagePolicyIds && packagePolicyIds.length) { + const { data } = await sendUpgradePackagePolicyDryRun(packagePolicyIds, latestVersion); + + setDryRunData(data); + } + }; + + fetchDryRunData(); + }, [latestVersion, packagePolicyIds]); + const updateAvailable = installedVersion && semverLt(installedVersion, latestVersion) ? true : false; @@ -88,6 +127,20 @@ export const SettingsPage: React.FC = memo(({ packageInfo }: Props) => { const isUpdating = installationStatus === InstallStatus.installing && installedVersion; + const numOfAssets = useMemo( + () => + Object.entries(packageInfo.assets).reduce( + (acc, [serviceName, serviceNameValue]) => + acc + + Object.entries(serviceNameValue).reduce( + (acc2, [assetName, assetNameValue]) => acc2 + assetNameValue.length, + 0 + ), + 0 + ), + [packageInfo.assets] + ); + return ( @@ -129,7 +182,6 @@ export const SettingsPage: React.FC = memo(({ packageInfo }: Props) => { {installedVersion} - {updateAvailable && } @@ -147,15 +199,21 @@ export const SettingsPage: React.FC = memo(({ packageInfo }: Props) => { - {updateAvailable && ( -

    - -

    + {(updateAvailable || isUpgradingPackagePolicies) && ( + <> + + +

    + +

    + )}
)} @@ -189,8 +247,9 @@ export const SettingsPage: React.FC = memo(({ packageInfo }: Props) => {

-

@@ -220,8 +279,9 @@ export const SettingsPage: React.FC = memo(({ packageInfo }: Props) => {

- diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/uninstall_button.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/uninstall_button.tsx new file mode 100644 index 0000000000000..00b6ccc2f7912 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/uninstall_button.tsx @@ -0,0 +1,92 @@ +/* + * 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 { EuiButton } from '@elastic/eui'; +import React, { useCallback, useState } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { InstallStatus } from '../../../../../types'; +import type { PackageInfo } from '../../../../../types'; + +import { + useCapabilities, + useGetPackageInstallStatus, + useUninstallPackage, +} from '../../../../../hooks'; + +import { ConfirmPackageUninstall } from './confirm_package_uninstall'; + +interface UninstallButtonProps extends Pick { + disabled?: boolean; + latestVersion?: string; + numOfAssets: number; +} + +export const UninstallButton: React.FunctionComponent = ({ + disabled = false, + latestVersion, + name, + numOfAssets, + title, + version, +}) => { + const hasWriteCapabilites = useCapabilities().write; + const uninstallPackage = useUninstallPackage(); + const getPackageInstallStatus = useGetPackageInstallStatus(); + const { status: installationStatus } = getPackageInstallStatus(name); + const isRemoving = installationStatus === InstallStatus.uninstalling; + + const [isUninstallModalVisible, setIsUninstallModalVisible] = useState(false); + + const handleClickUninstall = useCallback(() => { + uninstallPackage({ name, version, title, redirectToVersion: latestVersion ?? version }); + setIsUninstallModalVisible(false); + }, [uninstallPackage, name, title, version, latestVersion]); + + const uninstallModal = ( + setIsUninstallModalVisible(false)} + onConfirm={handleClickUninstall} + /> + ); + + return hasWriteCapabilites ? ( + <> + setIsUninstallModalVisible(true)} + color="danger" + disabled={disabled || isRemoving ? true : false} + > + {isRemoving ? ( + + ) : ( + + )} + + {isUninstallModalVisible && uninstallModal} + + ) : null; +}; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/update_button.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/update_button.tsx new file mode 100644 index 0000000000000..8cdb3ece30621 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/update_button.tsx @@ -0,0 +1,310 @@ +/* + * 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, { useCallback, useEffect, useMemo, useState } from 'react'; +import { useHistory } from 'react-router-dom'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiButton, + EuiCheckbox, + EuiCallOut, + EuiConfirmModal, + EuiSpacer, +} from '@elastic/eui'; +import { sumBy } from 'lodash'; + +import type { + GetAgentPoliciesResponse, + PackageInfo, + UpgradePackagePolicyDryRunResponse, +} from '../../../../../types'; +import { InstallStatus } from '../../../../../types'; +import { AGENT_POLICY_SAVED_OBJECT_TYPE } from '../../../../../constants'; +import { + sendGetAgentPolicies, + useInstallPackage, + useGetPackageInstallStatus, + sendUpgradePackagePolicy, + useStartServices, + useCapabilities, + useLink, +} from '../../../../../hooks'; +import { toMountPoint } from '../../../../../../../../../../../src/plugins/kibana_react/public'; + +interface UpdateButtonProps extends Pick { + dryRunData?: UpgradePackagePolicyDryRunResponse | null; + packagePolicyIds?: string[]; + isUpgradingPackagePolicies?: boolean; + setIsUpgradingPackagePolicies?: React.Dispatch>; +} + +/* + + Updating an integration to a new version entails a bit of logic. We allow the user to choose whether they'd like to + simultaneously upgrade any package policies that include the current version of the integration. For example, if + a user is running four agent policies that include the `nginx-0.2.4` package and they update to `nginx-0.7.0`, they + can elect to also deploy the new integration version to any agent running one of those four agent policies. + + If the user does not elect to upgrade their running policies, we simply install the latest version of the package and + navigate to the new version's settings page, e.g. `/detail/nginx-0.7.0/settings`. + + If the user _does_ elect to upgrade their running policies, we display a confirmation modal. In this modal, we'll report the + number of agents and policies that will be affected by the upgrade, and if there are any conflicts. In the case of a conflict + between versions, an upgrade for a given package policy will be skipped and the user will need to manually recreate their policy + to resolve any breaking changes between versions. Once the user confirms, we first install the latest version of the integration, + then we make a call to the "upgrade policies" API endpoint with a list of all package policy ID's that include the current version + of the integration. This API endpoint will complete the upgrade process in bulk for each package policy provided. Upon completion, + we navigate to the new version's settings page, as above. + +*/ + +export const UpdateButton: React.FunctionComponent = ({ + dryRunData, + isUpgradingPackagePolicies = false, + name, + packagePolicyIds = [], + setIsUpgradingPackagePolicies = () => {}, + title, + version, +}) => { + const history = useHistory(); + const { getPath } = useLink(); + + const { notifications } = useStartServices(); + const hasWriteCapabilites = useCapabilities().write; + + const installPackage = useInstallPackage(); + const getPackageInstallStatus = useGetPackageInstallStatus(); + const { status: installationStatus } = getPackageInstallStatus(name); + const isInstalling = installationStatus === InstallStatus.installing; + + const [isUpdateModalVisible, setIsUpdateModalVisible] = useState(false); + const [upgradePackagePolicies, setUpgradePackagePolicies] = useState(true); + const [agentPolicyData, setAgentPolicyData] = useState(); + + useEffect(() => { + const fetchAgentPolicyData = async () => { + if (packagePolicyIds && packagePolicyIds.length > 0) { + const { data } = await sendGetAgentPolicies({ + perPage: 1000, + page: 1, + // Fetch all agent policies that include one of the eligible package policies + kuery: `${AGENT_POLICY_SAVED_OBJECT_TYPE}.package_policies:${packagePolicyIds + .map((id) => `"${id}"`) + .join(' or ')}`, + }); + + setAgentPolicyData(data); + } + }; + + fetchAgentPolicyData(); + }, [packagePolicyIds]); + + const packagePolicyCount = useMemo(() => packagePolicyIds.length, [packagePolicyIds]); + const agentCount = useMemo( + () => sumBy(agentPolicyData?.items, ({ agents }) => agents ?? 0), + [agentPolicyData] + ); + const conflictCount = useMemo( + () => dryRunData?.filter((item) => item.hasErrors).length, + [dryRunData] + ); + + const handleUpgradePackagePoliciesChange = useCallback(() => { + setUpgradePackagePolicies((prev) => !prev); + }, []); + + const navigateToNewSettingsPage = useCallback(() => { + const settingsPath = getPath('integration_details_settings', { + pkgkey: `${name}-${version}`, + }); + history.push(settingsPath); + }, [history, getPath, name, version]); + + const handleClickUpdate = useCallback(async () => { + await installPackage({ name, version, title, fromUpdate: true }); + }, [installPackage, name, title, version]); + + const handleClickUpgradePolicies = useCallback(async () => { + if (isUpgradingPackagePolicies) { + return; + } + + setIsUpgradingPackagePolicies(true); + + await installPackage({ name, version, title }); + + await sendUpgradePackagePolicy( + // Only upgrade policies that don't have conflicts + packagePolicyIds.filter( + (id) => !dryRunData?.find((dryRunRecord) => dryRunRecord.diff?.[0].id === id)?.hasErrors + ) + ); + + setIsUpgradingPackagePolicies(false); + setIsUpdateModalVisible(false); + + notifications.toasts.addSuccess({ + title: toMountPoint( + + ), + text: toMountPoint( + + ), + }); + + navigateToNewSettingsPage(); + }, [ + dryRunData, + installPackage, + isUpgradingPackagePolicies, + name, + navigateToNewSettingsPage, + notifications.toasts, + packagePolicyIds, + setIsUpgradingPackagePolicies, + title, + version, + ]); + + const updateModal = ( + { + setIsUpdateModalVisible(false); + }} + cancelButtonText={i18n.translate( + 'xpack.fleet.integrations.settings.confirmUpdateModal.cancel', + { defaultMessage: 'Cancel' } + )} + onConfirm={handleClickUpgradePolicies} + confirmButtonText={i18n.translate( + 'xpack.fleet.integrations.settings.confirmUpdateModal.confirm', + { defaultMessage: 'Upgrade {packageName} and policies', values: { packageName: title } } + )} + title={i18n.translate('xpack.fleet.integrations.settings.confirmUpdateModal.updateTitle', { + defaultMessage: 'Upgrade {packageName} and policies', + values: { packageName: title }, + })} + > + <> + {conflictCount && conflictCount > 0 ? ( + <> + + + + {' '} + + + + + + ) : null} + + + + ), + agentCountText: ( + + + + ), + }} + /> + + + ); + + return hasWriteCapabilites ? ( + <> + + + setIsUpdateModalVisible(true) : handleClickUpdate + } + > + + + + {packagePolicyCount > 0 && ( + + + + )} + + + {isUpdateModalVisible && updateModal} + + ) : null; +}; diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps.tsx index 1cfdc45fb7dba..42746229e5ace 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps.tsx @@ -42,7 +42,7 @@ export const DownloadStep = () => { diff --git a/x-pack/plugins/fleet/public/components/agent_policy_package_badges.tsx b/x-pack/plugins/fleet/public/components/agent_policy_package_badges.tsx index 02518945cf7a5..9db4f77a02c37 100644 --- a/x-pack/plugins/fleet/public/components/agent_policy_package_badges.tsx +++ b/x-pack/plugins/fleet/public/components/agent_policy_package_badges.tsx @@ -84,33 +84,42 @@ export const AgentPolicyPackageBadges: React.FunctionComponent = ({ )} - {packages.map((pkg, idx) => { - return ( - - - - - // this collides with some EuiText (+img) CSS from the EuiIcon component - // which makes the button large, wide, and poorly layed out - // override those styles until the bug is fixed or we find a better approach - { margin: 'unset', width: '16px' } - } - /> - - {pkg.title} - - - ); - })} + + {packages.map((pkg, idx) => { + return ( + + + + + + // this collides with some EuiText (+img) CSS from the EuiIcon component + // which makes the button large, wide, and poorly layed out + // override those styles until the bug is fixed or we find a better approach + { margin: 'unset', width: '16px' } + } + /> + + {pkg.title} + + + + ); + })} + {showFleetServerWarning && ( <> diff --git a/x-pack/plugins/fleet/public/components/package_icon.tsx b/x-pack/plugins/fleet/public/components/package_icon.tsx index bb84a79056978..df0bd9864b60f 100644 --- a/x-pack/plugins/fleet/public/components/package_icon.tsx +++ b/x-pack/plugins/fleet/public/components/package_icon.tsx @@ -12,9 +12,8 @@ import { EuiIcon } from '@elastic/eui'; import type { UsePackageIconType } from '../hooks'; import { usePackageIconType } from '../hooks'; -export const PackageIcon: React.FunctionComponent< - UsePackageIconType & Omit -> = ({ packageName, integrationName, version, icons, tryApi, ...euiIconProps }) => { - const iconType = usePackageIconType({ packageName, integrationName, version, icons, tryApi }); - return ; -}; +export const PackageIcon: React.FunctionComponent> = + ({ packageName, integrationName, version, icons, tryApi, ...euiIconProps }) => { + const iconType = usePackageIconType({ packageName, integrationName, version, icons, tryApi }); + return ; + }; diff --git a/x-pack/plugins/fleet/public/components/package_policy_actions_menu.tsx b/x-pack/plugins/fleet/public/components/package_policy_actions_menu.tsx index eefa3c870f283..7dc313970ef20 100644 --- a/x-pack/plugins/fleet/public/components/package_policy_actions_menu.tsx +++ b/x-pack/plugins/fleet/public/components/package_policy_actions_menu.tsx @@ -117,7 +117,10 @@ export const PackagePolicyActionsMenu: React.FunctionComponent<{ disabled={!hasWriteCapabilities} icon="trash" onClick={() => { - deletePackagePoliciesPrompt([packagePolicy.id], refreshAgentPolicy); + deletePackagePoliciesPrompt([packagePolicy.id], () => { + setIsActionsMenuOpen(false); + refreshAgentPolicy(); + }); }} > = ({ ); const deletePackagePoliciesPrompt = useMemo( - (): DeletePackagePoliciesPrompt => (packagePoliciesToDelete, onSuccess = () => undefined) => { - if (!Array.isArray(packagePoliciesToDelete) || packagePoliciesToDelete.length === 0) { - throw new Error('No package policies specified for deletion'); - } - setIsModalOpen(true); - setPackagePolicies(packagePoliciesToDelete); - fetchAgentsCount(); - onSuccessCallback.current = onSuccess; - }, + (): DeletePackagePoliciesPrompt => + (packagePoliciesToDelete, onSuccess = () => undefined) => { + if (!Array.isArray(packagePoliciesToDelete) || packagePoliciesToDelete.length === 0) { + throw new Error('No package policies specified for deletion'); + } + setIsModalOpen(true); + setPackagePolicies(packagePoliciesToDelete); + fetchAgentsCount(); + onSuccessCallback.current = onSuccess; + }, [fetchAgentsCount] ); diff --git a/x-pack/plugins/fleet/public/constants/page_paths.ts b/x-pack/plugins/fleet/public/constants/page_paths.ts index 93ef0d56b17a6..e430e58d297f9 100644 --- a/x-pack/plugins/fleet/public/constants/page_paths.ts +++ b/x-pack/plugins/fleet/public/constants/page_paths.ts @@ -81,10 +81,9 @@ export const INTEGRATIONS_ROUTING_PATHS = { export const pagePathGetters: { [key in StaticPage]: () => [string, string]; -} & - { - [key in DynamicPage]: (values: DynamicPagePathValues) => [string, string]; - } = { +} & { + [key in DynamicPage]: (values: DynamicPagePathValues) => [string, string]; +} = { base: () => [FLEET_BASE_PATH, '/'], overview: () => [FLEET_BASE_PATH, '/'], integrations: () => [INTEGRATIONS_BASE_PATH, '/'], diff --git a/x-pack/plugins/fleet/public/hooks/use_request/agent_policy.ts b/x-pack/plugins/fleet/public/hooks/use_request/agent_policy.ts index 7b3007a61d248..33ef9afd3d8fd 100644 --- a/x-pack/plugins/fleet/public/hooks/use_request/agent_policy.ts +++ b/x-pack/plugins/fleet/public/hooks/use_request/agent_policy.ts @@ -33,6 +33,14 @@ export const useGetAgentPolicies = (query?: GetAgentPoliciesRequest['query']) => }); }; +export const sendGetAgentPolicies = (query?: GetAgentPoliciesRequest['query']) => { + return sendRequest({ + path: agentPolicyRouteService.getListPath(), + method: 'get', + query, + }); +}; + export const useGetOneAgentPolicy = (agentPolicyId: string | undefined) => { return useConditionalRequest({ path: agentPolicyId ? agentPolicyRouteService.getInfoPath(agentPolicyId) : undefined, diff --git a/x-pack/plugins/fleet/public/hooks/use_request/package_policy.ts b/x-pack/plugins/fleet/public/hooks/use_request/package_policy.ts index d39b15a3b3bfa..f8d14647439b2 100644 --- a/x-pack/plugins/fleet/public/hooks/use_request/package_policy.ts +++ b/x-pack/plugins/fleet/public/hooks/use_request/package_policy.ts @@ -66,14 +66,23 @@ export const sendGetOnePackagePolicy = (packagePolicyId: string) => { }); }; -export function sendUpgradePackagePolicyDryRun(packagePolicyIds: string[]) { +export function sendUpgradePackagePolicyDryRun( + packagePolicyIds: string[], + packageVersion?: string +) { + const body: { packagePolicyIds: string[]; dryRun: boolean; packageVersion?: string } = { + packagePolicyIds, + dryRun: true, + }; + + if (packageVersion) { + body.packageVersion = packageVersion; + } + return sendRequest({ path: packagePolicyRouteService.getUpgradePath(), method: 'post', - body: JSON.stringify({ - packagePolicyIds, - dryRun: true, - }), + body: JSON.stringify(body), }); } diff --git a/x-pack/plugins/fleet/public/services/ui_extensions.test.ts b/x-pack/plugins/fleet/public/services/ui_extensions.test.ts index d23219c1f653b..779fd28ebcd18 100644 --- a/x-pack/plugins/fleet/public/services/ui_extensions.test.ts +++ b/x-pack/plugins/fleet/public/services/ui_extensions.test.ts @@ -31,7 +31,7 @@ describe('UI Extension services', () => { it('should store an extension points', () => { const LazyCustomView = lazy(async () => { - return { default: ((() => {}) as unknown) as PackagePolicyEditExtensionComponent }; + return { default: (() => {}) as unknown as PackagePolicyEditExtensionComponent }; }); register({ view: 'package-policy-edit', @@ -48,10 +48,10 @@ describe('UI Extension services', () => { it('should throw if extension point has already registered', () => { const LazyCustomView = lazy(async () => { - return { default: ((() => {}) as unknown) as PackagePolicyEditExtensionComponent }; + return { default: (() => {}) as unknown as PackagePolicyEditExtensionComponent }; }); const LazyCustomView2 = lazy(async () => { - return { default: ((() => {}) as unknown) as PackagePolicyEditExtensionComponent }; + return { default: (() => {}) as unknown as PackagePolicyEditExtensionComponent }; }); register({ diff --git a/x-pack/plugins/fleet/public/types/ui_extensions.ts b/x-pack/plugins/fleet/public/types/ui_extensions.ts index 40e92fe86555d..d365b798fe83e 100644 --- a/x-pack/plugins/fleet/public/types/ui_extensions.ts +++ b/x-pack/plugins/fleet/public/types/ui_extensions.ts @@ -22,7 +22,8 @@ export interface UIExtensionsStorage { * UI Component Extension is used on the pages displaying the ability to edit an * Integration Policy */ -export type PackagePolicyEditExtensionComponent = ComponentType; +export type PackagePolicyEditExtensionComponent = + ComponentType; export interface PackagePolicyEditExtensionComponentProps { /** The current integration policy being edited */ @@ -66,7 +67,8 @@ export interface PackagePolicyEditTabsExtension { * UI Component Extension is used on the pages displaying the ability to Create an * Integration Policy */ -export type PackagePolicyCreateExtensionComponent = ComponentType; +export type PackagePolicyCreateExtensionComponent = + ComponentType; export interface PackagePolicyCreateExtensionComponentProps { /** The integration policy being created */ diff --git a/x-pack/plugins/fleet/server/collectors/agent_collectors.ts b/x-pack/plugins/fleet/server/collectors/agent_collectors.ts index 716c81573e85a..43c15e603a87a 100644 --- a/x-pack/plugins/fleet/server/collectors/agent_collectors.ts +++ b/x-pack/plugins/fleet/server/collectors/agent_collectors.ts @@ -37,14 +37,8 @@ export const getAgentUsage = async ( }; } - const { - total, - inactive, - online, - error, - offline, - updating, - } = await AgentService.getAgentStatusForAgentPolicy(esClient); + const { total, inactive, online, error, offline, updating } = + await AgentService.getAgentStatusForAgentPolicy(esClient); return { total_enrolled: total, healthy: online, diff --git a/x-pack/plugins/fleet/server/constants/index.ts b/x-pack/plugins/fleet/server/constants/index.ts index 28f3ea96f732e..0dcd5e7f47800 100644 --- a/x-pack/plugins/fleet/server/constants/index.ts +++ b/x-pack/plugins/fleet/server/constants/index.ts @@ -49,6 +49,7 @@ export { DEFAULT_FLEET_SERVER_AGENT_POLICY, DEFAULT_OUTPUT, DEFAULT_PACKAGES, + PACKAGE_POLICY_DEFAULT_INDEX_PRIVILEGES, // Fleet Server index FLEET_SERVER_SERVERS_INDEX, ENROLLMENT_API_KEYS_INDEX, diff --git a/x-pack/plugins/fleet/server/errors/utils.ts b/x-pack/plugins/fleet/server/errors/utils.ts index 2eae04e05bd6b..d58f82b94fcd7 100644 --- a/x-pack/plugins/fleet/server/errors/utils.ts +++ b/x-pack/plugins/fleet/server/errors/utils.ts @@ -11,6 +11,6 @@ export function isESClientError(error: unknown): error is ResponseError { return error instanceof ResponseError; } -export const isElasticsearchVersionConflictError = (error: Error): boolean => { +export function isElasticsearchVersionConflictError(error: Error): boolean { return isESClientError(error) && error.meta.statusCode === 409; -}; +} diff --git a/x-pack/plugins/fleet/server/index.test.ts b/x-pack/plugins/fleet/server/index.test.ts index 924fecc311073..659c7d2761d02 100644 --- a/x-pack/plugins/fleet/server/index.test.ts +++ b/x-pack/plugins/fleet/server/index.test.ts @@ -21,7 +21,9 @@ const applyConfigDeprecations = (settings: Record = {}) => { deprecation, path: '', })), - () => ({ message }) => deprecationMessages.push(message) + () => + ({ message }) => + deprecationMessages.push(message) ); return { messages: deprecationMessages, diff --git a/x-pack/plugins/fleet/server/index.ts b/x-pack/plugins/fleet/server/index.ts index 21cdf659f2f5a..accd5e040f4f0 100644 --- a/x-pack/plugins/fleet/server/index.ts +++ b/x-pack/plugins/fleet/server/index.ts @@ -9,7 +9,11 @@ import { schema } from '@kbn/config-schema'; import type { TypeOf } from '@kbn/config-schema'; import type { PluginConfigDescriptor, PluginInitializerContext } from 'src/core/server'; -import { PreconfiguredPackagesSchema, PreconfiguredAgentPoliciesSchema } from './types'; +import { + PreconfiguredPackagesSchema, + PreconfiguredAgentPoliciesSchema, + PreconfiguredOutputsSchema, +} from './types'; import { FleetPlugin } from './plugin'; @@ -38,7 +42,8 @@ export const config: PluginConfigDescriptor = { epm: true, agents: true, }, - deprecations: ({ renameFromRoot, unused, unusedFromRoot }) => [ + deprecations: ({ deprecate, renameFromRoot, unused, unusedFromRoot }) => [ + deprecate('enabled', '8.0.0'), // Fleet plugin was named ingestManager before renameFromRoot('xpack.ingestManager.enabled', 'xpack.fleet.enabled'), renameFromRoot('xpack.ingestManager.registryUrl', 'xpack.fleet.registryUrl'), @@ -113,6 +118,7 @@ export const config: PluginConfigDescriptor = { }), packages: PreconfiguredPackagesSchema, agentPolicies: PreconfiguredAgentPoliciesSchema, + outputs: PreconfiguredOutputsSchema, agentIdVerificationEnabled: schema.boolean({ defaultValue: true }), }), }; diff --git a/x-pack/plugins/fleet/server/plugin.ts b/x-pack/plugins/fleet/server/plugin.ts index 9991f4ee20980..6aad028666ee8 100644 --- a/x-pack/plugins/fleet/server/plugin.ts +++ b/x-pack/plugins/fleet/server/plugin.ts @@ -159,7 +159,8 @@ export interface FleetStartContract { } export class FleetPlugin - implements AsyncPlugin { + implements AsyncPlugin +{ private licensing$!: Observable; private config$: Observable; private configInitialValue: FleetConfigType; diff --git a/x-pack/plugins/fleet/server/routes/agent/actions_handlers.test.ts b/x-pack/plugins/fleet/server/routes/agent/actions_handlers.test.ts index 3f300aef692ff..b31cda7cbf61d 100644 --- a/x-pack/plugins/fleet/server/routes/agent/actions_handlers.test.ts +++ b/x-pack/plugins/fleet/server/routes/agent/actions_handlers.test.ts @@ -73,13 +73,13 @@ describe('test actions handlers', () => { const mockRequest = httpServerMock.createKibanaRequest(postNewAgentActionRequest); - const agentAction = ({ + const agentAction = { type: 'POLICY_CHANGE', id: 'action1', sent_at: '2020-03-14T19:45:02.620Z', timestamp: '2019-01-04T14:32:03.36764-05:00', created_at: '2020-03-14T19:45:02.620Z', - } as unknown) as AgentAction; + } as unknown as AgentAction; const actionsService: ActionsService = { getAgent: jest.fn().mockReturnValueOnce({ @@ -90,7 +90,7 @@ describe('test actions handlers', () => { const postNewAgentActionHandler = postNewAgentActionHandlerBuilder(actionsService); await postNewAgentActionHandler( - ({ + { core: { savedObjects: { client: mockSavedObjectsClient, @@ -101,13 +101,13 @@ describe('test actions handlers', () => { }, }, }, - } as unknown) as RequestHandlerContext, + } as unknown as RequestHandlerContext, mockRequest, mockResponse ); - const expectedAgentActionResponse = (mockResponse.ok.mock.calls[0][0] - ?.body as unknown) as PostNewAgentActionResponse; + const expectedAgentActionResponse = mockResponse.ok.mock.calls[0][0] + ?.body as unknown as PostNewAgentActionResponse; expect(expectedAgentActionResponse.item).toEqual(agentAction); }); diff --git a/x-pack/plugins/fleet/server/routes/agent/handlers.ts b/x-pack/plugins/fleet/server/routes/agent/handlers.ts index fd4721309eebb..14b26d2c7ba85 100644 --- a/x-pack/plugins/fleet/server/routes/agent/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/agent/handlers.ts @@ -28,53 +28,51 @@ import { defaultIngestErrorHandler } from '../../errors'; import { licenseService } from '../../services'; import * as AgentService from '../../services/agents'; -export const getAgentHandler: RequestHandler< - TypeOf -> = async (context, request, response) => { - const soClient = context.core.savedObjects.client; - const esClient = context.core.elasticsearch.client.asCurrentUser; +export const getAgentHandler: RequestHandler> = + async (context, request, response) => { + const soClient = context.core.savedObjects.client; + const esClient = context.core.elasticsearch.client.asCurrentUser; + + try { + const body: GetOneAgentResponse = { + item: await AgentService.getAgentById(esClient, request.params.agentId), + }; - try { - const body: GetOneAgentResponse = { - item: await AgentService.getAgentById(esClient, request.params.agentId), - }; + return response.ok({ body }); + } catch (error) { + if (soClient.errors.isNotFoundError(error)) { + return response.notFound({ + body: { message: `Agent ${request.params.agentId} not found` }, + }); + } - return response.ok({ body }); - } catch (error) { - if (soClient.errors.isNotFoundError(error)) { - return response.notFound({ - body: { message: `Agent ${request.params.agentId} not found` }, - }); + return defaultIngestErrorHandler({ error, response }); } + }; - return defaultIngestErrorHandler({ error, response }); - } -}; +export const deleteAgentHandler: RequestHandler> = + async (context, request, response) => { + const esClient = context.core.elasticsearch.client.asCurrentUser; -export const deleteAgentHandler: RequestHandler< - TypeOf -> = async (context, request, response) => { - const esClient = context.core.elasticsearch.client.asCurrentUser; + try { + await AgentService.deleteAgent(esClient, request.params.agentId); - try { - await AgentService.deleteAgent(esClient, request.params.agentId); + const body = { + action: 'deleted', + }; - const body = { - action: 'deleted', - }; + return response.ok({ body }); + } catch (error) { + if (error.isBoom) { + return response.customError({ + statusCode: error.output.statusCode, + body: { message: `Agent ${request.params.agentId} not found` }, + }); + } - return response.ok({ body }); - } catch (error) { - if (error.isBoom) { - return response.customError({ - statusCode: error.output.statusCode, - body: { message: `Agent ${request.params.agentId} not found` }, - }); + return defaultIngestErrorHandler({ error, response }); } - - return defaultIngestErrorHandler({ error, response }); - } -}; + }; export const updateAgentHandler: RequestHandler< TypeOf, diff --git a/x-pack/plugins/fleet/server/routes/output/handler.ts b/x-pack/plugins/fleet/server/routes/output/handler.ts index ebd3d213d33c3..0c56d55423e4b 100644 --- a/x-pack/plugins/fleet/server/routes/output/handler.ts +++ b/x-pack/plugins/fleet/server/routes/output/handler.ts @@ -31,28 +31,27 @@ export const getOutputsHandler: RequestHandler = async (context, request, respon } }; -export const getOneOuputHandler: RequestHandler< - TypeOf -> = async (context, request, response) => { - const soClient = context.core.savedObjects.client; - try { - const output = await outputService.get(soClient, request.params.outputId); +export const getOneOuputHandler: RequestHandler> = + async (context, request, response) => { + const soClient = context.core.savedObjects.client; + try { + const output = await outputService.get(soClient, request.params.outputId); - const body: GetOneOutputResponse = { - item: output, - }; + const body: GetOneOutputResponse = { + item: output, + }; - return response.ok({ body }); - } catch (error) { - if (error.isBoom && error.output.statusCode === 404) { - return response.notFound({ - body: { message: `Output ${request.params.outputId} not found` }, - }); - } + return response.ok({ body }); + } catch (error) { + if (error.isBoom && error.output.statusCode === 404) { + return response.notFound({ + body: { message: `Output ${request.params.outputId} not found` }, + }); + } - return defaultIngestErrorHandler({ error, response }); - } -}; + return defaultIngestErrorHandler({ error, response }); + } + }; export const putOuputHandler: RequestHandler< TypeOf, diff --git a/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts b/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts index fb28c7e2f5155..729417fa96060 100644 --- a/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts +++ b/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts @@ -28,51 +28,54 @@ type PackagePolicyServicePublicInterface = Omit< const packagePolicyServiceMock = packagePolicyService as jest.Mocked; -jest.mock('../../services/package_policy', (): { - packagePolicyService: jest.Mocked; -} => { - return { - packagePolicyService: { - compilePackagePolicyInputs: jest.fn((packageInfo, vars, dataInputs) => - Promise.resolve(dataInputs) - ), - buildPackagePolicyFromPackage: jest.fn(), - bulkCreate: jest.fn(), - create: jest.fn((soClient, esClient, newData) => - Promise.resolve({ - ...newData, - inputs: newData.inputs.map((input) => ({ - ...input, - streams: input.streams.map((stream) => ({ - id: stream.data_stream.dataset, - ...stream, +jest.mock( + '../../services/package_policy', + (): { + packagePolicyService: jest.Mocked; + } => { + return { + packagePolicyService: { + compilePackagePolicyInputs: jest.fn((packageInfo, vars, dataInputs) => + Promise.resolve(dataInputs) + ), + buildPackagePolicyFromPackage: jest.fn(), + bulkCreate: jest.fn(), + create: jest.fn((soClient, esClient, newData) => + Promise.resolve({ + ...newData, + inputs: newData.inputs.map((input) => ({ + ...input, + streams: input.streams.map((stream) => ({ + id: stream.data_stream.dataset, + ...stream, + })), })), - })), - id: '1', - revision: 1, - updated_at: new Date().toISOString(), - updated_by: 'elastic', - created_at: new Date().toISOString(), - created_by: 'elastic', - }) - ), - delete: jest.fn(), - get: jest.fn(), - getByIDs: jest.fn(), - list: jest.fn(), - listIds: jest.fn(), - update: jest.fn(), - // @ts-ignore - runExternalCallbacks: jest.fn((callbackType, packagePolicy, context, request) => - callbackType === 'postPackagePolicyDelete' - ? Promise.resolve(undefined) - : Promise.resolve(packagePolicy) - ), - upgrade: jest.fn(), - getUpgradeDryRunDiff: jest.fn(), - }, - }; -}); + id: '1', + revision: 1, + updated_at: new Date().toISOString(), + updated_by: 'elastic', + created_at: new Date().toISOString(), + created_by: 'elastic', + }) + ), + delete: jest.fn(), + get: jest.fn(), + getByIDs: jest.fn(), + list: jest.fn(), + listIds: jest.fn(), + update: jest.fn(), + // @ts-ignore + runExternalCallbacks: jest.fn((callbackType, packagePolicy, context, request) => + callbackType === 'postPackagePolicyDelete' + ? Promise.resolve(undefined) + : Promise.resolve(packagePolicy) + ), + upgrade: jest.fn(), + getUpgradeDryRunDiff: jest.fn(), + }, + }; + } +); jest.mock('../../services/epm/packages', () => { return { diff --git a/x-pack/plugins/fleet/server/saved_objects/index.ts b/x-pack/plugins/fleet/server/saved_objects/index.ts index 449a1984aa53b..83188e0047044 100644 --- a/x-pack/plugins/fleet/server/saved_objects/index.ts +++ b/x-pack/plugins/fleet/server/saved_objects/index.ts @@ -156,6 +156,8 @@ const getSavedObjectTypes = ( revision: { type: 'integer' }, monitoring_enabled: { type: 'keyword', index: false }, is_preconfigured: { type: 'keyword' }, + data_output_id: { type: 'keyword' }, + monitoring_output_id: { type: 'keyword' }, }, }, migrations: { @@ -196,6 +198,7 @@ const getSavedObjectTypes = ( }, mappings: { properties: { + output_id: { type: 'keyword', index: false }, name: { type: 'keyword' }, type: { type: 'keyword' }, is_default: { type: 'boolean' }, @@ -203,6 +206,7 @@ const getSavedObjectTypes = ( ca_sha256: { type: 'keyword', index: false }, config: { type: 'flattened' }, config_yaml: { type: 'text' }, + is_preconfigured: { type: 'boolean', index: false }, }, }, migrations: { @@ -251,6 +255,11 @@ const getSavedObjectTypes = ( properties: { dataset: { type: 'keyword' }, type: { type: 'keyword' }, + elasticsearch: { + properties: { + privileges: { type: 'flattened' }, + }, + }, }, }, vars: { type: 'flattened' }, diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_11_0.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_11_0.ts index 0c7b20ed28261..c17a167184b4b 100644 --- a/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_11_0.ts +++ b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_11_0.ts @@ -13,9 +13,8 @@ import type { PackagePolicy } from '../../../../common'; export const migratePackagePolicyToV7110: SavedObjectMigrationFn = ( packagePolicyDoc ) => { - const updatedPackagePolicyDoc: SavedObjectUnsanitizedDoc = cloneDeep( - packagePolicyDoc - ); + const updatedPackagePolicyDoc: SavedObjectUnsanitizedDoc = + cloneDeep(packagePolicyDoc); if (packagePolicyDoc.attributes.package?.name === 'endpoint') { const input = updatedPackagePolicyDoc.attributes.inputs[0]; const popup = { diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_12_0.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_12_0.ts index 9d961af74a12a..4fc8c15044e82 100644 --- a/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_12_0.ts +++ b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_12_0.ts @@ -13,9 +13,8 @@ import type { PackagePolicy } from '../../../../common'; export const migratePackagePolicyToV7120: SavedObjectMigrationFn = ( packagePolicyDoc ) => { - const updatedPackagePolicyDoc: SavedObjectUnsanitizedDoc = cloneDeep( - packagePolicyDoc - ); + const updatedPackagePolicyDoc: SavedObjectUnsanitizedDoc = + cloneDeep(packagePolicyDoc); if (packagePolicyDoc.attributes.package?.name === 'endpoint') { const input = updatedPackagePolicyDoc.attributes.inputs[0]; const ransomware = { diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_14_0.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_14_0.ts index 2f281bcf86a95..a56ef13375149 100644 --- a/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_14_0.ts +++ b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_14_0.ts @@ -14,9 +14,8 @@ export const migrateEndpointPackagePolicyToV7140: SavedObjectMigrationFn< PackagePolicy, PackagePolicy > = (packagePolicyDoc) => { - const updatedPackagePolicyDoc: SavedObjectUnsanitizedDoc = cloneDeep( - packagePolicyDoc - ); + const updatedPackagePolicyDoc: SavedObjectUnsanitizedDoc = + cloneDeep(packagePolicyDoc); if (packagePolicyDoc.attributes.package?.name === 'endpoint') { const input = updatedPackagePolicyDoc.attributes.inputs[0]; if (input && input.config) { diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_15_0.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_15_0.ts index 9ccafc58989f3..7f96cda589462 100644 --- a/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_15_0.ts +++ b/x-pack/plugins/fleet/server/saved_objects/migrations/security_solution/to_v7_15_0.ts @@ -17,9 +17,8 @@ export const migratePackagePolicyToV7150: SavedObjectMigrationFn = cloneDeep( - packagePolicyDoc - ); + const updatedPackagePolicyDoc: SavedObjectUnsanitizedDoc = + cloneDeep(packagePolicyDoc); const input = updatedPackagePolicyDoc.attributes.inputs[0]; const memory = { diff --git a/x-pack/plugins/fleet/server/services/agent_policies/__snapshots__/full_agent_policy.test.ts.snap b/x-pack/plugins/fleet/server/services/agent_policies/__snapshots__/full_agent_policy.test.ts.snap new file mode 100644 index 0000000000000..970bccbafa634 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/agent_policies/__snapshots__/full_agent_policy.test.ts.snap @@ -0,0 +1,292 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`getFullAgentPolicy should support a different data output 1`] = ` +Object { + "agent": Object { + "monitoring": Object { + "enabled": true, + "logs": false, + "metrics": true, + "namespace": "default", + "use_output": "default", + }, + }, + "fleet": Object { + "hosts": Array [ + "http://fleetserver:8220", + ], + }, + "id": "agent-policy", + "inputs": Array [], + "output_permissions": Object { + "data-output-id": Object { + "_elastic_agent_checks": Object { + "cluster": Array [ + "monitor", + ], + }, + "_fallback": Object { + "cluster": Array [ + "monitor", + ], + "indices": Array [ + Object { + "names": Array [ + "logs-*", + "metrics-*", + "traces-*", + "synthetics-*", + ".logs-endpoint.diagnostic.collection-*", + ], + "privileges": Array [ + "auto_configure", + "create_doc", + ], + }, + ], + }, + }, + "default": Object { + "_elastic_agent_checks": Object { + "cluster": Array [ + "monitor", + ], + "indices": Array [ + Object { + "names": Array [ + "metrics-elastic_agent-default", + "metrics-elastic_agent.elastic_agent-default", + "metrics-elastic_agent.apm_server-default", + "metrics-elastic_agent.filebeat-default", + "metrics-elastic_agent.fleet_server-default", + "metrics-elastic_agent.metricbeat-default", + "metrics-elastic_agent.osquerybeat-default", + "metrics-elastic_agent.packetbeat-default", + "metrics-elastic_agent.endpoint_security-default", + "metrics-elastic_agent.auditbeat-default", + "metrics-elastic_agent.heartbeat-default", + ], + "privileges": Array [ + "auto_configure", + "create_doc", + ], + }, + ], + }, + }, + }, + "outputs": Object { + "data-output-id": Object { + "api_key": undefined, + "ca_sha256": undefined, + "hosts": Array [ + "http://es-data.co:9201", + ], + "type": "elasticsearch", + }, + "default": Object { + "api_key": undefined, + "ca_sha256": undefined, + "hosts": Array [ + "http://127.0.0.1:9201", + ], + "type": "elasticsearch", + }, + }, + "revision": 1, +} +`; + +exports[`getFullAgentPolicy should support a different monitoring output 1`] = ` +Object { + "agent": Object { + "monitoring": Object { + "enabled": true, + "logs": false, + "metrics": true, + "namespace": "default", + "use_output": "monitoring-output-id", + }, + }, + "fleet": Object { + "hosts": Array [ + "http://fleetserver:8220", + ], + }, + "id": "agent-policy", + "inputs": Array [], + "output_permissions": Object { + "default": Object { + "_elastic_agent_checks": Object { + "cluster": Array [ + "monitor", + ], + }, + "_fallback": Object { + "cluster": Array [ + "monitor", + ], + "indices": Array [ + Object { + "names": Array [ + "logs-*", + "metrics-*", + "traces-*", + "synthetics-*", + ".logs-endpoint.diagnostic.collection-*", + ], + "privileges": Array [ + "auto_configure", + "create_doc", + ], + }, + ], + }, + }, + "monitoring-output-id": Object { + "_elastic_agent_checks": Object { + "cluster": Array [ + "monitor", + ], + "indices": Array [ + Object { + "names": Array [ + "metrics-elastic_agent-default", + "metrics-elastic_agent.elastic_agent-default", + "metrics-elastic_agent.apm_server-default", + "metrics-elastic_agent.filebeat-default", + "metrics-elastic_agent.fleet_server-default", + "metrics-elastic_agent.metricbeat-default", + "metrics-elastic_agent.osquerybeat-default", + "metrics-elastic_agent.packetbeat-default", + "metrics-elastic_agent.endpoint_security-default", + "metrics-elastic_agent.auditbeat-default", + "metrics-elastic_agent.heartbeat-default", + ], + "privileges": Array [ + "auto_configure", + "create_doc", + ], + }, + ], + }, + }, + }, + "outputs": Object { + "default": Object { + "api_key": undefined, + "ca_sha256": undefined, + "hosts": Array [ + "http://127.0.0.1:9201", + ], + "type": "elasticsearch", + }, + "monitoring-output-id": Object { + "api_key": undefined, + "ca_sha256": undefined, + "hosts": Array [ + "http://es-monitoring.co:9201", + ], + "type": "elasticsearch", + }, + }, + "revision": 1, +} +`; + +exports[`getFullAgentPolicy should support both different outputs for data and monitoring 1`] = ` +Object { + "agent": Object { + "monitoring": Object { + "enabled": true, + "logs": false, + "metrics": true, + "namespace": "default", + "use_output": "monitoring-output-id", + }, + }, + "fleet": Object { + "hosts": Array [ + "http://fleetserver:8220", + ], + }, + "id": "agent-policy", + "inputs": Array [], + "output_permissions": Object { + "data-output-id": Object { + "_elastic_agent_checks": Object { + "cluster": Array [ + "monitor", + ], + }, + "_fallback": Object { + "cluster": Array [ + "monitor", + ], + "indices": Array [ + Object { + "names": Array [ + "logs-*", + "metrics-*", + "traces-*", + "synthetics-*", + ".logs-endpoint.diagnostic.collection-*", + ], + "privileges": Array [ + "auto_configure", + "create_doc", + ], + }, + ], + }, + }, + "monitoring-output-id": Object { + "_elastic_agent_checks": Object { + "cluster": Array [ + "monitor", + ], + "indices": Array [ + Object { + "names": Array [ + "metrics-elastic_agent-default", + "metrics-elastic_agent.elastic_agent-default", + "metrics-elastic_agent.apm_server-default", + "metrics-elastic_agent.filebeat-default", + "metrics-elastic_agent.fleet_server-default", + "metrics-elastic_agent.metricbeat-default", + "metrics-elastic_agent.osquerybeat-default", + "metrics-elastic_agent.packetbeat-default", + "metrics-elastic_agent.endpoint_security-default", + "metrics-elastic_agent.auditbeat-default", + "metrics-elastic_agent.heartbeat-default", + ], + "privileges": Array [ + "auto_configure", + "create_doc", + ], + }, + ], + }, + }, + }, + "outputs": Object { + "data-output-id": Object { + "api_key": undefined, + "ca_sha256": undefined, + "hosts": Array [ + "http://es-data.co:9201", + ], + "type": "elasticsearch", + }, + "monitoring-output-id": Object { + "api_key": undefined, + "ca_sha256": undefined, + "hosts": Array [ + "http://es-monitoring.co:9201", + ], + "type": "elasticsearch", + }, + }, + "revision": 1, +} +`; diff --git a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts new file mode 100644 index 0000000000000..8df1234982ee6 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts @@ -0,0 +1,256 @@ +/* + * 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 { savedObjectsClientMock } from 'src/core/server/mocks'; + +import type { AgentPolicy, Output } from '../../types'; + +import { agentPolicyService } from '../agent_policy'; +import { agentPolicyUpdateEventHandler } from '../agent_policy_update'; + +import { getFullAgentPolicy } from './full_agent_policy'; + +const mockedAgentPolicyService = agentPolicyService as jest.Mocked; + +function mockAgentPolicy(data: Partial) { + mockedAgentPolicyService.get.mockResolvedValue({ + id: 'agent-policy', + status: 'active', + package_policies: [], + is_managed: false, + namespace: 'default', + revision: 1, + name: 'Policy', + updated_at: '2020-01-01', + updated_by: 'qwerty', + ...data, + }); +} + +jest.mock('../settings', () => { + return { + getSettings: () => { + return { + id: '93f74c0-e876-11ea-b7d3-8b2acec6f75c', + fleet_server_hosts: ['http://fleetserver:8220'], + }; + }, + }; +}); + +jest.mock('../agent_policy'); + +jest.mock('../output', () => { + return { + outputService: { + getDefaultOutputId: () => 'test-id', + get: (soClient: any, id: string): Output => { + switch (id) { + case 'data-output-id': + return { + id: 'data-output-id', + is_default: false, + name: 'Data output', + // @ts-ignore + type: 'elasticsearch', + hosts: ['http://es-data.co:9201'], + }; + case 'monitoring-output-id': + return { + id: 'monitoring-output-id', + is_default: false, + name: 'Monitoring output', + // @ts-ignore + type: 'elasticsearch', + hosts: ['http://es-monitoring.co:9201'], + }; + default: + return { + id: 'test-id', + is_default: true, + name: 'default', + // @ts-ignore + type: 'elasticsearch', + hosts: ['http://127.0.0.1:9201'], + }; + } + }, + }, + }; +}); + +jest.mock('../agent_policy_update'); +jest.mock('../agents'); +jest.mock('../package_policy'); + +function getAgentPolicyUpdateMock() { + return agentPolicyUpdateEventHandler as unknown as jest.Mock< + typeof agentPolicyUpdateEventHandler + >; +} + +describe('getFullAgentPolicy', () => { + beforeEach(() => { + getAgentPolicyUpdateMock().mockClear(); + mockedAgentPolicyService.get.mockReset(); + }); + + it('should return a policy without monitoring if monitoring is not enabled', async () => { + mockAgentPolicy({ + revision: 1, + }); + const agentPolicy = await getFullAgentPolicy(savedObjectsClientMock.create(), 'agent-policy'); + + expect(agentPolicy).toMatchObject({ + id: 'agent-policy', + outputs: { + default: { + type: 'elasticsearch', + hosts: ['http://127.0.0.1:9201'], + ca_sha256: undefined, + api_key: undefined, + }, + }, + inputs: [], + revision: 1, + fleet: { + hosts: ['http://fleetserver:8220'], + }, + agent: { + monitoring: { + enabled: false, + logs: false, + metrics: false, + }, + }, + }); + }); + + it('should return a policy with monitoring if monitoring is enabled for logs', async () => { + mockAgentPolicy({ + namespace: 'default', + revision: 1, + monitoring_enabled: ['logs'], + }); + const agentPolicy = await getFullAgentPolicy(savedObjectsClientMock.create(), 'agent-policy'); + + expect(agentPolicy).toMatchObject({ + id: 'agent-policy', + outputs: { + default: { + type: 'elasticsearch', + hosts: ['http://127.0.0.1:9201'], + ca_sha256: undefined, + api_key: undefined, + }, + }, + inputs: [], + revision: 1, + fleet: { + hosts: ['http://fleetserver:8220'], + }, + agent: { + monitoring: { + namespace: 'default', + use_output: 'default', + enabled: true, + logs: true, + metrics: false, + }, + }, + }); + }); + + it('should return a policy with monitoring if monitoring is enabled for metrics', async () => { + mockAgentPolicy({ + namespace: 'default', + revision: 1, + monitoring_enabled: ['metrics'], + }); + const agentPolicy = await getFullAgentPolicy(savedObjectsClientMock.create(), 'agent-policy'); + + expect(agentPolicy).toMatchObject({ + id: 'agent-policy', + outputs: { + default: { + type: 'elasticsearch', + hosts: ['http://127.0.0.1:9201'], + ca_sha256: undefined, + api_key: undefined, + }, + }, + inputs: [], + revision: 1, + fleet: { + hosts: ['http://fleetserver:8220'], + }, + agent: { + monitoring: { + namespace: 'default', + use_output: 'default', + enabled: true, + logs: false, + metrics: true, + }, + }, + }); + }); + + it('should support a different monitoring output', async () => { + mockAgentPolicy({ + namespace: 'default', + revision: 1, + monitoring_enabled: ['metrics'], + monitoring_output_id: 'monitoring-output-id', + }); + const agentPolicy = await getFullAgentPolicy(savedObjectsClientMock.create(), 'agent-policy'); + + expect(agentPolicy).toMatchSnapshot(); + }); + + it('should support a different data output', async () => { + mockAgentPolicy({ + namespace: 'default', + revision: 1, + monitoring_enabled: ['metrics'], + data_output_id: 'data-output-id', + }); + const agentPolicy = await getFullAgentPolicy(savedObjectsClientMock.create(), 'agent-policy'); + + expect(agentPolicy).toMatchSnapshot(); + }); + + it('should support both different outputs for data and monitoring ', async () => { + mockAgentPolicy({ + namespace: 'default', + revision: 1, + monitoring_enabled: ['metrics'], + data_output_id: 'data-output-id', + monitoring_output_id: 'monitoring-output-id', + }); + const agentPolicy = await getFullAgentPolicy(savedObjectsClientMock.create(), 'agent-policy'); + + expect(agentPolicy).toMatchSnapshot(); + }); + + it('should use "default" as the default policy id', async () => { + mockAgentPolicy({ + id: 'policy', + status: 'active', + package_policies: [], + is_managed: false, + namespace: 'default', + revision: 1, + data_output_id: 'test-id', + monitoring_output_id: 'test-id', + }); + + const agentPolicy = await getFullAgentPolicy(savedObjectsClientMock.create(), 'agent-policy'); + + expect(agentPolicy?.outputs.default).toBeDefined(); + }); +}); diff --git a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.ts new file mode 100644 index 0000000000000..4e8b3a2c1952e --- /dev/null +++ b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.ts @@ -0,0 +1,229 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { SavedObjectsClientContract } from 'kibana/server'; +import { safeLoad } from 'js-yaml'; + +import type { + FullAgentPolicy, + PackagePolicy, + Settings, + Output, + FullAgentPolicyOutput, +} from '../../types'; +import { agentPolicyService } from '../agent_policy'; +import { outputService } from '../output'; +import { + storedPackagePoliciesToAgentPermissions, + DEFAULT_PERMISSIONS, +} from '../package_policies_to_agent_permissions'; +import { storedPackagePoliciesToAgentInputs, dataTypes, outputType } from '../../../common'; +import type { FullAgentPolicyOutputPermissions } from '../../../common'; +import { getSettings } from '../settings'; +import { PACKAGE_POLICY_DEFAULT_INDEX_PRIVILEGES, DEFAULT_OUTPUT } from '../../constants'; + +const MONITORING_DATASETS = [ + 'elastic_agent', + 'elastic_agent.elastic_agent', + 'elastic_agent.apm_server', + 'elastic_agent.filebeat', + 'elastic_agent.fleet_server', + 'elastic_agent.metricbeat', + 'elastic_agent.osquerybeat', + 'elastic_agent.packetbeat', + 'elastic_agent.endpoint_security', + 'elastic_agent.auditbeat', + 'elastic_agent.heartbeat', +]; + +export async function getFullAgentPolicy( + soClient: SavedObjectsClientContract, + id: string, + options?: { standalone: boolean } +): Promise { + let agentPolicy; + const standalone = options?.standalone; + + try { + agentPolicy = await agentPolicyService.get(soClient, id); + } catch (err) { + if (!err.isBoom || err.output.statusCode !== 404) { + throw err; + } + } + + if (!agentPolicy) { + return null; + } + + const defaultOutputId = await outputService.getDefaultOutputId(soClient); + if (!defaultOutputId) { + throw new Error('Default output is not setup'); + } + + const dataOutputId = agentPolicy.data_output_id || defaultOutputId; + const monitoringOutputId = agentPolicy.monitoring_output_id || defaultOutputId; + + const outputs = await Promise.all( + Array.from(new Set([dataOutputId, monitoringOutputId])).map((outputId) => + outputService.get(soClient, outputId) + ) + ); + + const dataOutput = outputs.find((output) => output.id === dataOutputId); + if (!dataOutput) { + throw new Error(`Data output not found ${dataOutputId}`); + } + const monitoringOutput = outputs.find((output) => output.id === monitoringOutputId); + if (!monitoringOutput) { + throw new Error(`Monitoring output not found ${monitoringOutputId}`); + } + + const fullAgentPolicy: FullAgentPolicy = { + id: agentPolicy.id, + outputs: { + ...outputs.reduce((acc, output) => { + acc[getOutputIdForAgentPolicy(output)] = transformOutputToFullPolicyOutput(output); + + return acc; + }, {}), + }, + inputs: storedPackagePoliciesToAgentInputs( + agentPolicy.package_policies as PackagePolicy[], + getOutputIdForAgentPolicy(dataOutput) + ), + revision: agentPolicy.revision, + ...(agentPolicy.monitoring_enabled && agentPolicy.monitoring_enabled.length > 0 + ? { + agent: { + monitoring: { + namespace: agentPolicy.namespace, + use_output: getOutputIdForAgentPolicy(monitoringOutput), + enabled: true, + logs: agentPolicy.monitoring_enabled.includes(dataTypes.Logs), + metrics: agentPolicy.monitoring_enabled.includes(dataTypes.Metrics), + }, + }, + } + : { + agent: { + monitoring: { enabled: false, logs: false, metrics: false }, + }, + }), + }; + + const dataPermissions = (await storedPackagePoliciesToAgentPermissions( + soClient, + agentPolicy.package_policies + )) || { _fallback: DEFAULT_PERMISSIONS }; + + dataPermissions._elastic_agent_checks = { + cluster: DEFAULT_PERMISSIONS.cluster, + }; + + // TODO: fetch this from the elastic agent package https://github.com/elastic/kibana/issues/107738 + const monitoringNamespace = fullAgentPolicy.agent?.monitoring.namespace; + const monitoringPermissions: FullAgentPolicyOutputPermissions = + monitoringOutputId === dataOutputId + ? dataPermissions + : { + _elastic_agent_checks: { + cluster: DEFAULT_PERMISSIONS.cluster, + }, + }; + if ( + fullAgentPolicy.agent?.monitoring.enabled && + monitoringNamespace && + monitoringOutput && + monitoringOutput.type === outputType.Elasticsearch + ) { + let names: string[] = []; + if (fullAgentPolicy.agent.monitoring.logs) { + names = names.concat( + MONITORING_DATASETS.map((dataset) => `logs-${dataset}-${monitoringNamespace}`) + ); + } + if (fullAgentPolicy.agent.monitoring.metrics) { + names = names.concat( + MONITORING_DATASETS.map((dataset) => `metrics-${dataset}-${monitoringNamespace}`) + ); + } + + monitoringPermissions._elastic_agent_checks.indices = [ + { + names, + privileges: PACKAGE_POLICY_DEFAULT_INDEX_PRIVILEGES, + }, + ]; + } + + // Only add permissions if output.type is "elasticsearch" + fullAgentPolicy.output_permissions = Object.keys(fullAgentPolicy.outputs).reduce< + NonNullable + >((outputPermissions, outputId) => { + const output = fullAgentPolicy.outputs[outputId]; + if (output && output.type === outputType.Elasticsearch) { + outputPermissions[outputId] = + outputId === getOutputIdForAgentPolicy(dataOutput) + ? dataPermissions + : monitoringPermissions; + } + return outputPermissions; + }, {}); + + // only add settings if not in standalone + if (!standalone) { + let settings: Settings; + try { + settings = await getSettings(soClient); + } catch (error) { + throw new Error('Default settings is not setup'); + } + if (settings.fleet_server_hosts && settings.fleet_server_hosts.length) { + fullAgentPolicy.fleet = { + hosts: settings.fleet_server_hosts, + }; + } + } + return fullAgentPolicy; +} + +function transformOutputToFullPolicyOutput( + output: Output, + standalone = false +): FullAgentPolicyOutput { + // eslint-disable-next-line @typescript-eslint/naming-convention + const { config_yaml, type, hosts, ca_sha256, api_key } = output; + const configJs = config_yaml ? safeLoad(config_yaml) : {}; + const newOutput: FullAgentPolicyOutput = { + type, + hosts, + ca_sha256, + api_key, + ...configJs, + }; + + if (standalone) { + delete newOutput.api_key; + newOutput.username = 'ES_USERNAME'; + newOutput.password = 'ES_PASSWORD'; + } + + return newOutput; +} + +/** + * Get id used in full agent policy (sent to the agents) + * we use "default" for the default policy to avoid breaking changes + */ +function getOutputIdForAgentPolicy(output: Output) { + if (output.is_default) { + return DEFAULT_OUTPUT.name; + } + + return output.id; +} diff --git a/x-pack/plugins/uptime/server/rest_api/overview_filters/index.ts b/x-pack/plugins/fleet/server/services/agent_policies/index.ts similarity index 79% rename from x-pack/plugins/uptime/server/rest_api/overview_filters/index.ts rename to x-pack/plugins/fleet/server/services/agent_policies/index.ts index 164dae9c7588a..b793ed26a08b5 100644 --- a/x-pack/plugins/uptime/server/rest_api/overview_filters/index.ts +++ b/x-pack/plugins/fleet/server/services/agent_policies/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { createGetOverviewFilters } from './get_overview_filters'; +export { getFullAgentPolicy } from './full_agent_policy'; diff --git a/x-pack/plugins/fleet/server/services/agent_policy.test.ts b/x-pack/plugins/fleet/server/services/agent_policy.test.ts index 3267b2b7e2665..6a5cb28dbaa0a 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy.test.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy.test.ts @@ -7,7 +7,7 @@ import { elasticsearchServiceMock, savedObjectsClientMock } from 'src/core/server/mocks'; -import type { AgentPolicy, NewAgentPolicy, Output } from '../types'; +import type { AgentPolicy, NewAgentPolicy } from '../types'; import { agentPolicyService } from './agent_policy'; import { agentPolicyUpdateEventHandler } from './agent_policy_update'; @@ -47,30 +47,12 @@ function getSavedObjectMock(agentPolicyAttributes: any) { return mock; } -jest.mock('./output', () => { - return { - outputService: { - getDefaultOutputId: () => 'test-id', - get: (): Output => { - return { - id: 'test-id', - is_default: true, - name: 'default', - // @ts-ignore - type: 'elasticsearch', - hosts: ['http://127.0.0.1:9201'], - }; - }, - }, - }; -}); - jest.mock('./agent_policy_update'); jest.mock('./agents'); jest.mock('./package_policy'); function getAgentPolicyUpdateMock() { - return (agentPolicyUpdateEventHandler as unknown) as jest.Mock< + return agentPolicyUpdateEventHandler as unknown as jest.Mock< typeof agentPolicyUpdateEventHandler >; } @@ -79,7 +61,7 @@ function getAgentPolicyCreateMock() { const soClient = savedObjectsClientMock.create(); soClient.create.mockImplementation(async (type, attributes) => { return { - attributes: (attributes as unknown) as NewAgentPolicy, + attributes: attributes as unknown as NewAgentPolicy, id: 'mocked', type: 'mocked', references: [], @@ -186,106 +168,17 @@ describe('agent policy', () => { }); }); - describe('getFullAgentPolicy', () => { - it('should return a policy without monitoring if monitoring is not enabled', async () => { - const soClient = getSavedObjectMock({ - revision: 1, - }); - const agentPolicy = await agentPolicyService.getFullAgentPolicy(soClient, 'agent-policy'); - - expect(agentPolicy).toMatchObject({ - id: 'agent-policy', - outputs: { - default: { - type: 'elasticsearch', - hosts: ['http://127.0.0.1:9201'], - ca_sha256: undefined, - api_key: undefined, - }, - }, - inputs: [], - revision: 1, - fleet: { - hosts: ['http://fleetserver:8220'], - }, - agent: { - monitoring: { - enabled: false, - logs: false, - metrics: false, - }, - }, - }); - }); - - it('should return a policy with monitoring if monitoring is enabled for logs', async () => { - const soClient = getSavedObjectMock({ - namespace: 'default', - revision: 1, - monitoring_enabled: ['logs'], - }); - const agentPolicy = await agentPolicyService.getFullAgentPolicy(soClient, 'agent-policy'); - - expect(agentPolicy).toMatchObject({ - id: 'agent-policy', - outputs: { - default: { - type: 'elasticsearch', - hosts: ['http://127.0.0.1:9201'], - ca_sha256: undefined, - api_key: undefined, - }, - }, - inputs: [], - revision: 1, - fleet: { - hosts: ['http://fleetserver:8220'], - }, - agent: { - monitoring: { - namespace: 'default', - use_output: 'default', - enabled: true, - logs: true, - metrics: false, - }, - }, - }); - }); - - it('should return a policy with monitoring if monitoring is enabled for metrics', async () => { + describe('bumpAllAgentPoliciesForOutput', () => { + it('should call agentPolicyUpdateEventHandler with updated event once', async () => { const soClient = getSavedObjectMock({ - namespace: 'default', revision: 1, monitoring_enabled: ['metrics'], }); - const agentPolicy = await agentPolicyService.getFullAgentPolicy(soClient, 'agent-policy'); + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; - expect(agentPolicy).toMatchObject({ - id: 'agent-policy', - outputs: { - default: { - type: 'elasticsearch', - hosts: ['http://127.0.0.1:9201'], - ca_sha256: undefined, - api_key: undefined, - }, - }, - inputs: [], - revision: 1, - fleet: { - hosts: ['http://fleetserver:8220'], - }, - agent: { - monitoring: { - namespace: 'default', - use_output: 'default', - enabled: true, - logs: false, - metrics: true, - }, - }, - }); + await agentPolicyService.bumpAllAgentPoliciesForOutput(soClient, esClient, 'output-id-123'); + + expect(agentPolicyUpdateEventHandler).toHaveBeenCalledTimes(1); }); }); diff --git a/x-pack/plugins/fleet/server/services/agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policy.ts index 8539db05ffb54..751e981cb8085 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy.ts @@ -6,7 +6,6 @@ */ import { uniq, omit } from 'lodash'; -import { safeLoad } from 'js-yaml'; import uuid from 'uuid/v4'; import type { ElasticsearchClient, @@ -32,52 +31,27 @@ import type { ListWithKuery, NewPackagePolicy, } from '../types'; -import { - agentPolicyStatuses, - storedPackagePoliciesToAgentInputs, - dataTypes, - packageToPackagePolicy, - AGENT_POLICY_INDEX, -} from '../../common'; +import { agentPolicyStatuses, packageToPackagePolicy, AGENT_POLICY_INDEX } from '../../common'; import type { DeleteAgentPolicyResponse, - Settings, FleetServerPolicy, Installation, Output, DeletePackagePoliciesResponse, } from '../../common'; import { AgentPolicyNameExistsError, HostedAgentPolicyRestrictionRelatedError } from '../errors'; -import { - storedPackagePoliciesToAgentPermissions, - DEFAULT_PERMISSIONS, -} from '../services/package_policies_to_agent_permissions'; import { getPackageInfo } from './epm/packages'; import { getAgentsByKuery } from './agents'; import { packagePolicyService } from './package_policy'; import { outputService } from './output'; import { agentPolicyUpdateEventHandler } from './agent_policy_update'; -import { getSettings } from './settings'; import { normalizeKuery, escapeSearchQueryPhrase } from './saved_object'; import { appContextService } from './app_context'; +import { getFullAgentPolicy } from './agent_policies'; const SAVED_OBJECT_TYPE = AGENT_POLICY_SAVED_OBJECT_TYPE; -const MONITORING_DATASETS = [ - 'elastic_agent', - 'elastic_agent.elastic_agent', - 'elastic_agent.apm_server', - 'elastic_agent.filebeat', - 'elastic_agent.fleet_server', - 'elastic_agent.metricbeat', - 'elastic_agent.osquerybeat', - 'elastic_agent.packetbeat', - 'elastic_agent.endpoint_security', - 'elastic_agent.auditbeat', - 'elastic_agent.heartbeat', -]; - class AgentPolicyService { private triggerAgentPolicyUpdatedEvent = async ( soClient: SavedObjectsClientContract, @@ -471,6 +445,38 @@ class AgentPolicyService { return res; } + public async bumpAllAgentPoliciesForOutput( + soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, + outputId: string, + options?: { user?: AuthenticatedUser } + ): Promise> { + const currentPolicies = await soClient.find({ + type: SAVED_OBJECT_TYPE, + fields: ['revision', 'data_output_id', 'monitoring_output_id'], + searchFields: ['data_output_id', 'monitoring_output_id'], + search: escapeSearchQueryPhrase(outputId), + }); + const bumpedPolicies = currentPolicies.saved_objects.map((policy) => { + policy.attributes = { + ...policy.attributes, + revision: policy.attributes.revision + 1, + updated_at: new Date().toISOString(), + updated_by: options?.user ? options.user.username : 'system', + }; + return policy; + }); + const res = await soClient.bulkUpdate(bumpedPolicies); + + await Promise.all( + currentPolicies.saved_objects.map((policy) => + this.triggerAgentPolicyUpdatedEvent(soClient, esClient, 'updated', policy.id) + ) + ); + + return res; + } + public async bumpAllAgentPolicies( soClient: SavedObjectsClientContract, esClient: ElasticsearchClient, @@ -617,14 +623,15 @@ class AgentPolicyService { } if (agentPolicy.package_policies && agentPolicy.package_policies.length) { - const deletedPackagePolicies: DeletePackagePoliciesResponse = await packagePolicyService.delete( - soClient, - esClient, - agentPolicy.package_policies as string[], - { - skipUnassignFromAgentPolicies: true, - } - ); + const deletedPackagePolicies: DeletePackagePoliciesResponse = + await packagePolicyService.delete( + soClient, + esClient, + agentPolicy.package_policies as string[], + { + skipUnassignFromAgentPolicies: true, + } + ); try { await packagePolicyService.runDeleteExternalCallbacks(deletedPackagePolicies); } catch (error) { @@ -677,7 +684,7 @@ class AgentPolicyService { '@timestamp': new Date().toISOString(), revision_idx: fullPolicy.revision, coordinator_idx: 0, - data: (fullPolicy as unknown) as FleetServerPolicy['data'], + data: fullPolicy as unknown as FleetServerPolicy['data'], policy_id: fullPolicy.id, default_fleet_server: policy.is_default_fleet_server === true, }; @@ -722,139 +729,7 @@ class AgentPolicyService { id: string, options?: { standalone: boolean } ): Promise { - let agentPolicy; - const standalone = options?.standalone; - - try { - agentPolicy = await this.get(soClient, id); - } catch (err) { - if (!err.isBoom || err.output.statusCode !== 404) { - throw err; - } - } - - if (!agentPolicy) { - return null; - } - - const defaultOutputId = await outputService.getDefaultOutputId(soClient); - if (!defaultOutputId) { - throw new Error('Default output is not setup'); - } - const defaultOutput = await outputService.get(soClient, defaultOutputId); - - const fullAgentPolicy: FullAgentPolicy = { - id: agentPolicy.id, - outputs: { - // TEMPORARY as we only support a default output - ...[defaultOutput].reduce( - // eslint-disable-next-line @typescript-eslint/naming-convention - (outputs, { config_yaml, name, type, hosts, ca_sha256, api_key }) => { - const configJs = config_yaml ? safeLoad(config_yaml) : {}; - outputs[name] = { - type, - hosts, - ca_sha256, - api_key, - ...configJs, - }; - - if (options?.standalone) { - delete outputs[name].api_key; - outputs[name].username = 'ES_USERNAME'; - outputs[name].password = 'ES_PASSWORD'; - } - - return outputs; - }, - {} - ), - }, - inputs: storedPackagePoliciesToAgentInputs(agentPolicy.package_policies as PackagePolicy[]), - revision: agentPolicy.revision, - ...(agentPolicy.monitoring_enabled && agentPolicy.monitoring_enabled.length > 0 - ? { - agent: { - monitoring: { - namespace: agentPolicy.namespace, - use_output: defaultOutput.name, - enabled: true, - logs: agentPolicy.monitoring_enabled.includes(dataTypes.Logs), - metrics: agentPolicy.monitoring_enabled.includes(dataTypes.Metrics), - }, - }, - } - : { - agent: { - monitoring: { enabled: false, logs: false, metrics: false }, - }, - }), - }; - - const permissions = (await storedPackagePoliciesToAgentPermissions( - soClient, - agentPolicy.package_policies - )) || { _fallback: DEFAULT_PERMISSIONS }; - - permissions._elastic_agent_checks = { - cluster: DEFAULT_PERMISSIONS.cluster, - }; - - // TODO: fetch this from the elastic agent package - const monitoringOutput = fullAgentPolicy.agent?.monitoring.use_output; - const monitoringNamespace = fullAgentPolicy.agent?.monitoring.namespace; - if ( - fullAgentPolicy.agent?.monitoring.enabled && - monitoringNamespace && - monitoringOutput && - fullAgentPolicy.outputs[monitoringOutput]?.type === 'elasticsearch' - ) { - let names: string[] = []; - if (fullAgentPolicy.agent.monitoring.logs) { - names = names.concat( - MONITORING_DATASETS.map((dataset) => `logs-${dataset}-${monitoringNamespace}`) - ); - } - if (fullAgentPolicy.agent.monitoring.metrics) { - names = names.concat( - MONITORING_DATASETS.map((dataset) => `metrics-${dataset}-${monitoringNamespace}`) - ); - } - - permissions._elastic_agent_checks.indices = [ - { - names, - privileges: ['auto_configure', 'create_doc'], - }, - ]; - } - - // Only add permissions if output.type is "elasticsearch" - fullAgentPolicy.output_permissions = Object.keys(fullAgentPolicy.outputs).reduce< - NonNullable - >((outputPermissions, outputName) => { - const output = fullAgentPolicy.outputs[outputName]; - if (output && output.type === 'elasticsearch') { - outputPermissions[outputName] = permissions; - } - return outputPermissions; - }, {}); - - // only add settings if not in standalone - if (!standalone) { - let settings: Settings; - try { - settings = await getSettings(soClient); - } catch (error) { - throw new Error('Default settings is not setup'); - } - if (settings.fleet_server_hosts && settings.fleet_server_hosts.length) { - fullAgentPolicy.fleet = { - hosts: settings.fleet_server_hosts, - }; - } - } - return fullAgentPolicy; + return getFullAgentPolicy(soClient, id, options); } } diff --git a/x-pack/plugins/fleet/server/services/agent_policy_update.ts b/x-pack/plugins/fleet/server/services/agent_policy_update.ts index fe3a3907b9d29..9703467d84c18 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy_update.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy_update.ts @@ -13,7 +13,7 @@ import { unenrollForAgentPolicyId } from './agents'; import { agentPolicyService } from './agent_policy'; import { appContextService } from './app_context'; -const fakeRequest = ({ +const fakeRequest = { headers: {}, getBasePath: () => '', path: '/', @@ -26,7 +26,7 @@ const fakeRequest = ({ url: '/', }, }, -} as unknown) as KibanaRequest; +} as unknown as KibanaRequest; export async function agentPolicyUpdateEventHandler( soClient: SavedObjectsClientContract, diff --git a/x-pack/plugins/fleet/server/services/agents/crud.ts b/x-pack/plugins/fleet/server/services/agents/crud.ts index 2a69f983ec894..03647c52e05f0 100644 --- a/x-pack/plugins/fleet/server/services/agents/crud.ts +++ b/x-pack/plugins/fleet/server/services/agents/crud.ts @@ -29,25 +29,29 @@ function _joinFilters(filters: Array): KueryNode try { return filters .filter((filter) => filter !== undefined) - .reduce((acc: KueryNode | undefined, kuery: string | KueryNode | undefined): - | KueryNode - | undefined => { - if (kuery === undefined) { - return acc; - } - const kueryNode: KueryNode = - typeof kuery === 'string' ? fromKueryExpression(removeSOAttributes(kuery)) : kuery; - - if (!acc) { - return kueryNode; - } - - return { - type: 'function', - function: 'and', - arguments: [acc, kueryNode], - }; - }, undefined as KueryNode | undefined); + .reduce( + ( + acc: KueryNode | undefined, + kuery: string | KueryNode | undefined + ): KueryNode | undefined => { + if (kuery === undefined) { + return acc; + } + const kueryNode: KueryNode = + typeof kuery === 'string' ? fromKueryExpression(removeSOAttributes(kuery)) : kuery; + + if (!acc) { + return kueryNode; + } + + return { + type: 'function', + function: 'and', + arguments: [acc, kueryNode], + }; + }, + undefined as KueryNode | undefined + ); } catch (err) { throw new IngestManagerError(`Kuery is malformed: ${err.message}`); } diff --git a/x-pack/plugins/fleet/server/services/agents/crud_so.ts b/x-pack/plugins/fleet/server/services/agents/crud_so.ts index 246c91bbef518..aa3cb4e4ec1a7 100644 --- a/x-pack/plugins/fleet/server/services/agents/crud_so.ts +++ b/x-pack/plugins/fleet/server/services/agents/crud_so.ts @@ -25,27 +25,31 @@ const INACTIVE_AGENT_CONDITION = `NOT (${ACTIVE_AGENT_CONDITION})`; function _joinFilters(filters: Array) { return filters .filter((filter) => filter !== undefined) - .reduce((acc: KueryNode | undefined, kuery: string | KueryNode | undefined): - | KueryNode - | undefined => { - if (kuery === undefined) { - return acc; - } - const kueryNode: KueryNode = - typeof kuery === 'string' - ? fromKueryExpression(normalizeKuery(AGENT_SAVED_OBJECT_TYPE, kuery)) - : kuery; - - if (!acc) { - return kueryNode; - } - - return { - type: 'function', - function: 'and', - arguments: [acc, kueryNode], - }; - }, undefined as KueryNode | undefined); + .reduce( + ( + acc: KueryNode | undefined, + kuery: string | KueryNode | undefined + ): KueryNode | undefined => { + if (kuery === undefined) { + return acc; + } + const kueryNode: KueryNode = + typeof kuery === 'string' + ? fromKueryExpression(normalizeKuery(AGENT_SAVED_OBJECT_TYPE, kuery)) + : kuery; + + if (!acc) { + return kueryNode; + } + + return { + type: 'function', + function: 'and', + arguments: [acc, kueryNode], + }; + }, + undefined as KueryNode | undefined + ); } export async function listAgents( diff --git a/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.ts b/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.ts index b666a1678ea6a..cc2357351f6df 100644 --- a/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.ts +++ b/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.ts @@ -22,7 +22,8 @@ import { escapeSearchQueryPhrase } from '../saved_object'; import { invalidateAPIKeys } from './security'; -const uuidRegex = /^\([0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}\)$/; +const uuidRegex = + /^\([0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}\)$/; export async function listEnrollmentApiKeys( esClient: ElasticsearchClient, diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/default_settings.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/default_settings.ts index 2dced977229e1..e7fccb22566a5 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/default_settings.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/default_settings.ts @@ -53,9 +53,10 @@ export function buildDefaultSettings({ `large amount of default fields detected for index template ${templateName} in package ${packageName}, applying the first ${QUERY_DEFAULT_FIELD_LIMIT} fields` ); } - const defaultFieldNames = (defaultFields.length > QUERY_DEFAULT_FIELD_LIMIT - ? defaultFields.slice(0, QUERY_DEFAULT_FIELD_LIMIT) - : defaultFields + const defaultFieldNames = ( + defaultFields.length > QUERY_DEFAULT_FIELD_LIMIT + ? defaultFields.slice(0, QUERY_DEFAULT_FIELD_LIMIT) + : defaultFields ).map((field) => field.name); return { diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/transform.test.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/transform.test.ts index 6febd27286ad1..34dcd5427d655 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/transform.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/transform.test.ts @@ -50,7 +50,7 @@ describe('test transform install', () => { }); test('can install new versions and removes older version', async () => { - const previousInstallation: Installation = ({ + const previousInstallation: Installation = { installed_es: [ { id: 'metrics-endpoint.policy-0.16.0-dev.0', @@ -61,9 +61,9 @@ describe('test transform install', () => { type: ElasticsearchAssetType.transform, }, ], - } as unknown) as Installation; + } as unknown as Installation; - const currentInstallation: Installation = ({ + const currentInstallation: Installation = { installed_es: [ { id: 'metrics-endpoint.policy-0.16.0-dev.0', @@ -82,7 +82,7 @@ describe('test transform install', () => { type: ElasticsearchAssetType.transform, }, ], - } as unknown) as Installation; + } as unknown as Installation; (getAsset as jest.MockedFunction) .mockReturnValueOnce(Buffer.from('{"content": "data"}', 'utf8')) .mockReturnValueOnce(Buffer.from('{"content": "data"}', 'utf8')); @@ -91,14 +91,14 @@ describe('test transform install', () => { .mockReturnValueOnce(Promise.resolve(previousInstallation)) .mockReturnValueOnce(Promise.resolve(currentInstallation)); - (getInstallationObject as jest.MockedFunction< - typeof getInstallationObject - >).mockReturnValueOnce( - Promise.resolve(({ + ( + getInstallationObject as jest.MockedFunction + ).mockReturnValueOnce( + Promise.resolve({ attributes: { installed_es: previousInstallation.installed_es, }, - } as unknown) as SavedObject) + } as unknown as SavedObject) ); esClient.transform.getTransform.mockReturnValueOnce( @@ -115,7 +115,7 @@ describe('test transform install', () => { ); await installTransform( - ({ + { name: 'endpoint', version: '0.16.0-dev.0', data_streams: [ @@ -148,7 +148,7 @@ describe('test transform install', () => { path: 'metadata_current', }, ], - } as unknown) as RegistryPackage, + } as unknown as RegistryPackage, [ 'endpoint-0.16.0-dev.0/data_stream/policy/elasticsearch/ingest_pipeline/default.json', 'endpoint-0.16.0-dev.0/elasticsearch/transform/metadata/default.json', @@ -275,18 +275,18 @@ describe('test transform install', () => { }); test('can install new version and when no older version', async () => { - const previousInstallation: Installation = ({ + const previousInstallation: Installation = { installed_es: [], - } as unknown) as Installation; + } as unknown as Installation; - const currentInstallation: Installation = ({ + const currentInstallation: Installation = { installed_es: [ { id: 'metrics-endpoint.metadata-current-default-0.16.0-dev.0', type: ElasticsearchAssetType.transform, }, ], - } as unknown) as Installation; + } as unknown as Installation; (getAsset as jest.MockedFunction).mockReturnValueOnce( Buffer.from('{"content": "data"}', 'utf8') ); @@ -294,16 +294,16 @@ describe('test transform install', () => { .mockReturnValueOnce(Promise.resolve(previousInstallation)) .mockReturnValueOnce(Promise.resolve(currentInstallation)); - (getInstallationObject as jest.MockedFunction< - typeof getInstallationObject - >).mockReturnValueOnce( - Promise.resolve(({ + ( + getInstallationObject as jest.MockedFunction + ).mockReturnValueOnce( + Promise.resolve({ attributes: { installed_es: [] }, - } as unknown) as SavedObject) + } as unknown as SavedObject) ); await installTransform( - ({ + { name: 'endpoint', version: '0.16.0-dev.0', data_streams: [ @@ -322,7 +322,7 @@ describe('test transform install', () => { path: 'metadata_current', }, ], - } as unknown) as RegistryPackage, + } as unknown as RegistryPackage, ['endpoint-0.16.0-dev.0/elasticsearch/transform/metadata_current/default.json'], esClient, savedObjectsClient @@ -360,29 +360,29 @@ describe('test transform install', () => { }); test('can removes older version when no new install in package', async () => { - const previousInstallation: Installation = ({ + const previousInstallation: Installation = { installed_es: [ { id: 'endpoint.metadata-current-default-0.15.0-dev.0', type: ElasticsearchAssetType.transform, }, ], - } as unknown) as Installation; + } as unknown as Installation; - const currentInstallation: Installation = ({ + const currentInstallation: Installation = { installed_es: [], - } as unknown) as Installation; + } as unknown as Installation; (getInstallation as jest.MockedFunction) .mockReturnValueOnce(Promise.resolve(previousInstallation)) .mockReturnValueOnce(Promise.resolve(currentInstallation)); - (getInstallationObject as jest.MockedFunction< - typeof getInstallationObject - >).mockReturnValueOnce( - Promise.resolve(({ + ( + getInstallationObject as jest.MockedFunction + ).mockReturnValueOnce( + Promise.resolve({ attributes: { installed_es: currentInstallation.installed_es }, - } as unknown) as SavedObject) + } as unknown as SavedObject) ); esClient.transform.getTransform.mockReturnValueOnce( @@ -399,7 +399,7 @@ describe('test transform install', () => { ); await installTransform( - ({ + { name: 'endpoint', version: '0.16.0-dev.0', data_streams: [ @@ -432,7 +432,7 @@ describe('test transform install', () => { path: 'metadata_current', }, ], - } as unknown) as RegistryPackage, + } as unknown as RegistryPackage, [], esClient, savedObjectsClient @@ -489,18 +489,18 @@ describe('test transform install', () => { }); test('ignore already exists error if saved object and ES transforms are out of sync', async () => { - const previousInstallation: Installation = ({ + const previousInstallation: Installation = { installed_es: [], - } as unknown) as Installation; + } as unknown as Installation; - const currentInstallation: Installation = ({ + const currentInstallation: Installation = { installed_es: [ { id: 'metrics-endpoint.metadata-current-default-0.16.0-dev.0', type: ElasticsearchAssetType.transform, }, ], - } as unknown) as Installation; + } as unknown as Installation; (getAsset as jest.MockedFunction).mockReturnValueOnce( Buffer.from('{"content": "data"}', 'utf8') ); @@ -508,12 +508,12 @@ describe('test transform install', () => { .mockReturnValueOnce(Promise.resolve(previousInstallation)) .mockReturnValueOnce(Promise.resolve(currentInstallation)); - (getInstallationObject as jest.MockedFunction< - typeof getInstallationObject - >).mockReturnValueOnce( - Promise.resolve(({ + ( + getInstallationObject as jest.MockedFunction + ).mockReturnValueOnce( + Promise.resolve({ attributes: { installed_es: [] }, - } as unknown) as SavedObject) + } as unknown as SavedObject) ); esClient.transport.request.mockImplementationOnce(() => @@ -528,7 +528,7 @@ describe('test transform install', () => { ); await installTransform( - ({ + { name: 'endpoint', version: '0.16.0-dev.0', data_streams: [ @@ -547,7 +547,7 @@ describe('test transform install', () => { path: 'metadata_current', }, ], - } as unknown) as RegistryPackage, + } as unknown as RegistryPackage, ['endpoint-0.16.0-dev.0/elasticsearch/transform/metadata_current/default.json'], esClient, savedObjectsClient diff --git a/x-pack/plugins/fleet/server/services/fleet_server/saved_object_migrations.ts b/x-pack/plugins/fleet/server/services/fleet_server/saved_object_migrations.ts index 5c05ed7532ac6..379bc8fa39bff 100644 --- a/x-pack/plugins/fleet/server/services/fleet_server/saved_object_migrations.ts +++ b/x-pack/plugins/fleet/server/services/fleet_server/saved_object_migrations.ts @@ -33,7 +33,7 @@ export async function runFleetServerMigration() { } function getInternalUserSOClient() { - const fakeRequest = ({ + const fakeRequest = { headers: {}, getBasePath: () => '', path: '/', @@ -46,7 +46,7 @@ function getInternalUserSOClient() { url: '/', }, }, - } as unknown) as KibanaRequest; + } as unknown as KibanaRequest; return appContextService.getInternalUserSOClient(fakeRequest); } @@ -74,9 +74,7 @@ async function migrateAgents() { for (const so of res.saved_objects) { try { - const { - attributes, - } = await appContextService + const { attributes } = await appContextService .getEncryptedSavedObjects() .getDecryptedAsInternalUser(AGENT_SAVED_OBJECT_TYPE, so.id); diff --git a/x-pack/plugins/fleet/server/services/output.test.ts b/x-pack/plugins/fleet/server/services/output.test.ts index 26e3955607ada..8103794fb0805 100644 --- a/x-pack/plugins/fleet/server/services/output.test.ts +++ b/x-pack/plugins/fleet/server/services/output.test.ts @@ -5,8 +5,10 @@ * 2.0. */ -import { outputService } from './output'; +import { savedObjectsClientMock } from '../../../../../src/core/server/mocks'; +import type { OutputSOAttributes } from '../types'; +import { outputService, outputIdToUuid } from './output'; import { appContextService } from './app_context'; jest.mock('./app_context'); @@ -34,7 +36,97 @@ const CONFIG_WITHOUT_ES_HOSTS = { }, }; +function getMockedSoClient() { + const soClient = savedObjectsClientMock.create(); + soClient.get.mockImplementation(async (type: string, id: string) => { + switch (id) { + case outputIdToUuid('output-test'): { + return { + id: outputIdToUuid('output-test'), + type: 'ingest-outputs', + references: [], + attributes: { + output_id: 'output-test', + }, + }; + } + default: + throw new Error('not found'); + } + }); + + return soClient; +} + describe('Output Service', () => { + describe('create', () => { + it('work with a predefined id', async () => { + const soClient = getMockedSoClient(); + soClient.create.mockResolvedValue({ + id: outputIdToUuid('output-test'), + type: 'ingest-output', + attributes: {}, + references: [], + }); + await outputService.create( + soClient, + { + is_default: false, + name: 'Test', + type: 'elasticsearch', + }, + { id: 'output-test' } + ); + + expect(soClient.create).toBeCalled(); + + // ID should always be the same for a predefined id + expect(soClient.create.mock.calls[0][2]?.id).toEqual(outputIdToUuid('output-test')); + expect((soClient.create.mock.calls[0][1] as OutputSOAttributes).output_id).toEqual( + 'output-test' + ); + }); + }); + + describe('get', () => { + it('work with a predefined id', async () => { + const soClient = getMockedSoClient(); + const output = await outputService.get(soClient, 'output-test'); + + expect(soClient.get).toHaveBeenCalledWith('ingest-outputs', outputIdToUuid('output-test')); + + expect(output.id).toEqual('output-test'); + }); + }); + + describe('getDefaultOutputId', () => { + it('work with a predefined id', async () => { + const soClient = getMockedSoClient(); + soClient.find.mockResolvedValue({ + page: 1, + per_page: 100, + total: 1, + saved_objects: [ + { + id: outputIdToUuid('output-test'), + type: 'ingest-outputs', + references: [], + score: 0, + attributes: { + output_id: 'output-test', + is_default: true, + }, + }, + ], + }); + const defaultId = await outputService.getDefaultOutputId(soClient); + + expect(soClient.find).toHaveBeenCalled(); + + expect(defaultId).toEqual('output-test'); + }); + }); + describe('getDefaultESHosts', () => { afterEach(() => { mockedAppContextService.getConfig.mockReset(); diff --git a/x-pack/plugins/fleet/server/services/output.ts b/x-pack/plugins/fleet/server/services/output.ts index 8c6bc7eca0401..5a7ba1e2c1223 100644 --- a/x-pack/plugins/fleet/server/services/output.ts +++ b/x-pack/plugins/fleet/server/services/output.ts @@ -5,7 +5,8 @@ * 2.0. */ -import type { SavedObjectsClientContract } from 'src/core/server'; +import type { SavedObject, SavedObjectsClientContract } from 'src/core/server'; +import uuid from 'uuid/v5'; import type { NewOutput, Output, OutputSOAttributes } from '../types'; import { DEFAULT_OUTPUT, OUTPUT_SAVED_OBJECT_TYPE } from '../constants'; @@ -17,8 +18,33 @@ const SAVED_OBJECT_TYPE = OUTPUT_SAVED_OBJECT_TYPE; const DEFAULT_ES_HOSTS = ['http://localhost:9200']; +// differentiate +function isUUID(val: string) { + return ( + typeof val === 'string' && + val.match(/[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}/) + ); +} + +export function outputIdToUuid(id: string) { + if (isUUID(id)) { + return id; + } + + // UUID v5 need a namespace (uuid.DNS), changing this params will result in loosing the ability to generate predicable uuid + return uuid(id, uuid.DNS); +} + +function outputSavedObjectToOutput(so: SavedObject) { + const { output_id: outputId, ...atributes } = so.attributes; + return { + id: outputId ?? so.id, + ...atributes, + }; +} + class OutputService { - public async getDefaultOutput(soClient: SavedObjectsClientContract) { + private async _getDefaultOutputsSO(soClient: SavedObjectsClientContract) { return await soClient.find({ type: OUTPUT_SAVED_OBJECT_TYPE, searchFields: ['is_default'], @@ -27,7 +53,7 @@ class OutputService { } public async ensureDefaultOutput(soClient: SavedObjectsClientContract) { - const outputs = await this.getDefaultOutput(soClient); + const outputs = await this._getDefaultOutputsSO(soClient); if (!outputs.saved_objects.length) { const newDefaultOutput = { @@ -39,10 +65,7 @@ class OutputService { return await this.create(soClient, newDefaultOutput); } - return { - id: outputs.saved_objects[0].id, - ...outputs.saved_objects[0].attributes, - }; + return outputSavedObjectToOutput(outputs.saved_objects[0]); } public getDefaultESHosts(): string[] { @@ -60,49 +83,84 @@ class OutputService { } public async getDefaultOutputId(soClient: SavedObjectsClientContract) { - const outputs = await this.getDefaultOutput(soClient); + const outputs = await this._getDefaultOutputsSO(soClient); if (!outputs.saved_objects.length) { return null; } - return outputs.saved_objects[0].id; + return outputSavedObjectToOutput(outputs.saved_objects[0]).id; } public async create( soClient: SavedObjectsClientContract, output: NewOutput, - options?: { id?: string } + options?: { id?: string; overwrite?: boolean } ): Promise { - const data = { ...output }; + const data: OutputSOAttributes = { ...output }; + + // ensure only default output exists + if (data.is_default) { + const defaultOuput = await this.getDefaultOutputId(soClient); + if (defaultOuput) { + throw new Error(`A default output already exists (${defaultOuput})`); + } + } if (data.hosts) { data.hosts = data.hosts.map(normalizeHostsForAgents); } - const newSo = await soClient.create( - SAVED_OBJECT_TYPE, - data as Output, - options - ); + if (options?.id) { + data.output_id = options?.id; + } + + const newSo = await soClient.create(SAVED_OBJECT_TYPE, data, { + ...options, + id: options?.id ? outputIdToUuid(options.id) : undefined, + }); return { - id: newSo.id, + id: options?.id ?? newSo.id, ...newSo.attributes, }; } + public async bulkGet( + soClient: SavedObjectsClientContract, + ids: string[], + { ignoreNotFound = false } = { ignoreNotFound: true } + ) { + const res = await soClient.bulkGet( + ids.map((id) => ({ id: outputIdToUuid(id), type: SAVED_OBJECT_TYPE })) + ); + + return res.saved_objects + .map((so) => { + if (so.error) { + if (!ignoreNotFound || so.error.statusCode !== 404) { + throw so.error; + } + return undefined; + } + + return outputSavedObjectToOutput(so); + }) + .filter((output): output is Output => typeof output !== 'undefined'); + } + public async get(soClient: SavedObjectsClientContract, id: string): Promise { - const outputSO = await soClient.get(SAVED_OBJECT_TYPE, id); + const outputSO = await soClient.get(SAVED_OBJECT_TYPE, outputIdToUuid(id)); if (outputSO.error) { throw new Error(outputSO.error.message); } - return { - id: outputSO.id, - ...outputSO.attributes, - }; + return outputSavedObjectToOutput(outputSO); + } + + public async delete(soClient: SavedObjectsClientContract, id: string) { + return soClient.delete(SAVED_OBJECT_TYPE, outputIdToUuid(id)); } public async update(soClient: SavedObjectsClientContract, id: string, data: Partial) { @@ -111,8 +169,11 @@ class OutputService { if (updateData.hosts) { updateData.hosts = updateData.hosts.map(normalizeHostsForAgents); } - - const outputSO = await soClient.update(SAVED_OBJECT_TYPE, id, updateData); + const outputSO = await soClient.update( + SAVED_OBJECT_TYPE, + outputIdToUuid(id), + updateData + ); if (outputSO.error) { throw new Error(outputSO.error.message); @@ -127,12 +188,7 @@ class OutputService { }); return { - items: outputs.saved_objects.map((outputSO) => { - return { - id: outputSO.id, - ...outputSO.attributes, - }; - }), + items: outputs.saved_objects.map(outputSavedObjectToOutput), total: outputs.total, page: 1, perPage: 1000, diff --git a/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.test.ts b/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.test.ts index a84118cdf1bfa..9f8ac01afe6c9 100644 --- a/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.test.ts +++ b/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.test.ts @@ -13,7 +13,7 @@ import type { PackagePolicy, RegistryDataStream } from '../types'; import { getPackageInfo } from './epm/packages'; import { - getDataStreamPermissions, + getDataStreamPrivileges, storedPackagePoliciesToAgentPermissions, } from './package_policies_to_agent_permissions'; @@ -380,12 +380,12 @@ describe('storedPackagePoliciesToAgentPermissions()', () => { }); }); -describe('getDataStreamPermissions()', () => { - it('returns defaults for a datastream with no permissions', () => { +describe('getDataStreamPrivileges()', () => { + it('returns defaults for a datastream with no privileges', () => { const dataStream = { type: 'logs', dataset: 'test' } as RegistryDataStream; - const permissions = getDataStreamPermissions(dataStream); + const privileges = getDataStreamPrivileges(dataStream); - expect(permissions).toMatchObject({ + expect(privileges).toMatchObject({ names: ['logs-test-*'], privileges: ['auto_configure', 'create_doc'], }); @@ -393,9 +393,9 @@ describe('getDataStreamPermissions()', () => { it('adds the namespace to the index name', () => { const dataStream = { type: 'logs', dataset: 'test' } as RegistryDataStream; - const permissions = getDataStreamPermissions(dataStream, 'namespace'); + const privileges = getDataStreamPrivileges(dataStream, 'namespace'); - expect(permissions).toMatchObject({ + expect(privileges).toMatchObject({ names: ['logs-test-namespace'], privileges: ['auto_configure', 'create_doc'], }); @@ -407,9 +407,9 @@ describe('getDataStreamPermissions()', () => { dataset: 'test', dataset_is_prefix: true, } as RegistryDataStream; - const permissions = getDataStreamPermissions(dataStream, 'namespace'); + const privileges = getDataStreamPrivileges(dataStream, 'namespace'); - expect(permissions).toMatchObject({ + expect(privileges).toMatchObject({ names: ['logs-test.*-namespace'], privileges: ['auto_configure', 'create_doc'], }); @@ -421,25 +421,27 @@ describe('getDataStreamPermissions()', () => { dataset: 'test', hidden: true, } as RegistryDataStream; - const permissions = getDataStreamPermissions(dataStream, 'namespace'); + const privileges = getDataStreamPrivileges(dataStream, 'namespace'); - expect(permissions).toMatchObject({ + expect(privileges).toMatchObject({ names: ['.logs-test-namespace'], privileges: ['auto_configure', 'create_doc'], }); }); - it('uses custom permissions if they are present in the datastream', () => { + it('uses custom privileges if they are present in the datastream', () => { const dataStream = { type: 'logs', dataset: 'test', - permissions: { indices: ['read', 'write'] }, + elasticsearch: { + privileges: { indices: ['read', 'monitor'] }, + }, } as RegistryDataStream; - const permissions = getDataStreamPermissions(dataStream, 'namespace'); + const privileges = getDataStreamPrivileges(dataStream, 'namespace'); - expect(permissions).toMatchObject({ + expect(privileges).toMatchObject({ names: ['logs-test-namespace'], - privileges: ['read', 'write'], + privileges: ['read', 'monitor'], }); }); }); diff --git a/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.ts b/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.ts index 07ad892adc653..22dcb8ac7b4cb 100644 --- a/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.ts +++ b/x-pack/plugins/fleet/server/services/package_policies_to_agent_permissions.ts @@ -6,9 +6,9 @@ */ import type { SavedObjectsClientContract } from 'kibana/server'; -import type { FullAgentPolicyOutputPermissions, RegistryDataStreamPermissions } from '../../common'; +import type { FullAgentPolicyOutputPermissions, RegistryDataStreamPrivileges } from '../../common'; +import { PACKAGE_POLICY_DEFAULT_INDEX_PRIVILEGES } from '../constants'; import { getPackageInfo } from '../../server/services/epm/packages'; - import type { PackagePolicy } from '../types'; export const DEFAULT_PERMISSIONS = { @@ -22,7 +22,7 @@ export const DEFAULT_PERMISSIONS = { 'synthetics-*', '.logs-endpoint.diagnostic.collection-*', ], - privileges: ['auto_configure', 'create_doc'], + privileges: PACKAGE_POLICY_DEFAULT_INDEX_PRIVILEGES, }, ], }; @@ -104,12 +104,16 @@ export async function storedPackagePoliciesToAgentPermissions( return; } - const ds = { + const ds: DataStreamMeta = { type: stream.data_stream.type, dataset: stream.compiled_stream?.data_stream?.dataset ?? stream.data_stream.dataset, }; + if (stream.data_stream.elasticsearch) { + ds.elasticsearch = stream.data_stream.elasticsearch; + } + dataStreams_.push(ds); }); @@ -121,7 +125,7 @@ export async function storedPackagePoliciesToAgentPermissions( packagePolicy.name, { indices: dataStreamsForPermissions.map((ds) => - getDataStreamPermissions(ds, packagePolicy.namespace) + getDataStreamPrivileges(ds, packagePolicy.namespace) ), }, ]; @@ -136,10 +140,12 @@ interface DataStreamMeta { dataset: string; dataset_is_prefix?: boolean; hidden?: boolean; - permissions?: RegistryDataStreamPermissions; + elasticsearch?: { + privileges?: RegistryDataStreamPrivileges; + }; } -export function getDataStreamPermissions(dataStream: DataStreamMeta, namespace: string = '*') { +export function getDataStreamPrivileges(dataStream: DataStreamMeta, namespace: string = '*') { let index = `${dataStream.type}-${dataStream.dataset}`; if (dataStream.dataset_is_prefix) { @@ -152,8 +158,12 @@ export function getDataStreamPermissions(dataStream: DataStreamMeta, namespace: index += `-${namespace}`; + const privileges = dataStream?.elasticsearch?.privileges?.indices?.length + ? dataStream.elasticsearch.privileges.indices + : PACKAGE_POLICY_DEFAULT_INDEX_PRIVILEGES; + return { names: [index], - privileges: dataStream.permissions?.indices || ['auto_configure', 'create_doc'], + privileges, }; } diff --git a/x-pack/plugins/fleet/server/services/package_policy.test.ts b/x-pack/plugins/fleet/server/services/package_policy.test.ts index 204650574e92a..fe5a3030bd95a 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.test.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.test.ts @@ -14,19 +14,25 @@ import { import type { SavedObjectsClient, SavedObjectsUpdateResponse } from 'src/core/server'; import type { KibanaRequest } from 'kibana/server'; -import type { PackageInfo, PackagePolicySOAttributes, AgentPolicySOAttributes } from '../types'; +import type { + PackageInfo, + PackagePolicySOAttributes, + AgentPolicySOAttributes, + PostPackagePolicyDeleteCallback, + RegistryDataStream, + PackagePolicyInputStream, +} from '../types'; import { createPackagePolicyMock } from '../../common/mocks'; + import type { PutPackagePolicyUpdateCallback, PostPackagePolicyCreateCallback } from '..'; import { createAppContextStartContractMock, xpackMocks } from '../mocks'; -import type { PostPackagePolicyDeleteCallback } from '../types'; - import type { DeletePackagePoliciesResponse } from '../../common'; import { IngestManagerError } from '../errors'; -import { packagePolicyService } from './package_policy'; +import { packagePolicyService, _applyIndexPrivileges } from './package_policy'; import { appContextService } from './app_context'; async function mockedGetAssetsData(_a: any, _b: any, dataset: string) { @@ -117,7 +123,7 @@ describe('Package policy service', () => { describe('compilePackagePolicyInputs', () => { it('should work with config variables from the stream', async () => { const inputs = await packagePolicyService.compilePackagePolicyInputs( - ({ + { data_streams: [ { type: 'logs', @@ -131,7 +137,7 @@ describe('Package policy service', () => { inputs: [{ type: 'log' }], }, ], - } as unknown) as PackageInfo, + } as unknown as PackageInfo, {}, [ { @@ -180,7 +186,7 @@ describe('Package policy service', () => { it('should work with a two level dataset name', async () => { const inputs = await packagePolicyService.compilePackagePolicyInputs( - ({ + { data_streams: [ { type: 'logs', @@ -194,7 +200,7 @@ describe('Package policy service', () => { inputs: [{ type: 'log' }], }, ], - } as unknown) as PackageInfo, + } as unknown as PackageInfo, {}, [ { @@ -232,7 +238,7 @@ describe('Package policy service', () => { it('should work with config variables at the input level', async () => { const inputs = await packagePolicyService.compilePackagePolicyInputs( - ({ + { data_streams: [ { dataset: 'package.dataset1', @@ -246,7 +252,7 @@ describe('Package policy service', () => { inputs: [{ type: 'log' }], }, ], - } as unknown) as PackageInfo, + } as unknown as PackageInfo, {}, [ { @@ -295,7 +301,7 @@ describe('Package policy service', () => { it('should work with config variables at the package level', async () => { const inputs = await packagePolicyService.compilePackagePolicyInputs( - ({ + { data_streams: [ { dataset: 'package.dataset1', @@ -309,7 +315,7 @@ describe('Package policy service', () => { inputs: [{ type: 'log' }], }, ], - } as unknown) as PackageInfo, + } as unknown as PackageInfo, { hosts: { value: ['localhost'], @@ -363,14 +369,14 @@ describe('Package policy service', () => { it('should work with an input with a template and no streams', async () => { const inputs = await packagePolicyService.compilePackagePolicyInputs( - ({ + { data_streams: [], policy_templates: [ { inputs: [{ type: 'log', template_path: 'some_template_path.yml' }], }, ], - } as unknown) as PackageInfo, + } as unknown as PackageInfo, {}, [ { @@ -405,7 +411,7 @@ describe('Package policy service', () => { it('should work with an input with a template and streams', async () => { const inputs = await packagePolicyService.compilePackagePolicyInputs( - ({ + { data_streams: [ { dataset: 'package.dataset1', @@ -424,7 +430,7 @@ describe('Package policy service', () => { inputs: [{ type: 'log', template_path: 'some_template_path.yml' }], }, ], - } as unknown) as PackageInfo, + } as unknown as PackageInfo, {}, [ { @@ -510,13 +516,13 @@ describe('Package policy service', () => { it('should work with a package without input', async () => { const inputs = await packagePolicyService.compilePackagePolicyInputs( - ({ + { policy_templates: [ { inputs: undefined, }, ], - } as unknown) as PackageInfo, + } as unknown as PackageInfo, {}, [] ); @@ -526,13 +532,13 @@ describe('Package policy service', () => { it('should work with a package with a empty inputs array', async () => { const inputs = await packagePolicyService.compilePackagePolicyInputs( - ({ + { policy_templates: [ { inputs: [], }, ], - } as unknown) as PackageInfo, + } as unknown as PackageInfo, {}, [] ); @@ -1069,3 +1075,119 @@ describe('Package policy service', () => { }); }); }); + +describe('_applyIndexPrivileges()', () => { + function createPackageStream(indexPrivileges?: string[]): RegistryDataStream { + const stream: RegistryDataStream = { + type: '', + dataset: '', + title: '', + release: '', + package: '', + path: '', + }; + + if (indexPrivileges) { + stream.elasticsearch = { + privileges: { + indices: indexPrivileges, + }, + }; + } + + return stream; + } + + function createInputStream( + opts: Partial = {} + ): PackagePolicyInputStream { + return { + id: '', + enabled: true, + data_stream: { + dataset: '', + type: '', + }, + ...opts, + }; + } + + beforeAll(async () => { + appContextService.start(createAppContextStartContractMock()); + }); + + it('should do nothing if packageStream has no privileges', () => { + const packageStream = createPackageStream(); + const inputStream = createInputStream(); + + const streamOut = _applyIndexPrivileges(packageStream, inputStream); + expect(streamOut).toEqual(inputStream); + }); + + it('should not apply privileges if all privileges are forbidden', () => { + const forbiddenPrivileges = ['write', 'delete', 'delete_index', 'all']; + const packageStream = createPackageStream(forbiddenPrivileges); + const inputStream = createInputStream(); + + const streamOut = _applyIndexPrivileges(packageStream, inputStream); + expect(streamOut).toEqual(inputStream); + }); + + it('should not apply privileges if all privileges are unrecognized', () => { + const unrecognizedPrivileges = ['idnotexist', 'invalidperm']; + const packageStream = createPackageStream(unrecognizedPrivileges); + const inputStream = createInputStream(); + + const streamOut = _applyIndexPrivileges(packageStream, inputStream); + expect(streamOut).toEqual(inputStream); + }); + + it('should apply privileges if all privileges are valid', () => { + const validPrivileges = [ + 'auto_configure', + 'create_doc', + 'maintenance', + 'monitor', + 'read', + 'read_cross_cluster', + ]; + + const packageStream = createPackageStream(validPrivileges); + const inputStream = createInputStream(); + const expectedStream = { + ...inputStream, + data_stream: { + ...inputStream.data_stream, + elasticsearch: { + privileges: { + indices: validPrivileges, + }, + }, + }, + }; + + const streamOut = _applyIndexPrivileges(packageStream, inputStream); + expect(streamOut).toEqual(expectedStream); + }); + + it('should only apply valid privileges when there is a mix of valid and invalid', () => { + const mixedPrivileges = ['auto_configure', 'read_cross_cluster', 'idontexist', 'delete']; + + const packageStream = createPackageStream(mixedPrivileges); + const inputStream = createInputStream(); + const expectedStream = { + ...inputStream, + data_stream: { + ...inputStream.data_stream, + elasticsearch: { + privileges: { + indices: ['auto_configure', 'read_cross_cluster'], + }, + }, + }, + }; + + const streamOut = _applyIndexPrivileges(packageStream, inputStream); + expect(streamOut).toEqual(expectedStream); + }); +}); diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts index 598dd16b2928e..c806b37f88153 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { omit } from 'lodash'; +import { omit, partition } from 'lodash'; import { i18n } from '@kbn/i18n'; import semverLte from 'semver/functions/lte'; import { getFlattenedObject } from '@kbn/std'; @@ -38,6 +38,7 @@ import type { ListWithKuery, ListResult, UpgradePackagePolicyDryRunResponseItem, + RegistryDataStream, } from '../../common'; import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../constants'; import { @@ -71,6 +72,15 @@ export type InputsOverride = Partial & { const SAVED_OBJECT_TYPE = PACKAGE_POLICY_SAVED_OBJECT_TYPE; +export const DATA_STREAM_ALLOWED_INDEX_PRIVILEGES = new Set([ + 'auto_configure', + 'create_doc', + 'maintenance', + 'monitor', + 'read', + 'read_cross_cluster', +]); + class PackagePolicyService { public async create( soClient: SavedObjectsClientContract, @@ -794,13 +804,51 @@ async function _compilePackageStreams( return await Promise.all(streamsPromises); } +// temporary export to enable testing pending refactor https://github.com/elastic/kibana/issues/112386 +export function _applyIndexPrivileges( + packageDataStream: RegistryDataStream, + stream: PackagePolicyInputStream +): PackagePolicyInputStream { + const streamOut = { ...stream }; + + const indexPrivileges = packageDataStream?.elasticsearch?.privileges?.indices; + + if (!indexPrivileges?.length) { + return streamOut; + } + + const [valid, invalid] = partition(indexPrivileges, (permission) => + DATA_STREAM_ALLOWED_INDEX_PRIVILEGES.has(permission) + ); + + if (invalid.length) { + appContextService + .getLogger() + .warn( + `Ignoring invalid or forbidden index privilege(s) in "${stream.id}" data stream: ${invalid}` + ); + } + + if (valid.length) { + stream.data_stream.elasticsearch = { + privileges: { + indices: valid, + }, + }; + } + + return streamOut; +} + async function _compilePackageStream( registryPkgInfo: RegistryPackage, pkgInfo: PackageInfo, vars: PackagePolicy['vars'], input: PackagePolicyInput, - stream: PackagePolicyInputStream + streamIn: PackagePolicyInputStream ) { + let stream = streamIn; + if (!stream.enabled) { return { ...stream, compiled_stream: undefined }; } @@ -820,6 +868,8 @@ async function _compilePackageStream( ); } + stream = _applyIndexPrivileges(packageDataStream, streamIn); + const streamFromPkg = (packageDataStream.streams || []).find( (pkgStream) => pkgStream.input === input.type ); diff --git a/x-pack/plugins/fleet/server/services/preconfiguration.test.ts b/x-pack/plugins/fleet/server/services/preconfiguration.test.ts index d374553e1db83..43887bc2787f4 100644 --- a/x-pack/plugins/fleet/server/services/preconfiguration.test.ts +++ b/x-pack/plugins/fleet/server/services/preconfiguration.test.ts @@ -9,7 +9,7 @@ import { elasticsearchServiceMock, savedObjectsClientMock } from 'src/core/serve import { SavedObjectsErrorHelpers } from '../../../../../src/core/server'; -import type { PreconfiguredAgentPolicy } from '../../common/types'; +import type { PreconfiguredAgentPolicy, PreconfiguredOutput } from '../../common/types'; import type { AgentPolicy, NewPackagePolicy, Output } from '../types'; import { AGENT_POLICY_SAVED_OBJECT_TYPE } from '../constants'; @@ -19,9 +19,15 @@ import * as agentPolicy from './agent_policy'; import { ensurePreconfiguredPackagesAndPolicies, comparePreconfiguredPolicyToCurrent, + ensurePreconfiguredOutputs, + cleanPreconfiguredOutputs, } from './preconfiguration'; +import { outputService } from './output'; jest.mock('./agent_policy_update'); +jest.mock('./output'); + +const mockedOutputService = outputService as jest.Mocked; const mockInstalledPackages = new Map(); const mockConfiguredPolicies = new Map(); @@ -156,12 +162,17 @@ jest.mock('./app_context', () => ({ })); const spyAgentPolicyServiceUpdate = jest.spyOn(agentPolicy.agentPolicyService, 'update'); +const spyAgentPolicyServicBumpAllAgentPoliciesForOutput = jest.spyOn( + agentPolicy.agentPolicyService, + 'bumpAllAgentPoliciesForOutput' +); describe('policy preconfiguration', () => { beforeEach(() => { mockInstalledPackages.clear(); mockConfiguredPolicies.clear(); spyAgentPolicyServiceUpdate.mockClear(); + spyAgentPolicyServicBumpAllAgentPoliciesForOutput.mockClear(); }); it('should perform a no-op when passed no policies or packages', async () => { @@ -287,50 +298,46 @@ describe('policy preconfiguration', () => { const soClient = getPutPreconfiguredPackagesMock(); const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; - const { - policies: policiesA, - nonFatalErrors: nonFatalErrorsA, - } = await ensurePreconfiguredPackagesAndPolicies( - soClient, - esClient, - [ - { - name: 'Test policy', - namespace: 'default', - id: 'test-id', - package_policies: [], - }, - ] as PreconfiguredAgentPolicy[], - [], - mockDefaultOutput - ); + const { policies: policiesA, nonFatalErrors: nonFatalErrorsA } = + await ensurePreconfiguredPackagesAndPolicies( + soClient, + esClient, + [ + { + name: 'Test policy', + namespace: 'default', + id: 'test-id', + package_policies: [], + }, + ] as PreconfiguredAgentPolicy[], + [], + mockDefaultOutput + ); expect(policiesA.length).toEqual(1); expect(policiesA[0].id).toBe('mocked-test-id'); expect(nonFatalErrorsA.length).toBe(0); - const { - policies: policiesB, - nonFatalErrors: nonFatalErrorsB, - } = await ensurePreconfiguredPackagesAndPolicies( - soClient, - esClient, - [ - { - name: 'Test policy redo', - namespace: 'default', - id: 'test-id', - package_policies: [ - { - package: { name: 'some-uninstalled-package' }, - name: 'This package is not installed', - }, - ], - }, - ] as PreconfiguredAgentPolicy[], - [], - mockDefaultOutput - ); + const { policies: policiesB, nonFatalErrors: nonFatalErrorsB } = + await ensurePreconfiguredPackagesAndPolicies( + soClient, + esClient, + [ + { + name: 'Test policy redo', + namespace: 'default', + id: 'test-id', + package_policies: [ + { + package: { name: 'some-uninstalled-package' }, + name: 'This package is not installed', + }, + ], + }, + ] as PreconfiguredAgentPolicy[], + [], + mockDefaultOutput + ); expect(policiesB.length).toEqual(1); expect(policiesB[0].id).toBe('mocked-test-id'); @@ -352,26 +359,24 @@ describe('policy preconfiguration', () => { is_managed: true, } as PreconfiguredAgentPolicy); - const { - policies, - nonFatalErrors: nonFatalErrorsB, - } = await ensurePreconfiguredPackagesAndPolicies( - soClient, - esClient, - [ - { - name: 'Renamed Test policy', - description: 'Renamed Test policy description', - unenroll_timeout: 999, - namespace: 'default', - id: 'test-id', - is_managed: true, - package_policies: [], - }, - ] as PreconfiguredAgentPolicy[], - [], - mockDefaultOutput - ); + const { policies, nonFatalErrors: nonFatalErrorsB } = + await ensurePreconfiguredPackagesAndPolicies( + soClient, + esClient, + [ + { + name: 'Renamed Test policy', + description: 'Renamed Test policy description', + unenroll_timeout: 999, + namespace: 'default', + id: 'test-id', + is_managed: true, + package_policies: [], + }, + ] as PreconfiguredAgentPolicy[], + [], + mockDefaultOutput + ); expect(spyAgentPolicyServiceUpdate).toBeCalled(); expect(spyAgentPolicyServiceUpdate).toBeCalledWith( expect.anything(), // soClient @@ -400,16 +405,14 @@ describe('policy preconfiguration', () => { }; mockConfiguredPolicies.set('test-id', policy); - const { - policies, - nonFatalErrors: nonFatalErrorsB, - } = await ensurePreconfiguredPackagesAndPolicies( - soClient, - esClient, - [policy], - [], - mockDefaultOutput - ); + const { policies, nonFatalErrors: nonFatalErrorsB } = + await ensurePreconfiguredPackagesAndPolicies( + soClient, + esClient, + [policy], + [], + mockDefaultOutput + ); expect(spyAgentPolicyServiceUpdate).not.toBeCalled(); expect(policies.length).toEqual(1); expect(policies[0].id).toBe('test-id'); @@ -488,3 +491,168 @@ describe('comparePreconfiguredPolicyToCurrent', () => { expect(hasChanged).toBe(false); }); }); + +describe('output preconfiguration', () => { + beforeEach(() => { + mockedOutputService.create.mockReset(); + mockedOutputService.update.mockReset(); + mockedOutputService.getDefaultESHosts.mockReturnValue(['http://default-es:9200']); + mockedOutputService.bulkGet.mockImplementation(async (soClient, id): Promise => { + return [ + { + id: 'existing-output-1', + is_default: false, + name: 'Output 1', + // @ts-ignore + type: 'elasticsearch', + hosts: ['http://es.co:80'], + is_preconfigured: true, + }, + ]; + }); + }); + + it('should create preconfigured output that does not exists', async () => { + const soClient = savedObjectsClientMock.create(); + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + await ensurePreconfiguredOutputs(soClient, esClient, [ + { + id: 'non-existing-output-1', + name: 'Output 1', + type: 'elasticsearch', + is_default: false, + hosts: ['http://test.fr'], + }, + ]); + + expect(mockedOutputService.create).toBeCalled(); + expect(mockedOutputService.update).not.toBeCalled(); + expect(spyAgentPolicyServicBumpAllAgentPoliciesForOutput).not.toBeCalled(); + }); + + it('should set default hosts if hosts is not set output that does not exists', async () => { + const soClient = savedObjectsClientMock.create(); + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + await ensurePreconfiguredOutputs(soClient, esClient, [ + { + id: 'non-existing-output-1', + name: 'Output 1', + type: 'elasticsearch', + is_default: false, + }, + ]); + + expect(mockedOutputService.create).toBeCalled(); + expect(mockedOutputService.create.mock.calls[0][1].hosts).toEqual(['http://default-es:9200']); + }); + + it('should update output if preconfigured output exists and changed', async () => { + const soClient = savedObjectsClientMock.create(); + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + soClient.find.mockResolvedValue({ saved_objects: [], page: 0, per_page: 0, total: 0 }); + await ensurePreconfiguredOutputs(soClient, esClient, [ + { + id: 'existing-output-1', + is_default: false, + name: 'Output 1', + type: 'elasticsearch', + hosts: ['http://newhostichanged.co:9201'], // field that changed + }, + ]); + + expect(mockedOutputService.create).not.toBeCalled(); + expect(mockedOutputService.update).toBeCalled(); + expect(spyAgentPolicyServicBumpAllAgentPoliciesForOutput).toBeCalled(); + }); + + const SCENARIOS: Array<{ name: string; data: PreconfiguredOutput }> = [ + { + name: 'no changes', + data: { + id: 'existing-output-1', + is_default: false, + name: 'Output 1', + type: 'elasticsearch', + hosts: ['http://es.co:80'], + }, + }, + { + name: 'hosts without port', + data: { + id: 'existing-output-1', + is_default: false, + name: 'Output 1', + type: 'elasticsearch', + hosts: ['http://es.co'], + }, + }, + ]; + SCENARIOS.forEach((scenario) => { + const { data, name } = scenario; + it(`should do nothing if preconfigured output exists and did not changed (${name})`, async () => { + const soClient = savedObjectsClientMock.create(); + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + await ensurePreconfiguredOutputs(soClient, esClient, [data]); + + expect(mockedOutputService.create).not.toBeCalled(); + expect(mockedOutputService.update).not.toBeCalled(); + }); + }); + + it('should not delete non deleted preconfigured output', async () => { + const soClient = savedObjectsClientMock.create(); + mockedOutputService.list.mockResolvedValue({ + items: [ + { id: 'output1', is_preconfigured: true } as Output, + { id: 'output2', is_preconfigured: true } as Output, + ], + page: 1, + perPage: 10000, + total: 1, + }); + await cleanPreconfiguredOutputs(soClient, [ + { + id: 'output1', + is_default: false, + name: 'Output 1', + type: 'elasticsearch', + hosts: ['http://es.co:9201'], + }, + { + id: 'output2', + is_default: false, + name: 'Output 2', + type: 'elasticsearch', + hosts: ['http://es.co:9201'], + }, + ]); + + expect(mockedOutputService.delete).not.toBeCalled(); + }); + + it('should delete deleted preconfigured output', async () => { + const soClient = savedObjectsClientMock.create(); + mockedOutputService.list.mockResolvedValue({ + items: [ + { id: 'output1', is_preconfigured: true } as Output, + { id: 'output2', is_preconfigured: true } as Output, + ], + page: 1, + perPage: 10000, + total: 1, + }); + await cleanPreconfiguredOutputs(soClient, [ + { + id: 'output1', + is_default: false, + name: 'Output 1', + type: 'elasticsearch', + hosts: ['http://es.co:9201'], + }, + ]); + + expect(mockedOutputService.delete).toBeCalled(); + expect(mockedOutputService.delete).toBeCalledTimes(1); + expect(mockedOutputService.delete.mock.calls[0][1]).toEqual('output2'); + }); +}); diff --git a/x-pack/plugins/fleet/server/services/preconfiguration.ts b/x-pack/plugins/fleet/server/services/preconfiguration.ts index 37ed98a6f4aa0..30c5c27c68916 100644 --- a/x-pack/plugins/fleet/server/services/preconfiguration.ts +++ b/x-pack/plugins/fleet/server/services/preconfiguration.ts @@ -8,6 +8,7 @@ import type { ElasticsearchClient, SavedObjectsClientContract } from 'src/core/server'; import { i18n } from '@kbn/i18n'; import { groupBy, omit, pick, isEqual } from 'lodash'; +import { safeDump } from 'js-yaml'; import type { NewPackagePolicy, @@ -17,16 +18,15 @@ import type { PreconfiguredAgentPolicy, PreconfiguredPackage, PreconfigurationError, + PreconfiguredOutput, } from '../../common'; -import { AGENT_POLICY_SAVED_OBJECT_TYPE } from '../../common'; - +import { AGENT_POLICY_SAVED_OBJECT_TYPE, normalizeHostsForAgents } from '../../common'; import { PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE, PRECONFIGURATION_LATEST_KEYWORD, } from '../constants'; import { escapeSearchQueryPhrase } from './saved_object'; - import { pkgToPkgKey } from './epm/registry'; import { getInstallation, getPackageInfo } from './epm/packages'; import { ensurePackagesCompletedInstall } from './epm/packages/install'; @@ -35,6 +35,7 @@ import { agentPolicyService, addPackageToAgentPolicy } from './agent_policy'; import type { InputsOverride } from './package_policy'; import { overridePackageInputs } from './package_policy'; import { appContextService } from './app_context'; +import { outputService } from './output'; interface PreconfigurationResult { policies: Array<{ id: string; updated_at: string }>; @@ -42,6 +43,89 @@ interface PreconfigurationResult { nonFatalErrors: PreconfigurationError[]; } +function isPreconfiguredOutputDifferentFromCurrent( + existingOutput: Output, + preconfiguredOutput: Partial +): boolean { + return ( + existingOutput.is_default !== preconfiguredOutput.is_default || + existingOutput.name !== preconfiguredOutput.name || + existingOutput.type !== preconfiguredOutput.type || + (preconfiguredOutput.hosts && + !isEqual( + existingOutput.hosts?.map(normalizeHostsForAgents), + preconfiguredOutput.hosts.map(normalizeHostsForAgents) + )) || + existingOutput.ca_sha256 !== preconfiguredOutput.ca_sha256 || + existingOutput.config_yaml !== preconfiguredOutput.config_yaml + ); +} + +export async function ensurePreconfiguredOutputs( + soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, + outputs: PreconfiguredOutput[] +) { + if (outputs.length === 0) { + return; + } + + const existingOutputs = await outputService.bulkGet( + soClient, + outputs.map(({ id }) => id), + { ignoreNotFound: true } + ); + + await Promise.all( + outputs.map(async (output) => { + const existingOutput = existingOutputs.find((o) => o.id === output.id); + + const { id, config, ...outputData } = output; + + const configYaml = config ? safeDump(config) : undefined; + + const data = { + ...outputData, + config_yaml: configYaml, + is_preconfigured: true, + }; + + if (!data.hosts || data.hosts.length === 0) { + data.hosts = outputService.getDefaultESHosts(); + } + + if (!existingOutput) { + await outputService.create(soClient, data, { id, overwrite: true }); + } else if (isPreconfiguredOutputDifferentFromCurrent(existingOutput, data)) { + await outputService.update(soClient, id, data); + // Bump revision of all policies using that output + if (outputData.is_default) { + await agentPolicyService.bumpAllAgentPolicies(soClient, esClient); + } else { + await agentPolicyService.bumpAllAgentPoliciesForOutput(soClient, esClient, id); + } + } + }) + ); +} + +export async function cleanPreconfiguredOutputs( + soClient: SavedObjectsClientContract, + outputs: PreconfiguredOutput[] +) { + const existingPreconfiguredOutput = (await outputService.list(soClient)).items.filter( + (o) => o.is_preconfigured === true + ); + const logger = appContextService.getLogger(); + + for (const output of existingPreconfiguredOutput) { + if (!outputs.find(({ id }) => output.id === id)) { + logger.info(`Deleting preconfigured output ${output.id}`); + await outputService.delete(soClient, output.id); + } + } +} + export async function ensurePreconfiguredPackagesAndPolicies( soClient: SavedObjectsClientContract, esClient: ElasticsearchClient, @@ -224,7 +308,7 @@ export async function ensurePreconfiguredPackagesAndPolicies( } // Add the is_managed flag after configuring package policies to avoid errors if (shouldAddIsManagedFlag) { - agentPolicyService.update(soClient, esClient, policy!.id, { is_managed: true }); + await agentPolicyService.update(soClient, esClient, policy!.id, { is_managed: true }); } } } diff --git a/x-pack/plugins/fleet/server/services/setup.ts b/x-pack/plugins/fleet/server/services/setup.ts index 1f3c3c5082b34..8c49bffdbf25c 100644 --- a/x-pack/plugins/fleet/server/services/setup.ts +++ b/x-pack/plugins/fleet/server/services/setup.ts @@ -15,7 +15,11 @@ import { SO_SEARCH_LIMIT, DEFAULT_PACKAGES } from '../constants'; import { appContextService } from './app_context'; import { agentPolicyService } from './agent_policy'; -import { ensurePreconfiguredPackagesAndPolicies } from './preconfiguration'; +import { + cleanPreconfiguredOutputs, + ensurePreconfiguredOutputs, + ensurePreconfiguredPackagesAndPolicies, +} from './preconfiguration'; import { outputService } from './output'; import { generateEnrollmentAPIKey, hasEnrollementAPIKeysForPolicy } from './api_keys'; @@ -45,23 +49,27 @@ async function createSetupSideEffects( soClient: SavedObjectsClientContract, esClient: ElasticsearchClient ): Promise { - const [defaultOutput] = await Promise.all([ - outputService.ensureDefaultOutput(soClient), + const { + agentPolicies: policiesOrUndefined, + packages: packagesOrUndefined, + outputs: outputsOrUndefined, + } = appContextService.getConfig() ?? {}; + + const policies = policiesOrUndefined ?? []; + let packages = packagesOrUndefined ?? []; + + await Promise.all([ + ensurePreconfiguredOutputs(soClient, esClient, outputsOrUndefined ?? []), settingsService.settingsSetup(soClient), ]); + const defaultOutput = await outputService.ensureDefaultOutput(soClient); + await awaitIfFleetServerSetupPending(); if (appContextService.getConfig()?.agentIdVerificationEnabled) { await ensureFleetGlobalEsAssets(soClient, esClient); } - const { agentPolicies: policiesOrUndefined, packages: packagesOrUndefined } = - appContextService.getConfig() ?? {}; - - const policies = policiesOrUndefined ?? []; - - let packages = packagesOrUndefined ?? []; - // Ensure that required packages are always installed even if they're left out of the config const preconfiguredPackageNames = new Set(packages.map((pkg) => pkg.name)); @@ -90,6 +98,8 @@ async function createSetupSideEffects( defaultOutput ); + await cleanPreconfiguredOutputs(soClient, outputsOrUndefined ?? []); + await ensureDefaultEnrollmentAPIKeysExists(soClient, esClient); await ensureAgentActionPolicyChangeExists(soClient, esClient); diff --git a/x-pack/plugins/fleet/server/types/index.tsx b/x-pack/plugins/fleet/server/types/index.tsx index f686b969fd038..63e6c277ed710 100644 --- a/x-pack/plugins/fleet/server/types/index.tsx +++ b/x-pack/plugins/fleet/server/types/index.tsx @@ -27,6 +27,7 @@ export { PackagePolicySOAttributes, FullAgentPolicyInput, FullAgentPolicy, + FullAgentPolicyOutput, AgentPolicy, AgentPolicySOAttributes, NewAgentPolicy, diff --git a/x-pack/plugins/fleet/server/types/models/package_policy.ts b/x-pack/plugins/fleet/server/types/models/package_policy.ts index e69e38c187284..30321bdca3309 100644 --- a/x-pack/plugins/fleet/server/types/models/package_policy.ts +++ b/x-pack/plugins/fleet/server/types/models/package_policy.ts @@ -63,7 +63,19 @@ const PackagePolicyBaseSchema = { id: schema.maybe(schema.string()), // BWC < 7.11 enabled: schema.boolean(), keep_enabled: schema.maybe(schema.boolean()), - data_stream: schema.object({ dataset: schema.string(), type: schema.string() }), + data_stream: schema.object({ + dataset: schema.string(), + type: schema.string(), + elasticsearch: schema.maybe( + schema.object({ + privileges: schema.maybe( + schema.object({ + indices: schema.maybe(schema.arrayOf(schema.string())), + }) + ), + }) + ), + }), vars: schema.maybe(ConfigRecordSchema), config: schema.maybe( schema.recordOf( diff --git a/x-pack/plugins/fleet/server/types/models/preconfiguration.test.ts b/x-pack/plugins/fleet/server/types/models/preconfiguration.test.ts new file mode 100644 index 0000000000000..eb349e0d0f823 --- /dev/null +++ b/x-pack/plugins/fleet/server/types/models/preconfiguration.test.ts @@ -0,0 +1,81 @@ +/* + * 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 { PreconfiguredOutputsSchema, PreconfiguredAgentPoliciesSchema } from './preconfiguration'; + +describe('Test preconfiguration schema', () => { + describe('PreconfiguredOutputsSchema', () => { + it('should not allow multiple default output', () => { + expect(() => { + PreconfiguredOutputsSchema.validate([ + { + id: 'output-1', + name: 'Output 1', + type: 'elasticsearch', + is_default: true, + }, + { + id: 'output-2', + name: 'Output 2', + type: 'elasticsearch', + is_default: true, + }, + ]); + }).toThrowError('preconfigured outputs need to have only one default output.'); + }); + it('should not allow multiple output with same ids', () => { + expect(() => { + PreconfiguredOutputsSchema.validate([ + { + id: 'nonuniqueid', + name: 'Output 1', + type: 'elasticsearch', + }, + { + id: 'nonuniqueid', + name: 'Output 2', + type: 'elasticsearch', + }, + ]); + }).toThrowError('preconfigured outputs need to have unique ids.'); + }); + it('should not allow multiple output with same names', () => { + expect(() => { + PreconfiguredOutputsSchema.validate([ + { + id: 'output-1', + name: 'nonuniquename', + type: 'elasticsearch', + }, + { + id: 'output-2', + name: 'nonuniquename', + type: 'elasticsearch', + }, + ]); + }).toThrowError('preconfigured outputs need to have unique names.'); + }); + }); + + describe('PreconfiguredAgentPoliciesSchema', () => { + it('should not allow multiple outputs in one policy', () => { + expect(() => { + PreconfiguredAgentPoliciesSchema.validate([ + { + id: 'policy-1', + name: 'Policy 1', + package_policies: [], + data_output_id: 'test1', + monitoring_output_id: 'test2', + }, + ]); + }).toThrowError( + '[0]: Currently Fleet only support one output per agent policy data_output_id should be the same as monitoring_output_id.' + ); + }); + }); +}); diff --git a/x-pack/plugins/fleet/server/types/models/preconfiguration.ts b/x-pack/plugins/fleet/server/types/models/preconfiguration.ts index 4ea9f086bda68..b65fa122911dc 100644 --- a/x-pack/plugins/fleet/server/types/models/preconfiguration.ts +++ b/x-pack/plugins/fleet/server/types/models/preconfiguration.ts @@ -14,6 +14,8 @@ import { DEFAULT_FLEET_SERVER_AGENT_POLICY, DEFAULT_PACKAGES, } from '../../constants'; +import type { PreconfiguredOutput } from '../../../common'; +import { outputType } from '../../../common'; import { AgentPolicyBaseSchema } from './agent_policy'; import { NamespaceSchema } from './package_policy'; @@ -47,47 +49,94 @@ export const PreconfiguredPackagesSchema = schema.arrayOf( } ); -export const PreconfiguredAgentPoliciesSchema = schema.arrayOf( +function validatePreconfiguredOutputs(outputs: PreconfiguredOutput[]) { + const acc = { names: new Set(), ids: new Set(), is_default: false }; + + for (const output of outputs) { + if (acc.names.has(output.name)) { + return 'preconfigured outputs need to have unique names.'; + } + if (acc.ids.has(output.id)) { + return 'preconfigured outputs need to have unique ids.'; + } + if (acc.is_default && output.is_default) { + return 'preconfigured outputs need to have only one default output.'; + } + + acc.ids.add(output.id); + acc.names.add(output.name); + acc.is_default = acc.is_default || output.is_default; + } +} + +export const PreconfiguredOutputsSchema = schema.arrayOf( schema.object({ - ...AgentPolicyBaseSchema, - namespace: schema.maybe(NamespaceSchema), - id: schema.maybe(schema.oneOf([schema.string(), schema.number()])), - is_default: schema.maybe(schema.boolean()), - is_default_fleet_server: schema.maybe(schema.boolean()), - package_policies: schema.arrayOf( - schema.object({ - name: schema.string(), - package: schema.object({ - name: schema.string(), - }), - description: schema.maybe(schema.string()), - namespace: schema.maybe(NamespaceSchema), - inputs: schema.maybe( - schema.arrayOf( - schema.object({ - type: schema.string(), - enabled: schema.maybe(schema.boolean()), - keep_enabled: schema.maybe(schema.boolean()), - vars: varsSchema, - streams: schema.maybe( - schema.arrayOf( - schema.object({ - data_stream: schema.object({ - type: schema.maybe(schema.string()), - dataset: schema.string(), - }), - enabled: schema.maybe(schema.boolean()), - keep_enabled: schema.maybe(schema.boolean()), - vars: varsSchema, - }) - ) - ), - }) - ) - ), - }) - ), + id: schema.string(), + is_default: schema.boolean({ defaultValue: false }), + name: schema.string(), + type: schema.oneOf([schema.literal(outputType.Elasticsearch)]), + hosts: schema.maybe(schema.arrayOf(schema.uri({ scheme: ['http', 'https'] }))), + ca_sha256: schema.maybe(schema.string()), + config: schema.maybe(schema.object({}, { unknowns: 'allow' })), }), + { + defaultValue: [], + validate: validatePreconfiguredOutputs, + } +); + +export const PreconfiguredAgentPoliciesSchema = schema.arrayOf( + schema.object( + { + ...AgentPolicyBaseSchema, + namespace: schema.maybe(NamespaceSchema), + id: schema.maybe(schema.oneOf([schema.string(), schema.number()])), + is_default: schema.maybe(schema.boolean()), + is_default_fleet_server: schema.maybe(schema.boolean()), + data_output_id: schema.maybe(schema.string()), + monitoring_output_id: schema.maybe(schema.string()), + package_policies: schema.arrayOf( + schema.object({ + name: schema.string(), + package: schema.object({ + name: schema.string(), + }), + description: schema.maybe(schema.string()), + namespace: schema.maybe(NamespaceSchema), + inputs: schema.maybe( + schema.arrayOf( + schema.object({ + type: schema.string(), + enabled: schema.maybe(schema.boolean()), + keep_enabled: schema.maybe(schema.boolean()), + vars: varsSchema, + streams: schema.maybe( + schema.arrayOf( + schema.object({ + data_stream: schema.object({ + type: schema.maybe(schema.string()), + dataset: schema.string(), + }), + enabled: schema.maybe(schema.boolean()), + keep_enabled: schema.maybe(schema.boolean()), + vars: varsSchema, + }) + ) + ), + }) + ) + ), + }) + ), + }, + { + validate: (policy) => { + if (policy.data_output_id !== policy.monitoring_output_id) { + return 'Currently Fleet only support one output per agent policy data_output_id should be the same as monitoring_output_id.'; + } + }, + } + ), { defaultValue: [DEFAULT_AGENT_POLICY, DEFAULT_FLEET_SERVER_AGENT_POLICY], } diff --git a/x-pack/plugins/fleet/storybook/decorator.tsx b/x-pack/plugins/fleet/storybook/decorator.tsx new file mode 100644 index 0000000000000..499b01c2bfba0 --- /dev/null +++ b/x-pack/plugins/fleet/storybook/decorator.tsx @@ -0,0 +1,79 @@ +/* + * 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 { of } from 'rxjs'; +import type { DecoratorFn } from '@storybook/react'; +import { action } from '@storybook/addon-actions'; +import { createMemoryHistory } from 'history'; + +import { I18nProvider } from '@kbn/i18n/react'; + +import { ScopedHistory } from '../../../../src/core/public'; +import { IntegrationsAppContext } from '../public/applications/integrations/app'; +import type { FleetConfigType, FleetStartServices } from '../public/plugin'; + +// TODO: clintandrewhall - this is not ideal, or complete. The root context of Fleet applications +// requires full start contracts of its dependencies. As a result, we have to mock all of those contracts +// with Storybook equivalents. This is a temporary solution, and should be replaced with a more complete +// mock later, (or, ideally, Fleet starts to use a service abstraction). +// +// Expect this to grow as components that are given Stories need access to mocked services. +export const contextDecorator: DecoratorFn = (story: Function) => { + const basepath = '/'; + const memoryHistory = createMemoryHistory({ initialEntries: [basepath] }); + const history = new ScopedHistory(memoryHistory, basepath); + + const startServices = { + application: { + currentAppId$: of('home'), + navigateToUrl: (url: string) => action(`Navigate to: ${url}`), + getUrlForApp: (url: string) => url, + }, + http: { + basePath: { + prepend: () => basepath, + }, + }, + notifications: {}, + history, + uiSettings: { + get$: (key: string) => { + switch (key) { + case 'theme:darkMode': + return of(false); + default: + return of(); + } + }, + }, + i18n: { + Context: I18nProvider, + }, + } as unknown as FleetStartServices; + + const config = { + enabled: true, + agents: { + enabled: true, + elasticsearch: {}, + }, + } as unknown as FleetConfigType; + + const extensions = {}; + + const kibanaVersion = '1.2.3'; + + return ( + + {story()} + + ); +}; diff --git a/x-pack/plugins/fleet/storybook/main.ts b/x-pack/plugins/fleet/storybook/main.ts new file mode 100644 index 0000000000000..2cf4124c61783 --- /dev/null +++ b/x-pack/plugins/fleet/storybook/main.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Configuration } from 'webpack'; +import { defaultConfig, WebpackConfig } from '@kbn/storybook'; + +module.exports = { + ...defaultConfig, + addons: ['@storybook/addon-essentials'], + babel: () => ({ + presets: [require.resolve('@kbn/babel-preset/webpack_preset')], + }), + webpackFinal: (config: Configuration) => { + return WebpackConfig({ config }); + }, +}; diff --git a/x-pack/plugins/fleet/storybook/manager.ts b/x-pack/plugins/fleet/storybook/manager.ts new file mode 100644 index 0000000000000..471a735ed370f --- /dev/null +++ b/x-pack/plugins/fleet/storybook/manager.ts @@ -0,0 +1,20 @@ +/* + * 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 { addons } from '@storybook/addons'; +import { create } from '@storybook/theming'; +import { PANEL_ID } from '@storybook/addon-actions'; + +addons.setConfig({ + theme: create({ + base: 'light', + brandTitle: 'Kibana Fleet Storybook', + brandUrl: 'https://github.com/elastic/kibana/tree/master/x-pack/plugins/fleet', + }), + showPanel: true.valueOf, + selectedPanel: PANEL_ID, +}); diff --git a/x-pack/plugins/fleet/storybook/preview.tsx b/x-pack/plugins/fleet/storybook/preview.tsx new file mode 100644 index 0000000000000..a50ff2faaff56 --- /dev/null +++ b/x-pack/plugins/fleet/storybook/preview.tsx @@ -0,0 +1,28 @@ +/* + * 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 { addDecorator } from '@storybook/react'; +import { Title, Subtitle, Description, Primary, Stories } from '@storybook/addon-docs/blocks'; + +import { contextDecorator } from './decorator'; + +addDecorator(contextDecorator); + +export const parameters = { + docs: { + page: () => ( + <> + + <Subtitle /> + <Description /> + <Primary /> + <Stories /> + </> + ), + }, +}; diff --git a/x-pack/plugins/fleet/tsconfig.json b/x-pack/plugins/fleet/tsconfig.json index 5002bf2893872..a9dd66ce503a9 100644 --- a/x-pack/plugins/fleet/tsconfig.json +++ b/x-pack/plugins/fleet/tsconfig.json @@ -14,6 +14,7 @@ "server/**/*.json", "scripts/**/*", "package.json", + "storybook/**/*", "../../../typings/**/*" ], "references": [ diff --git a/x-pack/plugins/global_search/public/plugin.ts b/x-pack/plugins/global_search/public/plugin.ts index b3a890d84f036..00f320d23b606 100644 --- a/x-pack/plugins/global_search/public/plugin.ts +++ b/x-pack/plugins/global_search/public/plugin.ts @@ -25,7 +25,8 @@ export class GlobalSearchPlugin GlobalSearchPluginStart, GlobalSearchPluginSetupDeps, GlobalSearchPluginStartDeps - > { + > +{ private readonly config: GlobalSearchClientConfigType; private licenseChecker?: ILicenseChecker; private readonly searchService = new SearchService(); diff --git a/x-pack/plugins/global_search/server/plugin.ts b/x-pack/plugins/global_search/server/plugin.ts index d7c06a92f70e0..688691d8941e2 100644 --- a/x-pack/plugins/global_search/server/plugin.ts +++ b/x-pack/plugins/global_search/server/plugin.ts @@ -30,7 +30,8 @@ export class GlobalSearchPlugin GlobalSearchPluginStart, GlobalSearchPluginSetupDeps, GlobalSearchPluginStartDeps - > { + > +{ private readonly config: GlobalSearchConfigType; private readonly searchService = new SearchService(); private searchServiceStart?: SearchServiceStart; diff --git a/x-pack/plugins/global_search/server/routes/integration_tests/get_searchable_types.test.ts b/x-pack/plugins/global_search/server/routes/integration_tests/get_searchable_types.test.ts index 6f495bfbd6433..cd093c8009a92 100644 --- a/x-pack/plugins/global_search/server/routes/integration_tests/get_searchable_types.test.ts +++ b/x-pack/plugins/global_search/server/routes/integration_tests/get_searchable_types.test.ts @@ -30,9 +30,10 @@ describe('GET /internal/global_search/searchable_types', () => { 'globalSearch' >(pluginId, 'globalSearch', () => globalSearchHandlerContext); - const router = httpSetup.createRouter< - ReturnType<typeof globalSearchPluginMock.createRequestHandlerContext> - >('/'); + const router = + httpSetup.createRouter<ReturnType<typeof globalSearchPluginMock.createRequestHandlerContext>>( + '/' + ); registerInternalSearchableTypesRoute(router); diff --git a/x-pack/plugins/global_search/server/services/context.ts b/x-pack/plugins/global_search/server/services/context.ts index 454b0324ab3c1..b0669f35b99b9 100644 --- a/x-pack/plugins/global_search/server/services/context.ts +++ b/x-pack/plugins/global_search/server/services/context.ts @@ -14,20 +14,20 @@ export type GlobalSearchContextFactory = (request: KibanaRequest) => GlobalSearc /** * {@link GlobalSearchProviderContext | context} factory */ -export const getContextFactory = (coreStart: CoreStart) => ( - request: KibanaRequest -): GlobalSearchProviderContext => { - const soClient = coreStart.savedObjects.getScopedClient(request); - return { - core: { - savedObjects: { - client: soClient, - typeRegistry: coreStart.savedObjects.getTypeRegistry(), +export const getContextFactory = + (coreStart: CoreStart) => + (request: KibanaRequest): GlobalSearchProviderContext => { + const soClient = coreStart.savedObjects.getScopedClient(request); + return { + core: { + savedObjects: { + client: soClient, + typeRegistry: coreStart.savedObjects.getTypeRegistry(), + }, + uiSettings: { + client: coreStart.uiSettings.asScopedToClient(soClient), + }, + capabilities: from(coreStart.capabilities.resolveCapabilities(request)), }, - uiSettings: { - client: coreStart.uiSettings.asScopedToClient(soClient), - }, - capabilities: from(coreStart.capabilities.resolveCapabilities(request)), - }, + }; }; -}; diff --git a/x-pack/plugins/global_search_providers/public/plugin.ts b/x-pack/plugins/global_search_providers/public/plugin.ts index 5fd47947d1b75..89e8e9f57a3de 100644 --- a/x-pack/plugins/global_search_providers/public/plugin.ts +++ b/x-pack/plugins/global_search_providers/public/plugin.ts @@ -14,7 +14,8 @@ export interface GlobalSearchProvidersPluginSetupDeps { } export class GlobalSearchProvidersPlugin - implements Plugin<{}, {}, GlobalSearchProvidersPluginSetupDeps, {}> { + implements Plugin<{}, {}, GlobalSearchProvidersPluginSetupDeps, {}> +{ setup( { getStartServices }: CoreSetup<{}, {}>, { globalSearch }: GlobalSearchProvidersPluginSetupDeps diff --git a/x-pack/plugins/global_search_providers/public/providers/application.test.ts b/x-pack/plugins/global_search_providers/public/providers/application.test.ts index a686f416292df..aa54f3a0e5ff2 100644 --- a/x-pack/plugins/global_search_providers/public/providers/application.test.ts +++ b/x-pack/plugins/global_search_providers/public/providers/application.test.ts @@ -246,9 +246,9 @@ describe('applicationResultProvider', () => { // test scheduler doesnt play well with promises. need to workaround by passing // an observable instead. Behavior with promise is asserted in previous tests of the suite - const applicationPromise = (hot('a', { + const applicationPromise = hot('a', { a: application, - }) as unknown) as Promise<ApplicationStart>; + }) as unknown as Promise<ApplicationStart>; const provider = createApplicationResultProvider(applicationPromise); @@ -271,9 +271,9 @@ describe('applicationResultProvider', () => { // test scheduler doesnt play well with promises. need to workaround by passing // an observable instead. Behavior with promise is asserted in previous tests of the suite - const applicationPromise = (hot('a', { + const applicationPromise = hot('a', { a: application, - }) as unknown) as Promise<ApplicationStart>; + }) as unknown as Promise<ApplicationStart>; const provider = createApplicationResultProvider(applicationPromise); diff --git a/x-pack/plugins/global_search_providers/server/plugin.ts b/x-pack/plugins/global_search_providers/server/plugin.ts index cf63389c45a34..dae346144bce3 100644 --- a/x-pack/plugins/global_search_providers/server/plugin.ts +++ b/x-pack/plugins/global_search_providers/server/plugin.ts @@ -14,7 +14,8 @@ export interface GlobalSearchProvidersPluginSetupDeps { } export class GlobalSearchProvidersPlugin - implements Plugin<{}, {}, GlobalSearchProvidersPluginSetupDeps, {}> { + implements Plugin<{}, {}, GlobalSearchProvidersPluginSetupDeps, {}> +{ setup( { getStartServices }: CoreSetup<{}, {}>, { globalSearch }: GlobalSearchProvidersPluginSetupDeps diff --git a/x-pack/plugins/graph/public/components/graph_visualization/graph_visualization.test.tsx b/x-pack/plugins/graph/public/components/graph_visualization/graph_visualization.test.tsx index 1ae556a79edcb..8273d99f890c8 100644 --- a/x-pack/plugins/graph/public/components/graph_visualization/graph_visualization.test.tsx +++ b/x-pack/plugins/graph/public/components/graph_visualization/graph_visualization.test.tsx @@ -101,7 +101,7 @@ describe('graph_visualization', () => { width: 2.2, }, ]; - const workspace = ({ + const workspace = { nodes, edges, selectNone: () => {}, @@ -110,7 +110,7 @@ describe('graph_visualization', () => { return !node.isSelected; }), getAllIntersections: jest.fn(), - } as unknown) as jest.Mocked<Workspace>; + } as unknown as jest.Mocked<Workspace>; beforeEach(() => { jest.clearAllMocks(); @@ -120,7 +120,7 @@ describe('graph_visualization', () => { expect( shallow( <GraphVisualization - workspace={({} as unknown) as Workspace} + workspace={{} as unknown as Workspace} selectSelected={() => {}} onSetControl={() => {}} onSetMergeCandidates={() => {}} diff --git a/x-pack/plugins/graph/public/components/inspect_panel.tsx b/x-pack/plugins/graph/public/components/inspect_panel.tsx index a254d54f616e4..5e5547ea341fb 100644 --- a/x-pack/plugins/graph/public/components/inspect_panel.tsx +++ b/x-pack/plugins/graph/public/components/inspect_panel.tsx @@ -49,11 +49,10 @@ export const InspectPanel = ({ const onRequestClick = () => setSelectedTabId('request'); const onResponseClick = () => setSelectedTabId('response'); - const editorContent = useMemo(() => (selectedTabId === 'request' ? lastRequest : lastResponse), [ - lastRequest, - lastResponse, - selectedTabId, - ]); + const editorContent = useMemo( + () => (selectedTabId === 'request' ? lastRequest : lastResponse), + [lastRequest, lastResponse, selectedTabId] + ); if (showInspect) { return ( diff --git a/x-pack/plugins/graph/public/components/search_bar.test.tsx b/x-pack/plugins/graph/public/components/search_bar.test.tsx index 1b76cde1a62fb..9237acb4741f1 100644 --- a/x-pack/plugins/graph/public/components/search_bar.test.tsx +++ b/x-pack/plugins/graph/public/components/search_bar.test.tsx @@ -77,7 +77,7 @@ describe('search_bar', () => { const defaultProps = { isLoading: false, indexPatternProvider: { - get: jest.fn(() => Promise.resolve(({ fields: [] } as unknown) as IndexPattern)), + get: jest.fn(() => Promise.resolve({ fields: [] } as unknown as IndexPattern)), }, confirmWipeWorkspace: (callback: () => void) => { callback(); @@ -169,9 +169,9 @@ describe('search_bar', () => { // pick the button component out of the tree because // it's part of a popover and thus not covered by enzyme - (instance - .find(QueryStringInput) - .prop('prepend') as ReactElement).props.children.props.onClick(); + ( + instance.find(QueryStringInput).prop('prepend') as ReactElement + ).props.children.props.onClick(); expect(openSourceModal).toHaveBeenCalled(); }); diff --git a/x-pack/plugins/graph/public/components/search_bar.tsx b/x-pack/plugins/graph/public/components/search_bar.tsx index fc7e3be3d0d37..57e9831d5a656 100644 --- a/x-pack/plugins/graph/public/components/search_bar.tsx +++ b/x-pack/plugins/graph/public/components/search_bar.tsx @@ -81,9 +81,10 @@ export function SearchBarComponent(props: SearchBarStateProps & SearchBarProps) } = props; const [query, setQuery] = useState<Query>({ language: 'kuery', query: urlQuery || '' }); - useEffect(() => setQuery((prev) => ({ language: prev.language, query: urlQuery || '' })), [ - urlQuery, - ]); + useEffect( + () => setQuery((prev) => ({ language: prev.language, query: urlQuery || '' })), + [urlQuery] + ); useEffect(() => { async function fetchPattern() { diff --git a/x-pack/plugins/graph/public/components/workspace_layout/workspace_top_nav_menu.tsx b/x-pack/plugins/graph/public/components/workspace_layout/workspace_top_nav_menu.tsx index 07e21287e5c16..dc7365672ffa2 100644 --- a/x-pack/plugins/graph/public/components/workspace_layout/workspace_top_nav_menu.tsx +++ b/x-pack/plugins/graph/public/components/workspace_layout/workspace_top_nav_menu.tsx @@ -136,12 +136,12 @@ export const WorkspaceTopNavMenu = (props: WorkspaceTopNavMenuProps) => { // since settings button will be disabled only if workspace was set const workspace = props.workspace as Workspace; - const settingsObservable = (asSyncedObservable(() => ({ + const settingsObservable = asSyncedObservable(() => ({ blocklistedNodes: workspace.blocklistedNodes, unblockNode: workspace.unblockNode, unblockAll: workspace.unblockAll, canEditDrillDownUrls: props.canEditDrillDownUrls, - })) as unknown) as AsObservable<SettingsWorkspaceProps>['observable']; + })) as unknown as AsObservable<SettingsWorkspaceProps>['observable']; props.coreStart.overlays.openFlyout( toMountPoint( diff --git a/x-pack/plugins/graph/public/helpers/kql_encoder.test.ts b/x-pack/plugins/graph/public/helpers/kql_encoder.test.ts index 4cf46e18c8fa7..8ef3cd44a63b7 100644 --- a/x-pack/plugins/graph/public/helpers/kql_encoder.test.ts +++ b/x-pack/plugins/graph/public/helpers/kql_encoder.test.ts @@ -12,7 +12,7 @@ describe('kql_encoder', () => { let workspaceMock: jest.Mocked<Workspace>; beforeEach(() => { - workspaceMock = ({ + workspaceMock = { returnUnpackedGroupeds: (nodes: []) => nodes, getSelectedOrAllNodes: jest.fn(() => [ { @@ -34,7 +34,7 @@ describe('kql_encoder', () => { }, }, ]), - } as unknown) as jest.Mocked<Workspace>; + } as unknown as jest.Mocked<Workspace>; }); it('should encode query as URI component', () => { diff --git a/x-pack/plugins/graph/public/helpers/use_workspace_loader.test.tsx b/x-pack/plugins/graph/public/helpers/use_workspace_loader.test.tsx index db80289d0d32d..cd0857f82ab6b 100644 --- a/x-pack/plugins/graph/public/helpers/use_workspace_loader.test.tsx +++ b/x-pack/plugins/graph/public/helpers/use_workspace_loader.test.tsx @@ -33,13 +33,13 @@ jest.mock('react-router-dom', () => { }; }); -const mockSavedObjectsClient = ({ +const mockSavedObjectsClient = { resolve: jest.fn().mockResolvedValue({ saved_object: { id: 10, _version: '7.15.0', attributes: { wsState: '{}' } }, outcome: 'exactMatch', }), find: jest.fn().mockResolvedValue({ title: 'test' }), -} as unknown) as SavedObjectsClientCommon; +} as unknown as SavedObjectsClientCommon; async function setup(props: UseWorkspaceLoaderProps) { const returnVal = {}; @@ -66,7 +66,7 @@ describe('use_workspace_loader', () => { it('should not redirect if outcome is exactMatch', async () => { await act(async () => { - await setup((defaultProps as unknown) as UseWorkspaceLoaderProps); + await setup(defaultProps as unknown as UseWorkspaceLoaderProps); }); expect(defaultProps.spaces.ui.redirectLegacyUrl).not.toHaveBeenCalled(); expect(defaultProps.store.dispatch).toHaveBeenCalled(); @@ -85,7 +85,7 @@ describe('use_workspace_loader', () => { }, }; await act(async () => { - await setup((props as unknown) as UseWorkspaceLoaderProps); + await setup(props as unknown as UseWorkspaceLoaderProps); }); expect(props.spaces.ui.redirectLegacyUrl).toHaveBeenCalledWith( '#/workspace/aliasTargetId?query={}', diff --git a/x-pack/plugins/graph/public/plugin.ts b/x-pack/plugins/graph/public/plugin.ts index 1c44714a2c498..a1bc8a93f7f7a 100644 --- a/x-pack/plugins/graph/public/plugin.ts +++ b/x-pack/plugins/graph/public/plugin.ts @@ -49,7 +49,8 @@ export interface GraphPluginStartDependencies { } export class GraphPlugin - implements Plugin<void, void, GraphPluginSetupDependencies, GraphPluginStartDependencies> { + implements Plugin<void, void, GraphPluginSetupDependencies, GraphPluginStartDependencies> +{ private readonly appUpdater$ = new BehaviorSubject<AppUpdater>(() => ({})); constructor(private initializerContext: PluginInitializerContext<ConfigSchema>) {} diff --git a/x-pack/plugins/graph/public/services/persistence/serialize.test.ts b/x-pack/plugins/graph/public/services/persistence/serialize.test.ts index 2466582bc7b25..ec52159ae10e2 100644 --- a/x-pack/plugins/graph/public/services/persistence/serialize.test.ts +++ b/x-pack/plugins/graph/public/services/persistence/serialize.test.ts @@ -159,7 +159,7 @@ describe('serialize', () => { }); it('should serialize given workspace', () => { - const savedWorkspace = ({} as unknown) as GraphWorkspaceSavedObject; + const savedWorkspace = {} as unknown as GraphWorkspaceSavedObject; appStateToSavedWorkspace(savedWorkspace, appState, true); @@ -281,7 +281,7 @@ describe('serialize', () => { }); it('should not save data if set to false', () => { - const savedWorkspace = ({} as unknown) as GraphWorkspaceSavedObject; + const savedWorkspace = {} as unknown as GraphWorkspaceSavedObject; appStateToSavedWorkspace(savedWorkspace, appState, false); diff --git a/x-pack/plugins/graph/public/state_management/helpers.ts b/x-pack/plugins/graph/public/state_management/helpers.ts index 098c1e029be8f..db853afc9f147 100644 --- a/x-pack/plugins/graph/public/state_management/helpers.ts +++ b/x-pack/plugins/graph/public/state_management/helpers.ts @@ -25,5 +25,7 @@ export type InferActionType<X> = X extends ActionCreator<infer T> ? T : never; * * @param actionCreators The action creators to create a unified matcher for */ -export const matchesOne = (...actionCreators: Array<ActionCreator<any>>) => (action: AnyAction) => - actionCreators.some((actionCreator) => actionCreator.match(action)); +export const matchesOne = + (...actionCreators: Array<ActionCreator<any>>) => + (action: AnyAction) => + actionCreators.some((actionCreator) => actionCreator.match(action)); diff --git a/x-pack/plugins/graph/public/state_management/mocks.ts b/x-pack/plugins/graph/public/state_management/mocks.ts index 189875d04b015..6f7b5dba2e4a1 100644 --- a/x-pack/plugins/graph/public/state_management/mocks.ts +++ b/x-pack/plugins/graph/public/state_management/mocks.ts @@ -40,48 +40,48 @@ export function createMockGraphStore({ mockedDepsOverwrites?: Partial<jest.Mocked<GraphStoreDependencies>>; initialStateOverwrites?: Partial<GraphState>; }): MockedGraphEnvironment { - const workspaceMock = ({ + const workspaceMock = { runLayout: jest.fn(), nodes: [], edges: [], options: {}, blocklistedNodes: [], - } as unknown) as Workspace; + } as unknown as Workspace; const mockedDeps: jest.Mocked<GraphStoreDependencies> = { basePath: '', addBasePath: jest.fn((url: string) => url), changeUrl: jest.fn(), - chrome: ({ + chrome: { setBreadcrumbs: jest.fn(), - } as unknown) as ChromeStart, + } as unknown as ChromeStart, createWorkspace: jest.fn(), getWorkspace: jest.fn(() => workspaceMock), indexPatternProvider: { get: jest.fn(() => - Promise.resolve(({ id: '123', title: 'test-pattern' } as unknown) as IndexPattern) + Promise.resolve({ id: '123', title: 'test-pattern' } as unknown as IndexPattern) ), }, I18nContext: jest .fn() .mockImplementation(({ children }: { children: React.ReactNode }) => children), - notifications: ({ + notifications: { toasts: { addDanger: jest.fn(), addSuccess: jest.fn(), }, - } as unknown) as NotificationsStart, + } as unknown as NotificationsStart, http: {} as HttpStart, notifyReact: jest.fn(), savePolicy: 'configAndData', showSaveModal: jest.fn(), - overlays: ({ + overlays: { openModal: jest.fn(), - } as unknown) as OverlayStart, - savedObjectsClient: ({ + } as unknown as OverlayStart, + savedObjectsClient: { find: jest.fn(), get: jest.fn(), - } as unknown) as SavedObjectsClientContract, + } as unknown as SavedObjectsClientContract, handleSearchQueryError: jest.fn(), ...mockedDepsOverwrites, }; diff --git a/x-pack/plugins/graph/public/state_management/url_templates.ts b/x-pack/plugins/graph/public/state_management/url_templates.ts index 01b1a9296b0b6..736270e53a797 100644 --- a/x-pack/plugins/graph/public/state_management/url_templates.ts +++ b/x-pack/plugins/graph/public/state_management/url_templates.ts @@ -23,9 +23,8 @@ import { matchesOne } from './helpers'; const actionCreator = actionCreatorFactory('x-pack/graph/urlTemplates'); export const loadTemplates = actionCreator<UrlTemplate[]>('LOAD_TEMPLATES'); -export const saveTemplate = actionCreator<{ index: number; template: UrlTemplate }>( - 'SAVE_TEMPLATE' -); +export const saveTemplate = + actionCreator<{ index: number; template: UrlTemplate }>('SAVE_TEMPLATE'); export const removeTemplate = actionCreator<UrlTemplate>('REMOVE_TEMPLATE'); export type UrlTemplatesState = UrlTemplate[]; diff --git a/x-pack/plugins/graph/server/index.ts b/x-pack/plugins/graph/server/index.ts index 10ddca631a898..528e122da9a4d 100644 --- a/x-pack/plugins/graph/server/index.ts +++ b/x-pack/plugins/graph/server/index.ts @@ -18,4 +18,5 @@ export const config: PluginConfigDescriptor<ConfigSchema> = { savePolicy: true, }, schema: configSchema, + deprecations: ({ deprecate }) => [deprecate('enabled', '8.0.0')], }; diff --git a/x-pack/plugins/graph/server/sample_data/ecommerce.ts b/x-pack/plugins/graph/server/sample_data/ecommerce.ts index e8383ea542dbd..36e125864600d 100644 --- a/x-pack/plugins/graph/server/sample_data/ecommerce.ts +++ b/x-pack/plugins/graph/server/sample_data/ecommerce.ts @@ -335,8 +335,7 @@ const wsState: any = { ], urlTemplates: [ { - url: - '/app/discover#/?_a=(columns%3A!(_source)%2Cindex%3Aff959d40-b880-11e8-a6d9-e546fe2bba5f%2Cinterval%3Aauto%2Cquery%3A(language%3Akuery%2Cquery%3A{{gquery}})%2Csort%3A!(_score%2Cdesc))', + url: '/app/discover#/?_a=(columns%3A!(_source)%2Cindex%3Aff959d40-b880-11e8-a6d9-e546fe2bba5f%2Cinterval%3Aauto%2Cquery%3A(language%3Akuery%2Cquery%3A{{gquery}})%2Csort%3A!(_score%2Cdesc))', description: 'Raw documents', isDefault: true, encoderID: 'kql-loose', diff --git a/x-pack/plugins/graph/server/sample_data/flights.ts b/x-pack/plugins/graph/server/sample_data/flights.ts index d62b4e77e3251..61beacc3552f7 100644 --- a/x-pack/plugins/graph/server/sample_data/flights.ts +++ b/x-pack/plugins/graph/server/sample_data/flights.ts @@ -1589,8 +1589,7 @@ const wsState: any = { ], urlTemplates: [ { - url: - '/app/discover#/?_a=(columns%3A!(_source)%2Cindex%3Ad3d7af60-4c81-11e8-b3d7-01146121b73d%2Cinterval%3Aauto%2Cquery%3A(language%3Akuery%2Cquery%3A{{gquery}})%2Csort%3A!(_score%2Cdesc))', + url: '/app/discover#/?_a=(columns%3A!(_source)%2Cindex%3Ad3d7af60-4c81-11e8-b3d7-01146121b73d%2Cinterval%3Aauto%2Cquery%3A(language%3Akuery%2Cquery%3A{{gquery}})%2Csort%3A!(_score%2Cdesc))', description: 'Raw documents', isDefault: true, encoderID: 'kql-loose', diff --git a/x-pack/plugins/graph/server/sample_data/logs.ts b/x-pack/plugins/graph/server/sample_data/logs.ts index bd9b3a5c35097..d3d9c18a309e1 100644 --- a/x-pack/plugins/graph/server/sample_data/logs.ts +++ b/x-pack/plugins/graph/server/sample_data/logs.ts @@ -176,8 +176,7 @@ const wsState: any = { 'Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.50 Safari/534.24', color: '#CE0060', field: 'agent.keyword', - term: - 'Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.50 Safari/534.24', + term: 'Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.50 Safari/534.24', parent: null, size: 15, }, @@ -407,8 +406,7 @@ const wsState: any = { ], urlTemplates: [ { - url: - '/app/discover#/?_a=(columns%3A!(_source)%2Cindex%3A%2790943e30-9a47-11e8-b64d-95841ca0b247%27%2Cinterval%3Aauto%2Cquery%3A(language%3Akuery%2Cquery%3A{{gquery}})%2Csort%3A!(_score%2Cdesc))', + url: '/app/discover#/?_a=(columns%3A!(_source)%2Cindex%3A%2790943e30-9a47-11e8-b64d-95841ca0b247%27%2Cinterval%3Aauto%2Cquery%3A(language%3Akuery%2Cquery%3A{{gquery}})%2Csort%3A!(_score%2Cdesc))', description: 'Raw documents', isDefault: true, encoderID: 'kql-loose', diff --git a/x-pack/plugins/grokdebugger/server/routes/api/grokdebugger/register_grok_simulate_route.ts b/x-pack/plugins/grokdebugger/server/routes/api/grokdebugger/register_grok_simulate_route.ts index 7483e266a1ac9..1f2927436d4ae 100644 --- a/x-pack/plugins/grokdebugger/server/routes/api/grokdebugger/register_grok_simulate_route.ts +++ b/x-pack/plugins/grokdebugger/server/routes/api/grokdebugger/register_grok_simulate_route.ts @@ -35,9 +35,10 @@ export function registerGrokSimulateRoute(framework: KibanaFramework) { async (requestContext, request, response) => { try { const grokdebuggerRequest = GrokdebuggerRequest.fromDownstreamJSON(request.body); - const simulateResponseFromES = await requestContext.core.elasticsearch.client.asCurrentUser.ingest.simulate( - { body: grokdebuggerRequest.upstreamJSON } - ); + const simulateResponseFromES = + await requestContext.core.elasticsearch.client.asCurrentUser.ingest.simulate({ + body: grokdebuggerRequest.upstreamJSON, + }); const grokdebuggerResponse = GrokdebuggerResponse.fromUpstreamJSON( simulateResponseFromES.body ); diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/constants.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/constants.ts index 90fff3a187355..f57f351ae0831 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/constants.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/constants.ts @@ -181,7 +181,7 @@ export const POLICY_WITH_NODE_ROLE_ALLOCATION: PolicyFromES = { name: POLICY_NAME, }; -export const POLICY_WITH_KNOWN_AND_UNKNOWN_FIELDS = ({ +export const POLICY_WITH_KNOWN_AND_UNKNOWN_FIELDS = { version: 1, modified_date: Date.now().toString(), policy: { @@ -219,7 +219,7 @@ export const POLICY_WITH_KNOWN_AND_UNKNOWN_FIELDS = ({ name: POLICY_NAME, }, name: POLICY_NAME, -} as any) as PolicyFromES; +} as any as PolicyFromES; export const getGeneratedPolicies = (): PolicyFromES[] => { const policy = { diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/errors_actions.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/errors_actions.ts index 7acc6a3e2f26b..34d07d4a36ff9 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/errors_actions.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/errors_actions.ts @@ -17,17 +17,15 @@ const createWaitForValidationAction = (testBed: TestBed) => () => { component.update(); }; -const createExpectMessagesAction = (testBed: TestBed) => ( - expectedMessages: string[], - phase?: Phase -) => { - const { form } = testBed; - if (phase) { - expect(form.getErrorsMessages(`${phase}-phase`)).toEqual(expectedMessages); - } else { - expect(form.getErrorsMessages()).toEqual(expectedMessages); - } -}; +const createExpectMessagesAction = + (testBed: TestBed) => (expectedMessages: string[], phase?: Phase) => { + const { form } = testBed; + if (phase) { + expect(form.getErrorsMessages(`${phase}-phase`)).toEqual(expectedMessages); + } else { + expect(form.getErrorsMessages()).toEqual(expectedMessages); + } + }; export const createErrorsActions = (testBed: TestBed) => { const { exists } = testBed; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/forcemerge_actions.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/forcemerge_actions.ts index 400f3d2070e6a..f2a27aba9ec56 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/forcemerge_actions.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/forcemerge_actions.ts @@ -11,15 +11,14 @@ import { Phase } from '../../../../common/types'; import { createFormToggleAction } from './form_toggle_action'; import { createFormSetValueAction } from './form_set_value_action'; -const createFormCheckboxAction = (testBed: TestBed, dataTestSubject: string) => async ( - checked: boolean -) => { - const { form, component } = testBed; - await act(async () => { - form.selectCheckBox(dataTestSubject, checked); - }); - component.update(); -}; +const createFormCheckboxAction = + (testBed: TestBed, dataTestSubject: string) => async (checked: boolean) => { + const { form, component } = testBed; + await act(async () => { + form.selectCheckBox(dataTestSubject, checked); + }); + component.update(); + }; export const createForceMergeActions = (testBed: TestBed, phase: Phase) => { const { exists } = testBed; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/form_toggle_and_set_value_action.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/form_toggle_and_set_value_action.ts index 643e0f23a9dea..b3510466b4b90 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/form_toggle_and_set_value_action.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/form_toggle_and_set_value_action.ts @@ -9,14 +9,11 @@ import { TestBed } from '@kbn/test/jest'; import { createFormToggleAction } from './form_toggle_action'; import { createFormSetValueAction } from './form_set_value_action'; -export const createFormToggleAndSetValueAction = ( - testBed: TestBed, - toggleSelector: string, - inputSelector: string -) => async (value: string) => { - const { exists } = testBed; - if (!exists(inputSelector)) { - await createFormToggleAction(testBed, toggleSelector)(); - } - await createFormSetValueAction(testBed, inputSelector)(value); -}; +export const createFormToggleAndSetValueAction = + (testBed: TestBed, toggleSelector: string, inputSelector: string) => async (value: string) => { + const { exists } = testBed; + if (!exists(inputSelector)) { + await createFormToggleAction(testBed, toggleSelector)(); + } + await createFormSetValueAction(testBed, inputSelector)(value); + }; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/rollover_actions.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/rollover_actions.ts index 798b74e40055f..93a1b959ec969 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/rollover_actions.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/rollover_actions.ts @@ -10,29 +10,27 @@ import { TestBed } from '@kbn/test/jest'; import { createFormToggleAction } from './form_toggle_action'; import { createFormSetValueAction } from './form_set_value_action'; -const createSetPrimaryShardSizeAction = (testBed: TestBed) => async ( - value: string, - units?: string -) => { - const { find, component } = testBed; +const createSetPrimaryShardSizeAction = + (testBed: TestBed) => async (value: string, units?: string) => { + const { find, component } = testBed; - await act(async () => { - find('hot-selectedMaxPrimaryShardSize').simulate('change', { target: { value } }); - }); - component.update(); - - if (units) { - act(() => { - find('hot-selectedMaxPrimaryShardSize.show-filters-button').simulate('click'); + await act(async () => { + find('hot-selectedMaxPrimaryShardSize').simulate('change', { target: { value } }); }); component.update(); - act(() => { - find(`hot-selectedMaxPrimaryShardSize.filter-option-${units}`).simulate('click'); - }); - component.update(); - } -}; + if (units) { + act(() => { + find('hot-selectedMaxPrimaryShardSize.show-filters-button').simulate('click'); + }); + component.update(); + + act(() => { + find(`hot-selectedMaxPrimaryShardSize.filter-option-${units}`).simulate('click'); + }); + component.update(); + } + }; const createSetMaxAgeAction = (testBed: TestBed) => async (value: string, units?: string) => { const { find, component } = testBed; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/policy_table.test.tsx b/x-pack/plugins/index_lifecycle_management/__jest__/policy_table.test.tsx index 820f8393d933d..cf25b1c335ae9 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/policy_table.test.tsx +++ b/x-pack/plugins/index_lifecycle_management/__jest__/policy_table.test.tsx @@ -137,7 +137,7 @@ describe('policy table', () => { test('filters based on content of search input', () => { const rendered = mountWithIntl(component); const searchInput = rendered.find('.euiFieldSearch').first(); - ((searchInput.instance() as unknown) as HTMLInputElement).value = 'testy0'; + (searchInput.instance() as unknown as HTMLInputElement).value = 'testy0'; searchInput.simulate('keyup', { key: 'Enter', keyCode: 13, which: 13 }); rendered.update(); snapshot(getPolicyNames(rendered)); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/data_tier_allocation.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/data_tier_allocation.tsx index 1eeb32d84f677..6ca0324d8972d 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/data_tier_allocation.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/data_tier_allocation.tsx @@ -161,13 +161,8 @@ const getSelectOptions = (phase: PhaseWithAllocation, disableDataTierOption: boo ].filter(Boolean) as SelectOptions[]; export const DataTierAllocation: FunctionComponent<SharedProps> = (props) => { - const { - phase, - hasNodeAttributes, - isCloudEnabled, - isUsingDeprecatedDataRoleConfig, - isLoading, - } = props; + const { phase, hasNodeAttributes, isCloudEnabled, isUsingDeprecatedDataRoleConfig, isLoading } = + props; /** * On Cloud we want to disable the data tier allocation option when we detect that we are not diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/min_age_field/min_age_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/min_age_field/min_age_field.tsx index ad92ef2d44479..2c42d76415dbc 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/min_age_field/min_age_field.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/min_age_field/min_age_field.tsx @@ -86,9 +86,8 @@ export const MinAgeField: FunctionComponent<Props> = ({ phase }): React.ReactEle const { isUsingRollover } = useConfiguration(); const globalFields = useGlobalFields(); - const { setValue: setMillisecondValue } = globalFields[ - `${phase}MinAgeMilliSeconds` as 'coldMinAgeMilliSeconds' - ]; + const { setValue: setMillisecondValue } = + globalFields[`${phase}MinAgeMilliSeconds` as 'coldMinAgeMilliSeconds']; const [formData] = useFormData({ watch: [minAgeValuePath, minAgeUnitPath] }); const minAgeValue = get(formData, minAgeValuePath); const minAgeUnit = get(formData, minAgeUnitPath); @@ -153,9 +152,8 @@ export const MinAgeField: FunctionComponent<Props> = ({ phase }): React.ReactEle <EuiFlexItem grow={true} style={{ minWidth: 165 }}> <UseField path={minAgeUnitPath}> {(unitField) => { - const { isInvalid: isUnitFieldInvalid } = getFieldValidityAndErrorMessage( - unitField - ); + const { isInvalid: isUnitFieldInvalid } = + getFieldValidityAndErrorMessage(unitField); const icon = ( <> {/* This element is rendered for testing purposes only */} @@ -167,11 +165,10 @@ export const MinAgeField: FunctionComponent<Props> = ({ phase }): React.ReactEle /> </> ); - const selectAppendValue: Array< - string | React.ReactElement - > = isUsingRollover - ? [i18nTexts.minAgeUnitFieldSuffix, icon] - : [i18nTexts.minAgeUnitFieldSuffix]; + const selectAppendValue: Array<string | React.ReactElement> = + isUsingRollover + ? [i18nTexts.minAgeUnitFieldSuffix, icon] + : [i18nTexts.minAgeUnitFieldSuffix]; return ( <EuiSelect compressed diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/components/enhanced_use_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/components/enhanced_use_field.tsx index 34999368ffab2..0e760af8f1c1e 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/components/enhanced_use_field.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/components/enhanced_use_field.tsx @@ -17,8 +17,10 @@ import { UseFieldProps, FormData } from '../../../../../shared_imports'; import { useFormErrorsContext } from '../form_errors_context'; -const isXPhaseField = (phase: keyof Phases) => (fieldPath: string): boolean => - fieldPath.startsWith(`phases.${phase}`) || fieldPath.startsWith(`_meta.${phase}`); +const isXPhaseField = + (phase: keyof Phases) => + (fieldPath: string): boolean => + fieldPath.startsWith(`phases.${phase}`) || fieldPath.startsWith(`_meta.${phase}`); const isHotPhaseField = isXPhaseField('hot'); const isWarmPhaseField = isXPhaseField('warm'); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/deserializer.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/deserializer.ts index ddb21dba90157..dc3714bdaf8da 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/deserializer.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/deserializer.ts @@ -14,133 +14,133 @@ import { getDefaultRepository } from '../lib'; import { FormInternal } from '../types'; import { CLOUD_DEFAULT_REPO } from '../constants'; -export const createDeserializer = (isCloudEnabled: boolean) => ( - policy: SerializedPolicy -): FormInternal => { - const { - phases: { hot, warm, cold, frozen, delete: deletePhase }, - } = policy; +export const createDeserializer = + (isCloudEnabled: boolean) => + (policy: SerializedPolicy): FormInternal => { + const { + phases: { hot, warm, cold, frozen, delete: deletePhase }, + } = policy; - let defaultRepository = getDefaultRepository([ - hot?.actions.searchable_snapshot, - cold?.actions.searchable_snapshot, - frozen?.actions.searchable_snapshot, - ]); + let defaultRepository = getDefaultRepository([ + hot?.actions.searchable_snapshot, + cold?.actions.searchable_snapshot, + frozen?.actions.searchable_snapshot, + ]); - if (!defaultRepository && isCloudEnabled) { - defaultRepository = CLOUD_DEFAULT_REPO; - } + if (!defaultRepository && isCloudEnabled) { + defaultRepository = CLOUD_DEFAULT_REPO; + } - const _meta: FormInternal['_meta'] = { - hot: { - isUsingDefaultRollover: isUsingDefaultRollover(policy), - customRollover: { - enabled: Boolean(hot?.actions?.rollover), + const _meta: FormInternal['_meta'] = { + hot: { + isUsingDefaultRollover: isUsingDefaultRollover(policy), + customRollover: { + enabled: Boolean(hot?.actions?.rollover), + }, + bestCompression: hot?.actions?.forcemerge?.index_codec === 'best_compression', + readonlyEnabled: Boolean(hot?.actions?.readonly), }, - bestCompression: hot?.actions?.forcemerge?.index_codec === 'best_compression', - readonlyEnabled: Boolean(hot?.actions?.readonly), - }, - warm: { - enabled: Boolean(warm), - warmPhaseOnRollover: warm === undefined ? true : Boolean(warm.min_age === '0ms'), - bestCompression: warm?.actions?.forcemerge?.index_codec === 'best_compression', - dataTierAllocationType: determineDataTierAllocationType(warm?.actions), - readonlyEnabled: Boolean(warm?.actions?.readonly), - minAgeToMilliSeconds: -1, - }, - cold: { - enabled: Boolean(cold), - dataTierAllocationType: determineDataTierAllocationType(cold?.actions), - freezeEnabled: Boolean(cold?.actions?.freeze), - readonlyEnabled: Boolean(cold?.actions?.readonly), - minAgeToMilliSeconds: -1, - }, - frozen: { - enabled: Boolean(frozen), - dataTierAllocationType: determineDataTierAllocationType(frozen?.actions), - freezeEnabled: Boolean(frozen?.actions?.freeze), - minAgeToMilliSeconds: -1, - }, - delete: { - enabled: Boolean(deletePhase), - minAgeToMilliSeconds: -1, - }, - searchableSnapshot: { - repository: defaultRepository, - }, - }; + warm: { + enabled: Boolean(warm), + warmPhaseOnRollover: warm === undefined ? true : Boolean(warm.min_age === '0ms'), + bestCompression: warm?.actions?.forcemerge?.index_codec === 'best_compression', + dataTierAllocationType: determineDataTierAllocationType(warm?.actions), + readonlyEnabled: Boolean(warm?.actions?.readonly), + minAgeToMilliSeconds: -1, + }, + cold: { + enabled: Boolean(cold), + dataTierAllocationType: determineDataTierAllocationType(cold?.actions), + freezeEnabled: Boolean(cold?.actions?.freeze), + readonlyEnabled: Boolean(cold?.actions?.readonly), + minAgeToMilliSeconds: -1, + }, + frozen: { + enabled: Boolean(frozen), + dataTierAllocationType: determineDataTierAllocationType(frozen?.actions), + freezeEnabled: Boolean(frozen?.actions?.freeze), + minAgeToMilliSeconds: -1, + }, + delete: { + enabled: Boolean(deletePhase), + minAgeToMilliSeconds: -1, + }, + searchableSnapshot: { + repository: defaultRepository, + }, + }; - return produce<FormInternal>( - { - ...policy, - _meta, - }, - (draft: FormInternal) => { - if (draft.phases.hot?.actions?.rollover) { - if (draft.phases.hot.actions.rollover.max_size) { - const maxSize = splitSizeAndUnits(draft.phases.hot.actions.rollover.max_size); - draft.phases.hot.actions.rollover.max_size = maxSize.size; - draft._meta.hot.customRollover.maxStorageSizeUnit = maxSize.units; - } + return produce<FormInternal>( + { + ...policy, + _meta, + }, + (draft: FormInternal) => { + if (draft.phases.hot?.actions?.rollover) { + if (draft.phases.hot.actions.rollover.max_size) { + const maxSize = splitSizeAndUnits(draft.phases.hot.actions.rollover.max_size); + draft.phases.hot.actions.rollover.max_size = maxSize.size; + draft._meta.hot.customRollover.maxStorageSizeUnit = maxSize.units; + } - if (draft.phases.hot.actions.rollover.max_primary_shard_size) { - const maxPrimaryShardSize = splitSizeAndUnits( - draft.phases.hot.actions.rollover.max_primary_shard_size - ); - draft.phases.hot.actions.rollover.max_primary_shard_size = maxPrimaryShardSize.size; - draft._meta.hot.customRollover.maxPrimaryShardSizeUnit = maxPrimaryShardSize.units; - } + if (draft.phases.hot.actions.rollover.max_primary_shard_size) { + const maxPrimaryShardSize = splitSizeAndUnits( + draft.phases.hot.actions.rollover.max_primary_shard_size + ); + draft.phases.hot.actions.rollover.max_primary_shard_size = maxPrimaryShardSize.size; + draft._meta.hot.customRollover.maxPrimaryShardSizeUnit = maxPrimaryShardSize.units; + } - if (draft.phases.hot.actions.rollover.max_age) { - const maxAge = splitSizeAndUnits(draft.phases.hot.actions.rollover.max_age); - draft.phases.hot.actions.rollover.max_age = maxAge.size; - draft._meta.hot.customRollover.maxAgeUnit = maxAge.units; + if (draft.phases.hot.actions.rollover.max_age) { + const maxAge = splitSizeAndUnits(draft.phases.hot.actions.rollover.max_age); + draft.phases.hot.actions.rollover.max_age = maxAge.size; + draft._meta.hot.customRollover.maxAgeUnit = maxAge.units; + } } - } - if (draft.phases.warm) { - if (draft.phases.warm.actions?.allocate?.require) { - Object.entries(draft.phases.warm.actions.allocate.require).forEach((entry) => { - draft._meta.warm.allocationNodeAttribute = entry.join(':'); - }); - } + if (draft.phases.warm) { + if (draft.phases.warm.actions?.allocate?.require) { + Object.entries(draft.phases.warm.actions.allocate.require).forEach((entry) => { + draft._meta.warm.allocationNodeAttribute = entry.join(':'); + }); + } - if (draft.phases.warm.min_age) { - const minAge = splitSizeAndUnits(draft.phases.warm.min_age); - draft.phases.warm.min_age = minAge.size; - draft._meta.warm.minAgeUnit = minAge.units; + if (draft.phases.warm.min_age) { + const minAge = splitSizeAndUnits(draft.phases.warm.min_age); + draft.phases.warm.min_age = minAge.size; + draft._meta.warm.minAgeUnit = minAge.units; + } } - } - if (draft.phases.cold) { - if (draft.phases.cold.actions?.allocate?.require) { - Object.entries(draft.phases.cold.actions.allocate.require).forEach((entry) => { - draft._meta.cold.allocationNodeAttribute = entry.join(':'); - }); - } + if (draft.phases.cold) { + if (draft.phases.cold.actions?.allocate?.require) { + Object.entries(draft.phases.cold.actions.allocate.require).forEach((entry) => { + draft._meta.cold.allocationNodeAttribute = entry.join(':'); + }); + } - if (draft.phases.cold.min_age) { - const minAge = splitSizeAndUnits(draft.phases.cold.min_age); - draft.phases.cold.min_age = minAge.size; - draft._meta.cold.minAgeUnit = minAge.units; + if (draft.phases.cold.min_age) { + const minAge = splitSizeAndUnits(draft.phases.cold.min_age); + draft.phases.cold.min_age = minAge.size; + draft._meta.cold.minAgeUnit = minAge.units; + } } - } - if (draft.phases.frozen) { - if (draft.phases.frozen.min_age) { - const minAge = splitSizeAndUnits(draft.phases.frozen.min_age); - draft.phases.frozen.min_age = minAge.size; - draft._meta.frozen.minAgeUnit = minAge.units; + if (draft.phases.frozen) { + if (draft.phases.frozen.min_age) { + const minAge = splitSizeAndUnits(draft.phases.frozen.min_age); + draft.phases.frozen.min_age = minAge.size; + draft._meta.frozen.minAgeUnit = minAge.units; + } } - } - if (draft.phases.delete) { - if (draft.phases.delete.min_age) { - const minAge = splitSizeAndUnits(draft.phases.delete.min_age); - draft.phases.delete.min_age = minAge.size; - draft._meta.delete.minAgeUnit = minAge.units; + if (draft.phases.delete) { + if (draft.phases.delete.min_age) { + const minAge = splitSizeAndUnits(draft.phases.delete.min_age); + draft.phases.delete.min_age = minAge.size; + draft._meta.delete.minAgeUnit = minAge.units; + } } } - } - ); -}; + ); + }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/global_fields_context.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/global_fields_context.tsx index 94b804c1ce532..5834fe5b9ea77 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/global_fields_context.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/global_fields_context.tsx @@ -26,29 +26,27 @@ type GlobalFields = { const GlobalFieldsContext = createContext<GlobalFields | null>(null); -export const globalFields: Record< - keyof GlobalFields, - { path: string; config?: FieldConfig<any> } -> = { - deleteEnabled: { - path: '_meta.delete.enabled', - }, - searchableSnapshotRepo: { - path: '_meta.searchableSnapshot.repository', - }, - warmMinAgeMilliSeconds: { - path: '_meta.warm.minAgeToMilliSeconds', - }, - coldMinAgeMilliSeconds: { - path: '_meta.cold.minAgeToMilliSeconds', - }, - frozenMinAgeMilliSeconds: { - path: '_meta.frozen.minAgeToMilliSeconds', - }, - deleteMinAgeMilliSeconds: { - path: '_meta.delete.minAgeToMilliSeconds', - }, -}; +export const globalFields: Record<keyof GlobalFields, { path: string; config?: FieldConfig<any> }> = + { + deleteEnabled: { + path: '_meta.delete.enabled', + }, + searchableSnapshotRepo: { + path: '_meta.searchableSnapshot.repository', + }, + warmMinAgeMilliSeconds: { + path: '_meta.warm.minAgeToMilliSeconds', + }, + coldMinAgeMilliSeconds: { + path: '_meta.cold.minAgeToMilliSeconds', + }, + frozenMinAgeMilliSeconds: { + path: '_meta.frozen.minAgeToMilliSeconds', + }, + deleteMinAgeMilliSeconds: { + path: '_meta.delete.minAgeToMilliSeconds', + }, + }; export const GlobalFieldsProvider: FunctionComponent = ({ children }) => { return ( diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/serializer/serializer.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/serializer/serializer.ts index 2a7d21cce03bc..cf81468dd2b48 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/serializer/serializer.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/serializer/serializer.ts @@ -16,312 +16,312 @@ import { FormInternal } from '../../types'; import { serializeMigrateAndAllocateActions } from './serialize_migrate_and_allocate_actions'; -export const createSerializer = (originalPolicy?: SerializedPolicy) => ( - data: FormInternal -): SerializedPolicy => { - const { _meta, ...updatedPolicy } = data; - - updatedPolicy.phases = { hot: { actions: {} }, ...updatedPolicy.phases }; - - return produce<SerializedPolicy>(originalPolicy ?? defaultPolicy, (draft) => { - // Copy over all updated fields - merge(draft, updatedPolicy); - - /** - * Important shared values for serialization - */ - const isUsingRollover = Boolean( - _meta.hot?.isUsingDefaultRollover || _meta.hot?.customRollover.enabled - ); - - // Next copy over all meta fields and delete any fields that have been removed - // by fields exposed in the form. It is very important that we do not delete - // data that the form does not control! E.g., unfollow action in hot phase. - - /** - * HOT PHASE SERIALIZATION - */ - if (draft.phases.hot) { - draft.phases.hot.min_age = draft.phases.hot.min_age ?? '0ms'; - } - - if (draft.phases.hot?.actions) { - const hotPhaseActions = draft.phases.hot.actions; +export const createSerializer = + (originalPolicy?: SerializedPolicy) => + (data: FormInternal): SerializedPolicy => { + const { _meta, ...updatedPolicy } = data; + + updatedPolicy.phases = { hot: { actions: {} }, ...updatedPolicy.phases }; + + return produce<SerializedPolicy>(originalPolicy ?? defaultPolicy, (draft) => { + // Copy over all updated fields + merge(draft, updatedPolicy); /** - * HOT PHASE ROLLOVER + * Important shared values for serialization */ - if (isUsingRollover) { - if (_meta.hot?.isUsingDefaultRollover) { - hotPhaseActions.rollover = cloneDeep(defaultRolloverAction); - } else { - // Rollover may not exist if editing an existing policy with initially no rollover configured - if (!hotPhaseActions.rollover) { - hotPhaseActions.rollover = {}; - } + const isUsingRollover = Boolean( + _meta.hot?.isUsingDefaultRollover || _meta.hot?.customRollover.enabled + ); - // We are using user-defined, custom rollover settings. - if (updatedPolicy.phases.hot!.actions.rollover?.max_age) { - hotPhaseActions.rollover.max_age = `${hotPhaseActions.rollover.max_age}${_meta.hot?.customRollover.maxAgeUnit}`; + // Next copy over all meta fields and delete any fields that have been removed + // by fields exposed in the form. It is very important that we do not delete + // data that the form does not control! E.g., unfollow action in hot phase. + + /** + * HOT PHASE SERIALIZATION + */ + if (draft.phases.hot) { + draft.phases.hot.min_age = draft.phases.hot.min_age ?? '0ms'; + } + + if (draft.phases.hot?.actions) { + const hotPhaseActions = draft.phases.hot.actions; + + /** + * HOT PHASE ROLLOVER + */ + if (isUsingRollover) { + if (_meta.hot?.isUsingDefaultRollover) { + hotPhaseActions.rollover = cloneDeep(defaultRolloverAction); } else { - delete hotPhaseActions.rollover.max_age; + // Rollover may not exist if editing an existing policy with initially no rollover configured + if (!hotPhaseActions.rollover) { + hotPhaseActions.rollover = {}; + } + + // We are using user-defined, custom rollover settings. + if (updatedPolicy.phases.hot!.actions.rollover?.max_age) { + hotPhaseActions.rollover.max_age = `${hotPhaseActions.rollover.max_age}${_meta.hot?.customRollover.maxAgeUnit}`; + } else { + delete hotPhaseActions.rollover.max_age; + } + + if (typeof updatedPolicy.phases.hot!.actions.rollover?.max_docs !== 'number') { + delete hotPhaseActions.rollover.max_docs; + } + + if (updatedPolicy.phases.hot!.actions.rollover?.max_primary_shard_size) { + hotPhaseActions.rollover.max_primary_shard_size = `${hotPhaseActions.rollover.max_primary_shard_size}${_meta.hot?.customRollover.maxPrimaryShardSizeUnit}`; + } else { + delete hotPhaseActions.rollover.max_primary_shard_size; + } + + if (updatedPolicy.phases.hot!.actions.rollover?.max_size) { + hotPhaseActions.rollover.max_size = `${hotPhaseActions.rollover.max_size}${_meta.hot?.customRollover.maxStorageSizeUnit}`; + } else { + delete hotPhaseActions.rollover.max_size; + } } - if (typeof updatedPolicy.phases.hot!.actions.rollover?.max_docs !== 'number') { - delete hotPhaseActions.rollover.max_docs; + /** + * HOT PHASE FORCEMERGE + */ + if (!updatedPolicy.phases.hot!.actions?.forcemerge) { + delete hotPhaseActions.forcemerge; + } else if (_meta.hot?.bestCompression) { + hotPhaseActions.forcemerge!.index_codec = 'best_compression'; + } else { + delete hotPhaseActions.forcemerge!.index_codec; } - if (updatedPolicy.phases.hot!.actions.rollover?.max_primary_shard_size) { - hotPhaseActions.rollover.max_primary_shard_size = `${hotPhaseActions.rollover.max_primary_shard_size}${_meta.hot?.customRollover.maxPrimaryShardSizeUnit}`; - } else { - delete hotPhaseActions.rollover.max_primary_shard_size; + if (_meta.hot?.bestCompression && hotPhaseActions.forcemerge) { + hotPhaseActions.forcemerge.index_codec = 'best_compression'; } - if (updatedPolicy.phases.hot!.actions.rollover?.max_size) { - hotPhaseActions.rollover.max_size = `${hotPhaseActions.rollover.max_size}${_meta.hot?.customRollover.maxStorageSizeUnit}`; + /** + * HOT PHASE READ-ONLY + */ + if (_meta.hot?.readonlyEnabled) { + hotPhaseActions.readonly = hotPhaseActions.readonly ?? {}; } else { - delete hotPhaseActions.rollover.max_size; + delete hotPhaseActions.readonly; } + } else { + delete hotPhaseActions.rollover; + delete hotPhaseActions.forcemerge; + delete hotPhaseActions.readonly; } /** - * HOT PHASE FORCEMERGE + * HOT PHASE SET PRIORITY */ - if (!updatedPolicy.phases.hot!.actions?.forcemerge) { - delete hotPhaseActions.forcemerge; - } else if (_meta.hot?.bestCompression) { - hotPhaseActions.forcemerge!.index_codec = 'best_compression'; - } else { - delete hotPhaseActions.forcemerge!.index_codec; + if (!updatedPolicy.phases.hot!.actions?.set_priority) { + delete hotPhaseActions.set_priority; } - if (_meta.hot?.bestCompression && hotPhaseActions.forcemerge) { - hotPhaseActions.forcemerge.index_codec = 'best_compression'; + /** + * HOT PHASE SHRINK + */ + if (!updatedPolicy.phases.hot?.actions?.shrink) { + delete hotPhaseActions.shrink; } /** - * HOT PHASE READ-ONLY + * HOT PHASE SEARCHABLE SNAPSHOT */ - if (_meta.hot?.readonlyEnabled) { - hotPhaseActions.readonly = hotPhaseActions.readonly ?? {}; + if (updatedPolicy.phases.hot!.actions?.searchable_snapshot) { + hotPhaseActions.searchable_snapshot = { + ...hotPhaseActions.searchable_snapshot, + snapshot_repository: _meta.searchableSnapshot.repository, + }; } else { - delete hotPhaseActions.readonly; + delete hotPhaseActions.searchable_snapshot; } - } else { - delete hotPhaseActions.rollover; - delete hotPhaseActions.forcemerge; - delete hotPhaseActions.readonly; } /** - * HOT PHASE SET PRIORITY + * WARM PHASE SERIALIZATION */ - if (!updatedPolicy.phases.hot!.actions?.set_priority) { - delete hotPhaseActions.set_priority; - } + if (_meta.warm.enabled) { + draft.phases.warm!.actions = draft.phases.warm?.actions ?? {}; + const warmPhase = draft.phases.warm!; - /** - * HOT PHASE SHRINK - */ - if (!updatedPolicy.phases.hot?.actions?.shrink) { - delete hotPhaseActions.shrink; - } + /** + * WARM PHASE MIN AGE + * + */ + if (updatedPolicy.phases.warm?.min_age) { + warmPhase.min_age = `${updatedPolicy.phases.warm!.min_age}${_meta.warm.minAgeUnit}`; + } - /** - * HOT PHASE SEARCHABLE SNAPSHOT - */ - if (updatedPolicy.phases.hot!.actions?.searchable_snapshot) { - hotPhaseActions.searchable_snapshot = { - ...hotPhaseActions.searchable_snapshot, - snapshot_repository: _meta.searchableSnapshot.repository, - }; - } else { - delete hotPhaseActions.searchable_snapshot; - } - } + /** + * WARM PHASE DATA ALLOCATION + */ + warmPhase.actions = serializeMigrateAndAllocateActions( + _meta.warm, + warmPhase.actions, + originalPolicy?.phases.warm?.actions, + updatedPolicy.phases.warm?.actions?.allocate?.number_of_replicas + ); - /** - * WARM PHASE SERIALIZATION - */ - if (_meta.warm.enabled) { - draft.phases.warm!.actions = draft.phases.warm?.actions ?? {}; - const warmPhase = draft.phases.warm!; + /** + * WARM PHASE FORCEMERGE + */ + if (!updatedPolicy.phases.warm?.actions?.forcemerge) { + delete warmPhase.actions.forcemerge; + } else if (_meta.warm.bestCompression) { + warmPhase.actions.forcemerge!.index_codec = 'best_compression'; + } else { + delete warmPhase.actions.forcemerge!.index_codec; + } - /** - * WARM PHASE MIN AGE - * - */ - if (updatedPolicy.phases.warm?.min_age) { - warmPhase.min_age = `${updatedPolicy.phases.warm!.min_age}${_meta.warm.minAgeUnit}`; - } + /** + * WARM PHASE READ ONLY + */ + if (_meta.warm.readonlyEnabled) { + warmPhase.actions.readonly = warmPhase.actions.readonly ?? {}; + } else { + delete warmPhase.actions.readonly; + } - /** - * WARM PHASE DATA ALLOCATION - */ - warmPhase.actions = serializeMigrateAndAllocateActions( - _meta.warm, - warmPhase.actions, - originalPolicy?.phases.warm?.actions, - updatedPolicy.phases.warm?.actions?.allocate?.number_of_replicas - ); + /** + * WARM PHASE SET PRIORITY + */ + if (!updatedPolicy.phases.warm?.actions?.set_priority) { + delete warmPhase.actions.set_priority; + } - /** - * WARM PHASE FORCEMERGE - */ - if (!updatedPolicy.phases.warm?.actions?.forcemerge) { - delete warmPhase.actions.forcemerge; - } else if (_meta.warm.bestCompression) { - warmPhase.actions.forcemerge!.index_codec = 'best_compression'; + /** + * WARM PHASE SHRINK + */ + if (!updatedPolicy.phases.warm?.actions?.shrink) { + delete warmPhase.actions.shrink; + } } else { - delete warmPhase.actions.forcemerge!.index_codec; + delete draft.phases.warm; } /** - * WARM PHASE READ ONLY + * COLD PHASE SERIALIZATION */ - if (_meta.warm.readonlyEnabled) { - warmPhase.actions.readonly = warmPhase.actions.readonly ?? {}; - } else { - delete warmPhase.actions.readonly; - } + if (_meta.cold.enabled) { + draft.phases.cold!.actions = draft.phases.cold?.actions ?? {}; + const coldPhase = draft.phases.cold!; - /** - * WARM PHASE SET PRIORITY - */ - if (!updatedPolicy.phases.warm?.actions?.set_priority) { - delete warmPhase.actions.set_priority; - } + /** + * COLD PHASE MIN AGE + */ + if (updatedPolicy.phases.cold?.min_age) { + coldPhase.min_age = `${updatedPolicy.phases.cold!.min_age}${_meta.cold.minAgeUnit}`; + } - /** - * WARM PHASE SHRINK - */ - if (!updatedPolicy.phases.warm?.actions?.shrink) { - delete warmPhase.actions.shrink; - } - } else { - delete draft.phases.warm; - } + /** + * COLD PHASE DATA ALLOCATION + */ + coldPhase.actions = serializeMigrateAndAllocateActions( + _meta.cold, + coldPhase.actions, + originalPolicy?.phases.cold?.actions, + updatedPolicy.phases.cold?.actions?.allocate?.number_of_replicas + ); - /** - * COLD PHASE SERIALIZATION - */ - if (_meta.cold.enabled) { - draft.phases.cold!.actions = draft.phases.cold?.actions ?? {}; - const coldPhase = draft.phases.cold!; + /** + * COLD PHASE FREEZE + */ + if (_meta.cold.freezeEnabled) { + coldPhase.actions.freeze = coldPhase.actions.freeze ?? {}; + } else { + delete coldPhase.actions.freeze; + } - /** - * COLD PHASE MIN AGE - */ - if (updatedPolicy.phases.cold?.min_age) { - coldPhase.min_age = `${updatedPolicy.phases.cold!.min_age}${_meta.cold.minAgeUnit}`; - } + /** + * COLD PHASE READ ONLY + */ + if (_meta.cold.readonlyEnabled) { + coldPhase.actions.readonly = coldPhase.actions.readonly ?? {}; + } else { + delete coldPhase.actions.readonly; + } - /** - * COLD PHASE DATA ALLOCATION - */ - coldPhase.actions = serializeMigrateAndAllocateActions( - _meta.cold, - coldPhase.actions, - originalPolicy?.phases.cold?.actions, - updatedPolicy.phases.cold?.actions?.allocate?.number_of_replicas - ); + /** + * COLD PHASE SET PRIORITY + */ + if (!updatedPolicy.phases.cold?.actions?.set_priority) { + delete coldPhase.actions.set_priority; + } - /** - * COLD PHASE FREEZE - */ - if (_meta.cold.freezeEnabled) { - coldPhase.actions.freeze = coldPhase.actions.freeze ?? {}; + /** + * COLD PHASE SEARCHABLE SNAPSHOT + */ + if (updatedPolicy.phases.cold?.actions?.searchable_snapshot) { + coldPhase.actions.searchable_snapshot = { + ...coldPhase.actions.searchable_snapshot, + snapshot_repository: _meta.searchableSnapshot.repository, + }; + } else { + delete coldPhase.actions.searchable_snapshot; + } } else { - delete coldPhase.actions.freeze; + delete draft.phases.cold; } /** - * COLD PHASE READ ONLY + * FROZEN PHASE SERIALIZATION */ - if (_meta.cold.readonlyEnabled) { - coldPhase.actions.readonly = coldPhase.actions.readonly ?? {}; - } else { - delete coldPhase.actions.readonly; - } + if (_meta.frozen?.enabled) { + draft.phases.frozen!.actions = draft.phases.frozen?.actions ?? {}; + const frozenPhase = draft.phases.frozen!; - /** - * COLD PHASE SET PRIORITY - */ - if (!updatedPolicy.phases.cold?.actions?.set_priority) { - delete coldPhase.actions.set_priority; - } + /** + * FROZEN PHASE MIN AGE + */ + if (updatedPolicy.phases.frozen?.min_age) { + frozenPhase.min_age = `${updatedPolicy.phases.frozen!.min_age}${_meta.frozen.minAgeUnit}`; + } - /** - * COLD PHASE SEARCHABLE SNAPSHOT - */ - if (updatedPolicy.phases.cold?.actions?.searchable_snapshot) { - coldPhase.actions.searchable_snapshot = { - ...coldPhase.actions.searchable_snapshot, - snapshot_repository: _meta.searchableSnapshot.repository, - }; + /** + * FROZEN PHASE SEARCHABLE SNAPSHOT + */ + if (updatedPolicy.phases.frozen?.actions?.searchable_snapshot) { + frozenPhase.actions.searchable_snapshot = { + ...frozenPhase.actions.searchable_snapshot, + snapshot_repository: _meta.searchableSnapshot.repository, + }; + } else { + delete frozenPhase.actions.searchable_snapshot; + } } else { - delete coldPhase.actions.searchable_snapshot; - } - } else { - delete draft.phases.cold; - } - - /** - * FROZEN PHASE SERIALIZATION - */ - if (_meta.frozen?.enabled) { - draft.phases.frozen!.actions = draft.phases.frozen?.actions ?? {}; - const frozenPhase = draft.phases.frozen!; - - /** - * FROZEN PHASE MIN AGE - */ - if (updatedPolicy.phases.frozen?.min_age) { - frozenPhase.min_age = `${updatedPolicy.phases.frozen!.min_age}${_meta.frozen.minAgeUnit}`; + delete draft.phases.frozen; } /** - * FROZEN PHASE SEARCHABLE SNAPSHOT + * DELETE PHASE SERIALIZATION */ - if (updatedPolicy.phases.frozen?.actions?.searchable_snapshot) { - frozenPhase.actions.searchable_snapshot = { - ...frozenPhase.actions.searchable_snapshot, - snapshot_repository: _meta.searchableSnapshot.repository, - }; - } else { - delete frozenPhase.actions.searchable_snapshot; - } - } else { - delete draft.phases.frozen; - } - - /** - * DELETE PHASE SERIALIZATION - */ - if (_meta.delete.enabled) { - const deletePhase = draft.phases.delete!; + if (_meta.delete.enabled) { + const deletePhase = draft.phases.delete!; - /** - * DELETE PHASE DELETE - */ - deletePhase.actions = deletePhase.actions ?? {}; - deletePhase.actions.delete = deletePhase.actions.delete ?? {}; + /** + * DELETE PHASE DELETE + */ + deletePhase.actions = deletePhase.actions ?? {}; + deletePhase.actions.delete = deletePhase.actions.delete ?? {}; - /** - * DELETE PHASE MIN AGE - */ - if (updatedPolicy.phases.delete?.min_age) { - deletePhase.min_age = `${updatedPolicy.phases.delete!.min_age}${_meta.delete.minAgeUnit}`; - } + /** + * DELETE PHASE MIN AGE + */ + if (updatedPolicy.phases.delete?.min_age) { + deletePhase.min_age = `${updatedPolicy.phases.delete!.min_age}${_meta.delete.minAgeUnit}`; + } - /** - * DELETE PHASE WAIT FOR SNAPSHOT - */ - if (!updatedPolicy.phases.delete?.actions?.wait_for_snapshot) { - delete deletePhase.actions.wait_for_snapshot; + /** + * DELETE PHASE WAIT FOR SNAPSHOT + */ + if (!updatedPolicy.phases.delete?.actions?.wait_for_snapshot) { + delete deletePhase.actions.wait_for_snapshot; + } + } else { + delete draft.phases.delete; } - } else { - delete draft.phases.delete; - } - }); -}; + }); + }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/validations.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/validations.ts index db43faf6cef17..efa6af4f61d0f 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/validations.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/validations.ts @@ -164,110 +164,108 @@ export const createPolicyNameValidations = ({ * For example, the user can't define '5 days' for cold phase if the * warm phase is set to '10 days'. */ -export const minAgeGreaterThanPreviousPhase = (phase: PhaseWithTiming) => ({ - formData, -}: { - formData: Record<string, number>; -}) => { - if (phase === 'warm') { - return; - } +export const minAgeGreaterThanPreviousPhase = + (phase: PhaseWithTiming) => + ({ formData }: { formData: Record<string, number> }) => { + if (phase === 'warm') { + return; + } - const getValueFor = (_phase: PhaseWithTiming) => { - const milli = formData[`_meta.${_phase}.minAgeToMilliSeconds`]; + const getValueFor = (_phase: PhaseWithTiming) => { + const milli = formData[`_meta.${_phase}.minAgeToMilliSeconds`]; - const esFormat = - milli >= 0 - ? formData[`phases.${_phase}.min_age`] + formData[`_meta.${_phase}.minAgeUnit`] - : undefined; + const esFormat = + milli >= 0 + ? formData[`phases.${_phase}.min_age`] + formData[`_meta.${_phase}.minAgeUnit`] + : undefined; - return { - milli, - esFormat, + return { + milli, + esFormat, + }; }; - }; - const minAgeValues = { - warm: getValueFor('warm'), - cold: getValueFor('cold'), - frozen: getValueFor('frozen'), - delete: getValueFor('delete'), - }; + const minAgeValues = { + warm: getValueFor('warm'), + cold: getValueFor('cold'), + frozen: getValueFor('frozen'), + delete: getValueFor('delete'), + }; - const i18nErrors = { - greaterThanWarmPhase: i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.minAgeSmallerThanWarmPhaseError', - { - defaultMessage: 'Must be greater or equal than the warm phase value ({value})', - values: { - value: minAgeValues.warm.esFormat, - }, - } - ), - greaterThanColdPhase: i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.minAgeSmallerThanColdPhaseError', - { - defaultMessage: 'Must be greater or equal than the cold phase value ({value})', - values: { - value: minAgeValues.cold.esFormat, - }, - } - ), - greaterThanFrozenPhase: i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.minAgeSmallerThanFrozenPhaseError', - { - defaultMessage: 'Must be greater or equal than the frozen phase value ({value})', - values: { - value: minAgeValues.frozen.esFormat, - }, - } - ), - }; + const i18nErrors = { + greaterThanWarmPhase: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.minAgeSmallerThanWarmPhaseError', + { + defaultMessage: 'Must be greater or equal than the warm phase value ({value})', + values: { + value: minAgeValues.warm.esFormat, + }, + } + ), + greaterThanColdPhase: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.minAgeSmallerThanColdPhaseError', + { + defaultMessage: 'Must be greater or equal than the cold phase value ({value})', + values: { + value: minAgeValues.cold.esFormat, + }, + } + ), + greaterThanFrozenPhase: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.minAgeSmallerThanFrozenPhaseError', + { + defaultMessage: 'Must be greater or equal than the frozen phase value ({value})', + values: { + value: minAgeValues.frozen.esFormat, + }, + } + ), + }; - if (phase === 'cold') { - if (minAgeValues.warm.milli >= 0 && minAgeValues.cold.milli < minAgeValues.warm.milli) { - return { - message: i18nErrors.greaterThanWarmPhase, - }; + if (phase === 'cold') { + if (minAgeValues.warm.milli >= 0 && minAgeValues.cold.milli < minAgeValues.warm.milli) { + return { + message: i18nErrors.greaterThanWarmPhase, + }; + } + return; } - return; - } - if (phase === 'frozen') { - if (minAgeValues.cold.milli >= 0 && minAgeValues.frozen.milli < minAgeValues.cold.milli) { - return { - message: i18nErrors.greaterThanColdPhase, - }; - } else if ( - minAgeValues.warm.milli >= 0 && - minAgeValues.frozen.milli < minAgeValues.warm.milli - ) { - return { - message: i18nErrors.greaterThanWarmPhase, - }; + if (phase === 'frozen') { + if (minAgeValues.cold.milli >= 0 && minAgeValues.frozen.milli < minAgeValues.cold.milli) { + return { + message: i18nErrors.greaterThanColdPhase, + }; + } else if ( + minAgeValues.warm.milli >= 0 && + minAgeValues.frozen.milli < minAgeValues.warm.milli + ) { + return { + message: i18nErrors.greaterThanWarmPhase, + }; + } + return; } - return; - } - if (phase === 'delete') { - if (minAgeValues.frozen.milli >= 0 && minAgeValues.delete.milli < minAgeValues.frozen.milli) { - return { - message: i18nErrors.greaterThanFrozenPhase, - }; - } else if ( - minAgeValues.cold.milli >= 0 && - minAgeValues.delete.milli < minAgeValues.cold.milli - ) { - return { - message: i18nErrors.greaterThanColdPhase, - }; - } else if ( - minAgeValues.warm.milli >= 0 && - minAgeValues.delete.milli < minAgeValues.warm.milli - ) { - return { - message: i18nErrors.greaterThanWarmPhase, - }; + if (phase === 'delete') { + if (minAgeValues.frozen.milli >= 0 && minAgeValues.delete.milli < minAgeValues.frozen.milli) { + return { + message: i18nErrors.greaterThanFrozenPhase, + }; + } else if ( + minAgeValues.cold.milli >= 0 && + minAgeValues.delete.milli < minAgeValues.cold.milli + ) { + return { + message: i18nErrors.greaterThanColdPhase, + }; + } else if ( + minAgeValues.warm.milli >= 0 && + minAgeValues.delete.milli < minAgeValues.warm.milli + ) { + return { + message: i18nErrors.greaterThanWarmPhase, + }; + } } - } -}; + }; diff --git a/x-pack/plugins/index_lifecycle_management/public/plugin.tsx b/x-pack/plugins/index_lifecycle_management/public/plugin.tsx index 339d7a4d7db96..bc0981529c34f 100644 --- a/x-pack/plugins/index_lifecycle_management/public/plugin.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/plugin.tsx @@ -20,7 +20,8 @@ import { ClientConfigType, SetupDependencies, StartDependencies } from './types' import { IlmLocatorDefinition } from './locator'; export class IndexLifecycleManagementPlugin - implements Plugin<void, void, SetupDependencies, StartDependencies> { + implements Plugin<void, void, SetupDependencies, StartDependencies> +{ constructor(private readonly initializerContext: PluginInitializerContext) {} private breadcrumbService = new BreadcrumbService(); diff --git a/x-pack/plugins/index_lifecycle_management/server/index.ts b/x-pack/plugins/index_lifecycle_management/server/index.ts index e90518dbfa357..1f8b01913fd3e 100644 --- a/x-pack/plugins/index_lifecycle_management/server/index.ts +++ b/x-pack/plugins/index_lifecycle_management/server/index.ts @@ -17,4 +17,5 @@ export const config: PluginConfigDescriptor<IndexLifecycleManagementConfig> = { exposeToBrowser: { ui: true, }, + deprecations: ({ deprecate }) => [deprecate('enabled', '8.0.0')], }; diff --git a/x-pack/plugins/index_lifecycle_management/server/routes/api/nodes/__jest__/fixtures.ts b/x-pack/plugins/index_lifecycle_management/server/routes/api/nodes/__jest__/fixtures.ts index fe8d685dc2e01..0ebed383e261a 100644 --- a/x-pack/plugins/index_lifecycle_management/server/routes/api/nodes/__jest__/fixtures.ts +++ b/x-pack/plugins/index_lifecycle_management/server/routes/api/nodes/__jest__/fixtures.ts @@ -127,8 +127,7 @@ export const cloudNodeSettingsWithLegacy = { principal: 'http://saml.elastic-cloud.com/attributes/principal', }, sp: { - acs: - 'https://916c269173df465f9826f4471799de42.europe-west4.gcp.elastic-cloud.com:9243/api/security/saml/callback', + acs: 'https://916c269173df465f9826f4471799de42.europe-west4.gcp.elastic-cloud.com:9243/api/security/saml/callback', entity_id: 'ec:2628060457:916c269173df465f9826f4471799de42', logout: 'https://916c269173df465f9826f4471799de42.europe-west4.gcp.elastic-cloud.com:9243/logout', @@ -137,8 +136,7 @@ export const cloudNodeSettingsWithLegacy = { idp: { entity_id: 'urn:idp-cloud-elastic-co', metadata: { - path: - '/app/config/cloud-saml-metadata-916c269173df465f9826f4471799de42.xml', + path: '/app/config/cloud-saml-metadata-916c269173df465f9826f4471799de42.xml', }, }, }, @@ -354,8 +352,7 @@ export const cloudNodeSettingsWithLegacy = { principal: 'http://saml.elastic-cloud.com/attributes/principal', }, sp: { - acs: - 'https://916c269173df465f9826f4471799de42.europe-west4.gcp.elastic-cloud.com:9243/api/security/saml/callback', + acs: 'https://916c269173df465f9826f4471799de42.europe-west4.gcp.elastic-cloud.com:9243/api/security/saml/callback', entity_id: 'ec:2628060457:916c269173df465f9826f4471799de42', logout: 'https://916c269173df465f9826f4471799de42.europe-west4.gcp.elastic-cloud.com:9243/logout', @@ -364,8 +361,7 @@ export const cloudNodeSettingsWithLegacy = { idp: { entity_id: 'urn:idp-cloud-elastic-co', metadata: { - path: - '/app/config/cloud-saml-metadata-916c269173df465f9826f4471799de42.xml', + path: '/app/config/cloud-saml-metadata-916c269173df465f9826f4471799de42.xml', }, }, }, @@ -580,8 +576,7 @@ export const cloudNodeSettingsWithLegacy = { principal: 'http://saml.elastic-cloud.com/attributes/principal', }, sp: { - acs: - 'https://916c269173df465f9826f4471799de42.europe-west4.gcp.elastic-cloud.com:9243/api/security/saml/callback', + acs: 'https://916c269173df465f9826f4471799de42.europe-west4.gcp.elastic-cloud.com:9243/api/security/saml/callback', entity_id: 'ec:2628060457:916c269173df465f9826f4471799de42', logout: 'https://916c269173df465f9826f4471799de42.europe-west4.gcp.elastic-cloud.com:9243/logout', @@ -590,8 +585,7 @@ export const cloudNodeSettingsWithLegacy = { idp: { entity_id: 'urn:idp-cloud-elastic-co', metadata: { - path: - '/app/config/cloud-saml-metadata-916c269173df465f9826f4471799de42.xml', + path: '/app/config/cloud-saml-metadata-916c269173df465f9826f4471799de42.xml', }, }, }, @@ -806,8 +800,7 @@ export const cloudNodeSettingsWithLegacy = { principal: 'http://saml.elastic-cloud.com/attributes/principal', }, sp: { - acs: - 'https://916c269173df465f9826f4471799de42.europe-west4.gcp.elastic-cloud.com:9243/api/security/saml/callback', + acs: 'https://916c269173df465f9826f4471799de42.europe-west4.gcp.elastic-cloud.com:9243/api/security/saml/callback', entity_id: 'ec:2628060457:916c269173df465f9826f4471799de42', logout: 'https://916c269173df465f9826f4471799de42.europe-west4.gcp.elastic-cloud.com:9243/logout', @@ -816,8 +809,7 @@ export const cloudNodeSettingsWithLegacy = { idp: { entity_id: 'urn:idp-cloud-elastic-co', metadata: { - path: - '/app/config/cloud-saml-metadata-916c269173df465f9826f4471799de42.xml', + path: '/app/config/cloud-saml-metadata-916c269173df465f9826f4471799de42.xml', }, }, }, @@ -1032,8 +1024,7 @@ export const cloudNodeSettingsWithLegacy = { principal: 'http://saml.elastic-cloud.com/attributes/principal', }, sp: { - acs: - 'https://916c269173df465f9826f4471799de42.europe-west4.gcp.elastic-cloud.com:9243/api/security/saml/callback', + acs: 'https://916c269173df465f9826f4471799de42.europe-west4.gcp.elastic-cloud.com:9243/api/security/saml/callback', entity_id: 'ec:2628060457:916c269173df465f9826f4471799de42', logout: 'https://916c269173df465f9826f4471799de42.europe-west4.gcp.elastic-cloud.com:9243/logout', @@ -1042,8 +1033,7 @@ export const cloudNodeSettingsWithLegacy = { idp: { entity_id: 'urn:idp-cloud-elastic-co', metadata: { - path: - '/app/config/cloud-saml-metadata-916c269173df465f9826f4471799de42.xml', + path: '/app/config/cloud-saml-metadata-916c269173df465f9826f4471799de42.xml', }, }, }, @@ -1269,8 +1259,7 @@ export const cloudNodeSettingsWithoutLegacy = { principal: 'http://saml.elastic-cloud.com/attributes/principal', }, sp: { - acs: - 'https://916c269173df465f9826f4471799de42.europe-west4.gcp.elastic-cloud.com:9243/api/security/saml/callback', + acs: 'https://916c269173df465f9826f4471799de42.europe-west4.gcp.elastic-cloud.com:9243/api/security/saml/callback', entity_id: 'ec:2628060457:916c269173df465f9826f4471799de42', logout: 'https://916c269173df465f9826f4471799de42.europe-west4.gcp.elastic-cloud.com:9243/logout', @@ -1279,8 +1268,7 @@ export const cloudNodeSettingsWithoutLegacy = { idp: { entity_id: 'urn:idp-cloud-elastic-co', metadata: { - path: - '/app/config/cloud-saml-metadata-916c269173df465f9826f4471799de42.xml', + path: '/app/config/cloud-saml-metadata-916c269173df465f9826f4471799de42.xml', }, }, }, @@ -1496,8 +1484,7 @@ export const cloudNodeSettingsWithoutLegacy = { principal: 'http://saml.elastic-cloud.com/attributes/principal', }, sp: { - acs: - 'https://916c269173df465f9826f4471799de42.europe-west4.gcp.elastic-cloud.com:9243/api/security/saml/callback', + acs: 'https://916c269173df465f9826f4471799de42.europe-west4.gcp.elastic-cloud.com:9243/api/security/saml/callback', entity_id: 'ec:2628060457:916c269173df465f9826f4471799de42', logout: 'https://916c269173df465f9826f4471799de42.europe-west4.gcp.elastic-cloud.com:9243/logout', @@ -1506,8 +1493,7 @@ export const cloudNodeSettingsWithoutLegacy = { idp: { entity_id: 'urn:idp-cloud-elastic-co', metadata: { - path: - '/app/config/cloud-saml-metadata-916c269173df465f9826f4471799de42.xml', + path: '/app/config/cloud-saml-metadata-916c269173df465f9826f4471799de42.xml', }, }, }, @@ -1722,8 +1708,7 @@ export const cloudNodeSettingsWithoutLegacy = { principal: 'http://saml.elastic-cloud.com/attributes/principal', }, sp: { - acs: - 'https://916c269173df465f9826f4471799de42.europe-west4.gcp.elastic-cloud.com:9243/api/security/saml/callback', + acs: 'https://916c269173df465f9826f4471799de42.europe-west4.gcp.elastic-cloud.com:9243/api/security/saml/callback', entity_id: 'ec:2628060457:916c269173df465f9826f4471799de42', logout: 'https://916c269173df465f9826f4471799de42.europe-west4.gcp.elastic-cloud.com:9243/logout', @@ -1732,8 +1717,7 @@ export const cloudNodeSettingsWithoutLegacy = { idp: { entity_id: 'urn:idp-cloud-elastic-co', metadata: { - path: - '/app/config/cloud-saml-metadata-916c269173df465f9826f4471799de42.xml', + path: '/app/config/cloud-saml-metadata-916c269173df465f9826f4471799de42.xml', }, }, }, @@ -1948,8 +1932,7 @@ export const cloudNodeSettingsWithoutLegacy = { principal: 'http://saml.elastic-cloud.com/attributes/principal', }, sp: { - acs: - 'https://916c269173df465f9826f4471799de42.europe-west4.gcp.elastic-cloud.com:9243/api/security/saml/callback', + acs: 'https://916c269173df465f9826f4471799de42.europe-west4.gcp.elastic-cloud.com:9243/api/security/saml/callback', entity_id: 'ec:2628060457:916c269173df465f9826f4471799de42', logout: 'https://916c269173df465f9826f4471799de42.europe-west4.gcp.elastic-cloud.com:9243/logout', @@ -1958,8 +1941,7 @@ export const cloudNodeSettingsWithoutLegacy = { idp: { entity_id: 'urn:idp-cloud-elastic-co', metadata: { - path: - '/app/config/cloud-saml-metadata-916c269173df465f9826f4471799de42.xml', + path: '/app/config/cloud-saml-metadata-916c269173df465f9826f4471799de42.xml', }, }, }, @@ -2174,8 +2156,7 @@ export const cloudNodeSettingsWithoutLegacy = { principal: 'http://saml.elastic-cloud.com/attributes/principal', }, sp: { - acs: - 'https://916c269173df465f9826f4471799de42.europe-west4.gcp.elastic-cloud.com:9243/api/security/saml/callback', + acs: 'https://916c269173df465f9826f4471799de42.europe-west4.gcp.elastic-cloud.com:9243/api/security/saml/callback', entity_id: 'ec:2628060457:916c269173df465f9826f4471799de42', logout: 'https://916c269173df465f9826f4471799de42.europe-west4.gcp.elastic-cloud.com:9243/logout', @@ -2184,8 +2165,7 @@ export const cloudNodeSettingsWithoutLegacy = { idp: { entity_id: 'urn:idp-cloud-elastic-co', metadata: { - path: - '/app/config/cloud-saml-metadata-916c269173df465f9826f4471799de42.xml', + path: '/app/config/cloud-saml-metadata-916c269173df465f9826f4471799de42.xml', }, }, }, diff --git a/x-pack/plugins/index_lifecycle_management/server/routes/api/nodes/register_list_route.ts b/x-pack/plugins/index_lifecycle_management/server/routes/api/nodes/register_list_route.ts index accd8993abc62..314b206d6ea6b 100644 --- a/x-pack/plugins/index_lifecycle_management/server/routes/api/nodes/register_list_route.ts +++ b/x-pack/plugins/index_lifecycle_management/server/routes/api/nodes/register_list_route.ts @@ -88,15 +88,14 @@ export function registerListRoute({ { path: addBasePath('/nodes/list'), validate: false }, license.guardApiRoute(async (context, request, response) => { try { - const settingsResponse = await context.core.elasticsearch.client.asCurrentUser.transport.request( - { + const settingsResponse = + await context.core.elasticsearch.client.asCurrentUser.transport.request({ method: 'GET', path: '/_nodes/settings', querystring: { format: 'json', }, - } - ); + }); const body: ListNodesRouteResponse = convertSettingsIntoLists( settingsResponse.body as Settings, disallowedNodeAttributes diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/setup_environment.tsx b/x-pack/plugins/index_management/__jest__/client_integration/helpers/setup_environment.tsx index 9023a5ac53a98..7ce81fa47df97 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/setup_environment.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/setup_environment.tsx @@ -66,19 +66,19 @@ export const setupEnvironment = () => { }; }; -export const WithAppDependencies = (Comp: any, overridingDependencies: any = {}) => ( - props: any -) => { - const mergedDependencies = merge({}, appDependencies, overridingDependencies); - return ( - <AppContextProvider value={mergedDependencies}> - <MappingsEditorProvider> - <ComponentTemplatesProvider value={componentTemplatesMockDependencies}> - <GlobalFlyoutProvider> - <Comp {...props} /> - </GlobalFlyoutProvider> - </ComponentTemplatesProvider> - </MappingsEditorProvider> - </AppContextProvider> - ); -}; +export const WithAppDependencies = + (Comp: any, overridingDependencies: any = {}) => + (props: any) => { + const mergedDependencies = merge({}, appDependencies, overridingDependencies); + return ( + <AppContextProvider value={mergedDependencies}> + <MappingsEditorProvider> + <ComponentTemplatesProvider value={componentTemplatesMockDependencies}> + <GlobalFlyoutProvider> + <Comp {...props} /> + </GlobalFlyoutProvider> + </ComponentTemplatesProvider> + </MappingsEditorProvider> + </AppContextProvider> + ); + }; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts index 8e114b0596948..7c4d2573bdec6 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts @@ -348,11 +348,8 @@ describe('Data Streams tab', () => { describe('when there are special characters', () => { beforeEach(async () => { - const { - setLoadIndicesResponse, - setLoadDataStreamsResponse, - setLoadDataStreamResponse, - } = httpRequestsMockHelpers; + const { setLoadIndicesResponse, setLoadDataStreamsResponse, setLoadDataStreamResponse } = + httpRequestsMockHelpers; setLoadIndicesResponse([ createDataStreamBackingIndex('data-stream-index', '%dataStream'), diff --git a/x-pack/plugins/index_management/common/lib/template_serialization.ts b/x-pack/plugins/index_management/common/lib/template_serialization.ts index 3e6698ac57136..ce49c32cd8271 100644 --- a/x-pack/plugins/index_management/common/lib/template_serialization.ts +++ b/x-pack/plugins/index_management/common/lib/template_serialization.ts @@ -16,15 +16,8 @@ import { const hasEntries = (data: object = {}) => Object.entries(data).length > 0; export function serializeTemplate(templateDeserialized: TemplateDeserialized): TemplateSerialized { - const { - version, - priority, - indexPatterns, - template, - composedOf, - dataStream, - _meta, - } = templateDeserialized; + const { version, priority, indexPatterns, template, composedOf, dataStream, _meta } = + templateDeserialized; return { version, @@ -86,10 +79,8 @@ export function deserializeTemplateList( cloudManagedTemplatePrefix?: string ): TemplateListItem[] { return indexTemplates.map(({ name, index_template: templateSerialized }) => { - const { - template: { mappings, settings, aliases } = {}, - ...deserializedTemplate - } = deserializeTemplate({ name, ...templateSerialized }, cloudManagedTemplatePrefix); + const { template: { mappings, settings, aliases } = {}, ...deserializedTemplate } = + deserializeTemplate({ name, ...templateSerialized }, cloudManagedTemplatePrefix); return { ...deserializedTemplate, @@ -150,10 +141,8 @@ export function deserializeLegacyTemplateList( cloudManagedTemplatePrefix?: string ): TemplateListItem[] { return Object.entries(indexTemplatesByName).map(([name, templateSerialized]) => { - const { - template: { mappings, settings, aliases } = {}, - ...deserializedTemplate - } = deserializeLegacyTemplate({ name, ...templateSerialized }, cloudManagedTemplatePrefix); + const { template: { mappings, settings, aliases } = {}, ...deserializedTemplate } = + deserializeLegacyTemplate({ name, ...templateSerialized }, cloudManagedTemplatePrefix); return { ...deserializedTemplate, diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/setup_environment.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/setup_environment.tsx index ece377b0ee2cf..d532eaaba8923 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/setup_environment.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/setup_environment.tsx @@ -33,7 +33,7 @@ const appDependencies = { } as any; export const componentTemplatesDependencies = { - httpClient: (mockHttpClient as unknown) as HttpSetup, + httpClient: mockHttpClient as unknown as HttpSetup, apiBasePath: API_BASE_PATH, trackMetric: () => {}, docLinks: docLinksServiceMock.createStartContract(), @@ -51,15 +51,16 @@ export const setupEnvironment = () => { }; }; -export const WithAppDependencies = (Comp: any) => (props: any) => ( - <AppContextProvider value={appDependencies}> - <MappingsEditorProvider> - <ComponentTemplatesProvider value={componentTemplatesDependencies}> - <GlobalFlyoutProvider> - <Comp {...props} /> - </GlobalFlyoutProvider> - </ComponentTemplatesProvider> - </MappingsEditorProvider> - / - </AppContextProvider> -); +export const WithAppDependencies = (Comp: any) => (props: any) => + ( + <AppContextProvider value={appDependencies}> + <MappingsEditorProvider> + <ComponentTemplatesProvider value={componentTemplatesDependencies}> + <GlobalFlyoutProvider> + <Comp {...props} /> + </GlobalFlyoutProvider> + </ComponentTemplatesProvider> + </MappingsEditorProvider> + / + </AppContextProvider> + ); diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/component_template_details.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/component_template_details.tsx index 06a5e8b5c27dc..6dbf1b15174e9 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/component_template_details.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/component_template_details.tsx @@ -55,9 +55,11 @@ export const ComponentTemplateDetailsFlyoutContent: React.FunctionComponent<Prop const decodedComponentTemplateName = attemptToURIDecode(componentTemplateName)!; - const { data: componentTemplateDetails, isLoading, error } = api.useLoadComponentTemplate( - decodedComponentTemplateName - ); + const { + data: componentTemplateDetails, + isLoading, + error, + } = api.useLoadComponentTemplate(decodedComponentTemplateName); const [activeTab, setActiveTab] = useState<TabType>('summary'); diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/component_template_list.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/component_template_list.tsx index 77668f7d55072..6ff7c5826459e 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/component_template_list.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/component_template_list.tsx @@ -42,10 +42,8 @@ export const ComponentTemplateList: React.FunctionComponent<Props> = ({ componentTemplateName, history, }) => { - const { - addContent: addContentToGlobalFlyout, - removeContent: removeContentFromGlobalFlyout, - } = useGlobalFlyout(); + const { addContent: addContentToGlobalFlyout, removeContent: removeContentFromGlobalFlyout } = + useGlobalFlyout(); const { api, trackMetric, documentation } = useComponentTemplatesContext(); const { data, isLoading, error, resendRequest } = api.useLoadComponentTemplates(); diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/component_templates_selector.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/component_templates_selector.tsx index a1e1cc5557660..41be138ea9fa5 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/component_templates_selector.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/component_templates_selector.tsx @@ -60,10 +60,8 @@ export const ComponentTemplatesSelector = ({ emptyPrompt: { text, showCreateButton } = {}, }: Props) => { const { data: components, isLoading, error } = useApi().useLoadComponentTemplates(); - const { - addContent: addContentToGlobalFlyout, - removeContent: removeContentFromGlobalFlyout, - } = useGlobalFlyout(); + const { addContent: addContentToGlobalFlyout, removeContent: removeContentFromGlobalFlyout } = + useGlobalFlyout(); const [selectedComponent, setSelectedComponent] = useState<string | null>(null); const [componentsSelected, setComponentsSelected] = useState<ComponentTemplateListItem[]>([]); const isInitialized = useRef(false); diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_clone/component_template_clone.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_clone/component_template_clone.tsx index d19c500c3622a..1889da7c1336f 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_clone/component_template_clone.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_clone/component_template_clone.tsx @@ -24,9 +24,11 @@ export const ComponentTemplateClone: FunctionComponent<RouteComponentProps<Param const { toasts, api } = useComponentTemplatesContext(); - const { error, data: componentTemplateToClone, isLoading } = api.useLoadComponentTemplate( - decodedSourceName - ); + const { + error, + data: componentTemplateToClone, + isLoading, + } = api.useLoadComponentTemplate(decodedSourceName); useEffect(() => { if (error && !isLoading) { diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_form/component_template_form.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_form/component_template_form.tsx index 8763bb8881ed7..8d5e61c81120f 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_form/component_template_form.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_form/component_template_form.tsx @@ -160,22 +160,21 @@ export const ComponentTemplateForm = ({ }; const buildComponentTemplateObject = useCallback( - (initialTemplate: ComponentTemplateDeserialized) => ( - wizardData: WizardContent - ): ComponentTemplateDeserialized => { - const outputComponentTemplate = { - ...initialTemplate, - name: wizardData.logistics.name, - version: wizardData.logistics.version, - _meta: wizardData.logistics._meta, - template: { - settings: wizardData.settings, - mappings: wizardData.mappings, - aliases: wizardData.aliases, - }, - }; - return cleanupComponentTemplateObject(outputComponentTemplate); - }, + (initialTemplate: ComponentTemplateDeserialized) => + (wizardData: WizardContent): ComponentTemplateDeserialized => { + const outputComponentTemplate = { + ...initialTemplate, + name: wizardData.logistics.name, + version: wizardData.logistics.version, + _meta: wizardData.logistics._meta, + template: { + settings: wizardData.settings, + mappings: wizardData.mappings, + aliases: wizardData.aliases, + }, + }; + return cleanupComponentTemplateObject(outputComponentTemplate); + }, [] ); diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_templates_context.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_templates_context.tsx index 94f5befab61c9..89b4e90741410 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_templates_context.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_templates_context.tsx @@ -42,15 +42,8 @@ export const ComponentTemplatesProvider = ({ value: Props; children: React.ReactNode; }) => { - const { - httpClient, - apiBasePath, - trackMetric, - docLinks, - toasts, - setBreadcrumbs, - getUrlForApp, - } = value; + const { httpClient, apiBasePath, trackMetric, docLinks, toasts, setBreadcrumbs, getUrlForApp } = + value; const useRequest = getUseRequest(httpClient); const sendRequest = getSendRequest(httpClient); diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/lib/request.ts b/x-pack/plugins/index_management/public/application/components/component_templates/lib/request.ts index a7056e27b5cad..92847bbe5dc05 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/lib/request.ts +++ b/x-pack/plugins/index_management/public/application/components/component_templates/lib/request.ts @@ -22,14 +22,14 @@ export type UseRequestHook = <T = any, E = Error>( ) => UseRequestResponse<T, E>; export type SendRequestHook = (config: SendRequestConfig) => Promise<SendRequestResponse>; -export const getUseRequest = (httpClient: HttpSetup): UseRequestHook => <T = any, E = Error>( - config: UseRequestConfig -) => { - return _useRequest<T, E>(httpClient, config); -}; +export const getUseRequest = + (httpClient: HttpSetup): UseRequestHook => + <T = any, E = Error>(config: UseRequestConfig) => { + return _useRequest<T, E>(httpClient, config); + }; -export const getSendRequest = (httpClient: HttpSetup): SendRequestHook => <T = any>( - config: SendRequestConfig -) => { - return _sendRequest<T>(httpClient, config); -}; +export const getSendRequest = + (httpClient: HttpSetup): SendRequestHook => + <T = any>(config: SendRequestConfig) => { + return _sendRequest<T>(httpClient, config); + }; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/setup_environment.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/setup_environment.tsx index efd1ec7062423..7055dcc74ce7b 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/setup_environment.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/setup_environment.tsx @@ -78,12 +78,13 @@ const defaultProps = { docLinks: docLinksServiceMock.createStartContract(), }; -export const WithAppDependencies = (Comp: any) => (props: any) => ( - <KibanaReactContextProvider> - <MappingsEditorProvider> - <GlobalFlyoutProvider> - <Comp {...defaultProps} {...props} /> - </GlobalFlyoutProvider> - </MappingsEditorProvider> - </KibanaReactContextProvider> -); +export const WithAppDependencies = (Comp: any) => (props: any) => + ( + <KibanaReactContextProvider> + <MappingsEditorProvider> + <GlobalFlyoutProvider> + <Comp {...defaultProps} {...props} /> + </GlobalFlyoutProvider> + </MappingsEditorProvider> + </KibanaReactContextProvider> + ); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/document_fields.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/document_fields.tsx index 7b464a299c24b..a28531ed70953 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/document_fields.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/document_fields.tsx @@ -22,10 +22,8 @@ const { useGlobalFlyout } = GlobalFlyout; export const DocumentFields = React.memo(() => { const { fields, search, documentFields } = useMappingsState(); const dispatch = useDispatch(); - const { - addContent: addContentToGlobalFlyout, - removeContent: removeContentFromGlobalFlyout, - } = useGlobalFlyout(); + const { addContent: addContentToGlobalFlyout, removeContent: removeContentFromGlobalFlyout } = + useGlobalFlyout(); const { editor: editorType } = documentFields; const isEditing = documentFields.status === 'editingField'; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/analyzer_parameter.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/analyzer_parameter.tsx index bb6a5aae82f1a..5d5f0fc0b930c 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/analyzer_parameter.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/analyzer_parameter.tsx @@ -33,9 +33,9 @@ interface Props { const ANALYZER_OPTIONS = PARAMETERS_OPTIONS.analyzer!; // token_count requires a value for "analyzer", therefore, we cannot not allow "index_default" -const ANALYZER_OPTIONS_WITHOUT_DEFAULT = (PARAMETERS_OPTIONS.analyzer as SuperSelectOption[]).filter( - ({ value }) => value !== INDEX_DEFAULT -); +const ANALYZER_OPTIONS_WITHOUT_DEFAULT = ( + PARAMETERS_OPTIONS.analyzer as SuperSelectOption[] +).filter(({ value }) => value !== INDEX_DEFAULT); const getCustomAnalyzers = (indexSettings: IndexSettings): SelectOption[] | undefined => { const settings: IndexSettingsInterface = {}.hasOwnProperty.call(indexSettings, 'index') diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/path_parameter.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/path_parameter.tsx index 5ce48f63f0b94..7307dd0e41eda 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/path_parameter.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/path_parameter.tsx @@ -40,20 +40,20 @@ const getSuggestedFields = ( })) .sort((a, b) => (a.label > b.label ? 1 : a.label < b.label ? -1 : 0)); -const getDeserializer = (allFields: NormalizedFields['byId']): SerializerFunc => ( - value: string | object -): AliasOption[] => { - if (typeof value === 'string' && Boolean(value)) { - return [ - { - id: value, - label: allFields[value].path.join(' > '), - }, - ]; - } +const getDeserializer = + (allFields: NormalizedFields['byId']): SerializerFunc => + (value: string | object): AliasOption[] => { + if (typeof value === 'string' && Boolean(value)) { + return [ + { + id: value, + label: allFields[value].path.join(' > '), + }, + ]; + } - return []; -}; + return []; + }; interface Props { allFields: NormalizedFields['byId']; @@ -86,7 +86,7 @@ export const PathParameter = ({ field, allFields }: Props) => { 'xpack.idxMgmt.mappingsEditor.aliasType.aliasTargetFieldDescription', { defaultMessage: - 'Select the field you want your alias to point to. You will then be able to use the alias instead of the target field in search requests, and selected other APIs like field capabilities.', + 'Select the field you want your alias to point to. You will then be able to use the alias instead of the target field in search requests and select other APIs like field capabilities.', } )} withToggle={false} diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_types/text_type.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_types/text_type.tsx index 1a03dec48c97e..2a007e7741d4a 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_types/text_type.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_types/text_type.tsx @@ -74,13 +74,12 @@ const getDefaultToggleValue = (param: string, field: FieldType) => { }; export const TextType = React.memo(({ field }: Props) => { - const onIndexPrefixesChanage = (minField: FieldHook, maxField: FieldHook) => ([ - min, - max, - ]: any) => { - minField.setValue(min); - maxField.setValue(max); - }; + const onIndexPrefixesChanage = + (minField: FieldHook, maxField: FieldHook) => + ([min, max]: any) => { + minField.setValue(min); + maxField.setValue(max); + }; return ( <> diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list_item.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list_item.tsx index 1f4e1d1475e5d..76284f925c3b1 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list_item.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list_item.tsx @@ -131,12 +131,8 @@ function FieldListItemComponent( return null; } - const { - addMultiFieldButtonLabel, - addPropertyButtonLabel, - editButtonLabel, - deleteButtonLabel, - } = i18nTexts; + const { addMultiFieldButtonLabel, addPropertyButtonLabel, editButtonLabel, deleteButtonLabel } = + i18nTexts; return ( <EuiFlexGroup gutterSize="s" className="mappingsEditor__fieldsListItem__actions"> diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/load_mappings/load_mappings_provider.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/load_mappings/load_mappings_provider.test.tsx index 7f6aea8878a77..2413df5e5d64d 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/load_mappings/load_mappings_provider.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/load_mappings/load_mappings_provider.test.tsx @@ -37,20 +37,22 @@ const setup = (props: any) => defaultProps: props, })(); -const openModalWithJsonContent = ({ component, find }: TestBed) => (json: any) => { - act(() => { - find('load-json-button').simulate('click'); - }); +const openModalWithJsonContent = + ({ component, find }: TestBed) => + (json: any) => { + act(() => { + find('load-json-button').simulate('click'); + }); - component.update(); + component.update(); - act(() => { - // Set the mappings to load - find('mockCodeEditor').simulate('change', { - jsonString: JSON.stringify(json), + act(() => { + // Set the mappings to load + find('mockCodeEditor').simulate('change', { + jsonString: JSON.stringify(json), + }); }); - }); -}; + }; describe('<LoadMappingsProvider />', () => { test('it should forward valid mapping definition', () => { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/runtime_fields/runtime_fields_list.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/runtime_fields/runtime_fields_list.tsx index 387c53e3353fc..13cbd8e227328 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/runtime_fields/runtime_fields_list.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/runtime_fields/runtime_fields_list.tsx @@ -34,10 +34,8 @@ export const RuntimeFieldsList = () => { const dispatch = useDispatch(); - const { - addContent: addContentToGlobalFlyout, - removeContent: removeContentFromGlobalFlyout, - } = useGlobalFlyout(); + const { addContent: addContentToGlobalFlyout, removeContent: removeContentFromGlobalFlyout } = + useGlobalFlyout(); const { value: { docLinks }, diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/mappings_validator.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/mappings_validator.ts index 2abaeb45639bf..3eb2f8d6371d8 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/mappings_validator.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/mappings_validator.ts @@ -284,9 +284,8 @@ export const validateMappings = (mappings: any = {}): MappingsValidatorResponse const { properties, dynamic_templates: dynamicTemplates, ...mappingsConfiguration } = mappings; - const { value: parsedConfiguration, errors: configurationErrors } = validateMappingsConfiguration( - mappingsConfiguration - ); + const { value: parsedConfiguration, errors: configurationErrors } = + validateMappingsConfiguration(mappingsConfiguration); const { value: parsedProperties, errors: propertiesErrors } = validateProperties(properties); const errors = [...configurationErrors, ...propertiesErrors]; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.test.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.test.ts index 321500730c82f..162bb59a0528a 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.test.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.test.ts @@ -10,7 +10,7 @@ jest.mock('../constants', () => { return { MAIN_DATA_TYPE_DEFINITION: {}, TYPE_DEFINITION }; }); -import { stripUndefinedValues, getTypeLabelFromField } from './utils'; +import { stripUndefinedValues, getTypeLabelFromField, getFieldMeta } from './utils'; describe('utils', () => { describe('stripUndefinedValues()', () => { @@ -77,4 +77,17 @@ describe('utils', () => { ).toBe('Other: hyperdrive'); }); }); + + describe('getFieldMeta', () => { + test('returns "canHaveMultiFields:true" for IP data type', () => { + expect(getFieldMeta({ name: 'ip_field', type: 'ip' })).toEqual({ + canHaveChildFields: false, + canHaveMultiFields: true, + childFieldsName: 'fields', + hasChildFields: false, + hasMultiFields: false, + isExpanded: false, + }); + }); + }); }); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.ts index bc02640ba7b78..44461f1b98aef 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.ts @@ -40,7 +40,7 @@ import { TreeItem } from '../components/tree'; export const getUniqueId = () => uuid.v4(); export const getChildFieldsName = (dataType: DataType): ChildFieldName | undefined => { - if (dataType === 'text' || dataType === 'keyword') { + if (dataType === 'text' || dataType === 'keyword' || dataType === 'ip') { return 'fields'; } else if (dataType === 'object' || dataType === 'nested') { return 'properties'; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_editor.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_editor.tsx index cd2740d78df3f..b188b15b0fb72 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_editor.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_editor.tsx @@ -51,50 +51,48 @@ interface Props { } export const MappingsEditor = React.memo(({ onChange, value, docLinks, indexSettings }: Props) => { - const { - parsedDefaultValue, - multipleMappingsDeclared, - } = useMemo<MappingsEditorParsedMetadata>(() => { - const mappingsDefinition = extractMappingsDefinition(value); - - if (mappingsDefinition === null) { - return { multipleMappingsDeclared: true }; - } + const { parsedDefaultValue, multipleMappingsDeclared } = + useMemo<MappingsEditorParsedMetadata>(() => { + const mappingsDefinition = extractMappingsDefinition(value); + + if (mappingsDefinition === null) { + return { multipleMappingsDeclared: true }; + } - const { - _source, - _meta, - _routing, - dynamic, - properties, - runtime, - /* eslint-disable @typescript-eslint/naming-convention */ - numeric_detection, - date_detection, - dynamic_date_formats, - dynamic_templates, - /* eslint-enable @typescript-eslint/naming-convention */ - } = mappingsDefinition; - - const parsed = { - configuration: { + const { _source, _meta, _routing, dynamic, + properties, + runtime, + /* eslint-disable @typescript-eslint/naming-convention */ numeric_detection, date_detection, dynamic_date_formats, - }, - fields: properties, - templates: { dynamic_templates, - }, - runtime, - }; - - return { parsedDefaultValue: parsed, multipleMappingsDeclared: false }; - }, [value]); + /* eslint-enable @typescript-eslint/naming-convention */ + } = mappingsDefinition; + + const parsed = { + configuration: { + _source, + _meta, + _routing, + dynamic, + numeric_detection, + date_detection, + dynamic_date_formats, + }, + fields: properties, + templates: { + dynamic_templates, + }, + runtime, + }; + + return { parsedDefaultValue: parsed, multipleMappingsDeclared: false }; + }, [value]); /** * Hook that will listen to: diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/use_state_listener.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/use_state_listener.tsx index e7ace1aff3101..2314f269e540d 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/use_state_listener.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/use_state_listener.tsx @@ -41,9 +41,10 @@ export const useMappingsStateListener = ({ onChange, value }: Args) => { const { fields: mappedFields, runtime: runtimeFields } = value ?? {}; const parsedFieldsDefaultValue = useMemo(() => normalize(mappedFields), [mappedFields]); - const parsedRuntimeFieldsDefaultValue = useMemo(() => normalizeRuntimeFields(runtimeFields), [ - runtimeFields, - ]); + const parsedRuntimeFieldsDefaultValue = useMemo( + () => normalizeRuntimeFields(runtimeFields), + [runtimeFields] + ); useEffect(() => { // If we are creating a new field, but haven't entered any name diff --git a/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx b/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx index 4ccd77d275a94..a0362b440cb0b 100644 --- a/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx +++ b/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx @@ -196,22 +196,21 @@ export const TemplateForm = ({ }; const buildTemplateObject = useCallback( - (initialTemplate: TemplateDeserialized) => ( - wizardData: WizardContent - ): TemplateDeserialized => { - const outputTemplate = { - ...wizardData.logistics, - _kbnMeta: initialTemplate._kbnMeta, - composedOf: wizardData.components, - template: { - settings: wizardData.settings, - mappings: wizardData.mappings, - aliases: wizardData.aliases, - }, - }; - - return cleanupTemplateObject(outputTemplate); - }, + (initialTemplate: TemplateDeserialized) => + (wizardData: WizardContent): TemplateDeserialized => { + const outputTemplate = { + ...wizardData.logistics, + _kbnMeta: initialTemplate._kbnMeta, + composedOf: wizardData.components, + template: { + settings: wizardData.settings, + mappings: wizardData.mappings, + aliases: wizardData.aliases, + }, + }; + + return cleanupTemplateObject(outputTemplate); + }, [] ); diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx index 7bd7c163837d8..583d1fcb83ed4 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx @@ -61,7 +61,12 @@ export const DataStreamList: React.FunctionComponent<RouteComponentProps<MatchPa } = useAppContext(); const [isIncludeStatsChecked, setIsIncludeStatsChecked] = useState(false); - const { error, isLoading, data: dataStreams, resendRequest: reload } = useLoadDataStreams({ + const { + error, + isLoading, + data: dataStreams, + resendRequest: reload, + } = useLoadDataStreams({ includeStats: isIncludeStatsChecked, }); diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_actions_context_menu/index_actions_context_menu.js b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_actions_context_menu/index_actions_context_menu.js index 944992fbc9146..a97d5b11161b1 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_actions_context_menu/index_actions_context_menu.js +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_actions_context_menu/index_actions_context_menu.js @@ -217,12 +217,8 @@ export class IndexActionsContextMenu extends Component { getUrlForApp, }); if (actionExtensionDefinition) { - const { - buttonLabel, - requestMethod, - successMessage, - renderConfirmModal, - } = actionExtensionDefinition; + const { buttonLabel, requestMethod, successMessage, renderConfirmModal } = + actionExtensionDefinition; if (requestMethod) { items.push({ name: buttonLabel, diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js index 0a407927e3466..d40f04d03cc92 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js @@ -428,15 +428,8 @@ export class IndexTable extends Component { } render() { - const { - filter, - indices, - loadIndices, - indicesLoading, - indicesError, - allIndices, - pager, - } = this.props; + const { filter, indices, loadIndices, indicesLoading, indicesError, allIndices, pager } = + this.props; const { includeHiddenIndices } = this.readURLParams(); const hasContent = !indicesLoading && !indicesError; diff --git a/x-pack/plugins/index_management/public/application/sections/template_clone/template_clone.tsx b/x-pack/plugins/index_management/public/application/sections/template_clone/template_clone.tsx index 32c84bc3b15f1..bf71461554760 100644 --- a/x-pack/plugins/index_management/public/application/sections/template_clone/template_clone.tsx +++ b/x-pack/plugins/index_management/public/application/sections/template_clone/template_clone.tsx @@ -36,10 +36,11 @@ export const TemplateClone: React.FunctionComponent<RouteComponentProps<MatchPar const [isSaving, setIsSaving] = useState<boolean>(false); const [saveError, setSaveError] = useState<any>(null); - const { error: templateToCloneError, data: templateToClone, isLoading } = useLoadIndexTemplate( - decodedTemplateName, - isLegacy - ); + const { + error: templateToCloneError, + data: templateToClone, + isLoading, + } = useLoadIndexTemplate(decodedTemplateName, isLegacy); const onSave = async (template: TemplateDeserialized) => { setIsSaving(true); diff --git a/x-pack/plugins/index_management/public/application/store/actions/clear_cache_indices.js b/x-pack/plugins/index_management/public/application/store/actions/clear_cache_indices.js index 75dc762b8f894..179aa73b4f0a8 100644 --- a/x-pack/plugins/index_management/public/application/store/actions/clear_cache_indices.js +++ b/x-pack/plugins/index_management/public/application/store/actions/clear_cache_indices.js @@ -14,19 +14,21 @@ import { notificationService } from '../../services/notification'; import { clearRowStatus, reloadIndices } from '../actions'; export const clearCacheIndicesStart = createAction('INDEX_MANAGEMENT_CLEAR_CACHE_INDICES_START'); -export const clearCacheIndices = ({ indexNames }) => async (dispatch) => { - dispatch(clearCacheIndicesStart({ indexNames })); - try { - await request(indexNames); - } catch (error) { - notificationService.showDangerToast(error.message); - return dispatch(clearRowStatus({ indexNames })); - } - dispatch(reloadIndices(indexNames)); - notificationService.showSuccessToast( - i18n.translate('xpack.idxMgmt.clearCacheIndicesAction.successMessage', { - defaultMessage: 'Successfully cleared cache: [{indexNames}]', - values: { indexNames: indexNames.join(', ') }, - }) - ); -}; +export const clearCacheIndices = + ({ indexNames }) => + async (dispatch) => { + dispatch(clearCacheIndicesStart({ indexNames })); + try { + await request(indexNames); + } catch (error) { + notificationService.showDangerToast(error.message); + return dispatch(clearRowStatus({ indexNames })); + } + dispatch(reloadIndices(indexNames)); + notificationService.showSuccessToast( + i18n.translate('xpack.idxMgmt.clearCacheIndicesAction.successMessage', { + defaultMessage: 'Successfully cleared cache: [{indexNames}]', + values: { indexNames: indexNames.join(', ') }, + }) + ); + }; diff --git a/x-pack/plugins/index_management/public/application/store/actions/close_indices.js b/x-pack/plugins/index_management/public/application/store/actions/close_indices.js index 4ba2db666e009..1669d6b1b022f 100644 --- a/x-pack/plugins/index_management/public/application/store/actions/close_indices.js +++ b/x-pack/plugins/index_management/public/application/store/actions/close_indices.js @@ -12,19 +12,21 @@ import { notificationService } from '../../services/notification'; import { clearRowStatus, reloadIndices } from '../actions'; export const closeIndicesStart = createAction('INDEX_MANAGEMENT_CLOSE_INDICES_START'); -export const closeIndices = ({ indexNames }) => async (dispatch) => { - dispatch(closeIndicesStart({ indexNames })); - try { - await request(indexNames); - } catch (error) { - notificationService.showDangerToast(error.message); - return dispatch(clearRowStatus({ indexNames })); - } - dispatch(reloadIndices(indexNames)); - notificationService.showSuccessToast( - i18n.translate('xpack.idxMgmt.closeIndicesAction.successfullyClosedIndicesMessage', { - defaultMessage: 'Successfully closed: [{indexNames}]', - values: { indexNames: indexNames.join(', ') }, - }) - ); -}; +export const closeIndices = + ({ indexNames }) => + async (dispatch) => { + dispatch(closeIndicesStart({ indexNames })); + try { + await request(indexNames); + } catch (error) { + notificationService.showDangerToast(error.message); + return dispatch(clearRowStatus({ indexNames })); + } + dispatch(reloadIndices(indexNames)); + notificationService.showSuccessToast( + i18n.translate('xpack.idxMgmt.closeIndicesAction.successfullyClosedIndicesMessage', { + defaultMessage: 'Successfully closed: [{indexNames}]', + values: { indexNames: indexNames.join(', ') }, + }) + ); + }; diff --git a/x-pack/plugins/index_management/public/application/store/actions/delete_indices.js b/x-pack/plugins/index_management/public/application/store/actions/delete_indices.js index d886b35879d4f..307c359b0fe21 100644 --- a/x-pack/plugins/index_management/public/application/store/actions/delete_indices.js +++ b/x-pack/plugins/index_management/public/application/store/actions/delete_indices.js @@ -12,18 +12,20 @@ import { notificationService } from '../../services/notification'; import { clearRowStatus } from '../actions'; export const deleteIndicesSuccess = createAction('INDEX_MANAGEMENT_DELETE_INDICES_SUCCESS'); -export const deleteIndices = ({ indexNames }) => async (dispatch) => { - try { - await request(indexNames); - } catch (error) { - notificationService.showDangerToast(error.message); - return dispatch(clearRowStatus({ indexNames })); - } - notificationService.showSuccessToast( - i18n.translate('xpack.idxMgmt.deleteIndicesAction.successfullyDeletedIndicesMessage', { - defaultMessage: 'Successfully deleted: [{indexNames}]', - values: { indexNames: indexNames.join(', ') }, - }) - ); - dispatch(deleteIndicesSuccess({ indexNames })); -}; +export const deleteIndices = + ({ indexNames }) => + async (dispatch) => { + try { + await request(indexNames); + } catch (error) { + notificationService.showDangerToast(error.message); + return dispatch(clearRowStatus({ indexNames })); + } + notificationService.showSuccessToast( + i18n.translate('xpack.idxMgmt.deleteIndicesAction.successfullyDeletedIndicesMessage', { + defaultMessage: 'Successfully deleted: [{indexNames}]', + values: { indexNames: indexNames.join(', ') }, + }) + ); + dispatch(deleteIndicesSuccess({ indexNames })); + }; diff --git a/x-pack/plugins/index_management/public/application/store/actions/edit_index_settings.js b/x-pack/plugins/index_management/public/application/store/actions/edit_index_settings.js index 0ebb36f68311f..4a82ced82eb83 100644 --- a/x-pack/plugins/index_management/public/application/store/actions/edit_index_settings.js +++ b/x-pack/plugins/index_management/public/application/store/actions/edit_index_settings.js @@ -10,27 +10,29 @@ import { loadIndexSettings as request } from '../../services'; import { notificationService } from '../../services/notification'; import { loadIndexDataSuccess } from './load_index_data'; -export const editIndexSettings = ({ indexName }) => async (dispatch) => { - let indexSettings; - try { - indexSettings = await request(indexName); - } catch (error) { - return notificationService.showDangerToast(error.message); - } - notificationService.showSuccessToast( - i18n.translate( - 'xpack.idxMgmt.editIndexSettingsAction.successfullySavedSettingsForIndicesMessage', - { - defaultMessage: 'Successfully saved settings for {indexName}', - values: { indexName }, - } - ) - ); - dispatch( - loadIndexDataSuccess({ - data: indexSettings, - panelType: 'editIndexSettings', - indexName, - }) - ); -}; +export const editIndexSettings = + ({ indexName }) => + async (dispatch) => { + let indexSettings; + try { + indexSettings = await request(indexName); + } catch (error) { + return notificationService.showDangerToast(error.message); + } + notificationService.showSuccessToast( + i18n.translate( + 'xpack.idxMgmt.editIndexSettingsAction.successfullySavedSettingsForIndicesMessage', + { + defaultMessage: 'Successfully saved settings for {indexName}', + values: { indexName }, + } + ) + ); + dispatch( + loadIndexDataSuccess({ + data: indexSettings, + panelType: 'editIndexSettings', + indexName, + }) + ); + }; diff --git a/x-pack/plugins/index_management/public/application/store/actions/extension_action.js b/x-pack/plugins/index_management/public/application/store/actions/extension_action.js index 97937f67f9ae4..eda9d32d75fba 100644 --- a/x-pack/plugins/index_management/public/application/store/actions/extension_action.js +++ b/x-pack/plugins/index_management/public/application/store/actions/extension_action.js @@ -9,15 +9,15 @@ import { reloadIndices } from '../actions'; import { notificationService } from '../../services/notification'; import { httpService } from '../../services/http'; -export const performExtensionAction = ({ requestMethod, indexNames, successMessage }) => async ( - dispatch -) => { - try { - await requestMethod(indexNames, httpService.httpClient); - } catch (error) { - notificationService.showDangerToast(error.message); - return; - } - dispatch(reloadIndices(indexNames)); - notificationService.showSuccessToast(successMessage); -}; +export const performExtensionAction = + ({ requestMethod, indexNames, successMessage }) => + async (dispatch) => { + try { + await requestMethod(indexNames, httpService.httpClient); + } catch (error) { + notificationService.showDangerToast(error.message); + return; + } + dispatch(reloadIndices(indexNames)); + notificationService.showSuccessToast(successMessage); + }; diff --git a/x-pack/plugins/index_management/public/application/store/actions/flush_indices.js b/x-pack/plugins/index_management/public/application/store/actions/flush_indices.js index f3dae725947e1..364b87e9f2bb7 100644 --- a/x-pack/plugins/index_management/public/application/store/actions/flush_indices.js +++ b/x-pack/plugins/index_management/public/application/store/actions/flush_indices.js @@ -13,19 +13,21 @@ import { notificationService } from '../../services/notification'; export const flushIndicesStart = createAction('INDEX_MANAGEMENT_FLUSH_INDICES_START'); -export const flushIndices = ({ indexNames }) => async (dispatch) => { - dispatch(flushIndicesStart({ indexNames })); - try { - await request(indexNames); - } catch (error) { - notificationService.showDangerToast(error.message); - return dispatch(clearRowStatus({ indexNames })); - } - dispatch(reloadIndices(indexNames)); - notificationService.showSuccessToast( - i18n.translate('xpack.idxMgmt.flushIndicesAction.successfullyFlushedIndicesMessage', { - defaultMessage: 'Successfully flushed: [{indexNames}]', - values: { indexNames: indexNames.join(', ') }, - }) - ); -}; +export const flushIndices = + ({ indexNames }) => + async (dispatch) => { + dispatch(flushIndicesStart({ indexNames })); + try { + await request(indexNames); + } catch (error) { + notificationService.showDangerToast(error.message); + return dispatch(clearRowStatus({ indexNames })); + } + dispatch(reloadIndices(indexNames)); + notificationService.showSuccessToast( + i18n.translate('xpack.idxMgmt.flushIndicesAction.successfullyFlushedIndicesMessage', { + defaultMessage: 'Successfully flushed: [{indexNames}]', + values: { indexNames: indexNames.join(', ') }, + }) + ); + }; diff --git a/x-pack/plugins/index_management/public/application/store/actions/forcemerge_indices.js b/x-pack/plugins/index_management/public/application/store/actions/forcemerge_indices.js index 726e5a1fbed27..60f5da7d4925d 100644 --- a/x-pack/plugins/index_management/public/application/store/actions/forcemerge_indices.js +++ b/x-pack/plugins/index_management/public/application/store/actions/forcemerge_indices.js @@ -13,19 +13,24 @@ import { notificationService } from '../../services/notification'; export const forcemergeIndicesStart = createAction('INDEX_MANAGEMENT_FORCEMERGE_INDICES_START'); -export const forcemergeIndices = ({ indexNames, maxNumSegments }) => async (dispatch) => { - dispatch(forcemergeIndicesStart({ indexNames })); - try { - await request(indexNames, maxNumSegments); - } catch (error) { - notificationService.showDangerToast(error.message); - return dispatch(clearRowStatus({ indexNames })); - } - dispatch(reloadIndices(indexNames)); - notificationService.showSuccessToast( - i18n.translate('xpack.idxMgmt.forceMergeIndicesAction.successfullyForceMergedIndicesMessage', { - defaultMessage: 'Successfully force merged: [{indexNames}]', - values: { indexNames: indexNames.join(', ') }, - }) - ); -}; +export const forcemergeIndices = + ({ indexNames, maxNumSegments }) => + async (dispatch) => { + dispatch(forcemergeIndicesStart({ indexNames })); + try { + await request(indexNames, maxNumSegments); + } catch (error) { + notificationService.showDangerToast(error.message); + return dispatch(clearRowStatus({ indexNames })); + } + dispatch(reloadIndices(indexNames)); + notificationService.showSuccessToast( + i18n.translate( + 'xpack.idxMgmt.forceMergeIndicesAction.successfullyForceMergedIndicesMessage', + { + defaultMessage: 'Successfully force merged: [{indexNames}]', + values: { indexNames: indexNames.join(', ') }, + } + ) + ); + }; diff --git a/x-pack/plugins/index_management/public/application/store/actions/freeze_indices.js b/x-pack/plugins/index_management/public/application/store/actions/freeze_indices.js index f06239cc0d1b4..002b0c5f00c9d 100644 --- a/x-pack/plugins/index_management/public/application/store/actions/freeze_indices.js +++ b/x-pack/plugins/index_management/public/application/store/actions/freeze_indices.js @@ -13,19 +13,21 @@ import { notificationService } from '../../services/notification'; export const freezeIndicesStart = createAction('INDEX_MANAGEMENT_FREEZE_INDICES_START'); -export const freezeIndices = ({ indexNames }) => async (dispatch) => { - dispatch(freezeIndicesStart({ indexNames })); - try { - await request(indexNames); - } catch (error) { - notificationService.showDangerToast(error.message); - return dispatch(clearRowStatus({ indexNames })); - } - dispatch(reloadIndices(indexNames)); - notificationService.showSuccessToast( - i18n.translate('xpack.idxMgmt.freezeIndicesAction.successfullyFrozeIndicesMessage', { - defaultMessage: 'Successfully froze: [{indexNames}]', - values: { indexNames: indexNames.join(', ') }, - }) - ); -}; +export const freezeIndices = + ({ indexNames }) => + async (dispatch) => { + dispatch(freezeIndicesStart({ indexNames })); + try { + await request(indexNames); + } catch (error) { + notificationService.showDangerToast(error.message); + return dispatch(clearRowStatus({ indexNames })); + } + dispatch(reloadIndices(indexNames)); + notificationService.showSuccessToast( + i18n.translate('xpack.idxMgmt.freezeIndicesAction.successfullyFrozeIndicesMessage', { + defaultMessage: 'Successfully froze: [{indexNames}]', + values: { indexNames: indexNames.join(', ') }, + }) + ); + }; diff --git a/x-pack/plugins/index_management/public/application/store/actions/load_index_data.js b/x-pack/plugins/index_management/public/application/store/actions/load_index_data.js index 0fc37fa7cc98b..18925d6d05e64 100644 --- a/x-pack/plugins/index_management/public/application/store/actions/load_index_data.js +++ b/x-pack/plugins/index_management/public/application/store/actions/load_index_data.js @@ -11,12 +11,14 @@ import { notificationService } from '../../services/notification'; export const loadIndexDataSuccess = createAction('INDEX_MANAGEMENT_LOAD_INDEX_DATA_SUCCESS'); -export const loadIndexData = ({ indexName, dataType }) => async (dispatch) => { - let data; - try { - data = await request(dataType, indexName); - } catch (error) { - notificationService.showDangerToast(error.message); - } - dispatch(loadIndexDataSuccess({ data, indexName })); -}; +export const loadIndexData = + ({ indexName, dataType }) => + async (dispatch) => { + let data; + try { + data = await request(dataType, indexName); + } catch (error) { + notificationService.showDangerToast(error.message); + } + dispatch(loadIndexDataSuccess({ data, indexName })); + }; diff --git a/x-pack/plugins/index_management/public/application/store/actions/open_indices.js b/x-pack/plugins/index_management/public/application/store/actions/open_indices.js index 7c74f0ae870e9..73a4a3350b841 100644 --- a/x-pack/plugins/index_management/public/application/store/actions/open_indices.js +++ b/x-pack/plugins/index_management/public/application/store/actions/open_indices.js @@ -13,19 +13,21 @@ import { notificationService } from '../../services/notification'; export const openIndicesStart = createAction('INDEX_MANAGEMENT_OPEN_INDICES_START'); -export const openIndices = ({ indexNames }) => async (dispatch) => { - dispatch(openIndicesStart({ indexNames })); - try { - await request(indexNames); - } catch (error) { - notificationService.showDangerToast(error.message); - return dispatch(clearRowStatus({ indexNames })); - } - dispatch(reloadIndices(indexNames)); - notificationService.showSuccessToast( - i18n.translate('xpack.idxMgmt.openIndicesAction.successfullyOpenedIndicesMessage', { - defaultMessage: 'Successfully opened: [{indexNames}]', - values: { indexNames: indexNames.join(', ') }, - }) - ); -}; +export const openIndices = + ({ indexNames }) => + async (dispatch) => { + dispatch(openIndicesStart({ indexNames })); + try { + await request(indexNames); + } catch (error) { + notificationService.showDangerToast(error.message); + return dispatch(clearRowStatus({ indexNames })); + } + dispatch(reloadIndices(indexNames)); + notificationService.showSuccessToast( + i18n.translate('xpack.idxMgmt.openIndicesAction.successfullyOpenedIndicesMessage', { + defaultMessage: 'Successfully opened: [{indexNames}]', + values: { indexNames: indexNames.join(', ') }, + }) + ); + }; diff --git a/x-pack/plugins/index_management/public/application/store/actions/refresh_indices.js b/x-pack/plugins/index_management/public/application/store/actions/refresh_indices.js index 0f1162bacc814..77944344c47c7 100644 --- a/x-pack/plugins/index_management/public/application/store/actions/refresh_indices.js +++ b/x-pack/plugins/index_management/public/application/store/actions/refresh_indices.js @@ -13,19 +13,21 @@ import { clearRowStatus, reloadIndices } from '../actions'; import { notificationService } from '../../services/notification'; export const refreshIndicesStart = createAction('INDEX_MANAGEMENT_REFRESH_INDICES_START'); -export const refreshIndices = ({ indexNames }) => async (dispatch) => { - dispatch(refreshIndicesStart({ indexNames })); - try { - await request(indexNames); - } catch (error) { - notificationService.showDangerToast(error.message); - return dispatch(clearRowStatus({ indexNames })); - } - dispatch(reloadIndices(indexNames)); - notificationService.showSuccessToast( - i18n.translate('xpack.idxMgmt.refreshIndicesAction.successfullyRefreshedIndicesMessage', { - defaultMessage: 'Successfully refreshed: [{indexNames}]', - values: { indexNames: indexNames.join(', ') }, - }) - ); -}; +export const refreshIndices = + ({ indexNames }) => + async (dispatch) => { + dispatch(refreshIndicesStart({ indexNames })); + try { + await request(indexNames); + } catch (error) { + notificationService.showDangerToast(error.message); + return dispatch(clearRowStatus({ indexNames })); + } + dispatch(reloadIndices(indexNames)); + notificationService.showSuccessToast( + i18n.translate('xpack.idxMgmt.refreshIndicesAction.successfullyRefreshedIndicesMessage', { + defaultMessage: 'Successfully refreshed: [{indexNames}]', + values: { indexNames: indexNames.join(', ') }, + }) + ); + }; diff --git a/x-pack/plugins/index_management/public/application/store/actions/unfreeze_indices.js b/x-pack/plugins/index_management/public/application/store/actions/unfreeze_indices.js index 9b0220e33b175..96d38c48f240d 100644 --- a/x-pack/plugins/index_management/public/application/store/actions/unfreeze_indices.js +++ b/x-pack/plugins/index_management/public/application/store/actions/unfreeze_indices.js @@ -13,19 +13,21 @@ import { notificationService } from '../../services/notification'; export const unfreezeIndicesStart = createAction('INDEX_MANAGEMENT_UNFREEZE_INDICES_START'); -export const unfreezeIndices = ({ indexNames }) => async (dispatch) => { - dispatch(unfreezeIndicesStart({ indexNames })); - try { - await request(indexNames); - } catch (error) { - notificationService.showDangerToast(error.message); - return dispatch(clearRowStatus({ indexNames })); - } - dispatch(reloadIndices(indexNames)); - notificationService.showSuccessToast( - i18n.translate('xpack.idxMgmt.unfreezeIndicesAction.successfullyUnfrozeIndicesMessage', { - defaultMessage: 'Successfully unfroze: [{indexNames}]', - values: { indexNames: indexNames.join(', ') }, - }) - ); -}; +export const unfreezeIndices = + ({ indexNames }) => + async (dispatch) => { + dispatch(unfreezeIndicesStart({ indexNames })); + try { + await request(indexNames); + } catch (error) { + notificationService.showDangerToast(error.message); + return dispatch(clearRowStatus({ indexNames })); + } + dispatch(reloadIndices(indexNames)); + notificationService.showSuccessToast( + i18n.translate('xpack.idxMgmt.unfreezeIndicesAction.successfullyUnfrozeIndicesMessage', { + defaultMessage: 'Successfully unfroze: [{indexNames}]', + values: { indexNames: indexNames.join(', ') }, + }) + ); + }; diff --git a/x-pack/plugins/index_management/public/application/store/actions/update_index_settings.js b/x-pack/plugins/index_management/public/application/store/actions/update_index_settings.js index 5d2fd5353fe09..bbb251634976e 100644 --- a/x-pack/plugins/index_management/public/application/store/actions/update_index_settings.js +++ b/x-pack/plugins/index_management/public/application/store/actions/update_index_settings.js @@ -18,24 +18,26 @@ export const updateIndexSettingsError = createAction( 'INDEX_MANAGEMENT_UPDATE_INDEX_SETTINGS_ERROR' ); -export const updateIndexSettings = ({ indexName, settings }) => async (dispatch) => { - if (Object.keys(settings).length !== 0) { - try { - const { error, message } = await request(indexName, settings); +export const updateIndexSettings = + ({ indexName, settings }) => + async (dispatch) => { + if (Object.keys(settings).length !== 0) { + try { + const { error, message } = await request(indexName, settings); - if (error) { - return dispatch(updateIndexSettingsError({ error: message })); + if (error) { + return dispatch(updateIndexSettingsError({ error: message })); + } + } catch (error) { + return dispatch(updateIndexSettingsError({ error: error.message })); } - } catch (error) { - return dispatch(updateIndexSettingsError({ error: error.message })); } - } - dispatch(updateIndexSettingsSuccess()); - dispatch(reloadIndices([indexName])); - notificationService.showSuccessToast( - i18n.translate('xpack.idxMgmt.updateIndexSettingsAction.settingsSuccessUpdateMessage', { - defaultMessage: 'Successfully updated settings for index {indexName}', - values: { indexName }, - }) - ); -}; + dispatch(updateIndexSettingsSuccess()); + dispatch(reloadIndices([indexName])); + notificationService.showSuccessToast( + i18n.translate('xpack.idxMgmt.updateIndexSettingsAction.settingsSuccessUpdateMessage', { + defaultMessage: 'Successfully updated settings for index {indexName}', + values: { indexName }, + }) + ); + }; diff --git a/x-pack/plugins/index_management/server/index.ts b/x-pack/plugins/index_management/server/index.ts index 507401398a407..14b67e2ffd581 100644 --- a/x-pack/plugins/index_management/server/index.ts +++ b/x-pack/plugins/index_management/server/index.ts @@ -5,15 +5,16 @@ * 2.0. */ -import { PluginInitializerContext } from 'src/core/server'; +import { PluginInitializerContext, PluginConfigDescriptor } from 'src/core/server'; import { IndexMgmtServerPlugin } from './plugin'; import { configSchema } from './config'; export const plugin = (context: PluginInitializerContext) => new IndexMgmtServerPlugin(context); -export const config = { +export const config: PluginConfigDescriptor = { schema: configSchema, + deprecations: ({ deprecate }) => [deprecate('enabled', '8.0.0')], }; /** @public */ diff --git a/x-pack/plugins/index_management/server/routes/api/component_templates/register_privileges_route.test.ts b/x-pack/plugins/index_management/server/routes/api/component_templates/register_privileges_route.test.ts index fdbf0db89e689..2c5070ab846ad 100644 --- a/x-pack/plugins/index_management/server/routes/api/component_templates/register_privileges_route.test.ts +++ b/x-pack/plugins/index_management/server/routes/api/component_templates/register_privileges_route.test.ts @@ -19,7 +19,7 @@ const httpService = httpServiceMock.createSetupContract(); const mockedIndexDataEnricher = new IndexDataEnricher(); const mockRouteContext = ({ hasPrivileges }: { hasPrivileges: unknown }): RequestHandlerContext => { - const routeContextMock = ({ + const routeContextMock = { core: { elasticsearch: { client: { @@ -31,7 +31,7 @@ const mockRouteContext = ({ hasPrivileges }: { hasPrivileges: unknown }): Reques }, }, }, - } as unknown) as RequestHandlerContext; + } as unknown as RequestHandlerContext; return routeContextMock; }; diff --git a/x-pack/plugins/infra/common/formatters/index.ts b/x-pack/plugins/infra/common/formatters/index.ts index a4aeee8084824..372df5b28ca1b 100644 --- a/x-pack/plugins/infra/common/formatters/index.ts +++ b/x-pack/plugins/infra/common/formatters/index.ts @@ -25,13 +25,13 @@ export const FORMATTERS = { highPercision: formatHighPercision, }; -export const createFormatter = (format: InventoryFormatterType, template: string = '{{value}}') => ( - val: string | number -) => { - if (val == null) { - return ''; - } - const fmtFn = FORMATTERS[format]; - const value = fmtFn(Number(val)); - return template.replace(/{{value}}/g, value); -}; +export const createFormatter = + (format: InventoryFormatterType, template: string = '{{value}}') => + (val: string | number) => { + if (val == null) { + return ''; + } + const fmtFn = FORMATTERS[format]; + const value = fmtFn(Number(val)); + return template.replace(/{{value}}/g, value); + }; diff --git a/x-pack/plugins/infra/common/http_api/log_sources/patch_log_source_configuration.ts b/x-pack/plugins/infra/common/http_api/log_sources/patch_log_source_configuration.ts index 30f53bc256cd8..a16f0651e7e5d 100644 --- a/x-pack/plugins/infra/common/http_api/log_sources/patch_log_source_configuration.ts +++ b/x-pack/plugins/infra/common/http_api/log_sources/patch_log_source_configuration.ts @@ -44,7 +44,8 @@ export type PatchLogSourceConfigurationRequestBody = rt.TypeOf< * response */ -export const patchLogSourceConfigurationSuccessResponsePayloadRT = getLogSourceConfigurationSuccessResponsePayloadRT; +export const patchLogSourceConfigurationSuccessResponsePayloadRT = + getLogSourceConfigurationSuccessResponsePayloadRT; export type PatchLogSourceConfigurationSuccessResponsePayload = rt.TypeOf< typeof patchLogSourceConfigurationSuccessResponsePayloadRT diff --git a/x-pack/plugins/infra/common/inventory_models/create_tsvb_model.ts b/x-pack/plugins/infra/common/inventory_models/create_tsvb_model.ts index 4280ef40a8249..5c9b3beb31c7c 100644 --- a/x-pack/plugins/infra/common/inventory_models/create_tsvb_model.ts +++ b/x-pack/plugins/infra/common/inventory_models/create_tsvb_model.ts @@ -7,19 +7,21 @@ import { TSVBMetricModelCreator, TSVBMetricModel, TSVBSeries, InventoryMetric } from './types'; -export const createTSVBModel = ( - id: InventoryMetric, - requires: string[], - series: TSVBSeries[], - interval = '>=300s', - dropLastBucket = true -): TSVBMetricModelCreator => (timeField, indexPattern): TSVBMetricModel => ({ - id, - requires, - drop_last_bucket: dropLastBucket, - index_pattern: indexPattern, - interval, - time_field: timeField, - type: 'timeseries', - series, -}); +export const createTSVBModel = + ( + id: InventoryMetric, + requires: string[], + series: TSVBSeries[], + interval = '>=300s', + dropLastBucket = true + ): TSVBMetricModelCreator => + (timeField, indexPattern): TSVBMetricModel => ({ + id, + requires, + drop_last_bucket: dropLastBucket, + index_pattern: indexPattern, + interval, + time_field: timeField, + type: 'timeseries', + series, + }); diff --git a/x-pack/plugins/infra/common/inventory_models/shared/components/metrics_and_groupby_toolbar_items.tsx b/x-pack/plugins/infra/common/inventory_models/shared/components/metrics_and_groupby_toolbar_items.tsx index 691ebec3c829c..b881396963d17 100644 --- a/x-pack/plugins/infra/common/inventory_models/shared/components/metrics_and_groupby_toolbar_items.tsx +++ b/x-pack/plugins/infra/common/inventory_models/shared/components/metrics_and_groupby_toolbar_items.tsx @@ -34,9 +34,10 @@ export const MetricsAndGroupByToolbarItems = (props: Props) => { [props.metricTypes] ); - const groupByOptions = useMemo(() => props.groupByFields.map(toGroupByOpt), [ - props.groupByFields, - ]); + const groupByOptions = useMemo( + () => props.groupByFields.map(toGroupByOpt), + [props.groupByFields] + ); return ( <> diff --git a/x-pack/plugins/infra/common/log_sources/resolved_log_source_configuration.ts b/x-pack/plugins/infra/common/log_sources/resolved_log_source_configuration.ts index ee831d9a98eb9..9c41a216c7f92 100644 --- a/x-pack/plugins/infra/common/log_sources/resolved_log_source_configuration.ts +++ b/x-pack/plugins/infra/common/log_sources/resolved_log_source_configuration.ts @@ -106,9 +106,9 @@ const resolveKibanaIndexPatternReference = async ( const resolveRuntimeMappings = (indexPattern: IndexPattern): estypes.MappingRuntimeFields => { const { runtimeFields } = indexPattern.getComputedFields(); - const runtimeMappingsFromIndexPattern = (Object.entries(runtimeFields) as ObjectEntries< - typeof runtimeFields - >).reduce<estypes.MappingRuntimeFields>( + const runtimeMappingsFromIndexPattern = ( + Object.entries(runtimeFields) as ObjectEntries<typeof runtimeFields> + ).reduce<estypes.MappingRuntimeFields>( (accumulatedMappings, [runtimeFieldName, runtimeFieldSpec]) => ({ ...accumulatedMappings, [runtimeFieldName]: { diff --git a/x-pack/plugins/infra/common/runtime_types.ts b/x-pack/plugins/infra/common/runtime_types.ts index 43c1e3f520556..06df361b3de42 100644 --- a/x-pack/plugins/infra/common/runtime_types.ts +++ b/x-pack/plugins/infra/common/runtime_types.ts @@ -45,21 +45,25 @@ export const throwErrors = (createError: ErrorFactory) => (errors: Errors) => { throw createError(formatErrors(errors)); }; -export const decodeOrThrow = <DecodedValue, EncodedValue, InputValue>( - runtimeType: Type<DecodedValue, EncodedValue, InputValue>, - createError: ErrorFactory = createPlainError -) => (inputValue: InputValue) => - pipe(runtimeType.decode(inputValue), fold(throwErrors(createError), identity)); +export const decodeOrThrow = + <DecodedValue, EncodedValue, InputValue>( + runtimeType: Type<DecodedValue, EncodedValue, InputValue>, + createError: ErrorFactory = createPlainError + ) => + (inputValue: InputValue) => + pipe(runtimeType.decode(inputValue), fold(throwErrors(createError), identity)); type ValdidationResult<Value> = ReturnType<RouteValidationFunction<Value>>; -export const createValidationFunction = <DecodedValue, EncodedValue, InputValue>( - runtimeType: Type<DecodedValue, EncodedValue, InputValue> -): RouteValidationFunction<DecodedValue> => (inputValue, { badRequest, ok }) => - pipe( - runtimeType.decode(inputValue), - fold<Errors, DecodedValue, ValdidationResult<DecodedValue>>( - (errors: Errors) => badRequest(formatErrors(errors)), - (result: DecodedValue) => ok(result) - ) - ); +export const createValidationFunction = + <DecodedValue, EncodedValue, InputValue>( + runtimeType: Type<DecodedValue, EncodedValue, InputValue> + ): RouteValidationFunction<DecodedValue> => + (inputValue, { badRequest, ok }) => + pipe( + runtimeType.decode(inputValue), + fold<Errors, DecodedValue, ValdidationResult<DecodedValue>>( + (errors: Errors) => badRequest(formatErrors(errors)), + (result: DecodedValue) => ok(result) + ) + ); diff --git a/x-pack/plugins/infra/common/time/time_key.ts b/x-pack/plugins/infra/common/time/time_key.ts index 0647c6dcab42a..42b14625d22a9 100644 --- a/x-pack/plugins/infra/common/time/time_key.ts +++ b/x-pack/plugins/infra/common/time/time_key.ts @@ -54,10 +54,10 @@ export function compareTimeKeys( return timeComparison; } -export const compareToTimeKey = <Value>( - keyAccessor: (value: Value) => TimeKey, - compareValues?: Comparator -) => (value: Value, key: TimeKey) => compareTimeKeys(keyAccessor(value), key, compareValues); +export const compareToTimeKey = + <Value>(keyAccessor: (value: Value) => TimeKey, compareValues?: Comparator) => + (value: Value, key: TimeKey) => + compareTimeKeys(keyAccessor(value), key, compareValues); export const getIndexAtTimeKey = <Value>( keyAccessor: (value: Value) => TimeKey, diff --git a/x-pack/plugins/infra/common/utility_types.ts b/x-pack/plugins/infra/common/utility_types.ts index 1f26309973f0d..49d60c31a71ef 100644 --- a/x-pack/plugins/infra/common/utility_types.ts +++ b/x-pack/plugins/infra/common/utility_types.ts @@ -12,10 +12,9 @@ export type Pick3<T, K1 extends keyof T, K2 extends keyof T[K1], K3 extends keyo [P1 in K1]: { [P2 in K2]: { [P3 in K3]: T[K1][K2][P3] } }; }; -export type MandatoryProperty<T, Prop extends keyof T> = T & - { - [prop in Prop]-?: NonNullable<T[Prop]>; - }; +export type MandatoryProperty<T, Prop extends keyof T> = T & { + [prop in Prop]-?: NonNullable<T[Prop]>; +}; /** * Portions of below code are derived from https://github.com/tycho01/typical diff --git a/x-pack/plugins/infra/public/alerting/common/components/metrics_alert_dropdown.tsx b/x-pack/plugins/infra/public/alerting/common/components/metrics_alert_dropdown.tsx index 5cbd1909054af..b92144d37eaa1 100644 --- a/x-pack/plugins/infra/public/alerting/common/components/metrics_alert_dropdown.tsx +++ b/x-pack/plugins/infra/public/alerting/common/components/metrics_alert_dropdown.tsx @@ -26,9 +26,10 @@ export const MetricsAlertDropdown = () => { const [visibleFlyoutType, setVisibleFlyoutType] = useState<VisibleFlyoutType>(null); const uiCapabilities = useKibana().services.application?.capabilities; - const canCreateAlerts = useMemo(() => Boolean(uiCapabilities?.infrastructure?.save), [ - uiCapabilities, - ]); + const canCreateAlerts = useMemo( + () => Boolean(uiCapabilities?.infrastructure?.save), + [uiCapabilities] + ); const closeFlyout = useCallback(() => setVisibleFlyoutType(null), [setVisibleFlyoutType]); diff --git a/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx b/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx index 78005d9d87cf9..f72c9d4531424 100644 --- a/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx +++ b/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx @@ -119,9 +119,10 @@ export const Expressions: React.FC<Props> = (props) => { const [timeSize, setTimeSize] = useState<number | undefined>(1); const [timeUnit, setTimeUnit] = useState<Unit>('m'); - const derivedIndexPattern = useMemo(() => createDerivedIndexPattern(), [ - createDerivedIndexPattern, - ]); + const derivedIndexPattern = useMemo( + () => createDerivedIndexPattern(), + [createDerivedIndexPattern] + ); const updateParams = useCallback( (id, e: InventoryMetricConditions) => { @@ -436,16 +437,8 @@ export const ExpressionRow: React.FC<ExpressionRowProps> = (props) => { const [isExpanded, setRowState] = useState(true); const toggleRowState = useCallback(() => setRowState(!isExpanded), [isExpanded]); - const { - children, - setAlertParams, - expression, - errors, - expressionId, - remove, - canDelete, - fields, - } = props; + const { children, setAlertParams, expression, errors, expressionId, remove, canDelete, fields } = + props; const { metric, comparator = Comparator.GT, diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/editor.tsx b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/editor.tsx index 4eb0f3e8645cf..2664a0d6aa2b0 100644 --- a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/editor.tsx +++ b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/editor.tsx @@ -159,175 +159,177 @@ export const SourceStatusWrapper: React.FC = ({ children }) => { ); }; -export const Editor: React.FC< - AlertTypeParamsExpressionProps<PartialAlertParams, LogsContextMeta> -> = (props) => { - const { setAlertParams, alertParams, errors } = props; - const [hasSetDefaults, setHasSetDefaults] = useState<boolean>(false); - const { sourceId, resolvedSourceConfiguration } = useLogSourceContext(); - - const { - criteria: criteriaErrors, - threshold: thresholdErrors, - timeSizeUnit: timeSizeUnitErrors, - timeWindowSize: timeWindowSizeErrors, - } = useMemo(() => decodeOrThrow(errorsRT)(errors), [errors]); - - const supportedFields = useMemo(() => { - if (resolvedSourceConfiguration?.fields) { - return resolvedSourceConfiguration.fields.filter((field) => { - return (field.type === 'string' || field.type === 'number') && field.searchable; - }); - } else { - return []; - } - }, [resolvedSourceConfiguration]); - - const groupByFields = useMemo(() => { - if (resolvedSourceConfiguration?.fields) { - return resolvedSourceConfiguration.fields.filter((field) => { - return field.type === 'string' && field.aggregatable; - }); - } else { - return []; - } - }, [resolvedSourceConfiguration]); - - const updateThreshold = useCallback( - (thresholdParams) => { - const nextThresholdParams = { ...alertParams.count, ...thresholdParams }; - setAlertParams('count', nextThresholdParams); - }, - [alertParams.count, setAlertParams] - ); - - const updateCriteria = useCallback( - (criteria: PartialCriteriaType) => { - setAlertParams('criteria', criteria); - }, - [setAlertParams] - ); - - const updateTimeSize = useCallback( - (ts: number | undefined) => { - setAlertParams('timeSize', ts); - }, - [setAlertParams] - ); - - const updateTimeUnit = useCallback( - (tu: string) => { - if (timeUnitRT.is(tu)) { - setAlertParams('timeUnit', tu); +export const Editor: React.FC<AlertTypeParamsExpressionProps<PartialAlertParams, LogsContextMeta>> = + (props) => { + const { setAlertParams, alertParams, errors } = props; + const [hasSetDefaults, setHasSetDefaults] = useState<boolean>(false); + const { sourceId, resolvedSourceConfiguration } = useLogSourceContext(); + + const { + criteria: criteriaErrors, + threshold: thresholdErrors, + timeSizeUnit: timeSizeUnitErrors, + timeWindowSize: timeWindowSizeErrors, + } = useMemo(() => decodeOrThrow(errorsRT)(errors), [errors]); + + const supportedFields = useMemo(() => { + if (resolvedSourceConfiguration?.fields) { + return resolvedSourceConfiguration.fields.filter((field) => { + return (field.type === 'string' || field.type === 'number') && field.searchable; + }); + } else { + return []; } - }, - [setAlertParams] - ); - - const updateGroupBy = useCallback( - (groups: string[]) => { - setAlertParams('groupBy', groups); - }, - [setAlertParams] - ); - - const defaultCountAlertParams = useMemo(() => createDefaultCountAlertParams(supportedFields), [ - supportedFields, - ]); - - const updateType = useCallback( - (type: ThresholdType) => { - const defaults = - type === 'count' ? defaultCountAlertParams : createDefaultRatioAlertParams(supportedFields); - // Reset properties that don't make sense switching from one context to the other - setAlertParams('count', defaults.count); - setAlertParams('criteria', defaults.criteria); - }, - [defaultCountAlertParams, setAlertParams, supportedFields] - ); + }, [resolvedSourceConfiguration]); + + const groupByFields = useMemo(() => { + if (resolvedSourceConfiguration?.fields) { + return resolvedSourceConfiguration.fields.filter((field) => { + return field.type === 'string' && field.aggregatable; + }); + } else { + return []; + } + }, [resolvedSourceConfiguration]); + + const updateThreshold = useCallback( + (thresholdParams) => { + const nextThresholdParams = { ...alertParams.count, ...thresholdParams }; + setAlertParams('count', nextThresholdParams); + }, + [alertParams.count, setAlertParams] + ); - useMount(() => { - const newAlertParams = { ...defaultCountAlertParams, ...alertParams }; - for (const [key, value] of Object.entries(newAlertParams) as ObjectEntries< - typeof newAlertParams - >) { - setAlertParams(key, value); - } - setHasSetDefaults(true); - }); + const updateCriteria = useCallback( + (criteria: PartialCriteriaType) => { + setAlertParams('criteria', criteria); + }, + [setAlertParams] + ); - const shouldShowGroupByOptimizationWarning = useMemo(() => { - const hasSetGroupBy = alertParams.groupBy && alertParams.groupBy.length > 0; - return ( - hasSetGroupBy && - alertParams.count && - !isOptimizableGroupedThreshold(alertParams.count.comparator, alertParams.count.value) + const updateTimeSize = useCallback( + (ts: number | undefined) => { + setAlertParams('timeSize', ts); + }, + [setAlertParams] ); - }, [alertParams]); - - // Wait until the alert param defaults have been set - if (!hasSetDefaults) return null; - - const criteriaComponent = alertParams.criteria ? ( - <Criteria - fields={supportedFields} - criteria={alertParams.criteria} - defaultCriterion={defaultCountAlertParams.criteria[0]} - errors={criteriaErrors} - alertParams={alertParams} - sourceId={sourceId} - updateCriteria={updateCriteria} - /> - ) : null; - return ( - <> - <TypeSwitcher criteria={alertParams.criteria || []} updateType={updateType} /> + const updateTimeUnit = useCallback( + (tu: string) => { + if (timeUnitRT.is(tu)) { + setAlertParams('timeUnit', tu); + } + }, + [setAlertParams] + ); - {alertParams.criteria && !isRatioAlert(alertParams.criteria) && criteriaComponent} + const updateGroupBy = useCallback( + (groups: string[]) => { + setAlertParams('groupBy', groups); + }, + [setAlertParams] + ); - <Threshold - comparator={alertParams.count?.comparator} - value={alertParams.count?.value} - updateThreshold={updateThreshold} - errors={thresholdErrors} - /> + const defaultCountAlertParams = useMemo( + () => createDefaultCountAlertParams(supportedFields), + [supportedFields] + ); - <ForLastExpression - timeWindowSize={alertParams.timeSize} - timeWindowUnit={alertParams.timeUnit} - onChangeWindowSize={updateTimeSize} - onChangeWindowUnit={updateTimeUnit} - errors={{ timeWindowSize: timeWindowSizeErrors, timeSizeUnit: timeSizeUnitErrors }} - /> + const updateType = useCallback( + (type: ThresholdType) => { + const defaults = + type === 'count' + ? defaultCountAlertParams + : createDefaultRatioAlertParams(supportedFields); + // Reset properties that don't make sense switching from one context to the other + setAlertParams('count', defaults.count); + setAlertParams('criteria', defaults.criteria); + }, + [defaultCountAlertParams, setAlertParams, supportedFields] + ); - <GroupByExpression - selectedGroups={alertParams.groupBy} - onChange={updateGroupBy} - fields={groupByFields} + useMount(() => { + const newAlertParams = { ...defaultCountAlertParams, ...alertParams }; + for (const [key, value] of Object.entries(newAlertParams) as ObjectEntries< + typeof newAlertParams + >) { + setAlertParams(key, value); + } + setHasSetDefaults(true); + }); + + const shouldShowGroupByOptimizationWarning = useMemo(() => { + const hasSetGroupBy = alertParams.groupBy && alertParams.groupBy.length > 0; + return ( + hasSetGroupBy && + alertParams.count && + !isOptimizableGroupedThreshold(alertParams.count.comparator, alertParams.count.value) + ); + }, [alertParams]); + + // Wait until the alert param defaults have been set + if (!hasSetDefaults) return null; + + const criteriaComponent = alertParams.criteria ? ( + <Criteria + fields={supportedFields} + criteria={alertParams.criteria} + defaultCriterion={defaultCountAlertParams.criteria[0]} + errors={criteriaErrors} + alertParams={alertParams} + sourceId={sourceId} + updateCriteria={updateCriteria} /> + ) : null; - {alertParams.criteria && isRatioAlert(alertParams.criteria) && criteriaComponent} - - {shouldShowGroupByOptimizationWarning && ( - <> - <EuiSpacer size="l" /> - <EuiCallOut color="warning"> - {i18n.translate('xpack.infra.logs.alertFlyout.groupByOptimizationWarning', { - defaultMessage: - 'When setting a "group by" we highly recommend using the "{comparator}" comparator for your threshold. This can lead to significant performance improvements.', - values: { - comparator: Comparator.GT, - }, - })} - </EuiCallOut> - </> - )} - - <EuiSpacer size="l" /> - </> - ); -}; + return ( + <> + <TypeSwitcher criteria={alertParams.criteria || []} updateType={updateType} /> + + {alertParams.criteria && !isRatioAlert(alertParams.criteria) && criteriaComponent} + + <Threshold + comparator={alertParams.count?.comparator} + value={alertParams.count?.value} + updateThreshold={updateThreshold} + errors={thresholdErrors} + /> + + <ForLastExpression + timeWindowSize={alertParams.timeSize} + timeWindowUnit={alertParams.timeUnit} + onChangeWindowSize={updateTimeSize} + onChangeWindowUnit={updateTimeUnit} + errors={{ timeWindowSize: timeWindowSizeErrors, timeSizeUnit: timeSizeUnitErrors }} + /> + + <GroupByExpression + selectedGroups={alertParams.groupBy} + onChange={updateGroupBy} + fields={groupByFields} + /> + + {alertParams.criteria && isRatioAlert(alertParams.criteria) && criteriaComponent} + + {shouldShowGroupByOptimizationWarning && ( + <> + <EuiSpacer size="l" /> + <EuiCallOut color="warning"> + {i18n.translate('xpack.infra.logs.alertFlyout.groupByOptimizationWarning', { + defaultMessage: + 'When setting a "group by" we highly recommend using the "{comparator}" comparator for your threshold. This can lead to significant performance improvements.', + values: { + comparator: Comparator.GT, + }, + })} + </EuiCallOut> + </> + )} + + <EuiSpacer size="l" /> + </> + ); + }; // required for dynamic import // eslint-disable-next-line import/no-default-export diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/hooks/use_chart_preview_data.tsx b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/hooks/use_chart_preview_data.tsx index f97ad64adedd4..42f826d09aca8 100644 --- a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/hooks/use_chart_preview_data.tsx +++ b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/hooks/use_chart_preview_data.tsx @@ -49,9 +49,10 @@ export const useChartPreviewData = ({ sourceId, alertParams, buckets }: Options) [sourceId, http, alertParams, buckets] ); - const isLoading = useMemo(() => getChartPreviewDataRequest.state === 'pending', [ - getChartPreviewDataRequest.state, - ]); + const isLoading = useMemo( + () => getChartPreviewDataRequest.state === 'pending', + [getChartPreviewDataRequest.state] + ); return { chartPreviewData, diff --git a/x-pack/plugins/infra/public/alerting/metric_anomaly/components/expression.tsx b/x-pack/plugins/infra/public/alerting/metric_anomaly/components/expression.tsx index 9f5c47554ac56..5c821c89ad317 100644 --- a/x-pack/plugins/infra/public/alerting/metric_anomaly/components/expression.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_anomaly/components/expression.tsx @@ -66,9 +66,10 @@ export const Expression: React.FC<Props> = (props) => { toastWarning: notifications.toasts.addWarning, }); - const derivedIndexPattern = useMemo(() => createDerivedIndexPattern(), [ - createDerivedIndexPattern, - ]); + const derivedIndexPattern = useMemo( + () => createDerivedIndexPattern(), + [createDerivedIndexPattern] + ); const [influencerFieldName, updateInfluencerFieldName] = useState( alertParams.influencerFilter?.fieldName ?? 'host.name' diff --git a/x-pack/plugins/infra/public/alerting/metric_anomaly/components/influencer_filter.tsx b/x-pack/plugins/infra/public/alerting/metric_anomaly/components/influencer_filter.tsx index 34a917a77dcf5..fa149ab12fd63 100644 --- a/x-pack/plugins/infra/public/alerting/metric_anomaly/components/influencer_filter.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_anomaly/components/influencer_filter.tsx @@ -35,9 +35,10 @@ export const InfluencerFilter = ({ onChangeFieldValue, derivedIndexPattern, }: Props) => { - const fieldNameOptions = useMemo(() => (nodeType === 'k8s' ? k8sFieldNames : hostFieldNames), [ - nodeType, - ]); + const fieldNameOptions = useMemo( + () => (nodeType === 'k8s' ? k8sFieldNames : hostFieldNames), + [nodeType] + ); // If initial props contain a fieldValue, assume it was passed in from loaded alertParams, // and enable the UI element @@ -52,9 +53,10 @@ export const InfluencerFilter = ({ [nodeType, onChangeFieldName] ); - const onSelectFieldName = useCallback((e) => onChangeFieldName(e.target.value), [ - onChangeFieldName, - ]); + const onSelectFieldName = useCallback( + (e) => onChangeFieldName(e.target.value), + [onChangeFieldName] + ); const onUpdateFieldValue = useCallback( (value) => { updateStoredFieldValue(value); @@ -79,32 +81,29 @@ export const InfluencerFilter = ({ [onUpdateFieldValue] ); - const affixFieldNameToQuery: CurryLoadSuggestionsType = (fn) => ( - expression, - cursorPosition, - maxSuggestions - ) => { - // Add the field name to the front of the passed-in query - const prefix = `${fieldName}:`; - // Trim whitespace to prevent AND/OR suggestions - const modifiedExpression = `${prefix}${expression}`.trim(); - // Move the cursor position forward by the length of the field name - const modifiedPosition = cursorPosition + prefix.length; - return fn(modifiedExpression, modifiedPosition, maxSuggestions, (suggestions) => - suggestions - .map((s) => ({ - ...s, - // Remove quotes from suggestions - text: s.text.replace(/\"/g, '').trim(), - // Offset the returned suggestions' cursor positions so that they can be autocompleted accurately - start: s.start - prefix.length, - end: s.end - prefix.length, - })) - // Removing quotes can lead to an already-selected suggestion still coming up in the autocomplete list, - // so filter these out - .filter((s) => !expression.startsWith(s.text)) - ); - }; + const affixFieldNameToQuery: CurryLoadSuggestionsType = + (fn) => (expression, cursorPosition, maxSuggestions) => { + // Add the field name to the front of the passed-in query + const prefix = `${fieldName}:`; + // Trim whitespace to prevent AND/OR suggestions + const modifiedExpression = `${prefix}${expression}`.trim(); + // Move the cursor position forward by the length of the field name + const modifiedPosition = cursorPosition + prefix.length; + return fn(modifiedExpression, modifiedPosition, maxSuggestions, (suggestions) => + suggestions + .map((s) => ({ + ...s, + // Remove quotes from suggestions + text: s.text.replace(/\"/g, '').trim(), + // Offset the returned suggestions' cursor positions so that they can be autocompleted accurately + start: s.start - prefix.length, + end: s.end - prefix.length, + })) + // Removing quotes can lead to an already-selected suggestion still coming up in the autocomplete list, + // so filter these out + .filter((s) => !expression.startsWith(s.text)) + ); + }; return ( <EuiFormRow diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx index 58bc476f151bd..c6fbbb2481ea1 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx @@ -67,9 +67,10 @@ export const Expressions: React.FC<Props> = (props) => { const [timeSize, setTimeSize] = useState<number | undefined>(1); const [timeUnit, setTimeUnit] = useState<Unit | undefined>('m'); - const derivedIndexPattern = useMemo(() => createDerivedIndexPattern(), [ - createDerivedIndexPattern, - ]); + const derivedIndexPattern = useMemo( + () => createDerivedIndexPattern(), + [createDerivedIndexPattern] + ); const options = useMemo<MetricsExplorerOptions>(() => { if (metadata?.currentOptions?.metrics) { diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_row.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_row.tsx index f6618603388d0..4dd191313261b 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_row.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_row.tsx @@ -74,16 +74,8 @@ const StyledHealth = euiStyled(EuiHealth)` export const ExpressionRow: React.FC<ExpressionRowProps> = (props) => { const [isExpanded, setRowState] = useState(true); const toggleRowState = useCallback(() => setRowState(!isExpanded), [isExpanded]); - const { - children, - setAlertParams, - expression, - errors, - expressionId, - remove, - fields, - canDelete, - } = props; + const { children, setAlertParams, expression, errors, expressionId, remove, fields, canDelete } = + props; const { aggType = AGGREGATION_TYPES.MAX, metric, diff --git a/x-pack/plugins/infra/public/components/autocomplete_field/autocomplete_field.tsx b/x-pack/plugins/infra/public/components/autocomplete_field/autocomplete_field.tsx index fbb883b228088..0e3e2018c963b 100644 --- a/x-pack/plugins/infra/public/components/autocomplete_field/autocomplete_field.tsx +++ b/x-pack/plugins/infra/public/components/autocomplete_field/autocomplete_field.tsx @@ -265,18 +265,17 @@ const withNextSuggestionSelected = ( : 0, }); -const withSuggestionAtIndexSelected = (suggestionIndex: number) => ( - state: AutocompleteFieldState, - props: AutocompleteFieldProps -): AutocompleteFieldState => ({ - ...state, - selectedIndex: - props.suggestions.length === 0 - ? null - : suggestionIndex >= 0 && suggestionIndex < props.suggestions.length - ? suggestionIndex - : 0, -}); +const withSuggestionAtIndexSelected = + (suggestionIndex: number) => + (state: AutocompleteFieldState, props: AutocompleteFieldProps): AutocompleteFieldState => ({ + ...state, + selectedIndex: + props.suggestions.length === 0 + ? null + : suggestionIndex >= 0 && suggestionIndex < props.suggestions.length + ? suggestionIndex + : 0, + }); const withSuggestionsVisible = (state: AutocompleteFieldState) => ({ ...state, diff --git a/x-pack/plugins/infra/public/components/basic_table/row_expansion_button.tsx b/x-pack/plugins/infra/public/components/basic_table/row_expansion_button.tsx index 76e7280acd080..7b046e9f2d252 100644 --- a/x-pack/plugins/infra/public/components/basic_table/row_expansion_button.tsx +++ b/x-pack/plugins/infra/public/components/basic_table/row_expansion_button.tsx @@ -20,12 +20,10 @@ export const RowExpansionButton = <Item extends any>({ onCollapse: (item: Item) => void; onExpand: (item: Item) => void; }) => { - const handleClick = useCallback(() => (isExpanded ? onCollapse(item) : onExpand(item)), [ - isExpanded, - item, - onCollapse, - onExpand, - ]); + const handleClick = useCallback( + () => (isExpanded ? onCollapse(item) : onExpand(item)), + [isExpanded, item, onCollapse, onExpand] + ); return ( <EuiButtonIcon diff --git a/x-pack/plugins/infra/public/components/formatted_time.tsx b/x-pack/plugins/infra/public/components/formatted_time.tsx index 46c13bb667250..6c7309d814af4 100644 --- a/x-pack/plugins/infra/public/components/formatted_time.tsx +++ b/x-pack/plugins/infra/public/components/formatted_time.tsx @@ -39,11 +39,10 @@ export const useFormattedTime = ( }; const dateFormat = formatMap[format]; - const formattedTime = useMemo(() => getFormattedTime(time, dateFormat, fallbackFormat), [ - time, - dateFormat, - fallbackFormat, - ]); + const formattedTime = useMemo( + () => getFormattedTime(time, dateFormat, fallbackFormat), + [time, dateFormat, fallbackFormat] + ); return formattedTime; }; diff --git a/x-pack/plugins/infra/public/components/log_stream/log_stream_embeddable_factory.ts b/x-pack/plugins/infra/public/components/log_stream/log_stream_embeddable_factory.ts index 1c7e8ceb28fb4..15500dacf12d9 100644 --- a/x-pack/plugins/infra/public/components/log_stream/log_stream_embeddable_factory.ts +++ b/x-pack/plugins/infra/public/components/log_stream/log_stream_embeddable_factory.ts @@ -19,7 +19,8 @@ import { } from './log_stream_embeddable'; export class LogStreamEmbeddableFactoryDefinition - implements EmbeddableFactoryDefinition<LogStreamEmbeddableInput> { + implements EmbeddableFactoryDefinition<LogStreamEmbeddableInput> +{ public readonly type = LOG_STREAM_EMBEDDABLE; constructor(private getStartServices: StartServicesAccessor<InfraClientStartDeps>) {} diff --git a/x-pack/plugins/infra/public/components/logging/log_analysis_setup/setup_flyout/module_list.tsx b/x-pack/plugins/infra/public/components/logging/log_analysis_setup/setup_flyout/module_list.tsx index 969838284f1aa..0ccd208cf24bd 100644 --- a/x-pack/plugins/infra/public/components/logging/log_analysis_setup/setup_flyout/module_list.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_analysis_setup/setup_flyout/module_list.tsx @@ -23,14 +23,10 @@ export const LogAnalysisModuleList: React.FC<{ onViewModuleSetup: (module: ModuleId) => void; }> = ({ onViewModuleSetup }) => { const { hasLogAnalysisSetupCapabilities } = useLogAnalysisCapabilitiesContext(); - const { - setupStatus: logEntryRateSetupStatus, - jobIds: logEntryRateJobIds, - } = useLogEntryRateModuleContext(); - const { - setupStatus: logEntryCategoriesSetupStatus, - jobIds: logEntryCategoriesJobIds, - } = useLogEntryCategoriesModuleContext(); + const { setupStatus: logEntryRateSetupStatus, jobIds: logEntryRateJobIds } = + useLogEntryRateModuleContext(); + const { setupStatus: logEntryCategoriesSetupStatus, jobIds: logEntryCategoriesJobIds } = + useLogEntryCategoriesModuleContext(); const viewLogEntryRateSetupFlyout = useCallback(() => { onViewModuleSetup('logs_ui_analysis'); diff --git a/x-pack/plugins/infra/public/components/logging/log_analysis_setup/setup_flyout/setup_flyout.tsx b/x-pack/plugins/infra/public/components/logging/log_analysis_setup/setup_flyout/setup_flyout.tsx index 4a744eafaf5f9..77ebb9084dbe3 100644 --- a/x-pack/plugins/infra/public/components/logging/log_analysis_setup/setup_flyout/setup_flyout.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_analysis_setup/setup_flyout/setup_flyout.tsx @@ -26,12 +26,8 @@ const FLYOUT_HEADING_ID = 'logAnalysisSetupFlyoutHeading'; export const LogAnalysisSetupFlyout: React.FC<{ allowedModules?: ModuleId[]; }> = ({ allowedModules = moduleIds }) => { - const { - closeFlyout, - flyoutView, - showModuleList, - showModuleSetup, - } = useLogAnalysisSetupFlyoutStateContext(); + const { closeFlyout, flyoutView, showModuleList, showModuleSetup } = + useLogAnalysisSetupFlyoutStateContext(); if (flyoutView.view === 'hidden') { return null; diff --git a/x-pack/plugins/infra/public/components/logging/log_analysis_setup/setup_flyout/setup_flyout_state.ts b/x-pack/plugins/infra/public/components/logging/log_analysis_setup/setup_flyout/setup_flyout_state.ts index 4f73781a07d77..8167b50ab6021 100644 --- a/x-pack/plugins/infra/public/components/logging/log_analysis_setup/setup_flyout/setup_flyout_state.ts +++ b/x-pack/plugins/infra/public/components/logging/log_analysis_setup/setup_flyout/setup_flyout_state.ts @@ -42,7 +42,5 @@ export const useLogAnalysisSetupFlyoutState = ({ }; }; -export const [ - LogAnalysisSetupFlyoutStateProvider, - useLogAnalysisSetupFlyoutStateContext, -] = createContainer(useLogAnalysisSetupFlyoutState); +export const [LogAnalysisSetupFlyoutStateProvider, useLogAnalysisSetupFlyoutStateContext] = + createContainer(useLogAnalysisSetupFlyoutState); diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_row.tsx b/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_row.tsx index 5ba2ddf736059..c871011d0f01a 100644 --- a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_row.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_row.tsx @@ -80,10 +80,10 @@ export const LogEntryRow = memo( const setItemIsHovered = useCallback(() => setIsHovered(true), []); const setItemIsNotHovered = useCallback(() => setIsHovered(false), []); - const openFlyout = useCallback(() => openFlyoutWithItem?.(logEntry.id), [ - openFlyoutWithItem, - logEntry.id, - ]); + const openFlyout = useCallback( + () => openFlyoutWithItem?.(logEntry.id), + [openFlyoutWithItem, logEntry.id] + ); const handleOpenViewLogInContext = useCallback(() => { openViewLogInContext?.(logEntry); diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/text_styles.tsx b/x-pack/plugins/infra/public/components/logging/log_text_stream/text_styles.tsx index 56c6698935b3e..d1330bdb44b33 100644 --- a/x-pack/plugins/infra/public/components/logging/log_text_stream/text_styles.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_text_stream/text_styles.tsx @@ -77,11 +77,12 @@ export const useMeasuredCharacterDimensions = (scale: TextScale) => { }, []); const CharacterDimensionsProbe = useMemo( - () => () => ( - <MonospaceCharacterDimensionsProbe scale={scale} ref={measureElement}> - X - </MonospaceCharacterDimensionsProbe> - ), + () => () => + ( + <MonospaceCharacterDimensionsProbe scale={scale} ref={measureElement}> + X + </MonospaceCharacterDimensionsProbe> + ), [measureElement, scale] ); diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/vertical_scroll_panel.tsx b/x-pack/plugins/infra/public/components/logging/log_text_stream/vertical_scroll_panel.tsx index 123455ecab2c8..7e13f8b4131fe 100644 --- a/x-pack/plugins/infra/public/components/logging/log_text_stream/vertical_scroll_panel.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_text_stream/vertical_scroll_panel.tsx @@ -300,5 +300,6 @@ const getVisibleChildren = <Child extends {}>( }; }; -const getChildIndexBefore = bisector<[any, Rect], number>(([key, rect]) => rect.top + rect.height) - .left; +const getChildIndexBefore = bisector<[any, Rect], number>( + ([key, rect]) => rect.top + rect.height +).left; diff --git a/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_capabilities.tsx b/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_capabilities.tsx index 26a2e19671da3..db519b4c795eb 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_capabilities.tsx +++ b/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_capabilities.tsx @@ -17,9 +17,8 @@ import { useKibanaContextForPlugin } from '../../../hooks/use_kibana'; export const useLogAnalysisCapabilities = () => { const { services } = useKibanaContextForPlugin(); - const [mlCapabilities, setMlCapabilities] = useState<GetMlCapabilitiesResponsePayload>( - initialMlCapabilities - ); + const [mlCapabilities, setMlCapabilities] = + useState<GetMlCapabilitiesResponsePayload>(initialMlCapabilities); const [fetchMlCapabilitiesRequest, fetchMlCapabilities] = useTrackedPromise( { @@ -40,9 +39,10 @@ export const useLogAnalysisCapabilities = () => { fetchMlCapabilities(); }, [fetchMlCapabilities]); - const isLoading = useMemo(() => fetchMlCapabilitiesRequest.state === 'pending', [ - fetchMlCapabilitiesRequest.state, - ]); + const isLoading = useMemo( + () => fetchMlCapabilitiesRequest.state === 'pending', + [fetchMlCapabilitiesRequest.state] + ); const hasLogAnalysisSetupCapabilities = mlCapabilities.capabilities.canCreateJob; const hasLogAnalysisReadCapabilities = mlCapabilities.capabilities.canGetJobs; diff --git a/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_module.tsx b/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_module.tsx index a9ea7e6d6e39a..d844b7439710c 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_module.tsx +++ b/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_module.tsx @@ -127,9 +127,10 @@ export const useLogAnalysisModule = <JobType extends string>({ [spaceId, sourceId] ); - const isCleaningUp = useMemo(() => cleanUpModuleRequest.state === 'pending', [ - cleanUpModuleRequest.state, - ]); + const isCleaningUp = useMemo( + () => cleanUpModuleRequest.state === 'pending', + [cleanUpModuleRequest.state] + ); const cleanUpAndSetUpModule = useCallback( ( @@ -154,11 +155,10 @@ export const useLogAnalysisModule = <JobType extends string>({ dispatchModuleStatus({ type: 'viewedResults' }); }, [dispatchModuleStatus]); - const jobIds = useMemo(() => moduleDescriptor.getJobIds(spaceId, sourceId), [ - moduleDescriptor, - spaceId, - sourceId, - ]); + const jobIds = useMemo( + () => moduleDescriptor.getJobIds(spaceId, sourceId), + [moduleDescriptor, spaceId, sourceId] + ); return { cleanUpAndSetUpModule, diff --git a/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_module_configuration.ts b/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_module_configuration.ts index ae58fb91b8881..057580679210a 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_module_configuration.ts +++ b/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_module_configuration.ts @@ -27,33 +27,35 @@ export const useLogAnalysisModuleConfiguration = <JobType extends string>({ }; }; -export const isJobConfigurationOutdated = <JobType extends string>( - { bucketSpan }: ModuleDescriptor<JobType>, - currentSourceConfiguration: ModuleSourceConfiguration -) => (jobSummary: JobSummary): boolean => { - if ( - !jobSummary.fullJob || - !jobSummary.fullJob.custom_settings || - !jobSummary.fullJob.datafeed_config - ) { - return false; - } +export const isJobConfigurationOutdated = + <JobType extends string>( + { bucketSpan }: ModuleDescriptor<JobType>, + currentSourceConfiguration: ModuleSourceConfiguration + ) => + (jobSummary: JobSummary): boolean => { + if ( + !jobSummary.fullJob || + !jobSummary.fullJob.custom_settings || + !jobSummary.fullJob.datafeed_config + ) { + return false; + } - const jobConfiguration = jobSummary.fullJob.custom_settings.logs_source_config; - const datafeedRuntimeMappings = jobSummary.fullJob.datafeed_config.runtime_mappings ?? {}; + const jobConfiguration = jobSummary.fullJob.custom_settings.logs_source_config; + const datafeedRuntimeMappings = jobSummary.fullJob.datafeed_config.runtime_mappings ?? {}; - return !( - jobConfiguration && - jobConfiguration.bucketSpan === bucketSpan && - jobConfiguration.indexPattern && - isSubset( - new Set(jobConfiguration.indexPattern.split(',')), - new Set(currentSourceConfiguration.indices) - ) && - jobConfiguration.timestampField === currentSourceConfiguration.timestampField && - equal(datafeedRuntimeMappings, currentSourceConfiguration.runtimeMappings) - ); -}; + return !( + jobConfiguration && + jobConfiguration.bucketSpan === bucketSpan && + jobConfiguration.indexPattern && + isSubset( + new Set(jobConfiguration.indexPattern.split(',')), + new Set(currentSourceConfiguration.indices) + ) && + jobConfiguration.timestampField === currentSourceConfiguration.timestampField && + equal(datafeedRuntimeMappings, currentSourceConfiguration.runtimeMappings) + ); + }; const isSubset = <T>(subset: Set<T>, superset: Set<T>) => { return Array.from(subset).every((subsetElement) => superset.has(subsetElement)); diff --git a/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_module_status.tsx b/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_module_status.tsx index c3117c9326d1e..03522fd9ce6e5 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_module_status.tsx +++ b/x-pack/plugins/infra/public/containers/logs/log_analysis/log_analysis_module_status.tsx @@ -63,151 +63,152 @@ const createInitialState = <JobType extends string>({ setupStatus: { type: 'initializing' }, }); -const createStatusReducer = <JobType extends string>(jobTypes: JobType[]) => ( - state: StatusReducerState<JobType>, - action: StatusReducerAction -): StatusReducerState<JobType> => { - switch (action.type) { - case 'startedSetup': { - return { - ...state, - jobStatus: jobTypes.reduce( - (accumulatedJobStatus, jobType) => ({ - ...accumulatedJobStatus, - [jobType]: 'initializing', - }), - {} as Record<JobType, JobStatus> - ), - setupStatus: { type: 'pending' }, - }; - } - case 'finishedSetup': { - const { datafeedSetupResults, jobSetupResults, jobSummaries, spaceId, sourceId } = action; - const nextJobStatus = jobTypes.reduce( - (accumulatedJobStatus, jobType) => ({ - ...accumulatedJobStatus, - [jobType]: - hasSuccessfullyCreatedJob(getJobId(spaceId, sourceId, jobType))(jobSetupResults) && - hasSuccessfullyStartedDatafeed(getDatafeedId(spaceId, sourceId, jobType))( - datafeedSetupResults - ) - ? 'started' - : 'failed', - }), - {} as Record<JobType, JobStatus> - ); - const nextSetupStatus: SetupStatus = Object.values<JobStatus>(nextJobStatus).every( - (jobState) => jobState === 'started' - ) - ? { type: 'succeeded' } - : { - type: 'failed', - reasons: [ - ...Object.values(datafeedSetupResults) - .filter(hasError) - .map((datafeed) => datafeed.error.error?.reason), - ...Object.values(jobSetupResults) - .filter(hasError) - .map((job) => job.error.error?.reason), - ], - }; - - return { - ...state, - jobStatus: nextJobStatus, - jobSummaries, - setupStatus: nextSetupStatus, - }; - } - case 'failedSetup': { - return { - ...state, - jobStatus: jobTypes.reduce( +const createStatusReducer = + <JobType extends string>(jobTypes: JobType[]) => + ( + state: StatusReducerState<JobType>, + action: StatusReducerAction + ): StatusReducerState<JobType> => { + switch (action.type) { + case 'startedSetup': { + return { + ...state, + jobStatus: jobTypes.reduce( + (accumulatedJobStatus, jobType) => ({ + ...accumulatedJobStatus, + [jobType]: 'initializing', + }), + {} as Record<JobType, JobStatus> + ), + setupStatus: { type: 'pending' }, + }; + } + case 'finishedSetup': { + const { datafeedSetupResults, jobSetupResults, jobSummaries, spaceId, sourceId } = action; + const nextJobStatus = jobTypes.reduce( (accumulatedJobStatus, jobType) => ({ ...accumulatedJobStatus, - [jobType]: 'failed', + [jobType]: + hasSuccessfullyCreatedJob(getJobId(spaceId, sourceId, jobType))(jobSetupResults) && + hasSuccessfullyStartedDatafeed(getDatafeedId(spaceId, sourceId, jobType))( + datafeedSetupResults + ) + ? 'started' + : 'failed', }), {} as Record<JobType, JobStatus> - ), - setupStatus: { type: 'failed', reasons: ['unknown'] }, - }; - } - case 'fetchingJobStatuses': { - return { - ...state, - setupStatus: - state.setupStatus.type === 'unknown' ? { type: 'initializing' } : state.setupStatus, - }; - } - case 'fetchedJobStatuses': { - const { payload: jobSummaries, spaceId, sourceId } = action; - const { setupStatus } = state; + ); + const nextSetupStatus: SetupStatus = Object.values<JobStatus>(nextJobStatus).every( + (jobState) => jobState === 'started' + ) + ? { type: 'succeeded' } + : { + type: 'failed', + reasons: [ + ...Object.values(datafeedSetupResults) + .filter(hasError) + .map((datafeed) => datafeed.error.error?.reason), + ...Object.values(jobSetupResults) + .filter(hasError) + .map((job) => job.error.error?.reason), + ], + }; - const nextJobStatus = jobTypes.reduce( - (accumulatedJobStatus, jobType) => ({ - ...accumulatedJobStatus, - [jobType]: getJobStatus(getJobId(spaceId, sourceId, jobType))(jobSummaries), - }), - {} as Record<JobType, JobStatus> - ); - const nextSetupStatus = getSetupStatus(nextJobStatus)(setupStatus); + return { + ...state, + jobStatus: nextJobStatus, + jobSummaries, + setupStatus: nextSetupStatus, + }; + } + case 'failedSetup': { + return { + ...state, + jobStatus: jobTypes.reduce( + (accumulatedJobStatus, jobType) => ({ + ...accumulatedJobStatus, + [jobType]: 'failed', + }), + {} as Record<JobType, JobStatus> + ), + setupStatus: { type: 'failed', reasons: ['unknown'] }, + }; + } + case 'fetchingJobStatuses': { + return { + ...state, + setupStatus: + state.setupStatus.type === 'unknown' ? { type: 'initializing' } : state.setupStatus, + }; + } + case 'fetchedJobStatuses': { + const { payload: jobSummaries, spaceId, sourceId } = action; + const { setupStatus } = state; - return { - ...state, - jobSummaries, - jobStatus: nextJobStatus, - setupStatus: nextSetupStatus, - }; - } - case 'failedFetchingJobStatuses': { - return { - ...state, - setupStatus: { type: 'unknown' }, - jobStatus: jobTypes.reduce( + const nextJobStatus = jobTypes.reduce( (accumulatedJobStatus, jobType) => ({ ...accumulatedJobStatus, - [jobType]: 'unknown', + [jobType]: getJobStatus(getJobId(spaceId, sourceId, jobType))(jobSummaries), }), {} as Record<JobType, JobStatus> - ), - }; - } - case 'viewedResults': { - return { - ...state, - setupStatus: { type: 'skipped', newlyCreated: true }, - }; - } - default: { - return state; + ); + const nextSetupStatus = getSetupStatus(nextJobStatus)(setupStatus); + + return { + ...state, + jobSummaries, + jobStatus: nextJobStatus, + setupStatus: nextSetupStatus, + }; + } + case 'failedFetchingJobStatuses': { + return { + ...state, + setupStatus: { type: 'unknown' }, + jobStatus: jobTypes.reduce( + (accumulatedJobStatus, jobType) => ({ + ...accumulatedJobStatus, + [jobType]: 'unknown', + }), + {} as Record<JobType, JobStatus> + ), + }; + } + case 'viewedResults': { + return { + ...state, + setupStatus: { type: 'skipped', newlyCreated: true }, + }; + } + default: { + return state; + } } - } -}; + }; -const hasSuccessfullyCreatedJob = (jobId: string) => ( - jobSetupResponses: SetupMlModuleResponsePayload['jobs'] -) => - jobSetupResponses.filter( - (jobSetupResponse) => - jobSetupResponse.id === jobId && jobSetupResponse.success && !jobSetupResponse.error - ).length > 0; +const hasSuccessfullyCreatedJob = + (jobId: string) => (jobSetupResponses: SetupMlModuleResponsePayload['jobs']) => + jobSetupResponses.filter( + (jobSetupResponse) => + jobSetupResponse.id === jobId && jobSetupResponse.success && !jobSetupResponse.error + ).length > 0; -const hasSuccessfullyStartedDatafeed = (datafeedId: string) => ( - datafeedSetupResponses: SetupMlModuleResponsePayload['datafeeds'] -) => - datafeedSetupResponses.filter( - (datafeedSetupResponse) => - datafeedSetupResponse.id === datafeedId && - datafeedSetupResponse.success && - datafeedSetupResponse.started && - !datafeedSetupResponse.error - ).length > 0; +const hasSuccessfullyStartedDatafeed = + (datafeedId: string) => (datafeedSetupResponses: SetupMlModuleResponsePayload['datafeeds']) => + datafeedSetupResponses.filter( + (datafeedSetupResponse) => + datafeedSetupResponse.id === datafeedId && + datafeedSetupResponse.success && + datafeedSetupResponse.started && + !datafeedSetupResponse.error + ).length > 0; -const getJobStatus = (jobId: string) => (jobSummaries: FetchJobStatusResponsePayload): JobStatus => - jobSummaries - .filter((jobSummary) => jobSummary.id === jobId) - .map( - (jobSummary): JobStatus => { +const getJobStatus = + (jobId: string) => + (jobSummaries: FetchJobStatusResponsePayload): JobStatus => + jobSummaries + .filter((jobSummary) => jobSummary.id === jobId) + .map((jobSummary): JobStatus => { if (jobSummary.jobState === 'failed' || jobSummary.datafeedState === '') { return 'failed'; } else if ( @@ -230,27 +231,26 @@ const getJobStatus = (jobId: string) => (jobSummaries: FetchJobStatusResponsePay } return 'unknown'; + })[0] || 'missing'; + +const getSetupStatus = + <JobType extends string>(everyJobStatus: Record<JobType, JobStatus>) => + (previousSetupStatus: SetupStatus): SetupStatus => + Object.entries<JobStatus>(everyJobStatus).reduce<SetupStatus>((setupStatus, [, jobStatus]) => { + if (jobStatus === 'missing') { + return { type: 'required' }; + } else if (setupStatus.type === 'required' || setupStatus.type === 'succeeded') { + return setupStatus; + } else if (setupStatus.type === 'skipped' || isJobStatusWithResults(jobStatus)) { + return { + type: 'skipped', + // preserve newlyCreated status + newlyCreated: setupStatus.type === 'skipped' && setupStatus.newlyCreated, + }; } - )[0] || 'missing'; -const getSetupStatus = <JobType extends string>(everyJobStatus: Record<JobType, JobStatus>) => ( - previousSetupStatus: SetupStatus -): SetupStatus => - Object.entries<JobStatus>(everyJobStatus).reduce<SetupStatus>((setupStatus, [, jobStatus]) => { - if (jobStatus === 'missing') { - return { type: 'required' }; - } else if (setupStatus.type === 'required' || setupStatus.type === 'succeeded') { return setupStatus; - } else if (setupStatus.type === 'skipped' || isJobStatusWithResults(jobStatus)) { - return { - type: 'skipped', - // preserve newlyCreated status - newlyCreated: setupStatus.type === 'skipped' && setupStatus.newlyCreated, - }; - } - - return setupStatus; - }, previousSetupStatus); + }, previousSetupStatus); const hasError = <Value extends { error?: any }>( value: Value diff --git a/x-pack/plugins/infra/public/containers/logs/log_analysis/modules/log_entry_categories/use_log_entry_categories_module.tsx b/x-pack/plugins/infra/public/containers/logs/log_analysis/modules/log_entry_categories/use_log_entry_categories_module.tsx index 86e8e75dc3d41..99d4ab4becee5 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_analysis/modules/log_entry_categories/use_log_entry_categories_module.tsx +++ b/x-pack/plugins/infra/public/containers/logs/log_analysis/modules/log_entry_categories/use_log_entry_categories_module.tsx @@ -86,7 +86,5 @@ export const useLogEntryCategoriesModule = ({ }; }; -export const [ - LogEntryCategoriesModuleProvider, - useLogEntryCategoriesModuleContext, -] = createContainer(useLogEntryCategoriesModule); +export const [LogEntryCategoriesModuleProvider, useLogEntryCategoriesModuleContext] = + createContainer(useLogEntryCategoriesModule); diff --git a/x-pack/plugins/infra/public/containers/logs/log_analysis/modules/log_entry_rate/use_log_entry_rate_module.tsx b/x-pack/plugins/infra/public/containers/logs/log_analysis/modules/log_entry_rate/use_log_entry_rate_module.tsx index 0df5d9b446dbe..f3e8f7e777597 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_analysis/modules/log_entry_rate/use_log_entry_rate_module.tsx +++ b/x-pack/plugins/infra/public/containers/logs/log_analysis/modules/log_entry_rate/use_log_entry_rate_module.tsx @@ -80,6 +80,5 @@ export const useLogEntryRateModule = ({ }; }; -export const [LogEntryRateModuleProvider, useLogEntryRateModuleContext] = createContainer( - useLogEntryRateModule -); +export const [LogEntryRateModuleProvider, useLogEntryRateModuleContext] = + createContainer(useLogEntryRateModule); diff --git a/x-pack/plugins/infra/public/containers/logs/log_analysis/modules/log_entry_rate/use_log_entry_rate_setup.tsx b/x-pack/plugins/infra/public/containers/logs/log_analysis/modules/log_entry_rate/use_log_entry_rate_setup.tsx index cc1ef39de32cc..603747c2d2c2b 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_analysis/modules/log_entry_rate/use_log_entry_rate_setup.tsx +++ b/x-pack/plugins/infra/public/containers/logs/log_analysis/modules/log_entry_rate/use_log_entry_rate_setup.tsx @@ -56,6 +56,5 @@ export const useLogEntryRateSetup = () => { }; }; -export const [LogEntryRateSetupProvider, useLogEntryRateSetupContext] = createContainer( - useLogEntryRateSetup -); +export const [LogEntryRateSetupProvider, useLogEntryRateSetupContext] = + createContainer(useLogEntryRateSetup); diff --git a/x-pack/plugins/infra/public/containers/logs/log_highlights/log_highlights.tsx b/x-pack/plugins/infra/public/containers/logs/log_highlights/log_highlights.tsx index f7f6f156f78e8..dab4abff3bed4 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_highlights/log_highlights.tsx +++ b/x-pack/plugins/infra/public/containers/logs/log_highlights/log_highlights.tsx @@ -39,20 +39,17 @@ export const useLogHighlightsState = ({ const throttledStartTimestamp = useThrottle(startTimestamp, FETCH_THROTTLE_INTERVAL); const throttledEndTimestamp = useThrottle(endTimestamp, FETCH_THROTTLE_INTERVAL); - const { - logEntryHighlights, - logEntryHighlightsById, - loadLogEntryHighlightsRequest, - } = useLogEntryHighlights( - sourceId, - sourceVersion, - throttledStartTimestamp, - throttledEndTimestamp, - centerCursor, - size, - filterQuery, - highlightTerms - ); + const { logEntryHighlights, logEntryHighlightsById, loadLogEntryHighlightsRequest } = + useLogEntryHighlights( + sourceId, + sourceVersion, + throttledStartTimestamp, + throttledEndTimestamp, + centerCursor, + size, + filterQuery, + highlightTerms + ); const { logSummaryHighlights, loadLogSummaryHighlightsRequest } = useLogSummaryHighlights( sourceId, diff --git a/x-pack/plugins/infra/public/containers/logs/log_highlights/log_summary_highlights.ts b/x-pack/plugins/infra/public/containers/logs/log_highlights/log_summary_highlights.ts index 7a36fe6aae28c..59abb716f6cb3 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_highlights/log_summary_highlights.ts +++ b/x-pack/plugins/infra/public/containers/logs/log_highlights/log_summary_highlights.ts @@ -56,9 +56,10 @@ export const useLogSummaryHighlights = ( [sourceId, startTimestamp, endTimestamp, bucketSize, filterQuery, highlightTerms] ); - const debouncedLoadSummaryHighlights = useMemo(() => debounce(loadLogSummaryHighlights, 275), [ - loadLogSummaryHighlights, - ]); + const debouncedLoadSummaryHighlights = useMemo( + () => debounce(loadLogSummaryHighlights, 275), + [loadLogSummaryHighlights] + ); useEffect(() => { setLogSummaryHighlights([]); diff --git a/x-pack/plugins/infra/public/containers/logs/log_source/log_source.mock.ts b/x-pack/plugins/infra/public/containers/logs/log_source/log_source.mock.ts index bda1085d44612..6021c728d32af 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_source/log_source.mock.ts +++ b/x-pack/plugins/infra/public/containers/logs/log_source/log_source.mock.ts @@ -11,56 +11,56 @@ type CreateUseLogSource = (sourceConfiguration?: { sourceId?: string }) => typeo const defaultSourceId = 'default'; -export const createUninitializedUseLogSourceMock: CreateUseLogSource = ({ - sourceId = defaultSourceId, -} = {}) => () => ({ - derivedIndexPattern: { - fields: [], - title: 'unknown', - }, - hasFailedLoading: false, - hasFailedLoadingSource: false, - hasFailedLoadingSourceStatus: false, - hasFailedResolvingSource: false, - initialize: jest.fn(), - isLoading: false, - isLoadingSourceConfiguration: false, - isLoadingSourceStatus: false, - isResolvingSourceConfiguration: false, - isUninitialized: true, - loadSource: jest.fn(), - loadSourceConfiguration: jest.fn(), - latestLoadSourceFailures: [], - resolveSourceFailureMessage: undefined, - loadSourceStatus: jest.fn(), - sourceConfiguration: undefined, - sourceId, - sourceStatus: undefined, - updateSource: jest.fn(), - resolvedSourceConfiguration: undefined, - loadResolveLogSourceConfiguration: jest.fn(), -}); +export const createUninitializedUseLogSourceMock: CreateUseLogSource = + ({ sourceId = defaultSourceId } = {}) => + () => ({ + derivedIndexPattern: { + fields: [], + title: 'unknown', + }, + hasFailedLoading: false, + hasFailedLoadingSource: false, + hasFailedLoadingSourceStatus: false, + hasFailedResolvingSource: false, + initialize: jest.fn(), + isLoading: false, + isLoadingSourceConfiguration: false, + isLoadingSourceStatus: false, + isResolvingSourceConfiguration: false, + isUninitialized: true, + loadSource: jest.fn(), + loadSourceConfiguration: jest.fn(), + latestLoadSourceFailures: [], + resolveSourceFailureMessage: undefined, + loadSourceStatus: jest.fn(), + sourceConfiguration: undefined, + sourceId, + sourceStatus: undefined, + updateSource: jest.fn(), + resolvedSourceConfiguration: undefined, + loadResolveLogSourceConfiguration: jest.fn(), + }); -export const createLoadingUseLogSourceMock: CreateUseLogSource = ({ - sourceId = defaultSourceId, -} = {}) => (args) => ({ - ...createUninitializedUseLogSourceMock({ sourceId })(args), - isLoading: true, - isLoadingSourceConfiguration: true, - isLoadingSourceStatus: true, - isResolvingSourceConfiguration: true, -}); +export const createLoadingUseLogSourceMock: CreateUseLogSource = + ({ sourceId = defaultSourceId } = {}) => + (args) => ({ + ...createUninitializedUseLogSourceMock({ sourceId })(args), + isLoading: true, + isLoadingSourceConfiguration: true, + isLoadingSourceStatus: true, + isResolvingSourceConfiguration: true, + }); -export const createLoadedUseLogSourceMock: CreateUseLogSource = ({ - sourceId = defaultSourceId, -} = {}) => (args) => ({ - ...createUninitializedUseLogSourceMock({ sourceId })(args), - sourceConfiguration: createBasicSourceConfiguration(sourceId), - sourceStatus: { - logIndexFields: [], - logIndexStatus: 'available', - }, -}); +export const createLoadedUseLogSourceMock: CreateUseLogSource = + ({ sourceId = defaultSourceId } = {}) => + (args) => ({ + ...createUninitializedUseLogSourceMock({ sourceId })(args), + sourceConfiguration: createBasicSourceConfiguration(sourceId), + sourceStatus: { + logIndexFields: [], + logIndexStatus: 'available', + }, + }); export const createBasicSourceConfiguration = (sourceId: string): LogSourceConfiguration => ({ id: sourceId, diff --git a/x-pack/plugins/infra/public/containers/logs/log_stream/use_fetch_log_entries_after.ts b/x-pack/plugins/infra/public/containers/logs/log_stream/use_fetch_log_entries_after.ts index e41bc07dfd364..ea2162cb96e36 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_stream/use_fetch_log_entries_after.ts +++ b/x-pack/plugins/infra/public/containers/logs/log_stream/use_fetch_log_entries_after.ts @@ -88,13 +88,8 @@ export const useLogEntriesAfterResponse = <Request extends IKibanaSearchRequest> flattenLogEntriesAfterSearchResponse ); - const { - cancelRequest, - isRequestRunning, - isResponsePartial, - loaded, - total, - } = useDataSearchResponseState(logEntriesAfterSearchResponse$); + const { cancelRequest, isRequestRunning, isResponsePartial, loaded, total } = + useDataSearchResponseState(logEntriesAfterSearchResponse$); return { cancelRequest, diff --git a/x-pack/plugins/infra/public/containers/logs/log_stream/use_fetch_log_entries_before.ts b/x-pack/plugins/infra/public/containers/logs/log_stream/use_fetch_log_entries_before.ts index c9a7dab168200..7d99b3069d973 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_stream/use_fetch_log_entries_before.ts +++ b/x-pack/plugins/infra/public/containers/logs/log_stream/use_fetch_log_entries_before.ts @@ -44,34 +44,32 @@ export const useLogEntriesBeforeRequest = ({ sourceId: string; startTimestamp: number; }) => { - const { - search: fetchLogEntriesBefore, - requests$: logEntriesBeforeSearchRequests$, - } = useDataSearch({ - getRequest: useCallback( - (cursor: LogEntryBeforeCursor['before'], params: { size: number; extendTo?: number }) => { - return !!sourceId - ? { - request: { - params: logEntriesSearchRequestParamsRT.encode({ - before: cursor, - columns: columnOverrides, - endTimestamp, - highlightPhrase, - query: query as JsonObject, - size: params.size, - sourceId, - startTimestamp: params.extendTo ?? startTimestamp, - }), - }, - options: { strategy: LOG_ENTRIES_SEARCH_STRATEGY }, - } - : null; - }, - [columnOverrides, endTimestamp, highlightPhrase, query, sourceId, startTimestamp] - ), - parseResponses: parseLogEntriesBeforeSearchResponses, - }); + const { search: fetchLogEntriesBefore, requests$: logEntriesBeforeSearchRequests$ } = + useDataSearch({ + getRequest: useCallback( + (cursor: LogEntryBeforeCursor['before'], params: { size: number; extendTo?: number }) => { + return !!sourceId + ? { + request: { + params: logEntriesSearchRequestParamsRT.encode({ + before: cursor, + columns: columnOverrides, + endTimestamp, + highlightPhrase, + query: query as JsonObject, + size: params.size, + sourceId, + startTimestamp: params.extendTo ?? startTimestamp, + }), + }, + options: { strategy: LOG_ENTRIES_SEARCH_STRATEGY }, + } + : null; + }, + [columnOverrides, endTimestamp, highlightPhrase, query, sourceId, startTimestamp] + ), + parseResponses: parseLogEntriesBeforeSearchResponses, + }); return { fetchLogEntriesBefore, @@ -89,13 +87,8 @@ export const useLogEntriesBeforeResponse = <Request extends IKibanaSearchRequest flattenLogEntriesBeforeSearchResponse ); - const { - cancelRequest, - isRequestRunning, - isResponsePartial, - loaded, - total, - } = useDataSearchResponseState(logEntriesBeforeSearchResponse$); + const { cancelRequest, isRequestRunning, isResponsePartial, loaded, total } = + useDataSearchResponseState(logEntriesBeforeSearchResponse$); return { cancelRequest, diff --git a/x-pack/plugins/infra/public/containers/metrics_source/source.tsx b/x-pack/plugins/infra/public/containers/metrics_source/source.tsx index b730f8b007e43..c8e485b5d2856 100644 --- a/x-pack/plugins/infra/public/containers/metrics_source/source.tsx +++ b/x-pack/plugins/infra/public/containers/metrics_source/source.tsx @@ -121,9 +121,10 @@ export const useSource = ({ sourceId }: { sourceId: string }) => { ] ); - const isUninitialized = useMemo(() => loadSourceRequest.state === 'uninitialized', [ - loadSourceRequest.state, - ]); + const isUninitialized = useMemo( + () => loadSourceRequest.state === 'uninitialized', + [loadSourceRequest.state] + ); const sourceExists = useMemo(() => (source ? !!source.version : undefined), [source]); diff --git a/x-pack/plugins/infra/public/containers/metrics_source/use_source_via_http.ts b/x-pack/plugins/infra/public/containers/metrics_source/use_source_via_http.ts index 2947f8fb09847..c7da5afd8dcd6 100644 --- a/x-pack/plugins/infra/public/containers/metrics_source/use_source_via_http.ts +++ b/x-pack/plugins/infra/public/containers/metrics_source/use_source_via_http.ts @@ -47,19 +47,15 @@ export const useSourceViaHttp = ({ sourceId = 'default', fetch, toastWarning }: ); }; - const { - error, - loading, - response, - makeRequest, - } = useHTTPRequest<MetricsSourceConfigurationResponse>( - `/api/metrics/source/${sourceId}`, - 'GET', - null, - decodeResponse, - fetch, - toastWarning - ); + const { error, loading, response, makeRequest } = + useHTTPRequest<MetricsSourceConfigurationResponse>( + `/api/metrics/source/${sourceId}`, + 'GET', + null, + decodeResponse, + fetch, + toastWarning + ); useEffect(() => { (async () => { diff --git a/x-pack/plugins/infra/public/containers/ml/infra_ml_capabilities.tsx b/x-pack/plugins/infra/public/containers/ml/infra_ml_capabilities.tsx index 661ce8f8a253c..7b9fe25cc3fa1 100644 --- a/x-pack/plugins/infra/public/containers/ml/infra_ml_capabilities.tsx +++ b/x-pack/plugins/infra/public/containers/ml/infra_ml_capabilities.tsx @@ -20,9 +20,8 @@ import { useKibanaContextForPlugin } from '../../hooks/use_kibana'; export const useInfraMLCapabilities = () => { const { services } = useKibanaContextForPlugin(); - const [mlCapabilities, setMlCapabilities] = useState<GetMlCapabilitiesResponsePayload>( - initialMlCapabilities - ); + const [mlCapabilities, setMlCapabilities] = + useState<GetMlCapabilitiesResponsePayload>(initialMlCapabilities); const [fetchMlCapabilitiesRequest, fetchMlCapabilities] = useTrackedPromise( { @@ -46,9 +45,10 @@ export const useInfraMLCapabilities = () => { fetchMlCapabilities(); }, [fetchMlCapabilities]); - const isLoading = useMemo(() => fetchMlCapabilitiesRequest.state === 'pending', [ - fetchMlCapabilitiesRequest.state, - ]); + const isLoading = useMemo( + () => fetchMlCapabilitiesRequest.state === 'pending', + [fetchMlCapabilitiesRequest.state] + ); const hasInfraMLSetupCapabilities = mlCapabilities.capabilities.canCreateJob; const hasInfraMLReadCapabilities = mlCapabilities.capabilities.canGetJobs; @@ -63,9 +63,8 @@ export const useInfraMLCapabilities = () => { }; }; -export const [InfraMLCapabilitiesProvider, useInfraMLCapabilitiesContext] = createContainer( - useInfraMLCapabilities -); +export const [InfraMLCapabilitiesProvider, useInfraMLCapabilitiesContext] = + createContainer(useInfraMLCapabilities); const initialMlCapabilities = { capabilities: { diff --git a/x-pack/plugins/infra/public/containers/ml/infra_ml_module.tsx b/x-pack/plugins/infra/public/containers/ml/infra_ml_module.tsx index b55ae65e58e91..198a99f394850 100644 --- a/x-pack/plugins/infra/public/containers/ml/infra_ml_module.tsx +++ b/x-pack/plugins/infra/public/containers/ml/infra_ml_module.tsx @@ -104,9 +104,10 @@ export const useInfraMLModule = <JobType extends string>({ [spaceId, sourceId] ); - const isCleaningUp = useMemo(() => cleanUpModuleRequest.state === 'pending', [ - cleanUpModuleRequest.state, - ]); + const isCleaningUp = useMemo( + () => cleanUpModuleRequest.state === 'pending', + [cleanUpModuleRequest.state] + ); const cleanUpAndSetUpModule = useCallback( ( @@ -132,11 +133,10 @@ export const useInfraMLModule = <JobType extends string>({ dispatchModuleStatus({ type: 'viewedResults' }); }, [dispatchModuleStatus]); - const jobIds = useMemo(() => moduleDescriptor.getJobIds(spaceId, sourceId), [ - moduleDescriptor, - spaceId, - sourceId, - ]); + const jobIds = useMemo( + () => moduleDescriptor.getJobIds(spaceId, sourceId), + [moduleDescriptor, spaceId, sourceId] + ); return { cleanUpAndSetUpModule, diff --git a/x-pack/plugins/infra/public/containers/ml/infra_ml_module_configuration.ts b/x-pack/plugins/infra/public/containers/ml/infra_ml_module_configuration.ts index 3f8ebb835211d..4c876c1705364 100644 --- a/x-pack/plugins/infra/public/containers/ml/infra_ml_module_configuration.ts +++ b/x-pack/plugins/infra/public/containers/ml/infra_ml_module_configuration.ts @@ -26,27 +26,29 @@ export const useInfraMLModuleConfiguration = <JobType extends string>({ }; }; -export const isJobConfigurationOutdated = <JobType extends string>( - { bucketSpan }: ModuleDescriptor<JobType>, - currentSourceConfiguration: ModuleSourceConfiguration -) => (jobSummary: JobSummary): boolean => { - if (!jobSummary.fullJob || !jobSummary.fullJob.custom_settings) { - return false; - } +export const isJobConfigurationOutdated = + <JobType extends string>( + { bucketSpan }: ModuleDescriptor<JobType>, + currentSourceConfiguration: ModuleSourceConfiguration + ) => + (jobSummary: JobSummary): boolean => { + if (!jobSummary.fullJob || !jobSummary.fullJob.custom_settings) { + return false; + } - const jobConfiguration = jobSummary.fullJob.custom_settings.metrics_source_config; + const jobConfiguration = jobSummary.fullJob.custom_settings.metrics_source_config; - return !( - jobConfiguration && - jobConfiguration.bucketSpan === bucketSpan && - jobConfiguration.indexPattern && - isSubset( - new Set(jobConfiguration.indexPattern.split(',')), - new Set(currentSourceConfiguration.indices) - ) && - jobConfiguration.timestampField === currentSourceConfiguration.timestampField - ); -}; + return !( + jobConfiguration && + jobConfiguration.bucketSpan === bucketSpan && + jobConfiguration.indexPattern && + isSubset( + new Set(jobConfiguration.indexPattern.split(',')), + new Set(currentSourceConfiguration.indices) + ) && + jobConfiguration.timestampField === currentSourceConfiguration.timestampField + ); + }; const isSubset = <T>(subset: Set<T>, superset: Set<T>) => { return Array.from(subset).every((subsetElement) => superset.has(subsetElement)); diff --git a/x-pack/plugins/infra/public/containers/ml/infra_ml_module_status.tsx b/x-pack/plugins/infra/public/containers/ml/infra_ml_module_status.tsx index 4ebad85ef0fe7..9bbd7720e168f 100644 --- a/x-pack/plugins/infra/public/containers/ml/infra_ml_module_status.tsx +++ b/x-pack/plugins/infra/public/containers/ml/infra_ml_module_status.tsx @@ -63,153 +63,152 @@ const createInitialState = <JobType extends string>({ setupStatus: { type: 'initializing' }, }); -const createStatusReducer = <JobType extends string>(jobTypes: JobType[]) => ( - state: StatusReducerState<JobType>, - action: StatusReducerAction -): StatusReducerState<JobType> => { - switch (action.type) { - case 'startedSetup': { - return { - ...state, - jobStatus: jobTypes.reduce( +const createStatusReducer = + <JobType extends string>(jobTypes: JobType[]) => + ( + state: StatusReducerState<JobType>, + action: StatusReducerAction + ): StatusReducerState<JobType> => { + switch (action.type) { + case 'startedSetup': { + return { + ...state, + jobStatus: jobTypes.reduce( + (accumulatedJobStatus, jobType) => ({ + ...accumulatedJobStatus, + [jobType]: 'initializing', + }), + {} as Record<JobType, JobStatus> + ), + setupStatus: { type: 'pending' }, + }; + } + case 'finishedSetup': { + const { datafeedSetupResults, jobSetupResults, jobSummaries, spaceId, sourceId } = action; + const nextJobStatus = jobTypes.reduce( (accumulatedJobStatus, jobType) => ({ ...accumulatedJobStatus, - [jobType]: 'initializing', + [jobType]: + hasSuccessfullyCreatedJob(getJobId(spaceId, sourceId, jobType))(jobSetupResults) && + hasSuccessfullyStartedDatafeed(getDatafeedId(spaceId, sourceId, jobType))( + datafeedSetupResults + ) + ? 'started' + : 'failed', }), {} as Record<JobType, JobStatus> - ), - setupStatus: { type: 'pending' }, - }; - } - case 'finishedSetup': { - const { datafeedSetupResults, jobSetupResults, jobSummaries, spaceId, sourceId } = action; - const nextJobStatus = jobTypes.reduce( - (accumulatedJobStatus, jobType) => ({ - ...accumulatedJobStatus, - [jobType]: - hasSuccessfullyCreatedJob(getJobId(spaceId, sourceId, jobType))(jobSetupResults) && - hasSuccessfullyStartedDatafeed(getDatafeedId(spaceId, sourceId, jobType))( - datafeedSetupResults - ) - ? 'started' - : 'failed', - }), - {} as Record<JobType, JobStatus> - ); - const nextSetupStatus: SetupStatus = Object.values<JobStatus>(nextJobStatus).every( - (jobState) => jobState === 'started' - ) - ? { type: 'succeeded' } - : { - type: 'failed', - reasons: [ - ...Object.values(datafeedSetupResults) - .filter(hasError) - .map((datafeed) => datafeed.error.msg), - ...Object.values(jobSetupResults) - .filter(hasError) - .map((job) => job.error.msg), - ], - }; + ); + const nextSetupStatus: SetupStatus = Object.values<JobStatus>(nextJobStatus).every( + (jobState) => jobState === 'started' + ) + ? { type: 'succeeded' } + : { + type: 'failed', + reasons: [ + ...Object.values(datafeedSetupResults) + .filter(hasError) + .map((datafeed) => datafeed.error.msg), + ...Object.values(jobSetupResults) + .filter(hasError) + .map((job) => job.error.msg), + ], + }; - return { - ...state, - jobStatus: nextJobStatus, - jobSummaries, - setupStatus: nextSetupStatus, - }; - } - case 'failedSetup': { - return { - ...state, - jobStatus: jobTypes.reduce( + return { + ...state, + jobStatus: nextJobStatus, + jobSummaries, + setupStatus: nextSetupStatus, + }; + } + case 'failedSetup': { + return { + ...state, + jobStatus: jobTypes.reduce( + (accumulatedJobStatus, jobType) => ({ + ...accumulatedJobStatus, + [jobType]: 'failed', + }), + {} as Record<JobType, JobStatus> + ), + setupStatus: { type: 'failed', reasons: ['unknown'] }, + }; + } + case 'fetchingJobStatuses': { + return { + ...state, + setupStatus: + state.setupStatus.type === 'unknown' ? { type: 'initializing' } : state.setupStatus, + }; + } + case 'fetchedJobStatuses': { + const { payload: jobSummaries, spaceId, sourceId } = action; + const { setupStatus } = state; + const nextJobStatus = jobTypes.reduce( (accumulatedJobStatus, jobType) => ({ ...accumulatedJobStatus, - [jobType]: 'failed', + [jobType]: getJobStatus(getJobId(spaceId, sourceId, jobType))(jobSummaries), }), {} as Record<JobType, JobStatus> - ), - setupStatus: { type: 'failed', reasons: ['unknown'] }, - }; - } - case 'fetchingJobStatuses': { - return { - ...state, - setupStatus: - state.setupStatus.type === 'unknown' ? { type: 'initializing' } : state.setupStatus, - }; - } - case 'fetchedJobStatuses': { - const { payload: jobSummaries, spaceId, sourceId } = action; - const { setupStatus } = state; - const nextJobStatus = jobTypes.reduce( - (accumulatedJobStatus, jobType) => ({ - ...accumulatedJobStatus, - [jobType]: getJobStatus(getJobId(spaceId, sourceId, jobType))(jobSummaries), - }), - {} as Record<JobType, JobStatus> - ); - const nextSetupStatus = getSetupStatus(nextJobStatus)(setupStatus); + ); + const nextSetupStatus = getSetupStatus(nextJobStatus)(setupStatus); - return { - ...state, - jobSummaries, - jobStatus: nextJobStatus, - setupStatus: nextSetupStatus, - }; - } - case 'failedFetchingJobStatuses': { - return { - ...state, - setupStatus: { type: 'unknown' }, - jobStatus: jobTypes.reduce( - (accumulatedJobStatus, jobType) => ({ - ...accumulatedJobStatus, - [jobType]: 'unknown', - }), - {} as Record<JobType, JobStatus> - ), - }; - } - case 'viewedResults': { - return { - ...state, - setupStatus: { type: 'skipped', newlyCreated: true }, - }; - } - default: { - return state; + return { + ...state, + jobSummaries, + jobStatus: nextJobStatus, + setupStatus: nextSetupStatus, + }; + } + case 'failedFetchingJobStatuses': { + return { + ...state, + setupStatus: { type: 'unknown' }, + jobStatus: jobTypes.reduce( + (accumulatedJobStatus, jobType) => ({ + ...accumulatedJobStatus, + [jobType]: 'unknown', + }), + {} as Record<JobType, JobStatus> + ), + }; + } + case 'viewedResults': { + return { + ...state, + setupStatus: { type: 'skipped', newlyCreated: true }, + }; + } + default: { + return state; + } } - } -}; + }; -const hasSuccessfullyCreatedJob = (jobId: string) => ( - jobSetupResponses: SetupMlModuleResponsePayload['jobs'] -) => - jobSetupResponses.filter( - (jobSetupResponse) => - jobSetupResponse.id === jobId && jobSetupResponse.success && !jobSetupResponse.error - ).length > 0; +const hasSuccessfullyCreatedJob = + (jobId: string) => (jobSetupResponses: SetupMlModuleResponsePayload['jobs']) => + jobSetupResponses.filter( + (jobSetupResponse) => + jobSetupResponse.id === jobId && jobSetupResponse.success && !jobSetupResponse.error + ).length > 0; -const hasSuccessfullyStartedDatafeed = (datafeedId: string) => ( - datafeedSetupResponses: SetupMlModuleResponsePayload['datafeeds'] -) => - datafeedSetupResponses.filter( - (datafeedSetupResponse) => - datafeedSetupResponse.id === datafeedId && - datafeedSetupResponse.success && - datafeedSetupResponse.started && - !datafeedSetupResponse.error - ).length > 0; +const hasSuccessfullyStartedDatafeed = + (datafeedId: string) => (datafeedSetupResponses: SetupMlModuleResponsePayload['datafeeds']) => + datafeedSetupResponses.filter( + (datafeedSetupResponse) => + datafeedSetupResponse.id === datafeedId && + datafeedSetupResponse.success && + datafeedSetupResponse.started && + !datafeedSetupResponse.error + ).length > 0; -const getJobStatus = (jobId: string) => ( - jobSummaries: FetchJobStatusResponsePayload -): JobStatus => { - return ( - jobSummaries - .filter((jobSummary) => jobSummary.id === jobId) - .map( - (jobSummary): JobStatus => { +const getJobStatus = + (jobId: string) => + (jobSummaries: FetchJobStatusResponsePayload): JobStatus => { + return ( + jobSummaries + .filter((jobSummary) => jobSummary.id === jobId) + .map((jobSummary): JobStatus => { if (jobSummary.jobState === 'failed' || jobSummary.datafeedState === '') { return 'failed'; } else if ( @@ -232,33 +231,32 @@ const getJobStatus = (jobId: string) => ( } return 'unknown'; + })[0] || 'missing' + ); + }; + +const getSetupStatus = + <JobType extends string>(everyJobStatus: Record<JobType, JobStatus>) => + (previousSetupStatus: SetupStatus): SetupStatus => { + return Object.entries<JobStatus>(everyJobStatus).reduce<SetupStatus>( + (setupStatus, [, jobStatus]) => { + if (jobStatus === 'missing') { + return { type: 'required' }; + } else if (setupStatus.type === 'required' || setupStatus.type === 'succeeded') { + return setupStatus; + } else if (setupStatus.type === 'skipped' || isJobStatusWithResults(jobStatus)) { + return { + type: 'skipped', + // preserve newlyCreated status + newlyCreated: setupStatus.type === 'skipped' && setupStatus.newlyCreated, + }; } - )[0] || 'missing' - ); -}; -const getSetupStatus = <JobType extends string>(everyJobStatus: Record<JobType, JobStatus>) => ( - previousSetupStatus: SetupStatus -): SetupStatus => { - return Object.entries<JobStatus>(everyJobStatus).reduce<SetupStatus>( - (setupStatus, [, jobStatus]) => { - if (jobStatus === 'missing') { - return { type: 'required' }; - } else if (setupStatus.type === 'required' || setupStatus.type === 'succeeded') { return setupStatus; - } else if (setupStatus.type === 'skipped' || isJobStatusWithResults(jobStatus)) { - return { - type: 'skipped', - // preserve newlyCreated status - newlyCreated: setupStatus.type === 'skipped' && setupStatus.newlyCreated, - }; - } - - return setupStatus; - }, - previousSetupStatus - ); -}; + }, + previousSetupStatus + ); + }; const hasError = <Value extends { error?: any }>( value: Value diff --git a/x-pack/plugins/infra/public/containers/ml/modules/metrics_hosts/module.tsx b/x-pack/plugins/infra/public/containers/ml/modules/metrics_hosts/module.tsx index 2697a4223fc18..f892ab62ee3d8 100644 --- a/x-pack/plugins/infra/public/containers/ml/modules/metrics_hosts/module.tsx +++ b/x-pack/plugins/infra/public/containers/ml/modules/metrics_hosts/module.tsx @@ -76,6 +76,5 @@ export const useMetricHostsModule = ({ }; }; -export const [MetricHostsModuleProvider, useMetricHostsModuleContext] = createContainer( - useMetricHostsModule -); +export const [MetricHostsModuleProvider, useMetricHostsModuleContext] = + createContainer(useMetricHostsModule); diff --git a/x-pack/plugins/infra/public/containers/ml/modules/metrics_k8s/module.tsx b/x-pack/plugins/infra/public/containers/ml/modules/metrics_k8s/module.tsx index 98edbca2166b8..eadc374434817 100644 --- a/x-pack/plugins/infra/public/containers/ml/modules/metrics_k8s/module.tsx +++ b/x-pack/plugins/infra/public/containers/ml/modules/metrics_k8s/module.tsx @@ -76,6 +76,5 @@ export const useMetricK8sModule = ({ }; }; -export const [MetricK8sModuleProvider, useMetricK8sModuleContext] = createContainer( - useMetricK8sModule -); +export const [MetricK8sModuleProvider, useMetricK8sModuleContext] = + createContainer(useMetricK8sModule); diff --git a/x-pack/plugins/infra/public/containers/saved_view/saved_view.tsx b/x-pack/plugins/infra/public/containers/saved_view/saved_view.tsx index c54a2a69a994c..44792019a5f9c 100644 --- a/x-pack/plugins/infra/public/containers/saved_view/saved_view.tsx +++ b/x-pack/plugins/infra/public/containers/saved_view/saved_view.tsx @@ -62,9 +62,13 @@ export const useSavedView = (props: Props) => { } = useContext(Source.Context); const { viewType, defaultViewState } = props; type ViewState = typeof defaultViewState; - const { data, loading, find, error: errorOnFind, hasView } = useFindSavedObject< - SavedViewSavedObject<ViewState> - >(viewType); + const { + data, + loading, + find, + error: errorOnFind, + hasView, + } = useFindSavedObject<SavedViewSavedObject<ViewState>>(viewType); const [urlState, setUrlState] = useUrlState<SavedViewUrlState>({ defaultState: DEFAULT_SAVED_VIEW_STATE, decodeUrlState, @@ -75,12 +79,18 @@ export const useSavedView = (props: Props) => { const [shouldLoadDefault] = useState(props.shouldLoadDefault); const [currentView, setCurrentView] = useState<SavedView<any> | null>(null); const [loadingDefaultView, setLoadingDefaultView] = useState<boolean | null>(null); - const { create, error: errorOnCreate, data: createdViewData, createdId } = useCreateSavedObject( - viewType - ); - const { update, error: errorOnUpdate, data: updatedViewData, updatedId } = useUpdateSavedObject( - viewType - ); + const { + create, + error: errorOnCreate, + data: createdViewData, + createdId, + } = useCreateSavedObject(viewType); + const { + update, + error: errorOnUpdate, + data: updatedViewData, + updatedId, + } = useUpdateSavedObject(viewType); const { deleteObject, deletedId } = useDeleteSavedObject(viewType); const { getObject, data: currentViewSavedObject } = useGetSavedObject(viewType); const [createError, setCreateError] = useState<string | null>(null); diff --git a/x-pack/plugins/infra/public/containers/with_kuery_autocompletion.tsx b/x-pack/plugins/infra/public/containers/with_kuery_autocompletion.tsx index 1a759950f640d..f50c629d521e5 100644 --- a/x-pack/plugins/infra/public/containers/with_kuery_autocompletion.tsx +++ b/x-pack/plugins/infra/public/containers/with_kuery_autocompletion.tsx @@ -61,9 +61,8 @@ class WithKueryAutocompletionComponent extends React.Component< ) => { const { indexPattern } = this.props; const language = 'kuery'; - const hasQuerySuggestions = this.props.kibana.services.data?.autocomplete.hasQuerySuggestions( - language - ); + const hasQuerySuggestions = + this.props.kibana.services.data?.autocomplete.hasQuerySuggestions(language); if (!hasQuerySuggestions) { return; diff --git a/x-pack/plugins/infra/public/hooks/use_kibana.ts b/x-pack/plugins/infra/public/hooks/use_kibana.ts index 207e08589cde9..1d21f352a9ea4 100644 --- a/x-pack/plugins/infra/public/hooks/use_kibana.ts +++ b/x-pack/plugins/infra/public/hooks/use_kibana.ts @@ -21,4 +21,5 @@ export const createKibanaContextForPlugin = (core: CoreStart, pluginsStart: Infr ...pluginsStart, }); -export const useKibanaContextForPlugin = useKibana as () => KibanaReactContextValue<PluginKibanaContextValue>; +export const useKibanaContextForPlugin = + useKibana as () => KibanaReactContextValue<PluginKibanaContextValue>; diff --git a/x-pack/plugins/infra/public/metrics_overview_fetchers.ts b/x-pack/plugins/infra/public/metrics_overview_fetchers.ts index 4f5b73d685591..57017d25ecc64 100644 --- a/x-pack/plugins/infra/public/metrics_overview_fetchers.ts +++ b/x-pack/plugins/infra/public/metrics_overview_fetchers.ts @@ -17,44 +17,43 @@ import { FetchDataParams, MetricsFetchDataResponse } from '../../observability/p import { TopNodesRequest, TopNodesResponse } from '../common/http_api/overview_api'; import { InfraClientCoreSetup } from './types'; -export const createMetricsHasData = ( - getStartServices: InfraClientCoreSetup['getStartServices'] -) => async () => { - const [coreServices] = await getStartServices(); - const { http } = coreServices; - const results = await http.get<{ hasData: boolean }>('/api/metrics/source/default/hasData'); - return results.hasData; -}; +export const createMetricsHasData = + (getStartServices: InfraClientCoreSetup['getStartServices']) => async () => { + const [coreServices] = await getStartServices(); + const { http } = coreServices; + const results = await http.get<{ hasData: boolean }>('/api/metrics/source/default/hasData'); + return results.hasData; + }; -export const createMetricsFetchData = ( - getStartServices: InfraClientCoreSetup['getStartServices'] -) => async ({ absoluteTime, bucketSize }: FetchDataParams): Promise<MetricsFetchDataResponse> => { - const [coreServices] = await getStartServices(); - const { http } = coreServices; +export const createMetricsFetchData = + (getStartServices: InfraClientCoreSetup['getStartServices']) => + async ({ absoluteTime, bucketSize }: FetchDataParams): Promise<MetricsFetchDataResponse> => { + const [coreServices] = await getStartServices(); + const { http } = coreServices; - const makeRequest = async (overrides: Partial<TopNodesRequest> = {}) => { - const { start, end } = absoluteTime; + const makeRequest = async (overrides: Partial<TopNodesRequest> = {}) => { + const { start, end } = absoluteTime; - const overviewRequest: TopNodesRequest = { - sourceId: 'default', - bucketSize, - size: 5, - timerange: { - from: start, - to: end, - }, - ...overrides, - }; - const results = await http.post<TopNodesResponse>('/api/metrics/overview/top', { - body: JSON.stringify(overviewRequest), - }); - return { - appLink: `/app/metrics/inventory?waffleTime=(currentTime:${end},isAutoReloading:!f)`, - series: results.series, - sort: async (by: string, direction: string) => - makeRequest({ sort: by, sortDirection: direction }), + const overviewRequest: TopNodesRequest = { + sourceId: 'default', + bucketSize, + size: 5, + timerange: { + from: start, + to: end, + }, + ...overrides, + }; + const results = await http.post<TopNodesResponse>('/api/metrics/overview/top', { + body: JSON.stringify(overviewRequest), + }); + return { + appLink: `/app/metrics/inventory?waffleTime=(currentTime:${end},isAutoReloading:!f)`, + series: results.series, + sort: async (by: string, direction: string) => + makeRequest({ sort: by, sortDirection: direction }), + }; }; - }; - return await makeRequest(); -}; + return await makeRequest(); + }; diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_content.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_content.tsx index bfdbe03526164..a8d98ea843c96 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_content.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_content.tsx @@ -40,9 +40,10 @@ export const LogEntryCategoriesPageContent = () => { const { fetchJobStatus, setupStatus, jobStatus } = useLogEntryCategoriesModuleContext(); const { showModuleSetup } = useLogAnalysisSetupFlyoutStateContext(); - const showCategoriesModuleSetup = useCallback(() => showModuleSetup('logs_ui_categories'), [ - showModuleSetup, - ]); + const showCategoriesModuleSetup = useCallback( + () => showModuleSetup('logs_ui_categories'), + [showModuleSetup] + ); useEffect(() => { if (hasLogAnalysisReadCapabilities) { diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_results_content.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_results_content.tsx index 7098f457117d3..bf6fe978ddae2 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_results_content.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_results_content.tsx @@ -40,240 +40,241 @@ interface LogEntryCategoriesResultsContentProps { pageTitle: string; } -export const LogEntryCategoriesResultsContent: React.FunctionComponent<LogEntryCategoriesResultsContentProps> = ({ - onOpenSetup, - pageTitle, -}) => { - useTrackPageview({ app: 'infra_logs', path: 'log_entry_categories_results' }); - useTrackPageview({ app: 'infra_logs', path: 'log_entry_categories_results', delay: 15000 }); +export const LogEntryCategoriesResultsContent: React.FunctionComponent<LogEntryCategoriesResultsContentProps> = + ({ onOpenSetup, pageTitle }) => { + useTrackPageview({ app: 'infra_logs', path: 'log_entry_categories_results' }); + useTrackPageview({ app: 'infra_logs', path: 'log_entry_categories_results', delay: 15000 }); - const { - services: { ml, http }, - } = useKibanaContextForPlugin(); + const { + services: { ml, http }, + } = useKibanaContextForPlugin(); - const { hasLogAnalysisSetupCapabilities } = useLogAnalysisCapabilitiesContext(); + const { hasLogAnalysisSetupCapabilities } = useLogAnalysisCapabilitiesContext(); - const { - fetchJobStatus, - fetchModuleDefinition, - moduleDescriptor, - setupStatus, - hasOutdatedJobConfigurations, - hasOutdatedJobDefinitions, - hasStoppedJobs, - jobIds, - categoryQualityWarnings, - sourceConfiguration: { sourceId }, - } = useLogEntryCategoriesModuleContext(); + const { + fetchJobStatus, + fetchModuleDefinition, + moduleDescriptor, + setupStatus, + hasOutdatedJobConfigurations, + hasOutdatedJobDefinitions, + hasStoppedJobs, + jobIds, + categoryQualityWarnings, + sourceConfiguration: { sourceId }, + } = useLogEntryCategoriesModuleContext(); - const { - timeRange: selectedTimeRange, - setTimeRange: setSelectedTimeRange, - autoRefresh, - setAutoRefresh, - } = useLogEntryCategoriesResultsUrlState(); + const { + timeRange: selectedTimeRange, + setTimeRange: setSelectedTimeRange, + autoRefresh, + setAutoRefresh, + } = useLogEntryCategoriesResultsUrlState(); - const [categoryQueryTimeRange, setCategoryQueryTimeRange] = useState<{ - lastChangedTime: number; - timeRange: TimeRange; - }>(() => ({ - lastChangedTime: Date.now(), - timeRange: stringToNumericTimeRange(selectedTimeRange), - })); + const [categoryQueryTimeRange, setCategoryQueryTimeRange] = useState<{ + lastChangedTime: number; + timeRange: TimeRange; + }>(() => ({ + lastChangedTime: Date.now(), + timeRange: stringToNumericTimeRange(selectedTimeRange), + })); - const [categoryQueryDatasets, setCategoryQueryDatasets] = useState<string[]>([]); + const [categoryQueryDatasets, setCategoryQueryDatasets] = useState<string[]>([]); - const { services } = useKibana<{}>(); + const { services } = useKibana<{}>(); - const showLoadDataErrorNotification = useCallback( - (error: Error) => { - services.notifications?.toasts.addError(error, { - title: loadDataErrorTitle, - }); - }, - [services.notifications] - ); + const showLoadDataErrorNotification = useCallback( + (error: Error) => { + services.notifications?.toasts.addError(error, { + title: loadDataErrorTitle, + }); + }, + [services.notifications] + ); - const { - getLogEntryCategoryDatasets, - getTopLogEntryCategories, - isLoadingLogEntryCategoryDatasets, - isLoadingTopLogEntryCategories, - logEntryCategoryDatasets, - topLogEntryCategories, - sortOptions, - changeSortOptions, - } = useLogEntryCategoriesResults({ - categoriesCount: 25, - endTime: categoryQueryTimeRange.timeRange.endTime, - filteredDatasets: categoryQueryDatasets, - onGetTopLogEntryCategoriesError: showLoadDataErrorNotification, - sourceId, - startTime: categoryQueryTimeRange.timeRange.startTime, - }); + const { + getLogEntryCategoryDatasets, + getTopLogEntryCategories, + isLoadingLogEntryCategoryDatasets, + isLoadingTopLogEntryCategories, + logEntryCategoryDatasets, + topLogEntryCategories, + sortOptions, + changeSortOptions, + } = useLogEntryCategoriesResults({ + categoriesCount: 25, + endTime: categoryQueryTimeRange.timeRange.endTime, + filteredDatasets: categoryQueryDatasets, + onGetTopLogEntryCategoriesError: showLoadDataErrorNotification, + sourceId, + startTime: categoryQueryTimeRange.timeRange.startTime, + }); - const handleQueryTimeRangeChange = useCallback( - ({ start: startTime, end: endTime }: { start: string; end: string }) => { - setCategoryQueryTimeRange((previousQueryParameters) => ({ - ...previousQueryParameters, - timeRange: stringToNumericTimeRange({ startTime, endTime }), - lastChangedTime: Date.now(), - })); - }, - [setCategoryQueryTimeRange] - ); + const handleQueryTimeRangeChange = useCallback( + ({ start: startTime, end: endTime }: { start: string; end: string }) => { + setCategoryQueryTimeRange((previousQueryParameters) => ({ + ...previousQueryParameters, + timeRange: stringToNumericTimeRange({ startTime, endTime }), + lastChangedTime: Date.now(), + })); + }, + [setCategoryQueryTimeRange] + ); - const handleSelectedTimeRangeChange = useCallback( - (selectedTime: { start: string; end: string; isInvalid: boolean }) => { - if (selectedTime.isInvalid) { - return; - } - setSelectedTimeRange({ - startTime: selectedTime.start, - endTime: selectedTime.end, - }); - handleQueryTimeRangeChange(selectedTime); - }, - [setSelectedTimeRange, handleQueryTimeRangeChange] - ); + const handleSelectedTimeRangeChange = useCallback( + (selectedTime: { start: string; end: string; isInvalid: boolean }) => { + if (selectedTime.isInvalid) { + return; + } + setSelectedTimeRange({ + startTime: selectedTime.start, + endTime: selectedTime.end, + }); + handleQueryTimeRangeChange(selectedTime); + }, + [setSelectedTimeRange, handleQueryTimeRangeChange] + ); - const handleAutoRefreshChange = useCallback( - ({ isPaused, refreshInterval: interval }: { isPaused: boolean; refreshInterval: number }) => { - setAutoRefresh({ - isPaused, - interval, - }); - }, - [setAutoRefresh] - ); + const handleAutoRefreshChange = useCallback( + ({ isPaused, refreshInterval: interval }: { isPaused: boolean; refreshInterval: number }) => { + setAutoRefresh({ + isPaused, + interval, + }); + }, + [setAutoRefresh] + ); - const hasResults = useMemo(() => topLogEntryCategories.length > 0, [ - topLogEntryCategories.length, - ]); + const hasResults = useMemo( + () => topLogEntryCategories.length > 0, + [topLogEntryCategories.length] + ); - const isFirstUse = useMemo( - () => - ((setupStatus.type === 'skipped' && !!setupStatus.newlyCreated) || - setupStatus.type === 'succeeded') && - !hasResults, - [hasResults, setupStatus] - ); + const isFirstUse = useMemo( + () => + ((setupStatus.type === 'skipped' && !!setupStatus.newlyCreated) || + setupStatus.type === 'succeeded') && + !hasResults, + [hasResults, setupStatus] + ); - useEffect(() => { - getTopLogEntryCategories(); - }, [ - getTopLogEntryCategories, - categoryQueryDatasets, - categoryQueryTimeRange.lastChangedTime, - sortOptions, - ]); + useEffect(() => { + getTopLogEntryCategories(); + }, [ + getTopLogEntryCategories, + categoryQueryDatasets, + categoryQueryTimeRange.lastChangedTime, + sortOptions, + ]); - useEffect(() => { - getLogEntryCategoryDatasets(); - }, [getLogEntryCategoryDatasets, categoryQueryTimeRange.lastChangedTime]); + useEffect(() => { + getLogEntryCategoryDatasets(); + }, [getLogEntryCategoryDatasets, categoryQueryTimeRange.lastChangedTime]); - useEffect(() => { - fetchModuleDefinition(); - }, [fetchModuleDefinition]); + useEffect(() => { + fetchModuleDefinition(); + }, [fetchModuleDefinition]); - useInterval(() => { - fetchJobStatus(); - }, JOB_STATUS_POLLING_INTERVAL); + useInterval(() => { + fetchJobStatus(); + }, JOB_STATUS_POLLING_INTERVAL); - useInterval( - () => { - handleQueryTimeRangeChange({ - start: selectedTimeRange.startTime, - end: selectedTimeRange.endTime, - }); - }, - autoRefresh.isPaused ? null : autoRefresh.interval - ); + useInterval( + () => { + handleQueryTimeRangeChange({ + start: selectedTimeRange.startTime, + end: selectedTimeRange.endTime, + }); + }, + autoRefresh.isPaused ? null : autoRefresh.interval + ); - const analyzeInMlLink = useMlHref(ml, http.basePath.get(), { - page: ML_PAGES.ANOMALY_EXPLORER, - pageState: { - jobIds: [jobIds['log-entry-categories-count']], - timeRange: { - from: moment(categoryQueryTimeRange.timeRange.startTime).format('YYYY-MM-DDTHH:mm:ss.SSSZ'), - to: moment(categoryQueryTimeRange.timeRange.endTime).format('YYYY-MM-DDTHH:mm:ss.SSSZ'), - mode: 'absolute', + const analyzeInMlLink = useMlHref(ml, http.basePath.get(), { + page: ML_PAGES.ANOMALY_EXPLORER, + pageState: { + jobIds: [jobIds['log-entry-categories-count']], + timeRange: { + from: moment(categoryQueryTimeRange.timeRange.startTime).format( + 'YYYY-MM-DDTHH:mm:ss.SSSZ' + ), + to: moment(categoryQueryTimeRange.timeRange.endTime).format('YYYY-MM-DDTHH:mm:ss.SSSZ'), + mode: 'absolute', + }, }, - }, - }); + }); - return ( - <ViewLogInContext.Provider - sourceId={sourceId} - startTimestamp={categoryQueryTimeRange.timeRange.startTime} - endTimestamp={categoryQueryTimeRange.timeRange.endTime} - > - <LogsPageTemplate - pageHeader={{ - pageTitle, - rightSideItems: [ - <RecreateJobButton - hasSetupCapabilities={hasLogAnalysisSetupCapabilities} - onClick={onOpenSetup} - size="s" - />, - <AnalyzeInMlButton href={analyzeInMlLink} />, - ], - }} + return ( + <ViewLogInContext.Provider + sourceId={sourceId} + startTimestamp={categoryQueryTimeRange.timeRange.startTime} + endTimestamp={categoryQueryTimeRange.timeRange.endTime} > - <EuiFlexGroup direction="column"> - <EuiFlexItem grow={false}> - <EuiFlexGroup justifyContent="spaceBetween" alignItems="center"> - <EuiFlexItem> - <DatasetsSelector - availableDatasets={logEntryCategoryDatasets} - isLoading={isLoadingLogEntryCategoryDatasets} - onChangeDatasetSelection={setCategoryQueryDatasets} - selectedDatasets={categoryQueryDatasets} - /> - </EuiFlexItem> - <EuiFlexItem grow={false}> - <EuiSuperDatePicker - start={selectedTimeRange.startTime} - end={selectedTimeRange.endTime} - onTimeChange={handleSelectedTimeRangeChange} - isPaused={autoRefresh.isPaused} - refreshInterval={autoRefresh.interval} - onRefreshChange={handleAutoRefreshChange} - /> - </EuiFlexItem> - </EuiFlexGroup> - </EuiFlexItem> - <EuiFlexItem grow={false}> - <CategoryJobNoticesSection - hasOutdatedJobConfigurations={hasOutdatedJobConfigurations} - hasOutdatedJobDefinitions={hasOutdatedJobDefinitions} - hasSetupCapabilities={hasLogAnalysisSetupCapabilities} - hasStoppedJobs={hasStoppedJobs} - isFirstUse={isFirstUse} - moduleName={moduleDescriptor.moduleName} - onRecreateMlJobForReconfiguration={onOpenSetup} - onRecreateMlJobForUpdate={onOpenSetup} - qualityWarnings={categoryQualityWarnings} - /> - </EuiFlexItem> - <EuiFlexItem grow={false}> - <TopCategoriesSection - isLoadingTopCategories={isLoadingTopLogEntryCategories} - jobId={jobIds['log-entry-categories-count']} - sourceId={sourceId} - timeRange={categoryQueryTimeRange.timeRange} - topCategories={topLogEntryCategories} - sortOptions={sortOptions} - changeSortOptions={changeSortOptions} - /> - </EuiFlexItem> - </EuiFlexGroup> - </LogsPageTemplate> - <PageViewLogInContext /> - </ViewLogInContext.Provider> - ); -}; + <LogsPageTemplate + pageHeader={{ + pageTitle, + rightSideItems: [ + <RecreateJobButton + hasSetupCapabilities={hasLogAnalysisSetupCapabilities} + onClick={onOpenSetup} + size="s" + />, + <AnalyzeInMlButton href={analyzeInMlLink} />, + ], + }} + > + <EuiFlexGroup direction="column"> + <EuiFlexItem grow={false}> + <EuiFlexGroup justifyContent="spaceBetween" alignItems="center"> + <EuiFlexItem> + <DatasetsSelector + availableDatasets={logEntryCategoryDatasets} + isLoading={isLoadingLogEntryCategoryDatasets} + onChangeDatasetSelection={setCategoryQueryDatasets} + selectedDatasets={categoryQueryDatasets} + /> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiSuperDatePicker + start={selectedTimeRange.startTime} + end={selectedTimeRange.endTime} + onTimeChange={handleSelectedTimeRangeChange} + isPaused={autoRefresh.isPaused} + refreshInterval={autoRefresh.interval} + onRefreshChange={handleAutoRefreshChange} + /> + </EuiFlexItem> + </EuiFlexGroup> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <CategoryJobNoticesSection + hasOutdatedJobConfigurations={hasOutdatedJobConfigurations} + hasOutdatedJobDefinitions={hasOutdatedJobDefinitions} + hasSetupCapabilities={hasLogAnalysisSetupCapabilities} + hasStoppedJobs={hasStoppedJobs} + isFirstUse={isFirstUse} + moduleName={moduleDescriptor.moduleName} + onRecreateMlJobForReconfiguration={onOpenSetup} + onRecreateMlJobForUpdate={onOpenSetup} + qualityWarnings={categoryQualityWarnings} + /> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <TopCategoriesSection + isLoadingTopCategories={isLoadingTopLogEntryCategories} + jobId={jobIds['log-entry-categories-count']} + sourceId={sourceId} + timeRange={categoryQueryTimeRange.timeRange} + topCategories={topLogEntryCategories} + sortOptions={sortOptions} + changeSortOptions={changeSortOptions} + /> + </EuiFlexItem> + </EuiFlexGroup> + </LogsPageTemplate> + <PageViewLogInContext /> + </ViewLogInContext.Provider> + ); + }; const stringToNumericTimeRange = (timeRange: StringTimeRange): TimeRange => ({ startTime: moment( diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_setup_content.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_setup_content.tsx index 18adfaad03ef8..c012d97dd1fc0 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_setup_content.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_setup_content.tsx @@ -14,41 +14,40 @@ interface LogEntryCategoriesSetupContentProps { onOpenSetup: () => void; } -export const LogEntryCategoriesSetupContent: React.FunctionComponent<LogEntryCategoriesSetupContentProps> = ({ - onOpenSetup, -}) => { - useTrackPageview({ app: 'infra_logs', path: 'log_entry_categories_setup' }); - useTrackPageview({ app: 'infra_logs', path: 'log_entry_categories_setup', delay: 15000 }); +export const LogEntryCategoriesSetupContent: React.FunctionComponent<LogEntryCategoriesSetupContentProps> = + ({ onOpenSetup }) => { + useTrackPageview({ app: 'infra_logs', path: 'log_entry_categories_setup' }); + useTrackPageview({ app: 'infra_logs', path: 'log_entry_categories_setup', delay: 15000 }); - return ( - <EuiEmptyPrompt - data-test-subj="logEntryCategoriesSetupPage" - title={ - <h2> - <FormattedMessage - id="xpack.infra.logs.logEntryCategories.setupTitle" - defaultMessage="Set up log category analysis" - /> - </h2> - } - body={ - <EuiText size="s"> - <p> + return ( + <EuiEmptyPrompt + data-test-subj="logEntryCategoriesSetupPage" + title={ + <h2> <FormattedMessage - id="xpack.infra.logs.logEntryCategories.setupDescription" - defaultMessage="To enable log categories, set up a machine learning job." + id="xpack.infra.logs.logEntryCategories.setupTitle" + defaultMessage="Set up log category analysis" /> - </p> - </EuiText> - } - actions={ - <EuiButton fill onClick={onOpenSetup}> - <FormattedMessage - id="xpack.infra.logs.logEntryCategories.showAnalysisSetupButtonLabel" - defaultMessage="ML setup" - /> - </EuiButton> - } - /> - ); -}; + </h2> + } + body={ + <EuiText size="s"> + <p> + <FormattedMessage + id="xpack.infra.logs.logEntryCategories.setupDescription" + defaultMessage="To enable log categories, set up a machine learning job." + /> + </p> + </EuiText> + } + actions={ + <EuiButton fill onClick={onOpenSetup}> + <FormattedMessage + id="xpack.infra.logs.logEntryCategories.showAnalysisSetupButtonLabel" + defaultMessage="ML setup" + /> + </EuiButton> + } + /> + ); + }; diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/use_log_entry_categories_results.ts b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/use_log_entry_categories_results.ts index 73779af8752ee..b4cfb301e0402 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/use_log_entry_categories_results.ts +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/use_log_entry_categories_results.ts @@ -18,7 +18,8 @@ import { callGetLogEntryCategoryDatasetsAPI } from './service_calls/get_log_entr import { useKibanaContextForPlugin } from '../../../hooks/use_kibana'; type TopLogEntryCategories = GetLogEntryCategoriesSuccessResponsePayload['data']['categories']; -type LogEntryCategoryDatasets = GetLogEntryCategoryDatasetsSuccessResponsePayload['data']['datasets']; +type LogEntryCategoryDatasets = + GetLogEntryCategoryDatasetsSuccessResponsePayload['data']['datasets']; export type SortOptions = CategoriesSort; export type ChangeSortOptions = (sortOptions: CategoriesSort) => void; @@ -46,10 +47,8 @@ export const useLogEntryCategoriesResults = ({ }); const { services } = useKibanaContextForPlugin(); const [topLogEntryCategories, setTopLogEntryCategories] = useState<TopLogEntryCategories>([]); - const [ - logEntryCategoryDatasets, - setLogEntryCategoryDatasets, - ] = useState<LogEntryCategoryDatasets>([]); + const [logEntryCategoryDatasets, setLogEntryCategoryDatasets] = + useState<LogEntryCategoryDatasets>([]); const [getTopLogEntryCategoriesRequest, getTopLogEntryCategories] = useTrackedPromise( { diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_results_content.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_results_content.tsx index 2d833c87c1e25..2aac520dbc28a 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_results_content.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_results_content.tsx @@ -161,12 +161,14 @@ export const LogEntryRateResultsContent: React.FunctionComponent<{ const { showModuleList, showModuleSetup } = useLogAnalysisSetupFlyoutStateContext(); - const showLogEntryRateSetup = useCallback(() => showModuleSetup('logs_ui_analysis'), [ - showModuleSetup, - ]); - const showLogEntryCategoriesSetup = useCallback(() => showModuleSetup('logs_ui_categories'), [ - showModuleSetup, - ]); + const showLogEntryRateSetup = useCallback( + () => showModuleSetup('logs_ui_analysis'), + [showModuleSetup] + ); + const showLogEntryCategoriesSetup = useCallback( + () => showModuleSetup('logs_ui_categories'), + [showModuleSetup] + ); const hasAnomalyResults = logEntryAnomalies.length > 0; diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_anomalies_results.ts b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_anomalies_results.ts index 3cb0c4c89927c..80123bb10e550 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_anomalies_results.ts +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_anomalies_results.ts @@ -27,7 +27,8 @@ export type FetchPreviousPage = () => void; export type ChangeSortOptions = (sortOptions: AnomaliesSort) => void; export type ChangePaginationOptions = (paginationOptions: PaginationOptions) => void; export type LogEntryAnomalies = LogEntryAnomaly[]; -type LogEntryAnomaliesDatasets = GetLogEntryAnomaliesDatasetsSuccessResponsePayload['data']['datasets']; +type LogEntryAnomaliesDatasets = + GetLogEntryAnomaliesDatasetsSuccessResponsePayload['data']['datasets']; interface PaginationCursors { previousPageCursor: PaginationCursor; nextPageCursor: PaginationCursor; @@ -285,10 +286,8 @@ export const useLogEntryAnomaliesResults = ({ ); // Anomalies datasets - const [ - logEntryAnomaliesDatasets, - setLogEntryAnomaliesDatasets, - ] = useState<LogEntryAnomaliesDatasets>([]); + const [logEntryAnomaliesDatasets, setLogEntryAnomaliesDatasets] = + useState<LogEntryAnomaliesDatasets>([]); const [getLogEntryAnomaliesDatasetsRequest, getLogEntryAnomaliesDatasets] = useTrackedPromise( { @@ -315,9 +314,10 @@ export const useLogEntryAnomaliesResults = ({ [endTime, sourceId, startTime] ); - const isLoadingDatasets = useMemo(() => getLogEntryAnomaliesDatasetsRequest.state === 'pending', [ - getLogEntryAnomaliesDatasetsRequest.state, - ]); + const isLoadingDatasets = useMemo( + () => getLogEntryAnomaliesDatasetsRequest.state === 'pending', + [getLogEntryAnomaliesDatasetsRequest.state] + ); const hasFailedLoadingDatasets = useMemo( () => getLogEntryAnomaliesDatasetsRequest.state === 'rejected', diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_examples.ts b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_examples.ts index 34d14de4aa941..8f6269c46c3df 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_examples.ts +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_examples.ts @@ -53,9 +53,10 @@ export const useLogEntryExamples = ({ [dataset, endTime, exampleCount, sourceId, startTime] ); - const isLoadingLogEntryExamples = useMemo(() => getLogEntryExamplesRequest.state === 'pending', [ - getLogEntryExamplesRequest.state, - ]); + const isLoadingLogEntryExamples = useMemo( + () => getLogEntryExamplesRequest.state === 'pending', + [getLogEntryExamplesRequest.state] + ); const hasFailedLoadingLogEntryExamples = useMemo( () => getLogEntryExamplesRequest.state === 'rejected', diff --git a/x-pack/plugins/infra/public/pages/logs/settings/fields_configuration_panel.tsx b/x-pack/plugins/infra/public/pages/logs/settings/fields_configuration_panel.tsx index 3f8922b1871c9..d21b0b0588dea 100644 --- a/x-pack/plugins/infra/public/pages/logs/settings/fields_configuration_panel.tsx +++ b/x-pack/plugins/infra/public/pages/logs/settings/fields_configuration_panel.tsx @@ -118,18 +118,20 @@ export const FieldsConfigurationPanel = ({ defaultMessage="Timestamp" /> } - {...useMemo(() => getFormRowProps(timestampFieldFormElement), [ - timestampFieldFormElement, - ])} + {...useMemo( + () => getFormRowProps(timestampFieldFormElement), + [timestampFieldFormElement] + )} > <EuiFieldText fullWidth disabled={isLoading || isTimestampValueDefault} readOnly={isReadOnly} isLoading={isLoading} - {...useMemo(() => getStringInputFieldProps(timestampFieldFormElement), [ - timestampFieldFormElement, - ])} + {...useMemo( + () => getStringInputFieldProps(timestampFieldFormElement), + [timestampFieldFormElement] + )} /> </EuiFormRow> </EuiDescribedFormGroup> @@ -166,18 +168,20 @@ export const FieldsConfigurationPanel = ({ defaultMessage="Tiebreaker" /> } - {...useMemo(() => getFormRowProps(tiebreakerFieldFormElement), [ - tiebreakerFieldFormElement, - ])} + {...useMemo( + () => getFormRowProps(tiebreakerFieldFormElement), + [tiebreakerFieldFormElement] + )} > <EuiFieldText fullWidth disabled={isLoading || isTiebreakerValueDefault} readOnly={isReadOnly} isLoading={isLoading} - {...useMemo(() => getStringInputFieldProps(tiebreakerFieldFormElement), [ - tiebreakerFieldFormElement, - ])} + {...useMemo( + () => getStringInputFieldProps(tiebreakerFieldFormElement), + [tiebreakerFieldFormElement] + )} /> </EuiFormRow> </EuiDescribedFormGroup> diff --git a/x-pack/plugins/infra/public/pages/logs/settings/form_elements.tsx b/x-pack/plugins/infra/public/pages/logs/settings/form_elements.tsx index 90504a691cb95..8058a10bcd269 100644 --- a/x-pack/plugins/infra/public/pages/logs/settings/form_elements.tsx +++ b/x-pack/plugins/infra/public/pages/logs/settings/form_elements.tsx @@ -141,10 +141,10 @@ export const useCompositeFormElement = <FormValues extends {}, InvalidReason>({ } }, [childFormElementEntries]); - const validity = useMemo(() => getCombinedValidity(formValidity, childFormElementsValidity), [ - formValidity, - childFormElementsValidity, - ]); + const validity = useMemo( + () => getCombinedValidity(formValidity, childFormElementsValidity), + [formValidity, childFormElementsValidity] + ); const resetValue = useCallback(() => { childFormElementEntries.forEach(([, formElement]) => formElement.resetValue()); @@ -180,10 +180,10 @@ const useValidity = <Value, InvalidReason>( value: Value, validate?: (value: Value) => Promise<InvalidReason[]> ) => { - const validationState = useAsync(() => validate?.(value) ?? Promise.resolve([]), [ - validate, - value, - ]); + const validationState = useAsync( + () => validate?.(value) ?? Promise.resolve([]), + [validate, value] + ); const validity = useMemo<FormElementValidity<InvalidReason | GenericValidationError>>(() => { if (validationState.loading) { @@ -236,8 +236,9 @@ export const getCombinedValidity = <FirstInvalidReason, SecondInvalidReason>( } }; -export const isFormElementForType = <Value extends any>( - isValue: (value: any) => value is Value -) => <InvalidReason extends unknown>( - formElement: FormElement<any, InvalidReason> -): formElement is FormElement<Value, InvalidReason> => isValue(formElement.value); +export const isFormElementForType = + <Value extends any>(isValue: (value: any) => value is Value) => + <InvalidReason extends unknown>( + formElement: FormElement<any, InvalidReason> + ): formElement is FormElement<Value, InvalidReason> => + isValue(formElement.value); diff --git a/x-pack/plugins/infra/public/pages/logs/settings/form_field_props.tsx b/x-pack/plugins/infra/public/pages/logs/settings/form_field_props.tsx index 4a3927157b136..81711f7464da8 100644 --- a/x-pack/plugins/infra/public/pages/logs/settings/form_field_props.tsx +++ b/x-pack/plugins/infra/public/pages/logs/settings/form_field_props.tsx @@ -19,17 +19,19 @@ export const getFormRowProps = (formElement: FormElement<any, FormValidationErro isInvalid: formElement.validity.validity === 'invalid', }); -export const getInputFieldProps = <Value extends unknown>( - decodeInputValue: (value: string) => Value, - encodeInputValue: (value: Value) => string -) => (formElement: FormElement<Value, any>) => ({ - isInvalid: formElement.validity.validity === 'invalid', - onChange: (evt: React.ChangeEvent<HTMLInputElement>) => { - const newValue = evt.currentTarget.value; - formElement.updateValue(() => decodeInputValue(newValue)); - }, - value: encodeInputValue(formElement.value), -}); +export const getInputFieldProps = + <Value extends unknown>( + decodeInputValue: (value: string) => Value, + encodeInputValue: (value: Value) => string + ) => + (formElement: FormElement<Value, any>) => ({ + isInvalid: formElement.validity.validity === 'invalid', + onChange: (evt: React.ChangeEvent<HTMLInputElement>) => { + const newValue = evt.currentTarget.value; + formElement.updateValue(() => decodeInputValue(newValue)); + }, + value: encodeInputValue(formElement.value), + }); export const getStringInputFieldProps = getInputFieldProps<string>( (value) => `${value}`, diff --git a/x-pack/plugins/infra/public/pages/logs/settings/index_pattern_configuration_panel.tsx b/x-pack/plugins/infra/public/pages/logs/settings/index_pattern_configuration_panel.tsx index a16f15505bc30..7e466ee2a6ae9 100644 --- a/x-pack/plugins/infra/public/pages/logs/settings/index_pattern_configuration_panel.tsx +++ b/x-pack/plugins/infra/public/pages/logs/settings/index_pattern_configuration_panel.tsx @@ -79,10 +79,10 @@ export const IndexPatternConfigurationPanel: React.FC<{ defaultMessage="Log index pattern" /> } - {...useMemo(() => (isLoading ? {} : getFormRowProps(indexPatternFormElement)), [ - isLoading, - indexPatternFormElement, - ])} + {...useMemo( + () => (isLoading ? {} : getFormRowProps(indexPatternFormElement)), + [isLoading, indexPatternFormElement] + )} > <IndexPatternSelector isLoading={isLoading || indexPatternFormElement.validity.validity === 'pending'} diff --git a/x-pack/plugins/infra/public/pages/logs/settings/log_columns_configuration_panel.tsx b/x-pack/plugins/infra/public/pages/logs/settings/log_columns_configuration_panel.tsx index 70db7837b8ae5..148a18391b937 100644 --- a/x-pack/plugins/infra/public/pages/logs/settings/log_columns_configuration_panel.tsx +++ b/x-pack/plugins/infra/public/pages/logs/settings/log_columns_configuration_panel.tsx @@ -141,10 +141,10 @@ const LogColumnConfigurationPanel: React.FunctionComponent<{ dragHandleProps: DragHandleProps; onRemove: (logColumnConfiguration: LogColumnConfiguration) => void; }> = ({ logColumnConfiguration, dragHandleProps, onRemove }) => { - const removeColumn = useCallback(() => onRemove(logColumnConfiguration), [ - logColumnConfiguration, - onRemove, - ]); + const removeColumn = useCallback( + () => onRemove(logColumnConfiguration), + [logColumnConfiguration, onRemove] + ); return ( <> diff --git a/x-pack/plugins/infra/public/pages/logs/settings/source_configuration_form_state.tsx b/x-pack/plugins/infra/public/pages/logs/settings/source_configuration_form_state.tsx index 67e790a98f518..ac390a5bcfb8b 100644 --- a/x-pack/plugins/infra/public/pages/logs/settings/source_configuration_form_state.tsx +++ b/x-pack/plugins/infra/public/pages/logs/settings/source_configuration_form_state.tsx @@ -28,19 +28,16 @@ export const useLogSourceConfigurationFormState = ( ) ); - const { - fieldsFormElement, - tiebreakerFieldFormElement, - timestampFieldFormElement, - } = useFieldsFormElement( - useMemo( - () => ({ - tiebreakerField: configuration?.fields?.tiebreaker ?? '_doc', - timestampField: configuration?.fields?.timestamp ?? '@timestamp', - }), - [configuration] - ) - ); + const { fieldsFormElement, tiebreakerFieldFormElement, timestampFieldFormElement } = + useFieldsFormElement( + useMemo( + () => ({ + tiebreakerField: configuration?.fields?.tiebreaker ?? '_doc', + timestampField: configuration?.fields?.timestamp ?? '@timestamp', + }), + [configuration] + ) + ); const logColumnsFormElement = useLogColumnsFormElement( useMemo(() => configuration?.logColumns ?? [], [configuration]) diff --git a/x-pack/plugins/infra/public/pages/logs/settings/source_configuration_settings.tsx b/x-pack/plugins/infra/public/pages/logs/settings/source_configuration_settings.tsx index 51355cab1fba5..883c321db9ae6 100644 --- a/x-pack/plugins/infra/public/pages/logs/settings/source_configuration_settings.tsx +++ b/x-pack/plugins/infra/public/pages/logs/settings/source_configuration_settings.tsx @@ -76,10 +76,10 @@ export const LogsSettingsPage = () => { sourceConfigurationFormElement.resetValue(); }, [updateSource, sourceConfigurationFormElement, formState]); - const isWriteable = useMemo(() => shouldAllowEdit && source && source.origin !== 'internal', [ - shouldAllowEdit, - source, - ]); + const isWriteable = useMemo( + () => shouldAllowEdit && source && source.origin !== 'internal', + [shouldAllowEdit, source] + ); if ((isLoading || isUninitialized) && !resolvedSourceConfiguration) { return <SourceLoadingPage />; diff --git a/x-pack/plugins/infra/public/pages/logs/stream/page_toolbar.tsx b/x-pack/plugins/infra/public/pages/logs/stream/page_toolbar.tsx index 6d7f9fb09676a..015476687d468 100644 --- a/x-pack/plugins/infra/public/pages/logs/stream/page_toolbar.tsx +++ b/x-pack/plugins/infra/public/pages/logs/stream/page_toolbar.tsx @@ -27,12 +27,8 @@ export const LogsToolbar = () => { const { availableTextScales, setTextScale, setTextWrap, textScale, textWrap } = useContext( LogViewConfiguration.Context ); - const { - filterQueryDraft, - isFilterQueryDraftValid, - applyLogFilterQuery, - setLogFilterQueryDraft, - } = useContext(LogFilterState.Context); + const { filterQueryDraft, isFilterQueryDraftValid, applyLogFilterQuery, setLogFilterQueryDraft } = + useContext(LogFilterState.Context); const { setSurroundingLogsId } = useContext(LogFlyout.Context); const { diff --git a/x-pack/plugins/infra/public/pages/logs/stream/page_view_log_in_context.tsx b/x-pack/plugins/infra/public/pages/logs/stream/page_view_log_in_context.tsx index 5537ef9541f89..da507fac5272c 100644 --- a/x-pack/plugins/infra/public/pages/logs/stream/page_view_log_in_context.tsx +++ b/x-pack/plugins/infra/public/pages/logs/stream/page_view_log_in_context.tsx @@ -25,10 +25,8 @@ import { LogStream } from '../../../components/log_stream'; const MODAL_MARGIN = 25; export const PageViewLogInContext: React.FC = () => { - const [ - { contextEntry, startTimestamp, endTimestamp, sourceId }, - { setContextEntry }, - ] = useContext(ViewLogInContext.Context); + const [{ contextEntry, startTimestamp, endTimestamp, sourceId }, { setContextEntry }] = + useContext(ViewLogInContext.Context); const closeModal = useCallback(() => setContextEntry(undefined), [setContextEntry]); const { width: vw, height: vh } = useViewportDimensions(); diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomalies_table/anomalies_table.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomalies_table/anomalies_table.tsx index 98f3c82818dd2..05227dd1ed5e4 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomalies_table/anomalies_table.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/anomalies_table/anomalies_table.tsx @@ -268,16 +268,14 @@ export const AnomaliesTable = (props: Props) => { fetchPreviousPage: k8sPreviousPage, isLoadingMetricsK8sAnomalies: k8sLoading, } = useMetricsK8sAnomaliesResults(anomalyParams); - const page = useMemo(() => (jobType === 'hosts' ? hostPage : k8sPage), [ - jobType, - hostPage, - k8sPage, - ]); - const isLoading = useMemo(() => (jobType === 'hosts' ? hostLoading : k8sLoading), [ - jobType, - hostLoading, - k8sLoading, - ]); + const page = useMemo( + () => (jobType === 'hosts' ? hostPage : k8sPage), + [jobType, hostPage, k8sPage] + ); + const isLoading = useMemo( + () => (jobType === 'hosts' ? hostLoading : k8sLoading), + [jobType, hostLoading, k8sLoading] + ); const fetchNextPage = useMemo( () => (jobType === 'hosts' ? hostFetchNextPage : k8sFetchNextPage), [jobType, hostFetchNextPage, k8sFetchNextPage] diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/flyout_home.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/flyout_home.tsx index 993ae1d9d7571..a16aff903776e 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/flyout_home.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/flyout_home.tsx @@ -48,11 +48,8 @@ export const FlyoutHome = (props: Props) => { setupStatus: k8sSetupStatus, jobSummaries: k8sJobSummaries, } = useMetricK8sModuleContext(); - const { - hasInfraMLCapabilities, - hasInfraMLReadCapabilities, - hasInfraMLSetupCapabilities, - } = useInfraMLCapabilitiesContext(); + const { hasInfraMLCapabilities, hasInfraMLReadCapabilities, hasInfraMLSetupCapabilities } = + useInfraMLCapabilitiesContext(); const createHosts = useCallback(() => { goToSetup('hosts'); diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/job_setup_screen.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/job_setup_screen.tsx index f6d739078002e..fd75aa3bec7a6 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/job_setup_screen.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/job_setup_screen.tsx @@ -80,9 +80,10 @@ export const JobSetupScreen = (props: Props) => { } }, [props.jobType, k.jobSummaries, h.jobSummaries]); - const derivedIndexPattern = useMemo(() => createDerivedIndexPattern(), [ - createDerivedIndexPattern, - ]); + const derivedIndexPattern = useMemo( + () => createDerivedIndexPattern(), + [createDerivedIndexPattern] + ); const updateStart = useCallback((date: Moment) => { setStartDate(date); diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/overlay.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/overlay.tsx index e2803629afd51..f4c9f9b804135 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/overlay.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/overlay.tsx @@ -50,9 +50,10 @@ export const NodeContextPopover = ({ const inventoryModel = findInventoryModel(nodeType); const nodeDetailFrom = currentTime - inventoryModel.metrics.defaultTimeRangeInSeconds * 1000; const uiCapabilities = useKibana().services.application?.capabilities; - const canCreateAlerts = useMemo(() => Boolean(uiCapabilities?.infrastructure?.save), [ - uiCapabilities, - ]); + const canCreateAlerts = useMemo( + () => Boolean(uiCapabilities?.infrastructure?.save), + [uiCapabilities] + ); const tabs = useMemo(() => { return tabConfigs.map((m) => { diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/metrics/metrics.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/metrics/metrics.tsx index b554cb8024211..fbb8bd469c1e1 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/metrics/metrics.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/metrics/metrics.tsx @@ -72,9 +72,10 @@ const TabComponent = (props: TabProps) => { const { sourceId, createDerivedIndexPattern } = useSourceContext(); const { nodeType, accountId, region, customMetrics } = useWaffleOptionsContext(); const { currentTime, options, node } = props; - const derivedIndexPattern = useMemo(() => createDerivedIndexPattern('metrics'), [ - createDerivedIndexPattern, - ]); + const derivedIndexPattern = useMemo( + () => createDerivedIndexPattern('metrics'), + [createDerivedIndexPattern] + ); let filter = options.fields ? `${findInventoryFields(nodeType, options.fields).id}: "${node.id}"` : ''; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes/index.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes/index.tsx index df3b37c0aa386..c227a31edc4ab 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes/index.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes/index.tsx @@ -46,13 +46,12 @@ const TabComponent = ({ currentTime, node, nodeType, options }: TabProps) => { return { [field]: node.name }; }, [options, node, nodeType]); - const { loading, error, response, makeRequest: reload } = useProcessList( - hostTerm, - timefield, - currentTime, - sortBy, - parseSearchString(searchFilter) - ); + const { + loading, + error, + response, + makeRequest: reload, + } = useProcessList(hostTerm, timefield, currentTime, sortBy, parseSearchString(searchFilter)); const debouncedSearchOnChange = useMemo( () => debounce<(queryText: string) => void>((queryText) => setSearchFilter(queryText), 500), diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/timeline/timeline.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/timeline/timeline.tsx index dd1023e7f0185..65c3136cb48a6 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/timeline/timeline.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/timeline/timeline.tsx @@ -81,12 +81,10 @@ export const Timeline: React.FC<Props> = ({ interval, yAxisFormatter, isVisible defaultPaginationOptions: { pageSize: 100 }, }; - const { metricsHostsAnomalies, getMetricsHostsAnomalies } = useMetricsHostsAnomaliesResults( - anomalyParams - ); - const { metricsK8sAnomalies, getMetricsK8sAnomalies } = useMetricsK8sAnomaliesResults( - anomalyParams - ); + const { metricsHostsAnomalies, getMetricsHostsAnomalies } = + useMetricsHostsAnomaliesResults(anomalyParams); + const { metricsK8sAnomalies, getMetricsK8sAnomalies } = + useMetricsK8sAnomaliesResults(anomalyParams); const getAnomalies = useMemo(() => { if (nodeType === 'host') { diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/gradient_legend.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/gradient_legend.tsx index 2d3ff0beb2525..062349fa3ec6c 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/gradient_legend.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/gradient_legend.tsx @@ -21,20 +21,19 @@ interface Props { formatter: InfraFormatter; } -const createTickRender = (bounds: InfraWaffleMapBounds, formatter: InfraFormatter) => ( - rule: InfraWaffleMapGradientRule, - index: number -) => { - const value = rule.value === 0 ? bounds.min : bounds.max * rule.value; - const style = { left: `${rule.value * 100}%` }; - const label = formatter(value); - return ( - <GradientLegendTick style={style} key={`legend-rule-${index}`}> - <GradientLegendTickLine /> - <GradientLegendTickLabel>{label}</GradientLegendTickLabel> - </GradientLegendTick> - ); -}; +const createTickRender = + (bounds: InfraWaffleMapBounds, formatter: InfraFormatter) => + (rule: InfraWaffleMapGradientRule, index: number) => { + const value = rule.value === 0 ? bounds.min : bounds.max * rule.value; + const style = { left: `${rule.value * 100}%` }; + const label = formatter(value); + return ( + <GradientLegendTick style={style} key={`legend-rule-${index}`}> + <GradientLegendTickLine /> + <GradientLegendTickLabel>{label}</GradientLegendTickLabel> + </GradientLegendTick> + ); + }; export const GradientLegend: React.FC<Props> = ({ legend, bounds, formatter }) => { const maxValue = legend.rules.reduce((acc, rule) => { diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/waffle_time_controls.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/waffle_time_controls.tsx index 765ae9d131ba6..39150a98c2e89 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/waffle_time_controls.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/waffle_time_controls.tsx @@ -17,13 +17,8 @@ interface Props { } export const WaffleTimeControls = withTheme(({ theme }: Props) => { - const { - currentTime, - isAutoReloading, - startAutoReload, - stopAutoReload, - jumpToTime, - } = useWaffleTimeContext(); + const { currentTime, isAutoReloading, startAutoReload, stopAutoReload, jumpToTime } = + useWaffleTimeContext(); const currentMoment = moment(currentTime); diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_metrics_k8s_anomalies.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_metrics_k8s_anomalies.ts index 384cefa691d96..986c4dfa2f8e8 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_metrics_k8s_anomalies.ts +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_metrics_k8s_anomalies.ts @@ -315,16 +315,8 @@ export const callGetMetricsK8sAnomaliesAPI = async ( requestArgs: RequestArgs, fetch: HttpHandler ) => { - const { - sourceId, - anomalyThreshold, - startTime, - endTime, - metric, - query, - sort, - pagination, - } = requestArgs; + const { sourceId, anomalyThreshold, startTime, endTime, metric, query, sort, pagination } = + requestArgs; const response = await fetch(INFA_ML_GET_METRICS_K8S_ANOMALIES_PATH, { method: 'POST', body: JSON.stringify( diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_timeline.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_timeline.ts index b210379910c3f..53ff7e318e1f8 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_timeline.ts +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_timeline.ts @@ -77,9 +77,10 @@ export function useTimeline( const displayInterval = useMemo(() => getDisplayInterval(interval), [interval]); - const timeLengthResult = useMemo(() => getTimeLengthFromInterval(displayInterval), [ - displayInterval, - ]); + const timeLengthResult = useMemo( + () => getTimeLengthFromInterval(displayInterval), + [displayInterval] + ); const { timeLength, intervalInSeconds } = timeLengthResult; const endTime = currentTime + intervalInSeconds * 1000; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_waffle_filters.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_waffle_filters.ts index 90cf96330e758..67bc13251de20 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_waffle_filters.ts +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_waffle_filters.ts @@ -66,9 +66,10 @@ export const useWaffleFilters = () => { setFilterQueryDraft(filterQuery.expression); }, []); - const isFilterQueryDraftValid = useMemo(() => validateKuery(filterQueryDraft), [ - filterQueryDraft, - ]); + const isFilterQueryDraftValid = useMemo( + () => validateKuery(filterQueryDraft), + [filterQueryDraft] + ); const { inventoryPrefill } = useAlertPrefillContext(); const prefillContext = useMemo(() => inventoryPrefill, [inventoryPrefill]); // For Jest compatibility diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_waffle_options.test.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_waffle_options.test.ts index 0d8114af3d05c..06b6138404a68 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_waffle_options.test.ts +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_waffle_options.test.ts @@ -56,8 +56,7 @@ describe('useWaffleOptions', () => { customMetrics: [ { type: 'custom', - id: - "i don't want to bother to copy and paste an actual uuid so instead i'm going to smash my keyboard skjdghsjodkyjheurvjnsgn", + id: "i don't want to bother to copy and paste an actual uuid so instead i'm going to smash my keyboard skjdghsjodkyjheurvjnsgn", aggregation: 'avg', field: 'hey.system.are.you.good', }, diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/lib/create_inventory_metric_formatter.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/lib/create_inventory_metric_formatter.ts index b6ba48290010d..ca09762442d20 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/lib/create_inventory_metric_formatter.ts +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/lib/create_inventory_metric_formatter.ts @@ -78,17 +78,16 @@ const METRIC_FORMATTERS: MetricFormatters = { }, }; -export const createInventoryMetricFormatter = (metric: SnapshotMetricInput) => ( - val: string | number -) => { - if (SnapshotCustomMetricInputRT.is(metric)) { - const formatter = createFormatterForMetric(metric); +export const createInventoryMetricFormatter = + (metric: SnapshotMetricInput) => (val: string | number) => { + if (SnapshotCustomMetricInputRT.is(metric)) { + const formatter = createFormatterForMetric(metric); + return formatter(val); + } + const metricFormatter = get(METRIC_FORMATTERS, metric.type, METRIC_FORMATTERS.count); + if (val == null) { + return ''; + } + const formatter = createFormatter(metricFormatter.formatter, metricFormatter.template); return formatter(val); - } - const metricFormatter = get(METRIC_FORMATTERS, metric.type, METRIC_FORMATTERS.count); - if (val == null) { - return ''; - } - const formatter = createFormatter(metricFormatter.formatter, metricFormatter.template); - return formatter(val); -}; + }; diff --git a/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/gauges_section_vis.tsx b/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/gauges_section_vis.tsx index a4faa3d6c4755..709357719763b 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/gauges_section_vis.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/gauges_section_vis.tsx @@ -23,23 +23,25 @@ import { InventoryFormatterType } from '../../../../../common/inventory_models/t import { SeriesOverrides, VisSectionProps } from '../types'; import { getChartName } from './helpers'; -const getFormatter = ( - defaultFormatter: InventoryFormatterType = 'number', - defaultFormatterTemplate: string = '{{value}}', - seriesOverrides: SeriesOverrides = {}, - seriesId: string -) => (val: ReactText) => { - if (val == null) { - return ''; - } - const formatter = get(seriesOverrides, [seriesId, 'formatter'], defaultFormatter); - const formatterTemplate = get( - seriesOverrides, - [seriesId, 'formatterTemplate'], - defaultFormatterTemplate - ); - return createFormatter(formatter, formatterTemplate)(val); -}; +const getFormatter = + ( + defaultFormatter: InventoryFormatterType = 'number', + defaultFormatterTemplate: string = '{{value}}', + seriesOverrides: SeriesOverrides = {}, + seriesId: string + ) => + (val: ReactText) => { + if (val == null) { + return ''; + } + const formatter = get(seriesOverrides, [seriesId, 'formatter'], defaultFormatter); + const formatterTemplate = get( + seriesOverrides, + [seriesId, 'formatterTemplate'], + defaultFormatterTemplate + ); + return createFormatter(formatter, formatterTemplate)(val); + }; export const GaugesSectionVis = ({ id, diff --git a/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/helpers.ts b/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/helpers.ts index a0a795a760b58..0602e8a39b03b 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/helpers.ts +++ b/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/helpers.ts @@ -23,10 +23,10 @@ import { /** * Returns a formatter */ -export const getFormatter = ( - formatter: InventoryFormatterType = 'number', - template: string = '{{value}}' -) => (val: ReactText) => (val != null ? createFormatter(formatter, template)(val) : ''); +export const getFormatter = + (formatter: InventoryFormatterType = 'number', template: string = '{{value}}') => + (val: ReactText) => + val != null ? createFormatter(formatter, template)(val) : ''; /** * Does a series have more then two points? diff --git a/x-pack/plugins/infra/public/pages/metrics/metric_detail/page_providers.tsx b/x-pack/plugins/infra/public/pages/metrics/metric_detail/page_providers.tsx index c4e1b6bf8ef16..6f44d49ddce90 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metric_detail/page_providers.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/metric_detail/page_providers.tsx @@ -10,14 +10,15 @@ import React from 'react'; import { Source } from '../../../containers/metrics_source'; import { MetricsTimeProvider } from './hooks/use_metrics_time'; -export const withMetricPageProviders = <T extends object>(Component: React.ComponentType<T>) => ( - props: T -) => ( - <EuiErrorBoundary> - <Source.Provider sourceId="default"> - <MetricsTimeProvider> - <Component {...props} /> - </MetricsTimeProvider> - </Source.Provider> - </EuiErrorBoundary> -); +export const withMetricPageProviders = + <T extends object>(Component: React.ComponentType<T>) => + (props: T) => + ( + <EuiErrorBoundary> + <Source.Provider sourceId="default"> + <MetricsTimeProvider> + <Component {...props} /> + </MetricsTimeProvider> + </Source.Provider> + </EuiErrorBoundary> + ); diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/create_tsvb_link.test.ts b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/create_tsvb_link.test.ts index a7ed8763d1c72..a9e65bc30a3c6 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/create_tsvb_link.test.ts +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/create_tsvb_link.test.ts @@ -22,7 +22,7 @@ import { MetricsExplorerOptions } from '../../hooks/use_metrics_explorer_options jest.mock('uuid'); const mockedUuid = uuid as jest.Mocked<typeof uuid>; -mockedUuid.v1.mockReturnValue(('test-id' as unknown) as OutputBuffer); +mockedUuid.v1.mockReturnValue('test-id' as unknown as OutputBuffer); const series = { id: 'example-01', rows: [], columns: [] }; describe('createTSVBLink()', () => { @@ -32,8 +32,7 @@ describe('createTSVBLink()', () => { app: 'visualize', hash: '/create', search: { - _a: - "(filters:!(),linked:!f,query:(language:kuery,query:''),uiState:(),vis:(aggs:!(),params:(axis_formatter:number,axis_min:0,axis_position:left,axis_scale:normal,default_index_pattern:'metricbeat-*',filter:(language:kuery,query:'host.name : \"example-01\"'),id:test-id,index_pattern:'metricbeat-*',interval:auto,series:!((axis_position:right,chart_type:line,color:#6092C0,fill:0,formatter:percent,id:test-id,label:'avg(system.cpu.user.pct)',line_width:2,metrics:!((field:system.cpu.user.pct,id:test-id,type:avg)),point_size:0,separate_axis:0,split_mode:everything,stacked:none,value_template:{{value}})),show_grid:1,show_legend:1,time_field:'@timestamp',type:timeseries),title:example-01,type:metrics))", + _a: "(filters:!(),linked:!f,query:(language:kuery,query:''),uiState:(),vis:(aggs:!(),params:(axis_formatter:number,axis_min:0,axis_position:left,axis_scale:normal,default_index_pattern:'metricbeat-*',filter:(language:kuery,query:'host.name : \"example-01\"'),id:test-id,index_pattern:'metricbeat-*',interval:auto,series:!((axis_position:right,chart_type:line,color:#6092C0,fill:0,formatter:percent,id:test-id,label:'avg(system.cpu.user.pct)',line_width:2,metrics:!((field:system.cpu.user.pct,id:test-id,type:avg)),point_size:0,separate_axis:0,split_mode:everything,stacked:none,value_template:{{value}})),show_grid:1,show_legend:1,time_field:'@timestamp',type:timeseries),title:example-01,type:metrics))", _g: '(refreshInterval:(pause:!t,value:0),time:(from:now-1h,to:now))', type: 'metrics', }, @@ -50,8 +49,7 @@ describe('createTSVBLink()', () => { app: 'visualize', hash: '/create', search: { - _a: - "(filters:!(),linked:!f,query:(language:kuery,query:''),uiState:(),vis:(aggs:!(),params:(axis_formatter:number,axis_min:0,axis_position:left,axis_scale:normal,default_index_pattern:'metricbeat-*',filter:(language:kuery,query:'host.name : \"example-01\"'),id:test-id,index_pattern:'metricbeat-*',interval:auto,series:!((axis_position:right,chart_type:line,color:#6092C0,fill:0,formatter:bytes,id:test-id,label:'rate(system.network.out.bytes)',line_width:2,metrics:!((field:system.network.out.bytes,id:test-id,type:max),(field:test-id,id:test-id,type:derivative,unit:'1s'),(field:test-id,id:test-id,type:positive_only)),point_size:0,separate_axis:0,split_mode:everything,stacked:none,value_template:{{value}}/s)),show_grid:1,show_legend:1,time_field:'@timestamp',type:timeseries),title:example-01,type:metrics))", + _a: "(filters:!(),linked:!f,query:(language:kuery,query:''),uiState:(),vis:(aggs:!(),params:(axis_formatter:number,axis_min:0,axis_position:left,axis_scale:normal,default_index_pattern:'metricbeat-*',filter:(language:kuery,query:'host.name : \"example-01\"'),id:test-id,index_pattern:'metricbeat-*',interval:auto,series:!((axis_position:right,chart_type:line,color:#6092C0,fill:0,formatter:bytes,id:test-id,label:'rate(system.network.out.bytes)',line_width:2,metrics:!((field:system.network.out.bytes,id:test-id,type:max),(field:test-id,id:test-id,type:derivative,unit:'1s'),(field:test-id,id:test-id,type:positive_only)),point_size:0,separate_axis:0,split_mode:everything,stacked:none,value_template:{{value}}/s)),show_grid:1,show_legend:1,time_field:'@timestamp',type:timeseries),title:example-01,type:metrics))", _g: '(refreshInterval:(pause:!t,value:0),time:(from:now-1h,to:now))', type: 'metrics', }, @@ -64,8 +62,7 @@ describe('createTSVBLink()', () => { app: 'visualize', hash: '/create', search: { - _a: - "(filters:!(),linked:!f,query:(language:kuery,query:''),uiState:(),vis:(aggs:!(),params:(axis_formatter:number,axis_min:0,axis_position:left,axis_scale:normal,default_index_pattern:'metricbeat-*',filter:(language:kuery,query:'host.name : \"example-01\"'),id:test-id,index_pattern:'metricbeat-*',interval:auto,series:!((axis_position:right,chart_type:line,color:#6092C0,fill:0,formatter:percent,id:test-id,label:'avg(system.cpu.user.pct)',line_width:2,metrics:!((field:system.cpu.user.pct,id:test-id,type:avg)),point_size:0,separate_axis:0,split_mode:everything,stacked:none,value_template:{{value}})),show_grid:1,show_legend:1,time_field:'@timestamp',type:timeseries),title:example-01,type:metrics))", + _a: "(filters:!(),linked:!f,query:(language:kuery,query:''),uiState:(),vis:(aggs:!(),params:(axis_formatter:number,axis_min:0,axis_position:left,axis_scale:normal,default_index_pattern:'metricbeat-*',filter:(language:kuery,query:'host.name : \"example-01\"'),id:test-id,index_pattern:'metricbeat-*',interval:auto,series:!((axis_position:right,chart_type:line,color:#6092C0,fill:0,formatter:percent,id:test-id,label:'avg(system.cpu.user.pct)',line_width:2,metrics:!((field:system.cpu.user.pct,id:test-id,type:avg)),point_size:0,separate_axis:0,split_mode:everything,stacked:none,value_template:{{value}})),show_grid:1,show_legend:1,time_field:'@timestamp',type:timeseries),title:example-01,type:metrics))", _g: '(refreshInterval:(pause:!t,value:0),time:(from:now-10m,to:now))', type: 'metrics', }, @@ -82,8 +79,7 @@ describe('createTSVBLink()', () => { app: 'visualize', hash: '/create', search: { - _a: - "(filters:!(),linked:!f,query:(language:kuery,query:''),uiState:(),vis:(aggs:!(),params:(axis_formatter:number,axis_min:0,axis_position:left,axis_scale:normal,default_index_pattern:'my-beats-*',filter:(language:kuery,query:'host.name : \"example-01\"'),id:test-id,index_pattern:'my-beats-*',interval:auto,series:!((axis_position:right,chart_type:line,color:#6092C0,fill:0,formatter:percent,id:test-id,label:'avg(system.cpu.user.pct)',line_width:2,metrics:!((field:system.cpu.user.pct,id:test-id,type:avg)),point_size:0,separate_axis:0,split_mode:everything,stacked:none,value_template:{{value}})),show_grid:1,show_legend:1,time_field:time,type:timeseries),title:example-01,type:metrics))", + _a: "(filters:!(),linked:!f,query:(language:kuery,query:''),uiState:(),vis:(aggs:!(),params:(axis_formatter:number,axis_min:0,axis_position:left,axis_scale:normal,default_index_pattern:'my-beats-*',filter:(language:kuery,query:'host.name : \"example-01\"'),id:test-id,index_pattern:'my-beats-*',interval:auto,series:!((axis_position:right,chart_type:line,color:#6092C0,fill:0,formatter:percent,id:test-id,label:'avg(system.cpu.user.pct)',line_width:2,metrics:!((field:system.cpu.user.pct,id:test-id,type:avg)),point_size:0,separate_axis:0,split_mode:everything,stacked:none,value_template:{{value}})),show_grid:1,show_legend:1,time_field:time,type:timeseries),title:example-01,type:metrics))", _g: '(refreshInterval:(pause:!t,value:0),time:(from:now-1h,to:now))', type: 'metrics', }, @@ -101,8 +97,7 @@ describe('createTSVBLink()', () => { app: 'visualize', hash: '/create', search: { - _a: - "(filters:!(),linked:!f,query:(language:kuery,query:''),uiState:(),vis:(aggs:!(),params:(axis_formatter:number,axis_min:0,axis_position:left,axis_scale:normal,default_index_pattern:'my-beats-*',filter:(language:kuery,query:'system.network.name:lo* and host.name : \"example-01\"'),id:test-id,index_pattern:'my-beats-*',interval:auto,series:!((axis_position:right,chart_type:line,color:#6092C0,fill:0,formatter:percent,id:test-id,label:'avg(system.cpu.user.pct)',line_width:2,metrics:!((field:system.cpu.user.pct,id:test-id,type:avg)),point_size:0,separate_axis:0,split_mode:everything,stacked:none,value_template:{{value}})),show_grid:1,show_legend:1,time_field:time,type:timeseries),title:example-01,type:metrics))", + _a: "(filters:!(),linked:!f,query:(language:kuery,query:''),uiState:(),vis:(aggs:!(),params:(axis_formatter:number,axis_min:0,axis_position:left,axis_scale:normal,default_index_pattern:'my-beats-*',filter:(language:kuery,query:'system.network.name:lo* and host.name : \"example-01\"'),id:test-id,index_pattern:'my-beats-*',interval:auto,series:!((axis_position:right,chart_type:line,color:#6092C0,fill:0,formatter:percent,id:test-id,label:'avg(system.cpu.user.pct)',line_width:2,metrics:!((field:system.cpu.user.pct,id:test-id,type:avg)),point_size:0,separate_axis:0,split_mode:everything,stacked:none,value_template:{{value}})),show_grid:1,show_legend:1,time_field:time,type:timeseries),title:example-01,type:metrics))", _g: '(refreshInterval:(pause:!t,value:0),time:(from:now-1h,to:now))', type: 'metrics', }, @@ -116,8 +111,7 @@ describe('createTSVBLink()', () => { app: 'visualize', hash: '/create', search: { - _a: - "(filters:!(),linked:!f,query:(language:kuery,query:''),uiState:(),vis:(aggs:!(),params:(axis_formatter:number,axis_position:left,axis_scale:normal,default_index_pattern:'metricbeat-*',filter:(language:kuery,query:'host.name : \"example-01\"'),id:test-id,index_pattern:'metricbeat-*',interval:auto,series:!((axis_position:right,chart_type:line,color:#6092C0,fill:0,formatter:percent,id:test-id,label:'avg(system.cpu.user.pct)',line_width:2,metrics:!((field:system.cpu.user.pct,id:test-id,type:avg)),point_size:0,separate_axis:0,split_mode:everything,stacked:none,value_template:{{value}})),show_grid:1,show_legend:1,time_field:'@timestamp',type:timeseries),title:example-01,type:metrics))", + _a: "(filters:!(),linked:!f,query:(language:kuery,query:''),uiState:(),vis:(aggs:!(),params:(axis_formatter:number,axis_position:left,axis_scale:normal,default_index_pattern:'metricbeat-*',filter:(language:kuery,query:'host.name : \"example-01\"'),id:test-id,index_pattern:'metricbeat-*',interval:auto,series:!((axis_position:right,chart_type:line,color:#6092C0,fill:0,formatter:percent,id:test-id,label:'avg(system.cpu.user.pct)',line_width:2,metrics:!((field:system.cpu.user.pct,id:test-id,type:avg)),point_size:0,separate_axis:0,split_mode:everything,stacked:none,value_template:{{value}})),show_grid:1,show_legend:1,time_field:'@timestamp',type:timeseries),title:example-01,type:metrics))", _g: '(refreshInterval:(pause:!t,value:0),time:(from:now-1h,to:now))', type: 'metrics', }, @@ -131,8 +125,7 @@ describe('createTSVBLink()', () => { app: 'visualize', hash: '/create', search: { - _a: - "(filters:!(),linked:!f,query:(language:kuery,query:''),uiState:(),vis:(aggs:!(),params:(axis_formatter:number,axis_min:0,axis_position:left,axis_scale:normal,default_index_pattern:'metricbeat-*',filter:(language:kuery,query:'host.name : \"example-01\"'),id:test-id,index_pattern:'metricbeat-*',interval:auto,series:!((axis_position:right,chart_type:line,color:#6092C0,fill:0.5,formatter:percent,id:test-id,label:'avg(system.cpu.user.pct)',line_width:2,metrics:!((field:system.cpu.user.pct,id:test-id,type:avg)),point_size:0,separate_axis:0,split_mode:everything,stacked:none,value_template:{{value}})),show_grid:1,show_legend:1,time_field:'@timestamp',type:timeseries),title:example-01,type:metrics))", + _a: "(filters:!(),linked:!f,query:(language:kuery,query:''),uiState:(),vis:(aggs:!(),params:(axis_formatter:number,axis_min:0,axis_position:left,axis_scale:normal,default_index_pattern:'metricbeat-*',filter:(language:kuery,query:'host.name : \"example-01\"'),id:test-id,index_pattern:'metricbeat-*',interval:auto,series:!((axis_position:right,chart_type:line,color:#6092C0,fill:0.5,formatter:percent,id:test-id,label:'avg(system.cpu.user.pct)',line_width:2,metrics:!((field:system.cpu.user.pct,id:test-id,type:avg)),point_size:0,separate_axis:0,split_mode:everything,stacked:none,value_template:{{value}})),show_grid:1,show_legend:1,time_field:'@timestamp',type:timeseries),title:example-01,type:metrics))", _g: '(refreshInterval:(pause:!t,value:0),time:(from:now-1h,to:now))', type: 'metrics', }, @@ -150,8 +143,7 @@ describe('createTSVBLink()', () => { app: 'visualize', hash: '/create', search: { - _a: - "(filters:!(),linked:!f,query:(language:kuery,query:''),uiState:(),vis:(aggs:!(),params:(axis_formatter:number,axis_min:0,axis_position:left,axis_scale:normal,default_index_pattern:'metricbeat-*',filter:(language:kuery,query:'host.name : \"example-01\"'),id:test-id,index_pattern:'metricbeat-*',interval:auto,series:!((axis_position:right,chart_type:line,color:#6092C0,fill:0.5,formatter:percent,id:test-id,label:'avg(system.cpu.user.pct)',line_width:2,metrics:!((field:system.cpu.user.pct,id:test-id,type:avg)),point_size:0,separate_axis:0,split_mode:everything,stacked:stacked,value_template:{{value}})),show_grid:1,show_legend:1,time_field:'@timestamp',type:timeseries),title:example-01,type:metrics))", + _a: "(filters:!(),linked:!f,query:(language:kuery,query:''),uiState:(),vis:(aggs:!(),params:(axis_formatter:number,axis_min:0,axis_position:left,axis_scale:normal,default_index_pattern:'metricbeat-*',filter:(language:kuery,query:'host.name : \"example-01\"'),id:test-id,index_pattern:'metricbeat-*',interval:auto,series:!((axis_position:right,chart_type:line,color:#6092C0,fill:0.5,formatter:percent,id:test-id,label:'avg(system.cpu.user.pct)',line_width:2,metrics:!((field:system.cpu.user.pct,id:test-id,type:avg)),point_size:0,separate_axis:0,split_mode:everything,stacked:stacked,value_template:{{value}})),show_grid:1,show_legend:1,time_field:'@timestamp',type:timeseries),title:example-01,type:metrics))", _g: '(refreshInterval:(pause:!t,value:0),time:(from:now-1h,to:now))', type: 'metrics', }, @@ -169,8 +161,7 @@ describe('createTSVBLink()', () => { app: 'visualize', hash: '/create', search: { - _a: - "(filters:!(),linked:!f,query:(language:kuery,query:''),uiState:(),vis:(aggs:!(),params:(axis_formatter:number,axis_min:0,axis_position:left,axis_scale:normal,default_index_pattern:'metric*',filter:(language:kuery,query:'host.name : \"example-01\"'),id:test-id,index_pattern:'metric*',interval:auto,series:!((axis_position:right,chart_type:line,color:#6092C0,fill:0,formatter:percent,id:test-id,label:'avg(system.cpu.user.pct)',line_width:2,metrics:!((field:system.cpu.user.pct,id:test-id,type:avg)),point_size:0,separate_axis:0,split_mode:everything,stacked:none,value_template:{{value}})),show_grid:1,show_legend:1,time_field:time,type:timeseries),title:example-01,type:metrics))", + _a: "(filters:!(),linked:!f,query:(language:kuery,query:''),uiState:(),vis:(aggs:!(),params:(axis_formatter:number,axis_min:0,axis_position:left,axis_scale:normal,default_index_pattern:'metric*',filter:(language:kuery,query:'host.name : \"example-01\"'),id:test-id,index_pattern:'metric*',interval:auto,series:!((axis_position:right,chart_type:line,color:#6092C0,fill:0,formatter:percent,id:test-id,label:'avg(system.cpu.user.pct)',line_width:2,metrics:!((field:system.cpu.user.pct,id:test-id,type:avg)),point_size:0,separate_axis:0,split_mode:everything,stacked:none,value_template:{{value}})),show_grid:1,show_legend:1,time_field:time,type:timeseries),title:example-01,type:metrics))", _g: '(refreshInterval:(pause:!t,value:0),time:(from:now-1h,to:now))', type: 'metrics', }, diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/create_tsvb_link.ts b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/create_tsvb_link.ts index 1a549041823ec..84d87ee4ad1b7 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/create_tsvb_link.ts +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/create_tsvb_link.ts @@ -84,27 +84,26 @@ export const metricsExplorerMetricToTSVBMetric = (metric: MetricsExplorerOptions } }; -const mapMetricToSeries = (chartOptions: MetricsExplorerChartOptions) => ( - metric: MetricsExplorerOptionsMetric -) => { - const format = metricToFormat(metric); - return { - label: createMetricLabel(metric), - axis_position: 'right', - chart_type: 'line', - color: (metric.color && colorTransformer(metric.color)) || colorTransformer(Color.color0), - fill: chartOptions.type === MetricsExplorerChartType.area ? 0.5 : 0, - formatter: format === InfraFormatterType.bits ? InfraFormatterType.bytes : format, - value_template: 'rate' === metric.aggregation ? '{{value}}/s' : '{{value}}', - id: uuid.v1(), - line_width: 2, - metrics: metricsExplorerMetricToTSVBMetric(metric), - point_size: 0, - separate_axis: 0, - split_mode: 'everything', - stacked: chartOptions.stack ? 'stacked' : 'none', +const mapMetricToSeries = + (chartOptions: MetricsExplorerChartOptions) => (metric: MetricsExplorerOptionsMetric) => { + const format = metricToFormat(metric); + return { + label: createMetricLabel(metric), + axis_position: 'right', + chart_type: 'line', + color: (metric.color && colorTransformer(metric.color)) || colorTransformer(Color.color0), + fill: chartOptions.type === MetricsExplorerChartType.area ? 0.5 : 0, + formatter: format === InfraFormatterType.bits ? InfraFormatterType.bytes : format, + value_template: 'rate' === metric.aggregation ? '{{value}}/s' : '{{value}}', + id: uuid.v1(), + line_width: 2, + metrics: metricsExplorerMetricToTSVBMetric(metric), + point_size: 0, + separate_axis: 0, + split_mode: 'everything', + stacked: chartOptions.stack ? 'stacked' : 'none', + }; }; -}; export const createFilterFromOptions = ( options: MetricsExplorerOptions, diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/kuery_bar.tsx b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/kuery_bar.tsx index e22c6fa661181..cf201c9a57f7c 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/kuery_bar.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/kuery_bar.tsx @@ -99,5 +99,7 @@ export const MetricsExplorerKueryBar = ({ ); }; -const defaultCurryLoadSuggestions: CurryLoadSuggestionsType = (loadSuggestions) => (...args) => - loadSuggestions(...args); +const defaultCurryLoadSuggestions: CurryLoadSuggestionsType = + (loadSuggestions) => + (...args) => + loadSuggestions(...args); diff --git a/x-pack/plugins/infra/public/pages/metrics/settings/source_configuration_form_state.tsx b/x-pack/plugins/infra/public/pages/metrics/settings/source_configuration_form_state.tsx index 37da4bd1aa1bd..909bf294e4098 100644 --- a/x-pack/plugins/infra/public/pages/metrics/settings/source_configuration_form_state.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/settings/source_configuration_form_state.tsx @@ -32,21 +32,24 @@ export const useSourceConfigurationFormState = ( ), }); - const errors = useMemo(() => [...indicesConfigurationFormState.errors], [ - indicesConfigurationFormState.errors, - ]); + const errors = useMemo( + () => [...indicesConfigurationFormState.errors], + [indicesConfigurationFormState.errors] + ); const resetForm = useCallback(() => { indicesConfigurationFormState.resetForm(); }, [indicesConfigurationFormState]); - const isFormDirty = useMemo(() => indicesConfigurationFormState.isFormDirty, [ - indicesConfigurationFormState.isFormDirty, - ]); + const isFormDirty = useMemo( + () => indicesConfigurationFormState.isFormDirty, + [indicesConfigurationFormState.isFormDirty] + ); - const isFormValid = useMemo(() => indicesConfigurationFormState.isFormValid, [ - indicesConfigurationFormState.isFormValid, - ]); + const isFormValid = useMemo( + () => indicesConfigurationFormState.isFormValid, + [indicesConfigurationFormState.isFormValid] + ); const formState = useMemo( () => ({ diff --git a/x-pack/plugins/infra/public/pages/metrics/settings/source_configuration_settings.tsx b/x-pack/plugins/infra/public/pages/metrics/settings/source_configuration_settings.tsx index 62577b949bdba..0adf4ed6b5b45 100644 --- a/x-pack/plugins/infra/public/pages/metrics/settings/source_configuration_settings.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/settings/source_configuration_settings.tsx @@ -76,10 +76,10 @@ export const SourceConfigurationSettings = ({ formStateChanges, ]); - const isWriteable = useMemo(() => shouldAllowEdit && source && source.origin !== 'internal', [ - shouldAllowEdit, - source, - ]); + const isWriteable = useMemo( + () => shouldAllowEdit && source && source.origin !== 'internal', + [shouldAllowEdit, source] + ); const { hasInfraMLCapabilities } = useInfraMLCapabilitiesContext(); diff --git a/x-pack/plugins/infra/public/plugin.ts b/x-pack/plugins/infra/public/plugin.ts index 76e3e777e6378..712b7c01b9f0a 100644 --- a/x-pack/plugins/infra/public/plugin.ts +++ b/x-pack/plugins/infra/public/plugin.ts @@ -59,33 +59,39 @@ export class Plugin implements InfraClientPluginClass { /** !! Need to be kept in sync with the deepLinks in x-pack/plugins/infra/public/plugin.ts */ pluginsSetup.observability.navigation.registerSections( from(core.getStartServices()).pipe( - map(([{ application: { capabilities } }]) => [ - ...(capabilities.logs.show - ? [ - { - label: 'Logs', - sortKey: 200, - entries: [ - { label: 'Stream', app: 'logs', path: '/stream' }, - { label: 'Anomalies', app: 'logs', path: '/anomalies' }, - { label: 'Categories', app: 'logs', path: '/log-categories' }, - ], - }, - ] - : []), - ...(capabilities.infrastructure.show - ? [ - { - label: 'Metrics', - sortKey: 300, - entries: [ - { label: 'Inventory', app: 'metrics', path: '/inventory' }, - { label: 'Metrics Explorer', app: 'metrics', path: '/explorer' }, - ], - }, - ] - : []), - ]) + map( + ([ + { + application: { capabilities }, + }, + ]) => [ + ...(capabilities.logs.show + ? [ + { + label: 'Logs', + sortKey: 200, + entries: [ + { label: 'Stream', app: 'logs', path: '/stream' }, + { label: 'Anomalies', app: 'logs', path: '/anomalies' }, + { label: 'Categories', app: 'logs', path: '/log-categories' }, + ], + }, + ] + : []), + ...(capabilities.infrastructure.show + ? [ + { + label: 'Metrics', + sortKey: 300, + entries: [ + { label: 'Inventory', app: 'metrics', path: '/inventory' }, + { label: 'Metrics Explorer', app: 'metrics', path: '/explorer' }, + ], + }, + ] + : []), + ] + ) ) ); diff --git a/x-pack/plugins/infra/public/utils/data_search/normalize_data_search_responses.ts b/x-pack/plugins/infra/public/utils/data_search/normalize_data_search_responses.ts index 4c329110ae46e..71975af168f9f 100644 --- a/x-pack/plugins/infra/public/utils/data_search/normalize_data_search_responses.ts +++ b/x-pack/plugins/infra/public/utils/data_search/normalize_data_search_responses.ts @@ -12,9 +12,10 @@ import { AbortError } from '../../../../../../src/plugins/kibana_utils/public'; import { SearchStrategyError } from '../../../common/search_strategies/common/errors'; import { ParsedKibanaSearchResponse } from './types'; -export type RawResponseParser<RawResponse, Response> = ( - rawResponse: RawResponse -) => { data: Response; errors?: SearchStrategyError[] }; +export type RawResponseParser<RawResponse, Response> = (rawResponse: RawResponse) => { + data: Response; + errors?: SearchStrategyError[]; +}; /** * An operator factory that normalizes each {@link IKibanaSearchResponse} by @@ -30,49 +31,51 @@ export type RawResponseParser<RawResponse, Response> = ( * @return An operator that adds parsing and error handling transformations to * each response payload using the arguments given above. */ -export const normalizeDataSearchResponses = <RawResponse, Response, InitialResponse>( - initialResponse: InitialResponse, - parseRawResponse: RawResponseParser<RawResponse, Response> -) => ( - response$: Observable<IKibanaSearchResponse<RawResponse>> -): Observable<ParsedKibanaSearchResponse<Response | InitialResponse>> => - response$.pipe( - map((response) => { - const { data, errors = [] } = parseRawResponse(response.rawResponse); - return { - data, - errors, - isPartial: response.isPartial ?? false, - isRunning: response.isRunning ?? false, - loaded: response.loaded, - total: response.total, - }; - }), - startWith({ - data: initialResponse, - errors: [], - isPartial: true, - isRunning: true, - loaded: 0, - total: undefined, - }), - catchError((error) => - of({ +export const normalizeDataSearchResponses = + <RawResponse, Response, InitialResponse>( + initialResponse: InitialResponse, + parseRawResponse: RawResponseParser<RawResponse, Response> + ) => + ( + response$: Observable<IKibanaSearchResponse<RawResponse>> + ): Observable<ParsedKibanaSearchResponse<Response | InitialResponse>> => + response$.pipe( + map((response) => { + const { data, errors = [] } = parseRawResponse(response.rawResponse); + return { + data, + errors, + isPartial: response.isPartial ?? false, + isRunning: response.isRunning ?? false, + loaded: response.loaded, + total: response.total, + }; + }), + startWith({ data: initialResponse, - errors: [ - error instanceof AbortError - ? { - type: 'aborted' as const, - } - : { - type: 'generic' as const, - message: `${error.message ?? error}`, - }, - ], + errors: [], isPartial: true, - isRunning: false, + isRunning: true, loaded: 0, total: undefined, - }) - ) - ); + }), + catchError((error) => + of({ + data: initialResponse, + errors: [ + error instanceof AbortError + ? { + type: 'aborted' as const, + } + : { + type: 'generic' as const, + message: `${error.message ?? error}`, + }, + ], + isPartial: true, + isRunning: false, + loaded: 0, + total: undefined, + }) + ) + ); diff --git a/x-pack/plugins/infra/public/utils/data_search/use_latest_partial_data_search_response.test.tsx b/x-pack/plugins/infra/public/utils/data_search/use_latest_partial_data_search_response.test.tsx index 1ed912b0e01f6..71c49ef75023e 100644 --- a/x-pack/plugins/infra/public/utils/data_search/use_latest_partial_data_search_response.test.tsx +++ b/x-pack/plugins/infra/public/utils/data_search/use_latest_partial_data_search_response.test.tsx @@ -107,9 +107,8 @@ describe('useLatestPartialDataSearchResponse hook', () => { }), }; - const requests$ = of<ParsedDataSearchRequestDescriptor<IKibanaSearchRequest<string>, string>>( - firstRequest - ); + const requests$ = + of<ParsedDataSearchRequestDescriptor<IKibanaSearchRequest<string>, string>>(firstRequest); const { unmount } = renderHook(() => useLatestPartialDataSearchResponse(requests$)); diff --git a/x-pack/plugins/infra/public/utils/data_search/use_latest_partial_data_search_response.ts b/x-pack/plugins/infra/public/utils/data_search/use_latest_partial_data_search_response.ts index 741136b513116..2cafceaad3cb5 100644 --- a/x-pack/plugins/infra/public/utils/data_search/use_latest_partial_data_search_response.ts +++ b/x-pack/plugins/infra/public/utils/data_search/use_latest_partial_data_search_response.ts @@ -16,9 +16,8 @@ import { useDataSearchResponseState } from './use_data_search_response_state'; export const useLatestPartialDataSearchResponse = <Request extends IKibanaSearchRequest, Response>( requests$: Observable<ParsedDataSearchRequestDescriptor<Request, Response>> ) => { - const latestResponse$: Observable< - ParsedDataSearchResponseDescriptor<Request, Response> - > = useOperator(requests$, flattenLatestDataSearchResponse); + const latestResponse$: Observable<ParsedDataSearchResponseDescriptor<Request, Response>> = + useOperator(requests$, flattenLatestDataSearchResponse); const { cancelRequest, diff --git a/x-pack/plugins/infra/public/utils/loading_state/loading_progress.ts b/x-pack/plugins/infra/public/utils/loading_state/loading_progress.ts index 5ddc5761f03c3..4670883ec2a77 100644 --- a/x-pack/plugins/infra/public/utils/loading_state/loading_progress.ts +++ b/x-pack/plugins/infra/public/utils/loading_state/loading_progress.ts @@ -25,17 +25,19 @@ export const isRunningLoadingProgress = <P>( loadingProgress: LoadingProgress<P> ): loadingProgress is RunningLoadingProgress<P> => loadingProgress.progress === 'running'; -export const createIdleProgressReducer = <Parameters>() => ( - state: LoadingProgress<Parameters> -): IdleLoadingProgress => ({ - progress: 'idle', -}); +export const createIdleProgressReducer = + <Parameters>() => + (state: LoadingProgress<Parameters>): IdleLoadingProgress => ({ + progress: 'idle', + }); -export const createRunningProgressReducer = <Parameters>() => ( - state: LoadingProgress<Parameters>, - parameters: Parameters -): RunningLoadingProgress<Parameters> => ({ - parameters, - progress: 'running', - time: Date.now(), -}); +export const createRunningProgressReducer = + <Parameters>() => + ( + state: LoadingProgress<Parameters>, + parameters: Parameters + ): RunningLoadingProgress<Parameters> => ({ + parameters, + progress: 'running', + time: Date.now(), + }); diff --git a/x-pack/plugins/infra/public/utils/loading_state/loading_result.ts b/x-pack/plugins/infra/public/utils/loading_state/loading_result.ts index 683de7aeedc11..09411f6c6fce8 100644 --- a/x-pack/plugins/infra/public/utils/loading_state/loading_result.ts +++ b/x-pack/plugins/infra/public/utils/loading_state/loading_result.ts @@ -64,12 +64,15 @@ export const createSuccessResult = <Parameters = any, Payload = any>( time: Date.now(), }); -export const createSuccessResultReducer = <Parameters = any, Payload = any>( - isExhausted: (params: Parameters, result: Payload) => boolean -) => ( - state: LoadingResult<Parameters>, - { params, result }: { params: Parameters; result: Payload } -): SuccessLoadingResult<Parameters> => createSuccessResult(params, isExhausted(params, result)); +export const createSuccessResultReducer = + <Parameters = any, Payload = any>( + isExhausted: (params: Parameters, result: Payload) => boolean + ) => + ( + state: LoadingResult<Parameters>, + { params, result }: { params: Parameters; result: Payload } + ): SuccessLoadingResult<Parameters> => + createSuccessResult(params, isExhausted(params, result)); export const createFailureResult = <Parameters = any, ErrorPayload = any>( parameters: Parameters, @@ -81,9 +84,12 @@ export const createFailureResult = <Parameters = any, ErrorPayload = any>( time: Date.now(), }); -export const createFailureResultReducer = <Parameters = any, ErrorPayload = any>( - convertErrorToString: (error: ErrorPayload) => string = (error) => `${error}` -) => ( - state: LoadingResult<Parameters>, - { params, error }: { params: Parameters; error: ErrorPayload } -): FailureLoadingResult<Parameters> => createFailureResult(params, convertErrorToString(error)); +export const createFailureResultReducer = + <Parameters = any, ErrorPayload = any>( + convertErrorToString: (error: ErrorPayload) => string = (error) => `${error}` + ) => + ( + state: LoadingResult<Parameters>, + { params, error }: { params: Parameters; error: ErrorPayload } + ): FailureLoadingResult<Parameters> => + createFailureResult(params, convertErrorToString(error)); diff --git a/x-pack/plugins/infra/public/utils/logs_overview_fetches.test.ts b/x-pack/plugins/infra/public/utils/logs_overview_fetches.test.ts index 8d51f54e3f55a..6dfb400567717 100644 --- a/x-pack/plugins/infra/public/utils/logs_overview_fetches.test.ts +++ b/x-pack/plugins/infra/public/utils/logs_overview_fetches.test.ts @@ -21,9 +21,10 @@ const mockedCallFetchLogSourceStatusAPI = callFetchLogSourceStatusAPI as jest.Mo >; jest.mock('../containers/logs/log_source/api/fetch_log_source_configuration'); -const mockedCallFetchLogSourceConfigurationAPI = callFetchLogSourceConfigurationAPI as jest.MockedFunction< - typeof callFetchLogSourceConfigurationAPI ->; +const mockedCallFetchLogSourceConfigurationAPI = + callFetchLogSourceConfigurationAPI as jest.MockedFunction< + typeof callFetchLogSourceConfigurationAPI + >; const DEFAULT_PARAMS = { absoluteTime: { start: 1593430680000, end: 1593430800000 }, diff --git a/x-pack/plugins/infra/public/utils/styles.ts b/x-pack/plugins/infra/public/utils/styles.ts index db979a9ac68ee..bd0ba2099d95c 100644 --- a/x-pack/plugins/infra/public/utils/styles.ts +++ b/x-pack/plugins/infra/public/utils/styles.ts @@ -19,9 +19,7 @@ const asPropReader = (reader: string | string[] | PropReader) => ) => get(props, reader as Prop, defaultValue); export const switchProp = Object.assign( - (propName: string | string[] | PropReader, options: Map<any, any> | object) => ( - props: object - ) => { + (propName: string | string[] | PropReader, options: Map<any, any> | object) => (props: object) => { const propValue = asPropReader(propName)(props, switchProp.default); if (typeof propValue === 'undefined') { return; @@ -33,11 +31,10 @@ export const switchProp = Object.assign( } ); -export const ifProp = <Pass, Fail>( - propName: string | string[] | PropReader, - pass: Pass, - fail: Fail -) => (props: object) => (asPropReader(propName)(props) ? pass : fail); +export const ifProp = + <Pass, Fail>(propName: string | string[] | PropReader, pass: Pass, fail: Fail) => + (props: object) => + asPropReader(propName)(props) ? pass : fail; export const tintOrShade = ( textColor: string, diff --git a/x-pack/plugins/infra/public/utils/typed_redux.ts b/x-pack/plugins/infra/public/utils/typed_redux.ts index 04b3190c0146b..cd02539e719bb 100644 --- a/x-pack/plugins/infra/public/utils/typed_redux.ts +++ b/x-pack/plugins/infra/public/utils/typed_redux.ts @@ -23,15 +23,13 @@ export type GlobalSelectors<GlobalState, LocalSelectors extends Selectors> = { >; }; -export const globalizeSelector = < - GlobalState, - LocalSelector extends Selector<LocalState, Value>, - LocalState = any, - Value = any ->( - globalizer: Selector<GlobalState, LocalState>, - selector: LocalSelector -): Selector<GlobalState, Value> => (globalState: GlobalState) => selector(globalizer(globalState)); +export const globalizeSelector = + <GlobalState, LocalSelector extends Selector<LocalState, Value>, LocalState = any, Value = any>( + globalizer: Selector<GlobalState, LocalState>, + selector: LocalSelector + ): Selector<GlobalState, Value> => + (globalState: GlobalState) => + selector(globalizer(globalState)); export const globalizeSelectors = < GlobalState, @@ -63,9 +61,9 @@ type PlainActionCreator<WrappedActionCreator> = WrappedActionCreator extends () ? (payload: A) => R : never; -export const bindPlainActionCreators = <WrappedActionCreators extends ActionCreators>( - actionCreators: WrappedActionCreators -) => (dispatch: Dispatch) => - (bindActionCreators(actionCreators, dispatch) as unknown) as { - [P in keyof WrappedActionCreators]: PlainActionCreator<WrappedActionCreators[P]>; - }; +export const bindPlainActionCreators = + <WrappedActionCreators extends ActionCreators>(actionCreators: WrappedActionCreators) => + (dispatch: Dispatch) => + bindActionCreators(actionCreators, dispatch) as unknown as { + [P in keyof WrappedActionCreators]: PlainActionCreator<WrappedActionCreators[P]>; + }; diff --git a/x-pack/plugins/infra/public/utils/url_state.tsx b/x-pack/plugins/infra/public/utils/url_state.tsx index 0e6c38e893ca8..8148463ab3fe3 100644 --- a/x-pack/plugins/infra/public/utils/url_state.tsx +++ b/x-pack/plugins/infra/public/utils/url_state.tsx @@ -152,20 +152,19 @@ export const getParamFromQueryString = (queryString: string, key: string): strin return Array.isArray(queryParam) ? queryParam[0] : queryParam; }; -export const replaceStateKeyInQueryString = <UrlState extends any>( - stateKey: string, - urlState: UrlState | undefined -) => (queryString: string) => { - const previousQueryValues = parse(queryString, { sort: false }); - const newValue = - typeof urlState === 'undefined' - ? previousQueryValues - : { - ...previousQueryValues, - [stateKey]: encodeRisonUrlState(urlState), - }; - return stringify(url.encodeQuery(newValue), { sort: false, encode: false }); -}; +export const replaceStateKeyInQueryString = + <UrlState extends any>(stateKey: string, urlState: UrlState | undefined) => + (queryString: string) => { + const previousQueryValues = parse(queryString, { sort: false }); + const newValue = + typeof urlState === 'undefined' + ? previousQueryValues + : { + ...previousQueryValues, + [stateKey]: encodeRisonUrlState(urlState), + }; + return stringify(url.encodeQuery(newValue), { sort: false, encode: false }); + }; const replaceQueryStringInLocation = (location: Location, queryString: string): Location => { if (queryString === getQueryStringFromLocation(location)) { diff --git a/x-pack/plugins/infra/public/utils/use_observable.ts b/x-pack/plugins/infra/public/utils/use_observable.ts index 345d57e6fa5f6..87d182c94ac05 100644 --- a/x-pack/plugins/infra/public/utils/use_observable.ts +++ b/x-pack/plugins/infra/public/utils/use_observable.ts @@ -95,17 +95,19 @@ export const useOperator = <InputValue, OutputValue>( ); }; -export const tapUnsubscribe = (onUnsubscribe: () => void) => <T>(source$: Observable<T>) => { - return new Observable<T>((subscriber) => { - const subscription = source$.subscribe({ - next: (value) => subscriber.next(value), - error: (error) => subscriber.error(error), - complete: () => subscriber.complete(), +export const tapUnsubscribe = + (onUnsubscribe: () => void) => + <T>(source$: Observable<T>) => { + return new Observable<T>((subscriber) => { + const subscription = source$.subscribe({ + next: (value) => subscriber.next(value), + error: (error) => subscriber.error(error), + complete: () => subscriber.complete(), + }); + + return () => { + onUnsubscribe(); + subscription.unsubscribe(); + }; }); - - return () => { - onUnsubscribe(); - subscription.unsubscribe(); - }; - }); -}; + }; diff --git a/x-pack/plugins/infra/public/utils/use_tracked_promise.ts b/x-pack/plugins/infra/public/utils/use_tracked_promise.ts index 1b0c290bd6511..4380db2dcb4c4 100644 --- a/x-pack/plugins/infra/public/utils/use_tracked_promise.ts +++ b/x-pack/plugins/infra/public/utils/use_tracked_promise.ts @@ -113,109 +113,110 @@ export const useTrackedPromise = <Arguments extends any[], Result>( }); const execute = useMemo( - () => (...args: Arguments) => { - let rejectCancellationPromise!: (value: any) => void; - const cancellationPromise = new Promise<any>((_, reject) => { - rejectCancellationPromise = reject; - }); - - // remember the list of prior pending promises for cancellation - const previousPendingPromises = pendingPromises.current; - - const cancelPreviousPendingPromises = () => { - previousPendingPromises.forEach((promise) => promise.cancel()); - }; - - const newPromise = createPromise(...args); - const newCancelablePromise = Promise.race([newPromise, cancellationPromise]); - - // track this new state - setPromiseState({ - state: 'pending', - promise: newCancelablePromise, - }); - - if (cancelPreviousOn === 'creation') { - cancelPreviousPendingPromises(); - } - - const newPendingPromise: CancelablePromise<Result> = { - cancel: () => { - rejectCancellationPromise(new CanceledPromiseError()); - }, - cancelSilently: () => { - rejectCancellationPromise(new SilentCanceledPromiseError()); - }, - promise: newCancelablePromise.then( - (value) => { - setPromiseState((previousPromiseState) => - previousPromiseState.state === 'pending' && - previousPromiseState.promise === newCancelablePromise - ? { - state: 'resolved', - promise: newPendingPromise.promise, - value, - } - : previousPromiseState - ); - - if (['settlement', 'resolution'].includes(cancelPreviousOn)) { - cancelPreviousPendingPromises(); - } - - // remove itself from the list of pending promises - pendingPromises.current = pendingPromises.current.filter( - (pendingPromise) => pendingPromise.promise !== newPendingPromise.promise - ); - - if (onResolve && shouldTriggerOrThrow()) { - onResolve(value); - } - - return value; + () => + (...args: Arguments) => { + let rejectCancellationPromise!: (value: any) => void; + const cancellationPromise = new Promise<any>((_, reject) => { + rejectCancellationPromise = reject; + }); + + // remember the list of prior pending promises for cancellation + const previousPendingPromises = pendingPromises.current; + + const cancelPreviousPendingPromises = () => { + previousPendingPromises.forEach((promise) => promise.cancel()); + }; + + const newPromise = createPromise(...args); + const newCancelablePromise = Promise.race([newPromise, cancellationPromise]); + + // track this new state + setPromiseState({ + state: 'pending', + promise: newCancelablePromise, + }); + + if (cancelPreviousOn === 'creation') { + cancelPreviousPendingPromises(); + } + + const newPendingPromise: CancelablePromise<Result> = { + cancel: () => { + rejectCancellationPromise(new CanceledPromiseError()); }, - (value) => { - if (!(value instanceof SilentCanceledPromiseError)) { + cancelSilently: () => { + rejectCancellationPromise(new SilentCanceledPromiseError()); + }, + promise: newCancelablePromise.then( + (value) => { setPromiseState((previousPromiseState) => previousPromiseState.state === 'pending' && previousPromiseState.promise === newCancelablePromise ? { - state: 'rejected', - promise: newCancelablePromise, + state: 'resolved', + promise: newPendingPromise.promise, value, } : previousPromiseState ); - } - if (['settlement', 'rejection'].includes(cancelPreviousOn)) { - cancelPreviousPendingPromises(); - } + if (['settlement', 'resolution'].includes(cancelPreviousOn)) { + cancelPreviousPendingPromises(); + } - // remove itself from the list of pending promises - pendingPromises.current = pendingPromises.current.filter( - (pendingPromise) => pendingPromise.promise !== newPendingPromise.promise - ); + // remove itself from the list of pending promises + pendingPromises.current = pendingPromises.current.filter( + (pendingPromise) => pendingPromise.promise !== newPendingPromise.promise + ); - if (shouldTriggerOrThrow()) { - if (onReject) { - onReject(value); + if (onResolve && shouldTriggerOrThrow()) { + onResolve(value); } - throw value; + return value; + }, + (value) => { + if (!(value instanceof SilentCanceledPromiseError)) { + setPromiseState((previousPromiseState) => + previousPromiseState.state === 'pending' && + previousPromiseState.promise === newCancelablePromise + ? { + state: 'rejected', + promise: newCancelablePromise, + value, + } + : previousPromiseState + ); + } + + if (['settlement', 'rejection'].includes(cancelPreviousOn)) { + cancelPreviousPendingPromises(); + } + + // remove itself from the list of pending promises + pendingPromises.current = pendingPromises.current.filter( + (pendingPromise) => pendingPromise.promise !== newPendingPromise.promise + ); + + if (shouldTriggerOrThrow()) { + if (onReject) { + onReject(value); + } + + throw value; + } } - } - ), - }; + ), + }; - // add the new promise to the list of pending promises - pendingPromises.current = [...pendingPromises.current, newPendingPromise]; + // add the new promise to the list of pending promises + pendingPromises.current = [...pendingPromises.current, newPendingPromise]; - // silence "unhandled rejection" warnings - newPendingPromise.promise.catch(noOp); + // silence "unhandled rejection" warnings + newPendingPromise.promise.catch(noOp); - return newPendingPromise.promise; - }, + return newPendingPromise.promise; + }, // the dependencies are managed by the caller // eslint-disable-next-line react-hooks/exhaustive-deps dependencies diff --git a/x-pack/plugins/infra/public/utils/use_url_state.ts b/x-pack/plugins/infra/public/utils/use_url_state.ts index 970b3a20b2951..eef82438ff017 100644 --- a/x-pack/plugins/infra/public/utils/use_url_state.ts +++ b/x-pack/plugins/infra/public/utils/use_url_state.ts @@ -105,21 +105,20 @@ const getParamFromQueryString = (queryString: string, key: string) => { return Array.isArray(queryParam) ? queryParam[0] : queryParam; }; -export const replaceStateKeyInQueryString = <UrlState extends any>( - stateKey: string, - urlState: UrlState | undefined -) => (queryString: string) => { - const previousQueryValues = parse(queryString, { sort: false }); - const newValue = - typeof urlState === 'undefined' - ? previousQueryValues - : { - ...previousQueryValues, - [stateKey]: encodeRisonUrlState(urlState), - }; - - return stringify(url.encodeQuery(newValue), { sort: false, encode: false }); -}; +export const replaceStateKeyInQueryString = + <UrlState extends any>(stateKey: string, urlState: UrlState | undefined) => + (queryString: string) => { + const previousQueryValues = parse(queryString, { sort: false }); + const newValue = + typeof urlState === 'undefined' + ? previousQueryValues + : { + ...previousQueryValues, + [stateKey]: encodeRisonUrlState(urlState), + }; + + return stringify(url.encodeQuery(newValue), { sort: false, encode: false }); + }; const replaceQueryStringInLocation = (location: Location, queryString: string): Location => { if (queryString === getQueryStringFromLocation(location)) { diff --git a/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts b/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts index 3cd435ab0f6e8..4d4a0ff6320bd 100644 --- a/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts +++ b/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts @@ -15,7 +15,7 @@ import { PluginStart as DataPluginStart, } from '../../../../../../../src/plugins/data/server'; import { HomeServerPluginSetup } from '../../../../../../../src/plugins/home/server'; -import { VisTypeTimeseriesSetup } from '../../../../../../../src/plugins/vis_type_timeseries/server'; +import { VisTypeTimeseriesSetup } from '../../../../../../../src/plugins/vis_types/timeseries/server'; import { PluginSetupContract as FeaturesPluginSetup } from '../../../../../../plugins/features/server'; import { SpacesPluginSetup } from '../../../../../../plugins/spaces/server'; import { PluginSetupContract as AlertingPluginContract } from '../../../../../alerting/server'; diff --git a/x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts index 2aede2f6aad16..4576a2e8452ac 100644 --- a/x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts +++ b/x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts @@ -34,7 +34,7 @@ import { RequestHandler } from '../../../../../../../src/core/server'; import { InfraConfig } from '../../../plugin'; import type { InfraPluginRequestHandlerContext } from '../../../types'; import { UI_SETTINGS } from '../../../../../../../src/plugins/data/server'; -import { TimeseriesVisData } from '../../../../../../../src/plugins/vis_type_timeseries/server'; +import { TimeseriesVisData } from '../../../../../../../src/plugins/vis_types/timeseries/server'; import { InfraServerPluginStartDeps } from './adapter_types'; export class KibanaFramework { diff --git a/x-pack/plugins/infra/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts b/x-pack/plugins/infra/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts index 4ad2fa656f9b2..ab98de7901a3d 100644 --- a/x-pack/plugins/infra/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts +++ b/x-pack/plugins/infra/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts @@ -268,9 +268,7 @@ const createFilterClauses = ( const createQueryFilterClauses = (filterQuery: LogEntryQuery | undefined) => filterQuery ? [filterQuery] : []; -function processCursor( - cursor: LogEntriesParams['cursor'] -): { +function processCursor(cursor: LogEntriesParams['cursor']): { sortDirection: 'asc' | 'desc'; searchAfterClause: { search_after?: readonly [number, number] }; } { diff --git a/x-pack/plugins/infra/server/lib/adapters/metrics/kibana_metrics_adapter.ts b/x-pack/plugins/infra/server/lib/adapters/metrics/kibana_metrics_adapter.ts index a9ddcc8d3d4c1..730da9511dc38 100644 --- a/x-pack/plugins/infra/server/lib/adapters/metrics/kibana_metrics_adapter.ts +++ b/x-pack/plugins/infra/server/lib/adapters/metrics/kibana_metrics_adapter.ts @@ -21,7 +21,7 @@ import { import { calculateMetricInterval } from '../../../utils/calculate_metric_interval'; import { CallWithRequestParams, InfraDatabaseSearchResponse } from '../framework'; import type { InfraPluginRequestHandlerContext } from '../../../types'; -import { isVisSeriesData } from '../../../../../../../src/plugins/vis_type_timeseries/server'; +import { isVisSeriesData } from '../../../../../../../src/plugins/vis_types/timeseries/server'; export class KibanaMetricsAdapter implements InfraMetricsAdapter { private framework: KibanaFramework; diff --git a/x-pack/plugins/infra/server/lib/adapters/source_status/elasticsearch_source_status_adapter.ts b/x-pack/plugins/infra/server/lib/adapters/source_status/elasticsearch_source_status_adapter.ts index 079d39c080bf5..0aa305a580ffa 100644 --- a/x-pack/plugins/infra/server/lib/adapters/source_status/elasticsearch_source_status_adapter.ts +++ b/x-pack/plugins/infra/server/lib/adapters/source_status/elasticsearch_source_status_adapter.ts @@ -76,11 +76,11 @@ export class InfraElasticsearchSourceStatusAdapter implements InfraSourceStatusA } } -const withDefaultIfNotFound = <DefaultValue>(defaultValue: DefaultValue) => ( - error: any -): DefaultValue => { - if (error && error.status === 404) { - return defaultValue; - } - throw error; -}; +const withDefaultIfNotFound = + <DefaultValue>(defaultValue: DefaultValue) => + (error: any): DefaultValue => { + if (error && error.status === 404) { + return defaultValue; + } + throw error; + }; diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts index ddfc575438faa..bbbb0e917d518 100644 --- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts @@ -70,13 +70,8 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = InventoryMetricThresholdAlertInstanceContext, InventoryMetricThresholdAllowedActionGroups >(async ({ services, params }) => { - const { - criteria, - filterQuery, - sourceId, - nodeType, - alertOnNoData, - } = params as InventoryMetricThresholdParams; + const { criteria, filterQuery, sourceId, nodeType, alertOnNoData } = + params as InventoryMetricThresholdParams; if (criteria.length === 0) throw new Error('Cannot execute an alert with 0 conditions'); const { alertWithLifecycle, savedObjectsClient } = services; const alertInstanceFactory: InventoryMetricThresholdAlertInstanceFactory = (id, reason) => @@ -185,7 +180,7 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = * TODO: We're lying to the compiler here as explicitly calling `scheduleActions` on * the RecoveredActionGroup isn't allowed */ - (actionGroupId as unknown) as InventoryMetricThresholdAllowedActionGroups, + actionGroupId as unknown as InventoryMetricThresholdAllowedActionGroups, { group: item, alertState: stateToAlertMessage[nextState], diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/metric_anomaly_executor.ts b/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/metric_anomaly_executor.ts index 2282a7ff255d1..f7dbe95b4161c 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/metric_anomaly_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_anomaly/metric_anomaly_executor.ts @@ -25,80 +25,79 @@ import { KibanaRequest } from '../../../../../../../src/core/server'; import { InfraBackendLibs } from '../../infra_types'; import { evaluateCondition } from './evaluate_condition'; -export const createMetricAnomalyExecutor = (libs: InfraBackendLibs, ml?: MlPluginSetup) => async ({ - services, - params, - startedAt, -}: AlertExecutorOptions< - /** - * TODO: Remove this use of `any` by utilizing a proper type - */ - Record<string, any>, - Record<string, any>, - AlertInstanceState, - AlertInstanceContext, - MetricAnomalyAllowedActionGroups ->) => { - if (!ml) { - return; - } - const request = {} as KibanaRequest; - const mlSystem = ml.mlSystemProvider(request, services.savedObjectsClient); - const mlAnomalyDetectors = ml.anomalyDetectorsProvider(request, services.savedObjectsClient); +export const createMetricAnomalyExecutor = + (libs: InfraBackendLibs, ml?: MlPluginSetup) => + async ({ + services, + params, + startedAt, + }: AlertExecutorOptions< + /** + * TODO: Remove this use of `any` by utilizing a proper type + */ + Record<string, any>, + Record<string, any>, + AlertInstanceState, + AlertInstanceContext, + MetricAnomalyAllowedActionGroups + >) => { + if (!ml) { + return; + } + const request = {} as KibanaRequest; + const mlSystem = ml.mlSystemProvider(request, services.savedObjectsClient); + const mlAnomalyDetectors = ml.anomalyDetectorsProvider(request, services.savedObjectsClient); - const { - metric, - alertInterval, - influencerFilter, - sourceId, - spaceId, - nodeType, - threshold, - } = params as MetricAnomalyParams; + const { metric, alertInterval, influencerFilter, sourceId, spaceId, nodeType, threshold } = + params as MetricAnomalyParams; - const bucketInterval = getIntervalInSeconds('15m') * 1000; - const alertIntervalInMs = getIntervalInSeconds(alertInterval ?? '1m') * 1000; + const bucketInterval = getIntervalInSeconds('15m') * 1000; + const alertIntervalInMs = getIntervalInSeconds(alertInterval ?? '1m') * 1000; - const endTime = startedAt.getTime(); - // Anomalies are bucketed at :00, :15, :30, :45 minutes every hour - const previousBucketStartTime = endTime - (endTime % bucketInterval); + const endTime = startedAt.getTime(); + // Anomalies are bucketed at :00, :15, :30, :45 minutes every hour + const previousBucketStartTime = endTime - (endTime % bucketInterval); - // If the alert interval is less than 15m, make sure that it actually queries an anomaly bucket - const startTime = Math.min(endTime - alertIntervalInMs, previousBucketStartTime); + // If the alert interval is less than 15m, make sure that it actually queries an anomaly bucket + const startTime = Math.min(endTime - alertIntervalInMs, previousBucketStartTime); - const { data } = await evaluateCondition({ - sourceId: sourceId ?? 'default', - spaceId: spaceId ?? 'default', - mlSystem, - mlAnomalyDetectors, - startTime, - endTime, - metric, - threshold, - nodeType, - influencerFilter, - }); + const { data } = await evaluateCondition({ + sourceId: sourceId ?? 'default', + spaceId: spaceId ?? 'default', + mlSystem, + mlAnomalyDetectors, + startTime, + endTime, + metric, + threshold, + nodeType, + influencerFilter, + }); - const shouldAlertFire = data.length > 0; + const shouldAlertFire = data.length > 0; - if (shouldAlertFire) { - const { startTime: anomalyStartTime, anomalyScore, actual, typical, influencers } = first( - data as MappedAnomalyHit[] - )!; - const alertInstance = services.alertInstanceFactory(`${nodeType}-${metric}`); + if (shouldAlertFire) { + const { + startTime: anomalyStartTime, + anomalyScore, + actual, + typical, + influencers, + } = first(data as MappedAnomalyHit[])!; + const alertInstance = services.alertInstanceFactory(`${nodeType}-${metric}`); - alertInstance.scheduleActions(FIRED_ACTIONS_ID, { - alertState: stateToAlertMessage[AlertStates.ALERT], - timestamp: moment(anomalyStartTime).toISOString(), - anomalyScore, - actual, - typical, - metric: metricNameMap[metric], - summary: generateSummaryMessage(actual, typical), - influencers: influencers.join(', '), - }); - } -}; + alertInstance.scheduleActions(FIRED_ACTIONS_ID, { + alertState: stateToAlertMessage[AlertStates.ALERT], + timestamp: moment(anomalyStartTime).toISOString(), + anomalyScore, + actual, + typical, + metric: metricNameMap[metric], + summary: generateSummaryMessage(actual, typical), + influencers: influencers.join(', '), + }); + } + }; export const FIRED_ACTIONS_ID = 'metrics.anomaly.fired'; export const FIRED_ACTIONS: ActionGroup<typeof FIRED_ACTIONS_ID> = { diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_alert.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_alert.ts index a099b83fdb423..c7c1eb5454d1d 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_alert.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_alert.ts @@ -195,7 +195,7 @@ const getMetric: ( return { [UNGROUPED_FACTORY_KEY]: getValuesFromAggregations( - (result.aggregations! as unknown) as Aggregation, + result.aggregations! as unknown as Aggregation, aggType, dropPartialBucketsOptions, calculatedTimerange, @@ -225,16 +225,18 @@ interface DropPartialBucketOptions { bucketSizeInMillis: number; } -const dropPartialBuckets = ({ from, to, bucketSizeInMillis }: DropPartialBucketOptions) => ( - row: { - key: string; - value: number | null; - } | null -) => { - if (row == null) return null; - const timestamp = new Date(row.key).valueOf(); - return timestamp >= from && timestamp + bucketSizeInMillis <= to; -}; +const dropPartialBuckets = + ({ from, to, bucketSizeInMillis }: DropPartialBucketOptions) => + ( + row: { + key: string; + value: number | null; + } | null + ) => { + if (row == null) return null; + const timestamp = new Date(row.key).valueOf(); + return timestamp >= from && timestamp + bucketSizeInMillis <= to; + }; const getValuesFromAggregations = ( aggregations: Aggregation | undefined, diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts index 259318b6c93a1..9c99ad6bf49e2 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts @@ -207,14 +207,8 @@ const formatAlertResult = <AlertResult>( } & AlertResult, useWarningThreshold?: boolean ) => { - const { - metric, - currentValue, - threshold, - comparator, - warningThreshold, - warningComparator, - } = alertResult; + const { metric, currentValue, threshold, comparator, warningThreshold, warningComparator } = + alertResult; const noDataValue = i18n.translate( 'xpack.infra.metrics.alerting.threshold.noDataFormattedValue', { diff --git a/x-pack/plugins/infra/server/lib/create_search_client.ts b/x-pack/plugins/infra/server/lib/create_search_client.ts index afe16801dd2a2..6688ae1af1afc 100644 --- a/x-pack/plugins/infra/server/lib/create_search_client.ts +++ b/x-pack/plugins/infra/server/lib/create_search_client.ts @@ -9,10 +9,9 @@ import type { InfraPluginRequestHandlerContext } from '../types'; import { CallWithRequestParams, InfraDatabaseSearchResponse } from './adapters/framework'; import { KibanaFramework } from './adapters/framework/kibana_framework_adapter'; -export const createSearchClient = ( - requestContext: InfraPluginRequestHandlerContext, - framework: KibanaFramework -) => <Hit = {}, Aggregation = undefined>( - opts: CallWithRequestParams -): Promise<InfraDatabaseSearchResponse<Hit, Aggregation>> => - framework.callWithRequest(requestContext, 'search', opts); +export const createSearchClient = + (requestContext: InfraPluginRequestHandlerContext, framework: KibanaFramework) => + <Hit = {}, Aggregation = undefined>( + opts: CallWithRequestParams + ): Promise<InfraDatabaseSearchResponse<Hit, Aggregation>> => + framework.callWithRequest(requestContext, 'search', opts); diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts b/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts index f6be310d79ed2..16209e5e4b684 100644 --- a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts +++ b/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts @@ -166,28 +166,26 @@ export class InfraLogEntriesDomain { id: doc.id, index: doc.index, cursor: doc.cursor, - columns: columnDefinitions.map( - (column): LogColumn => { - if ('timestampColumn' in column) { - return { - columnId: column.timestampColumn.id, - timestamp: doc.cursor.time, - }; - } else if ('messageColumn' in column) { - return { - columnId: column.messageColumn.id, - message: messageFormattingRules.format(doc.fields, doc.highlights), - }; - } else { - return { - columnId: column.fieldColumn.id, - field: column.fieldColumn.field, - value: doc.fields[column.fieldColumn.field] ?? [], - highlights: doc.highlights[column.fieldColumn.field] ?? [], - }; - } + columns: columnDefinitions.map((column): LogColumn => { + if ('timestampColumn' in column) { + return { + columnId: column.timestampColumn.id, + timestamp: doc.cursor.time, + }; + } else if ('messageColumn' in column) { + return { + columnId: column.messageColumn.id, + message: messageFormattingRules.format(doc.fields, doc.highlights), + }; + } else { + return { + columnId: column.fieldColumn.id, + field: column.fieldColumn.field, + value: doc.fields[column.fieldColumn.field] ?? [], + highlights: doc.highlights[column.fieldColumn.field] ?? [], + }; } - ), + }), context: getContextFromDoc(doc), }; }); diff --git a/x-pack/plugins/infra/server/lib/host_details/process_list.ts b/x-pack/plugins/infra/server/lib/host_details/process_list.ts index e51e06af83f94..a9125c73fe5d0 100644 --- a/x-pack/plugins/infra/server/lib/host_details/process_list.ts +++ b/x-pack/plugins/infra/server/lib/host_details/process_list.ts @@ -129,8 +129,8 @@ export const getProcessList = async ( let summary: { [p: string]: number } = {}; if (result.aggregations!.summaryEvent.summary.hits.hits.length) { - summary = result.aggregations!.summaryEvent.summary.hits.hits[0]._source.system.process - .summary; + summary = + result.aggregations!.summaryEvent.summary.hits.hits[0]._source.system.process.summary; } return { diff --git a/x-pack/plugins/infra/server/lib/log_analysis/log_entry_categories_analysis.ts b/x-pack/plugins/infra/server/lib/log_analysis/log_entry_categories_analysis.ts index 7739f39cb5624..7023f7007763c 100644 --- a/x-pack/plugins/infra/server/lib/log_analysis/log_entry_categories_analysis.ts +++ b/x-pack/plugins/infra/server/lib/log_analysis/log_entry_categories_analysis.ts @@ -244,15 +244,14 @@ async function fetchTopLogEntryCategories( const topLogEntryCategories = topLogEntryCategoriesResponse.aggregations?.terms_category_id.buckets.map( (topCategoryBucket) => { - const maximumAnomalyScoresByDataset = topCategoryBucket.filter_record.terms_dataset.buckets.reduce< - Record<string, number> - >( - (accumulatedMaximumAnomalyScores, datasetFromRecord) => ({ - ...accumulatedMaximumAnomalyScores, - [datasetFromRecord.key]: datasetFromRecord.maximum_record_score.value ?? 0, - }), - {} - ); + const maximumAnomalyScoresByDataset = + topCategoryBucket.filter_record.terms_dataset.buckets.reduce<Record<string, number>>( + (accumulatedMaximumAnomalyScores, datasetFromRecord) => ({ + ...accumulatedMaximumAnomalyScores, + [datasetFromRecord.key]: datasetFromRecord.maximum_record_score.value ?? 0, + }), + {} + ); return { categoryId: parseCategoryId(topCategoryBucket.key), diff --git a/x-pack/plugins/infra/server/lib/log_analysis/log_entry_categories_datasets_stats.ts b/x-pack/plugins/infra/server/lib/log_analysis/log_entry_categories_datasets_stats.ts index 7c92a81e1a896..8c3f739339152 100644 --- a/x-pack/plugins/infra/server/lib/log_analysis/log_entry_categories_datasets_stats.ts +++ b/x-pack/plugins/infra/server/lib/log_analysis/log_entry_categories_datasets_stats.ts @@ -34,16 +34,17 @@ export async function getLatestLogEntriesCategoriesDatasetsStats( let afterLatestBatchKey: CompositeDatasetKey | undefined; while (true) { - const latestLogEntryCategoriesDatasetsStatsResponse = await context.infra.mlSystem.mlAnomalySearch( - createLatestLogEntryCategoriesDatasetsStatsQuery( - jobIds, - startTime, - endTime, - COMPOSITE_AGGREGATION_BATCH_SIZE, - afterLatestBatchKey - ), - jobIds - ); + const latestLogEntryCategoriesDatasetsStatsResponse = + await context.infra.mlSystem.mlAnomalySearch( + createLatestLogEntryCategoriesDatasetsStatsQuery( + jobIds, + startTime, + endTime, + COMPOSITE_AGGREGATION_BATCH_SIZE, + afterLatestBatchKey + ), + jobIds + ); const { after_key: afterKey, buckets: latestBatchBuckets = [] } = decodeOrThrow(latestLogEntryCategoriesDatasetsStatsResponseRT)( diff --git a/x-pack/plugins/infra/server/lib/metrics/lib/convert_histogram_buckets_to_timeseries.ts b/x-pack/plugins/infra/server/lib/metrics/lib/convert_histogram_buckets_to_timeseries.ts index f6761f72fabb9..82bd99896839f 100644 --- a/x-pack/plugins/infra/server/lib/metrics/lib/convert_histogram_buckets_to_timeseries.ts +++ b/x-pack/plugins/infra/server/lib/metrics/lib/convert_histogram_buckets_to_timeseries.ts @@ -64,9 +64,9 @@ const getValue = (valueObject: ValueObjectType) => { return null; }; -const dropOutOfBoundsBuckets = (from: number, to: number, bucketSizeInMillis: number) => ( - row: MetricsAPIRow -) => row.timestamp >= from && row.timestamp + bucketSizeInMillis <= to; +const dropOutOfBoundsBuckets = + (from: number, to: number, bucketSizeInMillis: number) => (row: MetricsAPIRow) => + row.timestamp >= from && row.timestamp + bucketSizeInMillis <= to; const convertBucketsToRows = ( options: MetricsAPIRequest, diff --git a/x-pack/plugins/infra/server/lib/sources/saved_object_references.test.ts b/x-pack/plugins/infra/server/lib/sources/saved_object_references.test.ts index 7d31f7342b05b..9f6f9cd284c67 100644 --- a/x-pack/plugins/infra/server/lib/sources/saved_object_references.test.ts +++ b/x-pack/plugins/infra/server/lib/sources/saved_object_references.test.ts @@ -17,8 +17,14 @@ describe('extractSavedObjectReferences function', () => { sourceConfigurationWithIndexPatternReference ); - expect(references).toMatchObject([{ id: 'INDEX_PATTERN_ID' }]); + expect(references).toMatchObject([ + { id: 'INDEX_PATTERN_ID' }, + { id: 'INVENTORY_DEFAULT_VIEW' }, + { id: 'METRICS_EXPLORER_DEFAULT_VIEW' }, + ]); expect(attributes).toHaveProperty(['logIndices', 'indexPatternId'], references[0].name); + expect(attributes).toHaveProperty(['inventoryDefaultView'], references[1].name); + expect(attributes).toHaveProperty(['metricsExplorerDefaultView'], references[2].name); }); it('ignores log index name references', () => { @@ -26,7 +32,29 @@ describe('extractSavedObjectReferences function', () => { sourceConfigurationWithIndexNameReference ); - expect(references).toHaveLength(0); + expect(references).toHaveLength(2); + expect(attributes).toHaveProperty(['logIndices', 'indexName'], 'INDEX_NAME'); + }); + + it('ignores default inventory view', () => { + const { attributes, references } = extractSavedObjectReferences({ + ...sourceConfigurationWithIndexNameReference, + inventoryDefaultView: '0', + }); + + expect(references).toHaveLength(1); + expect(references).toMatchObject([{ id: 'METRICS_EXPLORER_DEFAULT_VIEW' }]); + expect(attributes).toHaveProperty(['logIndices', 'indexName'], 'INDEX_NAME'); + }); + + it('ignores default metrics explorer view', () => { + const { attributes, references } = extractSavedObjectReferences({ + ...sourceConfigurationWithIndexNameReference, + metricsExplorerDefaultView: '0', + }); + + expect(references).toHaveLength(1); + expect(references).toMatchObject([{ id: 'INVENTORY_DEFAULT_VIEW' }]); expect(attributes).toHaveProperty(['logIndices', 'indexName'], 'INDEX_NAME'); }); }); diff --git a/x-pack/plugins/infra/server/lib/sources/saved_object_references.ts b/x-pack/plugins/infra/server/lib/sources/saved_object_references.ts index 31f36380cc23e..9ad8c9951d4f3 100644 --- a/x-pack/plugins/infra/server/lib/sources/saved_object_references.ts +++ b/x-pack/plugins/infra/server/lib/sources/saved_object_references.ts @@ -27,9 +27,11 @@ interface SavedObjectAttributesWithReferences<SavedObjectAttributes> { export const extractSavedObjectReferences = ( sourceConfiguration: InfraSourceConfiguration ): SavedObjectAttributesWithReferences<InfraSourceConfiguration> => - [extractLogIndicesSavedObjectReferences].reduce< - SavedObjectAttributesWithReferences<InfraSourceConfiguration> - >( + [ + extractLogIndicesSavedObjectReferences, + extractInventorySavedViewReferences, + extractMetricsExplorerSavedViewReferences, + ].reduce<SavedObjectAttributesWithReferences<InfraSourceConfiguration>>( ({ attributes: accumulatedAttributes, references: accumulatedReferences }, extract) => { const { attributes, references } = extract(accumulatedAttributes); return { @@ -52,7 +54,11 @@ export const resolveSavedObjectReferences = ( attributes: InfraSavedSourceConfiguration, references: SavedObjectReference[] ): InfraSavedSourceConfiguration => - [resolveLogIndicesSavedObjectReferences].reduce<InfraSavedSourceConfiguration>( + [ + resolveLogIndicesSavedObjectReferences, + resolveInventoryViewSavedObjectReferences, + resolveMetricsExplorerSavedObjectReferences, + ].reduce<InfraSavedSourceConfiguration>( (accumulatedAttributes, resolve) => resolve(accumulatedAttributes, references), attributes ); @@ -85,6 +91,58 @@ const extractLogIndicesSavedObjectReferences = ( } }; +const extractInventorySavedViewReferences = ( + sourceConfiguration: InfraSourceConfiguration +): SavedObjectAttributesWithReferences<InfraSourceConfiguration> => { + const { inventoryDefaultView } = sourceConfiguration; + if (inventoryDefaultView && inventoryDefaultView !== '0') { + const inventoryDefaultViewReference: SavedObjectReference = { + id: inventoryDefaultView, + type: 'inventory-view', + name: 'inventory-saved-view-0', + }; + const attributes: InfraSourceConfiguration = { + ...sourceConfiguration, + inventoryDefaultView: inventoryDefaultViewReference.name, + }; + return { + attributes, + references: [inventoryDefaultViewReference], + }; + } else { + return { + attributes: sourceConfiguration, + references: [], + }; + } +}; + +const extractMetricsExplorerSavedViewReferences = ( + sourceConfiguration: InfraSourceConfiguration +): SavedObjectAttributesWithReferences<InfraSourceConfiguration> => { + const { metricsExplorerDefaultView } = sourceConfiguration; + if (metricsExplorerDefaultView && metricsExplorerDefaultView !== '0') { + const metricsExplorerDefaultViewReference: SavedObjectReference = { + id: metricsExplorerDefaultView, + type: 'metrics-explorer-view', + name: 'metrics-explorer-saved-view-0', + }; + const attributes: InfraSourceConfiguration = { + ...sourceConfiguration, + metricsExplorerDefaultView: metricsExplorerDefaultViewReference.name, + }; + return { + attributes, + references: [metricsExplorerDefaultViewReference], + }; + } else { + return { + attributes: sourceConfiguration, + references: [], + }; + } +}; + const resolveLogIndicesSavedObjectReferences = ( attributes: InfraSavedSourceConfiguration, references: SavedObjectReference[] @@ -111,3 +169,51 @@ const resolveLogIndicesSavedObjectReferences = ( return attributes; } }; + +const resolveInventoryViewSavedObjectReferences = ( + attributes: InfraSavedSourceConfiguration, + references: SavedObjectReference[] +): InfraSavedSourceConfiguration => { + if (attributes.inventoryDefaultView && attributes.inventoryDefaultView !== '0') { + const inventoryViewReference = references.find( + (reference) => reference.name === 'inventory-saved-view-0' + ); + + if (inventoryViewReference == null) { + throw new SavedObjectReferenceResolutionError( + 'Failed to resolve Inventory default view "inventory-saved-view-0".' + ); + } + + return { + ...attributes, + inventoryDefaultView: inventoryViewReference.id, + }; + } else { + return attributes; + } +}; + +const resolveMetricsExplorerSavedObjectReferences = ( + attributes: InfraSavedSourceConfiguration, + references: SavedObjectReference[] +): InfraSavedSourceConfiguration => { + if (attributes.metricsExplorerDefaultView && attributes.metricsExplorerDefaultView !== '0') { + const metricsExplorerViewReference = references.find( + (reference) => reference.name === 'metrics-explorer-saved-view-0' + ); + + if (metricsExplorerViewReference == null) { + throw new SavedObjectReferenceResolutionError( + 'Failed to resolve Metrics Explorer default view "metrics-explorer-saved-view-0".' + ); + } + + return { + ...attributes, + metricsExplorerDefaultView: metricsExplorerViewReference.id, + }; + } else { + return attributes; + } +}; diff --git a/x-pack/plugins/infra/server/lib/sources/sources.ts b/x-pack/plugins/infra/server/lib/sources/sources.ts index 7dc47388bd1da..45da4546ad3b8 100644 --- a/x-pack/plugins/infra/server/lib/sources/sources.ts +++ b/x-pack/plugins/infra/server/lib/sources/sources.ts @@ -205,11 +205,12 @@ export class InfraSources { } private async getStaticDefaultSourceConfiguration() { - const staticSourceConfiguration: SourceConfigurationConfigFileProperties['sources']['default'] = pipe( - sourceConfigurationConfigFilePropertiesRT.decode(this.libs.config), - map(({ sources: { default: defaultConfiguration } }) => defaultConfiguration), - fold(constant({}), identity) - ); + const staticSourceConfiguration: SourceConfigurationConfigFileProperties['sources']['default'] = + pipe( + sourceConfigurationConfigFilePropertiesRT.decode(this.libs.config), + map(({ sources: { default: defaultConfiguration } }) => defaultConfiguration), + fold(constant({}), identity) + ); // NOTE: Legacy logAlias needs converting to a logIndices reference until we can remove // config file sources in 8.0.0. diff --git a/x-pack/plugins/infra/server/plugin.ts b/x-pack/plugins/infra/server/plugin.ts index de445affc178e..b77b81cf41ee1 100644 --- a/x-pack/plugins/infra/server/plugin.ts +++ b/x-pack/plugins/infra/server/plugin.ts @@ -9,7 +9,12 @@ import { Server } from '@hapi/hapi'; import { schema, TypeOf } from '@kbn/config-schema'; import { i18n } from '@kbn/i18n'; import { Logger } from '@kbn/logging'; -import { CoreSetup, PluginInitializerContext, Plugin } from 'src/core/server'; +import { + CoreSetup, + PluginInitializerContext, + Plugin, + PluginConfigDescriptor, +} from 'src/core/server'; import { LOGS_FEATURE_ID, METRICS_FEATURE_ID } from '../common/constants'; import { InfraStaticSourceConfiguration } from '../common/source_configuration/source_configuration'; import { inventoryViewSavedObjectType } from '../common/saved_objects/inventory_view'; @@ -36,7 +41,7 @@ import { createGetLogQueryFields } from './services/log_queries/get_log_query_fi import { handleEsError } from '../../../../src/plugins/es_ui_shared/server'; import { RulesService } from './services/rules'; -export const config = { +export const config: PluginConfigDescriptor = { schema: schema.object({ enabled: schema.boolean({ defaultValue: true }), inventory: schema.object({ @@ -63,6 +68,7 @@ export const config = { }) ), }), + deprecations: ({ deprecate }) => [deprecate('enabled', '8.0.0')], }; export type InfraConfig = TypeOf<typeof config.schema>; diff --git a/x-pack/plugins/infra/server/routes/metadata/index.ts b/x-pack/plugins/infra/server/routes/metadata/index.ts index cc8888e9bd09d..39021ba51c9d9 100644 --- a/x-pack/plugins/infra/server/routes/metadata/index.ts +++ b/x-pack/plugins/infra/server/routes/metadata/index.ts @@ -92,7 +92,9 @@ export const initMetadataRoute = (libs: InfraBackendLibs) => { ); }; -const nameToFeature = (source: string) => (name: string): InfraMetadataFeature => ({ - name, - source, -}); +const nameToFeature = + (source: string) => + (name: string): InfraMetadataFeature => ({ + name, + source, + }); diff --git a/x-pack/plugins/infra/server/routes/metrics_explorer/lib/transform_series.ts b/x-pack/plugins/infra/server/routes/metrics_explorer/lib/transform_series.ts index 3de020c2ad327..6b876887bd568 100644 --- a/x-pack/plugins/infra/server/routes/metrics_explorer/lib/transform_series.ts +++ b/x-pack/plugins/infra/server/routes/metrics_explorer/lib/transform_series.ts @@ -7,19 +7,21 @@ import { MetricsAPISeries, MetricsExplorerSeries } from '../../../../common/http_api'; -export const transformSeries = (hasGroupBy: boolean) => ( - series: MetricsAPISeries -): MetricsExplorerSeries => { - const id = series.keys?.join(' / ') ?? series.id; - return { - ...series, - id, - rows: series.rows.map((row) => { - if (hasGroupBy) { - return { ...row, groupBy: id }; - } - return row; - }), - columns: hasGroupBy ? [...series.columns, { name: 'groupBy', type: 'string' }] : series.columns, +export const transformSeries = + (hasGroupBy: boolean) => + (series: MetricsAPISeries): MetricsExplorerSeries => { + const id = series.keys?.join(' / ') ?? series.id; + return { + ...series, + id, + rows: series.rows.map((row) => { + if (hasGroupBy) { + return { ...row, groupBy: id }; + } + return row; + }), + columns: hasGroupBy + ? [...series.columns, { name: 'groupBy', type: 'string' }] + : series.columns, + }; }; -}; diff --git a/x-pack/plugins/infra/server/routes/snapshot/lib/query_all_data.ts b/x-pack/plugins/infra/server/routes/snapshot/lib/query_all_data.ts index ff0e9b6ce57f1..b92fbab90af93 100644 --- a/x-pack/plugins/infra/server/routes/snapshot/lib/query_all_data.ts +++ b/x-pack/plugins/infra/server/routes/snapshot/lib/query_all_data.ts @@ -9,25 +9,23 @@ import { MetricsAPIRequest, MetricsAPIResponse } from '../../../../common/http_a import { ESSearchClient } from '../../../lib/metrics/types'; import { query } from '../../../lib/metrics'; -const handleResponse = ( - client: ESSearchClient, - options: MetricsAPIRequest, - previousResponse?: MetricsAPIResponse -) => async (resp: MetricsAPIResponse): Promise<MetricsAPIResponse> => { - const combinedResponse = previousResponse - ? { - ...previousResponse, - series: [...previousResponse.series, ...resp.series], - info: resp.info, - } - : resp; - if (resp.info.afterKey) { - return query(client, { ...options, afterKey: resp.info.afterKey }).then( - handleResponse(client, options, combinedResponse) - ); - } - return combinedResponse; -}; +const handleResponse = + (client: ESSearchClient, options: MetricsAPIRequest, previousResponse?: MetricsAPIResponse) => + async (resp: MetricsAPIResponse): Promise<MetricsAPIResponse> => { + const combinedResponse = previousResponse + ? { + ...previousResponse, + series: [...previousResponse.series, ...resp.series], + info: resp.info, + } + : resp; + if (resp.info.afterKey) { + return query(client, { ...options, afterKey: resp.info.afterKey }).then( + handleResponse(client, options, combinedResponse) + ); + } + return combinedResponse; + }; export const queryAllData = (client: ESSearchClient, options: MetricsAPIRequest) => { return query(client, options).then(handleResponse(client, options)); diff --git a/x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.ts b/x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.ts index c47a1c163f9ec..651758d9cd976 100644 --- a/x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.ts +++ b/x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.ts @@ -197,17 +197,18 @@ const { asyncInitialRequestRT, asyncRecoveredRequestRT, asyncRequestRT } = creat logEntriesSearchRequestParamsRT ); -const getLogEntryFromHit = ( - columnDefinitions: LogSourceColumnConfiguration[], - messageFormattingRules: CompiledLogMessageFormattingRule -) => (hit: LogEntryHit): LogEntry => { - const cursor = getLogEntryCursorFromHit(hit); - return { - id: hit._id, - index: hit._index, - cursor, - columns: columnDefinitions.map( - (column): LogColumn => { +const getLogEntryFromHit = + ( + columnDefinitions: LogSourceColumnConfiguration[], + messageFormattingRules: CompiledLogMessageFormattingRule + ) => + (hit: LogEntryHit): LogEntry => { + const cursor = getLogEntryCursorFromHit(hit); + return { + id: hit._id, + index: hit._index, + cursor, + columns: columnDefinitions.map((column): LogColumn => { if ('timestampColumn' in column) { return { columnId: column.timestampColumn.id, @@ -226,11 +227,10 @@ const getLogEntryFromHit = ( highlights: hit.highlight?.[column.fieldColumn.field] ?? [], }; } - } - ), - context: getContextFromHit(hit), + }), + context: getContextFromHit(hit), + }; }; -}; const pickRequestCursor = ( params: LogEntriesSearchRequestParams diff --git a/x-pack/plugins/infra/server/utils/create_afterkey_handler.ts b/x-pack/plugins/infra/server/utils/create_afterkey_handler.ts index 0c320c7b8ec1b..6d41f17a6bb5e 100644 --- a/x-pack/plugins/infra/server/utils/create_afterkey_handler.ts +++ b/x-pack/plugins/infra/server/utils/create_afterkey_handler.ts @@ -8,18 +8,20 @@ import { set } from '@elastic/safer-lodash-set'; import { InfraDatabaseSearchResponse } from '../lib/adapters/framework'; -export const createAfterKeyHandler = ( - optionsAfterKeyPath: string | string[], - afterKeySelector: (input: InfraDatabaseSearchResponse<any, any>) => any -) => <Options extends object>( - options: Options, - response: InfraDatabaseSearchResponse<any, any> -): Options => { - if (!response.aggregations) { - return options; - } - const newOptions = { ...options }; - const afterKey = afterKeySelector(response); - set(newOptions, optionsAfterKeyPath, afterKey); - return newOptions; -}; +export const createAfterKeyHandler = + ( + optionsAfterKeyPath: string | string[], + afterKeySelector: (input: InfraDatabaseSearchResponse<any, any>) => any + ) => + <Options extends object>( + options: Options, + response: InfraDatabaseSearchResponse<any, any> + ): Options => { + if (!response.aggregations) { + return options; + } + const newOptions = { ...options }; + const afterKey = afterKeySelector(response); + set(newOptions, optionsAfterKeyPath, afterKey); + return newOptions; + }; diff --git a/x-pack/plugins/infra/tsconfig.json b/x-pack/plugins/infra/tsconfig.json index a9739bdfdedc7..a2d1d2b63655a 100644 --- a/x-pack/plugins/infra/tsconfig.json +++ b/x-pack/plugins/infra/tsconfig.json @@ -22,7 +22,7 @@ { "path": "../../../src/plugins/kibana_utils/tsconfig.json" }, { "path": "../../../src/plugins/kibana_react/tsconfig.json" }, { "path": "../../../src/plugins/usage_collection/tsconfig.json" }, - { "path": "../../../src/plugins/vis_type_timeseries/tsconfig.json" }, + { "path": "../../../src/plugins/vis_types/timeseries/tsconfig.json" }, { "path": "../data_enhanced/tsconfig.json" }, { "path": "../alerting/tsconfig.json" }, { "path": "../features/tsconfig.json" }, diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/setup_environment.tsx b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/setup_environment.tsx index abd8efb730ef5..7ba5e44cddf61 100644 --- a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/setup_environment.tsx +++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/setup_environment.tsx @@ -52,7 +52,7 @@ const appServices = { export const setupEnvironment = () => { uiMetricService.setup(usageCollectionPluginMock.createSetupContract()); - apiService.setup((mockHttpClient as unknown) as HttpSetup, uiMetricService); + apiService.setup(mockHttpClient as unknown as HttpSetup, uiMetricService); documentationService.setup(docLinksServiceMock.createStartContract()); breadcrumbService.setup(() => {}); @@ -64,8 +64,9 @@ export const setupEnvironment = () => { }; }; -export const WithAppDependencies = (Comp: any) => (props: any) => ( - <KibanaContextProvider services={appServices}> - <Comp {...props} /> - </KibanaContextProvider> -); +export const WithAppDependencies = (Comp: any) => (props: any) => + ( + <KibanaContextProvider services={appServices}> + <Comp {...props} /> + </KibanaContextProvider> + ); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/pipeline_processors_editor.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/pipeline_processors_editor.test.tsx index 5b02927ab873c..c5daa1db2ac07 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/pipeline_processors_editor.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/pipeline_processors_editor.test.tsx @@ -297,21 +297,25 @@ describe('Pipeline Editor', () => { none: 'No description', }; - const createAssertForProcessor = (processorIndex: string) => ({ - description, - descriptionVisible, - }: { - description: string; - descriptionVisible: boolean; - }) => { - expect(find(`processors>${processorIndex}.inlineTextInputNonEditableText`).text()).toBe( - description - ); - expect( - (find(`processors>${processorIndex}.pipelineProcessorItemDescriptionContainer`).props() - .className as string).includes('--displayNone') - ).toBe(!descriptionVisible); - }; + const createAssertForProcessor = + (processorIndex: string) => + ({ + description, + descriptionVisible, + }: { + description: string; + descriptionVisible: boolean; + }) => { + expect(find(`processors>${processorIndex}.inlineTextInputNonEditableText`).text()).toBe( + description + ); + expect( + ( + find(`processors>${processorIndex}.pipelineProcessorItemDescriptionContainer`).props() + .className as string + ).includes('--displayNone') + ).toBe(!descriptionVisible); + }; const assertScriptProcessor = createAssertForProcessor('0'); const assertSetProcessor = createAssertForProcessor('2'); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/field_components/drag_and_drop_text_list.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/field_components/drag_and_drop_text_list.tsx index 03bdc2ceb9579..7a0ea533b0d68 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/field_components/drag_and_drop_text_list.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/field_components/drag_and_drop_text_list.tsx @@ -152,9 +152,8 @@ function DragAndDropTextListComponent({ readDefaultValueOnForm={!item.isNew} > {(field) => { - const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage( - field - ); + const { isInvalid, errorMessage } = + getFieldValidityAndErrorMessage(field); return ( <EuiFlexGroup gutterSize="none" alignItems="center"> <EuiFlexItem> diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/network_direction.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/network_direction.tsx index 22f2226d80b10..ab8744ee01ab8 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/network_direction.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/network_direction.tsx @@ -86,9 +86,7 @@ const fieldsConfig: FieldsConfig = { }, }; -const getInternalNetworkConfig: ( - toggleCustom: () => void -) => Record< +const getInternalNetworkConfig: (toggleCustom: () => void) => Record< keyof InternalNetworkFields, { path: string; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/set.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/set.tsx index 16d89fcbfb119..6bbda366826f7 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/set.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/set.tsx @@ -97,9 +97,7 @@ const fieldsConfig: FieldsConfig = { }; // Required fields config -const getValueConfig: ( - toggleCustom: () => void -) => Record< +const getValueConfig: (toggleCustom: () => void) => Record< keyof ValueToggleFields, { path: string; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/shared.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/shared.ts index 9a45f7f0017c6..a14944a33a8ce 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/shared.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/shared.ts @@ -81,7 +81,7 @@ export const from = { return undefined; }, optionalArrayOfStrings: (v: string[]) => (v.length ? v : undefined), - undefinedIfValue: (value: unknown) => (v: boolean) => (v === value ? undefined : v), + undefinedIfValue: (value: unknown) => (v: boolean) => v === value ? undefined : v, emptyStringToUndefined: (v: unknown) => (v === '' ? undefined : v), /** * Useful when serializing user input from a <textarea /> that we want to later JSON.stringify but keep the same as what @@ -123,16 +123,18 @@ export const isJSONStringValidator: ValidationFunc = ({ value }) => { /** * Similar to the emptyField validator but we accept whitespace characters. */ -export const isEmptyString = (message: string): ValidationFunc => (field) => { - const { value } = field; - if (typeof value === 'string') { - const hasLength = Boolean(value.length); - const hasNonWhiteSpaceChars = hasLength && Boolean(value.trim().length); - if (hasNonWhiteSpaceChars) { - return emptyField(message)(field); +export const isEmptyString = + (message: string): ValidationFunc => + (field) => { + const { value } = field; + if (typeof value === 'string') { + const hasLength = Boolean(value.length); + const hasNonWhiteSpaceChars = hasLength && Boolean(value.trim().length); + if (hasNonWhiteSpaceChars) { + return emptyField(message)(field); + } } - } -}; + }; export const EDITOR_PX_HEIGHT = { extraSmall: 75, diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/test_pipeline/test_pipeline_flyout.container.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/test_pipeline/test_pipeline_flyout.container.tsx index 5a398a7a85dc1..ab361c38c4723 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/test_pipeline/test_pipeline_flyout.container.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/test_pipeline/test_pipeline_flyout.container.tsx @@ -43,11 +43,8 @@ export const TestPipelineFlyout: React.FunctionComponent<Props> = ({ const { services } = useKibana(); const isMounted = useIsMounted(); - const { - testPipelineData, - testPipelineDataDispatch, - updateTestOutputPerProcessor, - } = useTestPipelineContext(); + const { testPipelineData, testPipelineDataDispatch, updateTestOutputPerProcessor } = + useTestPipelineContext(); const { config: { documents: cachedDocuments, verbose: cachedVerbose }, diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/test_pipeline/test_pipeline_tabs/tab_documents/add_docs_accordion/add_docs_accordion.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/test_pipeline/test_pipeline_tabs/tab_documents/add_docs_accordion/add_docs_accordion.tsx index 81a6a8d79907b..ee9241dc966e6 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/test_pipeline/test_pipeline_tabs/tab_documents/add_docs_accordion/add_docs_accordion.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/test_pipeline/test_pipeline_tabs/tab_documents/add_docs_accordion/add_docs_accordion.tsx @@ -52,9 +52,8 @@ export const AddDocumentsAccordion: FunctionComponent<Props> = ({ onAddDocuments // This try/catch may not be necessary once // https://github.com/elastic/kibana/issues/78344 is addressed try { - ({ isDeprecated, createUrl } = services.urlGenerators.getUrlGenerator( - DISCOVER_URL_GENERATOR_ID - )); + ({ isDeprecated, createUrl } = + services.urlGenerators.getUrlGenerator(DISCOVER_URL_GENERATOR_ID)); } catch (e) { // Discover plugin is not enabled setDiscoverLink(undefined); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/processors_reducer/utils.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/processors_reducer/utils.ts index 1c2c899a96e33..889a2db971481 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/processors_reducer/utils.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/processors_reducer/utils.ts @@ -100,7 +100,7 @@ export const unsafeProcessorMove = ( sourceProcessors.splice(sourceIndex, 1); // If onFailure is empty, delete the array. - if (!sourceProcessors.length && !((sourceProcessor as unknown) as State).isRoot) { + if (!sourceProcessors.length && !(sourceProcessor as unknown as State).isRoot) { delete sourceProcessor.onFailure; } } else { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/utils.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/utils.ts index 1259dbd5a9b91..d7e6fc4a4d9ac 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/utils.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/utils.ts @@ -30,7 +30,7 @@ export const getValue = <Result = any>(path: Path, source: any) => { for (const key of path) { current = (current as any)[key]; } - return (current as unknown) as Result; + return current as unknown as Result; }; const ARRAY_TYPE = Object.prototype.toString.call([]); @@ -39,7 +39,7 @@ const OBJECT_TYPE = Object.prototype.toString.call({}); const dumbCopy = <R>(value: R): R => { const objectType = Object.prototype.toString.call(value); if (objectType === ARRAY_TYPE) { - return ([...(value as any)] as unknown) as R; + return [...(value as any)] as unknown as R; } else if (objectType === OBJECT_TYPE) { return { ...(value as any) } as R; } diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_clone/pipelines_clone.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_clone/pipelines_clone.tsx index f68b64cc5f613..ffa9d16d13097 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_clone/pipelines_clone.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_clone/pipelines_clone.tsx @@ -28,9 +28,12 @@ export const PipelinesClone: FunctionComponent<RouteComponentProps<ParamProps>> const { services } = useKibana(); const decodedSourceName = attemptToURIDecode(sourceName)!; - const { error, data: pipeline, isLoading, isInitialRequest } = services.api.useLoadPipeline( - decodedSourceName - ); + const { + error, + data: pipeline, + isLoading, + isInitialRequest, + } = services.api.useLoadPipeline(decodedSourceName); useEffect(() => { if (error && !isLoading) { diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_edit/pipelines_edit.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_edit/pipelines_edit.tsx index ea47f4c9a25e9..c375fe6f66b93 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_edit/pipelines_edit.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_edit/pipelines_edit.tsx @@ -40,9 +40,12 @@ export const PipelinesEdit: React.FunctionComponent<RouteComponentProps<MatchPar const decodedPipelineName = attemptToURIDecode(name)!; - const { error, data: pipeline, isLoading, resendRequest } = services.api.useLoadPipeline( - decodedPipelineName - ); + const { + error, + data: pipeline, + isLoading, + resendRequest, + } = services.api.useLoadPipeline(decodedPipelineName); const onSave = async (updatedPipeline: Pipeline) => { setIsSaving(true); diff --git a/x-pack/plugins/ingest_pipelines/public/locator.test.ts b/x-pack/plugins/ingest_pipelines/public/locator.test.ts index 47c7b13eb07ea..31558363513d9 100644 --- a/x-pack/plugins/ingest_pipelines/public/locator.test.ts +++ b/x-pack/plugins/ingest_pipelines/public/locator.test.ts @@ -7,12 +7,14 @@ import { ManagementAppLocatorDefinition } from 'src/plugins/management/common/locator'; import { IngestPipelinesLocatorDefinition, INGEST_PIPELINES_PAGES } from './locator'; +import { sharePluginMock } from '../../../../src/plugins/share/public/mocks'; describe('Ingest pipeline locator', () => { const setup = () => { const managementDefinition = new ManagementAppLocatorDefinition(); const definition = new IngestPipelinesLocatorDefinition({ managementAppLocator: { + ...sharePluginMock.createLocator(), getLocation: (params) => managementDefinition.getLocation(params), getUrl: async () => { throw new Error('not implemented'); @@ -21,10 +23,6 @@ describe('Ingest pipeline locator', () => { throw new Error('not implemented'); }, useUrl: () => '', - telemetry: jest.fn(), - extract: jest.fn(), - inject: jest.fn(), - migrations: {}, }, }); return { definition }; diff --git a/x-pack/plugins/ingest_pipelines/public/plugin.ts b/x-pack/plugins/ingest_pipelines/public/plugin.ts index b4eb33162a1f4..4e85490de6209 100644 --- a/x-pack/plugins/ingest_pipelines/public/plugin.ts +++ b/x-pack/plugins/ingest_pipelines/public/plugin.ts @@ -14,7 +14,8 @@ import { SetupDependencies, StartDependencies } from './types'; import { IngestPipelinesLocatorDefinition } from './locator'; export class IngestPipelinesPlugin - implements Plugin<void, void, SetupDependencies, StartDependencies> { + implements Plugin<void, void, SetupDependencies, StartDependencies> +{ public setup(coreSetup: CoreSetup<StartDependencies>, plugins: SetupDependencies): void { const { management, usageCollection, share } = plugins; const { http, getStartServices } = coreSetup; diff --git a/x-pack/plugins/lens/common/embeddable_factory/index.ts b/x-pack/plugins/lens/common/embeddable_factory/index.ts index 1eaa1dddfdf08..2ea5c5192a1da 100644 --- a/x-pack/plugins/lens/common/embeddable_factory/index.ts +++ b/x-pack/plugins/lens/common/embeddable_factory/index.ts @@ -18,7 +18,7 @@ export const inject: EmbeddableRegistryDefinition['inject'] = (state, references const typedState = state as LensEmbeddablePersistableState; if ('attributes' in typedState && typedState.attributes !== undefined) { - typedState.attributes.references = (references as unknown) as Serializable[]; + typedState.attributes.references = references as unknown as Serializable[]; } return typedState; @@ -29,7 +29,7 @@ export const extract: EmbeddableRegistryDefinition['extract'] = (state) => { const typedState = state as LensEmbeddablePersistableState; if ('attributes' in typedState && typedState.attributes !== undefined) { - references = (typedState.attributes.references as unknown) as SavedObjectReference[]; + references = typedState.attributes.references as unknown as SavedObjectReference[]; } return { state, references }; diff --git a/x-pack/plugins/lens/common/expressions/datatable/datatable_fn.ts b/x-pack/plugins/lens/common/expressions/datatable/datatable_fn.ts index cb03ec7f9dda6..4cd2a57cbc429 100644 --- a/x-pack/plugins/lens/common/expressions/datatable/datatable_fn.ts +++ b/x-pack/plugins/lens/common/expressions/datatable/datatable_fn.ts @@ -20,74 +20,76 @@ function isRange(meta: { params?: { id?: string } } | undefined) { return meta?.params?.id === 'range'; } -export const datatableFn = ( - getFormatFactory: (context: ExecutionContext) => FormatFactory | Promise<FormatFactory> -): DatatableExpressionFunction['fn'] => async (data, args, context) => { - let untransposedData: LensMultiTable | undefined; - // do the sorting at this level to propagate it also at CSV download - const [firstTable] = Object.values(data.tables); - const [layerId] = Object.keys(context.inspectorAdapters.tables || {}); - const formatters: Record<string, ReturnType<FormatFactory>> = {}; - const formatFactory = await getFormatFactory(context); +export const datatableFn = + ( + getFormatFactory: (context: ExecutionContext) => FormatFactory | Promise<FormatFactory> + ): DatatableExpressionFunction['fn'] => + async (data, args, context) => { + let untransposedData: LensMultiTable | undefined; + // do the sorting at this level to propagate it also at CSV download + const [firstTable] = Object.values(data.tables); + const [layerId] = Object.keys(context.inspectorAdapters.tables || {}); + const formatters: Record<string, ReturnType<FormatFactory>> = {}; + const formatFactory = await getFormatFactory(context); - firstTable.columns.forEach((column) => { - formatters[column.id] = formatFactory(column.meta?.params); - }); + firstTable.columns.forEach((column) => { + formatters[column.id] = formatFactory(column.meta?.params); + }); - const hasTransposedColumns = args.columns.some((c) => c.isTransposed); - if (hasTransposedColumns) { - // store original shape of data separately - untransposedData = cloneDeep(data); - // transposes table and args inplace - transposeTable(args, firstTable, formatters); - } + const hasTransposedColumns = args.columns.some((c) => c.isTransposed); + if (hasTransposedColumns) { + // store original shape of data separately + untransposedData = cloneDeep(data); + // transposes table and args inplace + transposeTable(args, firstTable, formatters); + } - const { sortingColumnId: sortBy, sortingDirection: sortDirection } = args; + const { sortingColumnId: sortBy, sortingDirection: sortDirection } = args; - const columnsReverseLookup = firstTable.columns.reduce< - Record<string, { name: string; index: number; meta?: DatatableColumnMeta }> - >((memo, { id, name, meta }, i) => { - memo[id] = { name, index: i, meta }; - return memo; - }, {}); + const columnsReverseLookup = firstTable.columns.reduce< + Record<string, { name: string; index: number; meta?: DatatableColumnMeta }> + >((memo, { id, name, meta }, i) => { + memo[id] = { name, index: i, meta }; + return memo; + }, {}); - const columnsWithSummary = args.columns.filter((c) => c.summaryRow); - for (const column of columnsWithSummary) { - column.summaryRowValue = computeSummaryRowForColumn( - column, - firstTable, - formatters, - formatFactory({ id: 'number' }) - ); - } + const columnsWithSummary = args.columns.filter((c) => c.summaryRow); + for (const column of columnsWithSummary) { + column.summaryRowValue = computeSummaryRowForColumn( + column, + firstTable, + formatters, + formatFactory({ id: 'number' }) + ); + } - if (sortBy && columnsReverseLookup[sortBy] && sortDirection !== 'none') { - // Sort on raw values for these types, while use the formatted value for the rest - const sortingCriteria = getSortingCriteria( - isRange(columnsReverseLookup[sortBy]?.meta) - ? 'range' - : columnsReverseLookup[sortBy]?.meta?.type, - sortBy, - formatters[sortBy], - sortDirection - ); - // replace the table here - context.inspectorAdapters.tables[layerId].rows = (firstTable.rows || []) - .slice() - .sort(sortingCriteria); - // replace also the local copy - firstTable.rows = context.inspectorAdapters.tables[layerId].rows; - } else { - args.sortingColumnId = undefined; - args.sortingDirection = 'none'; - } - return { - type: 'render', - as: 'lens_datatable_renderer', - value: { - data, - untransposedData, - args, - }, + if (sortBy && columnsReverseLookup[sortBy] && sortDirection !== 'none') { + // Sort on raw values for these types, while use the formatted value for the rest + const sortingCriteria = getSortingCriteria( + isRange(columnsReverseLookup[sortBy]?.meta) + ? 'range' + : columnsReverseLookup[sortBy]?.meta?.type, + sortBy, + formatters[sortBy], + sortDirection + ); + // replace the table here + context.inspectorAdapters.tables[layerId].rows = (firstTable.rows || []) + .slice() + .sort(sortingCriteria); + // replace also the local copy + firstTable.rows = context.inspectorAdapters.tables[layerId].rows; + } else { + args.sortingColumnId = undefined; + args.sortingDirection = 'none'; + } + return { + type: 'render', + as: 'lens_datatable_renderer', + value: { + data, + untransposedData, + args, + }, + }; }; -}; diff --git a/x-pack/plugins/lens/common/expressions/datatable/transpose_helpers.test.ts b/x-pack/plugins/lens/common/expressions/datatable/transpose_helpers.test.ts index 6adb8b59474db..ae6e20d976b5e 100644 --- a/x-pack/plugins/lens/common/expressions/datatable/transpose_helpers.test.ts +++ b/x-pack/plugins/lens/common/expressions/datatable/transpose_helpers.test.ts @@ -100,13 +100,13 @@ describe('transpose_helpes', () => { } function buildFormatters() { - return ({ + return { bucket1: { convert: (x: unknown) => x }, bucket2: { convert: (x: unknown) => x }, bucket3: { convert: (x: unknown) => x }, metric1: { convert: (x: unknown) => x }, metric2: { convert: (x: unknown) => x }, - } as unknown) as Record<string, FieldFormat>; + } as unknown as Record<string, FieldFormat>; } it('should transpose table by one column', () => { diff --git a/x-pack/plugins/lens/common/expressions/format_column/format_column.test.ts b/x-pack/plugins/lens/common/expressions/format_column/format_column.test.ts index fbf3ff9c05b19..28f9b08eff1cb 100644 --- a/x-pack/plugins/lens/common/expressions/format_column/format_column.test.ts +++ b/x-pack/plugins/lens/common/expressions/format_column/format_column.test.ts @@ -10,9 +10,8 @@ import { functionWrapper } from 'src/plugins/expressions/common/expression_funct import { FormatColumnArgs, formatColumn } from './index'; describe('format_column', () => { - const fn: (input: Datatable, args: FormatColumnArgs) => Promise<Datatable> = functionWrapper( - formatColumn - ); + const fn: (input: Datatable, args: FormatColumnArgs) => Promise<Datatable> = + functionWrapper(formatColumn); let datatable: Datatable; diff --git a/x-pack/plugins/lens/common/expressions/metric_chart/metric_chart.ts b/x-pack/plugins/lens/common/expressions/metric_chart/metric_chart.ts index 0a867e4155c22..6676ead2bca9e 100644 --- a/x-pack/plugins/lens/common/expressions/metric_chart/metric_chart.ts +++ b/x-pack/plugins/lens/common/expressions/metric_chart/metric_chart.ts @@ -50,8 +50,7 @@ export const metricChart: ExpressionFunctionDefinition< types: ['string'], options: ['reduced', 'full'], default: 'full', - help: - 'The display mode of the chart - reduced will only show the metric itself without min size', + help: 'The display mode of the chart - reduced will only show the metric itself without min size', }, }, inputTypes: ['lens_multitable'], diff --git a/x-pack/plugins/lens/common/expressions/time_scale/time_scale_fn.ts b/x-pack/plugins/lens/common/expressions/time_scale/time_scale_fn.ts index e6113afebca22..78d2e9896d4c3 100644 --- a/x-pack/plugins/lens/common/expressions/time_scale/time_scale_fn.ts +++ b/x-pack/plugins/lens/common/expressions/time_scale/time_scale_fn.ts @@ -25,81 +25,89 @@ const unitInMs: Record<TimeScaleUnit, number> = { d: 1000 * 60 * 60 * 24, }; -export const timeScaleFn = ( - getTimezone: (context: ExecutionContext) => string | Promise<string> -): TimeScaleExpressionFunction['fn'] => async ( - input, - { dateColumnId, inputColumnId, outputColumnId, outputColumnName, targetUnit }: TimeScaleArgs, - context -) => { - const dateColumnDefinition = input.columns.find((column) => column.id === dateColumnId); +export const timeScaleFn = + ( + getTimezone: (context: ExecutionContext) => string | Promise<string> + ): TimeScaleExpressionFunction['fn'] => + async ( + input, + { dateColumnId, inputColumnId, outputColumnId, outputColumnName, targetUnit }: TimeScaleArgs, + context + ) => { + const dateColumnDefinition = input.columns.find((column) => column.id === dateColumnId); - if (!dateColumnDefinition) { - throw new Error( - i18n.translate('xpack.lens.functions.timeScale.dateColumnMissingMessage', { - defaultMessage: 'Specified dateColumnId {columnId} does not exist.', - values: { - columnId: dateColumnId, - }, - }) + if (!dateColumnDefinition) { + throw new Error( + i18n.translate('xpack.lens.functions.timeScale.dateColumnMissingMessage', { + defaultMessage: 'Specified dateColumnId {columnId} does not exist.', + values: { + columnId: dateColumnId, + }, + }) + ); + } + + const resultColumns = buildResultColumns( + input, + outputColumnId, + inputColumnId, + outputColumnName, + { + allowColumnOverwrite: true, + } ); - } - const resultColumns = buildResultColumns(input, outputColumnId, inputColumnId, outputColumnName, { - allowColumnOverwrite: true, - }); + if (!resultColumns) { + return input; + } - if (!resultColumns) { - return input; - } + const targetUnitInMs = unitInMs[targetUnit]; + const timeInfo = getDateHistogramMetaDataByDatatableColumn(dateColumnDefinition, { + timeZone: await getTimezone(context), + }); + const intervalDuration = timeInfo?.interval && parseInterval(timeInfo.interval); - const targetUnitInMs = unitInMs[targetUnit]; - const timeInfo = getDateHistogramMetaDataByDatatableColumn(dateColumnDefinition, { - timeZone: await getTimezone(context), - }); - const intervalDuration = timeInfo?.interval && parseInterval(timeInfo.interval); + if (!timeInfo || !intervalDuration) { + throw new Error( + i18n.translate('xpack.lens.functions.timeScale.timeInfoMissingMessage', { + defaultMessage: 'Could not fetch date histogram information', + }) + ); + } + // the datemath plugin always parses dates by using the current default moment time zone. + // to use the configured time zone, we are switching just for the bounds calculation. + const defaultTimezone = moment().zoneName(); + moment.tz.setDefault(timeInfo.timeZone); - if (!timeInfo || !intervalDuration) { - throw new Error( - i18n.translate('xpack.lens.functions.timeScale.timeInfoMissingMessage', { - defaultMessage: 'Could not fetch date histogram information', - }) - ); - } - // the datemath plugin always parses dates by using the current default moment time zone. - // to use the configured time zone, we are switching just for the bounds calculation. - const defaultTimezone = moment().zoneName(); - moment.tz.setDefault(timeInfo.timeZone); + const timeBounds = timeInfo.timeRange && calculateBounds(timeInfo.timeRange); - const timeBounds = timeInfo.timeRange && calculateBounds(timeInfo.timeRange); + const result = { + ...input, + columns: resultColumns, + rows: input.rows.map((row) => { + const newRow = { ...row }; - const result = { - ...input, - columns: resultColumns, - rows: input.rows.map((row) => { - const newRow = { ...row }; + let startOfBucket = moment(row[dateColumnId]); + let endOfBucket = startOfBucket.clone().add(intervalDuration); + if (timeBounds && timeBounds.min) { + startOfBucket = moment.max(startOfBucket, timeBounds.min); + } + if (timeBounds && timeBounds.max) { + endOfBucket = moment.min(endOfBucket, timeBounds.max); + } + const bucketSize = endOfBucket.diff(startOfBucket); + const factor = bucketSize / targetUnitInMs; - let startOfBucket = moment(row[dateColumnId]); - let endOfBucket = startOfBucket.clone().add(intervalDuration); - if (timeBounds && timeBounds.min) { - startOfBucket = moment.max(startOfBucket, timeBounds.min); - } - if (timeBounds && timeBounds.max) { - endOfBucket = moment.min(endOfBucket, timeBounds.max); - } - const bucketSize = endOfBucket.diff(startOfBucket); - const factor = bucketSize / targetUnitInMs; + const currentValue = newRow[inputColumnId]; + if (currentValue != null) { + newRow[outputColumnId] = Number(currentValue) / factor; + } - const currentValue = newRow[inputColumnId]; - if (currentValue != null) { - newRow[outputColumnId] = Number(currentValue) / factor; - } + return newRow; + }), + }; + // reset default moment timezone + moment.tz.setDefault(defaultTimezone); - return newRow; - }), + return result; }; - // reset default moment timezone - moment.tz.setDefault(defaultTimezone); - - return result; -}; diff --git a/x-pack/plugins/lens/common/expressions/xy_chart/axis_config.ts b/x-pack/plugins/lens/common/expressions/xy_chart/axis_config.ts index 9a9273e43f6f1..29b0fb1352e5b 100644 --- a/x-pack/plugins/lens/common/expressions/xy_chart/axis_config.ts +++ b/x-pack/plugins/lens/common/expressions/xy_chart/axis_config.ts @@ -27,12 +27,18 @@ interface AxisConfig { hide?: boolean; } -export type YAxisMode = 'auto' | 'left' | 'right'; +export type YAxisMode = 'auto' | 'left' | 'right' | 'bottom'; +export type LineStyle = 'solid' | 'dashed' | 'dotted'; +export type FillStyle = 'none' | 'above' | 'below'; export interface YConfig { forAccessor: string; axisMode?: YAxisMode; color?: string; + icon?: string; + lineWidth?: number; + lineStyle?: LineStyle; + fill?: FillStyle; } export type AxisTitlesVisibilityConfigResult = AxesSettingsConfig & { @@ -161,6 +167,24 @@ export const yAxisConfig: ExpressionFunctionDefinition< types: ['string'], help: 'The color of the series', }, + lineStyle: { + types: ['string'], + options: ['solid', 'dotted', 'dashed'], + help: 'The style of the threshold line', + }, + lineWidth: { + types: ['number'], + help: 'The width of the threshold line', + }, + icon: { + types: ['string'], + help: 'An optional icon used for threshold lines', + }, + fill: { + types: ['string'], + options: ['none', 'above', 'below'], + help: '', + }, }, fn: function fn(input: unknown, args: YConfig) { return { diff --git a/x-pack/plugins/lens/common/suffix_formatter/suffix_formatter.test.ts b/x-pack/plugins/lens/common/suffix_formatter/suffix_formatter.test.ts index 9ab76b73cbb66..dea0957c4215a 100644 --- a/x-pack/plugins/lens/common/suffix_formatter/suffix_formatter.test.ts +++ b/x-pack/plugins/lens/common/suffix_formatter/suffix_formatter.test.ts @@ -12,7 +12,7 @@ describe('suffix formatter', () => { it('should call nested formatter and apply suffix', () => { const convertMock = jest.fn((x) => x); const formatFactory = jest.fn(() => ({ convert: convertMock })); - const SuffixFormatter = getSuffixFormatter(() => (formatFactory as unknown) as FormatFactory); + const SuffixFormatter = getSuffixFormatter(() => formatFactory as unknown as FormatFactory); const nestedParams = { abc: 123 }; const formatterInstance = new SuffixFormatter({ unit: 'h', @@ -30,7 +30,7 @@ describe('suffix formatter', () => { it('should not add suffix to empty strings', () => { const convertMock = jest.fn((x) => ''); const formatFactory = jest.fn(() => ({ convert: convertMock })); - const SuffixFormatter = getSuffixFormatter(() => (formatFactory as unknown) as FormatFactory); + const SuffixFormatter = getSuffixFormatter(() => formatFactory as unknown as FormatFactory); const nestedParams = { abc: 123 }; const formatterInstance = new SuffixFormatter({ unit: 'h', @@ -46,7 +46,7 @@ describe('suffix formatter', () => { it('should be a hidden formatter', () => { const convertMock = jest.fn((x) => ''); const formatFactory = jest.fn(() => ({ convert: convertMock })); - const SuffixFormatter = getSuffixFormatter(() => (formatFactory as unknown) as FormatFactory); + const SuffixFormatter = getSuffixFormatter(() => formatFactory as unknown as FormatFactory); expect(SuffixFormatter.hidden).toBe(true); }); }); diff --git a/x-pack/plugins/lens/public/app_plugin/app.test.tsx b/x-pack/plugins/lens/public/app_plugin/app.test.tsx index 5617b5b0edeea..25a809cb3c05d 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.test.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.test.tsx @@ -126,7 +126,7 @@ describe('Lens App', () => { beforeEach(() => { defaultSavedObjectId = '1234'; - defaultDoc = ({ + defaultDoc = { savedObjectId: defaultSavedObjectId, title: 'An extremely cool default document!', expression: 'definitely a valid expression', @@ -135,7 +135,7 @@ describe('Lens App', () => { filters: [{ query: { match_phrase: { src: 'test' } } }], }, references: [{ type: 'index-pattern', id: '1', name: 'index-pattern-0' }], - } as unknown) as Document; + } as unknown as Document; }); it('renders the editor frame', async () => { @@ -145,8 +145,8 @@ describe('Lens App', () => { it('updates global filters with store state', async () => { const services = makeDefaultServices(sessionIdSubject); - const indexPattern = ({ id: 'index1' } as unknown) as IndexPattern; - const pinnedField = ({ name: 'pinnedField' } as unknown) as FieldSpec; + const indexPattern = { id: 'index1' } as unknown as IndexPattern; + const pinnedField = { name: 'pinnedField' } as unknown as FieldSpec; const pinnedFilter = esFilters.buildExistsFilter(pinnedField, indexPattern); services.data.query.filterManager.getFilters = jest.fn().mockImplementation(() => { return []; @@ -173,7 +173,7 @@ describe('Lens App', () => { describe('breadcrumbs', () => { const breadcrumbDocSavedObjectId = defaultSavedObjectId; - const breadcrumbDoc = ({ + const breadcrumbDoc = { savedObjectId: breadcrumbDocSavedObjectId, title: 'Daaaaaaadaumching!', state: { @@ -181,7 +181,7 @@ describe('Lens App', () => { filters: [], }, references: [], - } as unknown) as Document; + } as unknown as Document; it('sets breadcrumbs when the document title changes', async () => { const { instance, services, lensStore } = await mountWith({}); @@ -309,19 +309,19 @@ describe('Lens App', () => { describe('persistence', () => { it('passes query and indexPatterns to TopNavMenu', async () => { const { instance, lensStore, services } = await mountWith({ preloadedState: {} }); - const document = ({ + const document = { savedObjectId: defaultSavedObjectId, state: { query: 'fake query', filters: [{ query: { match_phrase: { src: 'test' } } }], }, references: [{ type: 'index-pattern', id: '1', name: 'index-pattern-0' }], - } as unknown) as Document; + } as unknown as Document; act(() => { lensStore.dispatch( setState({ - query: ('fake query' as unknown) as Query, + query: 'fake query' as unknown as Query, persistedDoc: document, }) ); @@ -345,11 +345,9 @@ describe('Lens App', () => { } function getButton(inst: ReactWrapper): TopNavMenuData { - return (inst - .find('[data-test-subj="lnsApp_topNav"]') - .prop('config') as TopNavMenuData[]).find( - (button) => button.testId === 'lnsApp_saveButton' - )!; + return ( + inst.find('[data-test-subj="lnsApp_topNav"]').prop('config') as TopNavMenuData[] + ).find((button) => button.testId === 'lnsApp_saveButton')!; } async function testSave(inst: ReactWrapper, saveProps: SaveProps) { @@ -646,9 +644,9 @@ describe('Lens App', () => { }); it('saves app filters and does not save pinned filters', async () => { - const indexPattern = ({ id: 'index1' } as unknown) as IndexPattern; - const field = ({ name: 'myfield' } as unknown) as FieldSpec; - const pinnedField = ({ name: 'pinnedField' } as unknown) as FieldSpec; + const indexPattern = { id: 'index1' } as unknown as IndexPattern; + const field = { name: 'myfield' } as unknown as FieldSpec; + const pinnedField = { name: 'pinnedField' } as unknown as FieldSpec; const unpinned = esFilters.buildExistsFilter(field, indexPattern); const pinned = esFilters.buildExistsFilter(pinnedField, indexPattern); await act(async () => { @@ -685,7 +683,7 @@ describe('Lens App', () => { services, preloadedState: { isSaveable: true, - persistedDoc: ({ savedObjectId: '123' } as unknown) as Document, + persistedDoc: { savedObjectId: '123' } as unknown as Document, }, }); await act(async () => { @@ -722,11 +720,9 @@ describe('Lens App', () => { describe('download button', () => { function getButton(inst: ReactWrapper): TopNavMenuData { - return (inst - .find('[data-test-subj="lnsApp_topNav"]') - .prop('config') as TopNavMenuData[]).find( - (button) => button.testId === 'lnsApp_downloadCSVButton' - )!; + return ( + inst.find('[data-test-subj="lnsApp_topNav"]').prop('config') as TopNavMenuData[] + ).find((button) => button.testId === 'lnsApp_downloadCSVButton')!; } it('should be disabled when no data is available', async () => { @@ -768,11 +764,9 @@ describe('Lens App', () => { describe('inspector', () => { function getButton(inst: ReactWrapper): TopNavMenuData { - return (inst - .find('[data-test-subj="lnsApp_topNav"]') - .prop('config') as TopNavMenuData[]).find( - (button) => button.testId === 'lnsApp_inspectButton' - )!; + return ( + inst.find('[data-test-subj="lnsApp_topNav"]').prop('config') as TopNavMenuData[] + ).find((button) => button.testId === 'lnsApp_inspectButton')!; } async function runInspect(inst: ReactWrapper) { @@ -859,8 +853,8 @@ describe('Lens App', () => { it('updates the filters when the user changes them', async () => { const { instance, services, lensStore } = await mountWith({}); - const indexPattern = ({ id: 'index1' } as unknown) as IndexPattern; - const field = ({ name: 'myfield' } as unknown) as FieldSpec; + const indexPattern = { id: 'index1' } as unknown as IndexPattern; + const field = { name: 'myfield' } as unknown as FieldSpec; expect(lensStore.getState()).toEqual({ lens: expect.objectContaining({ filters: [], @@ -914,8 +908,8 @@ describe('Lens App', () => { searchSessionId: `sessionId-3`, }), }); - const indexPattern = ({ id: 'index1' } as unknown) as IndexPattern; - const field = ({ name: 'myfield' } as unknown) as FieldSpec; + const indexPattern = { id: 'index1' } as unknown as IndexPattern; + const field = { name: 'myfield' } as unknown as FieldSpec; act(() => services.data.query.filterManager.setFilters([ esFilters.buildExistsFilter(field, indexPattern), @@ -1049,9 +1043,9 @@ describe('Lens App', () => { query: { query: 'new', language: 'lucene' }, }) ); - const indexPattern = ({ id: 'index1' } as unknown) as IndexPattern; - const field = ({ name: 'myfield' } as unknown) as FieldSpec; - const pinnedField = ({ name: 'pinnedField' } as unknown) as FieldSpec; + const indexPattern = { id: 'index1' } as unknown as IndexPattern; + const field = { name: 'myfield' } as unknown as FieldSpec; + const pinnedField = { name: 'pinnedField' } as unknown as FieldSpec; const unpinned = esFilters.buildExistsFilter(field, indexPattern); const pinned = esFilters.buildExistsFilter(pinnedField, indexPattern); FilterManager.setFiltersStore([pinned], esFilters.FilterStateStore.GLOBAL_STATE); @@ -1106,9 +1100,9 @@ describe('Lens App', () => { query: { query: 'new', language: 'lucene' }, }) ); - const indexPattern = ({ id: 'index1' } as unknown) as IndexPattern; - const field = ({ name: 'myfield' } as unknown) as FieldSpec; - const pinnedField = ({ name: 'pinnedField' } as unknown) as FieldSpec; + const indexPattern = { id: 'index1' } as unknown as IndexPattern; + const field = { name: 'myfield' } as unknown as FieldSpec; + const pinnedField = { name: 'pinnedField' } as unknown as FieldSpec; const unpinned = esFilters.buildExistsFilter(field, indexPattern); const pinned = esFilters.buildExistsFilter(pinnedField, indexPattern); FilterManager.setFiltersStore([pinned], esFilters.FilterStateStore.GLOBAL_STATE); diff --git a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx index 332d404c6375f..f0785496dcf32 100644 --- a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx +++ b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx @@ -255,7 +255,7 @@ export const LensTopNavMenu = ({ }, }, actions: { - inspect: lensInspector.inspect, + inspect: () => lensInspector.inspect({ title }), exportToCSV: () => { if (!activeData) { return; @@ -335,7 +335,7 @@ export const LensTopNavMenu = ({ setIsSaveModalVisible, uiSettings, unsavedTitle, - lensInspector.inspect, + lensInspector, ] ); diff --git a/x-pack/plugins/lens/public/app_plugin/tags_saved_object_save_modal_dashboard_wrapper.tsx b/x-pack/plugins/lens/public/app_plugin/tags_saved_object_save_modal_dashboard_wrapper.tsx index 03e2b4d15d0cb..37c93bfbabc56 100644 --- a/x-pack/plugins/lens/public/app_plugin/tags_saved_object_save_modal_dashboard_wrapper.tsx +++ b/x-pack/plugins/lens/public/app_plugin/tags_saved_object_save_modal_dashboard_wrapper.tsx @@ -32,43 +32,39 @@ export type TagEnhancedSavedObjectSaveModalDashboardProps = Omit< const SavedObjectSaveModalDashboard = withSuspense(LazySavedObjectSaveModalDashboard); -export const TagEnhancedSavedObjectSaveModalDashboard: FC<TagEnhancedSavedObjectSaveModalDashboardProps> = ({ - initialTags, - onSave, - savedObjectsTagging, - ...otherProps -}) => { - const [selectedTags, setSelectedTags] = useState(initialTags); +export const TagEnhancedSavedObjectSaveModalDashboard: FC<TagEnhancedSavedObjectSaveModalDashboardProps> = + ({ initialTags, onSave, savedObjectsTagging, ...otherProps }) => { + const [selectedTags, setSelectedTags] = useState(initialTags); - const tagSelectorOption = useMemo( - () => - savedObjectsTagging ? ( - <savedObjectsTagging.ui.components.SavedObjectSaveModalTagSelector - initialSelection={initialTags} - onTagsSelected={setSelectedTags} - /> - ) : undefined, - [savedObjectsTagging, initialTags] - ); + const tagSelectorOption = useMemo( + () => + savedObjectsTagging ? ( + <savedObjectsTagging.ui.components.SavedObjectSaveModalTagSelector + initialSelection={initialTags} + onTagsSelected={setSelectedTags} + /> + ) : undefined, + [savedObjectsTagging, initialTags] + ); - const tagEnhancedOptions = <>{tagSelectorOption}</>; + const tagEnhancedOptions = <>{tagSelectorOption}</>; - const tagEnhancedOnSave: SaveModalDashboardProps['onSave'] = useCallback( - (saveOptions) => { - onSave({ - ...saveOptions, - returnToOrigin: false, - newTags: selectedTags, - }); - }, - [onSave, selectedTags] - ); + const tagEnhancedOnSave: SaveModalDashboardProps['onSave'] = useCallback( + (saveOptions) => { + onSave({ + ...saveOptions, + returnToOrigin: false, + newTags: selectedTags, + }); + }, + [onSave, selectedTags] + ); - return ( - <SavedObjectSaveModalDashboard - {...otherProps} - onSave={tagEnhancedOnSave} - tagOptions={tagEnhancedOptions} - /> - ); -}; + return ( + <SavedObjectSaveModalDashboard + {...otherProps} + onSave={tagEnhancedOnSave} + tagOptions={tagEnhancedOptions} + /> + ); + }; diff --git a/x-pack/plugins/lens/public/app_plugin/tags_saved_object_save_modal_origin_wrapper.tsx b/x-pack/plugins/lens/public/app_plugin/tags_saved_object_save_modal_origin_wrapper.tsx index 446f70920bcbc..7efdc5133349f 100644 --- a/x-pack/plugins/lens/public/app_plugin/tags_saved_object_save_modal_origin_wrapper.tsx +++ b/x-pack/plugins/lens/public/app_plugin/tags_saved_object_save_modal_origin_wrapper.tsx @@ -22,58 +22,53 @@ export type TagEnhancedSavedObjectSaveModalOriginProps = Omit<OriginSaveModalPro onSave: (props: OriginSaveProps) => void; }; -export const TagEnhancedSavedObjectSaveModalOrigin: FC<TagEnhancedSavedObjectSaveModalOriginProps> = ({ - initialTags, - onSave, - savedObjectsTagging, - options, - ...otherProps -}) => { - const [selectedTags, setSelectedTags] = useState(initialTags); +export const TagEnhancedSavedObjectSaveModalOrigin: FC<TagEnhancedSavedObjectSaveModalOriginProps> = + ({ initialTags, onSave, savedObjectsTagging, options, ...otherProps }) => { + const [selectedTags, setSelectedTags] = useState(initialTags); - const tagSelectorOption = useMemo( - () => - savedObjectsTagging ? ( - <savedObjectsTagging.ui.components.SavedObjectSaveModalTagSelector - initialSelection={initialTags} - onTagsSelected={setSelectedTags} - /> - ) : undefined, - [savedObjectsTagging, initialTags] - ); - - const tagEnhancedOptions = - typeof options === 'function' ? ( - (state: SaveModalState) => { - return ( - <> - {tagSelectorOption} - {options(state)} - </> - ); - } - ) : ( - <> - {tagSelectorOption} - {options} - </> + const tagSelectorOption = useMemo( + () => + savedObjectsTagging ? ( + <savedObjectsTagging.ui.components.SavedObjectSaveModalTagSelector + initialSelection={initialTags} + onTagsSelected={setSelectedTags} + /> + ) : undefined, + [savedObjectsTagging, initialTags] ); - const tagEnhancedOnSave: OriginSaveModalProps['onSave'] = useCallback( - (saveOptions) => { - onSave({ - ...saveOptions, - newTags: selectedTags, - }); - }, - [onSave, selectedTags] - ); + const tagEnhancedOptions = + typeof options === 'function' ? ( + (state: SaveModalState) => { + return ( + <> + {tagSelectorOption} + {options(state)} + </> + ); + } + ) : ( + <> + {tagSelectorOption} + {options} + </> + ); - return ( - <SavedObjectSaveModalOrigin - {...otherProps} - onSave={tagEnhancedOnSave} - options={tagEnhancedOptions} - /> - ); -}; + const tagEnhancedOnSave: OriginSaveModalProps['onSave'] = useCallback( + (saveOptions) => { + onSave({ + ...saveOptions, + newTags: selectedTags, + }); + }, + [onSave, selectedTags] + ); + + return ( + <SavedObjectSaveModalOrigin + {...otherProps} + onSave={tagEnhancedOnSave} + options={tagEnhancedOptions} + /> + ); + }; diff --git a/x-pack/plugins/lens/public/assets/chart_bar_threshold.tsx b/x-pack/plugins/lens/public/assets/chart_bar_threshold.tsx new file mode 100644 index 0000000000000..88e0a46b5538c --- /dev/null +++ b/x-pack/plugins/lens/public/assets/chart_bar_threshold.tsx @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiIconProps } from '@elastic/eui'; + +export const LensIconChartBarThreshold = ({ + title, + titleId, + ...props +}: Omit<EuiIconProps, 'type'>) => ( + <svg + viewBox="0 0 16 12" + width={30} + height={22} + fill="none" + xmlns="http://www.w3.org/2000/svg" + aria-labelledby={titleId} + {...props} + > + {title ? <title id={titleId}>{title} : null} + + + + + +); diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/cell_value.test.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/cell_value.test.tsx index 96413444d60c4..21f6cb6bc2052 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/cell_value.test.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/cell_value.test.tsx @@ -38,7 +38,7 @@ describe('datatable cell renderer', () => { }, { columns: [], sortingColumnId: '', sortingDirection: 'none' }, DataContext, - ({ get: jest.fn() } as unknown) as IUiSettingsClient + { get: jest.fn() } as unknown as IUiSettingsClient ); it('renders formatted value', () => { @@ -98,7 +98,7 @@ describe('datatable cell renderer', () => { }, columnConfig, DataContext, - ({ get: jest.fn() } as unknown) as IUiSettingsClient + { get: jest.fn() } as unknown as IUiSettingsClient ); } function getColumnConfiguration(): DatatableArgs { diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.test.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.test.tsx index ba4ca284fe26e..2239816667d62 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.test.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.test.tsx @@ -206,10 +206,12 @@ describe('data table dimension editor', () => { const instance = mountWithIntl(); act(() => - (instance - .find('[data-test-subj="lnsDatatable_dynamicColoring_trigger"]') - .first() - .prop('onClick') as () => void)?.() + ( + instance + .find('[data-test-subj="lnsDatatable_dynamicColoring_trigger"]') + .first() + .prop('onClick') as () => void + )?.() ); expect(instance.find(PalettePanelContainer).exists()).toBe(true); diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/table_actions.ts b/x-pack/plugins/lens/public/datatable_visualization/components/table_actions.ts index 62c2ec3a7f7fd..3c1297e864553 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/table_actions.ts +++ b/x-pack/plugins/lens/public/datatable_visualization/components/table_actions.ts @@ -13,115 +13,125 @@ import type { LensResizeAction, LensSortAction, LensToggleAction } from './types import type { ColumnConfig, LensGridDirection } from '../../../common/expressions'; import { getOriginalId } from '../../../common/expressions'; -export const createGridResizeHandler = ( - columnConfig: ColumnConfig, - setColumnConfig: React.Dispatch>, - onEditAction: (data: LensResizeAction['data']) => void -) => (eventData: { columnId: string; width: number | undefined }) => { - const originalColumnId = getOriginalId(eventData.columnId); - // directly set the local state of the component to make sure the visualization re-renders immediately, - // re-layouting and taking up all of the available space. - setColumnConfig({ - ...columnConfig, - columns: columnConfig.columns.map((column) => { - if (column.columnId === eventData.columnId || column.originalColumnId === originalColumnId) { - return { ...column, width: eventData.width }; - } - return column; - }), - }); - return onEditAction({ - action: 'resize', - columnId: originalColumnId, - width: eventData.width, - }); -}; +export const createGridResizeHandler = + ( + columnConfig: ColumnConfig, + setColumnConfig: React.Dispatch>, + onEditAction: (data: LensResizeAction['data']) => void + ) => + (eventData: { columnId: string; width: number | undefined }) => { + const originalColumnId = getOriginalId(eventData.columnId); + // directly set the local state of the component to make sure the visualization re-renders immediately, + // re-layouting and taking up all of the available space. + setColumnConfig({ + ...columnConfig, + columns: columnConfig.columns.map((column) => { + if ( + column.columnId === eventData.columnId || + column.originalColumnId === originalColumnId + ) { + return { ...column, width: eventData.width }; + } + return column; + }), + }); + return onEditAction({ + action: 'resize', + columnId: originalColumnId, + width: eventData.width, + }); + }; + +export const createGridHideHandler = + ( + columnConfig: ColumnConfig, + setColumnConfig: React.Dispatch>, + onEditAction: (data: LensToggleAction['data']) => void + ) => + (eventData: { columnId: string }) => { + const originalColumnId = getOriginalId(eventData.columnId); + // directly set the local state of the component to make sure the visualization re-renders immediately + setColumnConfig({ + ...columnConfig, + columns: columnConfig.columns.map((column) => { + if ( + column.columnId === eventData.columnId || + column.originalColumnId === originalColumnId + ) { + return { ...column, hidden: true }; + } + return column; + }), + }); + return onEditAction({ + action: 'toggle', + columnId: originalColumnId, + }); + }; -export const createGridHideHandler = ( - columnConfig: ColumnConfig, - setColumnConfig: React.Dispatch>, - onEditAction: (data: LensToggleAction['data']) => void -) => (eventData: { columnId: string }) => { - const originalColumnId = getOriginalId(eventData.columnId); - // directly set the local state of the component to make sure the visualization re-renders immediately - setColumnConfig({ - ...columnConfig, - columns: columnConfig.columns.map((column) => { - if (column.columnId === eventData.columnId || column.originalColumnId === originalColumnId) { - return { ...column, hidden: true }; - } - return column; - }), - }); - return onEditAction({ - action: 'toggle', - columnId: originalColumnId, - }); -}; +export const createGridFilterHandler = + ( + tableRef: React.MutableRefObject, + onClickValue: (data: LensFilterEvent['data']) => void + ) => + (field: string, value: unknown, colIndex: number, rowIndex: number, negate: boolean = false) => { + const col = tableRef.current.columns[colIndex]; + const isDate = col.meta?.type === 'date'; + const timeFieldName = negate && isDate ? undefined : col?.meta?.field; -export const createGridFilterHandler = ( - tableRef: React.MutableRefObject, - onClickValue: (data: LensFilterEvent['data']) => void -) => ( - field: string, - value: unknown, - colIndex: number, - rowIndex: number, - negate: boolean = false -) => { - const col = tableRef.current.columns[colIndex]; - const isDate = col.meta?.type === 'date'; - const timeFieldName = negate && isDate ? undefined : col?.meta?.field; + const data: LensFilterEvent['data'] = { + negate, + data: [ + { + row: rowIndex, + column: colIndex, + value, + table: tableRef.current, + }, + ], + timeFieldName, + }; - const data: LensFilterEvent['data'] = { - negate, - data: [ - { - row: rowIndex, - column: colIndex, - value, - table: tableRef.current, - }, - ], - timeFieldName, + onClickValue(data); }; - onClickValue(data); -}; +export const createTransposeColumnFilterHandler = + ( + onClickValue: (data: LensFilterEvent['data']) => void, + untransposedDataRef: React.MutableRefObject + ) => + ( + bucketValues: Array<{ originalBucketColumn: DatatableColumn; value: unknown }>, + negate: boolean = false + ) => { + if (!untransposedDataRef.current) return; + const originalTable = Object.values(untransposedDataRef.current.tables)[0]; + const timeField = bucketValues.find( + ({ originalBucketColumn }) => originalBucketColumn.meta.type === 'date' + )?.originalBucketColumn; + const isDate = Boolean(timeField); + const timeFieldName = negate && isDate ? undefined : timeField?.meta?.field; -export const createTransposeColumnFilterHandler = ( - onClickValue: (data: LensFilterEvent['data']) => void, - untransposedDataRef: React.MutableRefObject -) => ( - bucketValues: Array<{ originalBucketColumn: DatatableColumn; value: unknown }>, - negate: boolean = false -) => { - if (!untransposedDataRef.current) return; - const originalTable = Object.values(untransposedDataRef.current.tables)[0]; - const timeField = bucketValues.find( - ({ originalBucketColumn }) => originalBucketColumn.meta.type === 'date' - )?.originalBucketColumn; - const isDate = Boolean(timeField); - const timeFieldName = negate && isDate ? undefined : timeField?.meta?.field; + const data: LensFilterEvent['data'] = { + negate, + data: bucketValues.map(({ originalBucketColumn, value }) => { + const columnIndex = originalTable.columns.findIndex( + (c) => c.id === originalBucketColumn.id + ); + const rowIndex = originalTable.rows.findIndex((r) => r[originalBucketColumn.id] === value); + return { + row: rowIndex, + column: columnIndex, + value, + table: originalTable, + }; + }), + timeFieldName, + }; - const data: LensFilterEvent['data'] = { - negate, - data: bucketValues.map(({ originalBucketColumn, value }) => { - const columnIndex = originalTable.columns.findIndex((c) => c.id === originalBucketColumn.id); - const rowIndex = originalTable.rows.findIndex((r) => r[originalBucketColumn.id] === value); - return { - row: rowIndex, - column: columnIndex, - value, - table: originalTable, - }; - }), - timeFieldName, + onClickValue(data); }; - onClickValue(data); -}; - export const createGridSortingConfig = ( sortBy: string | undefined, sortDirection: LensGridDirection, diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.test.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.test.tsx index c156b870e7aa3..312d81e377f32 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.test.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.test.tsx @@ -112,7 +112,7 @@ describe('DatatableComponent', () => { dispatchEvent={onDispatchEvent} getType={jest.fn()} paletteService={chartPluginMock.createPaletteRegistry()} - uiSettings={({ get: jest.fn() } as unknown) as IUiSettingsClient} + uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient} renderMode="edit" /> ) @@ -133,7 +133,7 @@ describe('DatatableComponent', () => { rowHasRowClickTriggerActions={[true, true, true]} renderMode="edit" paletteService={chartPluginMock.createPaletteRegistry()} - uiSettings={({ get: jest.fn() } as unknown) as IUiSettingsClient} + uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient} /> ) ).toMatchSnapshot(); @@ -153,7 +153,7 @@ describe('DatatableComponent', () => { rowHasRowClickTriggerActions={[false, false, false]} renderMode="view" paletteService={chartPluginMock.createPaletteRegistry()} - uiSettings={({ get: jest.fn() } as unknown) as IUiSettingsClient} + uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient} /> ) ).toMatchSnapshot(); @@ -177,7 +177,7 @@ describe('DatatableComponent', () => { getType={jest.fn(() => ({ type: 'buckets' } as IAggType))} renderMode="edit" paletteService={chartPluginMock.createPaletteRegistry()} - uiSettings={({ get: jest.fn() } as unknown) as IUiSettingsClient} + uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient} /> ); @@ -222,7 +222,7 @@ describe('DatatableComponent', () => { getType={jest.fn(() => ({ type: 'buckets' } as IAggType))} renderMode="edit" paletteService={chartPluginMock.createPaletteRegistry()} - uiSettings={({ get: jest.fn() } as unknown) as IUiSettingsClient} + uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient} /> ); @@ -306,7 +306,7 @@ describe('DatatableComponent', () => { getType={jest.fn(() => ({ type: 'buckets' } as IAggType))} renderMode="edit" paletteService={chartPluginMock.createPaletteRegistry()} - uiSettings={({ get: jest.fn() } as unknown) as IUiSettingsClient} + uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient} /> ); @@ -356,7 +356,7 @@ describe('DatatableComponent', () => { )} renderMode="edit" paletteService={chartPluginMock.createPaletteRegistry()} - uiSettings={({ get: jest.fn() } as unknown) as IUiSettingsClient} + uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient} /> ); expect(component.find(VisualizationContainer)).toHaveLength(1); @@ -379,7 +379,7 @@ describe('DatatableComponent', () => { getType={jest.fn()} renderMode="edit" paletteService={chartPluginMock.createPaletteRegistry()} - uiSettings={({ get: jest.fn() } as unknown) as IUiSettingsClient} + uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient} /> ); @@ -429,7 +429,7 @@ describe('DatatableComponent', () => { getType={jest.fn()} renderMode="view" paletteService={chartPluginMock.createPaletteRegistry()} - uiSettings={({ get: jest.fn() } as unknown) as IUiSettingsClient} + uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient} /> ); @@ -459,7 +459,7 @@ describe('DatatableComponent', () => { getType={jest.fn()} renderMode="view" paletteService={chartPluginMock.createPaletteRegistry()} - uiSettings={({ get: jest.fn() } as unknown) as IUiSettingsClient} + uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient} /> ); @@ -487,7 +487,7 @@ describe('DatatableComponent', () => { getType={jest.fn()} renderMode="view" paletteService={chartPluginMock.createPaletteRegistry()} - uiSettings={({ get: jest.fn() } as unknown) as IUiSettingsClient} + uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient} /> ); @@ -513,7 +513,7 @@ describe('DatatableComponent', () => { getType={jest.fn()} renderMode="edit" paletteService={chartPluginMock.createPaletteRegistry()} - uiSettings={({ get: jest.fn() } as unknown) as IUiSettingsClient} + uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient} /> ); // mnake a copy of the data, changing only the name of the first column @@ -548,7 +548,7 @@ describe('DatatableComponent', () => { getType={jest.fn()} renderMode="view" paletteService={chartPluginMock.createPaletteRegistry()} - uiSettings={({ get: jest.fn() } as unknown) as IUiSettingsClient} + uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient} /> ); @@ -583,7 +583,7 @@ describe('DatatableComponent', () => { getType={jest.fn()} renderMode="view" paletteService={chartPluginMock.createPaletteRegistry()} - uiSettings={({ get: jest.fn() } as unknown) as IUiSettingsClient} + uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient} /> ); expect(wrapper.find('[data-test-subj="lnsDataTable-footer-a"]').exists()).toEqual(false); @@ -618,7 +618,7 @@ describe('DatatableComponent', () => { getType={jest.fn()} renderMode="view" paletteService={chartPluginMock.createPaletteRegistry()} - uiSettings={({ get: jest.fn() } as unknown) as IUiSettingsClient} + uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient} /> ); @@ -652,7 +652,7 @@ describe('DatatableComponent', () => { getType={jest.fn()} renderMode="view" paletteService={chartPluginMock.createPaletteRegistry()} - uiSettings={({ get: jest.fn() } as unknown) as IUiSettingsClient} + uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient} /> ); diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx index 6f00dc37fcd52..6be69e5d4d236 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx @@ -116,10 +116,10 @@ export const DatatableComponent = (props: DatatableRenderProps) => { [dispatchEvent] ); - const handleFilterClick = useMemo(() => createGridFilterHandler(firstTableRef, onClickValue), [ - firstTableRef, - onClickValue, - ]); + const handleFilterClick = useMemo( + () => createGridFilterHandler(firstTableRef, onClickValue), + [firstTableRef, onClickValue] + ); const handleTransposedColumnClick = useMemo( () => createTransposeColumnFilterHandler(onClickValue, untransposedDataRef), diff --git a/x-pack/plugins/lens/public/debounced_component/debounced_component.tsx b/x-pack/plugins/lens/public/debounced_component/debounced_component.tsx index 8182f030afebf..1f341c39d9fad 100644 --- a/x-pack/plugins/lens/public/debounced_component/debounced_component.tsx +++ b/x-pack/plugins/lens/public/debounced_component/debounced_component.tsx @@ -14,7 +14,7 @@ import { debounce } from 'lodash'; * During the debounce phase, it will return the previously rendered value. */ export function debouncedComponent(component: FunctionComponent, delay = 256) { - const MemoizedComponent = (memo(component) as unknown) as FunctionComponent; + const MemoizedComponent = memo(component) as unknown as FunctionComponent; return (props: TProps) => { const [cachedProps, setCachedProps] = useState(props); diff --git a/x-pack/plugins/lens/public/drag_drop/drag_drop.test.tsx b/x-pack/plugins/lens/public/drag_drop/drag_drop.test.tsx index e582c4318afc3..1d6c14c09136a 100644 --- a/x-pack/plugins/lens/public/drag_drop/drag_drop.test.tsx +++ b/x-pack/plugins/lens/public/drag_drop/drag_drop.test.tsx @@ -82,7 +82,7 @@ describe('DragDrop', () => { test('removes selection on mouse down before dragging', async () => { const removeAllRanges = jest.fn(); - global.getSelection = jest.fn(() => (({ removeAllRanges } as unknown) as Selection)); + global.getSelection = jest.fn(() => ({ removeAllRanges } as unknown as Selection)); const component = mount( diff --git a/x-pack/plugins/lens/public/drag_drop/drag_drop.tsx b/x-pack/plugins/lens/public/drag_drop/drag_drop.tsx index 5f116d29648c9..6896142aa7bf0 100644 --- a/x-pack/plugins/lens/public/drag_drop/drag_drop.tsx +++ b/x-pack/plugins/lens/public/drag_drop/drag_drop.tsx @@ -703,13 +703,8 @@ const ReorderableDrag = memo(function ReorderableDrag( setReorderState, } = useContext(ReorderContext); - const { - value, - setActiveDropTarget, - activeDraggingProps, - reorderableGroup, - setA11yMessage, - } = props; + const { value, setActiveDropTarget, activeDraggingProps, reorderableGroup, setA11yMessage } = + props; const keyboardMode = activeDraggingProps?.keyboardMode; const activeDropTarget = activeDraggingProps?.activeDropTarget; diff --git a/x-pack/plugins/lens/public/drag_drop/providers/providers.tsx b/x-pack/plugins/lens/public/drag_drop/providers/providers.tsx index 4db19e10ec701..99c144d517c01 100644 --- a/x-pack/plugins/lens/public/drag_drop/providers/providers.tsx +++ b/x-pack/plugins/lens/public/drag_drop/providers/providers.tsx @@ -67,9 +67,10 @@ export function RootDragDropProvider({ children }: { children: React.ReactNode } [setDraggingState] ); - const setA11yMessage = useMemo(() => (message: string) => setA11yMessageState(message), [ - setA11yMessageState, - ]); + const setA11yMessage = useMemo( + () => (message: string) => setA11yMessageState(message), + [setA11yMessageState] + ); const setActiveDropTarget = useMemo( () => (activeDropTarget?: DropIdentifier) => setActiveDropTargetState(activeDropTarget), diff --git a/x-pack/plugins/lens/public/drag_drop/providers/reorder_provider.tsx b/x-pack/plugins/lens/public/drag_drop/providers/reorder_provider.tsx index 77620ea131513..894a83cfd2874 100644 --- a/x-pack/plugins/lens/public/drag_drop/providers/reorder_provider.tsx +++ b/x-pack/plugins/lens/public/drag_drop/providers/reorder_provider.tsx @@ -67,9 +67,10 @@ export function ReorderProvider({ groupId: id, }); - const setReorderState = useMemo(() => (dispatch: SetReorderStateDispatch) => setState(dispatch), [ - setState, - ]); + const setReorderState = useMemo( + () => (dispatch: SetReorderStateDispatch) => setState(dispatch), + [setState] + ); return (

{ + if (!visualization.appendLayer || !visualizationState) { + return null; + } + return visualization.getSupportedLayers?.(visualizationState, layersMeta); + }, [visualization, visualizationState, layersMeta]); + + if (supportedLayers == null) { return null; } - const supportedLayers = visualization.getSupportedLayers?.(visualizationState, layersMeta); - if (supportedLayers?.length === 1) { + if (supportedLayers.length === 1) { return ( registerNewButtonRef(columnId, el), [ - registerNewButtonRef, - columnId, - ]); + const registerNewButtonRefMemoized = useCallback( + (el) => registerNewButtonRef(columnId, el), + [registerNewButtonRef, columnId] + ); const handleOnDrop = useCallback( (droppedItem, selectedDropType) => onDrop(droppedItem, value, selectedDropType), diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx index 25d99ed9bfd41..2668a31d70754 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx @@ -19,9 +19,13 @@ import { LayerPanel } from './layer_panel'; import { coreMock } from 'src/core/public/mocks'; import { generateId } from '../../../id_generator'; import { mountWithProvider } from '../../../mocks'; +import { layerTypes } from '../../../../common'; +import { ReactWrapper } from 'enzyme'; jest.mock('../../../id_generator'); +const waitMs = (time: number) => new Promise((r) => setTimeout(r, time)); + let container: HTMLDivElement | undefined; beforeEach(() => { @@ -58,11 +62,11 @@ describe('ConfigPanel', () => { datasourceMap: { mockindexpattern: mockDatasource, }, - activeVisualization: ({ + activeVisualization: { ...mockVisualization, getLayerIds: () => Object.keys(frame.datasourceLayers), appendLayer: jest.fn(), - } as unknown) as Visualization, + } as unknown as Visualization, datasourceStates: { mockindexpattern: { isLoading: false, @@ -137,7 +141,7 @@ describe('ConfigPanel', () => { const updater = () => 'updated'; updateDatasource('mockindexpattern', updater); - await new Promise((r) => setTimeout(r, 0)); + await waitMs(0); expect(lensStore.dispatch).toHaveBeenCalledTimes(1); expect( (lensStore.dispatch as jest.Mock).mock.calls[0][0].payload.updater( @@ -147,7 +151,7 @@ describe('ConfigPanel', () => { updateAll('mockindexpattern', updater, props.visualizationState); // wait for one tick so async updater has a chance to trigger - await new Promise((r) => setTimeout(r, 0)); + await waitMs(0); expect(lensStore.dispatch).toHaveBeenCalledTimes(2); expect( (lensStore.dispatch as jest.Mock).mock.calls[0][0].payload.updater( @@ -293,4 +297,164 @@ describe('ConfigPanel', () => { expect(focusedEl?.children[0].getAttribute('data-test-subj')).toEqual('lns-layerPanel-1'); }); }); + + describe('initial default value', () => { + function prepareAndMountComponent(props: ReturnType) { + (generateId as jest.Mock).mockReturnValue(`newId`); + return mountWithProvider( + , + + { + preloadedState: { + datasourceStates: { + mockindexpattern: { + isLoading: false, + state: 'state', + }, + }, + activeDatasourceId: 'mockindexpattern', + }, + }, + { + attachTo: container, + } + ); + } + function clickToAddLayer(instance: ReactWrapper) { + act(() => { + instance.find('[data-test-subj="lnsLayerAddButton"]').first().simulate('click'); + }); + instance.update(); + act(() => { + instance + .find(`[data-test-subj="lnsLayerAddButton-${layerTypes.THRESHOLD}"]`) + .first() + .simulate('click'); + }); + instance.update(); + + return waitMs(0); + } + + function clickToAddDimension(instance: ReactWrapper) { + act(() => { + instance.find('[data-test-subj="lns-empty-dimension"]').last().simulate('click'); + }); + return waitMs(0); + } + + it('should not add an initial dimension when not specified', async () => { + const props = getDefaultProps(); + props.activeVisualization.getSupportedLayers = jest.fn(() => [ + { type: layerTypes.DATA, label: 'Data Layer' }, + { + type: layerTypes.THRESHOLD, + label: 'Threshold layer', + }, + ]); + mockDatasource.initializeDimension = jest.fn(); + + const { instance, lensStore } = await prepareAndMountComponent(props); + await clickToAddLayer(instance); + + expect(lensStore.dispatch).toHaveBeenCalledTimes(1); + expect(mockDatasource.initializeDimension).not.toHaveBeenCalled(); + }); + + it('should not add an initial dimension when initialDimensions are not available for the given layer type', async () => { + const props = getDefaultProps(); + props.activeVisualization.getSupportedLayers = jest.fn(() => [ + { + type: layerTypes.DATA, + label: 'Data Layer', + initialDimensions: [ + { + groupId: 'testGroup', + columnId: 'myColumn', + dataType: 'number', + label: 'Initial value', + staticValue: 100, + }, + ], + }, + { + type: layerTypes.THRESHOLD, + label: 'Threshold layer', + }, + ]); + mockDatasource.initializeDimension = jest.fn(); + + const { instance, lensStore } = await prepareAndMountComponent(props); + await clickToAddLayer(instance); + + expect(lensStore.dispatch).toHaveBeenCalledTimes(1); + expect(mockDatasource.initializeDimension).not.toHaveBeenCalled(); + }); + + it('should use group initial dimension value when adding a new layer if available', async () => { + const props = getDefaultProps(); + props.activeVisualization.getSupportedLayers = jest.fn(() => [ + { type: layerTypes.DATA, label: 'Data Layer' }, + { + type: layerTypes.THRESHOLD, + label: 'Threshold layer', + initialDimensions: [ + { + groupId: 'testGroup', + columnId: 'myColumn', + dataType: 'number', + label: 'Initial value', + staticValue: 100, + }, + ], + }, + ]); + mockDatasource.initializeDimension = jest.fn(); + + const { instance, lensStore } = await prepareAndMountComponent(props); + await clickToAddLayer(instance); + + expect(lensStore.dispatch).toHaveBeenCalledTimes(1); + expect(mockDatasource.initializeDimension).toHaveBeenCalledWith(undefined, 'newId', { + columnId: 'myColumn', + dataType: 'number', + groupId: 'testGroup', + label: 'Initial value', + staticValue: 100, + }); + }); + + it('should add an initial dimension value when clicking on the empty dimension button', async () => { + const props = getDefaultProps(); + props.activeVisualization.getSupportedLayers = jest.fn(() => [ + { + type: layerTypes.DATA, + label: 'Data Layer', + initialDimensions: [ + { + groupId: 'a', + columnId: 'newId', + dataType: 'number', + label: 'Initial value', + staticValue: 100, + }, + ], + }, + ]); + mockDatasource.initializeDimension = jest.fn(); + + const { instance, lensStore } = await prepareAndMountComponent(props); + + await clickToAddDimension(instance); + expect(lensStore.dispatch).toHaveBeenCalledTimes(1); + + expect(mockDatasource.initializeDimension).toHaveBeenCalledWith('state', 'first', { + groupId: 'a', + columnId: 'newId', + dataType: 'number', + label: 'Initial value', + staticValue: 100, + }); + }); + }); }); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx index f7fe2beefa963..57e4cf5b8dffd 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx @@ -26,8 +26,9 @@ import { useLensSelector, selectVisualization, VisualizationState, + LensAppState, } from '../../../state_management'; -import { AddLayerButton } from './add_layer'; +import { AddLayerButton, getLayerType } from './add_layer'; export const ConfigPanelWrapper = memo(function ConfigPanelWrapper(props: ConfigPanelWrapperProps) { const visualization = useLensSelector(selectVisualization); @@ -177,6 +178,33 @@ export function LayerPanels( layerIds.length ) === 'clear' } + onEmptyDimensionAdd={(columnId, { groupId }) => { + // avoid state update if the datasource does not support initializeDimension + if ( + activeDatasourceId != null && + datasourceMap[activeDatasourceId]?.initializeDimension + ) { + dispatchLens( + updateState({ + subType: 'LAYER_DEFAULT_DIMENSION', + updater: (state) => + addInitialValueIfAvailable({ + ...props, + state, + activeDatasourceId, + layerId, + layerType: getLayerType( + activeVisualization, + state.visualization.state, + layerId + ), + columnId, + groupId, + }), + }) + ); + } + }} onRemoveLayer={() => { dispatchLens( updateState({ @@ -232,21 +260,92 @@ export function LayerPanels( dispatchLens( updateState({ subType: 'ADD_LAYER', - updater: (state) => - appendLayer({ + updater: (state) => { + const newState = appendLayer({ activeVisualization, generateId: () => id, trackUiEvent, activeDatasource: datasourceMap[activeDatasourceId!], state, layerType, - }), + }); + return addInitialValueIfAvailable({ + ...props, + activeDatasourceId: activeDatasourceId!, + state: newState, + layerId: id, + layerType, + }); + }, }) ); - setNextFocusedLayerId(id); }} /> ); } + +function addInitialValueIfAvailable({ + state, + activeVisualization, + framePublicAPI, + layerType, + activeDatasourceId, + datasourceMap, + layerId, + columnId, + groupId, +}: ConfigPanelWrapperProps & { + state: LensAppState; + activeDatasourceId: string; + activeVisualization: Visualization; + layerId: string; + layerType: string; + columnId?: string; + groupId?: string; +}) { + const layerInfo = activeVisualization + .getSupportedLayers(state.visualization.state, framePublicAPI) + .find(({ type }) => type === layerType); + + const activeDatasource = datasourceMap[activeDatasourceId]; + + if (layerInfo?.initialDimensions && activeDatasource?.initializeDimension) { + const info = groupId + ? layerInfo.initialDimensions.find(({ groupId: id }) => id === groupId) + : // pick the first available one if not passed + layerInfo.initialDimensions[0]; + + if (info) { + return { + ...state, + datasourceStates: { + ...state.datasourceStates, + [activeDatasourceId]: { + ...state.datasourceStates[activeDatasourceId], + state: activeDatasource.initializeDimension( + state.datasourceStates[activeDatasourceId].state, + layerId, + { + ...info, + columnId: columnId || info.columnId, + } + ), + }, + }, + visualization: { + ...state.visualization, + state: activeVisualization.setDimension({ + groupId: info.groupId, + layerId, + columnId: columnId || info.columnId, + prevState: state.visualization.state, + frame: framePublicAPI, + }), + }, + }; + } + } + return state; +} diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx index 13b7b8cfecf56..f777fd0976dfd 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx @@ -83,6 +83,7 @@ describe('LayerPanel', () => { registerNewLayerRef: jest.fn(), isFullscreen: false, toggleFullscreen: jest.fn(), + onEmptyDimensionAdd: jest.fn(), }; } @@ -920,4 +921,33 @@ describe('LayerPanel', () => { expect(updateVisualization).toHaveBeenCalledTimes(1); }); }); + + describe('add a new dimension', () => { + it('should call onEmptyDimensionAdd callback on new dimension creation', async () => { + mockVisualization.getConfiguration.mockReturnValue({ + groups: [ + { + groupLabel: 'A', + groupId: 'a', + accessors: [], + filterOperations: () => true, + supportsMoreColumns: true, + dataTestSubj: 'lnsGroup', + }, + ], + }); + const props = getDefaultProps(); + const { instance } = await mountWithProvider(); + + act(() => { + instance.find('[data-test-subj="lns-empty-dimension"]').first().simulate('click'); + }); + instance.update(); + + expect(props.onEmptyDimensionAdd).toHaveBeenCalledWith( + 'newid', + expect.objectContaining({ groupId: 'a' }) + ); + }); + }); }); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx index c729885fef8a9..8c947d3502f93 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx @@ -57,6 +57,7 @@ export function LayerPanel( onRemoveLayer: () => void; registerNewLayerRef: (layerId: string, instance: HTMLDivElement | null) => void; toggleFullscreen: () => void; + onEmptyDimensionAdd: (columnId: string, group: { groupId: string }) => void; } ) { const [activeDimension, setActiveDimension] = useState( @@ -88,10 +89,10 @@ export function LayerPanel( }, [activeVisualization.id]); const panelRef = useRef(null); - const registerLayerRef = useCallback((el) => registerNewLayerRef(layerId, el), [ - layerId, - registerNewLayerRef, - ]); + const registerLayerRef = useCallback( + (el) => registerNewLayerRef(layerId, el), + [layerId, registerNewLayerRef] + ); const layerVisualizationConfigProps = { layerId, @@ -124,7 +125,11 @@ export function LayerPanel( dateRange, }; - const { groups, supportStaticValue } = useMemo( + const { + groups, + supportStaticValue, + supportFieldFormat = true, + } = useMemo( () => activeVisualization.getConfiguration(layerVisualizationConfigProps), // eslint-disable-next-line react-hooks/exhaustive-deps [ @@ -166,7 +171,7 @@ export function LayerPanel( columnId, groupId, layerId: targetLayerId, - } = (targetItem as unknown) as DraggedOperation; + } = targetItem as unknown as DraggedOperation; if (dropType === 'reorder' || dropType === 'field_replace' || dropType === 'field_add') { setNextFocusedButtonId(droppedItem.id); } else { @@ -227,13 +232,25 @@ export function LayerPanel( const isDimensionPanelOpen = Boolean(activeId); const updateDataLayerState = useCallback( - (newState: unknown, { isDimensionComplete = true }: { isDimensionComplete?: boolean } = {}) => { + ( + newState: unknown, + { + isDimensionComplete = true, + // this flag is a hack to force a sync render where it was planned an async/setTimeout state update + // TODO: revisit this once we get rid of updateDatasourceAsync upstream + forceRender = false, + }: { isDimensionComplete?: boolean; forceRender?: boolean } = {} + ) => { if (!activeGroup || !activeId) { return; } if (allAccessors.includes(activeId)) { if (isDimensionComplete) { - updateDatasourceAsync(datasourceId, newState); + if (forceRender) { + updateDatasource(datasourceId, newState); + } else { + updateDatasourceAsync(datasourceId, newState); + } } else { // The datasource can indicate that the previously-valid column is no longer // complete, which clears the visualization. This keeps the flyout open and reuses @@ -263,7 +280,11 @@ export function LayerPanel( ); setActiveDimension({ ...activeDimension, isNew: false }); } else { - updateDatasourceAsync(datasourceId, newState); + if (forceRender) { + updateDatasource(datasourceId, newState); + } else { + updateDatasourceAsync(datasourceId, newState); + } } }, // eslint-disable-next-line react-hooks/exhaustive-deps @@ -295,11 +316,10 @@ export function LayerPanel( hasBorder hasShadow > -
+
)} -
+ {groups.map((group, groupIndex) => { const isMissing = !isEmptyLayer && group.required && group.accessors.length === 0; @@ -460,6 +480,8 @@ export function LayerPanel( columnId: accessorConfig.columnId, groupId: group.groupId, filterOperations: group.filterOperations, + invalid: group.invalid, + invalidMessage: group.invalidMessage, }} /> @@ -478,6 +500,7 @@ export function LayerPanel( layerDatasource={layerDatasource} layerDatasourceDropProps={layerDatasourceDropProps} onClick={(id) => { + props.onEmptyDimensionAdd(id, group); setActiveDimension({ activeGroup: group, activeId: id, @@ -538,6 +561,8 @@ export function LayerPanel( toggleFullscreen, isFullscreen, setState: updateDataLayerState, + supportStaticValue: Boolean(supportStaticValue), + supportFieldFormat: Boolean(supportFieldFormat), layerType: activeVisualization.getLayerType(layerId, visualizationState), }} /> diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_settings.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_settings.test.tsx new file mode 100644 index 0000000000000..04c430143a3c8 --- /dev/null +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_settings.test.tsx @@ -0,0 +1,71 @@ +/* + * 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 { + createMockFramePublicAPI, + createMockVisualization, + mountWithProvider, +} from '../../../mocks'; +import { Visualization } from '../../../types'; +import { LayerSettings } from './layer_settings'; + +describe('LayerSettings', () => { + let mockVisualization: jest.Mocked; + const frame = createMockFramePublicAPI(); + + function getDefaultProps() { + return { + activeVisualization: mockVisualization, + layerConfigProps: { + layerId: 'myLayer', + state: {}, + frame, + dateRange: { fromDate: 'now-7d', toDate: 'now' }, + activeData: frame.activeData, + setState: jest.fn(), + }, + }; + } + + beforeEach(() => { + mockVisualization = { + ...createMockVisualization(), + id: 'testVis', + visualizationTypes: [ + { + icon: 'empty', + id: 'testVis', + label: 'TEST1', + groupLabel: 'testVisGroup', + }, + ], + }; + }); + + it('should render nothing with no custom renderer nor description', async () => { + // @ts-expect-error + mockVisualization.getDescription.mockReturnValue(undefined); + const { instance } = await mountWithProvider(); + expect(instance.html()).toBe(null); + }); + + it('should render a static header if visualization has only a description value', async () => { + mockVisualization.getDescription.mockReturnValue({ + icon: 'myIcon', + label: 'myVisualizationType', + }); + const { instance } = await mountWithProvider(); + expect(instance.find('StaticHeader').first().prop('label')).toBe('myVisualizationType'); + }); + + it('should call the custom renderer if available', async () => { + mockVisualization.renderLayerHeader = jest.fn(); + await mountWithProvider(); + expect(mockVisualization.renderLayerHeader).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_settings.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_settings.tsx index 467b1ecfe1b5b..fc88ff2af8bbe 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_settings.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_settings.tsx @@ -6,44 +6,23 @@ */ import React from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiTitle } from '@elastic/eui'; import { NativeRenderer } from '../../../native_renderer'; import { Visualization, VisualizationLayerWidgetProps } from '../../../types'; +import { StaticHeader } from '../../../shared_components'; export function LayerSettings({ - layerId, activeVisualization, layerConfigProps, }: { - layerId: string; activeVisualization: Visualization; layerConfigProps: VisualizationLayerWidgetProps; }) { - const description = activeVisualization.getDescription(layerConfigProps.state); - if (!activeVisualization.renderLayerHeader) { + const description = activeVisualization.getDescription(layerConfigProps.state); if (!description) { return null; } - return ( - - {description.icon && ( - - {' '} - - )} - - -
{description.label}
-
-
-
- ); + return ; } return ( diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/remove_layer_button.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/remove_layer_button.tsx index fbc498b729d2a..145f621a5f405 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/remove_layer_button.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/remove_layer_button.tsx @@ -52,7 +52,7 @@ export function RemoveLayerButton({ // which is a strange UX in this case. e.target.blur doesn't work // due to who knows what, but probably event re-writing. Additionally, // activeElement does not have blur so, we need to do some casting + safeguards. - const el = (document.activeElement as unknown) as { blur: () => void }; + const el = document.activeElement as unknown as { blur: () => void }; if (el?.blur) { el.blur(); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/use_focus_update.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/use_focus_update.tsx index 34ab9cc60bafb..9bfc3ed715302 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/use_focus_update.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/use_focus_update.tsx @@ -19,7 +19,7 @@ const getFirstFocusable = (el: HTMLElement | null) => { if (!firstFocusable) { return null; } - return (firstFocusable as unknown) as { focus: () => void }; + return firstFocusable as unknown as { focus: () => void }; }; type RefsById = Record; diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx index 7c5fd4f5b8845..4be9de78dedce 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx @@ -603,9 +603,11 @@ describe('editor_frame', () => { instance.find('button[data-test-subj="datasource-switch"]').simulate('click'); }); await act(async () => { - (document.querySelector( - '[data-test-subj="datasource-switch-testDatasource2"]' - ) as HTMLButtonElement).click(); + ( + document.querySelector( + '[data-test-subj="datasource-switch-testDatasource2"]' + ) as HTMLButtonElement + ).click(); }); instance.update(); expect(mockDatasource2.initialize).toHaveBeenCalled(); @@ -618,9 +620,11 @@ describe('editor_frame', () => { instance.find('button[data-test-subj="datasource-switch"]').simulate('click'); await act(async () => { - (document.querySelector( - '[data-test-subj="datasource-switch-testDatasource2"]' - ) as HTMLButtonElement).click(); + ( + document.querySelector( + '[data-test-subj="datasource-switch-testDatasource2"]' + ) as HTMLButtonElement + ).click(); }); expect(mockDatasource2.renderDataPanel).toHaveBeenCalledWith( diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/expression_helpers.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/expression_helpers.ts index 4acc56f1dff3d..2ba93608eddc7 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/expression_helpers.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/expression_helpers.ts @@ -31,12 +31,9 @@ export function prependDatasourceExpression( if (datasourceExpressions.length === 0 || visualizationExpression === null) { return null; } - const parsedDatasourceExpressions: Array< - [string, Ast] - > = datasourceExpressions.map(([layerId, expr]) => [ - layerId, - typeof expr === 'string' ? fromExpression(expr) : expr, - ]); + const parsedDatasourceExpressions: Array<[string, Ast]> = datasourceExpressions.map( + ([layerId, expr]) => [layerId, typeof expr === 'string' ? fromExpression(expr) : expr] + ); const datafetchExpression: ExpressionFunctionAST = { type: 'function', diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.test.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.test.ts index 632989057b488..90fa2ab080dd2 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.test.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.test.ts @@ -45,21 +45,22 @@ describe('suggestion helpers', () => { generateSuggestion(), ]); const suggestedState = {}; - const suggestions = getSuggestions({ - visualizationMap: { - vis1: { - ...mockVisualization, - getSuggestions: () => [ - { - score: 0.5, - title: 'Test', - state: suggestedState, - previewIcon: 'empty', - }, - ], - }, + const visualizationMap = { + vis1: { + ...mockVisualization, + getSuggestions: () => [ + { + score: 0.5, + title: 'Test', + state: suggestedState, + previewIcon: 'empty', + }, + ], }, - activeVisualizationId: 'vis1', + }; + const suggestions = getSuggestions({ + visualizationMap, + activeVisualization: visualizationMap.vis1, visualizationState: {}, datasourceMap, datasourceStates, @@ -74,38 +75,39 @@ describe('suggestion helpers', () => { datasourceMap.mock.getDatasourceSuggestionsFromCurrentState.mockReturnValue([ generateSuggestion(), ]); - const suggestions = getSuggestions({ - visualizationMap: { - vis1: { - ...mockVisualization1, - getSuggestions: () => [ - { - score: 0.5, - title: 'Test', - state: {}, - previewIcon: 'empty', - }, - { - score: 0.5, - title: 'Test2', - state: {}, - previewIcon: 'empty', - }, - ], - }, - vis2: { - ...mockVisualization2, - getSuggestions: () => [ - { - score: 0.5, - title: 'Test3', - state: {}, - previewIcon: 'empty', - }, - ], - }, + const visualizationMap = { + vis1: { + ...mockVisualization1, + getSuggestions: () => [ + { + score: 0.5, + title: 'Test', + state: {}, + previewIcon: 'empty', + }, + { + score: 0.5, + title: 'Test2', + state: {}, + previewIcon: 'empty', + }, + ], + }, + vis2: { + ...mockVisualization2, + getSuggestions: () => [ + { + score: 0.5, + title: 'Test3', + state: {}, + previewIcon: 'empty', + }, + ], }, - activeVisualizationId: 'vis1', + }; + const suggestions = getSuggestions({ + visualizationMap, + activeVisualization: visualizationMap.vis1, visualizationState: {}, datasourceMap, datasourceStates, @@ -116,11 +118,12 @@ describe('suggestion helpers', () => { it('should call getDatasourceSuggestionsForField when a field is passed', () => { datasourceMap.mock.getDatasourceSuggestionsForField.mockReturnValue([generateSuggestion()]); const droppedField = {}; + const visualizationMap = { + vis1: createMockVisualization(), + }; getSuggestions({ - visualizationMap: { - vis1: createMockVisualization(), - }, - activeVisualizationId: 'vis1', + visualizationMap, + activeVisualization: visualizationMap.vis1, visualizationState: {}, datasourceMap, datasourceStates, @@ -128,7 +131,8 @@ describe('suggestion helpers', () => { }); expect(datasourceMap.mock.getDatasourceSuggestionsForField).toHaveBeenCalledWith( datasourceStates.mock.state, - droppedField + droppedField, + expect.any(Function) ); }); @@ -148,12 +152,13 @@ describe('suggestion helpers', () => { mock2: createMockDatasource('a'), mock3: createMockDatasource('a'), }; + const visualizationMap = { + vis1: createMockVisualization(), + }; const droppedField = {}; getSuggestions({ - visualizationMap: { - vis1: createMockVisualization(), - }, - activeVisualizationId: 'vis1', + visualizationMap, + activeVisualization: visualizationMap.vis1, visualizationState: {}, datasourceMap: multiDatasourceMap, datasourceStates: multiDatasourceStates, @@ -161,11 +166,13 @@ describe('suggestion helpers', () => { }); expect(multiDatasourceMap.mock.getDatasourceSuggestionsForField).toHaveBeenCalledWith( multiDatasourceStates.mock.state, - droppedField + droppedField, + expect.any(Function) ); expect(multiDatasourceMap.mock2.getDatasourceSuggestionsForField).toHaveBeenCalledWith( multiDatasourceStates.mock2.state, - droppedField + droppedField, + expect.any(Function) ); expect(multiDatasourceMap.mock3.getDatasourceSuggestionsForField).not.toHaveBeenCalled(); }); @@ -174,11 +181,14 @@ describe('suggestion helpers', () => { datasourceMap.mock.getDatasourceSuggestionsForVisualizeField.mockReturnValue([ generateSuggestion(), ]); + + const visualizationMap = { + vis1: createMockVisualization(), + }; + getSuggestions({ - visualizationMap: { - vis1: createMockVisualization(), - }, - activeVisualizationId: 'vis1', + visualizationMap, + activeVisualization: visualizationMap.vis1, visualizationState: {}, datasourceMap, datasourceStates, @@ -214,11 +224,13 @@ describe('suggestion helpers', () => { indexPatternId: '1', fieldName: 'test', }; + + const visualizationMap = { + vis1: createMockVisualization(), + }; getSuggestions({ - visualizationMap: { - vis1: createMockVisualization(), - }, - activeVisualizationId: 'vis1', + visualizationMap, + activeVisualization: visualizationMap.vis1, visualizationState: {}, datasourceMap: multiDatasourceMap, datasourceStates: multiDatasourceStates, @@ -245,38 +257,39 @@ describe('suggestion helpers', () => { datasourceMap.mock.getDatasourceSuggestionsFromCurrentState.mockReturnValue([ generateSuggestion(), ]); - const suggestions = getSuggestions({ - visualizationMap: { - vis1: { - ...mockVisualization1, - getSuggestions: () => [ - { - score: 0.2, - title: 'Test', - state: {}, - previewIcon: 'empty', - }, - { - score: 0.8, - title: 'Test2', - state: {}, - previewIcon: 'empty', - }, - ], - }, - vis2: { - ...mockVisualization2, - getSuggestions: () => [ - { - score: 0.6, - title: 'Test3', - state: {}, - previewIcon: 'empty', - }, - ], - }, + const visualizationMap = { + vis1: { + ...mockVisualization1, + getSuggestions: () => [ + { + score: 0.2, + title: 'Test', + state: {}, + previewIcon: 'empty', + }, + { + score: 0.8, + title: 'Test2', + state: {}, + previewIcon: 'empty', + }, + ], }, - activeVisualizationId: 'vis1', + vis2: { + ...mockVisualization2, + getSuggestions: () => [ + { + score: 0.6, + title: 'Test3', + state: {}, + previewIcon: 'empty', + }, + ], + }, + }; + const suggestions = getSuggestions({ + visualizationMap, + activeVisualization: visualizationMap.vis1, visualizationState: {}, datasourceMap, datasourceStates, @@ -305,12 +318,13 @@ describe('suggestion helpers', () => { { state: {}, table: table1, keptLayerIds: ['first'] }, { state: {}, table: table2, keptLayerIds: ['first'] }, ]); + const visualizationMap = { + vis1: mockVisualization1, + vis2: mockVisualization2, + }; getSuggestions({ - visualizationMap: { - vis1: mockVisualization1, - vis2: mockVisualization2, - }, - activeVisualizationId: 'vis1', + visualizationMap, + activeVisualization: visualizationMap.vis1, visualizationState: {}, datasourceMap, datasourceStates, @@ -357,18 +371,20 @@ describe('suggestion helpers', () => { previewIcon: 'empty', }, ]); - const suggestions = getSuggestions({ - visualizationMap: { - vis1: { - ...mockVisualization1, - getSuggestions: vis1Suggestions, - }, - vis2: { - ...mockVisualization2, - getSuggestions: vis2Suggestions, - }, + const visualizationMap = { + vis1: { + ...mockVisualization1, + getSuggestions: vis1Suggestions, }, - activeVisualizationId: 'vis1', + vis2: { + ...mockVisualization2, + getSuggestions: vis2Suggestions, + }, + }; + + const suggestions = getSuggestions({ + visualizationMap, + activeVisualization: visualizationMap.vis1, visualizationState: {}, datasourceMap, datasourceStates, @@ -389,12 +405,15 @@ describe('suggestion helpers', () => { generateSuggestion(0), generateSuggestion(1), ]); + + const visualizationMap = { + vis1: mockVisualization1, + vis2: mockVisualization2, + }; + getSuggestions({ - visualizationMap: { - vis1: mockVisualization1, - vis2: mockVisualization2, - }, - activeVisualizationId: 'vis1', + visualizationMap, + activeVisualization: visualizationMap.vis1, visualizationState: {}, datasourceMap, datasourceStates, @@ -419,12 +438,13 @@ describe('suggestion helpers', () => { generateSuggestion(0), generateSuggestion(1), ]); + const visualizationMap = { + vis1: mockVisualization1, + vis2: mockVisualization2, + }; getSuggestions({ - visualizationMap: { - vis1: mockVisualization1, - vis2: mockVisualization2, - }, - activeVisualizationId: 'vis1', + visualizationMap, + activeVisualization: visualizationMap.vis1, visualizationState: {}, datasourceMap, datasourceStates, @@ -451,12 +471,14 @@ describe('suggestion helpers', () => { generateSuggestion(0), generateSuggestion(1), ]); + const visualizationMap = { + vis1: mockVisualization1, + vis2: mockVisualization2, + }; + getSuggestions({ - visualizationMap: { - vis1: mockVisualization1, - vis2: mockVisualization2, - }, - activeVisualizationId: 'vis1', + visualizationMap, + activeVisualization: visualizationMap.vis1, visualizationState: {}, datasourceMap, datasourceStates, @@ -538,7 +560,8 @@ describe('suggestion helpers', () => { humanData: { label: 'myfieldLabel', }, - } + }, + expect.any(Function) ); }); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts index 2f3fe3795a881..a5c7871f33dfc 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_helpers.ts @@ -58,7 +58,7 @@ export function getSuggestions({ datasourceMap, datasourceStates, visualizationMap, - activeVisualizationId, + activeVisualization, subVisualizationId, visualizationState, field, @@ -69,7 +69,7 @@ export function getSuggestions({ datasourceMap: DatasourceMap; datasourceStates: DatasourceStates; visualizationMap: VisualizationMap; - activeVisualizationId: string | null; + activeVisualization?: Visualization; subVisualizationId?: string; visualizationState: unknown; field?: unknown; @@ -83,16 +83,12 @@ export function getSuggestions({ const layerTypesMap = datasources.reduce((memo, [datasourceId, datasource]) => { const datasourceState = datasourceStates[datasourceId].state; - if (!activeVisualizationId || !datasourceState || !visualizationMap[activeVisualizationId]) { + if (!activeVisualization || !datasourceState) { return memo; } const layers = datasource.getLayers(datasourceState); for (const layerId of layers) { - const type = getLayerType( - visualizationMap[activeVisualizationId], - visualizationState, - layerId - ); + const type = getLayerType(activeVisualization, visualizationState, layerId); memo[layerId] = type; } return memo; @@ -112,7 +108,11 @@ export function getSuggestions({ visualizeTriggerFieldContext.fieldName ); } else if (field) { - dataSourceSuggestions = datasource.getDatasourceSuggestionsForField(datasourceState, field); + dataSourceSuggestions = datasource.getDatasourceSuggestionsForField( + datasourceState, + field, + (layerId) => isLayerSupportedByVisualization(layerId, [layerTypes.DATA]) // a field dragged to workspace should added to data layer + ); } else { dataSourceSuggestions = datasource.getDatasourceSuggestionsFromCurrentState( datasourceState, @@ -121,7 +121,6 @@ export function getSuggestions({ } return dataSourceSuggestions.map((suggestion) => ({ ...suggestion, datasourceId })); }); - // Pass all table suggestions to all visualization extensions to get visualization suggestions // and rank them by score return Object.entries(visualizationMap) @@ -139,12 +138,8 @@ export function getSuggestions({ .flatMap((datasourceSuggestion) => { const table = datasourceSuggestion.table; const currentVisualizationState = - visualizationId === activeVisualizationId ? visualizationState : undefined; - const palette = - mainPalette || - (activeVisualizationId && visualizationMap[activeVisualizationId]?.getMainPalette - ? visualizationMap[activeVisualizationId].getMainPalette?.(visualizationState) - : undefined); + visualizationId === activeVisualization?.id ? visualizationState : undefined; + const palette = mainPalette || activeVisualization?.getMainPalette?.(visualizationState); return getVisualizationSuggestions( visualization, @@ -169,14 +164,14 @@ export function getVisualizeFieldSuggestions({ datasourceMap, datasourceStates, visualizationMap, - activeVisualizationId, + activeVisualization, visualizationState, visualizeTriggerFieldContext, }: { datasourceMap: DatasourceMap; datasourceStates: DatasourceStates; visualizationMap: VisualizationMap; - activeVisualizationId: string | null; + activeVisualization: Visualization; subVisualizationId?: string; visualizationState: unknown; visualizeTriggerFieldContext?: VisualizeFieldContext; @@ -185,12 +180,12 @@ export function getVisualizeFieldSuggestions({ datasourceMap, datasourceStates, visualizationMap, - activeVisualizationId, + activeVisualization, visualizationState, visualizeTriggerFieldContext, }); if (suggestions.length) { - return suggestions.find((s) => s.visualizationId === activeVisualizationId) || suggestions[0]; + return suggestions.find((s) => s.visualizationId === activeVisualization?.id) || suggestions[0]; } } @@ -263,18 +258,19 @@ export function getTopSuggestionForField( (datasourceLayer) => datasourceLayer.getTableSpec().length > 0 ); - const mainPalette = - visualization.activeId && visualizationMap[visualization.activeId]?.getMainPalette - ? visualizationMap[visualization.activeId].getMainPalette?.(visualization.state) - : undefined; + const activeVisualization = visualization.activeId + ? visualizationMap[visualization.activeId] + : undefined; + + const mainPalette = activeVisualization?.getMainPalette?.(visualization.state); const suggestions = getSuggestions({ datasourceMap: { [datasource.id]: datasource }, datasourceStates, visualizationMap: hasData && visualization.activeId - ? { [visualization.activeId]: visualizationMap[visualization.activeId] } + ? { [visualization.activeId]: activeVisualization! } : visualizationMap, - activeVisualizationId: visualization.activeId, + activeVisualization, visualizationState: visualization.state, field, mainPalette, diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx index 858fcedf215ef..5e5e19ea29e84 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx @@ -201,7 +201,9 @@ export function SuggestionPanel({ datasourceMap, datasourceStates: currentDatasourceStates, visualizationMap, - activeVisualizationId: currentVisualization.activeId, + activeVisualization: currentVisualization.activeId + ? visualizationMap[currentVisualization.activeId] + : undefined, visualizationState: currentVisualization.state, activeData, }) diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.test.tsx index 9e80dcfc47420..e7abf291b6eba 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.test.tsx @@ -129,14 +129,14 @@ describe('chart_switch', () => { datasourceLayers: layers.reduce( (acc, layerId) => ({ ...acc, - [layerId]: ({ + [layerId]: { getTableSpec: jest.fn(() => { return [{ columnId: 2 }]; }), getOperationForColumnId() { return {}; }, - } as unknown) as DatasourcePublicAPI, + } as unknown as DatasourcePublicAPI, }), {} as Record ), diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx index 010e4d73c4791..51d4f2955a52b 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx @@ -382,7 +382,7 @@ export const ChartSwitch = memo(function ChartSwitch(props: Props) { 'xpack.lens.chartSwitch.dataLossDescription', { defaultMessage: - 'Selecting this visualization type will result in a partial loss of currently applied configuration selections.', + 'Selecting this visualization type will remove incompatible configuration options and multiple layers, if present', } )} iconProps={{ @@ -515,11 +515,14 @@ function getTopSuggestion( props.visualizationMap[visualization.activeId].getMainPalette ? props.visualizationMap[visualization.activeId].getMainPalette!(visualization.state) : undefined; + const unfilteredSuggestions = getSuggestions({ datasourceMap: props.datasourceMap, datasourceStates, visualizationMap: { [visualizationId]: newVisualization }, - activeVisualizationId: visualization.activeId, + activeVisualization: visualization.activeId + ? props.visualizationMap[visualization.activeId] + : undefined, visualizationState: visualization.state, subVisualizationId, activeData: props.framePublicAPI.activeData, diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx index 380ff365337cc..4df3632c7f7da 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx @@ -45,7 +45,7 @@ const defaultPermissions: Record> >) = newCapabilities; @@ -79,7 +79,7 @@ describe('workspace_panel', () => { beforeEach(() => { // These are used in specific tests to assert function calls - trigger = ({ exec: jest.fn() } as unknown) as jest.Mocked; + trigger = { exec: jest.fn() } as unknown as jest.Mocked; uiActionsMock = uiActionsPluginMock.createStartContract(); uiActionsMock.getTrigger.mockReturnValue(trigger); mockVisualization = createMockVisualization(); @@ -419,8 +419,8 @@ describe('workspace_panel', () => { expect(expressionRendererMock).toHaveBeenCalledTimes(1); - const indexPattern = ({ id: 'index1' } as unknown) as IndexPattern; - const field = ({ name: 'myfield' } as unknown) as FieldSpec; + const indexPattern = { id: 'index1' } as unknown as IndexPattern; + const field = { name: 'myfield' } as unknown as FieldSpec; await act(async () => { instance.setProps({ diff --git a/x-pack/plugins/lens/public/editor_frame_service/mocks.tsx b/x-pack/plugins/lens/public/editor_frame_service/mocks.tsx index ff0d81c7fa277..f2db52060c351 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/mocks.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/mocks.tsx @@ -43,19 +43,19 @@ export type MockedStartDependencies = Omit { expressionRenderer, basePath, inspector: inspectorPluginMock.createStartContract(), - indexPatternService: ({ + indexPatternService: { get: (id: string) => Promise.resolve({ id }), - } as unknown) as IndexPatternsContract, + } as unknown as IndexPatternsContract, capabilities: { canSaveDashboards: true, canSaveVisualizations: true, @@ -408,12 +408,12 @@ describe('embeddable', () => { }); it('should re-render when dashboard view/edit mode changes if dynamic actions are set', async () => { - const sampleInput = ({ + const sampleInput = { id: '123', enhancements: { dynamicActions: {}, }, - } as unknown) as LensEmbeddableInput; + } as unknown as LensEmbeddableInput; const embeddable = new Embeddable( { timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter, @@ -635,7 +635,7 @@ describe('embeddable', () => { expressionRenderer, basePath, inspector: inspectorPluginMock.createStartContract(), - indexPatternService: ({ get: jest.fn() } as unknown) as IndexPatternsContract, + indexPatternService: { get: jest.fn() } as unknown as IndexPatternsContract, capabilities: { canSaveDashboards: true, canSaveVisualizations: true, @@ -836,7 +836,7 @@ describe('embeddable', () => { errors: undefined, }), }, - ({ id: '123', onLoad } as unknown) as LensEmbeddableInput + { id: '123', onLoad } as unknown as LensEmbeddableInput ); await embeddable.initializeSavedVis({ id: '123' } as LensEmbeddableInput); @@ -909,7 +909,7 @@ describe('embeddable', () => { errors: undefined, }), }, - ({ id: '123', onFilter } as unknown) as LensEmbeddableInput + { id: '123', onFilter } as unknown as LensEmbeddableInput ); await embeddable.initializeSavedVis({ id: '123' } as LensEmbeddableInput); @@ -957,7 +957,7 @@ describe('embeddable', () => { errors: undefined, }), }, - ({ id: '123', onBrushEnd } as unknown) as LensEmbeddableInput + { id: '123', onBrushEnd } as unknown as LensEmbeddableInput ); await embeddable.initializeSavedVis({ id: '123' } as LensEmbeddableInput); @@ -1005,7 +1005,7 @@ describe('embeddable', () => { errors: undefined, }), }, - ({ id: '123', onTableRowClick } as unknown) as LensEmbeddableInput + { id: '123', onTableRowClick } as unknown as LensEmbeddableInput ); await embeddable.initializeSavedVis({ id: '123' } as LensEmbeddableInput); diff --git a/x-pack/plugins/lens/public/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/embeddable/embeddable.tsx index d10423c76686c..263198871f07a 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable.tsx +++ b/x-pack/plugins/lens/public/embeddable/embeddable.tsx @@ -112,7 +112,8 @@ export interface LensEmbeddableDeps { export class Embeddable extends AbstractEmbeddable - implements ReferenceOrValueEmbeddable { + implements ReferenceOrValueEmbeddable +{ type = DOC_TYPE; deferEmbeddableLoad = true; @@ -261,12 +262,12 @@ export class Embeddable } async initializeSavedVis(input: LensEmbeddableInput) { - const attrs: - | ResolvedLensSavedObjectAttributes - | false = await this.deps.attributeService.unwrapAttributes(input).catch((e: Error) => { - this.onFatalError(e); - return false; - }); + const attrs: ResolvedLensSavedObjectAttributes | false = await this.deps.attributeService + .unwrapAttributes(input) + .catch((e: Error) => { + this.onFatalError(e); + return false; + }); if (!attrs || this.isDestroyed) { return; } @@ -455,7 +456,7 @@ export class Embeddable true ); if (this.input.onTableRowClick) { - this.input.onTableRowClick((event.data as unknown) as LensTableRowContextMenuEvent['data']); + this.input.onTableRowClick(event.data as unknown as LensTableRowContextMenuEvent['data']); } } }; diff --git a/x-pack/plugins/lens/public/heatmap_visualization/chart_component.tsx b/x-pack/plugins/lens/public/heatmap_visualization/chart_component.tsx index c666d27e780b5..20dcb8971ac54 100644 --- a/x-pack/plugins/lens/public/heatmap_visualization/chart_component.tsx +++ b/x-pack/plugins/lens/public/heatmap_visualization/chart_component.tsx @@ -125,7 +125,7 @@ export const HeatmapComponent: FC = ({ const tableId = Object.keys(data.tables)[0]; const table = data.tables[tableId]; - const paletteParams = args.palette?.params as CustomPaletteState; + const paletteParams = args.palette?.params; const xAxisColumnIndex = table.columns.findIndex((v) => v.id === args.xAccessor); const yAxisColumnIndex = table.columns.findIndex((v) => v.id === args.yAccessor); @@ -134,10 +134,10 @@ export const HeatmapComponent: FC = ({ const yAxisColumn = table.columns[yAxisColumnIndex]; const valueColumn = table.columns.find((v) => v.id === args.valueAccessor); - const minMaxByColumnId = useMemo(() => findMinMaxByColumnId([args.valueAccessor!], table), [ - args.valueAccessor, - table, - ]); + const minMaxByColumnId = useMemo( + () => findMinMaxByColumnId([args.valueAccessor!], table), + [args.valueAccessor, table] + ); if (!xAxisColumn || !valueColumn) { // Chart is not ready diff --git a/x-pack/plugins/lens/public/heatmap_visualization/types.ts b/x-pack/plugins/lens/public/heatmap_visualization/types.ts index 5515d77d1a8ab..d6b0e7ddc1d74 100644 --- a/x-pack/plugins/lens/public/heatmap_visualization/types.ts +++ b/x-pack/plugins/lens/public/heatmap_visualization/types.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { PaletteOutput } from '../../../../../src/plugins/charts/common'; +import type { CustomPaletteState, PaletteOutput } from '../../../../../src/plugins/charts/common'; import type { LensBrushEvent, LensFilterEvent } from '../types'; import type { LensMultiTable, FormatFactory, CustomPaletteParams, LayerType } from '../../common'; import type { HeatmapGridConfigResult, HeatmapLegendConfigResult } from '../../common/expressions'; @@ -36,7 +36,7 @@ export type HeatmapVisualizationState = HeatmapLayerState & { export type HeatmapExpressionArgs = SharedHeatmapLayerState & { title?: string; description?: string; - palette: PaletteOutput; + palette: PaletteOutput; }; export interface HeatmapRender { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/change_indexpattern.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/change_indexpattern.tsx index e643ea12528ee..64d7f5efc9c4d 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/change_indexpattern.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/change_indexpattern.tsx @@ -89,7 +89,7 @@ export function ChangeIndexPattern({ checked: id === indexPatternId ? 'on' : undefined, }))} onChange={(choices) => { - const choice = (choices.find(({ checked }) => checked) as unknown) as { + const choice = choices.find(({ checked }) => checked) as unknown as { value: string; }; trackUiEvent('indexpattern_changed'); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx index a6828bf9d5873..09617804e3bac 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx @@ -331,7 +331,7 @@ describe('IndexPattern Data Panel', () => { function testProps() { const setState = jest.fn(); core.http.post.mockImplementation(async (path) => { - const parts = ((path as unknown) as string).split('/'); + const parts = (path as unknown as string).split('/'); const indexPatternTitle = parts[parts.length - 1]; return { indexPatternTitle: `${indexPatternTitle}_testtitle`, @@ -396,7 +396,7 @@ describe('IndexPattern Data Panel', () => { if (stateChanges || propChanges) { await act(async () => { - ((inst.setProps as unknown) as (props: unknown) => {})({ + (inst.setProps as unknown as (props: unknown) => {})({ ...props, ...((propChanges as object) || {}), state: { @@ -561,7 +561,7 @@ describe('IndexPattern Data Panel', () => { } ++queryCount; - const parts = ((path as unknown) as string).split('/'); + const parts = (path as unknown as string).split('/'); const indexPatternTitle = parts[parts.length - 1]; const result = Promise.resolve({ indexPatternTitle, @@ -580,7 +580,7 @@ describe('IndexPattern Data Panel', () => { inst.update(); act(() => { - ((inst.setProps as unknown) as (props: unknown) => {})({ + (inst.setProps as unknown as (props: unknown) => {})({ ...props, dateRange: { fromDate: '2019-01-01', toDate: '2020-01-02' }, }); @@ -588,7 +588,7 @@ describe('IndexPattern Data Panel', () => { }); await act(async () => { - ((inst.setProps as unknown) as (props: unknown) => {})({ + (inst.setProps as unknown as (props: unknown) => {})({ ...props, dateRange: { fromDate: '2019-01-01', toDate: '2020-01-03' }, }); @@ -856,10 +856,12 @@ describe('IndexPattern Data Panel', () => { ); const wrapper = mountWithIntl(); act(() => { - (wrapper - .find('[data-test-subj="lnsIndexPatternActions-popover"]') - .first() - .prop('children') as ReactElement).props.items[0].props.onClick(); + ( + wrapper + .find('[data-test-subj="lnsIndexPatternActions-popover"]') + .first() + .prop('children') as ReactElement + ).props.items[0].props.onClick(); }); // wait for indx pattern to be loaded @@ -890,10 +892,12 @@ describe('IndexPattern Data Panel', () => { ); const wrapper = mountWithIntl(); act(() => { - (wrapper - .find('[data-test-subj="lnsIndexPatternActions-popover"]') - .first() - .prop('children') as ReactElement).props.items[0].props.onClick(); + ( + wrapper + .find('[data-test-subj="lnsIndexPatternActions-popover"]') + .first() + .prop('children') as ReactElement + ).props.items[0].props.onClick(); }); // wait for indx pattern to be loaded await act(async () => await new Promise((r) => setTimeout(r, 0))); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/bucket_nesting_editor.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/bucket_nesting_editor.test.tsx index c6ecdd73cb6ab..4eac0d372d462 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/bucket_nesting_editor.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/bucket_nesting_editor.test.tsx @@ -117,10 +117,12 @@ describe('BucketNestingEditor', () => { }, }); - (component - .find('[data-test-subj="indexPattern-nesting-switch"]') - .first() - .prop('onChange') as () => {})(); + ( + component + .find('[data-test-subj="indexPattern-nesting-switch"]') + .first() + .prop('onChange') as () => {} + )(); expect(setColumns).toHaveBeenCalledTimes(2); expect(setColumns).toHaveBeenLastCalledWith(['b', 'a', 'c']); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx index 0faaa1f342eeb..d25e6754fe03f 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx @@ -11,15 +11,11 @@ import { i18n } from '@kbn/i18n'; import { EuiListGroup, EuiFormRow, - EuiFieldText, EuiSpacer, EuiListGroupItemProps, EuiFormLabel, EuiToolTip, EuiText, - EuiTabs, - EuiTab, - EuiCallOut, } from '@elastic/eui'; import { IndexPatternDimensionEditorProps } from './dimension_panel'; import { OperationSupportMatrix } from './operation_support'; @@ -47,41 +43,29 @@ import { setTimeScaling, TimeScaling } from './time_scaling'; import { defaultFilter, Filtering, setFilter } from './filtering'; import { AdvancedOptions } from './advanced_options'; import { setTimeShift, TimeShift } from './time_shift'; -import { useDebouncedValue } from '../../shared_components'; +import { LayerType } from '../../../common'; +import { + quickFunctionsName, + staticValueOperationName, + isQuickFunction, + getParamEditor, + formulaOperationName, + DimensionEditorTabs, + CalloutWarning, + LabelInput, + getErrorMessage, +} from './dimensions_editor_helpers'; +import type { TemporaryState } from './dimensions_editor_helpers'; const operationPanels = getOperationDisplay(); export interface DimensionEditorProps extends IndexPatternDimensionEditorProps { selectedColumn?: IndexPatternColumn; + layerType: LayerType; operationSupportMatrix: OperationSupportMatrix; currentIndexPattern: IndexPattern; } -const LabelInput = ({ value, onChange }: { value: string; onChange: (value: string) => void }) => { - const { inputValue, handleInputChange, initialValue } = useDebouncedValue({ onChange, value }); - - return ( - - { - handleInputChange(e.target.value); - }} - placeholder={initialValue} - /> - - ); -}; - export function DimensionEditor(props: DimensionEditorProps) { const { selectedColumn, @@ -96,6 +80,8 @@ export function DimensionEditor(props: DimensionEditorProps) { dimensionGroups, toggleFullscreen, isFullscreen, + supportStaticValue, + supportFieldFormat = true, layerType, } = props; const services = { @@ -110,6 +96,11 @@ export function DimensionEditor(props: DimensionEditorProps) { const selectedOperationDefinition = selectedColumn && operationDefinitionMap[selectedColumn.operationType]; + const [temporaryState, setTemporaryState] = useState('none'); + + const temporaryQuickFunction = Boolean(temporaryState === quickFunctionsName); + const temporaryStaticValue = Boolean(temporaryState === staticValueOperationName); + const updateLayer = useCallback( (newLayer) => setState((prevState) => mergeLayer({ state: prevState, layerId, newLayer })), [layerId, setState] @@ -141,9 +132,64 @@ export function DimensionEditor(props: DimensionEditorProps) { ...incompleteParams } = incompleteInfo || {}; - const ParamEditor = selectedOperationDefinition?.paramEditor; + const isQuickFunctionSelected = Boolean( + supportStaticValue + ? selectedOperationDefinition && isQuickFunction(selectedOperationDefinition.type) + : !selectedOperationDefinition || isQuickFunction(selectedOperationDefinition.type) + ); + const showQuickFunctions = temporaryQuickFunction || isQuickFunctionSelected; + + const showStaticValueFunction = + temporaryStaticValue || + (temporaryState === 'none' && + supportStaticValue && + (!selectedColumn || selectedColumn?.operationType === staticValueOperationName)); + + const addStaticValueColumn = (prevLayer = props.state.layers[props.layerId]) => { + if (selectedColumn?.operationType !== staticValueOperationName) { + trackUiEvent(`indexpattern_dimension_operation_static_value`); + return insertOrReplaceColumn({ + layer: prevLayer, + indexPattern: currentIndexPattern, + columnId, + op: staticValueOperationName, + visualizationGroups: dimensionGroups, + }); + } + return prevLayer; + }; + + // this function intercepts the state update for static value function + // and. if in temporary state, it merges the "add new static value column" state with the incoming + // changes from the static value operation (which has to be a function) + // Note: it forced a rerender at this point to avoid UI glitches in async updates (another hack upstream) + // TODO: revisit this once we get rid of updateDatasourceAsync upstream + const moveDefinetelyToStaticValueAndUpdate = ( + setter: IndexPatternLayer | ((prevLayer: IndexPatternLayer) => IndexPatternLayer) + ) => { + if (temporaryStaticValue) { + setTemporaryState('none'); + if (typeof setter === 'function') { + return setState( + (prevState) => { + const layer = setter(addStaticValueColumn(prevState.layers[layerId])); + return mergeLayer({ state: prevState, layerId, newLayer: layer }); + }, + { + isDimensionComplete: true, + forceRender: true, + } + ); + } + } + return setStateWrapper(setter); + }; - const [temporaryQuickFunction, setQuickFunction] = useState(false); + const ParamEditor = getParamEditor( + temporaryStaticValue, + selectedOperationDefinition, + supportStaticValue && !showQuickFunctions + ); const possibleOperations = useMemo(() => { return Object.values(operationDefinitionMap) @@ -193,10 +239,10 @@ export function DimensionEditor(props: DimensionEditorProps) { }; }); - const currentFieldIsInvalid = useMemo(() => fieldIsInvalid(selectedColumn, currentIndexPattern), [ - selectedColumn, - currentIndexPattern, - ]); + const currentFieldIsInvalid = useMemo( + () => fieldIsInvalid(selectedColumn, currentIndexPattern), + [selectedColumn, currentIndexPattern] + ); const sideNavItems: EuiListGroupItemProps[] = operationsWithCompatibility.map( ({ operationType, compatibleWithCurrentField, disabledStatus }) => { @@ -245,9 +291,9 @@ export function DimensionEditor(props: DimensionEditorProps) { [`aria-pressed`]: isActive, onClick() { if ( - operationDefinitionMap[operationType].input === 'none' || - operationDefinitionMap[operationType].input === 'managedReference' || - operationDefinitionMap[operationType].input === 'fullReference' + ['none', 'fullReference', 'managedReference'].includes( + operationDefinitionMap[operationType].input + ) ) { // Clear invalid state because we are reseting to a valid column if (selectedColumn?.operationType === operationType) { @@ -264,9 +310,12 @@ export function DimensionEditor(props: DimensionEditorProps) { visualizationGroups: dimensionGroups, targetGroup: props.groupId, }); - if (temporaryQuickFunction && newLayer.columns[columnId].operationType !== 'formula') { + if ( + temporaryQuickFunction && + isQuickFunction(newLayer.columns[columnId].operationType) + ) { // Only switch the tab once the formula is fully removed - setQuickFunction(false); + setTemporaryState('none'); } setStateWrapper(newLayer); trackUiEvent(`indexpattern_dimension_operation_${operationType}`); @@ -297,9 +346,12 @@ export function DimensionEditor(props: DimensionEditorProps) { }); // ); } - if (temporaryQuickFunction && newLayer.columns[columnId].operationType !== 'formula') { + if ( + temporaryQuickFunction && + isQuickFunction(newLayer.columns[columnId].operationType) + ) { // Only switch the tab once the formula is fully removed - setQuickFunction(false); + setTemporaryState('none'); } setStateWrapper(newLayer); trackUiEvent(`indexpattern_dimension_operation_${operationType}`); @@ -314,7 +366,7 @@ export function DimensionEditor(props: DimensionEditorProps) { } if (temporaryQuickFunction) { - setQuickFunction(false); + setTemporaryState('none'); } const newLayer = replaceColumn({ layer: props.state.layers[props.layerId], @@ -348,29 +400,10 @@ export function DimensionEditor(props: DimensionEditorProps) { !currentFieldIsInvalid && !incompleteInfo && selectedColumn && - selectedColumn.operationType !== 'formula'; + isQuickFunction(selectedColumn.operationType); const quickFunctions = ( <> - {temporaryQuickFunction && selectedColumn?.operationType === 'formula' && ( - <> - -

- {i18n.translate('xpack.lens.indexPattern.formulaWarningText', { - defaultMessage: 'To overwrite your formula, select a quick function', - })} -

-
- - )}
{i18n.translate('xpack.lens.indexPattern.functionsLabel', { @@ -608,24 +641,28 @@ export function DimensionEditor(props: DimensionEditorProps) { ); - const formulaTab = ParamEditor ? ( - + const customParamEditor = ParamEditor ? ( + <> + + ) : null; + const TabContent = showQuickFunctions ? quickFunctions : customParamEditor; + const onFormatChange = useCallback( (newFormat) => { updateLayer( @@ -640,58 +677,69 @@ export function DimensionEditor(props: DimensionEditorProps) { [columnId, layerId, state.layers, updateLayer] ); + const hasFormula = + !isFullscreen && operationSupportMatrix.operationWithoutField.has(formulaOperationName); + + const hasTabs = hasFormula || supportStaticValue; + return (
- {!isFullscreen && operationSupportMatrix.operationWithoutField.has('formula') ? ( - - { - if (selectedColumn?.operationType === 'formula') { - setQuickFunction(true); + {hasTabs ? ( + { + if (tabClicked === 'quickFunctions') { + if (selectedColumn && !isQuickFunction(selectedColumn.operationType)) { + setTemporaryState(quickFunctionsName); + return; } - }} - > - {i18n.translate('xpack.lens.indexPattern.quickFunctionsLabel', { - defaultMessage: 'Quick functions', - })} - - { - if (selectedColumn?.operationType !== 'formula') { - setQuickFunction(false); + } + + if (tabClicked === 'static_value') { + // when coming from a formula, set a temporary state + if (selectedColumn?.operationType === formulaOperationName) { + return setTemporaryState(staticValueOperationName); + } + setTemporaryState('none'); + setStateWrapper(addStaticValueColumn()); + return; + } + + if (tabClicked === 'formula') { + setTemporaryState('none'); + if (selectedColumn?.operationType !== formulaOperationName) { const newLayer = insertOrReplaceColumn({ layer: props.state.layers[props.layerId], indexPattern: currentIndexPattern, columnId, - op: 'formula', + op: formulaOperationName, visualizationGroups: dimensionGroups, }); setStateWrapper(newLayer); trackUiEvent(`indexpattern_dimension_operation_formula`); - return; - } else { - setQuickFunction(false); } - }} - > - {i18n.translate('xpack.lens.indexPattern.formulaLabel', { - defaultMessage: 'Formula', - })} - - + } + }} + /> ) : null} - {isFullscreen - ? formulaTab - : selectedOperationDefinition?.type === 'formula' && !temporaryQuickFunction - ? formulaTab - : quickFunctions} + + {TabContent} - {!isFullscreen && !currentFieldIsInvalid && !temporaryQuickFunction && ( + {!isFullscreen && !currentFieldIsInvalid && temporaryState === 'none' && (
{!incompleteInfo && selectedColumn && ( )} - {!isFullscreen && + {supportFieldFormat && + !isFullscreen && selectedColumn && (selectedColumn.dataType === 'number' || selectedColumn.operationType === 'range') ? ( @@ -735,26 +784,3 @@ export function DimensionEditor(props: DimensionEditorProps) {
); } - -function getErrorMessage( - selectedColumn: IndexPatternColumn | undefined, - incompleteOperation: boolean, - input: 'none' | 'field' | 'fullReference' | 'managedReference' | undefined, - fieldInvalid: boolean -) { - if (selectedColumn && incompleteOperation) { - if (input === 'field') { - return i18n.translate('xpack.lens.indexPattern.invalidOperationLabel', { - defaultMessage: 'This field does not work with the selected function.', - }); - } - return i18n.translate('xpack.lens.indexPattern.chooseFieldLabel', { - defaultMessage: 'To use this function, select a field.', - }); - } - if (fieldInvalid) { - return i18n.translate('xpack.lens.indexPattern.invalidFieldLabel', { - defaultMessage: 'Invalid field. Check your index pattern or pick another field.', - }); - } -} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx index 6d96b853ab239..d823def1da114 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx @@ -52,6 +52,13 @@ jest.mock('lodash', () => { }; }); jest.mock('../../id_generator'); +// Mock the Monaco Editor component +jest.mock('../operations/definitions/formula/editor/formula_editor', () => { + return { + WrappedFormulaEditor: () =>
, + FormulaEditor: () =>
, + }; +}); const fields = [ { @@ -192,8 +199,8 @@ describe('IndexPatternDimensionEditorPanel', () => { uiSettings: {} as IUiSettingsClient, savedObjectsClient: {} as SavedObjectsClientContract, http: {} as HttpSetup, - data: ({ - fieldFormats: ({ + data: { + fieldFormats: { getType: jest.fn().mockReturnValue({ id: 'number', title: 'Number', @@ -205,12 +212,13 @@ describe('IndexPatternDimensionEditorPanel', () => { deserialize: jest.fn().mockReturnValue({ convert: () => 'formatted', }), - } as unknown) as DataPublicPluginStart['fieldFormats'], - } as unknown) as DataPublicPluginStart, + } as unknown as DataPublicPluginStart['fieldFormats'], + } as unknown as DataPublicPluginStart, core: {} as CoreSetup, dimensionGroups: [], groupId: 'a', isFullscreen: false, + supportStaticValue: false, toggleFullscreen: jest.fn(), }; @@ -402,8 +410,9 @@ describe('IndexPatternDimensionEditorPanel', () => { const items: EuiListGroupItemProps[] = wrapper.find(EuiListGroup).prop('listItems') || []; - expect(items.find(({ id }) => id === 'math')).toBeUndefined(); - expect(items.find(({ id }) => id === 'formula')).toBeUndefined(); + ['math', 'formula', 'static_value'].forEach((hiddenOp) => { + expect(items.some(({ id }) => id === hiddenOp)).toBe(false); + }); }); it('should indicate that reference-based operations are not compatible when they are incomplete', () => { @@ -1210,9 +1219,9 @@ describe('IndexPatternDimensionEditorPanel', () => { wrapper .find('[data-test-subj="indexPattern-time-scaling-unit"]') .find(EuiSelect) - .prop('onChange')!(({ + .prop('onChange')!({ target: { value: 'h' }, - } as unknown) as ChangeEvent); + } as unknown as ChangeEvent); expect(setState.mock.calls[0]).toEqual([expect.any(Function), { isDimensionComplete: true }]); expect(setState.mock.calls[0][0](props.state)).toEqual({ ...props.state, @@ -1237,9 +1246,9 @@ describe('IndexPatternDimensionEditorPanel', () => { wrapper .find('[data-test-subj="indexPattern-time-scaling-unit"]') .find(EuiSelect) - .prop('onChange')!(({ + .prop('onChange')!({ target: { value: 'h' }, - } as unknown) as ChangeEvent); + } as unknown as ChangeEvent); expect(setState.mock.calls[0]).toEqual([expect.any(Function), { isDimensionComplete: true }]); expect(setState.mock.calls[0][0](props.state)).toEqual({ ...props.state, @@ -2217,4 +2226,130 @@ describe('IndexPatternDimensionEditorPanel', () => { 0 ); }); + + it('should not show tabs when formula and static_value operations are not available', () => { + const stateWithInvalidCol: IndexPatternPrivateState = getStateWithColumns({ + col1: { + label: 'Average of memory', + dataType: 'number', + isBucketed: false, + // Private + operationType: 'average', + sourceField: 'memory', + params: { + format: { id: 'bytes', params: { decimals: 2 } }, + }, + }, + }); + + const props = { + ...defaultProps, + filterOperations: jest.fn((op) => { + // the formula operation will fall into this metadata category + return !(op.dataType === 'number' && op.scale === 'ratio'); + }), + }; + + wrapper = mount( + + ); + + expect(wrapper.find('[data-test-subj="lens-dimensionTabs"]').exists()).toBeFalsy(); + }); + + it('should show the formula tab when supported', () => { + const stateWithFormulaColumn: IndexPatternPrivateState = getStateWithColumns({ + col1: { + label: 'Formula', + dataType: 'number', + isBucketed: false, + operationType: 'formula', + references: ['ref1'], + params: {}, + }, + }); + + wrapper = mount( + + ); + + expect( + wrapper.find('[data-test-subj="lens-dimensionTabs-formula"]').first().prop('isSelected') + ).toBeTruthy(); + }); + + it('should now show the static_value tab when not supported', () => { + const stateWithFormulaColumn: IndexPatternPrivateState = getStateWithColumns({ + col1: { + label: 'Formula', + dataType: 'number', + isBucketed: false, + operationType: 'formula', + references: ['ref1'], + params: {}, + }, + }); + + wrapper = mount( + + ); + + expect(wrapper.find('[data-test-subj="lens-dimensionTabs-static_value"]').exists()).toBeFalsy(); + }); + + it('should show the static value tab when supported', () => { + const staticWithFormulaColumn: IndexPatternPrivateState = getStateWithColumns({ + col1: { + label: 'Formula', + dataType: 'number', + isBucketed: false, + operationType: 'formula', + references: ['ref1'], + params: {}, + }, + }); + + wrapper = mount( + + ); + + expect( + wrapper.find('[data-test-subj="lens-dimensionTabs-static_value"]').exists() + ).toBeTruthy(); + }); + + it('should select the quick function tab by default', () => { + const stateWithNoColumn: IndexPatternPrivateState = getStateWithColumns({}); + + wrapper = mount( + + ); + + expect( + wrapper + .find('[data-test-subj="lens-dimensionTabs-quickFunctions"]') + .first() + .prop('isSelected') + ).toBeTruthy(); + }); + + it('should select the static value tab when supported by default', () => { + const stateWithNoColumn: IndexPatternPrivateState = getStateWithColumns({}); + + wrapper = mount( + + ); + + expect( + wrapper.find('[data-test-subj="lens-dimensionTabs-static_value"]').first().prop('isSelected') + ).toBeTruthy(); + }); }); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.tsx index d6091557ce235..ac8296cca968e 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.tsx @@ -16,23 +16,25 @@ import { IndexPatternColumn } from '../indexpattern'; import { isColumnInvalid } from '../utils'; import { IndexPatternPrivateState } from '../types'; import { DimensionEditor } from './dimension_editor'; -import type { DateRange } from '../../../common'; +import { DateRange, layerTypes } from '../../../common'; import { getOperationSupportMatrix } from './operation_support'; -export type IndexPatternDimensionTriggerProps = DatasourceDimensionTriggerProps & { - uniqueLabel: string; -}; +export type IndexPatternDimensionTriggerProps = + DatasourceDimensionTriggerProps & { + uniqueLabel: string; + }; -export type IndexPatternDimensionEditorProps = DatasourceDimensionEditorProps & { - uiSettings: IUiSettingsClient; - storage: IStorageWrapper; - savedObjectsClient: SavedObjectsClientContract; - layerId: string; - http: HttpSetup; - data: DataPublicPluginStart; - uniqueLabel: string; - dateRange: DateRange; -}; +export type IndexPatternDimensionEditorProps = + DatasourceDimensionEditorProps & { + uiSettings: IUiSettingsClient; + storage: IStorageWrapper; + savedObjectsClient: SavedObjectsClientContract; + layerId: string; + http: HttpSetup; + data: DataPublicPluginStart; + uniqueLabel: string; + dateRange: DateRange; + }; function wrapOnDot(str?: string) { // u200B is a non-width white-space character, which allows @@ -47,11 +49,11 @@ export const IndexPatternDimensionTriggerComponent = function IndexPatternDimens const layerId = props.layerId; const layer = props.state.layers[layerId]; const currentIndexPattern = props.state.indexPatterns[layer.indexPatternId]; - const { columnId, uniqueLabel } = props; + const { columnId, uniqueLabel, invalid, invalidMessage } = props; const currentColumnHasErrors = useMemo( - () => isColumnInvalid(layer, columnId, currentIndexPattern), - [layer, columnId, currentIndexPattern] + () => invalid || isColumnInvalid(layer, columnId, currentIndexPattern), + [layer, columnId, currentIndexPattern, invalid] ); const selectedColumn: IndexPatternColumn | null = layer.columns[props.columnId] ?? null; @@ -65,15 +67,17 @@ export const IndexPatternDimensionTriggerComponent = function IndexPatternDimens return ( - {i18n.translate('xpack.lens.configure.invalidConfigTooltip', { - defaultMessage: 'Invalid configuration.', - })} -
- {i18n.translate('xpack.lens.configure.invalidConfigTooltipClick', { - defaultMessage: 'Click for more details.', - })} -

+ invalidMessage ?? ( +

+ {i18n.translate('xpack.lens.configure.invalidConfigTooltip', { + defaultMessage: 'Invalid configuration.', + })} +
+ {i18n.translate('xpack.lens.configure.invalidConfigTooltipClick', { + defaultMessage: 'Click for more details.', + })} +

+ ) } anchorClassName="eui-displayBlock" > @@ -125,6 +129,7 @@ export const IndexPatternDimensionEditorComponent = function IndexPatternDimensi return ( void; +}) => { + const { inputValue, handleInputChange, initialValue } = useDebouncedValue({ onChange, value }); + + return ( + + { + handleInputChange(e.target.value); + }} + placeholder={initialValue} + /> + + ); +}; + +export function getParamEditor( + temporaryStaticValue: boolean, + selectedOperationDefinition: typeof operationDefinitionMap[string] | undefined, + showDefaultStaticValue: boolean +) { + if (temporaryStaticValue) { + return operationDefinitionMap[staticValueOperationName].paramEditor; + } + if (selectedOperationDefinition?.paramEditor) { + return selectedOperationDefinition.paramEditor; + } + if (showDefaultStaticValue) { + return operationDefinitionMap[staticValueOperationName].paramEditor; + } + return null; +} + +export const CalloutWarning = ({ + currentOperationType, + temporaryStateType, +}: { + currentOperationType: keyof typeof operationDefinitionMap | undefined; + temporaryStateType: TemporaryState; +}) => { + if ( + temporaryStateType === 'none' || + (currentOperationType != null && isQuickFunction(currentOperationType)) + ) { + return null; + } + if ( + currentOperationType === staticValueOperationName && + temporaryStateType === 'quickFunctions' + ) { + return ( + <> + +

+ {i18n.translate('xpack.lens.indexPattern.staticValueWarningText', { + defaultMessage: 'To overwrite your static value, select a quick function', + })} +

+
+ + ); + } + return ( + <> + + {temporaryStateType !== 'quickFunctions' ? ( +

+ {i18n.translate('xpack.lens.indexPattern.formulaWarningStaticValueText', { + defaultMessage: 'To overwrite your formula, change the value in the input field', + })} +

+ ) : ( +

+ {i18n.translate('xpack.lens.indexPattern.formulaWarningText', { + defaultMessage: 'To overwrite your formula, select a quick function', + })} +

+ )} +
+ + ); +}; + +type DimensionEditorTabsType = + | typeof quickFunctionsName + | typeof staticValueOperationName + | typeof formulaOperationName; + +export const DimensionEditorTabs = ({ + tabsEnabled, + tabsState, + onClick, +}: { + tabsEnabled: Record; + tabsState: Record; + onClick: (tabClicked: DimensionEditorTabsType) => void; +}) => { + return ( + + {tabsEnabled.static_value ? ( + onClick(staticValueOperationName)} + > + {i18n.translate('xpack.lens.indexPattern.staticValueLabel', { + defaultMessage: 'Static value', + })} + + ) : null} + onClick(quickFunctionsName)} + > + {i18n.translate('xpack.lens.indexPattern.quickFunctionsLabel', { + defaultMessage: 'Quick functions', + })} + + {tabsEnabled.formula ? ( + onClick(formulaOperationName)} + > + {i18n.translate('xpack.lens.indexPattern.formulaLabel', { + defaultMessage: 'Formula', + })} + + ) : null} + + ); +}; + +export function getErrorMessage( + selectedColumn: IndexPatternColumn | undefined, + incompleteOperation: boolean, + input: 'none' | 'field' | 'fullReference' | 'managedReference' | undefined, + fieldInvalid: boolean +) { + if (selectedColumn && incompleteOperation) { + if (input === 'field') { + return i18n.translate('xpack.lens.indexPattern.invalidOperationLabel', { + defaultMessage: 'This field does not work with the selected function.', + }); + } + return i18n.translate('xpack.lens.indexPattern.chooseFieldLabel', { + defaultMessage: 'To use this function, select a field.', + }); + } + if (fieldInvalid) { + return i18n.translate('xpack.lens.indexPattern.invalidFieldLabel', { + defaultMessage: 'Invalid field. Check your index pattern or pick another field.', + }); + } +} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable/droppable.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable/droppable.test.ts index d1082da2beb20..85807721f80f6 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable/droppable.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable/droppable.test.ts @@ -17,6 +17,7 @@ import { OperationMetadata, DropType } from '../../../types'; import { IndexPatternColumn, MedianIndexPatternColumn } from '../../operations'; import { getFieldByNameFactory } from '../../pure_helpers'; import { generateId } from '../../../id_generator'; +import { layerTypes } from '../../../../common'; jest.mock('../../../id_generator'); @@ -263,7 +264,6 @@ describe('IndexPatternDimensionEditorPanel', () => { dateRange: { fromDate: 'now-1d', toDate: 'now' }, columnId: 'col1', layerId: 'first', - layerType: 'data', uniqueLabel: 'stuff', groupId: 'group1', filterOperations: () => true, @@ -271,8 +271,8 @@ describe('IndexPatternDimensionEditorPanel', () => { uiSettings: {} as IUiSettingsClient, savedObjectsClient: {} as SavedObjectsClientContract, http: {} as HttpSetup, - data: ({ - fieldFormats: ({ + data: { + fieldFormats: { getType: jest.fn().mockReturnValue({ id: 'number', title: 'Number', @@ -281,12 +281,14 @@ describe('IndexPatternDimensionEditorPanel', () => { id: 'bytes', title: 'Bytes', }), - } as unknown) as DataPublicPluginStart['fieldFormats'], - } as unknown) as DataPublicPluginStart, + } as unknown as DataPublicPluginStart['fieldFormats'], + } as unknown as DataPublicPluginStart, core: {} as CoreSetup, dimensionGroups: [], isFullscreen: false, toggleFullscreen: () => {}, + supportStaticValue: false, + layerType: layerTypes.DATA, }; jest.clearAllMocks(); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable/on_drop_handler.ts b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable/on_drop_handler.ts index e09c3e904f535..b518f667a0bfb 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable/on_drop_handler.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable/on_drop_handler.ts @@ -121,8 +121,12 @@ function onMoveCompatible( indexPattern, }); - let updatedColumnOrder = getColumnOrder(modifiedLayer); - updatedColumnOrder = reorderByGroups(dimensionGroups, groupId, updatedColumnOrder, columnId); + const updatedColumnOrder = reorderByGroups( + dimensionGroups, + groupId, + getColumnOrder(modifiedLayer), + columnId + ); // Time to replace setState( diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/field_select.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/field_select.tsx index 54eb3d48efccf..003af1f3ed4a7 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/field_select.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/field_select.tsx @@ -211,10 +211,10 @@ export function FieldSelect({ placeholder={i18n.translate('xpack.lens.indexPattern.fieldPlaceholder', { defaultMessage: 'Field', })} - options={(memoizedFieldOptions as unknown) as EuiComboBoxOptionOption[]} + options={memoizedFieldOptions as unknown as EuiComboBoxOptionOption[]} isInvalid={Boolean(incompleteOperation || fieldIsInvalid)} selectedOptions={ - ((selectedOperationType && selectedField + (selectedOperationType && selectedField ? [ { label: fieldIsInvalid @@ -223,7 +223,7 @@ export function FieldSelect({ value: { type: 'field', field: selectedField }, }, ] - : []) as unknown) as EuiComboBoxOptionOption[] + : []) as unknown as EuiComboBoxOptionOption[] } singleSelection={{ asPlainText: true }} onChange={(choices) => { @@ -232,7 +232,7 @@ export function FieldSelect({ return; } - const choice = (choices[0].value as unknown) as FieldChoice; + const choice = choices[0].value as unknown as FieldChoice; if (choice.field !== selectedField) { trackUiEvent('indexpattern_dimension_field_changed'); @@ -244,7 +244,7 @@ export function FieldSelect({ diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.test.tsx index 7cfa957f8a855..c84989e560871 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.test.tsx @@ -86,12 +86,12 @@ describe('IndexPattern Field Item', () => { core.http.post.mockClear(); defaultProps = { indexPattern, - fieldFormats: ({ + fieldFormats: { ...fieldFormatsServiceMock.createStartContract(), getDefaultInstance: jest.fn(() => ({ convert: jest.fn((s: unknown) => JSON.stringify(s)), })), - } as unknown) as FieldFormatsStart, + } as unknown as FieldFormatsStart, core, highlight: '', dateRange: { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/index.ts index 5f4afc9df6179..5ec90d84f6c4d 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/index.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/index.ts @@ -60,10 +60,8 @@ export class IndexPatternDatasource { fieldFormatsSetup.register([suffixFormatter]); } - const [ - coreStart, - { indexPatternFieldEditor, uiActions, data, fieldFormats }, - ] = await core.getStartServices(); + const [coreStart, { indexPatternFieldEditor, uiActions, data, fieldFormats }] = + await core.getStartServices(); return getIndexPatternDatasource({ core: coreStart, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts index 678644101d5ce..1dfc7d40f6f3e 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts @@ -216,7 +216,7 @@ describe('IndexPattern Data Source', () => { operationType: 'count', sourceField: 'Records', }; - const map = indexPatternDatasource.uniqueLabels(({ + const map = indexPatternDatasource.uniqueLabels({ layers: { a: { columnOrder: ['a', 'b'], @@ -238,7 +238,7 @@ describe('IndexPattern Data Source', () => { indexPatternId: 'foo', }, }, - } as unknown) as IndexPatternPrivateState); + } as unknown as IndexPatternPrivateState); expect(map).toMatchInlineSnapshot(` Object { @@ -1410,7 +1410,7 @@ describe('IndexPattern Data Source', () => { }, currentIndexPatternId: '1', }; - const warnings = indexPatternDatasource.getWarningMessages!(state, ({ + const warnings = indexPatternDatasource.getWarningMessages!(state, { activeData: { first: { type: 'datatable', @@ -1433,7 +1433,7 @@ describe('IndexPattern Data Source', () => { ], }, }, - } as unknown) as FramePublicAPI); + } as unknown as FramePublicAPI); expect(warnings!.length).toBe(2); expect((warnings![0] as React.ReactElement).props.id).toEqual( 'xpack.lens.indexPattern.timeShiftSmallWarning' @@ -1623,4 +1623,87 @@ describe('IndexPattern Data Source', () => { expect(indexPatternDatasource.isTimeBased(state)).toEqual(false); }); }); + + describe('#initializeDimension', () => { + it('should return the same state if no static value is passed', () => { + const state = enrichBaseState({ + currentIndexPatternId: '1', + layers: { + first: { + indexPatternId: '1', + columnOrder: ['metric'], + columns: { + metric: { + label: 'Count of records', + dataType: 'number', + isBucketed: false, + sourceField: 'Records', + operationType: 'count', + }, + }, + }, + }, + }); + expect( + indexPatternDatasource.initializeDimension!(state, 'first', { + columnId: 'newStatic', + label: 'MyNewColumn', + groupId: 'a', + dataType: 'number', + }) + ).toBe(state); + }); + + it('should add a new static value column if a static value is passed', () => { + const state = enrichBaseState({ + currentIndexPatternId: '1', + layers: { + first: { + indexPatternId: '1', + columnOrder: ['metric'], + columns: { + metric: { + label: 'Count of records', + dataType: 'number', + isBucketed: false, + sourceField: 'Records', + operationType: 'count', + }, + }, + }, + }, + }); + expect( + indexPatternDatasource.initializeDimension!(state, 'first', { + columnId: 'newStatic', + label: 'MyNewColumn', + groupId: 'a', + dataType: 'number', + staticValue: 0, // use a falsy value to check also this corner case + }) + ).toEqual({ + ...state, + layers: { + ...state.layers, + first: { + ...state.layers.first, + incompleteColumns: {}, + columnOrder: ['metric', 'newStatic'], + columns: { + ...state.layers.first.columns, + newStatic: { + dataType: 'number', + isBucketed: false, + label: 'Static value: 0', + operationType: 'static_value', + params: { value: 0 }, + references: [], + scale: 'ratio', + }, + }, + }, + }, + }); + }); + }); }); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx index 6a45e3c987f3d..2138b06a4c344 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx @@ -44,7 +44,7 @@ import { import { isDraggedField, normalizeOperationDataType } from './utils'; import { LayerPanel } from './layerpanel'; -import { IndexPatternColumn, getErrorMessages } from './operations'; +import { IndexPatternColumn, getErrorMessages, insertNewColumn } from './operations'; import { IndexPatternField, IndexPatternPrivateState, IndexPatternPersistedState } from './types'; import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public'; import { DataPublicPluginStart } from '../../../../../src/plugins/data/public'; @@ -192,6 +192,27 @@ export function getIndexPatternDatasource({ }); }, + initializeDimension(state, layerId, { columnId, groupId, label, dataType, staticValue }) { + const indexPattern = state.indexPatterns[state.layers[layerId]?.indexPatternId]; + if (staticValue == null) { + return state; + } + return mergeLayer({ + state, + layerId, + newLayer: insertNewColumn({ + layer: state.layers[layerId], + op: 'static_value', + columnId, + field: undefined, + indexPattern, + visualizationGroups: [], + initialParams: { params: { value: staticValue } }, + targetGroup: groupId, + }), + }); + }, + toExpression: (state, layerId) => toExpression(state, layerId, uiSettings), renderDataPanel( @@ -404,9 +425,14 @@ export function getIndexPatternDatasource({ }, }; }, - getDatasourceSuggestionsForField(state, draggedField) { + getDatasourceSuggestionsForField(state, draggedField, filterLayers) { return isDraggedField(draggedField) - ? getDatasourceSuggestionsForField(state, draggedField.indexPatternId, draggedField.field) + ? getDatasourceSuggestionsForField( + state, + draggedField.indexPatternId, + draggedField.field, + filterLayers + ) : []; }, getDatasourceSuggestionsFromCurrentState, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx index 4b8bbc09c6799..a5d6db4be3319 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.test.tsx @@ -1198,6 +1198,91 @@ describe('IndexPattern Data Source suggestions', () => { }) ); }); + + it('should apply layers filter if passed and model the suggestion based on that', () => { + (generateId as jest.Mock).mockReturnValue('newid'); + const initialState = stateWithNonEmptyTables(); + + const modifiedState: IndexPatternPrivateState = { + ...initialState, + layers: { + thresholdLayer: { + indexPatternId: '1', + columnOrder: ['threshold'], + columns: { + threshold: { + dataType: 'number', + isBucketed: false, + label: 'Static Value: 0', + operationType: 'static_value', + params: { value: '0' }, + references: [], + scale: 'ratio', + }, + }, + }, + currentLayer: { + indexPatternId: '1', + columnOrder: ['metric', 'ref'], + columns: { + metric: { + label: '', + customLabel: true, + dataType: 'number', + isBucketed: false, + operationType: 'average', + sourceField: 'bytes', + }, + ref: { + label: '', + customLabel: true, + dataType: 'number', + isBucketed: false, + operationType: 'cumulative_sum', + references: ['metric'], + }, + }, + }, + }, + }; + + const suggestions = getSuggestionSubset( + getDatasourceSuggestionsForField( + modifiedState, + '1', + documentField, + (layerId) => layerId !== 'thresholdLayer' + ) + ); + // should ignore the threshold layer + expect(suggestions).toContainEqual( + expect.objectContaining({ + table: expect.objectContaining({ + changeType: 'extended', + columns: [ + { + columnId: 'ref', + operation: { + dataType: 'number', + isBucketed: false, + label: '', + scale: undefined, + }, + }, + { + columnId: 'newid', + operation: { + dataType: 'number', + isBucketed: false, + label: 'Count of records', + scale: 'ratio', + }, + }, + ], + }), + }) + ); + }); }); describe('finding the layer that is using the current index pattern', () => { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts index b0793bf912bb2..0fe0ef617dc27 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern_suggestions.ts @@ -95,10 +95,14 @@ function buildSuggestion({ export function getDatasourceSuggestionsForField( state: IndexPatternPrivateState, indexPatternId: string, - field: IndexPatternField + field: IndexPatternField, + filterLayers?: (layerId: string) => boolean ): IndexPatternSuggestion[] { const layers = Object.keys(state.layers); - const layerIds = layers.filter((id) => state.layers[id].indexPatternId === indexPatternId); + let layerIds = layers.filter((id) => state.layers[id].indexPatternId === indexPatternId); + if (filterLayers) { + layerIds = layerIds.filter(filterLayers); + } if (layerIds.length === 0) { // The field we're suggesting on does not match any existing layer. diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/layerpanel.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/layerpanel.test.tsx index a7c1074ed4eef..6161bcee4678f 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/layerpanel.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/layerpanel.test.tsx @@ -215,12 +215,11 @@ describe('Layer Data Panel', () => { } function selectIndexPatternPickerOption(instance: ShallowWrapper, selectedLabel: string) { - const options: IndexPatternPickerOption[] = getIndexPatternPickerOptions( - instance - ).map((option: IndexPatternPickerOption) => - option.label === selectedLabel - ? { ...option, checked: 'on' } - : { ...option, checked: undefined } + const options: IndexPatternPickerOption[] = getIndexPatternPickerOptions(instance).map( + (option: IndexPatternPickerOption) => + option.label === selectedLabel + ? { ...option, checked: 'on' } + : { ...option, checked: undefined } ); return getIndexPatternPickerList(instance).prop('onChange')!(options); } diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/loader.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/loader.test.ts index 5eb530e50f190..e9cad4fc3a37e 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/loader.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/loader.test.ts @@ -36,7 +36,7 @@ const createMockStorage = (lastData?: Record) => { }; }; -const indexPattern1 = ({ +const indexPattern1 = { id: '1', title: 'my-fake-index-pattern', timeFieldName: 'timestamp', @@ -105,14 +105,14 @@ const indexPattern1 = ({ }, documentField, ], -} as unknown) as IndexPattern; +} as unknown as IndexPattern; const sampleIndexPatternsFromService = { '1': createMockedIndexPattern(), '2': createMockedRestrictedIndexPattern(), }; -const indexPattern2 = ({ +const indexPattern2 = { id: '2', title: 'my-fake-restricted-pattern', timeFieldName: 'timestamp', @@ -177,7 +177,7 @@ const indexPattern2 = ({ }, documentField, ], -} as unknown) as IndexPattern; +} as unknown as IndexPattern; const sampleIndexPatterns = { '1': indexPattern1, @@ -185,7 +185,7 @@ const sampleIndexPatterns = { }; function mockIndexPatternsService() { - return ({ + return { get: jest.fn(async (id: '1' | '2') => { const result = { ...sampleIndexPatternsFromService[id], metaFields: [] }; if (!result.fields) { @@ -205,7 +205,7 @@ function mockIndexPatternsService() { }, ]; }), - } as unknown) as Pick; + } as unknown as Pick; } describe('loader', () => { @@ -214,12 +214,12 @@ describe('loader', () => { const cache = await loadIndexPatterns({ cache: sampleIndexPatterns, patterns: ['1', '2'], - indexPatternsService: ({ + indexPatternsService: { get: jest.fn(() => Promise.reject('mockIndexPatternService.get should not have been called') ), getIdsWithTitle: jest.fn(), - } as unknown) as Pick, + } as unknown as Pick, }); expect(cache).toEqual(sampleIndexPatterns); @@ -251,7 +251,7 @@ describe('loader', () => { const cache = await loadIndexPatterns({ cache: {}, patterns: ['foo'], - indexPatternsService: ({ + indexPatternsService: { get: jest.fn(async () => ({ id: 'foo', title: 'Foo index', @@ -292,7 +292,7 @@ describe('loader', () => { id: 'foo', title: 'Foo index', })), - } as unknown) as Pick, + } as unknown as Pick, }); expect(cache.foo.getFieldByName('bytes')!.aggregationRestrictions).toEqual({ @@ -307,7 +307,7 @@ describe('loader', () => { const cache = await loadIndexPatterns({ cache: {}, patterns: ['foo'], - indexPatternsService: ({ + indexPatternsService: { get: jest.fn(async () => ({ id: 'foo', title: 'Foo index', @@ -348,7 +348,7 @@ describe('loader', () => { id: 'foo', title: 'Foo index', })), - } as unknown) as Pick, + } as unknown as Pick, }); expect(cache.foo.getFieldByName('timestamp')!.meta).toEqual(true); @@ -891,7 +891,7 @@ describe('loader', () => { it('should call once for each index pattern', async () => { const setState = jest.fn(); - const fetchJson = (jest.fn((path: string) => { + const fetchJson = jest.fn((path: string) => { const indexPatternTitle = last(path.split('/')); return { indexPatternTitle, @@ -899,7 +899,7 @@ describe('loader', () => { (fieldName) => `ip${indexPatternTitle}_${fieldName}` ), }; - }) as unknown) as HttpHandler; + }) as unknown as HttpHandler; await syncExistingFields({ dateRange: { fromDate: '1900-01-01', toDate: '2000-01-01' }, @@ -941,7 +941,7 @@ describe('loader', () => { it('should call showNoDataPopover callback if current index pattern returns no fields', async () => { const setState = jest.fn(); const showNoDataPopover = jest.fn(); - const fetchJson = (jest.fn((path: string) => { + const fetchJson = jest.fn((path: string) => { const indexPatternTitle = last(path.split('/')); return { indexPatternTitle, @@ -950,7 +950,7 @@ describe('loader', () => { ? ['field_1', 'field_2'].map((fieldName) => `${indexPatternTitle}_${fieldName}`) : [], }; - }) as unknown) as HttpHandler; + }) as unknown as HttpHandler; const args = { dateRange: { fromDate: '1900-01-01', toDate: '2000-01-01' }, @@ -977,11 +977,11 @@ describe('loader', () => { it('should set all fields to available and existence error flag if the request fails', async () => { const setState = jest.fn(); - const fetchJson = (jest.fn((path: string) => { + const fetchJson = jest.fn((path: string) => { return new Promise((resolve, reject) => { reject(new Error()); }); - }) as unknown) as HttpHandler; + }) as unknown as HttpHandler; const args = { dateRange: { fromDate: '1900-01-01', toDate: '2000-01-01' }, @@ -1019,18 +1019,18 @@ describe('loader', () => { it('should set all fields to available and existence error flag if the request times out', async () => { const setState = jest.fn(); - const fetchJson = (jest.fn((path: string) => { + const fetchJson = jest.fn((path: string) => { return new Promise((resolve, reject) => { reject( new HttpFetchError( 'timeout', 'name', - ({} as unknown) as Request, - ({ status: 408 } as unknown) as Response + {} as unknown as Request, + { status: 408 } as unknown as Response ) ); }); - }) as unknown) as HttpHandler; + }) as unknown as HttpHandler; const args = { dateRange: { fromDate: '1900-01-01', toDate: '2000-01-01' }, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts b/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts index 3db17a94d58b5..ecd15732cf094 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/loader.ts @@ -73,31 +73,29 @@ export async function loadIndexPatterns({ (field) => !indexPatternsUtils.isNestedField(field) && (!!field.aggregatable || !!field.scripted) ) - .map( - (field): IndexPatternField => { - // Convert the getters on the index pattern service into plain JSON - const base = { - name: field.name, - displayName: field.displayName, - type: field.type, - aggregatable: field.aggregatable, - searchable: field.searchable, - meta: indexPattern.metaFields.includes(field.name), - esTypes: field.esTypes, - scripted: field.scripted, - runtime: Boolean(field.runtimeField), - }; - - // Simplifies tests by hiding optional properties instead of undefined - return base.scripted - ? { - ...base, - lang: field.lang, - script: field.script, - } - : base; - } - ) + .map((field): IndexPatternField => { + // Convert the getters on the index pattern service into plain JSON + const base = { + name: field.name, + displayName: field.displayName, + type: field.type, + aggregatable: field.aggregatable, + searchable: field.searchable, + meta: indexPattern.metaFields.includes(field.name), + esTypes: field.esTypes, + scripted: field.scripted, + runtime: Boolean(field.runtimeField), + }; + + // Simplifies tests by hiding optional properties instead of undefined + return base.scripted + ? { + ...base, + lang: field.lang, + script: field.script, + } + : base; + }) .concat(documentField); const { typeMeta, title, timeFieldName, fieldFormatMap } = indexPattern; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/overall_metric.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/overall_metric.tsx index 96f6c995e23c8..3e13e01834412 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/overall_metric.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/overall_metric.tsx @@ -193,8 +193,8 @@ Example: Percentage of range }), }); -export const overallAverageOperation = buildOverallMetricOperation( - { +export const overallAverageOperation = + buildOverallMetricOperation({ type: 'overall_average', displayName: i18n.translate('xpack.lens.indexPattern.overallMax', { defaultMessage: 'Overall max', @@ -223,5 +223,4 @@ Example: Divergence from the mean: \`sum(bytes) - overall_average(sum(bytes))\` `, }), - } -); + }); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/utils.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/utils.ts index 29865ac8d60b8..87c4355c1dc9f 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/utils.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/utils.ts @@ -16,14 +16,12 @@ import type { ReferenceBasedIndexPatternColumn } from '../column_types'; import { getManagedColumnsFrom, isColumnValidAsReference } from '../../layer_helpers'; import { operationDefinitionMap } from '..'; -export const buildLabelFunction = (ofName: (name?: string) => string) => ( - name?: string, - timeScale?: TimeScaleUnit, - timeShift?: string -) => { - const rawLabel = ofName(name); - return adjustTimeScaleLabelSuffix(rawLabel, undefined, timeScale, undefined, timeShift); -}; +export const buildLabelFunction = + (ofName: (name?: string) => string) => + (name?: string, timeScale?: TimeScaleUnit, timeShift?: string) => { + const rawLabel = ofName(name); + return adjustTimeScaleLabelSuffix(rawLabel, undefined, timeScale, undefined, timeShift); + }; export function checkForDataLayerType(layerType: LayerType, name: string) { if (layerType === layerTypes.THRESHOLD) { @@ -146,7 +144,7 @@ export function dateBasedOperationToExpression( functionName: string, additionalArgs: Record = {} ): ExpressionFunctionAST[] { - const currentColumn = (layer.columns[columnId] as unknown) as ReferenceBasedIndexPatternColumn; + const currentColumn = layer.columns[columnId] as unknown as ReferenceBasedIndexPatternColumn; const buckets = layer.columnOrder.filter((colId) => layer.columns[colId].isBucketed); const dateColumnIndex = buckets.findIndex( (colId) => layer.columns[colId].operationType === 'date_histogram' @@ -177,7 +175,7 @@ export function optionallHistogramBasedOperationToExpression( functionName: string, additionalArgs: Record = {} ): ExpressionFunctionAST[] { - const currentColumn = (layer.columns[columnId] as unknown) as ReferenceBasedIndexPatternColumn; + const currentColumn = layer.columns[columnId] as unknown as ReferenceBasedIndexPatternColumn; const buckets = layer.columnOrder.filter((colId) => layer.columns[colId].isBucketed); const nonHistogramColumns = buckets.filter( (colId) => diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx index 3b7554d8110f8..77569b58e8e31 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx @@ -130,7 +130,7 @@ describe('date_histogram', () => { }); function layerWithInterval(interval: string) { - return ({ + return { ...layer, columns: { ...layer.columns, @@ -141,7 +141,7 @@ describe('date_histogram', () => { }, }, }, - } as unknown) as IndexPatternLayer; + } as unknown as IndexPatternLayer; } describe('buildColumn', () => { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx index 66d0a94acc116..ea875c069150d 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx @@ -71,274 +71,272 @@ function getMultipleDateHistogramsErrorMessage(layer: IndexPatternLayer, columnI }); } -export const dateHistogramOperation: OperationDefinition< - DateHistogramIndexPatternColumn, - 'field' -> = { - type: 'date_histogram', - displayName: i18n.translate('xpack.lens.indexPattern.dateHistogram', { - defaultMessage: 'Date histogram', - }), - input: 'field', - priority: 5, // Highest priority level used - operationParams: [{ name: 'interval', type: 'string', required: false }], - getErrorMessage: (layer, columnId, indexPattern) => - [ - ...(getInvalidFieldMessage( - layer.columns[columnId] as FieldBasedIndexPatternColumn, - indexPattern - ) || []), - getMultipleDateHistogramsErrorMessage(layer, columnId) || '', - ].filter(Boolean), - getHelpMessage: (props) => , - getPossibleOperationForField: ({ aggregationRestrictions, aggregatable, type }) => { - if ( - (type === 'date' || type === 'date_range') && - aggregatable && - (!aggregationRestrictions || aggregationRestrictions.date_histogram) - ) { +export const dateHistogramOperation: OperationDefinition = + { + type: 'date_histogram', + displayName: i18n.translate('xpack.lens.indexPattern.dateHistogram', { + defaultMessage: 'Date histogram', + }), + input: 'field', + priority: 5, // Highest priority level used + operationParams: [{ name: 'interval', type: 'string', required: false }], + getErrorMessage: (layer, columnId, indexPattern) => + [ + ...(getInvalidFieldMessage( + layer.columns[columnId] as FieldBasedIndexPatternColumn, + indexPattern + ) || []), + getMultipleDateHistogramsErrorMessage(layer, columnId) || '', + ].filter(Boolean), + getHelpMessage: (props) => , + getPossibleOperationForField: ({ aggregationRestrictions, aggregatable, type }) => { + if ( + (type === 'date' || type === 'date_range') && + aggregatable && + (!aggregationRestrictions || aggregationRestrictions.date_histogram) + ) { + return { + dataType: 'date', + isBucketed: true, + scale: 'interval', + }; + } + }, + getDefaultLabel: (column, indexPattern) => getSafeName(column.sourceField, indexPattern), + buildColumn({ field }, columnParams) { return { + label: field.displayName, dataType: 'date', + operationType: 'date_histogram', + sourceField: field.name, isBucketed: true, scale: 'interval', + params: { + interval: columnParams?.interval ?? autoInterval, + }, }; - } - }, - getDefaultLabel: (column, indexPattern) => getSafeName(column.sourceField, indexPattern), - buildColumn({ field }, columnParams) { - return { - label: field.displayName, - dataType: 'date', - operationType: 'date_histogram', - sourceField: field.name, - isBucketed: true, - scale: 'interval', - params: { - interval: columnParams?.interval ?? autoInterval, - }, - }; - }, - isTransferable: (column, newIndexPattern) => { - const newField = newIndexPattern.getFieldByName(column.sourceField); + }, + isTransferable: (column, newIndexPattern) => { + const newField = newIndexPattern.getFieldByName(column.sourceField); - return Boolean( - newField && - newField.type === 'date' && - newField.aggregatable && - (!newField.aggregationRestrictions || newField.aggregationRestrictions.date_histogram) - ); - }, - onFieldChange: (oldColumn, field) => { - return { - ...oldColumn, - label: field.displayName, - sourceField: field.name, - }; - }, - toEsAggsFn: (column, columnId, indexPattern) => { - const usedField = indexPattern.getFieldByName(column.sourceField); - let timeZone: string | undefined; - let interval = column.params?.interval ?? autoInterval; - if ( - usedField && - usedField.aggregationRestrictions && - usedField.aggregationRestrictions.date_histogram - ) { - interval = restrictedInterval(usedField.aggregationRestrictions) as string; - timeZone = usedField.aggregationRestrictions.date_histogram.time_zone; - } - return buildExpressionFunction('aggDateHistogram', { - id: columnId, - enabled: true, - schema: 'segment', - field: column.sourceField, - time_zone: timeZone, - useNormalizedEsInterval: !usedField?.aggregationRestrictions?.date_histogram, - interval, - drop_partials: false, - min_doc_count: 0, - extended_bounds: extendedBoundsToAst({}), - }).toAst(); - }, - paramEditor: function ParamEditor({ - layer, - columnId, - currentColumn, - updateLayer, - dateRange, - data, - indexPattern, - }: ParamEditorProps) { - const field = currentColumn && indexPattern.getFieldByName(currentColumn.sourceField); - const intervalIsRestricted = - field!.aggregationRestrictions && field!.aggregationRestrictions.date_histogram; + return Boolean( + newField && + newField.type === 'date' && + newField.aggregatable && + (!newField.aggregationRestrictions || newField.aggregationRestrictions.date_histogram) + ); + }, + onFieldChange: (oldColumn, field) => { + return { + ...oldColumn, + label: field.displayName, + sourceField: field.name, + }; + }, + toEsAggsFn: (column, columnId, indexPattern) => { + const usedField = indexPattern.getFieldByName(column.sourceField); + let timeZone: string | undefined; + let interval = column.params?.interval ?? autoInterval; + if ( + usedField && + usedField.aggregationRestrictions && + usedField.aggregationRestrictions.date_histogram + ) { + interval = restrictedInterval(usedField.aggregationRestrictions) as string; + timeZone = usedField.aggregationRestrictions.date_histogram.time_zone; + } + return buildExpressionFunction('aggDateHistogram', { + id: columnId, + enabled: true, + schema: 'segment', + field: column.sourceField, + time_zone: timeZone, + useNormalizedEsInterval: !usedField?.aggregationRestrictions?.date_histogram, + interval, + drop_partials: false, + min_doc_count: 0, + extended_bounds: extendedBoundsToAst({}), + }).toAst(); + }, + paramEditor: function ParamEditor({ + layer, + columnId, + currentColumn, + updateLayer, + dateRange, + data, + indexPattern, + }: ParamEditorProps) { + const field = currentColumn && indexPattern.getFieldByName(currentColumn.sourceField); + const intervalIsRestricted = + field!.aggregationRestrictions && field!.aggregationRestrictions.date_histogram; - const interval = parseInterval(currentColumn.params.interval); + const interval = parseInterval(currentColumn.params.interval); - // We force the interval value to 1 if it's empty, since that is the ES behavior, - // and the isValidInterval function doesn't handle the empty case properly. Fixing - // isValidInterval involves breaking changes in other areas. - const isValid = isValidInterval( - `${interval.value === '' ? '1' : interval.value}${interval.unit}`, - restrictedInterval(field!.aggregationRestrictions) - ); + // We force the interval value to 1 if it's empty, since that is the ES behavior, + // and the isValidInterval function doesn't handle the empty case properly. Fixing + // isValidInterval involves breaking changes in other areas. + const isValid = isValidInterval( + `${interval.value === '' ? '1' : interval.value}${interval.unit}`, + restrictedInterval(field!.aggregationRestrictions) + ); - function onChangeAutoInterval(ev: EuiSwitchEvent) { - const { fromDate, toDate } = dateRange; - const value = ev.target.checked - ? data.search.aggs.calculateAutoTimeExpression({ from: fromDate, to: toDate }) || '1h' - : autoInterval; - updateLayer(updateColumnParam({ layer, columnId, paramName: 'interval', value })); - } + function onChangeAutoInterval(ev: EuiSwitchEvent) { + const { fromDate, toDate } = dateRange; + const value = ev.target.checked + ? data.search.aggs.calculateAutoTimeExpression({ from: fromDate, to: toDate }) || '1h' + : autoInterval; + updateLayer(updateColumnParam({ layer, columnId, paramName: 'interval', value })); + } - const setInterval = (newInterval: typeof interval) => { - const isCalendarInterval = calendarOnlyIntervals.has(newInterval.unit); - const value = `${isCalendarInterval ? '1' : newInterval.value}${newInterval.unit || 'd'}`; + const setInterval = (newInterval: typeof interval) => { + const isCalendarInterval = calendarOnlyIntervals.has(newInterval.unit); + const value = `${isCalendarInterval ? '1' : newInterval.value}${newInterval.unit || 'd'}`; - updateLayer(updateColumnParam({ layer, columnId, paramName: 'interval', value })); - }; + updateLayer(updateColumnParam({ layer, columnId, paramName: 'interval', value })); + }; - return ( - <> - {!intervalIsRestricted && ( - - - - )} - {currentColumn.params.interval !== autoInterval && ( - - {intervalIsRestricted ? ( - + {!intervalIsRestricted && ( + + - ) : ( - <> - - - { - const newInterval = { - ...interval, - value: e.target.value, - }; - setInterval(newInterval); - }} - /> - - - { - const newInterval = { - ...interval, - unit: e.target.value, - }; - setInterval(newInterval); - }} - isInvalid={!isValid} - options={[ - { - value: 'ms', - text: i18n.translate( - 'xpack.lens.indexPattern.dateHistogram.milliseconds', - { - defaultMessage: 'milliseconds', - } - ), - }, - { - value: 's', - text: i18n.translate('xpack.lens.indexPattern.dateHistogram.seconds', { - defaultMessage: 'seconds', - }), - }, - { - value: 'm', - text: i18n.translate('xpack.lens.indexPattern.dateHistogram.minutes', { - defaultMessage: 'minutes', - }), - }, - { - value: 'h', - text: i18n.translate('xpack.lens.indexPattern.dateHistogram.hours', { - defaultMessage: 'hours', - }), - }, - { - value: 'd', - text: i18n.translate('xpack.lens.indexPattern.dateHistogram.days', { - defaultMessage: 'days', - }), - }, - { - value: 'w', - text: i18n.translate('xpack.lens.indexPattern.dateHistogram.week', { - defaultMessage: 'week', - }), - }, - { - value: 'M', - text: i18n.translate('xpack.lens.indexPattern.dateHistogram.month', { - defaultMessage: 'month', - }), - }, - // Quarterly intervals appear to be unsupported by esaggs - { - value: 'y', - text: i18n.translate('xpack.lens.indexPattern.dateHistogram.year', { - defaultMessage: 'year', - }), - }, - ]} - /> - - - {!isValid && ( - <> - - - {i18n.translate('xpack.lens.indexPattern.invalidInterval', { - defaultMessage: 'Invalid interval value', - })} - - - )} - - )} - - )} - - ); - }, -}; + + )} + {currentColumn.params.interval !== autoInterval && ( + + {intervalIsRestricted ? ( + + ) : ( + <> + + + { + const newInterval = { + ...interval, + value: e.target.value, + }; + setInterval(newInterval); + }} + /> + + + { + const newInterval = { + ...interval, + unit: e.target.value, + }; + setInterval(newInterval); + }} + isInvalid={!isValid} + options={[ + { + value: 'ms', + text: i18n.translate( + 'xpack.lens.indexPattern.dateHistogram.milliseconds', + { + defaultMessage: 'milliseconds', + } + ), + }, + { + value: 's', + text: i18n.translate('xpack.lens.indexPattern.dateHistogram.seconds', { + defaultMessage: 'seconds', + }), + }, + { + value: 'm', + text: i18n.translate('xpack.lens.indexPattern.dateHistogram.minutes', { + defaultMessage: 'minutes', + }), + }, + { + value: 'h', + text: i18n.translate('xpack.lens.indexPattern.dateHistogram.hours', { + defaultMessage: 'hours', + }), + }, + { + value: 'd', + text: i18n.translate('xpack.lens.indexPattern.dateHistogram.days', { + defaultMessage: 'days', + }), + }, + { + value: 'w', + text: i18n.translate('xpack.lens.indexPattern.dateHistogram.week', { + defaultMessage: 'week', + }), + }, + { + value: 'M', + text: i18n.translate('xpack.lens.indexPattern.dateHistogram.month', { + defaultMessage: 'month', + }), + }, + // Quarterly intervals appear to be unsupported by esaggs + { + value: 'y', + text: i18n.translate('xpack.lens.indexPattern.dateHistogram.year', { + defaultMessage: 'year', + }), + }, + ]} + /> + + + {!isValid && ( + <> + + + {i18n.translate('xpack.lens.indexPattern.invalidInterval', { + defaultMessage: 'Invalid interval value', + })} + + + )} + + )} + + )} + + ); + }, + }; function parseInterval(currentInterval: string) { const interval = currentInterval || ''; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/formula_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/formula_editor.tsx index 97eaf7604df83..4f6f13ea843ef 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/formula_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/formula_editor.tsx @@ -91,9 +91,10 @@ export function FormulaEditor({ const disposables = React.useRef([]); const editor1 = React.useRef(); - const visibleOperationsMap = useMemo(() => filterByVisibleOperation(operationDefinitionMap), [ - operationDefinitionMap, - ]); + const visibleOperationsMap = useMemo( + () => filterByVisibleOperation(operationDefinitionMap), + [operationDefinitionMap] + ); const baseInterval = 'interval' in dateHistogramInterval @@ -303,8 +304,9 @@ export function FormulaEditor({ [text] ); - const errorCount = warnings.filter((marker) => marker.severity === monaco.MarkerSeverity.Error) - .length; + const errorCount = warnings.filter( + (marker) => marker.severity === monaco.MarkerSeverity.Error + ).length; const warningCount = warnings.filter( (marker) => marker.severity === monaco.MarkerSeverity.Warning ).length; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/math_completion.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/math_completion.test.ts index c55f22dd682d0..9b7f34b583631 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/math_completion.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/math_completion.test.ts @@ -41,7 +41,7 @@ const stringOperation = () => ({ dataType: 'string', isBucketed: true }); // Only one of each type is needed const operationDefinitionMap: Record = { - sum: ({ + sum: { type: 'sum', input: 'field', buildColumn: buildGenericColumn('sum'), @@ -52,15 +52,15 @@ const operationDefinitionMap: Record = { signature: 'field: string', description: 'description', }, - } as unknown) as GenericOperationDefinition, - count: ({ + } as unknown as GenericOperationDefinition, + count: { type: 'count', input: 'field', buildColumn: buildGenericColumn('count'), getPossibleOperationForField: (field: IndexPatternField) => field.name === 'Records' ? numericOperation() : null, - } as unknown) as GenericOperationDefinition, - last_value: ({ + } as unknown as GenericOperationDefinition, + last_value: { type: 'last_value', input: 'field', buildColumn: buildGenericColumn('last_value'), @@ -68,8 +68,8 @@ const operationDefinitionMap: Record = { dataType: field.type, isBucketed: false, }), - } as unknown) as GenericOperationDefinition, - moving_average: ({ + } as unknown as GenericOperationDefinition, + moving_average: { type: 'moving_average', input: 'fullReference', requiredReferences: [ @@ -82,18 +82,18 @@ const operationDefinitionMap: Record = { operationParams: [{ name: 'window', type: 'number', required: true }], buildColumn: buildGenericColumn('moving_average'), getPossibleOperation: numericOperation, - } as unknown) as GenericOperationDefinition, - cumulative_sum: ({ + } as unknown as GenericOperationDefinition, + cumulative_sum: { type: 'cumulative_sum', input: 'fullReference', buildColumn: buildGenericColumn('cumulative_sum'), getPossibleOperation: numericOperation, - } as unknown) as GenericOperationDefinition, - terms: ({ + } as unknown as GenericOperationDefinition, + terms: { type: 'terms', input: 'field', getPossibleOperationForField: stringOperation, - } as unknown) as GenericOperationDefinition, + } as unknown as GenericOperationDefinition, }; describe('math completion', () => { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula.test.tsx index 936f1e477057d..499170349c3d5 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula.test.tsx @@ -22,7 +22,7 @@ jest.mock('../../layer_helpers', () => { }); const operationDefinitionMap: Record = { - average: ({ + average: { input: 'field', buildColumn: ({ field }: { field: IndexPatternField }) => ({ label: 'avg', @@ -33,12 +33,12 @@ const operationDefinitionMap: Record = { scale: 'ratio', timeScale: false, }), - } as unknown) as GenericOperationDefinition, + } as unknown as GenericOperationDefinition, terms: { input: 'field' } as GenericOperationDefinition, sum: { input: 'field', filterable: true } as GenericOperationDefinition, last_value: { input: 'field' } as GenericOperationDefinition, max: { input: 'field' } as GenericOperationDefinition, - count: ({ + count: { input: 'field', filterable: true, buildColumn: ({ field }: { field: IndexPatternField }) => ({ @@ -50,9 +50,9 @@ const operationDefinitionMap: Record = { scale: 'ratio', timeScale: false, }), - } as unknown) as GenericOperationDefinition, + } as unknown as GenericOperationDefinition, derivative: { input: 'fullReference' } as GenericOperationDefinition, - moving_average: ({ + moving_average: { input: 'fullReference', operationParams: [{ name: 'window', type: 'number', required: true }], buildColumn: ({ references }: { references: string[] }) => ({ @@ -66,7 +66,7 @@ const operationDefinitionMap: Record = { references, }), getErrorMessage: () => ['mock error'], - } as unknown) as GenericOperationDefinition, + } as unknown as GenericOperationDefinition, cumulative_sum: { input: 'fullReference' } as GenericOperationDefinition, }; @@ -355,6 +355,33 @@ describe('formula', () => { references: [], }); }); + + it('should move into Formula previous static_value operation', () => { + expect( + formulaOperation.buildColumn({ + previousColumn: { + label: 'Static value: 0', + dataType: 'number', + isBucketed: false, + operationType: 'static_value', + references: [], + params: { + value: '0', + }, + }, + layer, + indexPattern, + }) + ).toEqual({ + label: '0', + dataType: 'number', + operationType: 'formula', + isBucketed: false, + scale: 'ratio', + params: { isFormulaBroken: false, formula: '0' }, + references: [], + }); + }); }); describe('regenerateLayerFromAst()', () => { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula.tsx index 1b79448d82742..511f18415272e 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula.tsx @@ -35,130 +35,128 @@ export interface FormulaIndexPatternColumn extends ReferenceBasedIndexPatternCol }; } -export const formulaOperation: OperationDefinition< - FormulaIndexPatternColumn, - 'managedReference' -> = { - type: 'formula', - displayName: defaultLabel, - getDefaultLabel: (column, indexPattern) => column.params.formula ?? defaultLabel, - input: 'managedReference', - hidden: true, - getDisabledStatus(indexPattern: IndexPattern) { - return undefined; - }, - getErrorMessage(layer, columnId, indexPattern, operationDefinitionMap) { - const column = layer.columns[columnId] as FormulaIndexPatternColumn; - if (!column.params.formula || !operationDefinitionMap) { - return; - } +export const formulaOperation: OperationDefinition = + { + type: 'formula', + displayName: defaultLabel, + getDefaultLabel: (column, indexPattern) => column.params.formula ?? defaultLabel, + input: 'managedReference', + hidden: true, + getDisabledStatus(indexPattern: IndexPattern) { + return undefined; + }, + getErrorMessage(layer, columnId, indexPattern, operationDefinitionMap) { + const column = layer.columns[columnId] as FormulaIndexPatternColumn; + if (!column.params.formula || !operationDefinitionMap) { + return; + } - const visibleOperationsMap = filterByVisibleOperation(operationDefinitionMap); - const { root, error } = tryToParse(column.params.formula, visibleOperationsMap); - if (error || root == null) { - return error?.message ? [error.message] : []; - } + const visibleOperationsMap = filterByVisibleOperation(operationDefinitionMap); + const { root, error } = tryToParse(column.params.formula, visibleOperationsMap); + if (error || root == null) { + return error?.message ? [error.message] : []; + } - const errors = runASTValidation(root, layer, indexPattern, visibleOperationsMap); + const errors = runASTValidation(root, layer, indexPattern, visibleOperationsMap); - if (errors.length) { - return errors.map(({ message }) => message); - } + if (errors.length) { + return errors.map(({ message }) => message); + } - const managedColumns = getManagedColumnsFrom(columnId, layer.columns); - const innerErrors = managedColumns - .flatMap(([id, col]) => { - const def = visibleOperationsMap[col.operationType]; - if (def?.getErrorMessage) { - const messages = def.getErrorMessage(layer, id, indexPattern, visibleOperationsMap); - return messages ? { message: messages.join(', ') } : []; - } - return []; - }) - .filter((marker) => marker); + const managedColumns = getManagedColumnsFrom(columnId, layer.columns); + const innerErrors = managedColumns + .flatMap(([id, col]) => { + const def = visibleOperationsMap[col.operationType]; + if (def?.getErrorMessage) { + const messages = def.getErrorMessage(layer, id, indexPattern, visibleOperationsMap); + return messages ? { message: messages.join(', ') } : []; + } + return []; + }) + .filter((marker) => marker); - return innerErrors.length ? innerErrors.map(({ message }) => message) : undefined; - }, - getPossibleOperation() { - return { - dataType: 'number', - isBucketed: false, - scale: 'ratio', - }; - }, - toExpression: (layer, columnId) => { - const currentColumn = layer.columns[columnId] as FormulaIndexPatternColumn; - const params = currentColumn.params; - // TODO: improve this logic - const useDisplayLabel = currentColumn.label !== defaultLabel; - const label = !params?.isFormulaBroken - ? useDisplayLabel - ? currentColumn.label - : params?.formula ?? defaultLabel - : defaultLabel; + return innerErrors.length ? innerErrors.map(({ message }) => message) : undefined; + }, + getPossibleOperation() { + return { + dataType: 'number', + isBucketed: false, + scale: 'ratio', + }; + }, + toExpression: (layer, columnId) => { + const currentColumn = layer.columns[columnId] as FormulaIndexPatternColumn; + const params = currentColumn.params; + // TODO: improve this logic + const useDisplayLabel = currentColumn.label !== defaultLabel; + const label = !params?.isFormulaBroken + ? useDisplayLabel + ? currentColumn.label + : params?.formula ?? defaultLabel + : defaultLabel; - return [ - { - type: 'function', - function: currentColumn.references.length ? 'mathColumn' : 'mapColumn', - arguments: { - id: [columnId], - name: [label || defaultLabel], - expression: [currentColumn.references.length ? `"${currentColumn.references[0]}"` : ''], + return [ + { + type: 'function', + function: currentColumn.references.length ? 'mathColumn' : 'mapColumn', + arguments: { + id: [columnId], + name: [label || defaultLabel], + expression: [currentColumn.references.length ? `"${currentColumn.references[0]}"` : ''], + }, + }, + ]; + }, + buildColumn({ previousColumn, layer, indexPattern }, _, operationDefinitionMap) { + let previousFormula = ''; + if (previousColumn) { + previousFormula = generateFormula( + previousColumn, + layer, + previousFormula, + operationDefinitionMap + ); + } + // carry over the format settings from previous operation for seamless transfer + // NOTE: this works only for non-default formatters set in Lens + let prevFormat = {}; + if (previousColumn?.params && 'format' in previousColumn.params) { + prevFormat = { format: previousColumn.params.format }; + } + return { + label: previousFormula || defaultLabel, + dataType: 'number', + operationType: 'formula', + isBucketed: false, + scale: 'ratio', + params: previousFormula + ? { formula: previousFormula, isFormulaBroken: false, ...prevFormat } + : { ...prevFormat }, + references: [], + }; + }, + isTransferable: () => { + return true; + }, + createCopy(layer, sourceId, targetId, indexPattern, operationDefinitionMap) { + const currentColumn = layer.columns[sourceId] as FormulaIndexPatternColumn; + const tempLayer = { + ...layer, + columns: { + ...layer.columns, + [targetId]: { ...currentColumn }, }, - }, - ]; - }, - buildColumn({ previousColumn, layer, indexPattern }, _, operationDefinitionMap) { - let previousFormula = ''; - if (previousColumn) { - previousFormula = generateFormula( - previousColumn, - layer, - previousFormula, + }; + const { newLayer } = regenerateLayerFromAst( + currentColumn.params.formula ?? '', + tempLayer, + targetId, + currentColumn, + indexPattern, operationDefinitionMap ); - } - // carry over the format settings from previous operation for seamless transfer - // NOTE: this works only for non-default formatters set in Lens - let prevFormat = {}; - if (previousColumn?.params && 'format' in previousColumn.params) { - prevFormat = { format: previousColumn.params.format }; - } - return { - label: previousFormula || defaultLabel, - dataType: 'number', - operationType: 'formula', - isBucketed: false, - scale: 'ratio', - params: previousFormula - ? { formula: previousFormula, isFormulaBroken: false, ...prevFormat } - : { ...prevFormat }, - references: [], - }; - }, - isTransferable: () => { - return true; - }, - createCopy(layer, sourceId, targetId, indexPattern, operationDefinitionMap) { - const currentColumn = layer.columns[sourceId] as FormulaIndexPatternColumn; - const tempLayer = { - ...layer, - columns: { - ...layer.columns, - [targetId]: { ...currentColumn }, - }, - }; - const { newLayer } = regenerateLayerFromAst( - currentColumn.params.formula ?? '', - tempLayer, - targetId, - currentColumn, - indexPattern, - operationDefinitionMap - ); - return newLayer; - }, + return newLayer; + }, - paramEditor: WrappedFormulaEditor, -}; + paramEditor: WrappedFormulaEditor, + }; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/generate.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/generate.ts index 589f547434b91..3db9ebc6f969d 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/generate.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/generate.ts @@ -38,6 +38,11 @@ export function generateFormula( previousFormula: string, operationDefinitionMap: Record | undefined ) { + if (previousColumn.operationType === 'static_value') { + if (previousColumn.params && 'value' in previousColumn.params) { + return String(previousColumn.params.value); // make sure it's a string + } + } if ('references' in previousColumn) { const metric = layer.columns[previousColumn.references[0]]; if (metric && 'sourceField' in metric && metric.dataType === 'number') { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/parse.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/parse.ts index 088c7e0de64f8..adccecc875c22 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/parse.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/parse.ts @@ -101,10 +101,9 @@ function extractColumns( const mappedParams = getOperationParams(nodeOperation, namedArguments || []); - const newCol = (nodeOperation as OperationDefinition< - IndexPatternColumn, - 'field' - >).buildColumn( + const newCol = ( + nodeOperation as OperationDefinition + ).buildColumn( { layer, indexPattern, @@ -139,10 +138,9 @@ function extractColumns( } const mappedParams = getOperationParams(nodeOperation, namedArguments || []); - const newCol = (nodeOperation as OperationDefinition< - IndexPatternColumn, - 'fullReference' - >).buildColumn( + const newCol = ( + nodeOperation as OperationDefinition + ).buildColumn( { layer, indexPattern, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/types.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/types.ts index ce853dec1d951..b0b32d569193f 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/types.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/types.ts @@ -14,12 +14,10 @@ import { export type GroupedNodes = { [Key in TinymathNamedArgument['type']]: TinymathNamedArgument[]; -} & - { - [Key in TinymathVariable['type']]: Array; - } & - { - [Key in TinymathFunction['type']]: TinymathFunction[]; - }; +} & { + [Key in TinymathVariable['type']]: Array; +} & { + [Key in TinymathFunction['type']]: TinymathFunction[]; +}; export type TinymathNodeTypes = Exclude; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/util.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/util.ts index 6d0a585db048f..f1530ba46caef 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/util.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/util.ts @@ -19,12 +19,13 @@ import type { GroupedNodes } from './types'; export const unquotedStringRegex = /[^0-9A-Za-z._@\[\]/]/; export function groupArgsByType(args: TinymathAST[]) { - const { namedArgument, variable, function: functions } = groupBy( - args, - (arg: TinymathAST) => { - return isObject(arg) ? arg.type : 'variable'; - } - ) as GroupedNodes; + const { + namedArgument, + variable, + function: functions, + } = groupBy(args, (arg: TinymathAST) => { + return isObject(arg) ? arg.type : 'variable'; + }) as GroupedNodes; // better naming return { namedArguments: namedArgument || [], diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/validation.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/validation.ts index d65ef5ada8b37..8749ed4b690f4 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/validation.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/validation.ts @@ -203,7 +203,7 @@ function getMessageFromId({ }): ErrorWrapper { let message: string; // Use a less strict type instead of doing a typecast on each message type - const out = (values as unknown) as Record; + const out = values as unknown as Record; switch (messageId) { case 'wrongFirstArgument': message = i18n.translate('xpack.lens.indexPattern.formulaOperationWrongFirstArgument', { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/helpers.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/helpers.tsx index 45abbcd3d9cf9..a399183694863 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/helpers.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/helpers.tsx @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import { IndexPatternColumn, operationDefinitionMap } from '.'; -import { FieldBasedIndexPatternColumn } from './column_types'; +import { FieldBasedIndexPatternColumn, ReferenceBasedIndexPatternColumn } from './column_types'; import { IndexPattern } from '../../types'; export function getInvalidFieldMessage( @@ -81,8 +81,7 @@ export function isValidNumber( const inputValueAsNumber = Number(inputValue); return ( inputValue !== '' && - inputValue !== null && - inputValue !== undefined && + inputValue != null && !Number.isNaN(inputValueAsNumber) && Number.isFinite(inputValueAsNumber) && (!integer || Number.isInteger(inputValueAsNumber)) && @@ -91,7 +90,9 @@ export function isValidNumber( ); } -export function getFormatFromPreviousColumn(previousColumn: IndexPatternColumn | undefined) { +export function getFormatFromPreviousColumn( + previousColumn: IndexPatternColumn | ReferenceBasedIndexPatternColumn | undefined +) { return previousColumn?.dataType === 'number' && previousColumn.params && 'format' in previousColumn.params && diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts index 569045f39877e..0212c73f46879 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts @@ -49,6 +49,7 @@ import { formulaOperation, FormulaIndexPatternColumn, } from './formula'; +import { staticValueOperation, StaticValueIndexPatternColumn } from './static_value'; import { lastValueOperation, LastValueIndexPatternColumn } from './last_value'; import { FrameDatasourceAPI, OperationMetadata } from '../../../types'; import type { BaseIndexPatternColumn, ReferenceBasedIndexPatternColumn } from './column_types'; @@ -87,7 +88,8 @@ export type IndexPatternColumn = | DerivativeIndexPatternColumn | MovingAverageIndexPatternColumn | MathIndexPatternColumn - | FormulaIndexPatternColumn; + | FormulaIndexPatternColumn + | StaticValueIndexPatternColumn; export type FieldBasedIndexPatternColumn = Extract; @@ -119,6 +121,7 @@ export { CountIndexPatternColumn } from './count'; export { LastValueIndexPatternColumn } from './last_value'; export { RangeIndexPatternColumn } from './ranges'; export { FormulaIndexPatternColumn, MathIndexPatternColumn } from './formula'; +export { StaticValueIndexPatternColumn } from './static_value'; // List of all operation definitions registered to this data source. // If you want to implement a new operation, add the definition to this array and @@ -147,6 +150,7 @@ const internalOperationDefinitions = [ overallMinOperation, overallMaxOperation, overallAverageOperation, + staticValueOperation, ]; export { termsOperation } from './terms'; @@ -168,6 +172,7 @@ export { overallMinOperation, } from './calculations'; export { formulaOperation } from './formula/formula'; +export { staticValueOperation } from './static_value'; /** * Properties passed to the operation-specific part of the popover editor @@ -602,13 +607,11 @@ export const operationDefinitions = internalOperationDefinitions as GenericOpera * (e.g. `import { termsOperation } from './operations/definitions'`). This map is * intended to be used in situations where the operation type is not known during compile time. */ -export const operationDefinitionMap: Record< - string, - GenericOperationDefinition -> = internalOperationDefinitions.reduce( - (definitionMap, definition) => ({ ...definitionMap, [definition.type]: definition }), - {} -); +export const operationDefinitionMap: Record = + internalOperationDefinitions.reduce( + (definitionMap, definition) => ({ ...definitionMap, [definition.type]: definition }), + {} + ); /** * Cannot map the prev names, but can guarantee the new names are matching up using the type system diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.tsx index 267b8e1f66a1d..88c9d82092e21 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.tsx @@ -261,7 +261,7 @@ export const lastValueOperation: OperationDefinition diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx index aef086a6ee288..7ef8d1ec36b8a 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx @@ -77,9 +77,9 @@ type ReactMouseEvent = React.MouseEvent & React.MouseEvent; // need this for MAX_HISTOGRAM value -const uiSettingsMock = ({ +const uiSettingsMock = { get: jest.fn().mockReturnValue(100), -} as unknown) as IUiSettingsClient; +} as unknown as IUiSettingsClient; const sourceField = 'MyField'; const defaultOptions = { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/static_value.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/static_value.test.tsx new file mode 100644 index 0000000000000..0a6620eecf308 --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/static_value.test.tsx @@ -0,0 +1,404 @@ +/* + * 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 { shallow, mount } from 'enzyme'; +import { IUiSettingsClient, SavedObjectsClientContract, HttpSetup } from 'kibana/public'; +import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; +import { dataPluginMock } from '../../../../../../../src/plugins/data/public/mocks'; +import { createMockedIndexPattern } from '../../mocks'; +import { staticValueOperation } from './index'; +import { IndexPattern, IndexPatternLayer } from '../../types'; +import { StaticValueIndexPatternColumn } from './static_value'; +import { EuiFieldNumber } from '@elastic/eui'; +import { act } from 'react-dom/test-utils'; + +jest.mock('lodash', () => { + const original = jest.requireActual('lodash'); + + return { + ...original, + debounce: (fn: unknown) => fn, + }; +}); + +const uiSettingsMock = {} as IUiSettingsClient; + +const defaultProps = { + storage: {} as IStorageWrapper, + uiSettings: uiSettingsMock, + savedObjectsClient: {} as SavedObjectsClientContract, + dateRange: { fromDate: 'now-1d', toDate: 'now' }, + data: dataPluginMock.createStartContract(), + http: {} as HttpSetup, + indexPattern: { + ...createMockedIndexPattern(), + hasRestrictions: false, + } as IndexPattern, + operationDefinitionMap: {}, + isFullscreen: false, + toggleFullscreen: jest.fn(), + setIsCloseable: jest.fn(), + layerId: '1', +}; + +describe('static_value', () => { + let layer: IndexPatternLayer; + + beforeEach(() => { + layer = { + indexPatternId: '1', + columnOrder: ['col1', 'col2'], + columns: { + col1: { + label: 'Top value of category', + dataType: 'string', + isBucketed: true, + operationType: 'terms', + params: { + orderBy: { type: 'alphabetical' }, + size: 3, + orderDirection: 'asc', + }, + sourceField: 'category', + }, + col2: { + label: 'Static value: 23', + dataType: 'number', + isBucketed: false, + operationType: 'static_value', + references: [], + params: { + value: '23', + }, + }, + }, + }; + }); + + function getLayerWithStaticValue(newValue: string): IndexPatternLayer { + return { + ...layer, + columns: { + ...layer.columns, + col2: { + ...layer.columns.col2, + label: `Static value: ${newValue}`, + params: { + value: newValue, + }, + } as StaticValueIndexPatternColumn, + }, + }; + } + + describe('getDefaultLabel', () => { + it('should return the label for the given value', () => { + expect( + staticValueOperation.getDefaultLabel( + { + label: 'Static value: 23', + dataType: 'number', + isBucketed: false, + operationType: 'static_value', + references: [], + params: { + value: '23', + }, + }, + createMockedIndexPattern(), + layer.columns + ) + ).toBe('Static value: 23'); + }); + + it('should return the default label for non valid value', () => { + expect( + staticValueOperation.getDefaultLabel( + { + label: 'Static value', + dataType: 'number', + isBucketed: false, + operationType: 'static_value', + references: [], + params: { + value: '', + }, + }, + createMockedIndexPattern(), + layer.columns + ) + ).toBe('Static value'); + }); + }); + + describe('getErrorMessage', () => { + it('should return no error for valid values', () => { + expect( + staticValueOperation.getErrorMessage!( + getLayerWithStaticValue('23'), + 'col2', + createMockedIndexPattern() + ) + ).toBeUndefined(); + // test for potential falsy value + expect( + staticValueOperation.getErrorMessage!( + getLayerWithStaticValue('0'), + 'col2', + createMockedIndexPattern() + ) + ).toBeUndefined(); + }); + + it('should return error for invalid values', () => { + for (const value of ['NaN', 'Infinity', 'string']) { + expect( + staticValueOperation.getErrorMessage!( + getLayerWithStaticValue(value), + 'col2', + createMockedIndexPattern() + ) + ).toEqual(expect.arrayContaining([expect.stringMatching('is not a valid number')])); + } + }); + }); + + describe('toExpression', () => { + it('should return a mathColumn operation with valid value', () => { + for (const value of ['23', '0', '-1']) { + expect( + staticValueOperation.toExpression( + getLayerWithStaticValue(value), + 'col2', + createMockedIndexPattern() + ) + ).toEqual([ + { + type: 'function', + function: 'mathColumn', + arguments: { + id: ['col2'], + name: [`Static value: ${value}`], + expression: [value], + }, + }, + ]); + } + }); + + it('should fallback to mapColumn for invalid value', () => { + for (const value of ['NaN', '', 'Infinity']) { + expect( + staticValueOperation.toExpression( + getLayerWithStaticValue(value), + 'col2', + createMockedIndexPattern() + ) + ).toEqual([ + { + type: 'function', + function: 'mapColumn', + arguments: { + id: ['col2'], + name: [`Static value`], + expression: ['100'], + }, + }, + ]); + } + }); + }); + + describe('buildColumn', () => { + it('should set default static value', () => { + expect( + staticValueOperation.buildColumn({ + indexPattern: createMockedIndexPattern(), + layer: { columns: {}, columnOrder: [], indexPatternId: '' }, + }) + ).toEqual({ + label: 'Static value', + dataType: 'number', + operationType: 'static_value', + isBucketed: false, + scale: 'ratio', + params: { value: '100' }, + references: [], + }); + }); + + it('should merge a previousColumn', () => { + expect( + staticValueOperation.buildColumn({ + indexPattern: createMockedIndexPattern(), + layer: { columns: {}, columnOrder: [], indexPatternId: '' }, + previousColumn: { + label: 'Static value', + dataType: 'number', + operationType: 'static_value', + isBucketed: false, + scale: 'ratio', + params: { value: '23' }, + references: [], + }, + }) + ).toEqual({ + label: 'Static value: 23', + dataType: 'number', + operationType: 'static_value', + isBucketed: false, + scale: 'ratio', + params: { value: '23' }, + references: [], + }); + }); + + it('should create a static_value from passed arguments', () => { + expect( + staticValueOperation.buildColumn( + { + indexPattern: createMockedIndexPattern(), + layer: { columns: {}, columnOrder: [], indexPatternId: '' }, + }, + { value: '23' } + ) + ).toEqual({ + label: 'Static value: 23', + dataType: 'number', + operationType: 'static_value', + isBucketed: false, + scale: 'ratio', + params: { value: '23' }, + references: [], + }); + }); + + it('should prioritize passed arguments over previousColumn', () => { + expect( + staticValueOperation.buildColumn( + { + indexPattern: createMockedIndexPattern(), + layer: { columns: {}, columnOrder: [], indexPatternId: '' }, + previousColumn: { + label: 'Static value', + dataType: 'number', + operationType: 'static_value', + isBucketed: false, + scale: 'ratio', + params: { value: '23' }, + references: [], + }, + }, + { value: '53' } + ) + ).toEqual({ + label: 'Static value: 53', + dataType: 'number', + operationType: 'static_value', + isBucketed: false, + scale: 'ratio', + params: { value: '53' }, + references: [], + }); + }); + }); + + describe('paramEditor', () => { + const ParamEditor = staticValueOperation.paramEditor!; + it('should render current static_value', () => { + const updateLayerSpy = jest.fn(); + const instance = shallow( + + ); + + const input = instance.find('[data-test-subj="lns-indexPattern-static_value-input"]'); + + expect(input.prop('value')).toEqual('23'); + }); + + it('should update state on change', async () => { + const updateLayerSpy = jest.fn(); + const instance = mount( + + ); + + const input = instance + .find('[data-test-subj="lns-indexPattern-static_value-input"]') + .find(EuiFieldNumber); + + await act(async () => { + input.prop('onChange')!({ + currentTarget: { value: '27' }, + } as React.ChangeEvent); + }); + + instance.update(); + + expect(updateLayerSpy.mock.calls[0]).toEqual([expect.any(Function)]); + // check that the result of the setter call is correct + expect(updateLayerSpy.mock.calls[0][0](layer)).toEqual({ + ...layer, + columns: { + ...layer.columns, + col2: { + ...layer.columns.col2, + params: { + value: '27', + }, + label: 'Static value: 27', + }, + }, + }); + }); + + it('should not update on invalid input, but show invalid value locally', async () => { + const updateLayerSpy = jest.fn(); + const instance = mount( + + ); + + const input = instance + .find('[data-test-subj="lns-indexPattern-static_value-input"]') + .find(EuiFieldNumber); + + await act(async () => { + input.prop('onChange')!({ + currentTarget: { value: '' }, + } as React.ChangeEvent); + }); + + instance.update(); + + expect(updateLayerSpy).not.toHaveBeenCalled(); + expect( + instance + .find('[data-test-subj="lns-indexPattern-static_value-input"]') + .find(EuiFieldNumber) + .prop('value') + ).toEqual(''); + }); + }); +}); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/static_value.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/static_value.tsx new file mode 100644 index 0000000000000..a76c5f64d1750 --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/static_value.tsx @@ -0,0 +1,222 @@ +/* + * 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, { useCallback } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiFieldNumber, EuiFormLabel, EuiSpacer } from '@elastic/eui'; +import { OperationDefinition } from './index'; +import { ReferenceBasedIndexPatternColumn } from './column_types'; +import type { IndexPattern } from '../../types'; +import { useDebouncedValue } from '../../../shared_components'; +import { getFormatFromPreviousColumn, isValidNumber } from './helpers'; + +const defaultLabel = i18n.translate('xpack.lens.indexPattern.staticValueLabelDefault', { + defaultMessage: 'Static value', +}); + +const defaultValue = 100; + +function isEmptyValue(value: number | string | undefined) { + return value == null || value === ''; +} + +function ofName(value: number | string | undefined) { + if (isEmptyValue(value)) { + return defaultLabel; + } + return i18n.translate('xpack.lens.indexPattern.staticValueLabelWithValue', { + defaultMessage: 'Static value: {value}', + values: { value }, + }); +} + +export interface StaticValueIndexPatternColumn extends ReferenceBasedIndexPatternColumn { + operationType: 'static_value'; + params: { + value?: string; + format?: { + id: string; + params?: { + decimals: number; + }; + }; + }; +} + +export const staticValueOperation: OperationDefinition< + StaticValueIndexPatternColumn, + 'managedReference' +> = { + type: 'static_value', + displayName: defaultLabel, + getDefaultLabel: (column) => ofName(column.params.value), + input: 'managedReference', + hidden: true, + getDisabledStatus(indexPattern: IndexPattern) { + return undefined; + }, + getErrorMessage(layer, columnId) { + const column = layer.columns[columnId] as StaticValueIndexPatternColumn; + + return !isValidNumber(column.params.value) + ? [ + i18n.translate('xpack.lens.indexPattern.staticValueError', { + defaultMessage: 'The static value of {value} is not a valid number', + values: { value: column.params.value }, + }), + ] + : undefined; + }, + getPossibleOperation() { + return { + dataType: 'number', + isBucketed: false, + scale: 'ratio', + }; + }, + toExpression: (layer, columnId) => { + const currentColumn = layer.columns[columnId] as StaticValueIndexPatternColumn; + const params = currentColumn.params; + // TODO: improve this logic + const useDisplayLabel = currentColumn.label !== defaultLabel; + const label = isValidNumber(params.value) + ? useDisplayLabel + ? currentColumn.label + : params?.value ?? defaultLabel + : defaultLabel; + + return [ + { + type: 'function', + function: isValidNumber(params.value) ? 'mathColumn' : 'mapColumn', + arguments: { + id: [columnId], + name: [label || defaultLabel], + expression: [isValidNumber(params.value) ? params.value! : String(defaultValue)], + }, + }, + ]; + }, + buildColumn({ previousColumn, layer, indexPattern }, columnParams, operationDefinitionMap) { + const existingStaticValue = + previousColumn?.params && + 'value' in previousColumn.params && + isValidNumber(previousColumn.params.value) + ? previousColumn.params.value + : undefined; + const previousParams: StaticValueIndexPatternColumn['params'] = { + ...{ value: existingStaticValue }, + ...getFormatFromPreviousColumn(previousColumn), + ...columnParams, + }; + return { + label: ofName(previousParams.value), + dataType: 'number', + operationType: 'static_value', + isBucketed: false, + scale: 'ratio', + params: { ...previousParams, value: previousParams.value ?? String(defaultValue) }, + references: [], + }; + }, + isTransferable: (column) => { + return true; + }, + createCopy(layer, sourceId, targetId, indexPattern, operationDefinitionMap) { + const currentColumn = layer.columns[sourceId] as StaticValueIndexPatternColumn; + return { + ...layer, + columns: { + ...layer.columns, + [targetId]: { ...currentColumn }, + }, + }; + }, + + paramEditor: function StaticValueEditor({ + layer, + updateLayer, + currentColumn, + columnId, + activeData, + layerId, + indexPattern, + }) { + const onChange = useCallback( + (newValue) => { + // even if debounced it's triggering for empty string with the previous valid value + if (currentColumn.params.value === newValue) { + return; + } + // Because of upstream specific UX flows, we need fresh layer state here + // so need to use the updater pattern + updateLayer((newLayer) => { + const newColumn = newLayer.columns[columnId] as StaticValueIndexPatternColumn; + return { + ...newLayer, + columns: { + ...newLayer.columns, + [columnId]: { + ...newColumn, + label: newColumn?.customLabel ? newColumn.label : ofName(newValue), + params: { + ...newColumn.params, + value: newValue, + }, + }, + }, + }; + }); + }, + [columnId, updateLayer, currentColumn?.params?.value] + ); + + // Pick the data from the current activeData (to be used when the current operation is not static_value) + const activeDataValue = + activeData && + activeData[layerId] && + activeData[layerId]?.rows?.length === 1 && + activeData[layerId].rows[0][columnId]; + + const fallbackValue = + currentColumn?.operationType !== 'static_value' && activeDataValue != null + ? activeDataValue + : String(defaultValue); + + const { inputValue, handleInputChange } = useDebouncedValue( + { + value: currentColumn?.params?.value || fallbackValue, + onChange, + }, + { allowFalsyValue: true } + ); + + const onChangeHandler = useCallback( + (e: React.ChangeEvent) => { + const value = e.currentTarget.value; + handleInputChange(isValidNumber(value) ? value : undefined); + }, + [handleInputChange] + ); + + return ( +
+ + {i18n.translate('xpack.lens.indexPattern.staticValue.label', { + defaultMessage: 'Threshold value', + })} + + + +
+ ); + }, +}; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx index 9d5959c807490..f4b6e99e0a0d1 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx @@ -1077,10 +1077,10 @@ describe('terms', () => { }); it('returns fix action which calls field information endpoint and creates a pinned top values', async () => { const errorMessage = termsOperation.getErrorMessage!(layer, 'col1', indexPattern)![0]; - const fixAction = (typeof errorMessage === 'object' - ? errorMessage.fixAction!.newState - : undefined)!; - const coreMock = ({ + const fixAction = ( + typeof errorMessage === 'object' ? errorMessage.fixAction!.newState : undefined + )!; + const coreMock = { uiSettings: { get: () => undefined, }, @@ -1100,17 +1100,17 @@ describe('terms', () => { }) ), }, - } as unknown) as CoreStart; + } as unknown as CoreStart; const newLayer = await fixAction( coreMock, - ({ + { query: { language: 'kuery', query: 'a: b' }, filters: [], dateRange: { fromDate: '2020', toDate: '2021', }, - } as unknown) as FrameDatasourceAPI, + } as unknown as FrameDatasourceAPI, 'first' ); expect(newLayer.columns.col1).toEqual( diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts index 11c8206fee021..baacc7bb64d16 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts @@ -184,6 +184,7 @@ export function insertNewColumn({ targetGroup, shouldResetLabel, incompleteParams, + initialParams, }: ColumnChange): IndexPatternLayer { const operationDefinition = operationDefinitionMap[op]; @@ -197,7 +198,7 @@ export function insertNewColumn({ const baseOptions = { indexPattern, - previousColumn: { ...incompleteParams, ...layer.columns[columnId] }, + previousColumn: { ...incompleteParams, ...initialParams, ...layer.columns[columnId] }, }; if (operationDefinition.input === 'none' || operationDefinition.input === 'managedReference') { @@ -396,9 +397,17 @@ export function replaceColumn({ tempLayer = resetIncomplete(tempLayer, columnId); - if (previousDefinition.input === 'managedReference') { + if ( + previousDefinition.input === 'managedReference' && + operationDefinition.input !== previousDefinition.input + ) { // If the transition is incomplete, leave the managed state until it's finished. - tempLayer = deleteColumn({ layer: tempLayer, columnId, indexPattern }); + tempLayer = removeOrphanedColumns( + previousDefinition, + previousColumn, + tempLayer, + indexPattern + ); const hypotheticalLayer = insertNewColumn({ layer: tempLayer, @@ -641,21 +650,31 @@ function removeOrphanedColumns( previousDefinition: | OperationDefinition | OperationDefinition - | OperationDefinition, + | OperationDefinition + | OperationDefinition, previousColumn: IndexPatternColumn, tempLayer: IndexPatternLayer, indexPattern: IndexPattern ) { + let newLayer: IndexPatternLayer = tempLayer; + if (previousDefinition.input === 'managedReference') { + const [columnId] = + Object.entries(tempLayer.columns).find(([_, currColumn]) => currColumn === previousColumn) || + []; + if (columnId != null) { + newLayer = deleteColumn({ layer: tempLayer, columnId, indexPattern }); + } + } if (previousDefinition.input === 'fullReference') { (previousColumn as ReferenceBasedIndexPatternColumn).references.forEach((id: string) => { - tempLayer = deleteColumn({ + newLayer = deleteColumn({ layer: tempLayer, columnId: id, indexPattern, }); }); } - return tempLayer; + return newLayer; } export function canTransition({ diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts index 2ed6e2b3a7bcb..08136ed501cfc 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.test.ts @@ -378,6 +378,10 @@ describe('getOperationTypesForField', () => { "operationType": "formula", "type": "managedReference", }, + Object { + "operationType": "static_value", + "type": "managedReference", + }, ], }, Object { diff --git a/x-pack/plugins/lens/public/lens_inspector_service.ts b/x-pack/plugins/lens/public/lens_inspector_service.ts index 6266e7c21f792..d9573962f12d4 100644 --- a/x-pack/plugins/lens/public/lens_inspector_service.ts +++ b/x-pack/plugins/lens/public/lens_inspector_service.ts @@ -7,6 +7,7 @@ import type { Adapters, + InspectorOptions, Start as InspectorStartContract, } from '../../../../src/plugins/inspector/public'; @@ -16,7 +17,7 @@ export const getLensInspectorService = (inspector: InspectorStartContract) => { const adapters: Adapters = createDefaultInspectorAdapters(); return { adapters, - inspect: () => inspector.open(adapters), + inspect: (options?: InspectorOptions) => inspector.open(adapters, options), }; }; diff --git a/x-pack/plugins/lens/public/metric_visualization/metric_suggestions.test.ts b/x-pack/plugins/lens/public/metric_visualization/metric_suggestions.test.ts index 82e9a901d5041..8fceffa0db1fe 100644 --- a/x-pack/plugins/lens/public/metric_visualization/metric_suggestions.test.ts +++ b/x-pack/plugins/lens/public/metric_visualization/metric_suggestions.test.ts @@ -50,40 +50,40 @@ describe('metric_suggestions', () => { }; expect( - ([ - { - columns: [dateCol('a')], - isMultiRow: true, - layerId: 'l1', - changeType: 'unchanged', - }, - { - columns: [strCol('foo'), strCol('bar')], - isMultiRow: true, - layerId: 'l1', - changeType: 'unchanged', - }, - { - layerId: 'l1', - isMultiRow: true, - columns: [numCol('bar')], - changeType: 'unchanged', - }, - { - columns: [unknownCol(), numCol('bar')], - isMultiRow: true, - layerId: 'l1', - changeType: 'unchanged', - }, - { - columns: [numCol('bar'), numCol('baz')], - isMultiRow: false, - layerId: 'l1', - changeType: 'unchanged', - }, - ] as TableSuggestion[]).map((table) => - expect(getSuggestions({ table, keptLayerIds: ['l1'] })).toEqual([]) - ) + ( + [ + { + columns: [dateCol('a')], + isMultiRow: true, + layerId: 'l1', + changeType: 'unchanged', + }, + { + columns: [strCol('foo'), strCol('bar')], + isMultiRow: true, + layerId: 'l1', + changeType: 'unchanged', + }, + { + layerId: 'l1', + isMultiRow: true, + columns: [numCol('bar')], + changeType: 'unchanged', + }, + { + columns: [unknownCol(), numCol('bar')], + isMultiRow: true, + layerId: 'l1', + changeType: 'unchanged', + }, + { + columns: [numCol('bar'), numCol('baz')], + isMultiRow: false, + layerId: 'l1', + changeType: 'unchanged', + }, + ] as TableSuggestion[] + ).map((table) => expect(getSuggestions({ table, keptLayerIds: ['l1'] })).toEqual([])) ); }); diff --git a/x-pack/plugins/lens/public/mocks.tsx b/x-pack/plugins/lens/public/mocks.tsx index 8fbd263fe909e..402440f3302f6 100644 --- a/x-pack/plugins/lens/public/mocks.tsx +++ b/x-pack/plugins/lens/public/mocks.tsx @@ -59,9 +59,9 @@ export function mockDatasourceStates() { }; } -export function createMockVisualization(): jest.Mocked { +export function createMockVisualization(id = 'vis1'): jest.Mocked { return { - id: 'TEST_VIS', + id, clearLayer: jest.fn((state, _layerId) => state), removeLayer: jest.fn(), getLayerIds: jest.fn((_state) => ['layer1']), @@ -70,9 +70,9 @@ export function createMockVisualization(): jest.Mocked { visualizationTypes: [ { icon: 'empty', - id: 'TEST_VIS', + id, label: 'TEST', - groupLabel: 'TEST_VISGroup', + groupLabel: `${id}Group`, }, ], getVisualizationTypeId: jest.fn((_state) => 'empty'), @@ -122,7 +122,7 @@ export function createMockDatasource(id: string): DatasourceMock { return { id: 'mockindexpattern', clearLayer: jest.fn((state, _layerId) => state), - getDatasourceSuggestionsForField: jest.fn((_state, _item) => []), + getDatasourceSuggestionsForField: jest.fn((_state, _item, filterFn) => []), getDatasourceSuggestionsForVisualizeField: jest.fn((_state, _indexpatternId, _fieldName) => []), getDatasourceSuggestionsFromCurrentState: jest.fn((_state) => []), getPersistableState: jest.fn((x) => ({ @@ -205,7 +205,7 @@ export const lensPluginMock = { createStartContract, }; -export const defaultDoc = ({ +export const defaultDoc = { savedObjectId: '1234', title: 'An extremely cool default document!', expression: 'definitely a valid expression', @@ -219,7 +219,7 @@ export const defaultDoc = ({ visualization: {}, }, references: [{ type: 'index-pattern', id: '1', name: 'index-pattern-0' }], -} as unknown) as Document; +} as unknown as Document; export function createMockTimefilter() { const unsubscribe = jest.fn(); @@ -304,7 +304,7 @@ export function mockDataPlugin(sessionIdSubject = new Subject()) { getDefaultQuery: jest.fn(() => ({ query: '', language: 'lucene' })), }; } - return ({ + return { query: { filterManager: createMockFilterManager(), timefilter: { @@ -323,7 +323,7 @@ export function mockDataPlugin(sessionIdSubject = new Subject()) { fieldFormats: { deserialize: jest.fn(), }, - } as unknown) as DataPublicPluginStart; + } as unknown as DataPublicPluginStart; } export function makeDefaultServices( @@ -373,7 +373,7 @@ export function makeDefaultServices( }, }); attributeServiceMock.wrapAttributes = jest.fn().mockResolvedValue({ - savedObjectId: ((doc as unknown) as LensByReferenceInput).savedObjectId, + savedObjectId: (doc as unknown as LensByReferenceInput).savedObjectId, }); return attributeServiceMock; @@ -505,10 +505,10 @@ export const mountWithProvider = async ( let instance: ReactWrapper = {} as ReactWrapper; await act(async () => { - instance = mount(component, ({ + instance = mount(component, { wrappingComponent, ...restOptions, - } as unknown) as ReactWrapper); + } as unknown as ReactWrapper); }); return { instance, lensStore }; }; diff --git a/x-pack/plugins/lens/public/persistence/filter_references.ts b/x-pack/plugins/lens/public/persistence/filter_references.ts index 05958ace282ca..d080e2157c3d3 100644 --- a/x-pack/plugins/lens/public/persistence/filter_references.ts +++ b/x-pack/plugins/lens/public/persistence/filter_references.ts @@ -9,9 +9,10 @@ import { Filter } from '@kbn/es-query'; import { SavedObjectReference } from 'kibana/public'; import { PersistableFilter } from '../../common'; -export function extractFilterReferences( - filters: Filter[] -): { persistableFilters: PersistableFilter[]; references: SavedObjectReference[] } { +export function extractFilterReferences(filters: Filter[]): { + persistableFilters: PersistableFilter[]; + references: SavedObjectReference[]; +} { const references: SavedObjectReference[] = []; const persistableFilters = filters.map((filterRow, i) => { if (!filterRow.meta || !filterRow.meta.index) { diff --git a/x-pack/plugins/lens/public/persistence/saved_object_store.test.ts b/x-pack/plugins/lens/public/persistence/saved_object_store.test.ts index 29fc88a81d182..9d6b120653d79 100644 --- a/x-pack/plugins/lens/public/persistence/saved_object_store.test.ts +++ b/x-pack/plugins/lens/public/persistence/saved_object_store.test.ts @@ -17,7 +17,7 @@ describe('LensStore', () => { return { client, - store: new SavedObjectIndexStore((client as unknown) as SavedObjectsClientContract), + store: new SavedObjectIndexStore(client as unknown as SavedObjectsClientContract), }; } diff --git a/x-pack/plugins/lens/public/persistence/saved_object_store.ts b/x-pack/plugins/lens/public/persistence/saved_object_store.ts index b8615b8852b1a..bec604a0f7cfa 100644 --- a/x-pack/plugins/lens/public/persistence/saved_object_store.ts +++ b/x-pack/plugins/lens/public/persistence/saved_object_store.ts @@ -55,7 +55,7 @@ export class SavedObjectIndexStore implements SavedObjectStore { const { savedObjectId, type, references, ...rest } = vis; // TODO: SavedObjectAttributes should support this kind of object, // remove this workaround when SavedObjectAttributes is updated. - const attributes = (rest as unknown) as SavedObjectAttributes; + const attributes = rest as unknown as SavedObjectAttributes; const result = await this.client.create( DOC_TYPE, diff --git a/x-pack/plugins/lens/public/pie_visualization/get_legend_action.test.tsx b/x-pack/plugins/lens/public/pie_visualization/get_legend_action.test.tsx index 67e57dadd4935..91886c6cf19d6 100644 --- a/x-pack/plugins/lens/public/pie_visualization/get_legend_action.test.tsx +++ b/x-pack/plugins/lens/public/pie_visualization/get_legend_action.test.tsx @@ -36,12 +36,12 @@ describe('getLegendAction', function () { wrapperProps = { color: 'rgb(109, 204, 177)', label: 'Bar', - series: ([ + series: [ { specId: 'donut', key: 'Bar', }, - ] as unknown) as SeriesIdentifier[], + ] as unknown as SeriesIdentifier[], }; }); @@ -55,12 +55,12 @@ describe('getLegendAction', function () { const newProps = { ...wrapperProps, label: 'Hi', - series: ([ + series: [ { specId: 'donut', key: 'Hi', }, - ] as unknown) as SeriesIdentifier[], + ] as unknown as SeriesIdentifier[], }; wrapper = mountWithIntl(); expect(wrapper.find(EuiPopover).length).toBe(1); diff --git a/x-pack/plugins/lens/public/pie_visualization/render_function.test.tsx b/x-pack/plugins/lens/public/pie_visualization/render_function.test.tsx index 209d7ff652ea1..33e9154235147 100644 --- a/x-pack/plugins/lens/public/pie_visualization/render_function.test.tsx +++ b/x-pack/plugins/lens/public/pie_visualization/render_function.test.tsx @@ -147,7 +147,7 @@ describe('PieVisualization component', () => { ); (component.find(Partition).prop('layers')![1].shape!.fillColor as NodeColorAccessor)( - ({ + { dataName: 'third', depth: 2, parent: { @@ -173,7 +173,7 @@ describe('PieVisualization component', () => { }, value: 41, sortIndex: 2, - } as unknown) as ShapeTreeNode, + } as unknown as ShapeTreeNode, 0, [] as HierarchyOfArrays ); diff --git a/x-pack/plugins/lens/public/pie_visualization/render_function.tsx b/x-pack/plugins/lens/public/pie_visualization/render_function.tsx index 34476f4e3062e..e79066b0145d2 100644 --- a/x-pack/plugins/lens/public/pie_visualization/render_function.tsx +++ b/x-pack/plugins/lens/public/pie_visualization/render_function.tsx @@ -253,7 +253,7 @@ export function PieComponent( { meta: { type: 'number' }, } ) - ).toEqual(-100); + ).toEqual(0); }); - it('returns epsilon when metric is 0 without fallback', () => { + it('returns 0 when metric value is 0', () => { expect( getSliceValue( { a: 'Cat', b: 'Home', c: 0 }, @@ -46,7 +46,20 @@ describe('render helpers', () => { meta: { type: 'number' }, } ) - ).toEqual(Number.EPSILON); + ).toEqual(0); + }); + + it('returns 0 when metric value is infinite', () => { + expect( + getSliceValue( + { a: 'Cat', b: 'Home', c: Number.POSITIVE_INFINITY }, + { + id: 'c', + name: 'C', + meta: { type: 'number' }, + } + ) + ).toEqual(0); }); }); diff --git a/x-pack/plugins/lens/public/pie_visualization/render_helpers.ts b/x-pack/plugins/lens/public/pie_visualization/render_helpers.ts index 4a5587feaf271..d2858efa90153 100644 --- a/x-pack/plugins/lens/public/pie_visualization/render_helpers.ts +++ b/x-pack/plugins/lens/public/pie_visualization/render_helpers.ts @@ -10,10 +10,8 @@ import { Datatable, DatatableColumn } from 'src/plugins/expressions/public'; import { LensFilterEvent } from '../types'; export function getSliceValue(d: Datum, metricColumn: DatatableColumn) { - if (typeof d[metricColumn.id] === 'number' && d[metricColumn.id] !== 0) { - return d[metricColumn.id]; - } - return Number.EPSILON; + const value = d[metricColumn.id]; + return Number.isFinite(value) && value >= 0 ? value : 0; } export function getFilterContext( diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index 5326927d2c6c5..7891b5990989c 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -233,10 +233,10 @@ export class LensPlugin { const getPresentationUtilContext = () => startServices().plugins.presentationUtil.ContextProvider; - const ensureDefaultIndexPattern = async () => { + const ensureDefaultDataView = async () => { // make sure a default index pattern exists // if not, the page will be redirected to management and visualize won't be rendered - await startServices().plugins.data.indexPatterns.ensureDefaultIndexPattern(); + await startServices().plugins.data.indexPatterns.ensureDefaultDataView(); }; core.application.register({ @@ -261,7 +261,7 @@ export class LensPlugin { const frameStart = this.editorFrameService!.start(coreStart, deps); this.stopReportManager = stopReportManager; - await ensureDefaultIndexPattern(); + await ensureDefaultDataView(); return mountApp(core, params, { createEditorFrame: frameStart.createInstance, attributeService: getLensAttributeService(coreStart, deps), diff --git a/x-pack/plugins/lens/public/shared_components/coloring/color_stops.test.tsx b/x-pack/plugins/lens/public/shared_components/coloring/color_stops.test.tsx index 574d7fa7719ae..a27b6efb39075 100644 --- a/x-pack/plugins/lens/public/shared_components/coloring/color_stops.test.tsx +++ b/x-pack/plugins/lens/public/shared_components/coloring/color_stops.test.tsx @@ -138,9 +138,9 @@ describe('Color Stops component', () => { .find('[data-test-subj="my-test_dynamicColoring_stop_value_0"]') .first(); act(() => { - firstStopValueInput.prop('onChange')!(({ + firstStopValueInput.prop('onChange')!({ target: { value: ' 90' }, - } as unknown) as React.ChangeEvent); + } as unknown as React.ChangeEvent); }); component = component.update(); diff --git a/x-pack/plugins/lens/public/shared_components/coloring/palette_configuration.test.tsx b/x-pack/plugins/lens/public/shared_components/coloring/palette_configuration.test.tsx index 33f6ac379cd80..120975b5f3859 100644 --- a/x-pack/plugins/lens/public/shared_components/coloring/palette_configuration.test.tsx +++ b/x-pack/plugins/lens/public/shared_components/coloring/palette_configuration.test.tsx @@ -85,10 +85,12 @@ describe('palette panel', () => { }); function changePaletteIn(instance: ReactWrapper, newPaletteName: string) { - return ((instance - .find('[data-test-subj="lnsPalettePanel_dynamicColoring_palette_picker"]') - .at(1) - .prop('onChange') as unknown) as (value: string) => void)?.(newPaletteName); + return ( + instance + .find('[data-test-subj="lnsPalettePanel_dynamicColoring_palette_picker"]') + .at(1) + .prop('onChange') as unknown as (value: string) => void + )?.(newPaletteName); } it('should show only dynamic coloring enabled palette + custom option', () => { diff --git a/x-pack/plugins/lens/public/shared_components/coloring/utils.ts b/x-pack/plugins/lens/public/shared_components/coloring/utils.ts index 413e3708e9c9b..182e502563cd0 100644 --- a/x-pack/plugins/lens/public/shared_components/coloring/utils.ts +++ b/x-pack/plugins/lens/public/shared_components/coloring/utils.ts @@ -7,7 +7,7 @@ import chroma from 'chroma-js'; import { PaletteOutput, PaletteRegistry } from 'src/plugins/charts/public'; -import { euiLightVars, euiDarkVars } from '@kbn/ui-shared-deps/theme'; +import { euiLightVars, euiDarkVars } from '@kbn/ui-shared-deps-src/theme'; import { isColorDark } from '@elastic/eui'; import type { Datatable } from 'src/plugins/expressions/public'; import { diff --git a/x-pack/plugins/lens/public/shared_components/debounced_value.ts b/x-pack/plugins/lens/public/shared_components/debounced_value.ts index fa8fc22dedd57..412199a371f1f 100644 --- a/x-pack/plugins/lens/public/shared_components/debounced_value.ts +++ b/x-pack/plugins/lens/public/shared_components/debounced_value.ts @@ -11,6 +11,10 @@ import { debounce } from 'lodash'; /** * Debounces value changes and updates inputValue on root state changes if no debounced changes * are in flight because the user is currently modifying the value. + * + * * allowFalsyValue: update upstream with all falsy values but null or undefined + * + * When testing this function mock the "debounce" function in lodash (see this module test for an example) */ export const useDebouncedValue = ( diff --git a/x-pack/plugins/lens/public/shared_components/index.ts b/x-pack/plugins/lens/public/shared_components/index.ts index c200a18a25caf..f947ce699dce4 100644 --- a/x-pack/plugins/lens/public/shared_components/index.ts +++ b/x-pack/plugins/lens/public/shared_components/index.ts @@ -14,3 +14,4 @@ export * from './coloring'; export { useDebouncedValue } from './debounced_value'; export * from './helpers'; export { LegendActionPopover } from './legend_action_popover'; +export * from './static_header'; diff --git a/x-pack/plugins/lens/public/shared_components/static_header.tsx b/x-pack/plugins/lens/public/shared_components/static_header.tsx new file mode 100644 index 0000000000000..2250358234a7f --- /dev/null +++ b/x-pack/plugins/lens/public/shared_components/static_header.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiTitle, IconType } from '@elastic/eui'; + +export const StaticHeader = ({ label, icon }: { label: string; icon?: IconType }) => { + return ( + + {icon && ( + + {' '} + + )} + + +
{label}
+
+
+
+ ); +}; diff --git a/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.test.tsx b/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.test.tsx index 8093100c5f1e8..342490e5360a5 100644 --- a/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.test.tsx +++ b/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.test.tsx @@ -97,7 +97,7 @@ describe('init_middleware', () => { await act(async () => { await loadInitial(lensStore, storeDeps, { redirectCallback: jest.fn(), - initialInput: ({ savedObjectId: defaultSavedObjectId } as unknown) as LensEmbeddableInput, + initialInput: { savedObjectId: defaultSavedObjectId } as unknown as LensEmbeddableInput, }); }); expect(datasourceMap.testDatasource.initialize).toHaveBeenCalled(); @@ -169,7 +169,7 @@ describe('init_middleware', () => { await act(async () => { await loadInitial(lensStore, storeDeps, { redirectCallback: jest.fn(), - initialInput: ({ savedObjectId: defaultSavedObjectId } as unknown) as LensEmbeddableInput, + initialInput: { savedObjectId: defaultSavedObjectId } as unknown as LensEmbeddableInput, }); }); expect(datasourceMap.testDatasource.initialize).toHaveBeenCalled(); @@ -264,9 +264,9 @@ describe('init_middleware', () => { await act(async () => { await loadInitial(lensStore, storeDeps, { redirectCallback: jest.fn(), - initialInput: ({ + initialInput: { savedObjectId: defaultSavedObjectId, - } as unknown) as LensEmbeddableInput, + } as unknown as LensEmbeddableInput, emptyState, }); }); @@ -297,18 +297,18 @@ describe('init_middleware', () => { await act(async () => { await loadInitial(lensStore, storeDeps, { redirectCallback: jest.fn(), - initialInput: ({ + initialInput: { savedObjectId: defaultSavedObjectId, - } as unknown) as LensEmbeddableInput, + } as unknown as LensEmbeddableInput, }); }); await act(async () => { await loadInitial(lensStore, storeDeps, { redirectCallback: jest.fn(), - initialInput: ({ + initialInput: { savedObjectId: defaultSavedObjectId, - } as unknown) as LensEmbeddableInput, + } as unknown as LensEmbeddableInput, }); }); @@ -317,7 +317,7 @@ describe('init_middleware', () => { await act(async () => { await loadInitial(lensStore, storeDeps, { redirectCallback: jest.fn(), - initialInput: ({ savedObjectId: '5678' } as unknown) as LensEmbeddableInput, + initialInput: { savedObjectId: '5678' } as unknown as LensEmbeddableInput, }); }); @@ -338,9 +338,9 @@ describe('init_middleware', () => { await act(async () => { await loadInitial(lensStore, storeDeps, { redirectCallback, - initialInput: ({ + initialInput: { savedObjectId: defaultSavedObjectId, - } as unknown) as LensEmbeddableInput, + } as unknown as LensEmbeddableInput, }); }); expect(lensServices.attributeService.unwrapAttributes).toHaveBeenCalledWith({ @@ -366,9 +366,9 @@ describe('init_middleware', () => { await act(async () => { await loadInitial(lensStore, storeDeps, { redirectCallback: jest.fn(), - initialInput: ({ + initialInput: { savedObjectId: defaultSavedObjectId, - } as unknown) as LensEmbeddableInput, + } as unknown as LensEmbeddableInput, history: { location: { search: '?search', @@ -394,9 +394,9 @@ describe('init_middleware', () => { await act(async () => { await loadInitial(lensStore, storeDeps, { redirectCallback: jest.fn(), - initialInput: ({ + initialInput: { savedObjectId: defaultSavedObjectId, - } as unknown) as LensEmbeddableInput, + } as unknown as LensEmbeddableInput, }); }); diff --git a/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.ts b/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.ts index 8ae6e58019c91..7db03a17a3a8f 100644 --- a/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.ts +++ b/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.ts @@ -42,10 +42,10 @@ export const getPersisted = async ({ const result = await attributeService.unwrapAttributes(initialInput); if (!result) { return { - doc: ({ + doc: { ...initialInput, type: LENS_EMBEDDABLE_TYPE, - } as unknown) as Document, + } as unknown as Document, sharingSavedObjectProps: { outcome: 'exactMatch', }, @@ -149,7 +149,7 @@ export function loadInitial( datasourceMap, datasourceStates, visualizationMap, - activeVisualizationId: Object.keys(visualizationMap)[0] || null, + activeVisualization: visualizationMap?.[Object.keys(visualizationMap)[0]] || null, visualizationState: null, visualizeTriggerFieldContext: initialContext, }); diff --git a/x-pack/plugins/lens/public/state_management/time_range_middleware.test.ts b/x-pack/plugins/lens/public/state_management/time_range_middleware.test.ts index 8718f79f94782..ddf50f6fd0d82 100644 --- a/x-pack/plugins/lens/public/state_management/time_range_middleware.test.ts +++ b/x-pack/plugins/lens/public/state_management/time_range_middleware.test.ts @@ -107,7 +107,7 @@ function createMockTimefilter() { } function makeDefaultData(): jest.Mocked { - return ({ + return { query: { filterManager: createMockFilterManager(), timefilter: { @@ -123,7 +123,7 @@ function makeDefaultData(): jest.Mocked { nowProvider: { get: jest.fn(), }, - } as unknown) as DataPublicPluginStart; + } as unknown as DataPublicPluginStart; } const createMiddleware = (data: DataPublicPluginStart) => { diff --git a/x-pack/plugins/lens/public/state_management/types.ts b/x-pack/plugins/lens/public/state_management/types.ts index 33f311a982f05..c15b29d0040d0 100644 --- a/x-pack/plugins/lens/public/state_management/types.ts +++ b/x-pack/plugins/lens/public/state_management/types.ts @@ -46,9 +46,7 @@ export interface LensAppState extends EditorFrameState { sharingSavedObjectProps?: Omit; } -export type DispatchSetState = ( - state: Partial -) => { +export type DispatchSetState = (state: Partial) => { payload: Partial; type: string; }; diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index 844541cd2ad3e..cf6634c200d55 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -234,7 +234,11 @@ export interface Datasource { toExpression: (state: T, layerId: string) => ExpressionAstExpression | string | null; - getDatasourceSuggestionsForField: (state: T, field: unknown) => Array>; + getDatasourceSuggestionsForField: ( + state: T, + field: unknown, + filterFn: (layerId: string) => boolean + ) => Array>; getDatasourceSuggestionsForVisualizeField: ( state: T, indexPatternId: string, @@ -326,6 +330,8 @@ export type DatasourceDimensionProps = SharedDimensionProps & { onRemove?: (accessor: string) => void; state: T; activeData?: Record; + invalid?: boolean; + invalidMessage?: string; }; // The only way a visualization has to restrict the query building @@ -335,6 +341,7 @@ export type DatasourceDimensionEditorProps = DatasourceDimensionPro newState: Parameters>[0], publishToVisualization?: { isDimensionComplete?: boolean; + forceRender?: boolean; } ) => void; core: Pick; @@ -343,6 +350,8 @@ export type DatasourceDimensionEditorProps = DatasourceDimensionPro toggleFullscreen: () => void; isFullscreen: boolean; layerType: LayerType | undefined; + supportStaticValue: boolean; + supportFieldFormat?: boolean; }; export type DatasourceDimensionTriggerProps = DatasourceDimensionProps; @@ -434,7 +443,7 @@ export interface VisualizationToolbarProps { export type VisualizationDimensionEditorProps = VisualizationConfigProps & { groupId: string; accessor: string; - setState: (newState: T) => void; + setState(newState: T | ((currState: T) => T)): void; panelRef: MutableRefObject; }; @@ -466,13 +475,16 @@ export type VisualizationDimensionGroupConfig = SharedDimensionProps & { // this dimension group in the hierarchy. If not specified, the position of the dimension in the array is used. specified nesting // orders are always higher in the hierarchy than non-specified ones. nestingOrder?: number; + // some type of layers can produce groups even if invalid. Keep this information to visually show the user that. + invalid?: boolean; + invalidMessage?: string; }; interface VisualizationDimensionChangeProps { layerId: string; columnId: string; prevState: T; - frame: Pick; + frame: Pick; } /** @@ -652,9 +664,11 @@ export interface Visualization { /** * For consistency across different visualizations, the dimension configuration UI is standardized */ - getConfiguration: ( - props: VisualizationConfigProps - ) => { groups: VisualizationDimensionGroupConfig[]; supportStaticValue?: boolean }; + getConfiguration: (props: VisualizationConfigProps) => { + groups: VisualizationDimensionGroupConfig[]; + supportStaticValue?: boolean; + supportFieldFormat?: boolean; + }; /** * Header rendered as layer title This can be used for both static and dynamic content lioke diff --git a/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap index 69307d3d90cab..6326d8680757e 100644 --- a/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap +++ b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap @@ -5,6 +5,7 @@ exports[`xy_expression XYChart component it renders area 1`] = ` renderer="canvas" > , - formatFactory?: FormatFactory -): GroupsConfiguration { - const series: { auto: FormattedMetric[]; left: FormattedMetric[]; right: FormattedMetric[] } = { +export function groupAxesByType(layers: XYLayerConfig[], tables?: Record) { + const series: { + auto: FormattedMetric[]; + left: FormattedMetric[]; + right: FormattedMetric[]; + bottom: FormattedMetric[]; + } = { auto: [], left: [], right: [], + bottom: [], }; layers?.forEach((layer) => { @@ -89,6 +90,16 @@ export function getAxesConfiguration( series.right.push(currentSeries); } }); + return series; +} + +export function getAxesConfiguration( + layers: XYLayerConfig[], + shouldRotate: boolean, + tables?: Record, + formatFactory?: FormatFactory +): GroupsConfiguration { + const series = groupAxesByType(layers, tables); const axisGroups: GroupsConfiguration = []; diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx index 671db4653a88a..77364ac72a046 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx @@ -974,7 +974,6 @@ describe('xy_expression', () => { }} /> ); - wrapper.find(Settings).first().prop('onBrushEnd')!({ x: [1585757732783, 1585758880838] }); expect(onSelectRange).toHaveBeenCalledWith({ @@ -1075,6 +1074,22 @@ describe('xy_expression', () => { expect(wrapper.find(Settings).first().prop('onBrushEnd')).toBeUndefined(); }); + test('allowBrushingLastHistogramBucket is true for date histogram data', () => { + const { args } = sampleArgs(); + + const wrapper = mountWithIntl( + + ); + expect(wrapper.find(Settings).at(0).prop('allowBrushingLastHistogramBucket')).toEqual(true); + }); + test('onElementClick returns correct context data', () => { const geometry: GeometryValue = { x: 5, y: 1, accessor: 'y1', mark: null, datum: {} }; const series = { @@ -1335,6 +1350,36 @@ describe('xy_expression', () => { }); }); + test('allowBrushingLastHistogramBucket should be fakse for ordinal data', () => { + const { args, data } = sampleArgs(); + + const wrapper = mountWithIntl( + + ); + + expect(wrapper.find(Settings).at(0).prop('allowBrushingLastHistogramBucket')).toEqual(false); + }); + test('onElementClick is not triggering event on non-interactive mode', () => { const { args, data } = sampleArgs(); diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.tsx index b7f1a9dabf3c7..863289c31bba4 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/expression.tsx @@ -59,6 +59,7 @@ import { getAxesConfiguration, GroupsConfiguration, validateExtent } from './axe import { getColorAssignments } from './color_assignment'; import { getXDomain, XyEndzones } from './x_domain'; import { getLegendAction } from './get_legend_action'; +import { ThresholdAnnotations } from './expression_thresholds'; declare global { interface Window { @@ -251,6 +252,7 @@ export function XYChart({ const icon: IconType = layers.length > 0 ? getIconForSeriesType(layers[0].seriesType) : 'bar'; return ; } + const thresholdLayers = layers.filter((layer) => layer.layerType === layerTypes.THRESHOLD); // use formatting hint of first x axis column to format ticks const xAxisColumn = data.tables[filteredLayers[0].layerId].columns.find( @@ -516,6 +518,7 @@ export function XYChart({ boundary: document.getElementById('app-fixed-viewport') ?? undefined, headerFormatter: (d) => safeXAccessorLabelRenderer(d.value), }} + allowBrushingLastHistogramBucket={Boolean(isTimeViz)} rotation={shouldRotate ? 90 : 0} xDomain={xDomain} onBrushEnd={interactive ? brushHandler : undefined} @@ -831,6 +834,20 @@ export function XYChart({ } }) )} + {thresholdLayers.length ? ( + groupId === 'left')?.formatter, + right: yAxesConfiguration.find(({ groupId }) => groupId === 'right')?.formatter, + bottom: xAxisFormatter, + }} + /> + ) : null} ); } diff --git a/x-pack/plugins/lens/public/xy_visualization/expression_thresholds.tsx b/x-pack/plugins/lens/public/xy_visualization/expression_thresholds.tsx new file mode 100644 index 0000000000000..171e2f1cfba9e --- /dev/null +++ b/x-pack/plugins/lens/public/xy_visualization/expression_thresholds.tsx @@ -0,0 +1,193 @@ +/* + * 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 { groupBy } from 'lodash'; +import { EuiIcon } from '@elastic/eui'; +import { RectAnnotation, AnnotationDomainType, LineAnnotation } from '@elastic/charts'; +import type { PaletteRegistry, SeriesLayer } from 'src/plugins/charts/public'; +import type { FieldFormat } from 'src/plugins/field_formats/common'; +import type { LayerArgs } from '../../common/expressions'; +import type { LensMultiTable } from '../../common/types'; +import type { ColorAssignments } from './color_assignment'; + +export const ThresholdAnnotations = ({ + thresholdLayers, + data, + colorAssignments, + formatters, + paletteService, + syncColors, +}: { + thresholdLayers: LayerArgs[]; + data: LensMultiTable; + colorAssignments: ColorAssignments; + formatters: Record<'left' | 'right' | 'bottom', FieldFormat | undefined>; + paletteService: PaletteRegistry; + syncColors: boolean; +}) => { + return ( + <> + {thresholdLayers.flatMap((thresholdLayer) => { + if (!thresholdLayer.yConfig) { + return []; + } + const { columnToLabel, palette, yConfig: yConfigs, layerId } = thresholdLayer; + const columnToLabelMap: Record = columnToLabel + ? JSON.parse(columnToLabel) + : {}; + const table = data.tables[layerId]; + const colorAssignment = colorAssignments[palette.name]; + + const row = table.rows[0]; + + const yConfigByValue = yConfigs.sort( + ({ forAccessor: idA }, { forAccessor: idB }) => row[idA] - row[idB] + ); + + const groupedByDirection = groupBy(yConfigByValue, 'fill'); + + return yConfigByValue.flatMap((yConfig, i) => { + // Find the formatter for the given axis + const groupId = + yConfig.axisMode === 'bottom' + ? undefined + : yConfig.axisMode === 'right' + ? 'right' + : 'left'; + + const formatter = formatters[groupId || 'bottom']; + + const seriesLayers: SeriesLayer[] = [ + { + name: columnToLabelMap[yConfig.forAccessor], + totalSeriesAtDepth: colorAssignment.totalSeriesCount, + rankAtDepth: colorAssignment.getRank( + thresholdLayer, + String(yConfig.forAccessor), + String(yConfig.forAccessor) + ), + }, + ]; + const defaultColor = paletteService.get(palette.name).getCategoricalColor( + seriesLayers, + { + maxDepth: 1, + behindText: false, + totalSeries: colorAssignment.totalSeriesCount, + syncColors, + }, + palette.params + ); + + const props = { + groupId, + marker: yConfig.icon ? : undefined, + }; + const annotations = []; + + const dashStyle = + yConfig.lineStyle === 'dashed' + ? [(yConfig.lineWidth || 1) * 3, yConfig.lineWidth || 1] + : yConfig.lineStyle === 'dotted' + ? [yConfig.lineWidth || 1, yConfig.lineWidth || 1] + : undefined; + + const sharedStyle = { + strokeWidth: yConfig.lineWidth || 1, + stroke: (yConfig.color || defaultColor) ?? '#f00', + dash: dashStyle, + }; + + annotations.push( + ({ + dataValue: row[yConfig.forAccessor], + header: columnToLabelMap[yConfig.forAccessor], + details: formatter?.convert(row[yConfig.forAccessor]) || row[yConfig.forAccessor], + }))} + domainType={ + yConfig.axisMode === 'bottom' + ? AnnotationDomainType.XDomain + : AnnotationDomainType.YDomain + } + style={{ + line: { + ...sharedStyle, + opacity: 1, + }, + }} + /> + ); + + if (yConfig.fill && yConfig.fill !== 'none') { + const isFillAbove = yConfig.fill === 'above'; + const indexFromSameType = groupedByDirection[yConfig.fill].findIndex( + ({ forAccessor }) => forAccessor === yConfig.forAccessor + ); + const shouldCheckNextThreshold = + indexFromSameType < groupedByDirection[yConfig.fill].length - 1; + annotations.push( + { + if (yConfig.axisMode === 'bottom') { + return { + coordinates: { + x0: isFillAbove ? row[yConfig.forAccessor] : undefined, + y0: undefined, + x1: isFillAbove + ? shouldCheckNextThreshold + ? row[ + groupedByDirection[yConfig.fill!][indexFromSameType + 1].forAccessor + ] + : undefined + : row[yConfig.forAccessor], + y1: undefined, + }, + header: columnToLabelMap[yConfig.forAccessor], + details: + formatter?.convert(row[yConfig.forAccessor]) || row[yConfig.forAccessor], + }; + } + return { + coordinates: { + x0: undefined, + y0: isFillAbove ? row[yConfig.forAccessor] : undefined, + x1: undefined, + y1: isFillAbove + ? shouldCheckNextThreshold + ? row[ + groupedByDirection[yConfig.fill!][indexFromSameType + 1].forAccessor + ] + : undefined + : row[yConfig.forAccessor], + }, + header: columnToLabelMap[yConfig.forAccessor], + details: + formatter?.convert(row[yConfig.forAccessor]) || row[yConfig.forAccessor], + }; + })} + style={{ + ...sharedStyle, + fill: (yConfig.color || defaultColor) ?? '#f00', + opacity: 0.1, + }} + /> + ); + } + return annotations; + }); + })} + + ); +}; diff --git a/x-pack/plugins/lens/public/xy_visualization/get_legend_action.test.tsx b/x-pack/plugins/lens/public/xy_visualization/get_legend_action.test.tsx index 700aaf91ad5cb..5d1b45e481499 100644 --- a/x-pack/plugins/lens/public/xy_visualization/get_legend_action.test.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/get_legend_action.test.tsx @@ -180,11 +180,11 @@ describe('getLegendAction', function () { wrapperProps = { color: 'rgb(109, 204, 177)', label: "Women's Accessories", - series: ([ + series: [ { seriesKeys: ["Women's Accessories", 'test'], }, - ] as unknown) as SeriesIdentifier[], + ] as unknown as SeriesIdentifier[], }; }); @@ -197,11 +197,11 @@ describe('getLegendAction', function () { it('is rendered if row does not exist', () => { const newProps = { ...wrapperProps, - series: ([ + series: [ { seriesKeys: ['test', 'b'], }, - ] as unknown) as SeriesIdentifier[], + ] as unknown as SeriesIdentifier[], }; wrapper = mountWithIntl(); expect(wrapper).toEqual({}); @@ -211,11 +211,11 @@ describe('getLegendAction', function () { it('is rendered if layer is detected', () => { const newProps = { ...wrapperProps, - series: ([ + series: [ { seriesKeys: ["Women's Accessories", 'b'], }, - ] as unknown) as SeriesIdentifier[], + ] as unknown as SeriesIdentifier[], }; wrapper = mountWithIntl(); expect(wrapper.find(EuiPopover).length).toBe(1); diff --git a/x-pack/plugins/lens/public/xy_visualization/state_helpers.ts b/x-pack/plugins/lens/public/xy_visualization/state_helpers.ts index e3b16f5981f88..4edf7fdf5e512 100644 --- a/x-pack/plugins/lens/public/xy_visualization/state_helpers.ts +++ b/x-pack/plugins/lens/public/xy_visualization/state_helpers.ts @@ -18,6 +18,14 @@ export function isHorizontalSeries(seriesType: SeriesType) { ); } +export function isPercentageSeries(seriesType: SeriesType) { + return ( + seriesType === 'bar_percentage_stacked' || + seriesType === 'bar_horizontal_percentage_stacked' || + seriesType === 'area_percentage_stacked' + ); +} + export function isHorizontalChart(layers: Array<{ seriesType: SeriesType }>) { return layers.every((l) => isHorizontalSeries(l.seriesType)); } diff --git a/x-pack/plugins/lens/public/xy_visualization/threshold_helpers.tsx b/x-pack/plugins/lens/public/xy_visualization/threshold_helpers.tsx new file mode 100644 index 0000000000000..ec47350709473 --- /dev/null +++ b/x-pack/plugins/lens/public/xy_visualization/threshold_helpers.tsx @@ -0,0 +1,165 @@ +/* + * 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 { layerTypes } from '../../common'; +import type { XYLayerConfig, YConfig } from '../../common/expressions'; +import { Datatable } from '../../../../../src/plugins/expressions/public'; +import type { DatasourcePublicAPI, FramePublicAPI } from '../types'; +import { groupAxesByType } from './axes_configuration'; +import { isPercentageSeries } from './state_helpers'; +import type { XYState } from './types'; +import { checkScaleOperation } from './visualization_helpers'; + +export interface ThresholdBase { + label: 'x' | 'yRight' | 'yLeft'; +} + +/** + * Return the threshold layers groups to show based on multiple criteria: + * * what groups are current defined in data layers + * * what existing threshold are currently defined in data thresholds + */ +export function getGroupsToShow( + thresholdLayers: T[], + state: XYState | undefined, + datasourceLayers: Record, + tables: Record | undefined +): Array { + if (!state) { + return []; + } + const dataLayers = state.layers.filter( + ({ layerType = layerTypes.DATA }) => layerType === layerTypes.DATA + ); + const groupsAvailable = getGroupsAvailableInData(dataLayers, datasourceLayers, tables); + return thresholdLayers + .filter(({ label, config }: T) => groupsAvailable[label] || config?.length) + .map((layer) => ({ ...layer, valid: groupsAvailable[layer.label] })); +} + +/** + * Returns the threshold layers groups to show based on what groups are current defined in data layers. + */ +export function getGroupsRelatedToData( + thresholdLayers: T[], + state: XYState | undefined, + datasourceLayers: Record, + tables: Record | undefined +): T[] { + if (!state) { + return []; + } + const dataLayers = state.layers.filter( + ({ layerType = layerTypes.DATA }) => layerType === layerTypes.DATA + ); + const groupsAvailable = getGroupsAvailableInData(dataLayers, datasourceLayers, tables); + return thresholdLayers.filter(({ label }: T) => groupsAvailable[label]); +} +/** + * Returns a dictionary with the groups filled in all the data layers + */ +export function getGroupsAvailableInData( + dataLayers: XYState['layers'], + datasourceLayers: Record, + tables: Record | undefined +) { + const hasNumberHistogram = dataLayers.some( + checkScaleOperation('interval', 'number', datasourceLayers) + ); + const { right, left } = groupAxesByType(dataLayers, tables); + return { + x: dataLayers.some(({ xAccessor }) => xAccessor != null) && hasNumberHistogram, + yLeft: left.length > 0, + yRight: right.length > 0, + }; +} + +export function getStaticValue( + dataLayers: XYState['layers'], + groupId: 'x' | 'yLeft' | 'yRight', + { activeData }: Pick, + layerHasNumberHistogram: (layer: XYLayerConfig) => boolean +) { + const fallbackValue = 100; + if (!activeData) { + return fallbackValue; + } + + // filter and organize data dimensions into threshold groups + // now pick the columnId in the active data + const { dataLayer, accessor } = getAccessorCriteriaForGroup(groupId, dataLayers, activeData); + if (groupId === 'x' && dataLayer && !layerHasNumberHistogram(dataLayer)) { + return fallbackValue; + } + return ( + computeStaticValueForGroup( + dataLayer, + accessor, + activeData, + groupId !== 'x' // histogram axis should compute the min based on the current data + ) || fallbackValue + ); +} + +function getAccessorCriteriaForGroup( + groupId: 'x' | 'yLeft' | 'yRight', + dataLayers: XYState['layers'], + activeData: FramePublicAPI['activeData'] +) { + switch (groupId) { + case 'x': + const dataLayer = dataLayers.find(({ xAccessor }) => xAccessor); + return { + dataLayer, + accessor: dataLayer?.xAccessor, + }; + case 'yLeft': + const { left } = groupAxesByType(dataLayers, activeData); + return { + dataLayer: dataLayers.find(({ layerId }) => layerId === left[0]?.layer), + accessor: left[0]?.accessor, + }; + case 'yRight': + const { right } = groupAxesByType(dataLayers, activeData); + return { + dataLayer: dataLayers.find(({ layerId }) => layerId === right[0]?.layer), + accessor: right[0]?.accessor, + }; + } +} + +function computeStaticValueForGroup( + dataLayer: XYLayerConfig | undefined, + accessorId: string | undefined, + activeData: NonNullable, + minZeroBased: boolean +) { + const defaultThresholdFactor = 3 / 4; + + if (dataLayer && accessorId) { + if (isPercentageSeries(dataLayer?.seriesType)) { + return defaultThresholdFactor; + } + const tableId = Object.keys(activeData).find((key) => + activeData[key].columns.some(({ id }) => id === accessorId) + ); + if (tableId) { + const columnMax = activeData[tableId].rows.reduce( + (max, row) => Math.max(row[accessorId], max), + -Infinity + ); + const columnMin = activeData[tableId].rows.reduce( + (max, row) => Math.min(row[accessorId], max), + Infinity + ); + // Custom axis bounds can go below 0, so consider also lower values than 0 + const finalMinValue = minZeroBased ? Math.min(0, columnMin) : columnMin; + const interval = columnMax - finalMinValue; + return Number((finalMinValue + interval * defaultThresholdFactor).toFixed(2)); + } + } +} diff --git a/x-pack/plugins/lens/public/xy_visualization/to_expression.test.ts b/x-pack/plugins/lens/public/xy_visualization/to_expression.test.ts index 80808a4055f26..2a4941995054b 100644 --- a/x-pack/plugins/lens/public/xy_visualization/to_expression.test.ts +++ b/x-pack/plugins/lens/public/xy_visualization/to_expression.test.ts @@ -81,24 +81,26 @@ describe('#toExpression', () => { it('should default the fitting function to None', () => { expect( - (xyVisualization.toExpression( - { - legend: { position: Position.Bottom, isVisible: true }, - valueLabels: 'hide', - preferredSeriesType: 'bar', - layers: [ - { - layerId: 'first', - layerType: layerTypes.DATA, - seriesType: 'area', - splitAccessor: 'd', - xAccessor: 'a', - accessors: ['b', 'c'], - }, - ], - }, - frame.datasourceLayers - ) as Ast).chain[0].arguments.fittingFunction[0] + ( + xyVisualization.toExpression( + { + legend: { position: Position.Bottom, isVisible: true }, + valueLabels: 'hide', + preferredSeriesType: 'bar', + layers: [ + { + layerId: 'first', + layerType: layerTypes.DATA, + seriesType: 'area', + splitAccessor: 'd', + xAccessor: 'a', + accessors: ['b', 'c'], + }, + ], + }, + frame.datasourceLayers + ) as Ast + ).chain[0].arguments.fittingFunction[0] ).toEqual('None'); }); diff --git a/x-pack/plugins/lens/public/xy_visualization/to_expression.ts b/x-pack/plugins/lens/public/xy_visualization/to_expression.ts index 5290e0298ae5e..aa99aa9b7b316 100644 --- a/x-pack/plugins/lens/public/xy_visualization/to_expression.ts +++ b/x-pack/plugins/lens/public/xy_visualization/to_expression.ts @@ -322,6 +322,10 @@ export const buildExpression = ( forAccessor: [yConfig.forAccessor], axisMode: yConfig.axisMode ? [yConfig.axisMode] : [], color: yConfig.color ? [yConfig.color] : [], + lineStyle: yConfig.lineStyle ? [yConfig.lineStyle] : [], + lineWidth: yConfig.lineWidth ? [yConfig.lineWidth] : [], + fill: [yConfig.fill || 'none'], + icon: yConfig.icon ? [yConfig.icon] : [], }, }, ], diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts b/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts index 14a13fbb0f3bb..8907db4954f99 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts +++ b/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts @@ -15,6 +15,7 @@ import { createMockDatasource, createMockFramePublicAPI } from '../mocks'; import { LensIconChartBar } from '../assets/chart_bar'; import { chartPluginMock } from '../../../../../src/plugins/charts/public/mocks'; import { fieldFormatsServiceMock } from '../../../../../src/plugins/field_formats/public/mocks'; +import { Datatable } from 'src/plugins/expressions'; function exampleState(): State { return { @@ -216,8 +217,8 @@ describe('xy_visualization', () => { }); describe('#getSupportedLayers', () => { - it('should return a single layer type', () => { - expect(xyVisualization.getSupportedLayers()).toHaveLength(1); + it('should return a double layer types', () => { + expect(xyVisualization.getSupportedLayers()).toHaveLength(2); }); it('should return the icon for the visualization type', () => { @@ -317,6 +318,42 @@ describe('xy_visualization', () => { accessors: [], }); }); + + it('should add a dimension to a threshold layer', () => { + expect( + xyVisualization.setDimension({ + frame, + prevState: { + ...exampleState(), + layers: [ + { + layerId: 'threshold', + layerType: layerTypes.THRESHOLD, + seriesType: 'line', + accessors: [], + }, + ], + }, + layerId: 'threshold', + groupId: 'xThreshold', + columnId: 'newCol', + }).layers[0] + ).toEqual({ + layerId: 'threshold', + layerType: layerTypes.THRESHOLD, + seriesType: 'line', + accessors: ['newCol'], + yConfig: [ + { + axisMode: 'bottom', + forAccessor: 'newCol', + icon: undefined, + lineStyle: 'solid', + lineWidth: 1, + }, + ], + }); + }); }); describe('#removeDimension', () => { @@ -504,6 +541,300 @@ describe('xy_visualization', () => { expect(ops.filter(filterOperations).map((x) => x.dataType)).toEqual(['number']); }); + describe('thresholds', () => { + beforeEach(() => { + frame.datasourceLayers = { + first: mockDatasource.publicAPIMock, + threshold: mockDatasource.publicAPIMock, + }; + }); + + function getStateWithBaseThreshold(): State { + return { + ...exampleState(), + layers: [ + { + layerId: 'first', + layerType: layerTypes.DATA, + seriesType: 'area', + splitAccessor: undefined, + xAccessor: undefined, + accessors: ['a'], + }, + { + layerId: 'threshold', + layerType: layerTypes.THRESHOLD, + seriesType: 'line', + accessors: [], + yConfig: [{ axisMode: 'left', forAccessor: 'a' }], + }, + ], + }; + } + + it('should support static value', () => { + const state = getStateWithBaseThreshold(); + state.layers[0].accessors = []; + state.layers[1].yConfig = undefined; + + expect( + xyVisualization.getConfiguration({ + state: getStateWithBaseThreshold(), + frame, + layerId: 'threshold', + }).supportStaticValue + ).toBeTruthy(); + }); + + it('should return no threshold groups for a empty data layer', () => { + const state = getStateWithBaseThreshold(); + state.layers[0].accessors = []; + state.layers[1].yConfig = undefined; + + const options = xyVisualization.getConfiguration({ + state, + frame, + layerId: 'threshold', + }).groups; + + expect(options).toHaveLength(0); + }); + + it('should return a group for the vertical left axis', () => { + const options = xyVisualization.getConfiguration({ + state: getStateWithBaseThreshold(), + frame, + layerId: 'threshold', + }).groups; + + expect(options).toHaveLength(1); + expect(options[0].groupId).toBe('yThresholdLeft'); + }); + + it('should return a group for the vertical right axis', () => { + const state = getStateWithBaseThreshold(); + state.layers[0].yConfig = [{ axisMode: 'right', forAccessor: 'a' }]; + state.layers[1].yConfig![0].axisMode = 'right'; + + const options = xyVisualization.getConfiguration({ + state, + frame, + layerId: 'threshold', + }).groups; + + expect(options).toHaveLength(1); + expect(options[0].groupId).toBe('yThresholdRight'); + }); + + it('should compute no groups for thresholds when the only data accessor available is a date histogram', () => { + const state = getStateWithBaseThreshold(); + state.layers[0].xAccessor = 'b'; + state.layers[0].accessors = []; + state.layers[1].yConfig = []; // empty the configuration + // set the xAccessor as date_histogram + frame.datasourceLayers.threshold.getOperationForColumnId = jest.fn((accessor) => { + if (accessor === 'b') { + return { + dataType: 'date', + isBucketed: true, + scale: 'interval', + label: 'date_histogram', + }; + } + return null; + }); + + const options = xyVisualization.getConfiguration({ + state, + frame, + layerId: 'threshold', + }).groups; + + expect(options).toHaveLength(0); + }); + + it('should mark horizontal group is invalid when xAccessor is changed to a date histogram', () => { + const state = getStateWithBaseThreshold(); + state.layers[0].xAccessor = 'b'; + state.layers[0].accessors = []; + state.layers[1].yConfig![0].axisMode = 'bottom'; + // set the xAccessor as date_histogram + frame.datasourceLayers.threshold.getOperationForColumnId = jest.fn((accessor) => { + if (accessor === 'b') { + return { + dataType: 'date', + isBucketed: true, + scale: 'interval', + label: 'date_histogram', + }; + } + return null; + }); + + const options = xyVisualization.getConfiguration({ + state, + frame, + layerId: 'threshold', + }).groups; + + expect(options[0]).toEqual( + expect.objectContaining({ + invalid: true, + groupId: 'xThreshold', + }) + ); + }); + + it('should return groups in a specific order (left, right, bottom)', () => { + const state = getStateWithBaseThreshold(); + state.layers[0].xAccessor = 'c'; + state.layers[0].accessors = ['a', 'b']; + // invert them on purpose + state.layers[0].yConfig = [ + { axisMode: 'right', forAccessor: 'b' }, + { axisMode: 'left', forAccessor: 'a' }, + ]; + state.layers[1].yConfig = [ + { forAccessor: 'c', axisMode: 'bottom' }, + { forAccessor: 'b', axisMode: 'right' }, + { forAccessor: 'a', axisMode: 'left' }, + ]; + // set the xAccessor as number histogram + frame.datasourceLayers.threshold.getOperationForColumnId = jest.fn((accessor) => { + if (accessor === 'c') { + return { + dataType: 'number', + isBucketed: true, + scale: 'interval', + label: 'histogram', + }; + } + return null; + }); + + const [left, right, bottom] = xyVisualization.getConfiguration({ + state, + frame, + layerId: 'threshold', + }).groups; + + expect(left.groupId).toBe('yThresholdLeft'); + expect(right.groupId).toBe('yThresholdRight'); + expect(bottom.groupId).toBe('xThreshold'); + }); + + it('should ignore terms operation for xAccessor', () => { + const state = getStateWithBaseThreshold(); + state.layers[0].xAccessor = 'b'; + state.layers[0].accessors = []; + state.layers[1].yConfig = []; // empty the configuration + // set the xAccessor as top values + frame.datasourceLayers.threshold.getOperationForColumnId = jest.fn((accessor) => { + if (accessor === 'b') { + return { + dataType: 'string', + isBucketed: true, + scale: 'ordinal', + label: 'top values', + }; + } + return null; + }); + + const options = xyVisualization.getConfiguration({ + state, + frame, + layerId: 'threshold', + }).groups; + + expect(options).toHaveLength(0); + }); + + it('should mark horizontal group is invalid when accessor is changed to a terms operation', () => { + const state = getStateWithBaseThreshold(); + state.layers[0].xAccessor = 'b'; + state.layers[0].accessors = []; + state.layers[1].yConfig![0].axisMode = 'bottom'; + // set the xAccessor as date_histogram + frame.datasourceLayers.threshold.getOperationForColumnId = jest.fn((accessor) => { + if (accessor === 'b') { + return { + dataType: 'string', + isBucketed: true, + scale: 'ordinal', + label: 'top values', + }; + } + return null; + }); + + const options = xyVisualization.getConfiguration({ + state, + frame, + layerId: 'threshold', + }).groups; + + expect(options[0]).toEqual( + expect.objectContaining({ + invalid: true, + groupId: 'xThreshold', + }) + ); + }); + + it('differ vertical axis if the formatters are not compatibles between each other', () => { + const tables: Record = { + first: { + type: 'datatable', + rows: [], + columns: [ + { + id: 'xAccessorId', + name: 'horizontal axis', + meta: { + type: 'date', + params: { params: { id: 'date', params: { pattern: 'HH:mm' } } }, + }, + }, + { + id: 'yAccessorId', + name: 'left axis', + meta: { + type: 'number', + params: { id: 'number' }, + }, + }, + { + id: 'yAccessorId2', + name: 'right axis', + meta: { + type: 'number', + params: { id: 'bytes' }, + }, + }, + ], + }, + }; + + const state = getStateWithBaseThreshold(); + state.layers[0].accessors = ['yAccessorId', 'yAccessorId2']; + state.layers[1].yConfig = []; // empty the configuration + + const options = xyVisualization.getConfiguration({ + state, + frame: { ...frame, activeData: tables }, + layerId: 'threshold', + }).groups; + + expect(options).toEqual( + expect.arrayContaining([ + expect.objectContaining({ groupId: 'yThresholdLeft' }), + expect.objectContaining({ groupId: 'yThresholdRight' }), + ]) + ); + }); + }); + describe('color assignment', () => { function callConfig(layerConfigOverride: Partial) { const baseState = exampleState(); @@ -926,18 +1257,18 @@ describe('xy_visualization', () => { }; datasourceLayers.first.getOperationForColumnId = jest.fn((id: string) => id === 'a' - ? (({ + ? ({ dataType: 'date', scale: 'interval', - } as unknown) as Operation) + } as unknown as Operation) : null ); datasourceLayers.second.getOperationForColumnId = jest.fn((id: string) => id === 'e' - ? (({ + ? ({ dataType: 'number', scale: 'interval', - } as unknown) as Operation) + } as unknown as Operation) : null ); expect( @@ -982,18 +1313,18 @@ describe('xy_visualization', () => { }; datasourceLayers.first.getOperationForColumnId = jest.fn((id: string) => id === 'a' - ? (({ + ? ({ dataType: 'date', scale: 'interval', - } as unknown) as Operation) + } as unknown as Operation) : null ); datasourceLayers.second.getOperationForColumnId = jest.fn((id: string) => id === 'e' - ? (({ + ? ({ dataType: 'string', scale: 'ordinal', - } as unknown) as Operation) + } as unknown as Operation) : null ); expect( diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx index 026c2827cedbd..ed1cc015806c5 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { uniq } from 'lodash'; +import { groupBy, uniq } from 'lodash'; import { render } from 'react-dom'; import { Position } from '@elastic/charts'; import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; @@ -14,15 +14,10 @@ import { i18n } from '@kbn/i18n'; import { PaletteRegistry } from 'src/plugins/charts/public'; import { FieldFormatsStart } from 'src/plugins/field_formats/public'; import { getSuggestions } from './xy_suggestions'; -import { XyToolbar, DimensionEditor, LayerHeader } from './xy_config_panel'; -import type { - Visualization, - OperationMetadata, - VisualizationType, - AccessorConfig, - DatasourcePublicAPI, -} from '../types'; -import { State, visualizationTypes, XYState } from './types'; +import { XyToolbar, DimensionEditor } from './xy_config_panel'; +import { LayerHeader } from './xy_config_panel/layer_header'; +import type { Visualization, OperationMetadata, VisualizationType, AccessorConfig } from '../types'; +import { State, visualizationTypes } from './types'; import { SeriesType, XYLayerConfig } from '../../common/expressions'; import { LayerType, layerTypes } from '../../common'; import { isHorizontalChart } from './state_helpers'; @@ -32,6 +27,19 @@ import { LensIconChartMixedXy } from '../assets/chart_mixed_xy'; import { LensIconChartBarHorizontal } from '../assets/chart_bar_horizontal'; import { getAccessorColorConfig, getColorAssignments } from './color_assignment'; import { getColumnToLabelMap } from './state_helpers'; +import { LensIconChartBarThreshold } from '../assets/chart_bar_threshold'; +import { generateId } from '../id_generator'; +import { + getGroupsAvailableInData, + getGroupsRelatedToData, + getGroupsToShow, + getStaticValue, +} from './threshold_helpers'; +import { + checkScaleOperation, + checkXAccessorCompatibility, + getAxisName, +} from './visualization_helpers'; const defaultIcon = LensIconChartBarStacked; const defaultSeriesType = 'bar_stacked'; @@ -186,6 +194,39 @@ export const getXyVisualization = ({ }, getSupportedLayers(state, frame) { + const thresholdGroupIds = [ + { + id: 'yThresholdLeft', + label: 'yLeft' as const, + }, + { + id: 'yThresholdRight', + label: 'yRight' as const, + }, + { + id: 'xThreshold', + label: 'x' as const, + }, + ]; + + const dataLayers = + state?.layers.filter(({ layerType = layerTypes.DATA }) => layerType === layerTypes.DATA) || + []; + const filledDataLayers = dataLayers.filter( + ({ accessors, xAccessor }) => accessors.length || xAccessor + ); + const layerHasNumberHistogram = checkScaleOperation( + 'interval', + 'number', + frame?.datasourceLayers || {} + ); + const thresholdGroups = getGroupsRelatedToData( + thresholdGroupIds, + state, + frame?.datasourceLayers || {}, + frame?.activeData + ); + const layers = [ { type: layerTypes.DATA, @@ -194,6 +235,36 @@ export const getXyVisualization = ({ }), icon: LensIconChartMixedXy, }, + { + type: layerTypes.THRESHOLD, + label: i18n.translate('xpack.lens.xyChart.addThresholdLayerLabel', { + defaultMessage: 'Add threshold layer', + }), + icon: LensIconChartBarThreshold, + disabled: + !filledDataLayers.length || + (!dataLayers.some(layerHasNumberHistogram) && + dataLayers.every(({ accessors }) => !accessors.length)), + tooltipContent: filledDataLayers.length + ? undefined + : i18n.translate('xpack.lens.xyChart.addThresholdLayerLabelDisabledHelp', { + defaultMessage: 'Add some data to enable threshold layer', + }), + initialDimensions: state + ? thresholdGroups.map(({ id, label }) => ({ + groupId: id, + columnId: generateId(), + dataType: 'number', + label: getAxisName(label, { isHorizontal: isHorizontalChart(state?.layers || []) }), + staticValue: getStaticValue( + dataLayers, + label, + { activeData: frame?.activeData }, + layerHasNumberHistogram + ), + })) + : undefined, + }, ]; return layers; @@ -233,8 +304,70 @@ export const getXyVisualization = ({ const isDataLayer = !layer.layerType || layer.layerType === layerTypes.DATA; if (!isDataLayer) { + const idToIndex = sortedAccessors.reduce>((memo, id, index) => { + memo[id] = index; + return memo; + }, {}); + const { bottom, left, right } = groupBy( + [...(layer.yConfig || [])].sort( + ({ forAccessor: forA }, { forAccessor: forB }) => idToIndex[forA] - idToIndex[forB] + ), + ({ axisMode }) => { + return axisMode; + } + ); + const groupsToShow = getGroupsToShow( + [ + // When a threshold layer panel is added, a static threshold should automatically be included by default + // in the first available axis, in the following order: vertical left, vertical right, horizontal. + { + config: left, + id: 'yThresholdLeft', + label: 'yLeft', + dataTestSubj: 'lnsXY_yThresholdLeftPanel', + }, + { + config: right, + id: 'yThresholdRight', + label: 'yRight', + dataTestSubj: 'lnsXY_yThresholdRightPanel', + }, + { + config: bottom, + id: 'xThreshold', + label: 'x', + dataTestSubj: 'lnsXY_xThresholdPanel', + }, + ], + state, + frame.datasourceLayers, + frame?.activeData + ); return { - groups: [], + supportFieldFormat: false, + supportStaticValue: true, + // Each thresholds layer panel will have sections for each available axis + // (horizontal axis, vertical axis left, vertical axis right). + // Only axes that support numeric thresholds should be shown + groups: groupsToShow.map(({ config = [], id, label, dataTestSubj, valid }) => ({ + groupId: id, + groupLabel: getAxisName(label, { isHorizontal }), + accessors: config.map(({ forAccessor, color }) => ({ + columnId: forAccessor, + color: color || mappedAccessors.find(({ columnId }) => columnId === forAccessor)?.color, + triggerIcon: 'color', + })), + filterOperations: isNumericMetric, + supportsMoreColumns: true, + required: false, + enableDimensionEditor: true, + dataTestSubj, + invalid: !valid, + invalidMessage: i18n.translate('xpack.lens.configure.invalidThresholdDimension', { + defaultMessage: + 'This threshold is assigned to an axis that no longer exists. You may move this threshold to another available axis or remove it.', + }), + })), }; } @@ -305,6 +438,30 @@ export const getXyVisualization = ({ newLayer.splitAccessor = columnId; } + if (newLayer.layerType === layerTypes.THRESHOLD) { + newLayer.accessors = [...newLayer.accessors.filter((a) => a !== columnId), columnId]; + const hasYConfig = newLayer.yConfig?.some(({ forAccessor }) => forAccessor === columnId); + if (!hasYConfig) { + newLayer.yConfig = [ + ...(newLayer.yConfig || []), + // TODO: move this + // add a default config if none is available + { + forAccessor: columnId, + axisMode: + groupId === 'xThreshold' + ? 'bottom' + : groupId === 'yThresholdRight' + ? 'right' + : 'left', + icon: undefined, + lineStyle: 'solid', + lineWidth: 1, + }, + ]; + } + } + return { ...prevState, layers: prevState.layers.map((l) => (l.layerId === layerId ? newLayer : l)), @@ -331,7 +488,24 @@ export const getXyVisualization = ({ newLayer.yConfig = newLayer.yConfig.filter(({ forAccessor }) => forAccessor !== columnId); } - const newLayers = prevState.layers.map((l) => (l.layerId === layerId ? newLayer : l)); + let newLayers = prevState.layers.map((l) => (l.layerId === layerId ? newLayer : l)); + // // check if there's any threshold layer and pull it off if all data layers have no dimensions set + const layersByType = groupBy(newLayers, ({ layerType }) => layerType); + // // check for data layers if they all still have xAccessors + const groupsAvailable = getGroupsAvailableInData( + layersByType[layerTypes.DATA], + frame.datasourceLayers, + frame?.activeData + ); + if ( + (Object.keys(groupsAvailable) as Array<'x' | 'yLeft' | 'yRight'>).every( + (id) => !groupsAvailable[id] + ) + ) { + newLayers = newLayers.filter( + ({ layerType, accessors }) => layerType === layerTypes.DATA || accessors.length + ); + } return { ...prevState, @@ -510,19 +684,6 @@ function validateLayersForDimension( }; } -function getAxisName(axis: 'x' | 'y', { isHorizontal }: { isHorizontal: boolean }) { - const vertical = i18n.translate('xpack.lens.xyChart.verticalAxisLabel', { - defaultMessage: 'Vertical axis', - }); - const horizontal = i18n.translate('xpack.lens.xyChart.horizontalAxisLabel', { - defaultMessage: 'Horizontal axis', - }); - if (axis === 'x') { - return isHorizontal ? vertical : horizontal; - } - return isHorizontal ? horizontal : vertical; -} - // i18n ids cannot be dynamically generated, hence the function below function getMessageIdsForDimension(dimension: string, layers: number[], isHorizontal: boolean) { const layersList = layers.map((i: number) => i + 1).join(', '); @@ -566,76 +727,6 @@ function newLayerState( }; } -// min requirement for the bug: -// * 2 or more layers -// * at least one with date histogram -// * at least one with interval function -function checkXAccessorCompatibility( - state: XYState, - datasourceLayers: Record -) { - const errors = []; - const hasDateHistogramSet = state.layers.some( - checkScaleOperation('interval', 'date', datasourceLayers) - ); - const hasNumberHistogram = state.layers.some( - checkScaleOperation('interval', 'number', datasourceLayers) - ); - const hasOrdinalAxis = state.layers.some( - checkScaleOperation('ordinal', undefined, datasourceLayers) - ); - if (state.layers.length > 1 && hasDateHistogramSet && hasNumberHistogram) { - errors.push({ - shortMessage: i18n.translate('xpack.lens.xyVisualization.dataTypeFailureXShort', { - defaultMessage: `Wrong data type for {axis}.`, - values: { - axis: getAxisName('x', { isHorizontal: isHorizontalChart(state.layers) }), - }, - }), - longMessage: i18n.translate('xpack.lens.xyVisualization.dataTypeFailureXLong', { - defaultMessage: `Data type mismatch for the {axis}. Cannot mix date and number interval types.`, - values: { - axis: getAxisName('x', { isHorizontal: isHorizontalChart(state.layers) }), - }, - }), - }); - } - if (state.layers.length > 1 && (hasDateHistogramSet || hasNumberHistogram) && hasOrdinalAxis) { - errors.push({ - shortMessage: i18n.translate('xpack.lens.xyVisualization.dataTypeFailureXShort', { - defaultMessage: `Wrong data type for {axis}.`, - values: { - axis: getAxisName('x', { isHorizontal: isHorizontalChart(state.layers) }), - }, - }), - longMessage: i18n.translate('xpack.lens.xyVisualization.dataTypeFailureXOrdinalLong', { - defaultMessage: `Data type mismatch for the {axis}, use a different function.`, - values: { - axis: getAxisName('x', { isHorizontal: isHorizontalChart(state.layers) }), - }, - }), - }); - } - return errors; -} - -function checkScaleOperation( - scaleType: 'ordinal' | 'interval' | 'ratio', - dataType: 'date' | 'number' | 'string' | undefined, - datasourceLayers: Record -) { - return (layer: XYLayerConfig) => { - const datasourceAPI = datasourceLayers[layer.layerId]; - if (!layer.xAccessor) { - return false; - } - const operation = datasourceAPI?.getOperationForColumnId(layer.xAccessor); - return Boolean( - operation && (!dataType || operation.dataType === dataType) && operation.scale === scaleType - ); - }; -} - function getLayersByType(state: State, byType?: string) { return state.layers.filter(({ layerType = layerTypes.DATA }) => byType ? layerType === byType : true diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization_helpers.tsx b/x-pack/plugins/lens/public/xy_visualization/visualization_helpers.tsx new file mode 100644 index 0000000000000..22c3c7e895323 --- /dev/null +++ b/x-pack/plugins/lens/public/xy_visualization/visualization_helpers.tsx @@ -0,0 +1,116 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { DatasourcePublicAPI } from '../types'; +import { XYState } from './types'; +import { isHorizontalChart } from './state_helpers'; +import { XYLayerConfig } from '../../common/expressions'; + +export function getAxisName( + axis: 'x' | 'y' | 'yLeft' | 'yRight', + { isHorizontal }: { isHorizontal: boolean } +) { + const vertical = i18n.translate('xpack.lens.xyChart.verticalAxisLabel', { + defaultMessage: 'Vertical axis', + }); + const horizontal = i18n.translate('xpack.lens.xyChart.horizontalAxisLabel', { + defaultMessage: 'Horizontal axis', + }); + if (axis === 'x') { + return isHorizontal ? vertical : horizontal; + } + if (axis === 'y') { + return isHorizontal ? horizontal : vertical; + } + const verticalLeft = i18n.translate('xpack.lens.xyChart.verticalLeftAxisLabel', { + defaultMessage: 'Vertical left axis', + }); + const verticalRight = i18n.translate('xpack.lens.xyChart.verticalRightAxisLabel', { + defaultMessage: 'Vertical right axis', + }); + const horizontalTop = i18n.translate('xpack.lens.xyChart.horizontalLeftAxisLabel', { + defaultMessage: 'Horizontal top axis', + }); + const horizontalBottom = i18n.translate('xpack.lens.xyChart.horizontalRightAxisLabel', { + defaultMessage: 'Horizontal bottom axis', + }); + if (axis === 'yLeft') { + return isHorizontal ? horizontalTop : verticalLeft; + } + return isHorizontal ? horizontalBottom : verticalRight; +} + +// min requirement for the bug: +// * 2 or more layers +// * at least one with date histogram +// * at least one with interval function +export function checkXAccessorCompatibility( + state: XYState, + datasourceLayers: Record +) { + const errors = []; + const hasDateHistogramSet = state.layers.some( + checkScaleOperation('interval', 'date', datasourceLayers) + ); + const hasNumberHistogram = state.layers.some( + checkScaleOperation('interval', 'number', datasourceLayers) + ); + const hasOrdinalAxis = state.layers.some( + checkScaleOperation('ordinal', undefined, datasourceLayers) + ); + if (state.layers.length > 1 && hasDateHistogramSet && hasNumberHistogram) { + errors.push({ + shortMessage: i18n.translate('xpack.lens.xyVisualization.dataTypeFailureXShort', { + defaultMessage: `Wrong data type for {axis}.`, + values: { + axis: getAxisName('x', { isHorizontal: isHorizontalChart(state.layers) }), + }, + }), + longMessage: i18n.translate('xpack.lens.xyVisualization.dataTypeFailureXLong', { + defaultMessage: `Data type mismatch for the {axis}. Cannot mix date and number interval types.`, + values: { + axis: getAxisName('x', { isHorizontal: isHorizontalChart(state.layers) }), + }, + }), + }); + } + if (state.layers.length > 1 && (hasDateHistogramSet || hasNumberHistogram) && hasOrdinalAxis) { + errors.push({ + shortMessage: i18n.translate('xpack.lens.xyVisualization.dataTypeFailureXShort', { + defaultMessage: `Wrong data type for {axis}.`, + values: { + axis: getAxisName('x', { isHorizontal: isHorizontalChart(state.layers) }), + }, + }), + longMessage: i18n.translate('xpack.lens.xyVisualization.dataTypeFailureXOrdinalLong', { + defaultMessage: `Data type mismatch for the {axis}, use a different function.`, + values: { + axis: getAxisName('x', { isHorizontal: isHorizontalChart(state.layers) }), + }, + }), + }); + } + return errors; +} + +export function checkScaleOperation( + scaleType: 'ordinal' | 'interval' | 'ratio', + dataType: 'date' | 'number' | 'string' | undefined, + datasourceLayers: Record +) { + return (layer: XYLayerConfig) => { + const datasourceAPI = datasourceLayers[layer.layerId]; + if (!layer.xAccessor) { + return false; + } + const operation = datasourceAPI?.getOperationForColumnId(layer.xAccessor); + return Boolean( + operation && (!dataType || operation.dataType === dataType) && operation.scale === scaleType + ); + }; +} diff --git a/x-pack/plugins/lens/public/xy_visualization/axis_settings_popover.test.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/axis_settings_popover.test.tsx similarity index 98% rename from x-pack/plugins/lens/public/xy_visualization/axis_settings_popover.test.tsx rename to x-pack/plugins/lens/public/xy_visualization/xy_config_panel/axis_settings_popover.test.tsx index aa287795c8181..ebe0e536a4d77 100644 --- a/x-pack/plugins/lens/public/xy_visualization/axis_settings_popover.test.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/axis_settings_popover.test.tsx @@ -8,8 +8,8 @@ import React from 'react'; import { shallowWithIntl as shallow } from '@kbn/test/jest'; import { AxisSettingsPopover, AxisSettingsPopoverProps } from './axis_settings_popover'; -import { ToolbarPopover } from '../shared_components'; -import { layerTypes } from '../../common'; +import { ToolbarPopover } from '../../shared_components'; +import { layerTypes } from '../../../common'; describe('Axes Settings', () => { let props: AxisSettingsPopoverProps; diff --git a/x-pack/plugins/lens/public/xy_visualization/axis_settings_popover.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/axis_settings_popover.tsx similarity index 96% rename from x-pack/plugins/lens/public/xy_visualization/axis_settings_popover.tsx rename to x-pack/plugins/lens/public/xy_visualization/xy_config_panel/axis_settings_popover.tsx index 2285cd1a7a43a..e0a30bdb2c511 100644 --- a/x-pack/plugins/lens/public/xy_visualization/axis_settings_popover.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/axis_settings_popover.tsx @@ -20,15 +20,15 @@ import { EuiFieldNumber, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { XYLayerConfig, AxesSettingsConfig, AxisExtentConfig } from '../../common/expressions'; -import { ToolbarPopover, useDebouncedValue } from '../shared_components'; -import { isHorizontalChart } from './state_helpers'; -import { EuiIconAxisBottom } from '../assets/axis_bottom'; -import { EuiIconAxisLeft } from '../assets/axis_left'; -import { EuiIconAxisRight } from '../assets/axis_right'; -import { EuiIconAxisTop } from '../assets/axis_top'; -import { ToolbarButtonProps } from '../../../../../src/plugins/kibana_react/public'; -import { validateExtent } from './axes_configuration'; +import { XYLayerConfig, AxesSettingsConfig, AxisExtentConfig } from '../../../common/expressions'; +import { ToolbarPopover, useDebouncedValue } from '../../shared_components'; +import { isHorizontalChart } from '../state_helpers'; +import { EuiIconAxisBottom } from '../../assets/axis_bottom'; +import { EuiIconAxisLeft } from '../../assets/axis_left'; +import { EuiIconAxisRight } from '../../assets/axis_right'; +import { EuiIconAxisTop } from '../../assets/axis_top'; +import { ToolbarButtonProps } from '../../../../../../src/plugins/kibana_react/public'; +import { validateExtent } from '../axes_configuration'; type AxesSettingsConfigKeys = keyof AxesSettingsConfig; diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/color_picker.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/color_picker.tsx new file mode 100644 index 0000000000000..5a6458a4654d0 --- /dev/null +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/color_picker.tsx @@ -0,0 +1,175 @@ +/* + * 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 './xy_config_panel.scss'; +import React, { useMemo, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { debounce } from 'lodash'; +import { EuiFormRow, EuiColorPicker, EuiColorPickerProps, EuiToolTip, EuiIcon } from '@elastic/eui'; +import type { PaletteRegistry } from 'src/plugins/charts/public'; +import type { VisualizationDimensionEditorProps } from '../../types'; +import { State } from '../types'; +import { FormatFactory } from '../../../common'; +import { getSeriesColor } from '../state_helpers'; +import { getAccessorColorConfig, getColorAssignments } from '../color_assignment'; +import { getSortedAccessors } from '../to_expression'; +import { updateLayer } from '.'; +import { TooltipWrapper } from '../../shared_components'; + +const tooltipContent = { + auto: i18n.translate('xpack.lens.configPanel.color.tooltip.auto', { + defaultMessage: 'Lens automatically picks colors for you unless you specify a custom color.', + }), + custom: i18n.translate('xpack.lens.configPanel.color.tooltip.custom', { + defaultMessage: 'Clear the custom color to return to “Auto” mode.', + }), + disabled: i18n.translate('xpack.lens.configPanel.color.tooltip.disabled', { + defaultMessage: + 'Individual series cannot be custom colored when the layer includes a “Break down by.“', + }), +}; + +export const ColorPicker = ({ + state, + setState, + layerId, + accessor, + frame, + formatFactory, + paletteService, + label, + disableHelpTooltip, +}: VisualizationDimensionEditorProps & { + formatFactory: FormatFactory; + paletteService: PaletteRegistry; + label?: string; + disableHelpTooltip?: boolean; +}) => { + const index = state.layers.findIndex((l) => l.layerId === layerId); + const layer = state.layers[index]; + const disabled = Boolean(layer.splitAccessor); + + const overwriteColor = getSeriesColor(layer, accessor); + const currentColor = useMemo(() => { + if (overwriteColor || !frame.activeData) return overwriteColor; + + const datasource = frame.datasourceLayers[layer.layerId]; + const sortedAccessors: string[] = getSortedAccessors(datasource, layer); + + const colorAssignments = getColorAssignments( + state.layers, + { tables: frame.activeData }, + formatFactory + ); + const mappedAccessors = getAccessorColorConfig( + colorAssignments, + frame, + { + ...layer, + accessors: sortedAccessors.filter((sorted) => layer.accessors.includes(sorted)), + }, + paletteService + ); + + return mappedAccessors.find((a) => a.columnId === accessor)?.color || null; + }, [overwriteColor, frame, paletteService, state.layers, accessor, formatFactory, layer]); + + const [color, setColor] = useState(currentColor); + + const handleColor: EuiColorPickerProps['onChange'] = (text, output) => { + setColor(text); + if (output.isValid || text === '') { + updateColorInState(text, output); + } + }; + + const updateColorInState: EuiColorPickerProps['onChange'] = useMemo( + () => + debounce((text, output) => { + const newYConfigs = [...(layer.yConfig || [])]; + const existingIndex = newYConfigs.findIndex((yConfig) => yConfig.forAccessor === accessor); + if (existingIndex !== -1) { + if (text === '') { + newYConfigs[existingIndex] = { ...newYConfigs[existingIndex], color: undefined }; + } else { + newYConfigs[existingIndex] = { ...newYConfigs[existingIndex], color: output.hex }; + } + } else { + newYConfigs.push({ + forAccessor: accessor, + color: output.hex, + }); + } + setState(updateLayer(state, { ...layer, yConfig: newYConfigs }, index)); + }, 256), + [state, setState, layer, accessor, index] + ); + + const inputLabel = + label ?? + i18n.translate('xpack.lens.xyChart.seriesColor.label', { + defaultMessage: 'Series color', + }); + + const colorPicker = ( + + ); + + return ( + + + {inputLabel} + {!disableHelpTooltip && ( + <> + {''} + + + )} + + + } + > + {disabled ? ( + + {colorPicker} + + ) : ( + colorPicker + )} + + ); +}; diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/index.tsx similarity index 72% rename from x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx rename to x-pack/plugins/lens/public/xy_visualization/xy_config_panel/index.tsx index cd90fa52cd402..1427a3d28ea39 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/index.tsx @@ -6,24 +6,15 @@ */ import './xy_config_panel.scss'; -import React, { useMemo, useState, memo, useCallback } from 'react'; +import React, { memo, useCallback } from 'react'; import { i18n } from '@kbn/i18n'; import { Position, ScaleType, VerticalAlignment, HorizontalAlignment } from '@elastic/charts'; -import { debounce } from 'lodash'; import { EuiButtonGroup, EuiFlexGroup, EuiFlexItem, EuiFormRow, htmlIdGenerator, - EuiColorPicker, - EuiColorPickerProps, - EuiToolTip, - EuiIcon, - EuiPopover, - EuiSelectable, - EuiText, - EuiPopoverTitle, } from '@elastic/eui'; import type { PaletteRegistry } from 'src/plugins/charts/public'; import type { @@ -31,30 +22,34 @@ import type { VisualizationToolbarProps, VisualizationDimensionEditorProps, FramePublicAPI, -} from '../types'; -import { State, visualizationTypes, XYState } from './types'; -import type { FormatFactory } from '../../common'; +} from '../../types'; +import { State, visualizationTypes, XYState } from '../types'; +import type { FormatFactory } from '../../../common'; import { SeriesType, YAxisMode, AxesSettingsConfig, AxisExtentConfig, -} from '../../common/expressions'; -import { isHorizontalChart, isHorizontalSeries, getSeriesColor } from './state_helpers'; -import { trackUiEvent } from '../lens_ui_telemetry'; -import { LegendSettingsPopover } from '../shared_components'; +} from '../../../common/expressions'; +import { isHorizontalChart, isHorizontalSeries } from '../state_helpers'; +import { trackUiEvent } from '../../lens_ui_telemetry'; +import { LegendSettingsPopover } from '../../shared_components'; import { AxisSettingsPopover } from './axis_settings_popover'; -import { getAxesConfiguration, GroupsConfiguration } from './axes_configuration'; -import { PalettePicker, TooltipWrapper } from '../shared_components'; -import { getAccessorColorConfig, getColorAssignments } from './color_assignment'; -import { getScaleType, getSortedAccessors } from './to_expression'; -import { VisualOptionsPopover } from './visual_options_popover/visual_options_popover'; -import { ToolbarButton } from '../../../../../src/plugins/kibana_react/public'; +import { getAxesConfiguration, GroupsConfiguration } from '../axes_configuration'; +import { VisualOptionsPopover } from './visual_options_popover'; +import { getScaleType } from '../to_expression'; +import { ColorPicker } from './color_picker'; +import { ThresholdPanel } from './threshold_panel'; +import { PalettePicker, TooltipWrapper } from '../../shared_components'; type UnwrapArray = T extends Array ? P : T; type AxesSettingsConfigKeys = keyof AxesSettingsConfig; -function updateLayer(state: State, layer: UnwrapArray, index: number): State { +export function updateLayer( + state: State, + layer: UnwrapArray, + index: number +): State { const newLayers = [...state.layers]; newLayers[index] = layer; @@ -92,90 +87,6 @@ const legendOptions: Array<{ }, ]; -export function LayerHeader(props: VisualizationLayerWidgetProps) { - const [isPopoverOpen, setPopoverIsOpen] = useState(false); - const { state, layerId } = props; - const horizontalOnly = isHorizontalChart(state.layers); - const index = state.layers.findIndex((l) => l.layerId === layerId); - const layer = state.layers[index]; - if (!layer) { - return null; - } - - const currentVisType = visualizationTypes.find(({ id }) => id === layer.seriesType)!; - - const createTrigger = function () { - return ( - setPopoverIsOpen(!isPopoverOpen)} - fullWidth - size="s" - > - <> - - - {currentVisType.fullLabel || currentVisType.label} - - - - ); - }; - - return ( - <> - setPopoverIsOpen(false)} - display="block" - panelPaddingSize="s" - ownFocus - > - - {i18n.translate('xpack.lens.layerPanel.layerVisualizationType', { - defaultMessage: 'Layer visualization type', - })} - -
- - singleSelection="always" - options={visualizationTypes - .filter((t) => isHorizontalSeries(t.id as SeriesType) === horizontalOnly) - .map((t) => ({ - value: t.id, - key: t.id, - checked: t.id === currentVisType.id ? 'on' : undefined, - prepend: , - label: t.fullLabel || t.label, - 'data-test-subj': `lnsXY_seriesType-${t.id}`, - }))} - onChange={(newOptions) => { - const chosenType = newOptions.find(({ checked }) => checked === 'on'); - if (!chosenType) { - return; - } - const id = chosenType.value!; - trackUiEvent('xy_change_layer_display'); - props.setState(updateLayer(state, { ...layer, seriesType: id as SeriesType }, index)); - setPopoverIsOpen(false); - }} - > - {(list) => <>{list}} - -
-
- - ); -} - export function LayerContextMenu(props: VisualizationLayerWidgetProps) { const { state, layerId } = props; const horizontalOnly = isHorizontalChart(state.layers); @@ -622,7 +533,7 @@ export const XyToolbar = memo(function XyToolbar(props: VisualizationToolbarProp ); }); -const idPrefix = htmlIdGenerator()(); +export const idPrefix = htmlIdGenerator()(); export function DimensionEditor( props: VisualizationDimensionEditorProps & { @@ -653,6 +564,10 @@ export function DimensionEditor( ); } + if (layer.layerType === 'threshold') { + return ; + } + return ( <> @@ -728,140 +643,3 @@ export function DimensionEditor( ); } - -const tooltipContent = { - auto: i18n.translate('xpack.lens.configPanel.color.tooltip.auto', { - defaultMessage: 'Lens automatically picks colors for you unless you specify a custom color.', - }), - custom: i18n.translate('xpack.lens.configPanel.color.tooltip.custom', { - defaultMessage: 'Clear the custom color to return to “Auto” mode.', - }), - disabled: i18n.translate('xpack.lens.configPanel.color.tooltip.disabled', { - defaultMessage: - 'Individual series cannot be custom colored when the layer includes a “Break down by.“', - }), -}; - -const ColorPicker = ({ - state, - setState, - layerId, - accessor, - frame, - formatFactory, - paletteService, -}: VisualizationDimensionEditorProps & { - formatFactory: FormatFactory; - paletteService: PaletteRegistry; -}) => { - const index = state.layers.findIndex((l) => l.layerId === layerId); - const layer = state.layers[index]; - const disabled = !!layer.splitAccessor; - - const overwriteColor = getSeriesColor(layer, accessor); - const currentColor = useMemo(() => { - if (overwriteColor || !frame.activeData) return overwriteColor; - - const datasource = frame.datasourceLayers[layer.layerId]; - const sortedAccessors: string[] = getSortedAccessors(datasource, layer); - - const colorAssignments = getColorAssignments( - state.layers, - { tables: frame.activeData }, - formatFactory - ); - const mappedAccessors = getAccessorColorConfig( - colorAssignments, - frame, - { - ...layer, - accessors: sortedAccessors.filter((sorted) => layer.accessors.includes(sorted)), - }, - paletteService - ); - - return mappedAccessors.find((a) => a.columnId === accessor)?.color || null; - }, [overwriteColor, frame, paletteService, state.layers, accessor, formatFactory, layer]); - - const [color, setColor] = useState(currentColor); - - const handleColor: EuiColorPickerProps['onChange'] = (text, output) => { - setColor(text); - if (output.isValid || text === '') { - updateColorInState(text, output); - } - }; - - const updateColorInState: EuiColorPickerProps['onChange'] = useMemo( - () => - debounce((text, output) => { - const newYConfigs = [...(layer.yConfig || [])]; - const existingIndex = newYConfigs.findIndex((yConfig) => yConfig.forAccessor === accessor); - if (existingIndex !== -1) { - if (text === '') { - newYConfigs[existingIndex] = { ...newYConfigs[existingIndex], color: undefined }; - } else { - newYConfigs[existingIndex] = { ...newYConfigs[existingIndex], color: output.hex }; - } - } else { - newYConfigs.push({ - forAccessor: accessor, - color: output.hex, - }); - } - setState(updateLayer(state, { ...layer, yConfig: newYConfigs }, index)); - }, 256), - [state, setState, layer, accessor, index] - ); - - const colorPicker = ( - - ); - - return ( - - - {i18n.translate('xpack.lens.xyChart.seriesColor.label', { - defaultMessage: 'Series color', - })}{' '} - - -
- } - > - {disabled ? ( - - {colorPicker} - - ) : ( - colorPicker - )} - - ); -}; diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/layer_header.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/layer_header.tsx new file mode 100644 index 0000000000000..dde4de0dd4bc3 --- /dev/null +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/layer_header.tsx @@ -0,0 +1,115 @@ +/* + * 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 './xy_config_panel.scss'; +import React, { useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiIcon, EuiPopover, EuiSelectable, EuiText, EuiPopoverTitle } from '@elastic/eui'; +import type { VisualizationLayerWidgetProps } from '../../types'; +import { State, visualizationTypes } from '../types'; +import { layerTypes } from '../../../common'; +import { SeriesType } from '../../../common/expressions'; +import { isHorizontalChart, isHorizontalSeries } from '../state_helpers'; +import { trackUiEvent } from '../../lens_ui_telemetry'; +import { StaticHeader } from '../../shared_components'; +import { ToolbarButton } from '../../../../../../src/plugins/kibana_react/public'; +import { LensIconChartBarThreshold } from '../../assets/chart_bar_threshold'; +import { updateLayer } from '.'; + +export function LayerHeader(props: VisualizationLayerWidgetProps) { + const [isPopoverOpen, setPopoverIsOpen] = useState(false); + const { state, layerId } = props; + const horizontalOnly = isHorizontalChart(state.layers); + const index = state.layers.findIndex((l) => l.layerId === layerId); + const layer = state.layers[index]; + if (!layer) { + return null; + } + // if it's a threshold just draw a static text + if (layer.layerType === layerTypes.THRESHOLD) { + return ( + + ); + } + const currentVisType = visualizationTypes.find(({ id }) => id === layer.seriesType)!; + + const createTrigger = function () { + return ( + setPopoverIsOpen(!isPopoverOpen)} + fullWidth + size="s" + > + <> + + + {currentVisType.fullLabel || currentVisType.label} + + + + ); + }; + + return ( + <> + setPopoverIsOpen(false)} + display="block" + panelPaddingSize="s" + ownFocus + > + + {i18n.translate('xpack.lens.layerPanel.layerVisualizationType', { + defaultMessage: 'Layer visualization type', + })} + +
+ + singleSelection="always" + options={visualizationTypes + .filter((t) => isHorizontalSeries(t.id as SeriesType) === horizontalOnly) + .map((t) => ({ + value: t.id, + key: t.id, + checked: t.id === currentVisType.id ? 'on' : undefined, + prepend: , + label: t.fullLabel || t.label, + 'data-test-subj': `lnsXY_seriesType-${t.id}`, + }))} + onChange={(newOptions) => { + const chosenType = newOptions.find(({ checked }) => checked === 'on'); + if (!chosenType) { + return; + } + const id = chosenType.value!; + trackUiEvent('xy_change_layer_display'); + props.setState(updateLayer(state, { ...layer, seriesType: id as SeriesType }, index)); + setPopoverIsOpen(false); + }} + > + {(list) => <>{list}} + +
+
+ + ); +} diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/threshold_panel.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/threshold_panel.tsx new file mode 100644 index 0000000000000..1e5b90e41b623 --- /dev/null +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/threshold_panel.tsx @@ -0,0 +1,326 @@ +/* + * 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 './xy_config_panel.scss'; +import React, { useCallback } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiButtonGroup, EuiComboBox, EuiFormRow, EuiIcon, EuiRange } from '@elastic/eui'; +import type { PaletteRegistry } from 'src/plugins/charts/public'; +import type { VisualizationDimensionEditorProps } from '../../types'; +import { State } from '../types'; +import { FormatFactory } from '../../../common'; +import { YConfig } from '../../../common/expressions'; +import { LineStyle, FillStyle } from '../../../common/expressions/xy_chart'; + +import { ColorPicker } from './color_picker'; +import { updateLayer, idPrefix } from '.'; +import { useDebouncedValue } from '../../shared_components'; + +const icons = [ + { + value: 'none', + label: i18n.translate('xpack.lens.xyChart.thresholds.noIconLabel', { defaultMessage: 'None' }), + }, + { + value: 'asterisk', + label: i18n.translate('xpack.lens.xyChart.thresholds.asteriskIconLabel', { + defaultMessage: 'Asterisk', + }), + }, + { + value: 'bell', + label: i18n.translate('xpack.lens.xyChart.thresholds.bellIconLabel', { + defaultMessage: 'Bell', + }), + }, + { + value: 'bolt', + label: i18n.translate('xpack.lens.xyChart.thresholds.boltIconLabel', { + defaultMessage: 'Bolt', + }), + }, + { + value: 'bug', + label: i18n.translate('xpack.lens.xyChart.thresholds.bugIconLabel', { + defaultMessage: 'Bug', + }), + }, + { + value: 'editorComment', + label: i18n.translate('xpack.lens.xyChart.thresholds.commentIconLabel', { + defaultMessage: 'Comment', + }), + }, + { + value: 'alert', + label: i18n.translate('xpack.lens.xyChart.thresholds.alertIconLabel', { + defaultMessage: 'Alert', + }), + }, + { + value: 'flag', + label: i18n.translate('xpack.lens.xyChart.thresholds.flagIconLabel', { + defaultMessage: 'Flag', + }), + }, + { + value: 'tag', + label: i18n.translate('xpack.lens.xyChart.thresholds.tagIconLabel', { + defaultMessage: 'Tag', + }), + }, +]; + +const IconView = (props: { value?: string; label: string }) => { + if (!props.value) return null; + return ( + + + {` ${props.label}`} + + ); +}; + +const IconSelect = ({ + value, + onChange, +}: { + value?: string; + onChange: (newIcon: string) => void; +}) => { + const selectedIcon = icons.find((option) => value === option.value) || icons[0]; + + return ( + { + onChange(selection[0].value!); + }} + singleSelection={{ asPlainText: true }} + renderOption={IconView} + compressed + /> + ); +}; + +export const ThresholdPanel = ( + props: VisualizationDimensionEditorProps & { + formatFactory: FormatFactory; + paletteService: PaletteRegistry; + } +) => { + const { state, setState, layerId, accessor } = props; + const index = state.layers.findIndex((l) => l.layerId === layerId); + const layer = state.layers[index]; + + const setYConfig = useCallback( + (yConfig: Partial | undefined) => { + if (yConfig == null) { + return; + } + setState((currState) => { + const currLayer = currState.layers[index]; + const newYConfigs = [...(currLayer.yConfig || [])]; + const existingIndex = newYConfigs.findIndex( + (yAxisConfig) => yAxisConfig.forAccessor === accessor + ); + if (existingIndex !== -1) { + newYConfigs[existingIndex] = { ...newYConfigs[existingIndex], ...yConfig }; + } else { + newYConfigs.push({ forAccessor: accessor, ...yConfig }); + } + return updateLayer(currState, { ...currLayer, yConfig: newYConfigs }, index); + }); + }, + [accessor, index, setState] + ); + + const currentYConfig = layer.yConfig?.find((yConfig) => yConfig.forAccessor === accessor); + + return ( + <> + + + { + const newMode = id.replace(idPrefix, '') as LineStyle; + setYConfig({ forAccessor: accessor, lineStyle: newMode }); + }} + /> + + + { + setYConfig({ forAccessor: accessor, lineWidth: value }); + }} + /> + + + { + const newMode = id.replace(idPrefix, '') as FillStyle; + setYConfig({ forAccessor: accessor, fill: newMode }); + }} + /> + + + { + setYConfig({ forAccessor: accessor, icon: newIcon }); + }} + /> + + + ); +}; + +const minRange = 1; +const maxRange = 10; + +function getSafeValue(value: number | '', prevValue: number, min: number, max: number) { + if (value === '') { + return prevValue; + } + return Math.max(minRange, Math.min(value, maxRange)); +} + +const LineThicknessSlider = ({ + value, + onChange, +}: { + value: number; + onChange: (value: number) => void; +}) => { + const onChangeWrapped = useCallback( + (newValue) => { + if (Number.isInteger(newValue)) { + onChange(getSafeValue(newValue, newValue, minRange, maxRange)); + } + }, + [onChange] + ); + const { inputValue, handleInputChange } = useDebouncedValue( + { value, onChange: onChangeWrapped }, + { allowFalsyValue: true } + ); + + return ( + { + const newValue = e.currentTarget.value; + handleInputChange(newValue === '' ? '' : Number(newValue)); + }} + onBlur={() => { + handleInputChange(getSafeValue(inputValue, value, minRange, maxRange)); + }} + /> + ); +}; diff --git a/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/fill_opacity_option.test.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/visual_options_popover/fill_opacity_option.test.tsx similarity index 100% rename from x-pack/plugins/lens/public/xy_visualization/visual_options_popover/fill_opacity_option.test.tsx rename to x-pack/plugins/lens/public/xy_visualization/xy_config_panel/visual_options_popover/fill_opacity_option.test.tsx diff --git a/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/fill_opacity_option.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/visual_options_popover/fill_opacity_option.tsx similarity index 95% rename from x-pack/plugins/lens/public/xy_visualization/visual_options_popover/fill_opacity_option.tsx rename to x-pack/plugins/lens/public/xy_visualization/xy_config_panel/visual_options_popover/fill_opacity_option.tsx index eb8d35c54a99b..09b381dd03f7a 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/fill_opacity_option.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/visual_options_popover/fill_opacity_option.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { EuiFormRow, EuiRange } from '@elastic/eui'; -import { useDebouncedValue } from '../../shared_components'; +import { useDebouncedValue } from '../../../shared_components'; export interface FillOpacityOptionProps { /** diff --git a/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/visual_options_popover.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/visual_options_popover/index.tsx similarity index 93% rename from x-pack/plugins/lens/public/xy_visualization/visual_options_popover/visual_options_popover.tsx rename to x-pack/plugins/lens/public/xy_visualization/xy_config_panel/visual_options_popover/index.tsx index 6d0e5c2d55b70..2a19897445e63 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/visual_options_popover.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/visual_options_popover/index.tsx @@ -7,14 +7,14 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { ToolbarPopover, TooltipWrapper } from '../../shared_components'; +import { ToolbarPopover, TooltipWrapper } from '../../../shared_components'; import { MissingValuesOptions } from './missing_values_option'; import { LineCurveOption } from './line_curve_option'; import { FillOpacityOption } from './fill_opacity_option'; -import { XYState } from '../types'; -import { hasHistogramSeries } from '../state_helpers'; -import { ValidLayer } from '../../../common/expressions'; -import type { FramePublicAPI } from '../../types'; +import { XYState } from '../../types'; +import { hasHistogramSeries } from '../../state_helpers'; +import { ValidLayer } from '../../../../common/expressions'; +import type { FramePublicAPI } from '../../../types'; function getValueLabelDisableReason({ isAreaPercentage, diff --git a/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/line_curve_option.test.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/visual_options_popover/line_curve_option.test.tsx similarity index 100% rename from x-pack/plugins/lens/public/xy_visualization/visual_options_popover/line_curve_option.test.tsx rename to x-pack/plugins/lens/public/xy_visualization/xy_config_panel/visual_options_popover/line_curve_option.test.tsx diff --git a/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/line_curve_option.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/visual_options_popover/line_curve_option.tsx similarity index 95% rename from x-pack/plugins/lens/public/xy_visualization/visual_options_popover/line_curve_option.tsx rename to x-pack/plugins/lens/public/xy_visualization/xy_config_panel/visual_options_popover/line_curve_option.tsx index 6080a8c68e57d..96926412afb8a 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/line_curve_option.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/visual_options_popover/line_curve_option.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { EuiFormRow, EuiSwitch } from '@elastic/eui'; -import type { XYCurveType } from '../../../common/expressions'; +import type { XYCurveType } from '../../../../common/expressions'; export interface LineCurveOptionProps { /** diff --git a/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/missing_value_option.test.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/visual_options_popover/missing_value_option.test.tsx similarity index 100% rename from x-pack/plugins/lens/public/xy_visualization/visual_options_popover/missing_value_option.test.tsx rename to x-pack/plugins/lens/public/xy_visualization/xy_config_panel/visual_options_popover/missing_value_option.test.tsx diff --git a/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/missing_values_option.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/visual_options_popover/missing_values_option.tsx similarity index 97% rename from x-pack/plugins/lens/public/xy_visualization/visual_options_popover/missing_values_option.tsx rename to x-pack/plugins/lens/public/xy_visualization/xy_config_panel/visual_options_popover/missing_values_option.tsx index 3dba8757903e9..b12e2d2f57112 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/missing_values_option.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/visual_options_popover/missing_values_option.tsx @@ -8,8 +8,8 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { EuiButtonGroup, EuiFormRow, EuiIconTip, EuiSuperSelect, EuiText } from '@elastic/eui'; -import { fittingFunctionDefinitions } from '../../../common/expressions'; -import type { FittingFunction, ValueLabelConfig } from '../../../common/expressions'; +import { fittingFunctionDefinitions } from '../../../../common/expressions'; +import type { FittingFunction, ValueLabelConfig } from '../../../../common/expressions'; export interface MissingValuesOptionProps { valueLabels?: ValueLabelConfig; diff --git a/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/visual_options_popover.test.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/visual_options_popover/visual_options_popover.test.tsx similarity index 96% rename from x-pack/plugins/lens/public/xy_visualization/visual_options_popover/visual_options_popover.test.tsx rename to x-pack/plugins/lens/public/xy_visualization/xy_config_panel/visual_options_popover/visual_options_popover.test.tsx index cd6a20c37dd38..0136612c46705 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/visual_options_popover.test.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/visual_options_popover/visual_options_popover.test.tsx @@ -8,14 +8,14 @@ import React from 'react'; import { shallowWithIntl as shallow } from '@kbn/test/jest'; import { Position } from '@elastic/charts'; -import type { FramePublicAPI } from '../../types'; -import { createMockDatasource, createMockFramePublicAPI } from '../../mocks'; -import { State } from '../types'; -import { VisualOptionsPopover } from './visual_options_popover'; -import { ToolbarPopover } from '../../shared_components'; +import type { FramePublicAPI } from '../../../types'; +import { createMockDatasource, createMockFramePublicAPI } from '../../../mocks'; +import { State } from '../../types'; +import { VisualOptionsPopover } from '.'; +import { ToolbarPopover } from '../../../shared_components'; import { MissingValuesOptions } from './missing_values_option'; import { FillOpacityOption } from './fill_opacity_option'; -import { layerTypes } from '../../../common'; +import { layerTypes } from '../../../../common'; describe('Visual options popover', () => { let frame: FramePublicAPI; diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.scss b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/xy_config_panel.scss similarity index 100% rename from x-pack/plugins/lens/public/xy_visualization/xy_config_panel.scss rename to x-pack/plugins/lens/public/xy_visualization/xy_config_panel/xy_config_panel.scss diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.test.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/xy_config_panel.test.tsx similarity index 98% rename from x-pack/plugins/lens/public/xy_visualization/xy_config_panel.test.tsx rename to x-pack/plugins/lens/public/xy_visualization/xy_config_panel/xy_config_panel.test.tsx index 9ca9021382fda..e5b1870c73404 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.test.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/xy_config_panel.test.tsx @@ -8,15 +8,15 @@ import React from 'react'; import { mountWithIntl as mount, shallowWithIntl as shallow } from '@kbn/test/jest'; import { EuiButtonGroupProps, EuiButtonGroup } from '@elastic/eui'; -import { LayerContextMenu, XyToolbar, DimensionEditor } from './xy_config_panel'; +import { LayerContextMenu, XyToolbar, DimensionEditor } from '.'; import { AxisSettingsPopover } from './axis_settings_popover'; -import { FramePublicAPI } from '../types'; -import { State } from './types'; +import { FramePublicAPI } from '../../types'; +import { State } from '../types'; import { Position } from '@elastic/charts'; -import { createMockFramePublicAPI, createMockDatasource } from '../mocks'; +import { createMockFramePublicAPI, createMockDatasource } from '../../mocks'; import { chartPluginMock } from 'src/plugins/charts/public/mocks'; import { EuiColorPicker } from '@elastic/eui'; -import { layerTypes } from '../../common'; +import { layerTypes } from '../../../common'; describe('XY Config panels', () => { let frame: FramePublicAPI; diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.test.ts b/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.test.ts index 36e69ab6cbf74..d45e13af4ae80 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.test.ts +++ b/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.test.ts @@ -88,26 +88,28 @@ describe('xy_suggestions', () => { test('partially maps invalid combinations, but hides them', () => { expect( - ([ - { - isMultiRow: true, - columns: [dateCol('a')], - layerId: 'first', - changeType: 'unchanged', - }, - { - isMultiRow: true, - columns: [strCol('foo'), strCol('bar')], - layerId: 'first', - changeType: 'unchanged', - }, - { - isMultiRow: false, - columns: [numCol('bar')], - layerId: 'first', - changeType: 'unchanged', - }, - ] as TableSuggestion[]).map((table) => { + ( + [ + { + isMultiRow: true, + columns: [dateCol('a')], + layerId: 'first', + changeType: 'unchanged', + }, + { + isMultiRow: true, + columns: [strCol('foo'), strCol('bar')], + layerId: 'first', + changeType: 'unchanged', + }, + { + isMultiRow: false, + columns: [numCol('bar')], + layerId: 'first', + changeType: 'unchanged', + }, + ] as TableSuggestion[] + ).map((table) => { const suggestions = getSuggestions({ table, keptLayerIds: [] }); expect(suggestions.every((suggestion) => suggestion.hide)).toEqual(true); expect(suggestions).toHaveLength(10); @@ -117,20 +119,22 @@ describe('xy_suggestions', () => { test('rejects incomplete configurations if there is a state already but no sub visualization id', () => { expect( - ([ - { - isMultiRow: true, - columns: [dateCol('a')], - layerId: 'first', - changeType: 'reduced', - }, - { - isMultiRow: false, - columns: [numCol('bar')], - layerId: 'first', - changeType: 'reduced', - }, - ] as TableSuggestion[]).map((table) => { + ( + [ + { + isMultiRow: true, + columns: [dateCol('a')], + layerId: 'first', + changeType: 'reduced', + }, + { + isMultiRow: false, + columns: [numCol('bar')], + layerId: 'first', + changeType: 'reduced', + }, + ] as TableSuggestion[] + ).map((table) => { const suggestions = getSuggestions({ table, keptLayerIds: [], @@ -231,16 +235,7 @@ describe('xy_suggestions', () => { expect(suggestions).toHaveLength(visualizationTypes.length); expect(suggestions.map(({ state }) => state.layers.length)).toEqual([ - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ]); expect(suggestions.map(({ state }) => xyVisualization.getVisualizationTypeId(state))).toEqual([ 'bar_stacked', diff --git a/x-pack/plugins/lens/server/embeddable/lens_embeddable_factory.ts b/x-pack/plugins/lens/server/embeddable/lens_embeddable_factory.ts index 4423d9e659119..0e79e342d4427 100644 --- a/x-pack/plugins/lens/server/embeddable/lens_embeddable_factory.ts +++ b/x-pack/plugins/lens/server/embeddable/lens_embeddable_factory.ts @@ -29,36 +29,36 @@ export const lensEmbeddableFactory = (): EmbeddableRegistryDefinition => { migrations: { // This migration is run in 7.13.1 for `by value` panels because the 7.13 release window was missed. '7.13.1': (state) => { - const lensState = (state as unknown) as { attributes: LensDocShapePre712 }; + const lensState = state as unknown as { attributes: LensDocShapePre712 }; const migratedLensState = commonRenameOperationsForFormula(lensState.attributes); - return ({ + return { ...lensState, attributes: migratedLensState, - } as unknown) as SerializableRecord; + } as unknown as SerializableRecord; }, '7.14.0': (state) => { - const lensState = (state as unknown) as { attributes: LensDocShape713 }; + const lensState = state as unknown as { attributes: LensDocShape713 }; const migratedLensState = commonRemoveTimezoneDateHistogramParam(lensState.attributes); - return ({ + return { ...lensState, attributes: migratedLensState, - } as unknown) as SerializableRecord; + } as unknown as SerializableRecord; }, '7.15.0': (state) => { - const lensState = (state as unknown) as { attributes: LensDocShape715 }; + const lensState = state as unknown as { attributes: LensDocShape715 }; const migratedLensState = commonUpdateVisLayerType(lensState.attributes); - return ({ + return { ...lensState, attributes: migratedLensState, - } as unknown) as SerializableRecord; + } as unknown as SerializableRecord; }, '7.16.0': (state) => { - const lensState = (state as unknown) as { attributes: LensDocShape715 }; + const lensState = state as unknown as { attributes: LensDocShape715 }; const migratedLensState = commonMakeReversePaletteAsCustom(lensState.attributes); - return ({ + return { ...lensState, attributes: migratedLensState, - } as unknown) as SerializableRecord; + } as unknown as SerializableRecord; }, }, extract, diff --git a/x-pack/plugins/lens/server/expressions/utils.ts b/x-pack/plugins/lens/server/expressions/utils.ts index bfe51e4b960ef..40bee0386cc95 100644 --- a/x-pack/plugins/lens/server/expressions/utils.ts +++ b/x-pack/plugins/lens/server/expressions/utils.ts @@ -22,26 +22,24 @@ const getUiSettings = (coreStart: CoreStart, context: ExecutionContext) => { }; /** @internal **/ -export const getFormatFactory = (core: CoreSetup) => async ( - context: ExecutionContext -) => { - const [coreStart, { fieldFormats: fieldFormatsStart }] = await core.getStartServices(); +export const getFormatFactory = + (core: CoreSetup) => async (context: ExecutionContext) => { + const [coreStart, { fieldFormats: fieldFormatsStart }] = await core.getStartServices(); - const fieldFormats = await fieldFormatsStart.fieldFormatServiceFactory( - getUiSettings(coreStart, context) - ); + const fieldFormats = await fieldFormatsStart.fieldFormatServiceFactory( + getUiSettings(coreStart, context) + ); - return fieldFormats.deserialize; -}; + return fieldFormats.deserialize; + }; /** @internal **/ -export const getTimeZoneFactory = (core: CoreSetup) => async ( - context: ExecutionContext -) => { - const [coreStart] = await core.getStartServices(); - const uiSettings = await getUiSettings(coreStart, context); - const timezone = await uiSettings.get('dateFormat:tz'); - - /** if `Browser`, hardcode it to 'UTC' so the export has data that makes sense **/ - return timezone === 'Browser' ? 'UTC' : timezone; -}; +export const getTimeZoneFactory = + (core: CoreSetup) => async (context: ExecutionContext) => { + const [coreStart] = await core.getStartServices(); + const uiSettings = await getUiSettings(coreStart, context); + const timezone = await uiSettings.get('dateFormat:tz'); + + /** if `Browser`, hardcode it to 'UTC' so the export has data that makes sense **/ + return timezone === 'Browser' ? 'UTC' : timezone; + }; diff --git a/x-pack/plugins/lens/server/index.ts b/x-pack/plugins/lens/server/index.ts index 08f1eb1562739..e2117506e9b72 100644 --- a/x-pack/plugins/lens/server/index.ts +++ b/x-pack/plugins/lens/server/index.ts @@ -19,6 +19,7 @@ import { configSchema, ConfigSchema } from '../config'; export const config: PluginConfigDescriptor = { schema: configSchema, + deprecations: ({ deprecate }) => [deprecate('enabled', '8.0.0')], }; export const plugin = (initializerContext: PluginInitializerContext) => diff --git a/x-pack/plugins/lens/server/migrations/common_migrations.ts b/x-pack/plugins/lens/server/migrations/common_migrations.ts index 5755416957440..290655ec634eb 100644 --- a/x-pack/plugins/lens/server/migrations/common_migrations.ts +++ b/x-pack/plugins/lens/server/migrations/common_migrations.ts @@ -33,27 +33,28 @@ export const commonRenameOperationsForFormula = ( } const newAttributes = cloneDeep(attributes); const datasourceLayers = newAttributes.state.datasourceStates.indexpattern.layers || {}; - (newAttributes as LensDocShapePost712).state.datasourceStates.indexpattern.layers = Object.fromEntries( - Object.entries(datasourceLayers).map(([layerId, layer]) => { - return [ - layerId, - { - ...layer, - columns: Object.fromEntries( - Object.entries(layer.columns).map(([columnId, column]) => { - const copy = { - ...column, - operationType: shouldBeRenamed(column.operationType) - ? renameMapping[column.operationType] - : column.operationType, - }; - return [columnId, copy]; - }) - ), - }, - ]; - }) - ); + (newAttributes as LensDocShapePost712).state.datasourceStates.indexpattern.layers = + Object.fromEntries( + Object.entries(datasourceLayers).map(([layerId, layer]) => { + return [ + layerId, + { + ...layer, + columns: Object.fromEntries( + Object.entries(layer.columns).map(([columnId, column]) => { + const copy = { + ...column, + operationType: shouldBeRenamed(column.operationType) + ? renameMapping[column.operationType] + : column.operationType, + }; + return [columnId, copy]; + }) + ), + }, + ]; + }) + ); return newAttributes as LensDocShapePost712; }; @@ -62,26 +63,27 @@ export const commonRemoveTimezoneDateHistogramParam = ( ): LensDocShape714 => { const newAttributes = cloneDeep(attributes); const datasourceLayers = newAttributes.state.datasourceStates.indexpattern.layers || {}; - (newAttributes as LensDocShapePost712).state.datasourceStates.indexpattern.layers = Object.fromEntries( - Object.entries(datasourceLayers).map(([layerId, layer]) => { - return [ - layerId, - { - ...layer, - columns: Object.fromEntries( - Object.entries(layer.columns).map(([columnId, column]) => { - if (column.operationType === 'date_histogram' && 'params' in column) { - const copy = { ...column, params: { ...column.params } }; - delete copy.params.timeZone; - return [columnId, copy]; - } - return [columnId, column]; - }) - ), - }, - ]; - }) - ); + (newAttributes as LensDocShapePost712).state.datasourceStates.indexpattern.layers = + Object.fromEntries( + Object.entries(datasourceLayers).map(([layerId, layer]) => { + return [ + layerId, + { + ...layer, + columns: Object.fromEntries( + Object.entries(layer.columns).map(([columnId, column]) => { + if (column.operationType === 'date_histogram' && 'params' in column) { + const copy = { ...column, params: { ...column.params } }; + delete copy.params.timeZone; + return [columnId, copy]; + } + return [columnId, column]; + }) + ), + }, + ]; + }) + ); return newAttributes as LensDocShapePost712; }; diff --git a/x-pack/plugins/lens/server/migrations/saved_object_migrations.test.ts b/x-pack/plugins/lens/server/migrations/saved_object_migrations.test.ts index c16c5b5169ac5..ef67c768a8997 100644 --- a/x-pack/plugins/lens/server/migrations/saved_object_migrations.test.ts +++ b/x-pack/plugins/lens/server/migrations/saved_object_migrations.test.ts @@ -170,7 +170,7 @@ describe('Lens migrations', () => { }); describe('7.8.0 auto timestamp', () => { - const context = ({ log: { warning: () => {} } } as unknown) as SavedObjectMigrationContext; + const context = { log: { warning: () => {} } } as unknown as SavedObjectMigrationContext; const example = { type: 'lens', @@ -522,7 +522,7 @@ describe('Lens migrations', () => { }); describe('7.11.0 remove suggested priority', () => { - const context = ({ log: { warning: () => {} } } as unknown) as SavedObjectMigrationContext; + const context = { log: { warning: () => {} } } as unknown as SavedObjectMigrationContext; const example = { type: 'lens', @@ -607,7 +607,7 @@ describe('Lens migrations', () => { }); describe('7.12.0 restructure datatable state', () => { - const context = ({ log: { warning: () => {} } } as unknown) as SavedObjectMigrationContext; + const context = { log: { warning: () => {} } } as unknown as SavedObjectMigrationContext; const example = { type: 'lens', id: 'mock-saved-object-id', @@ -680,7 +680,7 @@ describe('Lens migrations', () => { }); describe('7.13.0 rename operations for Formula', () => { - const context = ({ log: { warning: () => {} } } as unknown) as SavedObjectMigrationContext; + const context = { log: { warning: () => {} } } as unknown as SavedObjectMigrationContext; const example = { type: 'lens', id: 'mocked-saved-object-id', @@ -858,7 +858,7 @@ describe('Lens migrations', () => { }); describe('7.14.0 remove time zone from date histogram', () => { - const context = ({ log: { warning: () => {} } } as unknown) as SavedObjectMigrationContext; + const context = { log: { warning: () => {} } } as unknown as SavedObjectMigrationContext; const example = { type: 'lens', id: 'mocked-saved-object-id', @@ -950,8 +950,8 @@ describe('Lens migrations', () => { }); describe('7.15.0 add layer type information', () => { - const context = ({ log: { warning: () => {} } } as unknown) as SavedObjectMigrationContext; - const example = ({ + const context = { log: { warning: () => {} } } as unknown as SavedObjectMigrationContext; + const example = { type: 'lens', id: 'mocked-saved-object-id', attributes: { @@ -1006,12 +1006,12 @@ describe('Lens migrations', () => { filters: [], }, }, - } as unknown) as SavedObjectUnsanitizedDoc>; + } as unknown as SavedObjectUnsanitizedDoc>; it('should add the layerType to a XY visualization', () => { const xyExample = cloneDeep(example); xyExample.attributes.visualizationType = 'lnsXY'; - (xyExample.attributes as LensDocShape715).state.visualization = ({ + (xyExample.attributes as LensDocShape715).state.visualization = { title: 'Empty XY chart', legend: { isVisible: true, position: 'right' }, valueLabels: 'hide', @@ -1042,7 +1042,7 @@ describe('Lens migrations', () => { xAccessor: '2e57a41e-5a52-42d3-877f-bd211d903ef8', }, ], - } as unknown) as VisStatePre715; + } as unknown as VisStatePre715; const result = migrations['7.15.0'](xyExample, context) as ReturnType< SavedObjectMigrationFn >; @@ -1057,7 +1057,7 @@ describe('Lens migrations', () => { it('should add layer info to a pie visualization', () => { const pieExample = cloneDeep(example); pieExample.attributes.visualizationType = 'lnsPie'; - (pieExample.attributes as LensDocShape715).state.visualization = ({ + (pieExample.attributes as LensDocShape715).state.visualization = { shape: 'pie', layers: [ { @@ -1070,7 +1070,7 @@ describe('Lens migrations', () => { nestedLegend: false, }, ], - } as unknown) as VisStatePre715; + } as unknown as VisStatePre715; const result = migrations['7.15.0'](pieExample, context) as ReturnType< SavedObjectMigrationFn >; @@ -1084,10 +1084,10 @@ describe('Lens migrations', () => { it('should add layer info to a metric visualization', () => { const metricExample = cloneDeep(example); metricExample.attributes.visualizationType = 'lnsMetric'; - (metricExample.attributes as LensDocShape715).state.visualization = ({ + (metricExample.attributes as LensDocShape715).state.visualization = { layerId: '1', accessor: undefined, - } as unknown) as VisStatePre715; + } as unknown as VisStatePre715; const result = migrations['7.15.0'](metricExample, context) as ReturnType< SavedObjectMigrationFn >; @@ -1100,10 +1100,10 @@ describe('Lens migrations', () => { it('should add layer info to a datatable visualization', () => { const datatableExample = cloneDeep(example); datatableExample.attributes.visualizationType = 'lnsDatatable'; - (datatableExample.attributes as LensDocShape715).state.visualization = ({ + (datatableExample.attributes as LensDocShape715).state.visualization = { layerId: '1', accessor: undefined, - } as unknown) as VisStatePre715; + } as unknown as VisStatePre715; const result = migrations['7.15.0'](datatableExample, context) as ReturnType< SavedObjectMigrationFn >; @@ -1116,10 +1116,10 @@ describe('Lens migrations', () => { it('should add layer info to a heatmap visualization', () => { const heatmapExample = cloneDeep(example); heatmapExample.attributes.visualizationType = 'lnsHeatmap'; - (heatmapExample.attributes as LensDocShape715).state.visualization = ({ + (heatmapExample.attributes as LensDocShape715).state.visualization = { layerId: '1', accessor: undefined, - } as unknown) as VisStatePre715; + } as unknown as VisStatePre715; const result = migrations['7.15.0'](heatmapExample, context) as ReturnType< SavedObjectMigrationFn >; @@ -1132,8 +1132,8 @@ describe('Lens migrations', () => { }); describe('7.16.0 move reversed default palette to custom palette', () => { - const context = ({ log: { warning: () => {} } } as unknown) as SavedObjectMigrationContext; - const example = ({ + const context = { log: { warning: () => {} } } as unknown as SavedObjectMigrationContext; + const example = { type: 'lens', id: 'mocked-saved-object-id', attributes: { @@ -1188,14 +1188,14 @@ describe('Lens migrations', () => { filters: [], }, }, - } as unknown) as SavedObjectUnsanitizedDoc>; + } as unknown as SavedObjectUnsanitizedDoc>; it('should just return the same document for XY, partition and metric visualization types', () => { for (const vizType of ['lnsXY', 'lnsPie', 'lnsMetric']) { const exampleCopy = cloneDeep(example); exampleCopy.attributes.visualizationType = vizType; // add datatable state here, even with another viz (manual change?) - (exampleCopy.attributes as LensDocShape715).state.visualization = ({ + (exampleCopy.attributes as LensDocShape715).state.visualization = { columns: [ { palette: { type: 'palette', name: 'temperature' }, colorMode: 'cell' }, { palette: { type: 'palette', name: 'temperature' }, colorMode: 'text' }, @@ -1204,7 +1204,7 @@ describe('Lens migrations', () => { colorMode: 'cell', }, ], - } as unknown) as VisState716; + } as unknown as VisState716; const result = migrations['7.16.0'](exampleCopy, context) as ReturnType< SavedObjectMigrationFn >; @@ -1215,7 +1215,7 @@ describe('Lens migrations', () => { it('should not change non reversed default palettes in datatable', () => { const datatableExample = cloneDeep(example); datatableExample.attributes.visualizationType = 'lnsDatatable'; - (datatableExample.attributes as LensDocShape715).state.visualization = ({ + (datatableExample.attributes as LensDocShape715).state.visualization = { columns: [ { palette: { type: 'palette', name: 'temperature' }, colorMode: 'cell' }, { palette: { type: 'palette', name: 'temperature' }, colorMode: 'text' }, @@ -1224,7 +1224,7 @@ describe('Lens migrations', () => { colorMode: 'cell', }, ], - } as unknown) as VisState716; + } as unknown as VisState716; const result = migrations['7.16.0'](datatableExample, context) as ReturnType< SavedObjectMigrationFn >; @@ -1234,7 +1234,7 @@ describe('Lens migrations', () => { it('should not change custom palettes in datatable', () => { const datatableExample = cloneDeep(example); datatableExample.attributes.visualizationType = 'lnsDatatable'; - (datatableExample.attributes as LensDocShape715).state.visualization = ({ + (datatableExample.attributes as LensDocShape715).state.visualization = { columns: [ { palette: { type: 'palette', name: 'custom' }, colorMode: 'cell' }, { palette: { type: 'palette', name: 'custom' }, colorMode: 'text' }, @@ -1243,7 +1243,7 @@ describe('Lens migrations', () => { colorMode: 'cell', }, ], - } as unknown) as VisState716; + } as unknown as VisState716; const result = migrations['7.16.0'](datatableExample, context) as ReturnType< SavedObjectMigrationFn >; @@ -1253,9 +1253,9 @@ describe('Lens migrations', () => { it('should not change a datatable with no conditional coloring', () => { const datatableExample = cloneDeep(example); datatableExample.attributes.visualizationType = 'lnsDatatable'; - (datatableExample.attributes as LensDocShape715).state.visualization = ({ + (datatableExample.attributes as LensDocShape715).state.visualization = { columns: [{ colorMode: 'none' }, {}], - } as unknown) as VisState716; + } as unknown as VisState716; const result = migrations['7.16.0'](datatableExample, context) as ReturnType< SavedObjectMigrationFn >; @@ -1265,7 +1265,7 @@ describe('Lens migrations', () => { it('should not change default palette if the colorMode is set to "none" in datatable', () => { const datatableExample = cloneDeep(example); datatableExample.attributes.visualizationType = 'lnsDatatable'; - (datatableExample.attributes as LensDocShape715).state.visualization = ({ + (datatableExample.attributes as LensDocShape715).state.visualization = { columns: [ { palette: { type: 'palette', name: 'temperature' }, colorMode: 'none' }, { palette: { type: 'palette', name: 'temperature' }, colorMode: 'none' }, @@ -1274,7 +1274,7 @@ describe('Lens migrations', () => { colorMode: 'cell', }, ], - } as unknown) as VisState716; + } as unknown as VisState716; const result = migrations['7.16.0'](datatableExample, context) as ReturnType< SavedObjectMigrationFn >; @@ -1284,7 +1284,7 @@ describe('Lens migrations', () => { it('should change a default palette reversed in datatable', () => { const datatableExample = cloneDeep(example); datatableExample.attributes.visualizationType = 'lnsDatatable'; - (datatableExample.attributes as LensDocShape715).state.visualization = ({ + (datatableExample.attributes as LensDocShape715).state.visualization = { columns: [ { colorMode: 'cell', @@ -1323,13 +1323,13 @@ describe('Lens migrations', () => { }, }, ], - } as unknown) as VisState716; + } as unknown as VisState716; const result = migrations['7.16.0'](datatableExample, context) as ReturnType< SavedObjectMigrationFn >; - const state = (result.attributes as LensDocShape715< - Extract - >).state.visualization; + const state = ( + result.attributes as LensDocShape715> + ).state.visualization; for (const column of state.columns) { expect(column.palette!.name).toBe('custom'); expect(column.palette!.params!.name).toBe('custom'); @@ -1357,7 +1357,7 @@ describe('Lens migrations', () => { it('should change a default palette reversed in heatmap', () => { const datatableExample = cloneDeep(example); datatableExample.attributes.visualizationType = 'lnsHeatmap'; - (datatableExample.attributes as LensDocShape715).state.visualization = ({ + (datatableExample.attributes as LensDocShape715).state.visualization = { palette: { type: 'palette', name: 'temperature1', @@ -1373,13 +1373,15 @@ describe('Lens migrations', () => { ], }, }, - } as unknown) as VisState716; + } as unknown as VisState716; const result = migrations['7.16.0'](datatableExample, context) as ReturnType< SavedObjectMigrationFn >; - const state = (result.attributes as LensDocShape715< - Extract }> - >).state.visualization; + const state = ( + result.attributes as LensDocShape715< + Extract }> + > + ).state.visualization; expect(state.palette!.name).toBe('custom'); expect(state.palette!.params!.name).toBe('custom'); expect(state.palette!.params!.rangeMin).toBe(0); diff --git a/x-pack/plugins/lens/server/migrations/saved_object_migrations.ts b/x-pack/plugins/lens/server/migrations/saved_object_migrations.ts index 901f0b5d6e684..5982d9ea1e9b5 100644 --- a/x-pack/plugins/lens/server/migrations/saved_object_migrations.ts +++ b/x-pack/plugins/lens/server/migrations/saved_object_migrations.ts @@ -258,8 +258,8 @@ const removeInvalidAccessors: SavedObjectMigrationFn< if (newDoc.attributes.visualizationType === 'lnsXY') { const datasourceLayers = newDoc.attributes.state.datasourceStates.indexpattern.layers || {}; const xyState = newDoc.attributes.state.visualization; - (newDoc.attributes as LensDocShapePre710).state.visualization.layers = xyState.layers.map( - (layer: XYLayerPre77) => { + (newDoc.attributes as LensDocShapePre710).state.visualization.layers = + xyState.layers.map((layer: XYLayerPre77) => { const layerId = layer.layerId; const datasource = datasourceLayers[layerId]; return { @@ -268,8 +268,7 @@ const removeInvalidAccessors: SavedObjectMigrationFn< splitAccessor: datasource?.columns[layer.splitAccessor] ? layer.splitAccessor : undefined, accessors: layer.accessors.filter((accessor) => !!datasource?.columns[accessor]), }; - } - ); + }); } return newDoc; }; @@ -379,7 +378,7 @@ const transformTableState: SavedObjectMigrationFn< > = (doc) => { // nothing to do for non-datatable visualizations if (doc.attributes.visualizationType !== 'lnsDatatable') - return (doc as unknown) as SavedObjectUnsanitizedDoc>; + return doc as unknown as SavedObjectUnsanitizedDoc>; const oldState = doc.attributes.state.visualization; const layer = oldState.layers[0] || { layerId: '', @@ -403,16 +402,14 @@ const transformTableState: SavedObjectMigrationFn< return newDoc; }; -const renameOperationsForFormula: SavedObjectMigrationFn< - LensDocShapePre712, - LensDocShapePost712 -> = (doc) => { - const newDoc = cloneDeep(doc); - return { - ...newDoc, - attributes: commonRenameOperationsForFormula(newDoc.attributes), +const renameOperationsForFormula: SavedObjectMigrationFn = + (doc) => { + const newDoc = cloneDeep(doc); + return { + ...newDoc, + attributes: commonRenameOperationsForFormula(newDoc.attributes), + }; }; -}; const removeTimezoneDateHistogramParam: SavedObjectMigrationFn = ( doc diff --git a/x-pack/plugins/lens/server/routes/existing_fields.test.ts b/x-pack/plugins/lens/server/routes/existing_fields.test.ts index 703b946149e26..3664c01d0e1e0 100644 --- a/x-pack/plugins/lens/server/routes/existing_fields.test.ts +++ b/x-pack/plugins/lens/server/routes/existing_fields.test.ts @@ -111,7 +111,7 @@ describe('buildFieldList', () => { }; it('supports scripted fields', () => { - const fields = buildFieldList((indexPattern as unknown) as IndexPattern, []); + const fields = buildFieldList(indexPattern as unknown as IndexPattern, []); expect(fields.find((f) => f.isScript)).toMatchObject({ isScript: true, name: 'foo', @@ -121,7 +121,7 @@ describe('buildFieldList', () => { }); it('supports runtime fields', () => { - const fields = buildFieldList((indexPattern as unknown) as IndexPattern, []); + const fields = buildFieldList(indexPattern as unknown as IndexPattern, []); expect(fields.find((f) => f.runtimeField)).toMatchObject({ name: 'runtime_foo', runtimeField: { type: 'long', script: { source: '2+2' } }, @@ -129,7 +129,7 @@ describe('buildFieldList', () => { }); it('supports meta fields', () => { - const fields = buildFieldList((indexPattern as unknown) as IndexPattern, ['_mymeta']); + const fields = buildFieldList(indexPattern as unknown as IndexPattern, ['_mymeta']); expect(fields.find((f) => f.isMeta)).toMatchObject({ isScript: false, isMeta: true, diff --git a/x-pack/plugins/license_management/public/application/store/actions/permissions.js b/x-pack/plugins/license_management/public/application/store/actions/permissions.js index 29df6fbf22446..c1681aa21b3da 100644 --- a/x-pack/plugins/license_management/public/application/store/actions/permissions.js +++ b/x-pack/plugins/license_management/public/application/store/actions/permissions.js @@ -14,14 +14,16 @@ export const permissionsSuccess = createAction('LICENSE_MANAGEMENT_PERMISSIONS_S export const permissionsError = createAction('LICENSE_MANAGEMENT_PERMISSIONS_ERROR'); -export const loadPermissions = () => async (dispatch, getState, { http }) => { - dispatch(permissionsLoading(true)); - try { - const permissions = await getPermissions(http); - dispatch(permissionsLoading(false)); - dispatch(permissionsSuccess(permissions.hasPermission)); - } catch (e) { - dispatch(permissionsLoading(false)); - dispatch(permissionsError(e)); - } -}; +export const loadPermissions = + () => + async (dispatch, getState, { http }) => { + dispatch(permissionsLoading(true)); + try { + const permissions = await getPermissions(http); + dispatch(permissionsLoading(false)); + dispatch(permissionsSuccess(permissions.hasPermission)); + } catch (e) { + dispatch(permissionsLoading(false)); + dispatch(permissionsError(e)); + } + }; diff --git a/x-pack/plugins/license_management/public/application/store/actions/set_breadcrumb.ts b/x-pack/plugins/license_management/public/application/store/actions/set_breadcrumb.ts index 58e9de74a0ee9..ad3540a20c34c 100644 --- a/x-pack/plugins/license_management/public/application/store/actions/set_breadcrumb.ts +++ b/x-pack/plugins/license_management/public/application/store/actions/set_breadcrumb.ts @@ -8,12 +8,10 @@ import { ThunkAction } from 'redux-thunk'; import { BreadcrumbService } from '../../breadcrumbs'; -export const setBreadcrumb = ( - section: 'dashboard' | 'upload' -): ThunkAction => ( - dispatch, - getState, - { breadcrumbService } -) => { - breadcrumbService.setBreadcrumbs(section); -}; +export const setBreadcrumb = + ( + section: 'dashboard' | 'upload' + ): ThunkAction => + (dispatch, getState, { breadcrumbService }) => { + breadcrumbService.setBreadcrumbs(section); + }; diff --git a/x-pack/plugins/license_management/public/application/store/actions/start_basic.js b/x-pack/plugins/license_management/public/application/store/actions/start_basic.js index 5643500ca9e46..c103fff81c822 100644 --- a/x-pack/plugins/license_management/public/application/store/actions/start_basic.js +++ b/x-pack/plugins/license_management/public/application/store/actions/start_basic.js @@ -17,42 +17,40 @@ export const cancelStartBasicLicense = createAction( 'LICENSE_MANAGEMENT_CANCEL_START_BASIC_LICENSE' ); -export const startBasicLicense = (currentLicenseType, ack) => async ( - dispatch, - getState, - { licensing, toasts, http } -) => { - /*eslint camelcase: 0*/ - const { acknowledged, basic_was_started, error_message, acknowledge } = await startBasic( - http, - ack - ); - if (acknowledged) { - if (basic_was_started) { - await licensing.refresh(); - // reload necessary to get left nav to refresh with proper links - window.location.reload(); +export const startBasicLicense = + (currentLicenseType, ack) => + async (dispatch, getState, { licensing, toasts, http }) => { + /*eslint camelcase: 0*/ + const { acknowledged, basic_was_started, error_message, acknowledge } = await startBasic( + http, + ack + ); + if (acknowledged) { + if (basic_was_started) { + await licensing.refresh(); + // reload necessary to get left nav to refresh with proper links + window.location.reload(); + } else { + return toasts.addDanger(error_message); + } } else { - return toasts.addDanger(error_message); - } - } else { - //messages coming back in arrays - const messages = Object.values(acknowledge) - .slice(1) - .map((item) => { - return item[0]; - }); - const first = i18n.translate( - 'xpack.licenseMgmt.replacingCurrentLicenseWithBasicLicenseWarningMessage', - { - //eslint-disable-next-line + //messages coming back in arrays + const messages = Object.values(acknowledge) + .slice(1) + .map((item) => { + return item[0]; + }); + const first = i18n.translate( + 'xpack.licenseMgmt.replacingCurrentLicenseWithBasicLicenseWarningMessage', + { + //eslint-disable-next-line defaultMessage: - 'Some functionality will be lost if you replace your {currentLicenseType} license with a BASIC license. Review the list of features below.', - values: { - currentLicenseType: currentLicenseType.toUpperCase(), - }, - } - ); - dispatch(startBasicLicenseStatus({ acknowledge: true, messages: [first, ...messages] })); - } -}; + 'Some functionality will be lost if you replace your {currentLicenseType} license with a BASIC license. Review the list of features below.', + values: { + currentLicenseType: currentLicenseType.toUpperCase(), + }, + } + ); + dispatch(startBasicLicenseStatus({ acknowledge: true, messages: [first, ...messages] })); + } + }; diff --git a/x-pack/plugins/license_management/public/application/store/actions/start_trial.js b/x-pack/plugins/license_management/public/application/store/actions/start_trial.js index 77c9db747af5a..476a1180db4c5 100644 --- a/x-pack/plugins/license_management/public/application/store/actions/start_trial.js +++ b/x-pack/plugins/license_management/public/application/store/actions/start_trial.js @@ -10,19 +10,23 @@ import { canStartTrial, startTrial } from '../../lib/es'; export const trialStatusLoaded = createAction('LICENSE_MANAGEMENT_TRIAL_STATUS_LOADED'); -export const loadTrialStatus = () => async (dispatch, getState, { http }) => { - const trialOK = await canStartTrial(http); - dispatch(trialStatusLoaded(trialOK)); -}; +export const loadTrialStatus = + () => + async (dispatch, getState, { http }) => { + const trialOK = await canStartTrial(http); + dispatch(trialStatusLoaded(trialOK)); + }; -export const startLicenseTrial = () => async (dispatch, getState, { licensing, toasts, http }) => { - /*eslint camelcase: 0*/ - const { trial_was_started, error_message } = await startTrial(http); - if (trial_was_started) { - await licensing.refresh(); - // reload necessary to get left nav to refresh with proper links - window.location.reload(); - } else { - return toasts.addDanger(error_message); - } -}; +export const startLicenseTrial = + () => + async (dispatch, getState, { licensing, toasts, http }) => { + /*eslint camelcase: 0*/ + const { trial_was_started, error_message } = await startTrial(http); + if (trial_was_started) { + await licensing.refresh(); + // reload necessary to get left nav to refresh with proper links + window.location.reload(); + } else { + return toasts.addDanger(error_message); + } + }; diff --git a/x-pack/plugins/license_management/public/application/store/actions/upload_license.js b/x-pack/plugins/license_management/public/application/store/actions/upload_license.js index e319a9bda4eeb..acbf4b30440c4 100644 --- a/x-pack/plugins/license_management/public/application/store/actions/upload_license.js +++ b/x-pack/plugins/license_management/public/application/store/actions/upload_license.js @@ -77,39 +77,36 @@ const dispatchFromResponse = async ( } }; -export const uploadLicense = (licenseString, currentLicenseType, acknowledge) => async ( - dispatch, - getState, - services -) => { - dispatch(uploadLicenseStatus({ applying: true })); - let newLicenseType = null; - try { - ({ type: newLicenseType } = JSON.parse(licenseString).license); - } catch (err) { - dispatch(uploadLicenseStatus({})); - return dispatch( - addUploadErrorMessage( - i18n.translate('xpack.licenseMgmt.uploadLicense.checkLicenseFileErrorMessage', { - defaultMessage: '{genericUploadError} Check your license file.', - values: { - genericUploadError, - }, - }) - ) - ); - } - try { - const response = await putLicense(services.http, licenseString, acknowledge); - await dispatchFromResponse(response, dispatch, currentLicenseType, newLicenseType, services); - } catch (err) { - const message = - err.responseJSON && err.responseJSON.error.reason - ? err.responseJSON.error.reason - : i18n.translate('xpack.licenseMgmt.uploadLicense.unknownErrorErrorMessage', { - defaultMessage: 'Unknown error.', - }); - dispatch(uploadLicenseStatus({})); - dispatch(addUploadErrorMessage(`${genericUploadError} ${message}`)); - } -}; +export const uploadLicense = + (licenseString, currentLicenseType, acknowledge) => async (dispatch, getState, services) => { + dispatch(uploadLicenseStatus({ applying: true })); + let newLicenseType = null; + try { + ({ type: newLicenseType } = JSON.parse(licenseString).license); + } catch (err) { + dispatch(uploadLicenseStatus({})); + return dispatch( + addUploadErrorMessage( + i18n.translate('xpack.licenseMgmt.uploadLicense.checkLicenseFileErrorMessage', { + defaultMessage: '{genericUploadError} Check your license file.', + values: { + genericUploadError, + }, + }) + ) + ); + } + try { + const response = await putLicense(services.http, licenseString, acknowledge); + await dispatchFromResponse(response, dispatch, currentLicenseType, newLicenseType, services); + } catch (err) { + const message = + err.responseJSON && err.responseJSON.error.reason + ? err.responseJSON.error.reason + : i18n.translate('xpack.licenseMgmt.uploadLicense.unknownErrorErrorMessage', { + defaultMessage: 'Unknown error.', + }); + dispatch(uploadLicenseStatus({})); + dispatch(addUploadErrorMessage(`${genericUploadError} ${message}`)); + } + }; diff --git a/x-pack/plugins/license_management/public/plugin.ts b/x-pack/plugins/license_management/public/plugin.ts index 5fb796d3e77fb..22fd5d756f160 100644 --- a/x-pack/plugins/license_management/public/plugin.ts +++ b/x-pack/plugins/license_management/public/plugin.ts @@ -31,7 +31,8 @@ export interface LicenseManagementUIPluginSetup { export type LicenseManagementUIPluginStart = void; export class LicenseManagementUIPlugin - implements Plugin { + implements Plugin +{ private breadcrumbService = new BreadcrumbService(); constructor(private readonly initializerContext: PluginInitializerContext) {} diff --git a/x-pack/plugins/license_management/server/index.ts b/x-pack/plugins/license_management/server/index.ts index 7a24845c981e9..e78ffe07b50c0 100644 --- a/x-pack/plugins/license_management/server/index.ts +++ b/x-pack/plugins/license_management/server/index.ts @@ -17,4 +17,5 @@ export const config: PluginConfigDescriptor = { exposeToBrowser: { ui: true, }, + deprecations: ({ deprecate }) => [deprecate('enabled', '8.0.0')], }; diff --git a/x-pack/plugins/license_management/server/plugin.ts b/x-pack/plugins/license_management/server/plugin.ts index 5772b8d9a6518..daf872148de0a 100644 --- a/x-pack/plugins/license_management/server/plugin.ts +++ b/x-pack/plugins/license_management/server/plugin.ts @@ -12,7 +12,8 @@ import { handleEsError } from './shared_imports'; import { SetupDependencies, StartDependencies } from './types'; export class LicenseManagementServerPlugin - implements Plugin { + implements Plugin +{ private readonly apiRoutes = new ApiRoutes(); setup( diff --git a/x-pack/plugins/lists/common/schemas/request/create_exception_list_item_schema.mock.ts b/x-pack/plugins/lists/common/schemas/request/create_exception_list_item_schema.mock.ts index 447e79e4f77d7..d4e444498e7a4 100644 --- a/x-pack/plugins/lists/common/schemas/request/create_exception_list_item_schema.mock.ts +++ b/x-pack/plugins/lists/common/schemas/request/create_exception_list_item_schema.mock.ts @@ -51,11 +51,12 @@ export const getCreateExceptionListItemMinimalSchemaMock = (): CreateExceptionLi /** * Useful for end to end testing */ -export const getCreateExceptionListItemMinimalSchemaMockWithoutId = (): CreateExceptionListItemSchema => ({ - description: DESCRIPTION, - entries: ENTRIES, - list_id: LIST_ID, - name: NAME, - os_types: OS_TYPES, - type: ITEM_TYPE, -}); +export const getCreateExceptionListItemMinimalSchemaMockWithoutId = + (): CreateExceptionListItemSchema => ({ + description: DESCRIPTION, + entries: ENTRIES, + list_id: LIST_ID, + name: NAME, + os_types: OS_TYPES, + type: ITEM_TYPE, + }); diff --git a/x-pack/plugins/lists/common/schemas/response/exception_list_item_schema.mock.ts b/x-pack/plugins/lists/common/schemas/response/exception_list_item_schema.mock.ts index d04080e8a56c0..b49ad7150bdbc 100644 --- a/x-pack/plugins/lists/common/schemas/response/exception_list_item_schema.mock.ts +++ b/x-pack/plugins/lists/common/schemas/response/exception_list_item_schema.mock.ts @@ -52,17 +52,18 @@ export const getExceptionListItemSchemaMock = ( * This is useful for end to end tests where we remove the auto generated parts for comparisons * such as created_at, updated_at, and id. */ -export const getExceptionListItemResponseMockWithoutAutoGeneratedValues = (): Partial => ({ - comments: [], - created_by: ELASTIC_USER, - description: DESCRIPTION, - entries: ENTRIES, - item_id: ITEM_ID, - list_id: LIST_ID, - name: NAME, - namespace_type: 'single', - os_types: OS_TYPES, - tags: [], - type: ITEM_TYPE, - updated_by: ELASTIC_USER, -}); +export const getExceptionListItemResponseMockWithoutAutoGeneratedValues = + (): Partial => ({ + comments: [], + created_by: ELASTIC_USER, + description: DESCRIPTION, + entries: ENTRIES, + item_id: ITEM_ID, + list_id: LIST_ID, + name: NAME, + namespace_type: 'single', + os_types: OS_TYPES, + tags: [], + type: ITEM_TYPE, + updated_by: ELASTIC_USER, + }); diff --git a/x-pack/plugins/lists/common/schemas/response/exception_list_schema.mock.ts b/x-pack/plugins/lists/common/schemas/response/exception_list_schema.mock.ts index f1062d7ff1f4a..42c35ba1a5d7a 100644 --- a/x-pack/plugins/lists/common/schemas/response/exception_list_schema.mock.ts +++ b/x-pack/plugins/lists/common/schemas/response/exception_list_schema.mock.ts @@ -61,16 +61,17 @@ export const getTrustedAppsListSchemaMock = (): ExceptionListSchema => { * This is useful for end to end tests where we remove the auto generated parts for comparisons * such as created_at, updated_at, and id. */ -export const getExceptionResponseMockWithoutAutoGeneratedValues = (): Partial => ({ - created_by: ELASTIC_USER, - description: DESCRIPTION, - immutable: IMMUTABLE, - list_id: LIST_ID, - name: NAME, - namespace_type: 'single', - os_types: [], - tags: [], - type: ENDPOINT_TYPE, - updated_by: ELASTIC_USER, - version: VERSION, -}); +export const getExceptionResponseMockWithoutAutoGeneratedValues = + (): Partial => ({ + created_by: ELASTIC_USER, + description: DESCRIPTION, + immutable: IMMUTABLE, + list_id: LIST_ID, + name: NAME, + namespace_type: 'single', + os_types: [], + tags: [], + type: ENDPOINT_TYPE, + updated_by: ELASTIC_USER, + version: VERSION, + }); diff --git a/x-pack/plugins/lists/public/exceptions/components/and_or_badge/rounded_badge.tsx b/x-pack/plugins/lists/public/exceptions/components/and_or_badge/rounded_badge.tsx index 0e8a8ee823593..31e06ded33526 100644 --- a/x-pack/plugins/lists/public/exceptions/components/and_or_badge/rounded_badge.tsx +++ b/x-pack/plugins/lists/public/exceptions/components/and_or_badge/rounded_badge.tsx @@ -13,7 +13,7 @@ import * as i18n from './translations'; import { AndOr } from '.'; -const RoundBadge = (styled(EuiBadge)` +const RoundBadge = styled(EuiBadge)` align-items: center; border-radius: 100%; display: inline-flex; @@ -31,7 +31,7 @@ const RoundBadge = (styled(EuiBadge)` .euiBadge__text { text-overflow: clip; } -` as unknown) as typeof EuiBadge; +` as unknown as typeof EuiBadge; RoundBadge.displayName = 'RoundBadge'; diff --git a/x-pack/plugins/lists/public/exceptions/components/builder/builder.stories.tsx b/x-pack/plugins/lists/public/exceptions/components/builder/builder.stories.tsx index da320e871072c..73b7fe53b1ccc 100644 --- a/x-pack/plugins/lists/public/exceptions/components/builder/builder.stories.tsx +++ b/x-pack/plugins/lists/public/exceptions/components/builder/builder.stories.tsx @@ -11,10 +11,7 @@ import { HttpStart } from 'kibana/public'; import { AutocompleteStart } from '../../../../../../../src/plugins/data/public'; import { EuiThemeProvider } from '../../../../../../../src/plugins/kibana_react/common'; -import { - fields, - getField, -} from '../../../../../../../src/plugins/data/common/index_patterns/fields/fields.mocks'; +import { fields, getField } from '../../../../../../../src/plugins/data/common/mocks'; import { getEntryMatchAnyMock } from '../../../../common/schemas/types/entry_match_any.mock'; import { getEntryMatchMock } from '../../../../common/schemas/types/entry_match.mock'; import { getEntryExistsMock } from '../../../../common/schemas/types/entry_exists.mock'; @@ -27,7 +24,7 @@ import { OnChangeProps, } from './exception_items_renderer'; -const mockHttpService: HttpStart = ({ +const mockHttpService: HttpStart = { addLoadingCountSource: (): void => {}, anonymousPaths: { isAnonymous: (): void => {}, @@ -47,8 +44,8 @@ const mockHttpService: HttpStart = ({ patch: (): void => {}, post: (): void => {}, put: (): void => {}, -} as unknown) as HttpStart; -const mockAutocompleteService = ({ +} as unknown as HttpStart; +const mockAutocompleteService = { getValueSuggestions: () => new Promise((resolve) => { setTimeout(() => { @@ -62,7 +59,7 @@ const mockAutocompleteService = ({ ]); }, 300); }), -} as unknown) as AutocompleteStart; +} as unknown as AutocompleteStart; addDecorator((storyFn) => {storyFn()}); diff --git a/x-pack/plugins/lists/public/exceptions/components/builder/entry_renderer.stories.tsx b/x-pack/plugins/lists/public/exceptions/components/builder/entry_renderer.stories.tsx index 9bf2ca0fc017a..b1357f35d140b 100644 --- a/x-pack/plugins/lists/public/exceptions/components/builder/entry_renderer.stories.tsx +++ b/x-pack/plugins/lists/public/exceptions/components/builder/entry_renderer.stories.tsx @@ -15,12 +15,12 @@ import { } from '@kbn/securitysolution-io-ts-list-types'; import { AutocompleteStart } from '../../../../../../../src/plugins/data/public'; -import { fields } from '../../../../../../../src/plugins/data/common/index_patterns/fields/fields.mocks'; +import { fields } from '../../../../../../../src/plugins/data/common/mocks'; import { EuiThemeProvider } from '../../../../../../../src/plugins/kibana_react/common'; import { BuilderEntryItem, EntryItemProps } from './entry_renderer'; -const mockAutocompleteService = ({ +const mockAutocompleteService = { getValueSuggestions: () => new Promise((resolve) => { setTimeout(() => { @@ -54,7 +54,7 @@ const mockAutocompleteService = ({ ]); }, 300); }), -} as unknown) as AutocompleteStart; +} as unknown as AutocompleteStart; export default { argTypes: { diff --git a/x-pack/plugins/lists/public/exceptions/components/builder/entry_renderer.test.tsx b/x-pack/plugins/lists/public/exceptions/components/builder/entry_renderer.test.tsx index f692ad96988cf..a06115c970221 100644 --- a/x-pack/plugins/lists/public/exceptions/components/builder/entry_renderer.test.tsx +++ b/x-pack/plugins/lists/public/exceptions/components/builder/entry_renderer.test.tsx @@ -22,10 +22,7 @@ import { import { useFindLists } from '@kbn/securitysolution-list-hooks'; import { FieldSpec } from 'src/plugins/data/common'; -import { - fields, - getField, -} from '../../../../../../../src/plugins/data/common/index_patterns/fields/fields.mocks'; +import { fields, getField } from '../../../../../../../src/plugins/data/common/mocks'; import { dataPluginMock } from '../../../../../../../src/plugins/data/public/mocks'; import { coreMock } from '../../../../../../../src/core/public/mocks'; import { getFoundListSchemaMock } from '../../../../common/schemas/response/found_list_schema.mock'; @@ -463,9 +460,11 @@ describe('BuilderEntryItem', () => { /> ); - ((wrapper.find(EuiComboBox).at(0).props() as unknown) as { - onChange: (a: EuiComboBoxOptionOption[]) => void; - }).onChange([{ label: 'machine.os' }]); + ( + wrapper.find(EuiComboBox).at(0).props() as unknown as { + onChange: (a: EuiComboBoxOptionOption[]) => void; + } + ).onChange([{ label: 'machine.os' }]); expect(mockOnChange).toHaveBeenCalledWith( { field: 'machine.os', id: '123', operator: 'included', type: 'match', value: '' }, @@ -501,9 +500,11 @@ describe('BuilderEntryItem', () => { /> ); - ((wrapper.find(EuiComboBox).at(1).props() as unknown) as { - onChange: (a: EuiComboBoxOptionOption[]) => void; - }).onChange([{ label: 'is not' }]); + ( + wrapper.find(EuiComboBox).at(1).props() as unknown as { + onChange: (a: EuiComboBoxOptionOption[]) => void; + } + ).onChange([{ label: 'is not' }]); expect(mockOnChange).toHaveBeenCalledWith( { field: 'ip', id: '123', operator: 'excluded', type: 'match', value: '1234' }, @@ -539,9 +540,11 @@ describe('BuilderEntryItem', () => { /> ); - ((wrapper.find(EuiComboBox).at(2).props() as unknown) as { - onCreateOption: (a: string) => void; - }).onCreateOption('127.0.0.1'); + ( + wrapper.find(EuiComboBox).at(2).props() as unknown as { + onCreateOption: (a: string) => void; + } + ).onCreateOption('127.0.0.1'); expect(mockOnChange).toHaveBeenCalledWith( { field: 'ip', id: '123', operator: 'excluded', type: 'match', value: '127.0.0.1' }, @@ -577,9 +580,11 @@ describe('BuilderEntryItem', () => { /> ); - ((wrapper.find(EuiComboBox).at(2).props() as unknown) as { - onCreateOption: (a: string) => void; - }).onCreateOption('127.0.0.1'); + ( + wrapper.find(EuiComboBox).at(2).props() as unknown as { + onCreateOption: (a: string) => void; + } + ).onCreateOption('127.0.0.1'); expect(mockOnChange).toHaveBeenCalledWith( { field: 'ip', id: '123', operator: 'included', type: 'match_any', value: ['127.0.0.1'] }, @@ -615,9 +620,11 @@ describe('BuilderEntryItem', () => { /> ); - ((wrapper.find(EuiComboBox).at(2).props() as unknown) as { - onChange: (a: EuiComboBoxOptionOption[]) => void; - }).onChange([{ label: 'some name' }]); + ( + wrapper.find(EuiComboBox).at(2).props() as unknown as { + onChange: (a: EuiComboBoxOptionOption[]) => void; + } + ).onChange([{ label: 'some name' }]); expect(mockOnChange).toHaveBeenCalledWith( { @@ -660,9 +667,11 @@ describe('BuilderEntryItem', () => { ); await waitFor(() => { - ((wrapper.find(EuiComboBox).at(2).props() as unknown) as { - onBlur: () => void; - }).onBlur(); + ( + wrapper.find(EuiComboBox).at(2).props() as unknown as { + onBlur: () => void; + } + ).onBlur(); }); expect(mockSetErrorExists).toHaveBeenCalledWith(true); @@ -697,14 +706,18 @@ describe('BuilderEntryItem', () => { ); await waitFor(() => { - ((wrapper.find(EuiComboBox).at(2).props() as unknown) as { - onBlur: () => void; - }).onBlur(); + ( + wrapper.find(EuiComboBox).at(2).props() as unknown as { + onBlur: () => void; + } + ).onBlur(); // Invalid input because field type is number - ((wrapper.find(EuiComboBox).at(2).props() as unknown) as { - onSearchChange: (arg: string) => void; - }).onSearchChange('hellooo'); + ( + wrapper.find(EuiComboBox).at(2).props() as unknown as { + onSearchChange: (arg: string) => void; + } + ).onSearchChange('hellooo'); }); expect(mockSetErrorExists).toHaveBeenCalledWith(true); diff --git a/x-pack/plugins/lists/public/exceptions/components/builder/exception_item_renderer.test.tsx b/x-pack/plugins/lists/public/exceptions/components/builder/exception_item_renderer.test.tsx index b896f2a44f67b..ccda52e280586 100644 --- a/x-pack/plugins/lists/public/exceptions/components/builder/exception_item_renderer.test.tsx +++ b/x-pack/plugins/lists/public/exceptions/components/builder/exception_item_renderer.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { mount } from 'enzyme'; import { dataPluginMock } from 'src/plugins/data/public/mocks'; -import { fields } from '../../../../../../../src/plugins/data/common/index_patterns/fields/fields.mocks'; +import { fields } from '../../../../../../../src/plugins/data/common/mocks'; import { EuiThemeProvider } from '../../../../../../../src/plugins/kibana_react/common'; import { getExceptionListItemSchemaMock } from '../../../../common/schemas/response/exception_list_item_schema.mock'; import { getEntryMatchMock } from '../../../../common/schemas/types/entry_match.mock'; diff --git a/x-pack/plugins/lists/public/exceptions/components/builder/exception_items_renderer.test.tsx b/x-pack/plugins/lists/public/exceptions/components/builder/exception_items_renderer.test.tsx index 532f3457ca645..d9dfbfeee299d 100644 --- a/x-pack/plugins/lists/public/exceptions/components/builder/exception_items_renderer.test.tsx +++ b/x-pack/plugins/lists/public/exceptions/components/builder/exception_items_renderer.test.tsx @@ -12,10 +12,7 @@ import { coreMock } from 'src/core/public/mocks'; import { dataPluginMock } from 'src/plugins/data/public/mocks'; import { EuiThemeProvider } from '../../../../../../../src/plugins/kibana_react/common'; -import { - fields, - getField, -} from '../../../../../../../src/plugins/data/common/index_patterns/fields/fields.mocks'; +import { fields, getField } from '../../../../../../../src/plugins/data/common/mocks'; import { getExceptionListItemSchemaMock } from '../../../../common/schemas/response/exception_list_item_schema.mock'; import { getEntryMatchAnyMock } from '../../../../common/schemas/types/entry_match_any.mock'; import { getEmptyValue } from '../../../common/empty_value'; diff --git a/x-pack/plugins/lists/public/exceptions/components/builder/helpers.test.ts b/x-pack/plugins/lists/public/exceptions/components/builder/helpers.test.ts index 8592408dde56e..fab49453958dd 100644 --- a/x-pack/plugins/lists/public/exceptions/components/builder/helpers.test.ts +++ b/x-pack/plugins/lists/public/exceptions/components/builder/helpers.test.ts @@ -57,10 +57,7 @@ import { IndexPatternBase, IndexPatternFieldBase } from '@kbn/es-query'; import { ENTRIES_WITH_IDS } from '../../../../common/constants.mock'; import { getEntryExistsMock } from '../../../../common/schemas/types/entry_exists.mock'; import { getExceptionListItemSchemaMock } from '../../../../common/schemas/response/exception_list_item_schema.mock'; -import { - fields, - getField, -} from '../../../../../../../src/plugins/data/common/index_patterns/fields/fields.mocks'; +import { fields, getField } from '../../../../../../../src/plugins/data/common/mocks'; import { FieldSpec } from '../../../../../../../src/plugins/data/common'; import { getEntryNestedMock } from '../../../../common/schemas/types/entry_nested.mock'; import { getEntryMatchMock } from '../../../../common/schemas/types/entry_match.mock'; @@ -1554,11 +1551,8 @@ describe('Exception builder helpers', () => { // Please see `x-pack/plugins/lists/public/exceptions/transforms.ts` doc notes // for context around the temporary `id` test('it correctly validates entries that include a temporary `id`', () => { - const output: Array< - ExceptionListItemSchema | CreateExceptionListItemSchema - > = filterExceptionItems([ - { ...getExceptionListItemSchemaMock(), entries: ENTRIES_WITH_IDS }, - ]); + const output: Array = + filterExceptionItems([{ ...getExceptionListItemSchemaMock(), entries: ENTRIES_WITH_IDS }]); expect(output).toEqual([{ ...getExceptionListItemSchemaMock(), entries: ENTRIES_WITH_IDS }]); }); @@ -1591,14 +1585,13 @@ describe('Exception builder helpers', () => { type: OperatorTypeEnum.MATCH, value: '', }; - const output: Array< - ExceptionListItemSchema | CreateExceptionListItemSchema - > = filterExceptionItems([ - { - ...rest, - entries: [...entries, mockEmptyException], - }, - ]); + const output: Array = + filterExceptionItems([ + { + ...rest, + entries: [...entries, mockEmptyException], + }, + ]); expect(output).toEqual([{ ...getExceptionListItemSchemaMock() }]); }); @@ -1612,14 +1605,13 @@ describe('Exception builder helpers', () => { type: OperatorTypeEnum.MATCH, value: 'some value', }; - const output: Array< - ExceptionListItemSchema | CreateExceptionListItemSchema - > = filterExceptionItems([ - { - ...rest, - entries: [...entries, mockEmptyException], - }, - ]); + const output: Array = + filterExceptionItems([ + { + ...rest, + entries: [...entries, mockEmptyException], + }, + ]); expect(output).toEqual([{ ...getExceptionListItemSchemaMock() }]); }); @@ -1633,14 +1625,13 @@ describe('Exception builder helpers', () => { type: OperatorTypeEnum.MATCH_ANY, value: ['some value'], }; - const output: Array< - ExceptionListItemSchema | CreateExceptionListItemSchema - > = filterExceptionItems([ - { - ...rest, - entries: [...entries, mockEmptyException], - }, - ]); + const output: Array = + filterExceptionItems([ + { + ...rest, + entries: [...entries, mockEmptyException], + }, + ]); expect(output).toEqual([{ ...getExceptionListItemSchemaMock() }]); }); @@ -1652,14 +1643,13 @@ describe('Exception builder helpers', () => { field: '', type: OperatorTypeEnum.NESTED, }; - const output: Array< - ExceptionListItemSchema | CreateExceptionListItemSchema - > = filterExceptionItems([ - { - ...rest, - entries: [...entries, mockEmptyException], - }, - ]); + const output: Array = + filterExceptionItems([ + { + ...rest, + entries: [...entries, mockEmptyException], + }, + ]); expect(output).toEqual([{ ...getExceptionListItemSchemaMock() }]); }); @@ -1671,14 +1661,13 @@ describe('Exception builder helpers', () => { field: 'host.name', type: OperatorTypeEnum.NESTED, }; - const output: Array< - ExceptionListItemSchema | CreateExceptionListItemSchema - > = filterExceptionItems([ - { - ...rest, - entries: [...entries, mockEmptyException], - }, - ]); + const output: Array = + filterExceptionItems([ + { + ...rest, + entries: [...entries, mockEmptyException], + }, + ]); expect(output).toEqual([ { @@ -1698,14 +1687,13 @@ describe('Exception builder helpers', () => { field: 'host.name', type: OperatorTypeEnum.NESTED, }; - const output: Array< - ExceptionListItemSchema | CreateExceptionListItemSchema - > = filterExceptionItems([ - { - ...rest, - entries: [...entries, mockEmptyException], - }, - ]); + const output: Array = + filterExceptionItems([ + { + ...rest, + entries: [...entries, mockEmptyException], + }, + ]); expect(output).toEqual([{ ...getExceptionListItemSchemaMock() }]); }); diff --git a/x-pack/plugins/lists/public/exceptions/components/builder/reducer.ts b/x-pack/plugins/lists/public/exceptions/components/builder/reducer.ts index 14744bc5cc773..4ace0c7d31ef8 100644 --- a/x-pack/plugins/lists/public/exceptions/components/builder/reducer.ts +++ b/x-pack/plugins/lists/public/exceptions/components/builder/reducer.ts @@ -58,75 +58,77 @@ export type Action = errorExists: boolean; }; -export const exceptionsBuilderReducer = () => (state: State, action: Action): State => { - switch (action.type) { - case 'setExceptions': { - const isAndLogicIncluded = - action.exceptions.filter(({ entries }) => entries.length > 1).length > 0; - const [lastExceptionItem] = action.exceptions.slice(-1); - const isAddNested = - lastExceptionItem != null - ? lastExceptionItem.entries.slice(-1).filter(({ type }) => type === 'nested').length > 0 - : false; - const lastEntry = lastExceptionItem != null ? lastExceptionItem.entries.slice(-1)[0] : null; - const isAndDisabled = - lastEntry != null && lastEntry.type === 'nested' && lastEntry.entries.length === 0; - const isOrDisabled = lastEntry != null && lastEntry.type === 'nested'; - const containsValueList = action.exceptions.some( - ({ entries }) => entries.filter(({ type }) => type === OperatorTypeEnum.LIST).length > 0 - ); +export const exceptionsBuilderReducer = + () => + (state: State, action: Action): State => { + switch (action.type) { + case 'setExceptions': { + const isAndLogicIncluded = + action.exceptions.filter(({ entries }) => entries.length > 1).length > 0; + const [lastExceptionItem] = action.exceptions.slice(-1); + const isAddNested = + lastExceptionItem != null + ? lastExceptionItem.entries.slice(-1).filter(({ type }) => type === 'nested').length > 0 + : false; + const lastEntry = lastExceptionItem != null ? lastExceptionItem.entries.slice(-1)[0] : null; + const isAndDisabled = + lastEntry != null && lastEntry.type === 'nested' && lastEntry.entries.length === 0; + const isOrDisabled = lastEntry != null && lastEntry.type === 'nested'; + const containsValueList = action.exceptions.some( + ({ entries }) => entries.filter(({ type }) => type === OperatorTypeEnum.LIST).length > 0 + ); - return { - ...state, - addNested: isAddNested, - andLogicIncluded: isAndLogicIncluded, - disableAnd: isAndDisabled, - disableNested: containsValueList, - disableOr: isOrDisabled, - exceptions: action.exceptions, - }; - } - case 'setDefault': { - return { - ...state, - ...action.initialState, - exceptions: [{ ...action.lastException, entries: [getDefaultEmptyEntry()] }], - }; - } - case 'setExceptionsToDelete': { - return { - ...state, - exceptionsToDelete: action.exceptions, - }; - } - case 'setDisableAnd': { - return { - ...state, - disableAnd: action.shouldDisable, - }; - } - case 'setDisableOr': { - return { - ...state, - disableOr: action.shouldDisable, - }; - } - case 'setAddNested': { - return { - ...state, - addNested: action.addNested, - }; - } - case 'setErrorsExist': { - const { errorExists } = state; - const errTotal = action.errorExists ? errorExists + 1 : errorExists - 1; + return { + ...state, + addNested: isAddNested, + andLogicIncluded: isAndLogicIncluded, + disableAnd: isAndDisabled, + disableNested: containsValueList, + disableOr: isOrDisabled, + exceptions: action.exceptions, + }; + } + case 'setDefault': { + return { + ...state, + ...action.initialState, + exceptions: [{ ...action.lastException, entries: [getDefaultEmptyEntry()] }], + }; + } + case 'setExceptionsToDelete': { + return { + ...state, + exceptionsToDelete: action.exceptions, + }; + } + case 'setDisableAnd': { + return { + ...state, + disableAnd: action.shouldDisable, + }; + } + case 'setDisableOr': { + return { + ...state, + disableOr: action.shouldDisable, + }; + } + case 'setAddNested': { + return { + ...state, + addNested: action.addNested, + }; + } + case 'setErrorsExist': { + const { errorExists } = state; + const errTotal = action.errorExists ? errorExists + 1 : errorExists - 1; - return { - ...state, - errorExists: errTotal < 0 ? 0 : errTotal, - }; + return { + ...state, + errorExists: errTotal < 0 ? 0 : errTotal, + }; + } + default: + return state; } - default: - return state; - } -}; + }; diff --git a/x-pack/plugins/lists/public/exceptions/hooks/use_exception_list_items.test.ts b/x-pack/plugins/lists/public/exceptions/hooks/use_exception_list_items.test.ts index a33ede6395a73..ef05b5f4750a3 100644 --- a/x-pack/plugins/lists/public/exceptions/hooks/use_exception_list_items.test.ts +++ b/x-pack/plugins/lists/public/exceptions/hooks/use_exception_list_items.test.ts @@ -112,9 +112,8 @@ describe('useExceptionListItems', () => { await waitForNextUpdate(); await waitForNextUpdate(); - const expectedListItemsResult: ExceptionListItemSchema[] = getFoundExceptionListItemSchemaMock().data.map( - (item) => transformInput(item) - ); + const expectedListItemsResult: ExceptionListItemSchema[] = + getFoundExceptionListItemSchemaMock().data.map((item) => transformInput(item)); const expectedResult: UseExceptionListItemsSuccess = { exceptions: expectedListItemsResult, pagination: { page: 1, perPage: 1, total: 1 }, diff --git a/x-pack/plugins/lists/public/lists/api.test.ts b/x-pack/plugins/lists/public/lists/api.test.ts index 4681b81702b61..8002092235a81 100644 --- a/x-pack/plugins/lists/public/lists/api.test.ts +++ b/x-pack/plugins/lists/public/lists/api.test.ts @@ -74,7 +74,7 @@ describe('Value Lists API', () => { await expect( deleteList({ http: httpMock, - ...((payload as unknown) as ApiPayload), + ...(payload as unknown as ApiPayload), signal: abortCtrl.signal, }) ).rejects.toEqual(new Error('Invalid value "23" supplied to "id"')); @@ -201,7 +201,7 @@ describe('Value Lists API', () => { ); // httpmock's fetch signature is inferred incorrectly - const [[, { body }]] = (httpMock.fetch.mock.calls as unknown) as Array< + const [[, { body }]] = httpMock.fetch.mock.calls as unknown as Array< [unknown, HttpFetchOptions] >; const actualFile = (body as FormData).get('file'); @@ -231,7 +231,7 @@ describe('Value Lists API', () => { it('rejects with an error if request body is invalid (and does not make API call)', async () => { const abortCtrl = new AbortController(); const payload: ApiPayload = { - file: (undefined as unknown) as File, + file: undefined as unknown as File, listId: 'list-id', type: 'ip', }; @@ -326,7 +326,7 @@ describe('Value Lists API', () => { it('rejects with an error if request params are invalid (and does not make API call)', async () => { const abortCtrl = new AbortController(); const payload: ApiPayload = { - listId: (23 as unknown) as string, + listId: 23 as unknown as string, }; await expect( diff --git a/x-pack/plugins/lists/public/lists/hooks/use_import_list.test.ts b/x-pack/plugins/lists/public/lists/hooks/use_import_list.test.ts index a6955dcd08749..209ff113aeaca 100644 --- a/x-pack/plugins/lists/public/lists/hooks/use_import_list.test.ts +++ b/x-pack/plugins/lists/public/lists/hooks/use_import_list.test.ts @@ -30,7 +30,7 @@ describe('useImportList', () => { }); it('invokes Api.importList', async () => { - const fileMock = ('my file' as unknown) as File; + const fileMock = 'my file' as unknown as File; const { result, waitForNextUpdate } = renderHook(() => useImportList()); @@ -54,7 +54,7 @@ describe('useImportList', () => { }); it('populates result with the response of Api.importList', async () => { - const fileMock = ('my file' as unknown) as File; + const fileMock = 'my file' as unknown as File; const { result, waitForNextUpdate } = renderHook(() => useImportList()); @@ -72,7 +72,7 @@ describe('useImportList', () => { }); it('error is populated if importList rejects', async () => { - const fileMock = ('my file' as unknown) as File; + const fileMock = 'my file' as unknown as File; (Api.importList as jest.Mock).mockRejectedValue(new Error('whoops')); const { result, waitForNextUpdate } = renderHook(() => useImportList()); diff --git a/x-pack/plugins/lists/server/index.ts b/x-pack/plugins/lists/server/index.ts index 250b5e79ed109..7e1283927aa86 100644 --- a/x-pack/plugins/lists/server/index.ts +++ b/x-pack/plugins/lists/server/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { PluginInitializerContext } from '../../../../src/core/server'; +import { PluginConfigDescriptor, PluginInitializerContext } from '../../../../src/core/server'; import { ConfigSchema } from './config'; import { ListPlugin } from './plugin'; @@ -19,6 +19,9 @@ export { export { ExceptionListClient } from './services/exception_lists/exception_list_client'; export type { ListPluginSetup, ListsApiRequestHandlerContext } from './types'; -export const config = { schema: ConfigSchema }; +export const config: PluginConfigDescriptor = { + deprecations: ({ deprecate }) => [deprecate('enabled', '8.0.0')], + schema: ConfigSchema, +}; export const plugin = (initializerContext: PluginInitializerContext): ListPlugin => new ListPlugin(initializerContext); diff --git a/x-pack/plugins/lists/server/plugin.ts b/x-pack/plugins/lists/server/plugin.ts index 004677852d020..7fa1bc460723a 100644 --- a/x-pack/plugins/lists/server/plugin.ts +++ b/x-pack/plugins/lists/server/plugin.ts @@ -27,8 +27,7 @@ import { getUser } from './get_user'; import { initSavedObjects } from './saved_objects'; import { ExceptionListClient } from './services/exception_lists/exception_list_client'; -export class ListPlugin - implements Plugin, ListsPluginStart, {}, PluginsStart> { +export class ListPlugin implements Plugin { private readonly logger: Logger; private readonly config: ConfigType; private spaces: SpacesServiceStart | undefined | null; @@ -39,7 +38,7 @@ export class ListPlugin this.config = this.initializerContext.config.get(); } - public async setup(core: CoreSetup): Promise { + public setup(core: CoreSetup): ListPluginSetup { const { config } = this; initSavedObjects(core.savedObjects); @@ -69,7 +68,7 @@ export class ListPlugin }; } - public start(core: CoreStart, plugins: PluginsStart): void { + public start(core: CoreStart, plugins: PluginsStart): ListsPluginStart { this.logger.debug('Starting plugin'); this.security = plugins.security; this.spaces = plugins.spaces?.spacesService; diff --git a/x-pack/plugins/lists/server/routes/create_list_route.ts b/x-pack/plugins/lists/server/routes/create_list_route.ts index 898f9b142d9db..b259fe31b931d 100644 --- a/x-pack/plugins/lists/server/routes/create_list_route.ts +++ b/x-pack/plugins/lists/server/routes/create_list_route.ts @@ -36,16 +36,8 @@ export const createListRoute = (router: ListsPluginRouter): void => { async (context, request, response) => { const siemResponse = buildSiemResponse(response); try { - const { - name, - description, - deserializer, - id, - serializer, - type, - meta, - version, - } = request.body; + const { name, description, deserializer, id, serializer, type, meta, version } = + request.body; const lists = getListClient(context); const listExists = await lists.getListIndexExists(); if (!listExists) { diff --git a/x-pack/plugins/lists/server/routes/utils/route_validation.ts b/x-pack/plugins/lists/server/routes/utils/route_validation.ts index 8e74760d6d15f..680b67949bde5 100644 --- a/x-pack/plugins/lists/server/routes/utils/route_validation.ts +++ b/x-pack/plugins/lists/server/routes/utils/route_validation.ts @@ -40,17 +40,17 @@ type RequestValidationResult = * * TODO: Figure out a way to move this function into a package rather than copying it/forking it within plugins */ -export const buildRouteValidation = >( - schema: T -): RouteValidationFunction => ( - inputValue: unknown, - validationResult: RouteValidationResultFactory -): RequestValidationResult => - pipe( - schema.decode(inputValue), - (decoded) => exactCheck(inputValue, decoded), - fold>( - (errors: rt.Errors) => validationResult.badRequest(formatErrors(errors).join()), - (validatedInput: A) => validationResult.ok(validatedInput) - ) - ); +export const buildRouteValidation = + >(schema: T): RouteValidationFunction => + ( + inputValue: unknown, + validationResult: RouteValidationResultFactory + ): RequestValidationResult => + pipe( + schema.decode(inputValue), + (decoded) => exactCheck(inputValue, decoded), + fold>( + (errors: rt.Errors) => validationResult.badRequest(formatErrors(errors).join()), + (validatedInput: A) => validationResult.ok(validatedInput) + ) + ); diff --git a/x-pack/plugins/lists/server/services/exception_lists/utils.test.ts b/x-pack/plugins/lists/server/services/exception_lists/utils.test.ts index c06bc1aafe2cd..074fdf92e2ac0 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/utils.test.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/utils.test.ts @@ -20,7 +20,7 @@ describe('utils', () => { let clock: sinon.SinonFakeTimers; beforeEach(() => { - ((uuid.v4 as unknown) as jest.Mock) + (uuid.v4 as unknown as jest.Mock) .mockImplementationOnce(() => '123') .mockImplementationOnce(() => '456'); diff --git a/x-pack/plugins/lists/server/services/items/delete_list_item.test.ts b/x-pack/plugins/lists/server/services/items/delete_list_item.test.ts index de5b6540eee40..d2c424804297d 100644 --- a/x-pack/plugins/lists/server/services/items/delete_list_item.test.ts +++ b/x-pack/plugins/lists/server/services/items/delete_list_item.test.ts @@ -26,7 +26,7 @@ describe('delete_list_item', () => { }); test('Delete returns a null if "getListItem" returns a null', async () => { - ((getListItem as unknown) as jest.Mock).mockResolvedValueOnce(null); + (getListItem as unknown as jest.Mock).mockResolvedValueOnce(null); const options = getDeleteListItemOptionsMock(); const deletedListItem = await deleteListItem(options); expect(deletedListItem).toEqual(null); @@ -34,7 +34,7 @@ describe('delete_list_item', () => { test('Delete returns the same list item if a list item is returned from "getListItem"', async () => { const listItem = getListItemResponseMock(); - ((getListItem as unknown) as jest.Mock).mockResolvedValueOnce(listItem); + (getListItem as unknown as jest.Mock).mockResolvedValueOnce(listItem); const options = getDeleteListItemOptionsMock(); const deletedListItem = await deleteListItem(options); expect(deletedListItem).toEqual(listItem); @@ -42,7 +42,7 @@ describe('delete_list_item', () => { test('Delete calls "delete" if a list item is returned from "getListItem"', async () => { const listItem = getListItemResponseMock(); - ((getListItem as unknown) as jest.Mock).mockResolvedValueOnce(listItem); + (getListItem as unknown as jest.Mock).mockResolvedValueOnce(listItem); const options = getDeleteListItemOptionsMock(); await deleteListItem(options); const deleteQuery = { diff --git a/x-pack/plugins/lists/server/services/items/delete_list_item_by_value.test.ts b/x-pack/plugins/lists/server/services/items/delete_list_item_by_value.test.ts index 2755ff8e7aba6..91e67f8571c0a 100644 --- a/x-pack/plugins/lists/server/services/items/delete_list_item_by_value.test.ts +++ b/x-pack/plugins/lists/server/services/items/delete_list_item_by_value.test.ts @@ -25,7 +25,7 @@ describe('delete_list_item_by_value', () => { }); test('Delete returns a an empty array if the list items are also empty', async () => { - ((getListItemByValues as unknown) as jest.Mock).mockResolvedValueOnce([]); + (getListItemByValues as unknown as jest.Mock).mockResolvedValueOnce([]); const options = getDeleteListItemByValueOptionsMock(); const deletedListItem = await deleteListItemByValue(options); expect(deletedListItem).toEqual([]); @@ -33,7 +33,7 @@ describe('delete_list_item_by_value', () => { test('Delete returns the list item if a list item is returned from "getListByValues"', async () => { const listItems = [getListItemResponseMock()]; - ((getListItemByValues as unknown) as jest.Mock).mockResolvedValueOnce(listItems); + (getListItemByValues as unknown as jest.Mock).mockResolvedValueOnce(listItems); const options = getDeleteListItemByValueOptionsMock(); const deletedListItem = await deleteListItemByValue(options); expect(deletedListItem).toEqual(listItems); @@ -41,7 +41,7 @@ describe('delete_list_item_by_value', () => { test('Delete calls "deleteByQuery" if a list item is returned from "getListByValues"', async () => { const listItems = [getListItemResponseMock()]; - ((getListItemByValues as unknown) as jest.Mock).mockResolvedValueOnce(listItems); + (getListItemByValues as unknown as jest.Mock).mockResolvedValueOnce(listItems); const options = getDeleteListItemByValueOptionsMock(); await deleteListItemByValue(options); const deleteByQuery = { diff --git a/x-pack/plugins/lists/server/services/items/get_list_item_by_value.test.ts b/x-pack/plugins/lists/server/services/items/get_list_item_by_value.test.ts index 1b66d22ebb6c2..c4b3d6a6c5cc7 100644 --- a/x-pack/plugins/lists/server/services/items/get_list_item_by_value.test.ts +++ b/x-pack/plugins/lists/server/services/items/get_list_item_by_value.test.ts @@ -26,7 +26,7 @@ describe('get_list_by_value', () => { test('Calls get_list_item_by_values with its input', async () => { const listItemMock = getListItemResponseMock(); - ((getListItemByValues as unknown) as jest.Mock).mockResolvedValueOnce([listItemMock]); + (getListItemByValues as unknown as jest.Mock).mockResolvedValueOnce([listItemMock]); const options = getListItemByValueOptionsMocks(); const listItem = await getListItemByValue(options); const expected = getListItemResponseMock(); diff --git a/x-pack/plugins/lists/server/services/items/get_list_item_by_values.ts b/x-pack/plugins/lists/server/services/items/get_list_item_by_values.ts index 9c7709b6f4459..38e2283af98a7 100644 --- a/x-pack/plugins/lists/server/services/items/get_list_item_by_values.ts +++ b/x-pack/plugins/lists/server/services/items/get_list_item_by_values.ts @@ -42,8 +42,8 @@ export const getListItemByValues = async ({ index: listItemIndex, size: 10000, // TODO: This has a limit on the number which is 10,000 the default of Elastic but we might want to provide a way to increase that number }); - return transformElasticToListItem(({ + return transformElasticToListItem({ response, type, - } as unknown) as TransformElasticToListItemOptions); + } as unknown as TransformElasticToListItemOptions); }; diff --git a/x-pack/plugins/lists/server/services/items/search_list_item_by_values.ts b/x-pack/plugins/lists/server/services/items/search_list_item_by_values.ts index 942791c0ebe91..ad4a79e69683f 100644 --- a/x-pack/plugins/lists/server/services/items/search_list_item_by_values.ts +++ b/x-pack/plugins/lists/server/services/items/search_list_item_by_values.ts @@ -42,9 +42,9 @@ export const searchListItemByValues = async ({ index: listItemIndex, size: 10000, // TODO: This has a limit on the number which is 10,000 the default of Elastic but we might want to provide a way to increase that number }); - return transformElasticNamedSearchToListItem(({ + return transformElasticNamedSearchToListItem({ response, type, value, - } as unknown) as TransformElasticMSearchToListItemOptions); + } as unknown as TransformElasticMSearchToListItemOptions); }; diff --git a/x-pack/plugins/lists/server/services/items/update_list_item.test.ts b/x-pack/plugins/lists/server/services/items/update_list_item.test.ts index 8d8f1e117647a..37cb665eb906e 100644 --- a/x-pack/plugins/lists/server/services/items/update_list_item.test.ts +++ b/x-pack/plugins/lists/server/services/items/update_list_item.test.ts @@ -30,7 +30,7 @@ describe('update_list_item', () => { test('it returns a list item as expected with the id changed out for the elastic id when there is a list item to update', async () => { const listItem = getListItemResponseMock(); - ((getListItem as unknown) as jest.Mock).mockResolvedValueOnce(listItem); + (getListItem as unknown as jest.Mock).mockResolvedValueOnce(listItem); const options = getUpdateListItemOptionsMock(); const esClient = elasticsearchClientMock.createScopedClusterClient().asCurrentUser; esClient.update.mockReturnValue( @@ -43,7 +43,7 @@ describe('update_list_item', () => { }); test('it returns null when there is not a list item to update', async () => { - ((getListItem as unknown) as jest.Mock).mockResolvedValueOnce(null); + (getListItem as unknown as jest.Mock).mockResolvedValueOnce(null); const options = getUpdateListItemOptionsMock(); const updatedList = await updateListItem(options); expect(updatedList).toEqual(null); @@ -56,7 +56,7 @@ describe('update_list_item', () => { type: 'ip_range', value: '127.0.0.1', }; - ((getListItem as unknown) as jest.Mock).mockResolvedValueOnce(listItem); + (getListItem as unknown as jest.Mock).mockResolvedValueOnce(listItem); const options = getUpdateListItemOptionsMock(); const updatedList = await updateListItem(options); expect(updatedList).toEqual(null); diff --git a/x-pack/plugins/lists/server/services/items/write_list_items_to_stream.ts b/x-pack/plugins/lists/server/services/items/write_list_items_to_stream.ts index a70db9aba3880..1bf337dbd7532 100644 --- a/x-pack/plugins/lists/server/services/items/write_list_items_to_stream.ts +++ b/x-pack/plugins/lists/server/services/items/write_list_items_to_stream.ts @@ -117,7 +117,7 @@ export const getResponse = async ({ listItemIndex, size = SIZE, }: GetResponseOptions): Promise> => { - return (( + return ( await esClient.search({ body: { query: { @@ -132,7 +132,7 @@ export const getResponse = async ({ index: listItemIndex, size, }) - ).body as unknown) as estypes.SearchResponse; + ).body as unknown as estypes.SearchResponse; }; export interface WriteResponseHitsToStreamOptions { diff --git a/x-pack/plugins/lists/server/services/lists/delete_list.test.ts b/x-pack/plugins/lists/server/services/lists/delete_list.test.ts index f379fd977f51a..9fb0c5c8d6469 100644 --- a/x-pack/plugins/lists/server/services/lists/delete_list.test.ts +++ b/x-pack/plugins/lists/server/services/lists/delete_list.test.ts @@ -26,7 +26,7 @@ describe('delete_list', () => { }); test('Delete returns a null if the list is also null', async () => { - ((getList as unknown) as jest.Mock).mockResolvedValueOnce(null); + (getList as unknown as jest.Mock).mockResolvedValueOnce(null); const options = getDeleteListOptionsMock(); const deletedList = await deleteList(options); expect(deletedList).toEqual(null); @@ -34,7 +34,7 @@ describe('delete_list', () => { test('Delete returns the list if a list is returned from getList', async () => { const list = getListResponseMock(); - ((getList as unknown) as jest.Mock).mockResolvedValueOnce(list); + (getList as unknown as jest.Mock).mockResolvedValueOnce(list); const options = getDeleteListOptionsMock(); const deletedList = await deleteList(options); expect(deletedList).toEqual(list); @@ -42,7 +42,7 @@ describe('delete_list', () => { test('Delete calls "deleteByQuery" and "delete" if a list is returned from getList', async () => { const list = getListResponseMock(); - ((getList as unknown) as jest.Mock).mockResolvedValueOnce(list); + (getList as unknown as jest.Mock).mockResolvedValueOnce(list); const options = getDeleteListOptionsMock(); await deleteList(options); const deleteByQuery = { @@ -55,7 +55,7 @@ describe('delete_list', () => { test('Delete calls "delete" second if a list is returned from getList', async () => { const list = getListResponseMock(); - ((getList as unknown) as jest.Mock).mockResolvedValueOnce(list); + (getList as unknown as jest.Mock).mockResolvedValueOnce(list); const options = getDeleteListOptionsMock(); await deleteList(options); const deleteQuery = { @@ -67,7 +67,7 @@ describe('delete_list', () => { }); test('Delete does not call data client if the list returns null', async () => { - ((getList as unknown) as jest.Mock).mockResolvedValueOnce(null); + (getList as unknown as jest.Mock).mockResolvedValueOnce(null); const options = getDeleteListOptionsMock(); await deleteList(options); expect(options.esClient.delete).not.toHaveBeenCalled(); diff --git a/x-pack/plugins/lists/server/services/lists/update_list.test.ts b/x-pack/plugins/lists/server/services/lists/update_list.test.ts index df5aa4e53ca6c..ab8ea71a82b1e 100644 --- a/x-pack/plugins/lists/server/services/lists/update_list.test.ts +++ b/x-pack/plugins/lists/server/services/lists/update_list.test.ts @@ -30,7 +30,7 @@ describe('update_list', () => { test('it returns a list as expected with the id changed out for the elastic id when there is a list to update', async () => { const list = getListResponseMock(); - ((getList as unknown) as jest.Mock).mockResolvedValueOnce(list); + (getList as unknown as jest.Mock).mockResolvedValueOnce(list); const options = getUpdateListOptionsMock(); const esClient = elasticsearchClientMock.createScopedClusterClient().asCurrentUser; esClient.update.mockReturnValue( @@ -48,7 +48,7 @@ describe('update_list', () => { deserializer: '{{value}}', serializer: '(?)', }; - ((getList as unknown) as jest.Mock).mockResolvedValueOnce(list); + (getList as unknown as jest.Mock).mockResolvedValueOnce(list); const options = getUpdateListOptionsMock(); const esClient = elasticsearchClientMock.createScopedClusterClient().asCurrentUser; esClient.update.mockReturnValue( @@ -66,7 +66,7 @@ describe('update_list', () => { }); test('it returns null when there is not a list to update', async () => { - ((getList as unknown) as jest.Mock).mockResolvedValueOnce(null); + (getList as unknown as jest.Mock).mockResolvedValueOnce(null); const options = getUpdateListOptionsMock(); const updatedList = await updateList(options); expect(updatedList).toEqual(null); diff --git a/x-pack/plugins/logstash/server/index.ts b/x-pack/plugins/logstash/server/index.ts index 4606a518fa8c5..33f3777297f63 100644 --- a/x-pack/plugins/logstash/server/index.ts +++ b/x-pack/plugins/logstash/server/index.ts @@ -15,4 +15,5 @@ export const config: PluginConfigDescriptor = { schema: schema.object({ enabled: schema.boolean({ defaultValue: true }), }), + deprecations: ({ deprecate }) => [deprecate('enabled', '8.0.0')], }; diff --git a/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.ts b/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.ts index f2c13a81045ee..2412dd12199a3 100644 --- a/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.ts +++ b/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.ts @@ -8,11 +8,8 @@ /* eslint-disable @typescript-eslint/consistent-type-definitions */ import type { Query } from 'src/plugins/data/common'; -import { SortDirection } from 'src/plugins/data/common/search'; -import { RENDER_AS, SCALING_TYPES } from '../constants'; import { MapExtent } from './map_descriptor'; import { Filter, TimeRange } from '../../../../../src/plugins/data/common'; -import { ESTermSourceDescriptor } from './source_descriptor_types'; export type Timeslice = { from: number; @@ -32,33 +29,6 @@ export type DataFilters = { isReadOnly: boolean; }; -export type ESSearchSourceSyncMeta = { - filterByMapBounds: boolean; - sortField: string; - sortOrder: SortDirection; - scalingType: SCALING_TYPES; - topHitsSplitField: string; - topHitsSize: number; -}; - -type ESGeoGridSourceSyncMeta = { - requestType: RENDER_AS; -}; - -type ESGeoLineSourceSyncMeta = { - splitField: string; - sortField: string; -}; - -export type ESTermSourceSyncMeta = Pick; - -export type VectorSourceSyncMeta = - | ESSearchSourceSyncMeta - | ESGeoGridSourceSyncMeta - | ESGeoLineSourceSyncMeta - | ESTermSourceSyncMeta - | null; - export type VectorSourceRequestMeta = DataFilters & { applyGlobalQuery: boolean; applyGlobalTime: boolean; @@ -67,7 +37,7 @@ export type VectorSourceRequestMeta = DataFilters & { geogridPrecision?: number; timesliceMaskField?: string; sourceQuery?: Query; - sourceMeta: VectorSourceSyncMeta; + sourceMeta: object | null; isForceRefresh: boolean; }; diff --git a/x-pack/plugins/maps/common/descriptor_types/layer_descriptor_types.ts b/x-pack/plugins/maps/common/descriptor_types/layer_descriptor_types.ts index 740da8493c53c..244ebc59efd17 100644 --- a/x-pack/plugins/maps/common/descriptor_types/layer_descriptor_types.ts +++ b/x-pack/plugins/maps/common/descriptor_types/layer_descriptor_types.ts @@ -23,6 +23,7 @@ import { KBN_IS_TILE_COMPLETE, KBN_METADATA_FEATURE, KBN_VECTOR_SHAPE_TYPE_COUNTS, + LAYER_TYPE, } from '../constants'; export type Attribution = { @@ -56,7 +57,6 @@ export type LayerDescriptor = { alpha?: number; attribution?: Attribution; id: string; - joins?: JoinDescriptor[]; label?: string | null; areLabelsOnTop?: boolean; minZoom?: number; @@ -70,9 +70,12 @@ export type LayerDescriptor = { }; export type VectorLayerDescriptor = LayerDescriptor & { + type: LAYER_TYPE.VECTOR | LAYER_TYPE.TILED_VECTOR | LAYER_TYPE.BLENDED_VECTOR; + joins?: JoinDescriptor[]; style: VectorStyleDescriptor; }; export type HeatmapLayerDescriptor = LayerDescriptor & { + type: LAYER_TYPE.HEATMAP; style: HeatmapStyleDescriptor; }; diff --git a/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.ts b/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.ts index 94f3cbb0fed28..98782e7447b34 100644 --- a/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.ts +++ b/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.ts @@ -224,7 +224,7 @@ export function convertESShapeToGeojsonGeometry(value: ESGeometry): Geometry { ); throw new Error(errorMessage); } - return (geoJson as unknown) as Geometry; + return geoJson as unknown as Geometry; } function convertWKTStringToGeojson(value: string): Geometry { @@ -265,7 +265,7 @@ export function geoShapeToGeometry( value.type === GEO_JSON_TYPE.GEOMETRY_COLLECTION || value.type === 'geometrycollection' ) { - const geometryCollection = (value as unknown) as { geometries: ESGeometry[] }; + const geometryCollection = value as unknown as { geometries: ESGeometry[] }; for (let i = 0; i < geometryCollection.geometries.length; i++) { geoShapeToGeometry(geometryCollection.geometries[i], accumulator); } diff --git a/x-pack/plugins/maps/common/elasticsearch_util/spatial_filter_utils.test.ts b/x-pack/plugins/maps/common/elasticsearch_util/spatial_filter_utils.test.ts index 7aef32dfb4f8a..c912b662e6375 100644 --- a/x-pack/plugins/maps/common/elasticsearch_util/spatial_filter_utils.test.ts +++ b/x-pack/plugins/maps/common/elasticsearch_util/spatial_filter_utils.test.ts @@ -542,8 +542,7 @@ describe('extractFeaturesFromFilters', () => { const features = extractFeaturesFromFilters([spatialFilter]); expect((features[0].geometry as Polygon).coordinates[0][0]).toEqual([ - -89.87125, - 63.35109118642093, + -89.87125, 63.35109118642093, ]); expect(features[0].properties).toEqual({ filter: 'within 1096km of -89.87125, 53.49454', @@ -559,8 +558,7 @@ describe('extractFeaturesFromFilters', () => { const features = extractFeaturesFromFilters([spatialFilter]); expect((features[0].geometry as Polygon).coordinates[0][0]).toEqual([ - -89.87125, - 63.35109118642093, + -89.87125, 63.35109118642093, ]); expect(features[0].properties).toEqual({ filter: 'within 1096km of -89.87125, 53.49454', diff --git a/x-pack/plugins/maps/common/geo_tile_utils.ts b/x-pack/plugins/maps/common/geo_tile_utils.ts index a6df1d79131e7..1a8ac3cbe17ae 100644 --- a/x-pack/plugins/maps/common/geo_tile_utils.ts +++ b/x-pack/plugins/maps/common/geo_tile_utils.ts @@ -29,9 +29,12 @@ export interface ESBounds { }; } -export function parseTileKey( - tileKey: string -): { x: number; y: number; zoom: number; tileCount: number } { +export function parseTileKey(tileKey: string): { + x: number; + y: number; + zoom: number; + tileCount: number; +} { const tileKeyParts = tileKey.split('/'); if (tileKeyParts.length !== 3) { diff --git a/x-pack/plugins/maps/common/get_centroid_features.ts b/x-pack/plugins/maps/common/get_centroid_features.ts index edf8167f9c15d..8aaeb56576a84 100644 --- a/x-pack/plugins/maps/common/get_centroid_features.ts +++ b/x-pack/plugins/maps/common/get_centroid_features.ts @@ -108,5 +108,5 @@ export function getCentroid(feature: Feature): Feature | null { function getLineCentroid(feature: Feature): Geometry { const length = turfLength(feature); - return turfAlong((feature as unknown) as LineString, length / 2).geometry!; + return turfAlong(feature as unknown as LineString, length / 2).geometry!; } diff --git a/x-pack/plugins/maps/common/index.ts b/x-pack/plugins/maps/common/index.ts index c1b5d26fca292..8374a4d0dbaa3 100644 --- a/x-pack/plugins/maps/common/index.ts +++ b/x-pack/plugins/maps/common/index.ts @@ -12,6 +12,7 @@ export { FIELD_ORIGIN, INITIAL_LOCATION, LABEL_BORDER_SIZES, + LAYER_TYPE, MAP_SAVED_OBJECT_TYPE, SOURCE_TYPES, STYLE_TYPE, diff --git a/x-pack/plugins/maps/common/migrations/add_type_to_termjoin.test.ts b/x-pack/plugins/maps/common/migrations/add_type_to_termjoin.test.ts index a65e19b5e9b15..d795e63bf81d1 100644 --- a/x-pack/plugins/maps/common/migrations/add_type_to_termjoin.test.ts +++ b/x-pack/plugins/maps/common/migrations/add_type_to_termjoin.test.ts @@ -11,7 +11,7 @@ import { LayerDescriptor } from '../descriptor_types'; describe('addTypeToTermJoin', () => { test('Should handle missing type attribute', () => { - const layerListJSON = JSON.stringify(([ + const layerListJSON = JSON.stringify([ { type: LAYER_TYPE.VECTOR, joins: [ @@ -30,7 +30,7 @@ describe('addTypeToTermJoin', () => { }, ], }, - ] as unknown) as LayerDescriptor[]); + ] as unknown as LayerDescriptor[]); const attributes = { title: 'my map', diff --git a/x-pack/plugins/maps/common/migrations/add_type_to_termjoin.ts b/x-pack/plugins/maps/common/migrations/add_type_to_termjoin.ts index d40d85f9b6192..e46bf6a1a6e7f 100644 --- a/x-pack/plugins/maps/common/migrations/add_type_to_termjoin.ts +++ b/x-pack/plugins/maps/common/migrations/add_type_to_termjoin.ts @@ -6,8 +6,8 @@ */ import { MapSavedObjectAttributes } from '../map_saved_object_type'; -import { JoinDescriptor, LayerDescriptor } from '../descriptor_types'; -import { LAYER_TYPE, SOURCE_TYPES } from '../constants'; +import { JoinDescriptor, LayerDescriptor, VectorLayerDescriptor } from '../descriptor_types'; +import { SOURCE_TYPES } from '../constants'; // enforce type property on joins. It's possible older saved-objects do not have this correctly filled in // e.g. sample-data was missing the right.type field. @@ -24,14 +24,15 @@ export function addTypeToTermJoin({ const layerList: LayerDescriptor[] = JSON.parse(attributes.layerListJSON); layerList.forEach((layer: LayerDescriptor) => { - if (layer.type !== LAYER_TYPE.VECTOR) { + if (!('joins' in layer)) { return; } - if (!layer.joins) { + const vectorLayer = layer as VectorLayerDescriptor; + if (!vectorLayer.joins) { return; } - layer.joins.forEach((join: JoinDescriptor) => { + vectorLayer.joins.forEach((join: JoinDescriptor) => { if (!join.right) { return; } diff --git a/x-pack/plugins/maps/common/migrations/join_agg_key.test.ts b/x-pack/plugins/maps/common/migrations/join_agg_key.test.ts index c4dfb438ad227..96d60e2f92385 100644 --- a/x-pack/plugins/maps/common/migrations/join_agg_key.test.ts +++ b/x-pack/plugins/maps/common/migrations/join_agg_key.test.ts @@ -75,8 +75,7 @@ describe('migrateJoinAggKey', () => { color: 'Blues', colorCategory: 'palette_0', field: { - name: - '__kbnjoin__avg_of_bytes_groupby_kibana_sample_data_logs.machine.os.keyword', + name: '__kbnjoin__avg_of_bytes_groupby_kibana_sample_data_logs.machine.os.keyword', origin: 'join', }, fieldMetaOptions: { diff --git a/x-pack/plugins/maps/common/migrations/move_attribution.test.ts b/x-pack/plugins/maps/common/migrations/move_attribution.test.ts index 250361a3b6bed..796948d2ed69b 100644 --- a/x-pack/plugins/maps/common/migrations/move_attribution.test.ts +++ b/x-pack/plugins/maps/common/migrations/move_attribution.test.ts @@ -18,7 +18,7 @@ test('Should handle missing layerListJSON attribute', () => { }); test('Should migrate source attribution to layer attribution', () => { - const layerListJSON = JSON.stringify(([ + const layerListJSON = JSON.stringify([ { sourceDescriptor: { attributionText: 'myLabel', @@ -26,7 +26,7 @@ test('Should migrate source attribution to layer attribution', () => { id: 'mySourceId', }, }, - ] as unknown) as LayerDescriptor[]); + ] as unknown as LayerDescriptor[]); const attributes = { title: 'my map', @@ -44,13 +44,13 @@ test('Should migrate source attribution to layer attribution', () => { }); test('Should not add attribution to layer when source does not provide attribution', () => { - const layerListJSON = JSON.stringify(([ + const layerListJSON = JSON.stringify([ { sourceDescriptor: { id: 'mySourceId', }, }, - ] as unknown) as LayerDescriptor[]); + ] as unknown as LayerDescriptor[]); const attributes = { title: 'my map', diff --git a/x-pack/plugins/maps/common/migrations/references.ts b/x-pack/plugins/maps/common/migrations/references.ts index d48be6bd56fbe..41d9dc063fe47 100644 --- a/x-pack/plugins/maps/common/migrations/references.ts +++ b/x-pack/plugins/maps/common/migrations/references.ts @@ -9,7 +9,7 @@ import { SavedObjectReference } from '../../../../../src/core/types'; import { MapSavedObjectAttributes } from '../map_saved_object_type'; -import { LayerDescriptor } from '../descriptor_types'; +import { LayerDescriptor, VectorLayerDescriptor } from '../descriptor_types'; interface IndexPatternReferenceDescriptor { indexPatternId?: string; @@ -44,21 +44,24 @@ export function extractReferences({ sourceDescriptor.indexPatternRefName = refName; } - // Extract index-pattern references from join - const joins = layer.joins ? layer.joins : []; - joins.forEach((join, joinIndex) => { - if ('indexPatternId' in join.right) { - const sourceDescriptor = join.right as IndexPatternReferenceDescriptor; - const refName = `layer_${layerIndex}_join_${joinIndex}_index_pattern`; - extractedReferences.push({ - name: refName, - type: 'index-pattern', - id: sourceDescriptor.indexPatternId!, - }); - delete sourceDescriptor.indexPatternId; - sourceDescriptor.indexPatternRefName = refName; - } - }); + if ('joins' in layer) { + // Extract index-pattern references from join + const vectorLayer = layer as VectorLayerDescriptor; + const joins = vectorLayer.joins ? vectorLayer.joins : []; + joins.forEach((join, joinIndex) => { + if ('indexPatternId' in join.right) { + const sourceDescriptor = join.right as IndexPatternReferenceDescriptor; + const refName = `layer_${layerIndex}_join_${joinIndex}_index_pattern`; + extractedReferences.push({ + name: refName, + type: 'index-pattern', + id: sourceDescriptor.indexPatternId!, + }); + delete sourceDescriptor.indexPatternId; + sourceDescriptor.indexPatternRefName = refName; + } + }); + } }); return { @@ -99,16 +102,19 @@ export function injectReferences({ delete sourceDescriptor.indexPatternRefName; } - // Inject index-pattern references into join - const joins = layer.joins ? layer.joins : []; - joins.forEach((join) => { - if ('indexPatternRefName' in join.right) { - const sourceDescriptor = join.right as IndexPatternReferenceDescriptor; - const reference = findReference(sourceDescriptor.indexPatternRefName!, references); - sourceDescriptor.indexPatternId = reference.id; - delete sourceDescriptor.indexPatternRefName; - } - }); + if ('joins' in layer) { + // Inject index-pattern references into join + const vectorLayer = layer as VectorLayerDescriptor; + const joins = vectorLayer.joins ? vectorLayer.joins : []; + joins.forEach((join) => { + if ('indexPatternRefName' in join.right) { + const sourceDescriptor = join.right as IndexPatternReferenceDescriptor; + const reference = findReference(sourceDescriptor.indexPatternRefName!, references); + sourceDescriptor.indexPatternId = reference.id; + delete sourceDescriptor.indexPatternRefName; + } + }); + } }); return { diff --git a/x-pack/plugins/maps/public/actions/layer_actions.test.ts b/x-pack/plugins/maps/public/actions/layer_actions.test.ts index 4545155c6adb9..06adbed92c0cf 100644 --- a/x-pack/plugins/maps/public/actions/layer_actions.test.ts +++ b/x-pack/plugins/maps/public/actions/layer_actions.test.ts @@ -50,7 +50,7 @@ describe('layer_actions', () => { }); it('should register feature-use', async () => { - const action = addLayer(({} as unknown) as LayerDescriptor); + const action = addLayer({} as unknown as LayerDescriptor); await action(dispatchMock, getStoreMock); expect(notifyLicensedFeatureUsageMock).toHaveBeenCalledWith( LICENSED_FEATURES.GEO_SHAPE_AGGS_GEO_TILE diff --git a/x-pack/plugins/maps/public/actions/layer_actions.ts b/x-pack/plugins/maps/public/actions/layer_actions.ts index d5bb061ccf430..d67aef645b03a 100644 --- a/x-pack/plugins/maps/public/actions/layer_actions.ts +++ b/x-pack/plugins/maps/public/actions/layer_actions.ts @@ -534,14 +534,9 @@ function updateStyleProperties(layerId: string, previousFields: IField[]) { } const nextFields = await (targetLayer as IVectorLayer).getFields(); // take into account all fields, since labels can be driven by any field (source or join) - const { - hasChanges, - nextStyleDescriptor, - } = await (style as IVectorStyle).getDescriptorWithUpdatedStyleProps( - nextFields, - previousFields, - getMapColors(getState()) - ); + const { hasChanges, nextStyleDescriptor } = await ( + style as IVectorStyle + ).getDescriptorWithUpdatedStyleProps(nextFields, previousFields, getMapColors(getState())); if (hasChanges && nextStyleDescriptor) { dispatch(updateLayerStyle(layerId, nextStyleDescriptor)); } diff --git a/x-pack/plugins/maps/public/api/index.ts b/x-pack/plugins/maps/public/api/index.ts index 186fd98c90bf6..feded3e16f375 100644 --- a/x-pack/plugins/maps/public/api/index.ts +++ b/x-pack/plugins/maps/public/api/index.ts @@ -6,6 +6,7 @@ */ export { MapsStartApi } from './start_api'; +export { MapsSetupApi } from './setup_api'; export { createLayerDescriptors } from './create_layer_descriptors'; export { registerLayerWizard, registerSource } from './register'; export { suggestEMSTermJoinConfig } from './ems'; diff --git a/x-pack/plugins/maps/public/api/setup_api.ts b/x-pack/plugins/maps/public/api/setup_api.ts new file mode 100644 index 0000000000000..1b4fee968aad4 --- /dev/null +++ b/x-pack/plugins/maps/public/api/setup_api.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { SourceRegistryEntry } from '../classes/sources/source_registry'; +import type { LayerWizard } from '../classes/layers/layer_wizard_registry'; + +export interface MapsSetupApi { + registerLayerWizard(layerWizard: LayerWizard): Promise; + registerSource(entry: SourceRegistryEntry): Promise; +} diff --git a/x-pack/plugins/maps/public/api/start_api.ts b/x-pack/plugins/maps/public/api/start_api.ts index e4213fe07a49c..eea440b8b2afc 100644 --- a/x-pack/plugins/maps/public/api/start_api.ts +++ b/x-pack/plugins/maps/public/api/start_api.ts @@ -6,8 +6,6 @@ */ import type { LayerDescriptor } from '../../common/descriptor_types'; -import type { SourceRegistryEntry } from '../classes/sources/source_registry'; -import type { LayerWizard } from '../classes/layers/layer_wizard_registry'; import type { CreateLayerDescriptorParams } from '../classes/sources/es_search_source'; import type { SampleValuesConfig, EMSTermJoinConfig } from '../ems_autosuggest'; @@ -22,7 +20,5 @@ export interface MapsStartApi { params: CreateLayerDescriptorParams ) => Promise; }; - registerLayerWizard(layerWizard: LayerWizard): Promise; - registerSource(entry: SourceRegistryEntry): Promise; suggestEMSTermJoinConfig(config: SampleValuesConfig): Promise; } diff --git a/x-pack/plugins/maps/public/classes/fields/agg/agg_field.test.ts b/x-pack/plugins/maps/public/classes/fields/agg/agg_field.test.ts index b78eb1355ad8e..f5e2a467c5e62 100644 --- a/x-pack/plugins/maps/public/classes/fields/agg/agg_field.test.ts +++ b/x-pack/plugins/maps/public/classes/fields/agg/agg_field.test.ts @@ -9,7 +9,7 @@ import { AggField } from './agg_field'; import { AGG_TYPE, FIELD_ORIGIN } from '../../../../common/constants'; import { IESAggSource } from '../../sources/es_agg_source'; -const mockEsAggSource = ({} as unknown) as IESAggSource; +const mockEsAggSource = {} as unknown as IESAggSource; const defaultParams = { label: 'my agg field', diff --git a/x-pack/plugins/maps/public/classes/fields/agg/agg_field_types.ts b/x-pack/plugins/maps/public/classes/fields/agg/agg_field_types.ts index ca3ef1e1e53c5..e54c846883c20 100644 --- a/x-pack/plugins/maps/public/classes/fields/agg/agg_field_types.ts +++ b/x-pack/plugins/maps/public/classes/fields/agg/agg_field_types.ts @@ -6,7 +6,7 @@ */ import { IField } from '../field'; -import { IndexPattern } from '../../../../../../../src/plugins/data/common/index_patterns/index_patterns'; +import { IndexPattern } from '../../../../../../../src/plugins/data/common'; import { IESAggSource } from '../../sources/es_agg_source'; import { FIELD_ORIGIN } from '../../../../common/constants'; diff --git a/x-pack/plugins/maps/public/classes/fields/agg/count_agg_field.test.ts b/x-pack/plugins/maps/public/classes/fields/agg/count_agg_field.test.ts index 664ac32edf6df..c9970123c8095 100644 --- a/x-pack/plugins/maps/public/classes/fields/agg/count_agg_field.test.ts +++ b/x-pack/plugins/maps/public/classes/fields/agg/count_agg_field.test.ts @@ -9,7 +9,7 @@ import { CountAggField } from './count_agg_field'; import { AGG_TYPE, FIELD_ORIGIN } from '../../../../common/constants'; import { IESAggSource } from '../../sources/es_agg_source'; -const mockEsAggSource = ({} as unknown) as IESAggSource; +const mockEsAggSource = {} as unknown as IESAggSource; const defaultParams = { label: 'my agg field', diff --git a/x-pack/plugins/maps/public/classes/fields/agg/es_agg_factory.test.ts b/x-pack/plugins/maps/public/classes/fields/agg/es_agg_factory.test.ts index 8753ea7fc617c..04827a674de52 100644 --- a/x-pack/plugins/maps/public/classes/fields/agg/es_agg_factory.test.ts +++ b/x-pack/plugins/maps/public/classes/fields/agg/es_agg_factory.test.ts @@ -9,11 +9,11 @@ import { esAggFieldsFactory } from './es_agg_factory'; import { AGG_TYPE, FIELD_ORIGIN } from '../../../../common/constants'; import { IESAggSource } from '../../sources/es_agg_source'; -const mockEsAggSource = ({ +const mockEsAggSource = { getAggKey() { return 'foobar'; }, -} as unknown) as IESAggSource; +} as unknown as IESAggSource; describe('esAggFieldsFactory', () => { test('Should only create top terms field when term field is not provided', () => { diff --git a/x-pack/plugins/maps/public/classes/fields/agg/percentile_agg_field.ts b/x-pack/plugins/maps/public/classes/fields/agg/percentile_agg_field.ts index 37941af81f9df..141f6aea5d301 100644 --- a/x-pack/plugins/maps/public/classes/fields/agg/percentile_agg_field.ts +++ b/x-pack/plugins/maps/public/classes/fields/agg/percentile_agg_field.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { IndexPattern } from 'src/plugins/data/common/index_patterns/index_patterns'; +import { IndexPattern } from 'src/plugins/data/common'; import { i18n } from '@kbn/i18n'; import { AGG_TYPE } from '../../../../common/constants'; import { IESAggField, CountAggFieldParams } from './agg_field_types'; diff --git a/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts b/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts index d2734265f3bc3..a158892be9d09 100644 --- a/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts +++ b/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts @@ -33,7 +33,6 @@ import { SizeDynamicOptions, DynamicStylePropertyOptions, StylePropertyOptions, - LayerDescriptor, Timeslice, VectorLayerDescriptor, VectorSourceRequestMeta, @@ -179,7 +178,7 @@ export class BlendedVectorLayer extends VectorLayer implements IVectorLayer { mapColors: string[] ): VectorLayerDescriptor { const layerDescriptor = VectorLayer.createDescriptor(options, mapColors); - layerDescriptor.type = BlendedVectorLayer.type; + layerDescriptor.type = LAYER_TYPE.BLENDED_VECTOR; return layerDescriptor; } @@ -256,7 +255,7 @@ export class BlendedVectorLayer extends VectorLayer implements IVectorLayer { return false; } - async cloneDescriptor(): Promise { + async cloneDescriptor(): Promise { const clonedDescriptor = await super.cloneDescriptor(); // Use super getDisplayName instead of instance getDisplayName to avoid getting 'Clustered Clone of Clustered' diff --git a/x-pack/plugins/maps/public/classes/layers/layer.test.ts b/x-pack/plugins/maps/public/classes/layers/layer.test.ts index dcc183c5c1741..194b41680872c 100644 --- a/x-pack/plugins/maps/public/classes/layers/layer.test.ts +++ b/x-pack/plugins/maps/public/classes/layers/layer.test.ts @@ -9,21 +9,6 @@ import { AbstractLayer } from './layer'; import { ISource } from '../sources/source'; -import { - AGG_TYPE, - FIELD_ORIGIN, - LAYER_STYLE_TYPE, - SOURCE_TYPES, - VECTOR_STYLES, -} from '../../../common/constants'; -import { ESTermSourceDescriptor, VectorStyleDescriptor } from '../../../common/descriptor_types'; -import { getDefaultDynamicProperties } from '../styles/vector/vector_style_defaults'; - -jest.mock('uuid/v4', () => { - return function () { - return '12345'; - }; -}); class MockLayer extends AbstractLayer {} @@ -36,111 +21,11 @@ class MockSource { return {}; } - getDisplayName() { - return 'mySource'; - } - async supportsFitToBounds() { return this._fitToBounds; } } -describe('cloneDescriptor', () => { - describe('with joins', () => { - const styleDescriptor = { - type: LAYER_STYLE_TYPE.VECTOR, - properties: { - ...getDefaultDynamicProperties(), - }, - } as VectorStyleDescriptor; - // @ts-expect-error - styleDescriptor.properties[VECTOR_STYLES.FILL_COLOR].options.field = { - name: '__kbnjoin__count__557d0f15', - origin: FIELD_ORIGIN.JOIN, - }; - // @ts-expect-error - styleDescriptor.properties[VECTOR_STYLES.LINE_COLOR].options.field = { - name: 'bytes', - origin: FIELD_ORIGIN.SOURCE, - }; - // @ts-expect-error - styleDescriptor.properties[VECTOR_STYLES.LABEL_BORDER_COLOR].options.field = { - name: '__kbnjoin__count__6666666666', - origin: FIELD_ORIGIN.JOIN, - }; - - test('Should update data driven styling properties using join fields', async () => { - const layerDescriptor = AbstractLayer.createDescriptor({ - style: styleDescriptor, - joins: [ - { - leftField: 'iso2', - right: { - id: '557d0f15', - indexPatternId: 'myIndexPattern', - indexPatternTitle: 'logs-*', - metrics: [{ type: AGG_TYPE.COUNT }], - term: 'myTermField', - type: SOURCE_TYPES.ES_TERM_SOURCE, - applyGlobalQuery: true, - applyGlobalTime: true, - applyForceRefresh: true, - }, - }, - ], - }); - const layer = new MockLayer({ - layerDescriptor, - source: (new MockSource() as unknown) as ISource, - }); - const clonedDescriptor = await layer.cloneDescriptor(); - const clonedStyleProps = (clonedDescriptor.style as VectorStyleDescriptor).properties; - // Should update style field belonging to join - // @ts-expect-error - expect(clonedStyleProps[VECTOR_STYLES.FILL_COLOR].options.field.name).toEqual( - '__kbnjoin__count__12345' - ); - // Should not update style field belonging to source - // @ts-expect-error - expect(clonedStyleProps[VECTOR_STYLES.LINE_COLOR].options.field.name).toEqual('bytes'); - // Should not update style feild belonging to different join - // @ts-expect-error - expect(clonedStyleProps[VECTOR_STYLES.LABEL_BORDER_COLOR].options.field.name).toEqual( - '__kbnjoin__count__6666666666' - ); - }); - - test('Should update data driven styling properties using join fields when metrics are not provided', async () => { - const layerDescriptor = AbstractLayer.createDescriptor({ - style: styleDescriptor, - joins: [ - { - leftField: 'iso2', - right: ({ - id: '557d0f15', - indexPatternId: 'myIndexPattern', - indexPatternTitle: 'logs-*', - term: 'myTermField', - type: 'joinSource', - } as unknown) as ESTermSourceDescriptor, - }, - ], - }); - const layer = new MockLayer({ - layerDescriptor, - source: (new MockSource() as unknown) as ISource, - }); - const clonedDescriptor = await layer.cloneDescriptor(); - const clonedStyleProps = (clonedDescriptor.style as VectorStyleDescriptor).properties; - // Should update style field belonging to join - // @ts-expect-error - expect(clonedStyleProps[VECTOR_STYLES.FILL_COLOR].options.field.name).toEqual( - '__kbnjoin__count__12345' - ); - }); - }); -}); - describe('isFittable', () => { [ { @@ -179,7 +64,7 @@ describe('isFittable', () => { }); const layer = new MockLayer({ layerDescriptor, - source: (new MockSource({ fitToBounds: test.fitToBounds }) as unknown) as ISource, + source: new MockSource({ fitToBounds: test.fitToBounds }) as unknown as ISource, }); expect(await layer.isFittable()).toBe(test.canFit); }); diff --git a/x-pack/plugins/maps/public/classes/layers/layer.tsx b/x-pack/plugins/maps/public/classes/layers/layer.tsx index 472a796a6e7c9..e1043a33f28ad 100644 --- a/x-pack/plugins/maps/public/classes/layers/layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/layer.tsx @@ -16,23 +16,16 @@ import uuid from 'uuid/v4'; import { FeatureCollection } from 'geojson'; import { DataRequest } from '../util/data_request'; import { - AGG_TYPE, - FIELD_ORIGIN, LAYER_TYPE, MAX_ZOOM, MB_SOURCE_ID_LAYER_ID_PREFIX_DELIMITER, MIN_ZOOM, SOURCE_BOUNDS_DATA_REQUEST_ID, SOURCE_DATA_REQUEST_ID, - SOURCE_TYPES, - STYLE_TYPE, } from '../../../common/constants'; import { copyPersistentState } from '../../reducers/copy_persistent_state'; import { - AggDescriptor, Attribution, - ESTermSourceDescriptor, - JoinDescriptor, LayerDescriptor, MapExtent, StyleDescriptor, @@ -42,7 +35,6 @@ import { import { ImmutableSourceProperty, ISource, SourceEditorArgs } from '../sources/source'; import { DataRequestContext } from '../../actions'; import { IStyle } from '../styles/style'; -import { getJoinAggKey } from '../../../common/get_agg_key'; import { LICENSED_FEATURES } from '../../licensed_features'; import { IESSource } from '../sources/es_source'; @@ -97,8 +89,6 @@ export interface ILayer { isPreviewLayer: () => boolean; areLabelsOnTop: () => boolean; supportsLabelsOnTop: () => boolean; - showJoinEditor(): boolean; - getJoinsDisabledReason(): string | null; isFittable(): Promise; isIncludeInFitToBounds(): boolean; getLicensedFeatures(): Promise; @@ -177,55 +167,6 @@ export class AbstractLayer implements ILayer { const displayName = await this.getDisplayName(); clonedDescriptor.label = `Clone of ${displayName}`; clonedDescriptor.sourceDescriptor = this.getSource().cloneDescriptor(); - - if (clonedDescriptor.joins) { - clonedDescriptor.joins.forEach((joinDescriptor: JoinDescriptor) => { - if (joinDescriptor.right && joinDescriptor.right.type === SOURCE_TYPES.TABLE_SOURCE) { - throw new Error( - 'Cannot clone table-source. Should only be used in MapEmbeddable, not in UX' - ); - } - const termSourceDescriptor: ESTermSourceDescriptor = joinDescriptor.right as ESTermSourceDescriptor; - - // todo: must tie this to generic thing - const originalJoinId = joinDescriptor.right.id!; - - // right.id is uuid used to track requests in inspector - joinDescriptor.right.id = uuid(); - - // Update all data driven styling properties using join fields - if (clonedDescriptor.style && 'properties' in clonedDescriptor.style) { - const metrics = - termSourceDescriptor.metrics && termSourceDescriptor.metrics.length - ? termSourceDescriptor.metrics - : [{ type: AGG_TYPE.COUNT }]; - metrics.forEach((metricsDescriptor: AggDescriptor) => { - const originalJoinKey = getJoinAggKey({ - aggType: metricsDescriptor.type, - aggFieldName: 'field' in metricsDescriptor ? metricsDescriptor.field : '', - rightSourceId: originalJoinId, - }); - const newJoinKey = getJoinAggKey({ - aggType: metricsDescriptor.type, - aggFieldName: 'field' in metricsDescriptor ? metricsDescriptor.field : '', - rightSourceId: joinDescriptor.right.id!, - }); - - Object.keys(clonedDescriptor.style.properties).forEach((key) => { - const styleProp = clonedDescriptor.style.properties[key]; - if ( - styleProp.type === STYLE_TYPE.DYNAMIC && - styleProp.options.field && - styleProp.options.field.origin === FIELD_ORIGIN.JOIN && - styleProp.options.field.name === originalJoinKey - ) { - styleProp.options.field.name = newJoinKey; - } - }); - }); - } - }); - } return clonedDescriptor; } @@ -233,14 +174,6 @@ export class AbstractLayer implements ILayer { return `${this.getId()}${MB_SOURCE_ID_LAYER_ID_PREFIX_DELIMITER}${layerNameSuffix}`; } - showJoinEditor(): boolean { - return this.getSource().showJoinEditor(); - } - - getJoinsDisabledReason() { - return this.getSource().getJoinsDisabledReason(); - } - isPreviewLayer(): boolean { return !!this._descriptor.__isPreviewLayer; } diff --git a/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx index 0d365cc5fc8c4..9b5298685865a 100644 --- a/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx @@ -48,7 +48,7 @@ export class TiledVectorLayer extends VectorLayer { mapColors?: string[] ): VectorLayerDescriptor { const layerDescriptor = super.createDescriptor(descriptor, mapColors); - layerDescriptor.type = TiledVectorLayer.type; + layerDescriptor.type = LAYER_TYPE.TILED_VECTOR; if (!layerDescriptor.style) { const styleProperties = VectorStyle.createDefaultStyleProperties(mapColors ? mapColors : []); @@ -123,7 +123,8 @@ export class TiledVectorLayer extends VectorLayer { ); const prevDataRequest = this.getSourceDataRequest(); if (prevDataRequest) { - const data: MVTSingleLayerVectorSourceConfig = prevDataRequest.getData() as MVTSingleLayerVectorSourceConfig; + const data: MVTSingleLayerVectorSourceConfig = + prevDataRequest.getData() as MVTSingleLayerVectorSourceConfig; if (data) { const noChangesInSourceState: boolean = data.layerName === this._source.getLayerName() && @@ -197,7 +198,8 @@ export class TiledVectorLayer extends VectorLayer { return; } - const sourceMeta: MVTSingleLayerVectorSourceConfig | null = sourceDataRequest.getData() as MVTSingleLayerVectorSourceConfig; + const sourceMeta: MVTSingleLayerVectorSourceConfig | null = + sourceDataRequest.getData() as MVTSingleLayerVectorSourceConfig; if (!sourceMeta) { return; } @@ -226,7 +228,8 @@ export class TiledVectorLayer extends VectorLayer { if (!sourceDataRequest) { return; } - const sourceMeta: MVTSingleLayerVectorSourceConfig = sourceDataRequest.getData() as MVTSingleLayerVectorSourceConfig; + const sourceMeta: MVTSingleLayerVectorSourceConfig = + sourceDataRequest.getData() as MVTSingleLayerVectorSourceConfig; if (sourceMeta.layerName === '') { return; } @@ -247,7 +250,8 @@ export class TiledVectorLayer extends VectorLayer { if (!sourceDataRequest) { return null; } - const sourceMeta: MVTSingleLayerVectorSourceConfig = sourceDataRequest.getData() as MVTSingleLayerVectorSourceConfig; + const sourceMeta: MVTSingleLayerVectorSourceConfig = + sourceDataRequest.getData() as MVTSingleLayerVectorSourceConfig; if (sourceMeta.layerName === '') { return null; } @@ -292,7 +296,8 @@ export class TiledVectorLayer extends VectorLayer { if (!dataRequest) { return false; } - const tiledSourceMeta: MVTSingleLayerVectorSourceConfig | null = dataRequest.getData() as MVTSingleLayerVectorSourceConfig; + const tiledSourceMeta: MVTSingleLayerVectorSourceConfig | null = + dataRequest.getData() as MVTSingleLayerVectorSourceConfig; if (!tiledSourceMeta) { return false; diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/index.ts b/x-pack/plugins/maps/public/classes/layers/vector_layer/index.ts index 3c8449c5aa4ae..cb964f77613da 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/index.ts +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/index.ts @@ -7,6 +7,7 @@ export { addGeoJsonMbSource, getVectorSourceBounds, syncVectorSource } from './utils'; export { + isVectorLayer, IVectorLayer, VectorLayer, VectorLayerArguments, diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/perform_inner_joins.test.ts b/x-pack/plugins/maps/public/classes/layers/vector_layer/perform_inner_joins.test.ts index aad1b693be79b..9346bb1621e44 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/perform_inner_joins.test.ts +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/perform_inner_joins.test.ts @@ -66,7 +66,7 @@ const joinDescriptor = { type: SOURCE_TYPES.ES_TERM_SOURCE, } as ESTermSourceDescriptor, }; -const mockVectorSource = ({ +const mockVectorSource = { getInspectorAdapters: () => { return undefined; }, @@ -77,7 +77,7 @@ const mockVectorSource = ({ }, } as IField; }, -} as unknown) as IVectorSource; +} as unknown as IVectorSource; const innerJoin = new InnerJoin(joinDescriptor, mockVectorSource); const propertiesMap = new Map>(); propertiesMap.set('alpha', { [COUNT_PROPERTY_NAME]: 1 }); diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/utils.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/utils.tsx index 6bc72f09e9387..8e4eb349036ea 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/utils.tsx +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/utils.tsx @@ -68,13 +68,8 @@ export async function syncVectorSource({ source: IVectorSource; getUpdateDueToTimeslice: (timeslice?: Timeslice) => boolean; }): Promise<{ refreshed: boolean; featureCollection: FeatureCollection }> { - const { - startLoading, - stopLoading, - onLoadError, - registerCancelCallback, - isRequestStillActive, - } = syncContext; + const { startLoading, stopLoading, onLoadError, registerCancelCallback, isRequestStillActive } = + syncContext; const dataRequestId = SOURCE_DATA_REQUEST_ID; const requestToken = Symbol(`${layerId}-${dataRequestId}`); diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.test.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.test.tsx new file mode 100644 index 0000000000000..618be0b21cd73 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.test.tsx @@ -0,0 +1,136 @@ +/* + * 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. + */ + +/* eslint-disable max-classes-per-file */ + +jest.mock('../../styles/vector/vector_style', () => ({ + VectorStyle: class MockVectorStyle {}, +})); + +jest.mock('uuid/v4', () => { + return function () { + return '12345'; + }; +}); + +import { + AGG_TYPE, + FIELD_ORIGIN, + LAYER_STYLE_TYPE, + SOURCE_TYPES, + VECTOR_STYLES, +} from '../../../../common/constants'; +import { ESTermSourceDescriptor, VectorStyleDescriptor } from '../../../../common/descriptor_types'; +import { getDefaultDynamicProperties } from '../../styles/vector/vector_style_defaults'; +import { IVectorSource } from '../../sources/vector_source'; +import { VectorLayer } from './vector_layer'; + +class MockSource { + cloneDescriptor() { + return {}; + } + + getDisplayName() { + return 'mySource'; + } +} + +describe('cloneDescriptor', () => { + describe('with joins', () => { + const styleDescriptor = { + type: LAYER_STYLE_TYPE.VECTOR, + properties: { + ...getDefaultDynamicProperties(), + }, + } as VectorStyleDescriptor; + // @ts-expect-error + styleDescriptor.properties[VECTOR_STYLES.FILL_COLOR].options.field = { + name: '__kbnjoin__count__557d0f15', + origin: FIELD_ORIGIN.JOIN, + }; + // @ts-expect-error + styleDescriptor.properties[VECTOR_STYLES.LINE_COLOR].options.field = { + name: 'bytes', + origin: FIELD_ORIGIN.SOURCE, + }; + // @ts-expect-error + styleDescriptor.properties[VECTOR_STYLES.LABEL_BORDER_COLOR].options.field = { + name: '__kbnjoin__count__6666666666', + origin: FIELD_ORIGIN.JOIN, + }; + + test('Should update data driven styling properties using join fields', async () => { + const layerDescriptor = VectorLayer.createDescriptor({ + style: styleDescriptor, + joins: [ + { + leftField: 'iso2', + right: { + id: '557d0f15', + indexPatternId: 'myIndexPattern', + indexPatternTitle: 'logs-*', + metrics: [{ type: AGG_TYPE.COUNT }], + term: 'myTermField', + type: SOURCE_TYPES.ES_TERM_SOURCE, + applyGlobalQuery: true, + applyGlobalTime: true, + applyForceRefresh: true, + }, + }, + ], + }); + const layer = new VectorLayer({ + layerDescriptor, + source: new MockSource() as unknown as IVectorSource, + }); + const clonedDescriptor = await layer.cloneDescriptor(); + const clonedStyleProps = (clonedDescriptor.style as VectorStyleDescriptor).properties; + // Should update style field belonging to join + // @ts-expect-error + expect(clonedStyleProps[VECTOR_STYLES.FILL_COLOR].options.field.name).toEqual( + '__kbnjoin__count__12345' + ); + // Should not update style field belonging to source + // @ts-expect-error + expect(clonedStyleProps[VECTOR_STYLES.LINE_COLOR].options.field.name).toEqual('bytes'); + // Should not update style feild belonging to different join + // @ts-expect-error + expect(clonedStyleProps[VECTOR_STYLES.LABEL_BORDER_COLOR].options.field.name).toEqual( + '__kbnjoin__count__6666666666' + ); + }); + + test('Should update data driven styling properties using join fields when metrics are not provided', async () => { + const layerDescriptor = VectorLayer.createDescriptor({ + style: styleDescriptor, + joins: [ + { + leftField: 'iso2', + right: { + id: '557d0f15', + indexPatternId: 'myIndexPattern', + indexPatternTitle: 'logs-*', + term: 'myTermField', + type: 'joinSource', + } as unknown as ESTermSourceDescriptor, + }, + ], + }); + const layer = new VectorLayer({ + layerDescriptor, + source: new MockSource() as unknown as IVectorSource, + }); + const clonedDescriptor = await layer.cloneDescriptor(); + const clonedStyleProps = (clonedDescriptor.style as VectorStyleDescriptor).properties; + // Should update style field belonging to join + // @ts-expect-error + expect(clonedStyleProps[VECTOR_STYLES.FILL_COLOR].options.field.name).toEqual( + '__kbnjoin__count__12345' + ); + }); + }); +}); diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx index c4903ddb325b2..3faf92715451c 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx @@ -6,6 +6,7 @@ */ import React from 'react'; +import uuid from 'uuid/v4'; import type { Map as MbMap, AnyLayer as MbLayer, @@ -19,6 +20,7 @@ import { i18n } from '@kbn/i18n'; import { AbstractLayer } from '../layer'; import { IVectorStyle, VectorStyle } from '../../styles/vector/vector_style'; import { + AGG_TYPE, FEATURE_ID_PROPERTY_NAME, SOURCE_META_DATA_REQUEST_ID, SOURCE_FORMATTERS_DATA_REQUEST_ID, @@ -29,8 +31,11 @@ import { FIELD_ORIGIN, KBN_TOO_MANY_FEATURES_IMAGE_ID, FieldFormatter, + SOURCE_TYPES, + STYLE_TYPE, SUPPORTS_FEATURE_EDITING_REQUEST_ID, KBN_IS_TILE_COMPLETE, + VECTOR_STYLES, } from '../../../../common/constants'; import { JoinTooltipProperty } from '../../tooltips/join_tooltip_property'; import { DataRequestAbortError } from '../../util/data_request'; @@ -48,8 +53,11 @@ import { TimesliceMaskConfig, } from '../../util/mb_filter_expressions'; import { + AggDescriptor, DynamicStylePropertyOptions, DataFilters, + ESTermSourceDescriptor, + JoinDescriptor, StyleMetaDescriptor, Timeslice, VectorLayerDescriptor, @@ -71,6 +79,11 @@ import { ITermJoinSource } from '../../sources/term_join_source'; import { addGeoJsonMbSource, getVectorSourceBounds, syncVectorSource } from './utils'; import { JoinState, performInnerJoins } from './perform_inner_joins'; import { buildVectorRequestMeta } from '../build_vector_request_meta'; +import { getJoinAggKey } from '../../../../common/get_agg_key'; + +export function isVectorLayer(layer: ILayer) { + return (layer as IVectorLayer).canShowTooltip !== undefined; +} export interface VectorLayerArguments { source: IVectorSource; @@ -83,11 +96,13 @@ export interface IVectorLayer extends ILayer { getFields(): Promise; getStyleEditorFields(): Promise; getJoins(): InnerJoin[]; + getJoinsDisabledReason(): string | null; getValidJoins(): InnerJoin[]; getSource(): IVectorSource; getFeatureById(id: string | number): Feature | null; getPropertiesForTooltip(properties: GeoJsonProperties): Promise; hasJoins(): boolean; + showJoinEditor(): boolean; canShowTooltip(): boolean; supportsFeatureEditing(): boolean; getLeftJoinFields(): Promise; @@ -113,8 +128,8 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { options: Partial, mapColors?: string[] ): VectorLayerDescriptor { - const layerDescriptor = super.createDescriptor(options); - layerDescriptor.type = VectorLayer.type; + const layerDescriptor = super.createDescriptor(options) as VectorLayerDescriptor; + layerDescriptor.type = LAYER_TYPE.VECTOR; if (!options.style) { const styleProperties = VectorStyle.createDefaultStyleProperties(mapColors ? mapColors : []); @@ -125,7 +140,7 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { layerDescriptor.joins = []; } - return layerDescriptor as VectorLayerDescriptor; + return layerDescriptor; } constructor({ @@ -147,6 +162,62 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { ); } + async cloneDescriptor(): Promise { + const clonedDescriptor = (await super.cloneDescriptor()) as VectorLayerDescriptor; + if (clonedDescriptor.joins) { + clonedDescriptor.joins.forEach((joinDescriptor: JoinDescriptor) => { + if (joinDescriptor.right && joinDescriptor.right.type === SOURCE_TYPES.TABLE_SOURCE) { + throw new Error( + 'Cannot clone table-source. Should only be used in MapEmbeddable, not in UX' + ); + } + const termSourceDescriptor: ESTermSourceDescriptor = + joinDescriptor.right as ESTermSourceDescriptor; + + // todo: must tie this to generic thing + const originalJoinId = joinDescriptor.right.id!; + + // right.id is uuid used to track requests in inspector + joinDescriptor.right.id = uuid(); + + // Update all data driven styling properties using join fields + if (clonedDescriptor.style && 'properties' in clonedDescriptor.style) { + const metrics = + termSourceDescriptor.metrics && termSourceDescriptor.metrics.length + ? termSourceDescriptor.metrics + : [{ type: AGG_TYPE.COUNT }]; + metrics.forEach((metricsDescriptor: AggDescriptor) => { + const originalJoinKey = getJoinAggKey({ + aggType: metricsDescriptor.type, + aggFieldName: 'field' in metricsDescriptor ? metricsDescriptor.field : '', + rightSourceId: originalJoinId, + }); + const newJoinKey = getJoinAggKey({ + aggType: metricsDescriptor.type, + aggFieldName: 'field' in metricsDescriptor ? metricsDescriptor.field : '', + rightSourceId: joinDescriptor.right.id!, + }); + + Object.keys(clonedDescriptor.style.properties).forEach((key) => { + const styleProp = clonedDescriptor.style.properties[key as VECTOR_STYLES]; + if ('type' in styleProp && styleProp.type === STYLE_TYPE.DYNAMIC) { + const options = styleProp.options as DynamicStylePropertyOptions; + if ( + options.field && + options.field.origin === FIELD_ORIGIN.JOIN && + options.field.name === originalJoinKey + ) { + options.field.name = newJoinKey; + } + } + }); + }); + } + }); + } + return clonedDescriptor; + } + getSource(): IVectorSource { return super.getSource() as IVectorSource; } @@ -176,6 +247,10 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { return this._joins.slice(); } + getJoinsDisabledReason() { + return this.getSource().getJoinsDisabledReason(); + } + getValidJoins() { return this.getJoins().filter((join) => { return join.hasCompleteConfig(); @@ -192,6 +267,10 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { return this.getValidJoins().length > 0; } + showJoinEditor(): boolean { + return this.getSource().showJoinEditor(); + } + isInitialDataLoadComplete() { const sourceDataRequest = this.getSourceDataRequest(); if (!sourceDataRequest || !sourceDataRequest.hasData()) { @@ -231,11 +310,8 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { } const sourceDataRequest = this.getSourceDataRequest(); - const { - tooltipContent, - areResultsTrimmed, - isDeprecated, - } = this.getSource().getSourceTooltipContent(sourceDataRequest); + const { tooltipContent, areResultsTrimmed, isDeprecated } = + this.getSource().getSourceTooltipContent(sourceDataRequest); return { icon: isDeprecated ? ( diff --git a/x-pack/plugins/maps/public/classes/layers/vector_tile_layer/vector_tile_layer.test.ts b/x-pack/plugins/maps/public/classes/layers/vector_tile_layer/vector_tile_layer.test.ts index fe3c6d27ef588..c15a0e1cf2fb8 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_tile_layer/vector_tile_layer.test.ts +++ b/x-pack/plugins/maps/public/classes/layers/vector_tile_layer/vector_tile_layer.test.ts @@ -56,15 +56,15 @@ describe('VectorTileLayer', () => { let actualMeta; let actualErrorMessage; - const mockContext = ({ + const mockContext = { startLoading: (requestId: string, token: string, meta: unknown) => { actualMeta = meta; }, onLoadError: (requestId: string, token: string, message: string) => { actualErrorMessage = message; }, - dataFilters: ({ foo: 'bar' } as unknown) as DataFilters, - } as unknown) as DataRequestContext; + dataFilters: { foo: 'bar' } as unknown as DataFilters, + } as unknown as DataRequestContext; await layer.syncData(mockContext); diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx index f0cec4abf0b14..8a02066343914 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx @@ -45,15 +45,16 @@ import { ESGeoGridSourceDescriptor, MapExtent, VectorSourceRequestMeta, - VectorSourceSyncMeta, } from '../../../../common/descriptor_types'; import { ImmutableSourceProperty, SourceEditorArgs } from '../source'; import { ISearchSource } from '../../../../../../../src/plugins/data/common/search/search_source'; -import { IndexPattern } from '../../../../../../../src/plugins/data/common/index_patterns/index_patterns'; +import { IndexPattern } from '../../../../../../../src/plugins/data/common'; import { Adapters } from '../../../../../../../src/plugins/inspector/common/adapters'; import { isValidStringConfig } from '../../util/valid_string_config'; import { ITiledSingleLayerMvtParams } from '../tiled_single_layer_vector_source/tiled_single_layer_vector_source'; +type ESGeoGridSourceSyncMeta = Pick; + export const MAX_GEOTILE_LEVEL = 29; export const clustersTitle = i18n.translate('xpack.maps.source.esGridClustersTitle', { @@ -106,7 +107,7 @@ export class ESGeoGridSource extends AbstractESAggSource implements ITiledSingle ); } - getSyncMeta(): VectorSourceSyncMeta { + getSyncMeta(): ESGeoGridSourceSyncMeta { return { requestType: this._descriptor.requestType, }; diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/es_geo_line_source.test.ts b/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/es_geo_line_source.test.ts index 4a818d898a190..81472e0eacd6e 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/es_geo_line_source.test.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/es_geo_line_source.test.ts @@ -28,9 +28,8 @@ describe('getSourceTooltipContent', () => { totalEntities: 70, }, }); - const { tooltipContent, areResultsTrimmed } = geoLineSource.getSourceTooltipContent( - sourceDataRequest - ); + const { tooltipContent, areResultsTrimmed } = + geoLineSource.getSourceTooltipContent(sourceDataRequest); expect(areResultsTrimmed).toBe(false); expect(tooltipContent).toBe('Found 70 tracks.'); }); @@ -47,9 +46,8 @@ describe('getSourceTooltipContent', () => { totalEntities: 5000, }, }); - const { tooltipContent, areResultsTrimmed } = geoLineSource.getSourceTooltipContent( - sourceDataRequest - ); + const { tooltipContent, areResultsTrimmed } = + geoLineSource.getSourceTooltipContent(sourceDataRequest); expect(areResultsTrimmed).toBe(true); expect(tooltipContent).toBe('Results limited to first 1,000 tracks of ~5,000.'); }); @@ -66,9 +64,8 @@ describe('getSourceTooltipContent', () => { totalEntities: 70, }, }); - const { tooltipContent, areResultsTrimmed } = geoLineSource.getSourceTooltipContent( - sourceDataRequest - ); + const { tooltipContent, areResultsTrimmed } = + geoLineSource.getSourceTooltipContent(sourceDataRequest); expect(areResultsTrimmed).toBe(true); expect(tooltipContent).toBe('Found 70 tracks. 10 of 70 tracks are incomplete.'); }); @@ -85,9 +82,8 @@ describe('getSourceTooltipContent', () => { totalEntities: 5000, }, }); - const { tooltipContent, areResultsTrimmed } = geoLineSource.getSourceTooltipContent( - sourceDataRequest - ); + const { tooltipContent, areResultsTrimmed } = + geoLineSource.getSourceTooltipContent(sourceDataRequest); expect(areResultsTrimmed).toBe(true); expect(tooltipContent).toBe( 'Results limited to first 1,000 tracks of ~5,000. 10 of 1,000 tracks are incomplete.' diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/es_geo_line_source.tsx b/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/es_geo_line_source.tsx index 4755781147e5b..c9f99be886990 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/es_geo_line_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/es_geo_line_source.tsx @@ -40,6 +40,8 @@ import { esFilters } from '../../../../../../../src/plugins/data/public'; import { getIsGoldPlus } from '../../../licensed_features'; import { LICENSED_FEATURES } from '../../../licensed_features'; +type ESGeoLineSourceSyncMeta = Pick; + const MAX_TRACKS = 250; export const geoLineTitle = i18n.translate('xpack.maps.source.esGeoLineTitle', { @@ -99,7 +101,7 @@ export class ESGeoLineSource extends AbstractESAggSource { ); } - getSyncMeta() { + getSyncMeta(): ESGeoLineSourceSyncMeta { return { splitField: this._descriptor.splitField, sortField: this._descriptor.sortField, diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts index 5bff5d69aeab7..6359abd06d3be 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts @@ -63,10 +63,10 @@ describe('ESSearchSource', () => { const mockSearchService = { searchSource: { async create() { - return (mockSearchSource as unknown) as SearchSource; + return mockSearchSource as unknown as SearchSource; }, createEmpty() { - return (mockSearchSource as unknown) as SearchSource; + return mockSearchSource as unknown as SearchSource; }, }, }; diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx index 2b847d218434d..f53aed0ecbc23 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx @@ -52,7 +52,6 @@ import { ESSearchSourceDescriptor, Timeslice, VectorSourceRequestMeta, - VectorSourceSyncMeta, } from '../../../../common/descriptor_types'; import { Adapters } from '../../../../../../../src/plugins/inspector/common/adapters'; import { TimeRange } from '../../../../../../../src/plugins/data/common'; @@ -74,6 +73,16 @@ import { getMatchingIndexes, } from './util/feature_edit'; +type ESSearchSourceSyncMeta = Pick< + ESSearchSourceDescriptor, + | 'filterByMapBounds' + | 'sortField' + | 'sortOrder' + | 'scalingType' + | 'topHitsSplitField' + | 'topHitsSize' +>; + export function timerangeToTimeextent(timerange: TimeRange): Timeslice | undefined { const timeRangeBounds = getTimeFilter().calculateBounds(timerange); return timeRangeBounds.min !== undefined && timeRangeBounds.max !== undefined @@ -189,11 +198,9 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye return field.aggregatable; }); - return fields.map( - (field): IField => { - return this.createField({ fieldName: field.name }); - } - ); + return fields.map((field): IField => { + return this.createField({ fieldName: field.name }); + }); } catch (error) { // failed index-pattern retrieval will show up as error-message in the layer-toc-entry return []; @@ -636,11 +643,9 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye async getLeftJoinFields(): Promise { const indexPattern = await this.getIndexPattern(); // Left fields are retrieved from _source. - return getSourceFields(indexPattern.fields).map( - (field): IField => { - return this.createField({ fieldName: field.name }); - } - ); + return getSourceFields(indexPattern.fields).map((field): IField => { + return this.createField({ fieldName: field.name }); + }); } async getSupportedShapeTypes(): Promise { @@ -714,7 +719,7 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye }; } - getSyncMeta(): VectorSourceSyncMeta | null { + getSyncMeta(): ESSearchSourceSyncMeta { return { filterByMapBounds: this._descriptor.filterByMapBounds, sortField: this._descriptor.sortField, @@ -897,7 +902,7 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye sessionId: searchFilters.searchSessionId, legacyHitsTotal: false, }); - return !isTotalHitsGreaterThan((resp.hits.total as unknown) as TotalHits, maxResultWindow); + return !isTotalHitsGreaterThan(resp.hits.total as unknown as TotalHits, maxResultWindow); } } diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/create_source_editor.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/create_source_editor.tsx index e71ee803d77e6..f6262e426033a 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/create_source_editor.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/create_source_editor.tsx @@ -77,14 +77,8 @@ export class CreateSourceEditor extends Component { }; _previewLayer = () => { - const { - indexPattern, - geoFieldName, - sortField, - sortOrder, - topHitsSplitField, - topHitsSize, - } = this.state; + const { indexPattern, geoFieldName, sortField, sortOrder, topHitsSplitField, topHitsSize } = + this.state; const tooltipProperties: string[] = []; if (topHitsSplitField) { diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/util/get_docvalue_source_fields.test.ts b/x-pack/plugins/maps/public/classes/sources/es_search_source/util/get_docvalue_source_fields.test.ts index a4429c663da84..5e27f613abdd4 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/util/get_docvalue_source_fields.test.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/util/get_docvalue_source_fields.test.ts @@ -19,7 +19,7 @@ function createMockIndexPattern(fields: IndexPatternField[]): IndexPattern { }, }; - return (indexPattern as unknown) as IndexPattern; + return indexPattern as unknown as IndexPattern; } describe('getDocValueAndSourceFields', () => { diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/util/get_docvalue_source_fields.ts b/x-pack/plugins/maps/public/classes/sources/es_search_source/util/get_docvalue_source_fields.ts index 949dc990c44fe..23f896a4b9e8f 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/util/get_docvalue_source_fields.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/util/get_docvalue_source_fields.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { IndexPattern } from '../../../../../../../../src/plugins/data/common/index_patterns/index_patterns'; +import { IndexPattern } from '../../../../../../../../src/plugins/data/common'; import { getField } from '../../../../../common/elasticsearch_util'; export interface ScriptField { diff --git a/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.ts b/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.ts index 93342d1167aeb..f7fc863eabb4a 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_term_source/es_term_source.ts @@ -25,18 +25,19 @@ import { } from '../../../../common/elasticsearch_util'; import { ESTermSourceDescriptor, - ESTermSourceSyncMeta, VectorJoinSourceRequestMeta, } from '../../../../common/descriptor_types'; import { Adapters } from '../../../../../../../src/plugins/inspector/common/adapters'; import { PropertiesMap } from '../../../../common/elasticsearch_util'; import { isValidStringConfig } from '../../util/valid_string_config'; -import { ITermJoinSource } from '../term_join_source/term_join_source'; +import { ITermJoinSource } from '../term_join_source'; import { IField } from '../../fields/field'; const TERMS_AGG_NAME = 'join'; const TERMS_BUCKET_KEYS_TO_IGNORE = ['key', 'doc_count']; +type ESTermSourceSyncMeta = Pick; + export function extractPropertiesMap(rawEsData: any, countPropertyName: string): PropertiesMap { const propertiesMap: PropertiesMap = new Map(); const buckets: any[] = _.get(rawEsData, ['aggregations', TERMS_AGG_NAME, 'buckets'], []); @@ -171,7 +172,7 @@ export class ESTermSource extends AbstractESAggSource implements ITermJoinSource return this.getMetricFields().map((esAggMetricField) => esAggMetricField.getName()); } - getSyncMeta(): ESTermSourceSyncMeta | null { + getSyncMeta(): ESTermSourceSyncMeta { return { indexPatternId: this._descriptor.indexPatternId, size: this._descriptor.size, diff --git a/x-pack/plugins/maps/public/classes/sources/geojson_file_source/geojson_file.test.ts b/x-pack/plugins/maps/public/classes/sources/geojson_file_source/geojson_file.test.ts index 9e21f16d7f30a..bce634dcc8740 100644 --- a/x-pack/plugins/maps/public/classes/sources/geojson_file_source/geojson_file.test.ts +++ b/x-pack/plugins/maps/public/classes/sources/geojson_file_source/geojson_file.test.ts @@ -20,7 +20,7 @@ describe('GeoJsonFileSource', () => { it('should get null bounds', async () => { const geojsonFileSource = new GeoJsonFileSource({}); expect( - await geojsonFileSource.getBoundsForFilters(({} as unknown) as BoundsRequestMeta, () => {}) + await geojsonFileSource.getBoundsForFilters({} as unknown as BoundsRequestMeta, () => {}) ).toEqual(null); }); @@ -51,7 +51,7 @@ describe('GeoJsonFileSource', () => { expect(geojsonFileSource.isBoundsAware()).toBe(true); expect( - await geojsonFileSource.getBoundsForFilters(({} as unknown) as BoundsRequestMeta, () => {}) + await geojsonFileSource.getBoundsForFilters({} as unknown as BoundsRequestMeta, () => {}) ).toEqual({ maxLat: 3, maxLon: 2, diff --git a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx index d041e0d3ad5de..34a30ae9ec977 100644 --- a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx @@ -25,7 +25,6 @@ import { MapExtent, MVTFieldDescriptor, TiledSingleLayerVectorSourceDescriptor, - VectorSourceSyncMeta, } from '../../../../common/descriptor_types'; import { MVTField } from '../../fields/mvt_field'; import { UpdateSourceEditor } from './update_source_editor'; @@ -42,7 +41,8 @@ export const sourceTitle = i18n.translate( export class MVTSingleLayerVectorSource extends AbstractSource - implements ITiledSingleLayerVectorSource { + implements ITiledSingleLayerVectorSource +{ static createDescriptor({ urlTemplate, layerName, @@ -196,7 +196,7 @@ export class MVTSingleLayerVectorSource return null; } - getSyncMeta(): VectorSourceSyncMeta { + getSyncMeta(): null { return null; } @@ -243,6 +243,14 @@ export class MVTSingleLayerVectorSource async getDefaultFields(): Promise>> { return {}; } + + showJoinEditor(): boolean { + return false; + } + + getJoinsDisabledReason(): string | null { + return null; + } } registerSource({ diff --git a/x-pack/plugins/maps/public/classes/sources/source.ts b/x-pack/plugins/maps/public/classes/sources/source.ts index 5b2fc16d18b41..4c2cffcf8b070 100644 --- a/x-pack/plugins/maps/public/classes/sources/source.ts +++ b/x-pack/plugins/maps/public/classes/sources/source.ts @@ -53,8 +53,6 @@ export interface ISource { isESSource(): boolean; renderSourceSettingsEditor(sourceEditorArgs: SourceEditorArgs): ReactElement | null; supportsFitToBounds(): Promise; - showJoinEditor(): boolean; - getJoinsDisabledReason(): string | null; cloneDescriptor(): AbstractSourceDescriptor; getFieldNames(): string[]; getApplyGlobalQuery(): boolean; @@ -155,14 +153,6 @@ export class AbstractSource implements ISource { return 0; } - showJoinEditor(): boolean { - return false; - } - - getJoinsDisabledReason(): string | null { - return null; - } - isESSource(): boolean { return false; } diff --git a/x-pack/plugins/maps/public/classes/sources/table_source/table_source.test.ts b/x-pack/plugins/maps/public/classes/sources/table_source/table_source.test.ts index 62404cbe942e3..30aa2e3ed258c 100644 --- a/x-pack/plugins/maps/public/classes/sources/table_source/table_source.test.ts +++ b/x-pack/plugins/maps/public/classes/sources/table_source/table_source.test.ts @@ -5,13 +5,11 @@ * 2.0. */ -import type { Query } from 'src/plugins/data/common'; import { TableSource } from './table_source'; import { FIELD_ORIGIN } from '../../../../common/constants'; import { - DataFilters, VectorJoinSourceRequestMeta, - VectorSourceSyncMeta, + VectorSourceRequestMeta, } from '../../../../common/descriptor_types'; describe('TableSource', () => { @@ -59,7 +57,7 @@ describe('TableSource', () => { }); const propertiesMap = await tableSource.getPropertiesMap( - ({} as unknown) as VectorJoinSourceRequestMeta, + {} as unknown as VectorJoinSourceRequestMeta, 'a', 'b', () => {} @@ -178,14 +176,7 @@ describe('TableSource', () => { try { await tableSource.getGeoJsonWithMeta( 'foobar', - ({} as unknown) as DataFilters & { - applyGlobalQuery: boolean; - applyGlobalTime: boolean; - fieldNames: string[]; - geogridPrecision?: number; - sourceQuery?: Query; - sourceMeta: VectorSourceSyncMeta; - }, + {} as unknown as VectorSourceRequestMeta, () => {}, () => { return false; diff --git a/x-pack/plugins/maps/public/classes/sources/table_source/table_source.ts b/x-pack/plugins/maps/public/classes/sources/table_source/table_source.ts index 8730ea7e3d02b..bb2b670ae9249 100644 --- a/x-pack/plugins/maps/public/classes/sources/table_source/table_source.ts +++ b/x-pack/plugins/maps/public/classes/sources/table_source/table_source.ts @@ -10,10 +10,9 @@ import type { Query } from 'src/plugins/data/common'; import { FIELD_ORIGIN, SOURCE_TYPES, VECTOR_SHAPE_TYPE } from '../../../../common/constants'; import { MapExtent, - DataFilters, TableSourceDescriptor, VectorJoinSourceRequestMeta, - VectorSourceSyncMeta, + VectorSourceRequestMeta, } from '../../../../common/descriptor_types'; import { Adapters } from '../../../../../../../src/plugins/inspector/common/adapters'; import { ITermJoinSource } from '../term_join_source'; @@ -55,7 +54,7 @@ export class TableSource extends AbstractVectorSource implements ITermJoinSource return `table source ${uuid()}`; } - getSyncMeta(): VectorSourceSyncMeta | null { + getSyncMeta(): null { return null; } @@ -186,14 +185,7 @@ export class TableSource extends AbstractVectorSource implements ITermJoinSource // Could be useful to implement, e.g. to preview raw csv data async getGeoJsonWithMeta( layerName: string, - searchFilters: DataFilters & { - applyGlobalQuery: boolean; - applyGlobalTime: boolean; - fieldNames: string[]; - geogridPrecision?: number; - sourceQuery?: Query; - sourceMeta: VectorSourceSyncMeta; - }, + searchFilters: VectorSourceRequestMeta, registerCancelCallback: (callback: () => void) => void, isRequestStillActive: () => boolean ): Promise { diff --git a/x-pack/plugins/maps/public/classes/sources/term_join_source/term_join_source.ts b/x-pack/plugins/maps/public/classes/sources/term_join_source/term_join_source.ts index b9cd572cd8a19..8cf1a631bf223 100644 --- a/x-pack/plugins/maps/public/classes/sources/term_join_source/term_join_source.ts +++ b/x-pack/plugins/maps/public/classes/sources/term_join_source/term_join_source.ts @@ -8,10 +8,7 @@ import { GeoJsonProperties } from 'geojson'; import { IField } from '../../fields/field'; import { Query } from '../../../../../../../src/plugins/data/common/query'; -import { - VectorJoinSourceRequestMeta, - VectorSourceSyncMeta, -} from '../../../../common/descriptor_types'; +import { VectorJoinSourceRequestMeta } from '../../../../common/descriptor_types'; import { PropertiesMap } from '../../../../common/elasticsearch_util'; import { ITooltipProperty } from '../../tooltips/tooltip_property'; import { ISource } from '../source'; @@ -26,7 +23,13 @@ export interface ITermJoinSource extends ISource { leftFieldName: string, registerCancelCallback: (callback: () => void) => void ): Promise; - getSyncMeta(): VectorSourceSyncMeta | null; + + /* + * Vector layer avoids unnecessarily re-fetching join data. + * Use getSyncMeta to expose fields that require join data re-fetch when changed. + */ + getSyncMeta(): object | null; + getId(): string; getRightFields(): IField[]; getTooltipProperties(properties: GeoJsonProperties): Promise; diff --git a/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx b/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx index bf0752d54c426..3c0adf64216e6 100644 --- a/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx @@ -17,7 +17,6 @@ import { MapExtent, Timeslice, VectorSourceRequestMeta, - VectorSourceSyncMeta, } from '../../../../common/descriptor_types'; import { DataRequest } from '../../util/data_request'; @@ -60,7 +59,15 @@ export interface IVectorSource extends ISource { getFields(): Promise; getFieldByName(fieldName: string): IField | null; getLeftJoinFields(): Promise; - getSyncMeta(): VectorSourceSyncMeta | null; + showJoinEditor(): boolean; + getJoinsDisabledReason(): string | null; + + /* + * Vector layer avoids unnecessarily re-fetching source data. + * Use getSyncMeta to expose fields that require source data re-fetch when changed. + */ + getSyncMeta(): object | null; + getFieldNames(): string[]; createField({ fieldName }: { fieldName: string }): IField; hasTooltipProperties(): boolean; @@ -117,6 +124,10 @@ export class AbstractVectorSource extends AbstractSource implements IVectorSourc return []; } + getJoinsDisabledReason(): string | null { + return null; + } + async getGeoJsonWithMeta( layerName: string, searchFilters: VectorSourceRequestMeta, @@ -159,7 +170,7 @@ export class AbstractVectorSource extends AbstractSource implements IVectorSourc return { tooltipContent: null, areResultsTrimmed: false }; } - getSyncMeta(): VectorSourceSyncMeta | null { + getSyncMeta(): object | null { return null; } diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/symbol/icon_map_select.test.tsx b/x-pack/plugins/maps/public/classes/styles/vector/components/symbol/icon_map_select.test.tsx index db74c1978debd..be59b2eae9026 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/symbol/icon_map_select.test.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/symbol/icon_map_select.test.tsx @@ -49,7 +49,8 @@ class MockDynamicStyleProperty { const defaultProps = { iconPaletteId: 'filledShapes', onChange: () => {}, - styleProperty: (new MockDynamicStyleProperty() as unknown) as IDynamicStyleProperty, + styleProperty: + new MockDynamicStyleProperty() as unknown as IDynamicStyleProperty, isCustomOnly: false, }; diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/vector_style_editor.test.tsx b/x-pack/plugins/maps/public/classes/styles/vector/components/vector_style_editor.test.tsx index 9d75c85b7a988..a53b5019a7587 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/vector_style_editor.test.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/vector_style_editor.test.tsx @@ -41,7 +41,7 @@ function createLayerMock( for (let i = 0; i < numFields; i++) { fields.push(new MockField({ fieldName: `field${i}`, origin: FIELD_ORIGIN.SOURCE })); } - return ({ + return { getStyleEditorFields: async () => { return fields; }, @@ -49,16 +49,16 @@ function createLayerMock( return layerType; }, getSource: () => { - return ({ + return { getSupportedShapeTypes: async () => { return supportedShapeTypes; }, isESSource() { return isESSource; }, - } as unknown) as IVectorSource; + } as unknown as IVectorSource; }, - } as unknown) as IVectorLayer; + } as unknown as IVectorLayer; } const vectorStyleDescriptor = { @@ -68,8 +68,8 @@ const vectorStyleDescriptor = { }; const vectorStyle = new VectorStyle( vectorStyleDescriptor, - ({} as unknown) as IVectorSource, - ({} as unknown) as IVectorLayer + {} as unknown as IVectorSource, + {} as unknown as IVectorLayer ); const styleProperties: StyleProperties = {}; vectorStyle.getAllStyleProperties().forEach((styleProperty) => { diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/vector_style_editor.tsx b/x-pack/plugins/maps/public/classes/styles/vector/components/vector_style_editor.tsx index 4fb2887c52876..d909f31315e7d 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/vector_style_editor.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/vector_style_editor.tsx @@ -378,9 +378,9 @@ export class VectorStyleEditor extends Component { let iconOrientationEditor; let iconEditor; if ( - (this.props.styleProperties[ - VECTOR_STYLES.SYMBOLIZE_AS - ] as SymbolizeAsProperty).isSymbolizedAsIcon() + ( + this.props.styleProperties[VECTOR_STYLES.SYMBOLIZE_AS] as SymbolizeAsProperty + ).isSymbolizedAsIcon() ) { iconOrientationEditor = ( diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.test.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.test.tsx index dfe358586cb94..9aaa71a11f8f1 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.test.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.test.tsx @@ -32,7 +32,7 @@ const makeProperty = (options: ColorDynamicOptions, style?: MockStyle, field?: I options, VECTOR_STYLES.LINE_COLOR, field ? field : mockField, - (new MockLayer(style ? style : new MockStyle()) as unknown) as IVectorLayer, + new MockLayer(style ? style : new MockStyle()) as unknown as IVectorLayer, () => { return (value: RawValue) => value + '_format'; } @@ -316,7 +316,7 @@ describe('supportsFieldMeta', () => { dynamicStyleOptions, VECTOR_STYLES.LINE_COLOR, null, - (new MockLayer(new MockStyle()) as unknown) as IVectorLayer, + new MockLayer(new MockStyle()) as unknown as IVectorLayer, () => { return (value: RawValue) => value + '_format'; } diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.test.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.test.tsx index 46339c5a4a20d..08ad93c5b8cb7 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.test.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.test.tsx @@ -27,7 +27,7 @@ const makeProperty = (options: Partial, field: IField = mock iconPaletteId: null, fieldMetaOptions: { isEnabled: false }, }; - const mockVectorLayer = (new MockLayer() as unknown) as IVectorLayer; + const mockVectorLayer = new MockLayer() as unknown as IVectorLayer; return new DynamicIconProperty( { ...defaultOptions, ...options }, VECTOR_STYLES.ICON, diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.test.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.test.tsx index e1a92fdcad08a..107cf430113dc 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.test.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.test.tsx @@ -47,7 +47,7 @@ const makeProperty = ( options, VECTOR_STYLES.ICON_SIZE, field, - (new MockLayer(mockStyle) as unknown) as IVectorLayer, + new MockLayer(mockStyle) as unknown as IVectorLayer, () => { return (value: RawValue) => value + '_format'; }, @@ -80,7 +80,7 @@ describe('syncSize', () => { { minSize: 8, maxSize: 32, fieldMetaOptions }, new MockStyle({ min: 0, max: 100 }) ); - const mockMbMap = (new MockMbMap() as unknown) as MbMap; + const mockMbMap = new MockMbMap() as unknown as MbMap; sizeProp.syncCircleRadiusWithMb('foobar', mockMbMap); @@ -116,7 +116,7 @@ describe('syncSize', () => { { minSize: 8, maxSize: 32, fieldMetaOptions }, new MockStyle({ min: 100, max: 100 }) ); - const mockMbMap = (new MockMbMap() as unknown) as MbMap; + const mockMbMap = new MockMbMap() as unknown as MbMap; sizeProp.syncCircleRadiusWithMb('foobar', mockMbMap); diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.tsx index 7f0e6027e7af6..f37c3a9d0f141 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.tsx @@ -77,7 +77,8 @@ export interface IDynamicStyleProperty extends IStyleProperty { export class DynamicStyleProperty extends AbstractStyleProperty - implements IDynamicStyleProperty { + implements IDynamicStyleProperty +{ static type = STYLE_TYPE.DYNAMIC; protected readonly _field: IField | null; diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_text_property.test.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_text_property.test.tsx index b5872773fdce6..2b90292ccc75a 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_text_property.test.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_text_property.test.tsx @@ -54,7 +54,7 @@ const makeProperty = (mockStyle: MockStyle, field: IField | null) => { {}, VECTOR_STYLES.LABEL_TEXT, field, - (new MockLayer(mockStyle) as unknown) as IVectorLayer, + new MockLayer(mockStyle) as unknown as IVectorLayer, () => { return (value: RawValue) => value + '_format'; } @@ -65,7 +65,7 @@ describe('syncTextFieldWithMb', () => { describe('with field', () => { test('Should set', async () => { const dynamicTextProperty = makeProperty(new MockStyle({ min: 0, max: 100 }), mockField); - const mockMbMap = (new MockMbMap() as unknown) as MbMap; + const mockMbMap = new MockMbMap() as unknown as MbMap; dynamicTextProperty.syncTextFieldWithMb('foobar', mockMbMap); @@ -79,10 +79,10 @@ describe('syncTextFieldWithMb', () => { describe('without field', () => { test('Should clear', async () => { const dynamicTextProperty = makeProperty(new MockStyle({ min: 0, max: 100 }), null); - const mockMbMap = (new MockMbMap([ + const mockMbMap = new MockMbMap([ 'foobar', ['coalesce', ['get', '__kbn__dynamic__foobar__labelText'], ''], - ]) as unknown) as MbMap; + ]) as unknown as MbMap; dynamicTextProperty.syncTextFieldWithMb('foobar', mockMbMap); @@ -98,7 +98,7 @@ describe('syncTextFieldWithMb', () => { // Do not remove this logic without verifying that mapbox-gl does not re-issue tile-requests for previously requested tiles const dynamicTextProperty = makeProperty(new MockStyle({ min: 0, max: 100 }), null); - const mockMbMap = (new MockMbMap(undefined) as unknown) as MbMap; + const mockMbMap = new MockMbMap(undefined) as unknown as MbMap; dynamicTextProperty.syncTextFieldWithMb('foobar', mockMbMap); diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/label_border_size_property.ts b/x-pack/plugins/maps/public/classes/styles/vector/properties/label_border_size_property.ts index 5d4425a9d4995..2934676d2ad85 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/label_border_size_property.ts +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/label_border_size_property.ts @@ -51,8 +51,9 @@ export class LabelBorderSizeProperty extends AbstractStyleProperty { describe('syncTextFieldWithMb', () => { test('Should set with value', async () => { const dynamicTextProperty = makeProperty('foo'); - const mockMbMap = (new MockMbMap() as unknown) as MbMap; + const mockMbMap = new MockMbMap() as unknown as MbMap; dynamicTextProperty.syncTextFieldWithMb('foobar', mockMbMap); @@ -60,7 +60,7 @@ describe('syncTextFieldWithMb', () => { // Do not remove this logic without verifying that mapbox-gl does not re-issue tile-requests for previously requested tiles const dynamicTextProperty = makeProperty(''); - const mockMbMap = (new MockMbMap(undefined) as unknown) as MbMap; + const mockMbMap = new MockMbMap(undefined) as unknown as MbMap; dynamicTextProperty.syncTextFieldWithMb('foobar', mockMbMap); diff --git a/x-pack/plugins/maps/public/classes/styles/vector/style_fields_helper.test.ts b/x-pack/plugins/maps/public/classes/styles/vector/style_fields_helper.test.ts index bb76e2114c9aa..4407620f09ce5 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/style_fields_helper.test.ts +++ b/x-pack/plugins/maps/public/classes/styles/vector/style_fields_helper.test.ts @@ -28,9 +28,7 @@ class MockField extends AbstractField { describe('StyleFieldHelper', () => { describe('isFieldDataTypeCompatibleWithStyleType', () => { - async function createHelper( - supportsAutoDomain: boolean - ): Promise<{ + async function createHelper(supportsAutoDomain: boolean): Promise<{ styleFieldHelper: StyleFieldsHelper; stringField: IField; numberField: IField; diff --git a/x-pack/plugins/maps/public/classes/styles/vector/vector_style.test.js b/x-pack/plugins/maps/public/classes/styles/vector/vector_style.test.js index e4f8b95806ce9..9057d0dfd5c7f 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/vector_style.test.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/vector_style.test.js @@ -86,14 +86,8 @@ describe('getDescriptorWithUpdatedStyleProps', () => { const vectorStyle = new VectorStyle({ properties }, new MockSource()); const nextFields = []; - const { - hasChanges, - nextStyleDescriptor, - } = await vectorStyle.getDescriptorWithUpdatedStyleProps( - nextFields, - previousFields, - mapColors - ); + const { hasChanges, nextStyleDescriptor } = + await vectorStyle.getDescriptorWithUpdatedStyleProps(nextFields, previousFields, mapColors); expect(hasChanges).toBe(true); expect(nextStyleDescriptor.properties[VECTOR_STYLES.LINE_COLOR]).toEqual({ options: { @@ -119,14 +113,8 @@ describe('getDescriptorWithUpdatedStyleProps', () => { supportsAutoDomain: false, }), ]; - const { - hasChanges, - nextStyleDescriptor, - } = await vectorStyle.getDescriptorWithUpdatedStyleProps( - nextFields, - previousFields, - mapColors - ); + const { hasChanges, nextStyleDescriptor } = + await vectorStyle.getDescriptorWithUpdatedStyleProps(nextFields, previousFields, mapColors); expect(hasChanges).toBe(true); expect(nextStyleDescriptor.properties[VECTOR_STYLES.ICON_SIZE]).toEqual({ options: { @@ -142,14 +130,8 @@ describe('getDescriptorWithUpdatedStyleProps', () => { const vectorStyle = new VectorStyle({ properties }, new MockSource()); const nextFields = [new MockField({ fieldName: 'someOtherField', dataType: 'number' })]; - const { - hasChanges, - nextStyleDescriptor, - } = await vectorStyle.getDescriptorWithUpdatedStyleProps( - nextFields, - previousFields, - mapColors - ); + const { hasChanges, nextStyleDescriptor } = + await vectorStyle.getDescriptorWithUpdatedStyleProps(nextFields, previousFields, mapColors); expect(hasChanges).toBe(true); expect(nextStyleDescriptor.properties[VECTOR_STYLES.LINE_COLOR]).toEqual({ options: { diff --git a/x-pack/plugins/maps/public/classes/styles/vector/vector_style.tsx b/x-pack/plugins/maps/public/classes/styles/vector/vector_style.tsx index 695ae95b1b438..1e7267b9e1e32 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/vector_style.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/vector_style.tsx @@ -258,9 +258,9 @@ export class VectorStyle implements IVectorStyle { mapColors: string[] ) { const originalProperties = this.getRawProperties(); - const invalidStyleNames: VECTOR_STYLES[] = (Object.keys( - originalProperties - ) as VECTOR_STYLES[]).filter((key) => { + const invalidStyleNames: VECTOR_STYLES[] = ( + Object.keys(originalProperties) as VECTOR_STYLES[] + ).filter((key) => { const dynamicOptions = getDynamicOptions(originalProperties, key); if (!dynamicOptions || !dynamicOptions.field || !dynamicOptions.field.name) { return false; @@ -547,12 +547,10 @@ export class VectorStyle implements IVectorStyle { } dynamicProperties.forEach((dynamicProperty) => { - const ordinalStyleMeta = dynamicProperty.pluckOrdinalStyleMetaFromTileMetaFeatures( - metaFeatures - ); - const categoricalStyleMeta = dynamicProperty.pluckCategoricalStyleMetaFromTileMetaFeatures( - metaFeatures - ); + const ordinalStyleMeta = + dynamicProperty.pluckOrdinalStyleMetaFromTileMetaFeatures(metaFeatures); + const categoricalStyleMeta = + dynamicProperty.pluckCategoricalStyleMetaFromTileMetaFeatures(metaFeatures); const name = dynamicProperty.getFieldName(); if (!styleMeta.fieldMeta[name]) { @@ -633,9 +631,8 @@ export class VectorStyle implements IVectorStyle { dynamicProperties.forEach( (dynamicProperty: IDynamicStyleProperty) => { - const categoricalStyleMeta = dynamicProperty.pluckCategoricalStyleMetaFromFeatures( - features - ); + const categoricalStyleMeta = + dynamicProperty.pluckCategoricalStyleMetaFromFeatures(features); const ordinalStyleMeta = dynamicProperty.pluckOrdinalStyleMetaFromFeatures(features); const name = dynamicProperty.getFieldName(); if (!styleMeta.fieldMeta[name]) { diff --git a/x-pack/plugins/maps/public/classes/util/can_skip_fetch.test.ts b/x-pack/plugins/maps/public/classes/util/can_skip_fetch.test.ts index 16d25469025f4..a564644df7af0 100644 --- a/x-pack/plugins/maps/public/classes/util/can_skip_fetch.test.ts +++ b/x-pack/plugins/maps/public/classes/util/can_skip_fetch.test.ts @@ -128,12 +128,12 @@ describe('canSkipSourceUpdate', () => { it('can skip update when filter changes', async () => { const nextRequestMeta = { applyGlobalQuery: prevApplyGlobalQuery, - filters: [({} as unknown) as Filter], + filters: [{} as unknown as Filter], query: prevQuery, }; const canSkipUpdate = await canSkipSourceUpdate({ - source: (queryAwareSourceMock as unknown) as ISource, + source: queryAwareSourceMock as unknown as ISource, prevDataRequest, nextRequestMeta, extentAware: queryAwareSourceMock.isFilterByMapBounds(), @@ -154,7 +154,7 @@ describe('canSkipSourceUpdate', () => { }; const canSkipUpdate = await canSkipSourceUpdate({ - source: (queryAwareSourceMock as unknown) as ISource, + source: queryAwareSourceMock as unknown as ISource, prevDataRequest, nextRequestMeta, extentAware: queryAwareSourceMock.isFilterByMapBounds(), @@ -174,7 +174,7 @@ describe('canSkipSourceUpdate', () => { }; const canSkipUpdate = await canSkipSourceUpdate({ - source: (queryAwareSourceMock as unknown) as ISource, + source: queryAwareSourceMock as unknown as ISource, prevDataRequest, nextRequestMeta, extentAware: queryAwareSourceMock.isFilterByMapBounds(), @@ -194,7 +194,7 @@ describe('canSkipSourceUpdate', () => { }; const canSkipUpdate = await canSkipSourceUpdate({ - source: (queryAwareSourceMock as unknown) as ISource, + source: queryAwareSourceMock as unknown as ISource, prevDataRequest, nextRequestMeta, extentAware: queryAwareSourceMock.isFilterByMapBounds(), @@ -212,7 +212,7 @@ describe('canSkipSourceUpdate', () => { }; const canSkipUpdate = await canSkipSourceUpdate({ - source: (queryAwareSourceMock as unknown) as ISource, + source: queryAwareSourceMock as unknown as ISource, prevDataRequest, nextRequestMeta, extentAware: queryAwareSourceMock.isFilterByMapBounds(), @@ -239,12 +239,12 @@ describe('canSkipSourceUpdate', () => { it('can not skip update when filter changes', async () => { const nextRequestMeta = { applyGlobalQuery: prevApplyGlobalQuery, - filters: [({} as unknown) as Filter], + filters: [{} as unknown as Filter], query: prevQuery, }; const canSkipUpdate = await canSkipSourceUpdate({ - source: (queryAwareSourceMock as unknown) as ISource, + source: queryAwareSourceMock as unknown as ISource, prevDataRequest, nextRequestMeta, extentAware: queryAwareSourceMock.isFilterByMapBounds(), @@ -265,7 +265,7 @@ describe('canSkipSourceUpdate', () => { }; const canSkipUpdate = await canSkipSourceUpdate({ - source: (queryAwareSourceMock as unknown) as ISource, + source: queryAwareSourceMock as unknown as ISource, prevDataRequest, nextRequestMeta, extentAware: queryAwareSourceMock.isFilterByMapBounds(), @@ -285,7 +285,7 @@ describe('canSkipSourceUpdate', () => { }; const canSkipUpdate = await canSkipSourceUpdate({ - source: (queryAwareSourceMock as unknown) as ISource, + source: queryAwareSourceMock as unknown as ISource, prevDataRequest, nextRequestMeta, extentAware: queryAwareSourceMock.isFilterByMapBounds(), @@ -303,7 +303,7 @@ describe('canSkipSourceUpdate', () => { }; const canSkipUpdate = await canSkipSourceUpdate({ - source: (queryAwareSourceMock as unknown) as ISource, + source: queryAwareSourceMock as unknown as ISource, prevDataRequest, nextRequestMeta, extentAware: queryAwareSourceMock.isFilterByMapBounds(), @@ -339,7 +339,7 @@ describe('canSkipSourceUpdate', () => { describe('applyGlobalTime', () => { it('can not skip update when applyGlobalTime changes', async () => { const canSkipUpdate = await canSkipSourceUpdate({ - source: (createSourceMock() as unknown) as ISource, + source: createSourceMock() as unknown as ISource, prevDataRequest: new DataRequest({ dataId: SOURCE_DATA_REQUEST_ID, dataRequestMeta: { @@ -359,7 +359,7 @@ describe('canSkipSourceUpdate', () => { it('can skip update when applyGlobalTime does not change', async () => { const canSkipUpdate = await canSkipSourceUpdate({ - source: (createSourceMock() as unknown) as ISource, + source: createSourceMock() as unknown as ISource, prevDataRequest: new DataRequest({ dataId: SOURCE_DATA_REQUEST_ID, dataRequestMeta: { @@ -381,7 +381,7 @@ describe('canSkipSourceUpdate', () => { describe('timeFilters', () => { it('can not skip update when time range changes', async () => { const canSkipUpdate = await canSkipSourceUpdate({ - source: (createSourceMock() as unknown) as ISource, + source: createSourceMock() as unknown as ISource, prevDataRequest: new DataRequest({ dataId: SOURCE_DATA_REQUEST_ID, dataRequestMeta: { @@ -409,7 +409,7 @@ describe('canSkipSourceUpdate', () => { it('can skip update when time range does not change', async () => { const canSkipUpdate = await canSkipSourceUpdate({ - source: (createSourceMock() as unknown) as ISource, + source: createSourceMock() as unknown as ISource, prevDataRequest: new DataRequest({ dataId: SOURCE_DATA_REQUEST_ID, dataRequestMeta: { @@ -437,7 +437,7 @@ describe('canSkipSourceUpdate', () => { it('can skip update when time range changes but applyGlobalTime is false', async () => { const canSkipUpdate = await canSkipSourceUpdate({ - source: (createSourceMock() as unknown) as ISource, + source: createSourceMock() as unknown as ISource, prevDataRequest: new DataRequest({ dataId: SOURCE_DATA_REQUEST_ID, dataRequestMeta: { @@ -468,7 +468,7 @@ describe('canSkipSourceUpdate', () => { const mockSource = createSourceMock(); it('can not skip update when timeslice changes (undefined => provided)', async () => { const canSkipUpdate = await canSkipSourceUpdate({ - source: (mockSource as unknown) as ISource, + source: mockSource as unknown as ISource, prevDataRequest: new DataRequest({ dataId: SOURCE_DATA_REQUEST_ID, dataRequestMeta: { @@ -500,7 +500,7 @@ describe('canSkipSourceUpdate', () => { it('can not skip update when timeslice changes', async () => { const canSkipUpdate = await canSkipSourceUpdate({ - source: (mockSource as unknown) as ISource, + source: mockSource as unknown as ISource, prevDataRequest: new DataRequest({ dataId: SOURCE_DATA_REQUEST_ID, dataRequestMeta: { @@ -536,7 +536,7 @@ describe('canSkipSourceUpdate', () => { it('can not skip update when timeslice changes (provided => undefined)', async () => { const canSkipUpdate = await canSkipSourceUpdate({ - source: (mockSource as unknown) as ISource, + source: mockSource as unknown as ISource, prevDataRequest: new DataRequest({ dataId: SOURCE_DATA_REQUEST_ID, dataRequestMeta: { @@ -568,7 +568,7 @@ describe('canSkipSourceUpdate', () => { it('can skip update when timeslice does not change', async () => { const canSkipUpdate = await canSkipSourceUpdate({ - source: (mockSource as unknown) as ISource, + source: mockSource as unknown as ISource, prevDataRequest: new DataRequest({ dataId: SOURCE_DATA_REQUEST_ID, dataRequestMeta: { @@ -604,7 +604,7 @@ describe('canSkipSourceUpdate', () => { it('can skip update when timeslice changes but applyGlobalTime is false', async () => { const canSkipUpdate = await canSkipSourceUpdate({ - source: (mockSource as unknown) as ISource, + source: mockSource as unknown as ISource, prevDataRequest: new DataRequest({ dataId: SOURCE_DATA_REQUEST_ID, dataRequestMeta: { diff --git a/x-pack/plugins/maps/public/components/force_refresh_checkbox.tsx b/x-pack/plugins/maps/public/components/force_refresh_checkbox.tsx index 1b0d021b2efdb..b705d1a6dce21 100644 --- a/x-pack/plugins/maps/public/components/force_refresh_checkbox.tsx +++ b/x-pack/plugins/maps/public/components/force_refresh_checkbox.tsx @@ -15,7 +15,7 @@ interface Props { } export function ForceRefreshCheckbox({ applyForceRefresh, setApplyForceRefresh }: Props) { - const onRespondToForceRefreshChange = (event: EuiSwitchEvent) => { + const onChange = (event: EuiSwitchEvent) => { setApplyForceRefresh(event.target.checked); }; @@ -24,7 +24,7 @@ export function ForceRefreshCheckbox({ applyForceRefresh, setApplyForceRefresh } diff --git a/x-pack/plugins/maps/public/components/global_time_checkbox.tsx b/x-pack/plugins/maps/public/components/global_time_checkbox.tsx index ae0c50c063b68..70d6859b8b02a 100644 --- a/x-pack/plugins/maps/public/components/global_time_checkbox.tsx +++ b/x-pack/plugins/maps/public/components/global_time_checkbox.tsx @@ -24,7 +24,7 @@ export function GlobalTimeCheckbox({ applyGlobalTime, label, setApplyGlobalTime
mockSourceSettings @@ -112,6 +114,7 @@ exports[`EditLayerPanel is rendered 1`] = ` { return '1'; }, @@ -64,6 +64,9 @@ const mockLayer = ({ showJoinEditor: () => { return true; }, + canShowTooltip: () => { + return true; + }, supportsElasticsearchFilters: () => { return false; }, @@ -76,7 +79,10 @@ const mockLayer = ({ hasErrors: () => { return false; }, -} as unknown) as ILayer; + supportsFitToBounds: () => { + return true; + }, +} as unknown as ILayer; const defaultProps = { selectedLayer: mockLayer, diff --git a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/edit_layer_panel.tsx b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/edit_layer_panel.tsx index eb3c472252252..424c4b8e16bec 100644 --- a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/edit_layer_panel.tsx +++ b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/edit_layer_panel.tsx @@ -33,7 +33,7 @@ import { Storage } from '../../../../../../src/plugins/kibana_utils/public'; import { LAYER_TYPE } from '../../../common/constants'; import { getData, getCore } from '../../kibana_services'; import { ILayer } from '../../classes/layers/layer'; -import { IVectorLayer } from '../../classes/layers/vector_layer'; +import { isVectorLayer, IVectorLayer } from '../../classes/layers/vector_layer'; import { ImmutableSourceProperty, OnSourceChangeArgs } from '../../classes/sources/source'; import { IField } from '../../classes/fields/field'; @@ -111,18 +111,20 @@ export class EditLayerPanel extends Component { }; async _loadLeftJoinFields() { - if ( - !this.props.selectedLayer || - !this.props.selectedLayer.showJoinEditor() || - (this.props.selectedLayer as IVectorLayer).getLeftJoinFields === undefined - ) { + if (!this.props.selectedLayer || !isVectorLayer(this.props.selectedLayer)) { + return; + } + + const vectorLayer = this.props.selectedLayer as IVectorLayer; + if (!vectorLayer.showJoinEditor() || vectorLayer.getLeftJoinFields === undefined) { return; } let leftJoinFields: JoinField[] = []; try { - const leftFieldsInstances = await (this.props - .selectedLayer as IVectorLayer).getLeftJoinFields(); + const leftFieldsInstances = await ( + this.props.selectedLayer as IVectorLayer + ).getLeftJoinFields(); const leftFieldPromises = leftFieldsInstances.map(async (field: IField) => { return { name: field.getName(), @@ -181,7 +183,11 @@ export class EditLayerPanel extends Component { } _renderJoinSection() { - if (!this.props.selectedLayer || !this.props.selectedLayer.showJoinEditor()) { + if (!this.props.selectedLayer || !isVectorLayer(this.props.selectedLayer)) { + return; + } + const vectorLayer = this.props.selectedLayer as IVectorLayer; + if (!vectorLayer.showJoinEditor()) { return null; } @@ -189,7 +195,7 @@ export class EditLayerPanel extends Component { diff --git a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/filter_editor/filter_editor.tsx b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/filter_editor/filter_editor.tsx index c92bd43af14cf..548344466cac9 100644 --- a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/filter_editor/filter_editor.tsx +++ b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/filter_editor/filter_editor.tsx @@ -240,7 +240,7 @@ export class FilterEditor extends Component { { const component = shallow( - + ); expect(component).toMatchSnapshot(); }); @@ -57,7 +57,7 @@ test('Should render callout when joins are disabled', () => { const component = shallow( ); expect(component).toMatchSnapshot(); diff --git a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/join_editor.tsx b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/join_editor.tsx index e0d630994566d..e99ec6a688092 100644 --- a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/join_editor.tsx +++ b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/join_editor.tsx @@ -20,7 +20,7 @@ import { import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { Join } from './resources/join'; -import { ILayer } from '../../../classes/layers/layer'; +import { IVectorLayer } from '../../../classes/layers/vector_layer'; import { JoinDescriptor } from '../../../../common/descriptor_types'; import { SOURCE_TYPES } from '../../../../common/constants'; @@ -31,10 +31,10 @@ export interface JoinField { export interface Props { joins: JoinDescriptor[]; - layer: ILayer; + layer: IVectorLayer; layerDisplayName: string; leftJoinFields: JoinField[]; - onChange: (layer: ILayer, joins: JoinDescriptor[]) => void; + onChange: (layer: IVectorLayer, joins: JoinDescriptor[]) => void; } export function JoinEditor({ joins, layer, onChange, leftJoinFields, layerDisplayName }: Props) { diff --git a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/resources/join.tsx b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/resources/join.tsx index c636047e2be37..189adde85c6b3 100644 --- a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/resources/join.tsx +++ b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/resources/join.tsx @@ -211,7 +211,7 @@ export class Join extends Component { } setApplyGlobalQuery={this._onApplyGlobalQueryChange} label={i18n.translate('xpack.maps.layerPanel.join.applyGlobalQueryCheckboxLabel', { - defaultMessage: `Apply global filter to join`, + defaultMessage: `Apply global search to join`, })} /> ); diff --git a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/layer_settings/attribution_form_row.test.tsx b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/layer_settings/attribution_form_row.test.tsx index bd8be0ab37b51..ed830195f40f4 100644 --- a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/layer_settings/attribution_form_row.test.tsx +++ b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/layer_settings/attribution_form_row.test.tsx @@ -17,61 +17,61 @@ const defaultProps = { }; test('Should render null when layer source has attribution provider', () => { - const sourceMock = ({ + const sourceMock = { getAttributionProvider: () => { return async () => { return [{ url: 'url1', label: 'label1' }]; }; }, - } as unknown) as ISource; - const layerMock = ({ + } as unknown as ISource; + const layerMock = { getSource: () => { return sourceMock; }, - } as unknown) as ILayer; + } as unknown as ILayer; const component = shallow(); expect(component).toMatchSnapshot(); }); test('Should render add form row when attribution not provided', () => { - const sourceMock = ({ + const sourceMock = { getAttributionProvider: () => { return null; }, - } as unknown) as ISource; - const layerMock = ({ + } as unknown as ISource; + const layerMock = { getSource: () => { return sourceMock; }, getDescriptor: () => { - return ({} as unknown) as LayerDescriptor; + return {} as unknown as LayerDescriptor; }, - } as unknown) as ILayer; + } as unknown as ILayer; const component = shallow(); expect(component).toMatchSnapshot(); }); test('Should render edit form row when attribution not provided', () => { - const sourceMock = ({ + const sourceMock = { getAttributionProvider: () => { return null; }, - } as unknown) as ISource; - const layerMock = ({ + } as unknown as ISource; + const layerMock = { getSource: () => { return sourceMock; }, getDescriptor: () => { - return ({ + return { attribution: { url: 'url1', label: 'label1', }, - } as unknown) as LayerDescriptor; + } as unknown as LayerDescriptor; }, - } as unknown) as ILayer; + } as unknown as ILayer; const component = shallow(); expect(component).toMatchSnapshot(); diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/scale_control/scale_control.test.tsx b/x-pack/plugins/maps/public/connected_components/mb_map/scale_control/scale_control.test.tsx index 0eb3ff2de16e8..99e8f700acdd6 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/scale_control/scale_control.test.tsx +++ b/x-pack/plugins/maps/public/connected_components/mb_map/scale_control/scale_control.test.tsx @@ -14,7 +14,7 @@ const CLIENT_HEIGHT_PIXELS = 1200; const DISTANCE_METERS = 87653; const mockMbMapHandlers: { [key: string]: () => void } = {}; -const mockMBMap = ({ +const mockMBMap = { on: (eventName: string, callback: () => void) => { mockMbMapHandlers[eventName] = callback; }, @@ -30,23 +30,23 @@ const mockMBMap = ({ return 4; }, getBounds: () => { - return ({ + return { getNorth: () => { return 75; }, getSouth: () => { return -60; }, - } as unknown) as LngLatBounds; + } as unknown as LngLatBounds; }, unproject: (point: PointLike) => { - return ({ + return { distanceTo: (lngLat: LngLat) => { return DISTANCE_METERS; }, - } as unknown) as LngLat; + } as unknown as LngLat; }, -} as unknown) as MapboxMap; +} as unknown as MapboxMap; test('render', () => { const component = shallow(); diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/sort_layers.test.ts b/x-pack/plugins/maps/public/connected_components/mb_map/sort_layers.test.ts index 596ba793db708..74a57bd76ab8f 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/sort_layers.test.ts +++ b/x-pack/plugins/maps/public/connected_components/mb_map/sort_layers.test.ts @@ -114,14 +114,11 @@ describe('sortLayer', () => { const BRAVO_LAYER_ID = 'bravo'; const CHARLIE_LAYER_ID = 'charlie'; - const spatialFilterLayer = (new MockMapLayer( - SPATIAL_FILTERS_LAYER_ID, - false - ) as unknown) as ILayer; + const spatialFilterLayer = new MockMapLayer(SPATIAL_FILTERS_LAYER_ID, false) as unknown as ILayer; const mapLayers = [ - (new MockMapLayer(CHARLIE_LAYER_ID, true) as unknown) as ILayer, - (new MockMapLayer(BRAVO_LAYER_ID, false) as unknown) as ILayer, - (new MockMapLayer(ALPHA_LAYER_ID, false) as unknown) as ILayer, + new MockMapLayer(CHARLIE_LAYER_ID, true) as unknown as ILayer, + new MockMapLayer(BRAVO_LAYER_ID, false) as unknown as ILayer, + new MockMapLayer(ALPHA_LAYER_ID, false) as unknown as ILayer, ]; beforeEach(() => { @@ -149,7 +146,7 @@ describe('sortLayer', () => { ], }; const mbMap = new MockMbMap(initialMbStyle); - syncLayerOrder((mbMap as unknown) as MbMap, spatialFilterLayer, mapLayers); + syncLayerOrder(mbMap as unknown as MbMap, spatialFilterLayer, mapLayers); const sortedMbStyle = mbMap.getStyle(); const sortedMbLayerIds = sortedMbStyle.layers!.map((mbLayer) => { return mbLayer.id; @@ -187,7 +184,7 @@ describe('sortLayer', () => { ], }; const mbMap = new MockMbMap(initialMbStyle); - syncLayerOrder((mbMap as unknown) as MbMap, spatialFilterLayer, mapLayers); + syncLayerOrder(mbMap as unknown as MbMap, spatialFilterLayer, mapLayers); const sortedMbStyle = mbMap.getStyle(); const sortedMbLayerIds = sortedMbStyle.layers!.map((mbLayer) => { return mbLayer.id; @@ -215,7 +212,7 @@ describe('sortLayer', () => { ], }; const mbMap = new MockMbMap(initialMbStyle); - syncLayerOrder((mbMap as unknown) as MbMap, spatialFilterLayer, mapLayers); + syncLayerOrder(mbMap as unknown as MbMap, spatialFilterLayer, mapLayers); const sortedMbStyle = mbMap.getStyle(); const sortedMbLayerIds = sortedMbStyle.layers!.map((mbLayer) => { return mbLayer.id; @@ -242,7 +239,7 @@ describe('sortLayer', () => { ], }; const mbMap = new MockMbMap(initialMbStyle); - syncLayerOrder((mbMap as unknown) as MbMap, spatialFilterLayer, mapLayers); + syncLayerOrder(mbMap as unknown as MbMap, spatialFilterLayer, mapLayers); expect(moveCounter).toBe(0); }); }); diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/sort_layers.ts b/x-pack/plugins/maps/public/connected_components/mb_map/sort_layers.ts index 927c6819351c2..ef248380af36a 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/sort_layers.ts +++ b/x-pack/plugins/maps/public/connected_components/mb_map/sort_layers.ts @@ -124,11 +124,11 @@ export function syncLayerOrder(mbMap: MbMap, spatialFiltersLayer: ILayer, layerL let beneathMbLayerId = getBottomMbLayerId(mbLayers, spatialFiltersLayer, LAYER_CLASS.ANY); // Ensure gl-draw layers are on top of all layerList layers - const glDrawLayer = ({ + const glDrawLayer = { ownsMbLayerId: (mbLayerId: string) => { return isGlDrawLayer(mbLayerId); }, - } as unknown) as ILayer; + } as unknown as ILayer; moveMapLayer(mbMap, mbLayers, glDrawLayer, LAYER_CLASS.ANY, beneathMbLayerId); const glDrawBottomMbLayerId = getBottomMbLayerId(mbLayers, glDrawLayer, LAYER_CLASS.ANY); if (glDrawBottomMbLayerId) { diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/tile_status_tracker.test.ts b/x-pack/plugins/maps/public/connected_components/mb_map/tile_status_tracker.test.ts index c2995802339c7..76052f4590aba 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/tile_status_tracker.test.ts +++ b/x-pack/plugins/maps/public/connected_components/mb_map/tile_status_tracker.test.ts @@ -52,7 +52,7 @@ class MockLayer { } function createMockLayer(id: string, mbSourceId: string): ILayer { - return (new MockLayer(id, mbSourceId) as unknown) as ILayer; + return new MockLayer(id, mbSourceId) as unknown as ILayer; } function createMockMbDataEvent(mbSourceId: string, tileKey: string): unknown { @@ -83,7 +83,7 @@ describe('TileStatusTracker', () => { const mockMbMap = new MockMbMap(); const loadedMap: Map = new Map(); new TileStatusTracker({ - mbMap: (mockMbMap as unknown) as MbMap, + mbMap: mockMbMap as unknown as MbMap, updateTileStatus: (layer, areTilesLoaded) => { loadedMap.set(layer.getId(), areTilesLoaded); }, @@ -126,7 +126,7 @@ describe('TileStatusTracker', () => { test('should cleanup listeners on destroy', async () => { const mockMbMap = new MockMbMap(); const tileStatusTracker = new TileStatusTracker({ - mbMap: (mockMbMap as unknown) as MbMap, + mbMap: mockMbMap as unknown as MbMap, updateTileStatus: () => {}, getCurrentLayerList: () => { return []; diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/tile_status_tracker.ts b/x-pack/plugins/maps/public/connected_components/mb_map/tile_status_tracker.ts index b25bbf63a1e44..72d692a147ff2 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/tile_status_tracker.ts +++ b/x-pack/plugins/maps/public/connected_components/mb_map/tile_status_tracker.ts @@ -36,14 +36,13 @@ export class TileStatusTracker { ) { const tracked = this._tileCache.find((tile) => { return ( - tile.mbKey === ((e.tile.tileID.key as unknown) as string) && - tile.mbSourceId === e.sourceId + tile.mbKey === (e.tile.tileID.key as unknown as string) && tile.mbSourceId === e.sourceId ); }); if (!tracked) { this._tileCache.push({ - mbKey: (e.tile.tileID.key as unknown) as string, + mbKey: e.tile.tileID.key as unknown as string, mbSourceId: e.sourceId, mbTile: e.tile, }); @@ -59,7 +58,7 @@ export class TileStatusTracker { e.tile && (e.source.type === 'vector' || e.source.type === 'raster') ) { - this._removeTileFromCache(e.sourceId, (e.tile.tileID.key as unknown) as string); + this._removeTileFromCache(e.sourceId, e.tile.tileID.key as unknown as string); } }; private readonly _onSourceData = (e: MapSourceDataEvent) => { @@ -70,7 +69,7 @@ export class TileStatusTracker { e.tile && (e.source.type === 'vector' || e.source.type === 'raster') ) { - this._removeTileFromCache(e.sourceId, (e.tile.tileID.key as unknown) as string); + this._removeTileFromCache(e.sourceId, e.tile.tileID.key as unknown as string); } }; @@ -114,7 +113,7 @@ export class TileStatusTracker { _removeTileFromCache = (mbSourceId: string, mbKey: string) => { const trackedIndex = this._tileCache.findIndex((tile) => { - return tile.mbKey === ((mbKey as unknown) as string) && tile.mbSourceId === mbSourceId; + return tile.mbKey === (mbKey as unknown as string) && tile.mbSourceId === mbSourceId; }); if (trackedIndex >= 0) { diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/features_tooltip/feature_properties.test.tsx b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/features_tooltip/feature_properties.test.tsx index cfe94f43167ca..3c7278a387179 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/features_tooltip/feature_properties.test.tsx +++ b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/features_tooltip/feature_properties.test.tsx @@ -51,17 +51,17 @@ const defaultProps = { showFilterButtons: false, addFilters: async () => {}, getActionContext: () => { - return ({} as unknown) as ActionExecutionContext; + return {} as unknown as ActionExecutionContext; }, getFilterActions: async () => { - return [({ id: ACTION_GLOBAL_APPLY_FILTER } as unknown) as Action]; + return [{ id: ACTION_GLOBAL_APPLY_FILTER } as unknown as Action]; }, showFilterActions: () => {}, }; const mockTooltipProperties = [ - (new MockTooltipProperty('prop1', 'foobar1', true) as unknown) as ITooltipProperty, - (new MockTooltipProperty('prop2', 'foobar2', false) as unknown) as ITooltipProperty, + new MockTooltipProperty('prop1', 'foobar1', true) as unknown as ITooltipProperty, + new MockTooltipProperty('prop2', 'foobar2', false) as unknown as ITooltipProperty, ]; describe('FeatureProperties', () => { @@ -111,7 +111,7 @@ describe('FeatureProperties', () => { return mockTooltipProperties; }} getFilterActions={async () => { - return [({ id: 'drilldown1' } as unknown) as Action]; + return [{ id: 'drilldown1' } as unknown as Action]; }} /> ); diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/features_tooltip/footer.test.tsx b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/features_tooltip/footer.test.tsx index 15702096817dd..5b5a8b0d35007 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/features_tooltip/footer.test.tsx +++ b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/features_tooltip/footer.test.tsx @@ -13,14 +13,14 @@ import { IVectorLayer } from '../../../../classes/layers/vector_layer'; const defaultProps = { isLocked: false, findLayerById: (id: string) => { - return ({ + return { async getDisplayName() { return `display + ${id}`; }, getId() { return id; }, - } as unknown) as IVectorLayer; + } as unknown as IVectorLayer; }, setCurrentFeature: () => {}, }; diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/features_tooltip/header.test.tsx b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/features_tooltip/header.test.tsx index 8a8bbeb1b7db6..4a5449506feea 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/features_tooltip/header.test.tsx +++ b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/features_tooltip/header.test.tsx @@ -10,7 +10,7 @@ import { shallow } from 'enzyme'; import { Header } from './header'; import { IVectorLayer } from '../../../../classes/layers/vector_layer'; -const layerMock = ({ +const layerMock = { getDisplayName: async () => { return 'myLayerName'; }, @@ -19,7 +19,7 @@ const layerMock = ({ icon: mockIcon, }; }, -} as unknown) as IVectorLayer; +} as unknown as IVectorLayer; const defaultProps = { findLayerById: (layerId: string) => { diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_control.test.tsx b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_control.test.tsx index 94ce1e7545800..7e79113d6b242 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_control.test.tsx +++ b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_control.test.tsx @@ -23,7 +23,7 @@ let featuresAtLocation: MapboxGeoJSONFeature[] = []; const layerId = 'tfi3f'; const mbLayerId = 'tfi3f_circle'; -const mockLayer = ({ +const mockLayer = { getMbLayerIds: () => { return [mbLayerId]; }, @@ -52,10 +52,10 @@ const mockLayer = ({ }, }; }, -} as unknown) as IVectorLayer; +} as unknown as IVectorLayer; const mockMbMapHandlers: { [key: string]: (event?: MapMouseEvent) => void } = {}; -const mockMBMap = ({ +const mockMBMap = { on: (eventName: string, callback: (event?: MapMouseEvent) => void) => { mockMbMapHandlers[eventName] = callback; }, @@ -66,7 +66,7 @@ const mockMBMap = ({ queryRenderedFeatures: () => { return featuresAtLocation; }, -} as unknown) as MbMap; +} as unknown as MbMap; const defaultProps = { mbMap: mockMBMap, @@ -192,10 +192,10 @@ describe('TooltipControl', () => { }); describe('on click', () => { - const mockMapMouseEvent = ({ + const mockMapMouseEvent = { point: { x: 0, y: 0 }, lngLat: { lng: 0, lat: 0 }, - } as unknown) as MapMouseEvent; + } as unknown as MapMouseEvent; const openOnClickTooltipStub = sinon.stub(); const closeOnClickTooltipStub = sinon.stub(); @@ -236,7 +236,7 @@ describe('TooltipControl', () => { }); test('should set tooltip state when there are features at clicked location and remove duplicate features', () => { - const feature = ({ + const feature = { geometry: { type: 'Point', coordinates: [100, 30], @@ -247,7 +247,7 @@ describe('TooltipControl', () => { properties: { __kbn__feature_id__: 1, }, - } as unknown) as MapboxGeoJSONFeature; + } as unknown as MapboxGeoJSONFeature; featuresAtLocation = [feature, feature]; mount( Promise) | null; closeOnClickTooltip: (tooltipId: string) => void; diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_popover.test.tsx b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_popover.test.tsx index 002ec09e68c2f..cb96f364925b4 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_popover.test.tsx +++ b/x-pack/plugins/maps/public/connected_components/mb_map/tooltip_control/tooltip_popover.test.tsx @@ -30,7 +30,7 @@ const mockMbMapBounds = { const layerId = 'tfi3f'; const mockMbMapHandlers: { [key: string]: () => void } = {}; -const mockMBMap = ({ +const mockMBMap = { project: (lonLatArray: [number, number]) => { const lonDistanceFromCenter = Math.abs(lonLatArray[0] - mapCenter[0]); const latDistanceFromCenter = Math.abs(lonLatArray[1] - mapCenter[1]); @@ -61,7 +61,7 @@ const mockMBMap = ({ }, }; }, -} as unknown) as MbMap; +} as unknown as MbMap; const defaultProps = { mbMap: mockMBMap, diff --git a/x-pack/plugins/maps/public/connected_components/right_side_controls/attribution_control/attribution_control.test.tsx b/x-pack/plugins/maps/public/connected_components/right_side_controls/attribution_control/attribution_control.test.tsx index 8835cdfbaf649..630e06f014bc6 100644 --- a/x-pack/plugins/maps/public/connected_components/right_side_controls/attribution_control/attribution_control.test.tsx +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/attribution_control/attribution_control.test.tsx @@ -13,16 +13,16 @@ import { AttributionControl } from './attribution_control'; describe('AttributionControl', () => { test('is rendered', async () => { - const mockLayer1 = ({ + const mockLayer1 = { getAttributions: async () => { return [{ url: '', label: 'attribution with no link' }]; }, - } as unknown) as ILayer; - const mockLayer2 = ({ + } as unknown as ILayer; + const mockLayer2 = { getAttributions: async () => { return [{ url: 'https://coolmaps.com', label: 'attribution with link' }]; }, - } as unknown) as ILayer; + } as unknown as ILayer; const component = shallow( ); diff --git a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_control.test.tsx b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_control.test.tsx index cde42f42362e0..0526eddc6521d 100644 --- a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_control.test.tsx +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_control.test.tsx @@ -55,7 +55,7 @@ describe('LayerControl', () => { describe('spinner icon', () => { const isLayerLoading = true; let isVisible = true; - const mockLayerThatIsLoading = ({ + const mockLayerThatIsLoading = { hasErrors: () => { return false; }, @@ -65,7 +65,7 @@ describe('LayerControl', () => { isVisible: () => { return isVisible; }, - } as unknown) as ILayer; + } as unknown as ILayer; test('Should render expand button with loading icon when layer is loading', () => { const component = shallow( { }); test('Should render expand button with error icon when layer has error', () => { - const mockLayerThatHasError = ({ + const mockLayerThatHasError = { hasErrors: () => { return true; }, isLayerLoading: () => { return false; }, - } as unknown) as ILayer; + } as unknown as ILayer; const component = shallow( { return '1'; }, supportsFitToBounds: () => { return true; }, - } as unknown) as ILayer, - ({ + } as unknown as ILayer, + { getId: () => { return '2'; }, supportsFitToBounds: () => { return false; }, - } as unknown) as ILayer, + } as unknown as ILayer, ]; const defaultProps = { diff --git a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/__snapshots__/toc_entry.test.tsx.snap b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/__snapshots__/toc_entry.test.tsx.snap index 42618d099ffcf..5beaf12029d2f 100644 --- a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/__snapshots__/toc_entry.test.tsx.snap +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/__snapshots__/toc_entry.test.tsx.snap @@ -24,10 +24,11 @@ exports[`TOCEntry is rendered 1`] = ` "isVisible": [Function], "renderLegendDetails": [Function], "showAtZoomLevel": [Function], + "supportsFitToBounds": [Function], } } openLayerSettings={[Function]} - supportsFitToBounds={false} + supportsFitToBounds={true} />
+
+
+
+
+ { return LAYER_ID; }, @@ -46,7 +46,10 @@ const mockLayer = ({ hasLegendDetails: () => { return true; }, -} as unknown) as ILayer; + supportsFitToBounds: () => { + return true; + }, +} as unknown as ILayer; const defaultProps = { layer: mockLayer, @@ -113,7 +116,7 @@ describe('TOCEntry', () => { }); test('Should shade background when not selected layer', async () => { - const differentLayer = (Object.create(mockLayer) as unknown) as ILayer; + const differentLayer = Object.create(mockLayer) as unknown as ILayer; differentLayer.getId = () => { return 'foobar'; }; diff --git a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx index 33b95f4ca5816..51acab6453921 100644 --- a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx @@ -20,7 +20,6 @@ import { import { ESSearchSource } from '../../../../../../classes/sources/es_search_source'; import { VectorLayer } from '../../../../../../classes/layers/vector_layer'; import { SCALING_TYPES, VECTOR_SHAPE_TYPE } from '../../../../../../../common/constants'; -import { ESSearchSourceSyncMeta } from '../../../../../../../common/descriptor_types'; export interface Props { cloneLayer: (layerId: string) => void; @@ -85,15 +84,13 @@ export class TOCEntryActionsPopover extends Component { async _getIsFeatureEditingEnabled(): Promise { const vectorLayer = this.props.layer as VectorLayer; - const layerSource = await this.props.layer.getSource(); + const layerSource = this.props.layer.getSource(); if (!(layerSource instanceof ESSearchSource)) { return false; } - const isClustered = - (layerSource?.getSyncMeta() as ESSearchSourceSyncMeta)?.scalingType === - SCALING_TYPES.CLUSTERS; + if ( - isClustered || + (layerSource as ESSearchSource).getSyncMeta().scalingType === SCALING_TYPES.CLUSTERS || (await vectorLayer.isFilteredByGlobalTime()) || vectorLayer.isPreviewLayer() || !vectorLayer.isVisible() || @@ -201,7 +198,9 @@ export class TOCEntryActionsPopover extends Component { disabled: !this.state.isFeatureEditingEnabled || this.props.editModeActiveForLayer, onClick: async () => { this._closePopover(); - const supportedShapeTypes = await (this.props.layer.getSource() as ESSearchSource).getSupportedShapeTypes(); + const supportedShapeTypes = await ( + this.props.layer.getSource() as ESSearchSource + ).getSupportedShapeTypes(); const supportsShapes = supportedShapeTypes.includes(VECTOR_SHAPE_TYPE.POLYGON) && supportedShapeTypes.includes(VECTOR_SHAPE_TYPE.LINE); diff --git a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_button/toc_entry_button.tsx b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_button/toc_entry_button.tsx index eabb2b782272b..eb703eadeecfb 100644 --- a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_button/toc_entry_button.tsx +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_button/toc_entry_button.tsx @@ -117,7 +117,7 @@ export class TOCEntryButton extends Component { footnotes.push({ icon: , message: i18n.translate('xpack.maps.layer.isUsingSearchMsg', { - defaultMessage: 'Results narrowed by query and filters', + defaultMessage: 'Results narrowed by global search', }), }); } @@ -125,7 +125,7 @@ export class TOCEntryButton extends Component { footnotes.push({ icon: , message: i18n.translate('xpack.maps.layer.isUsingTimeFilter', { - defaultMessage: 'Results narrowed by time filter', + defaultMessage: 'Results narrowed by global time', }), }); } diff --git a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx index 3255fb6b5e8ee..c15138f6c5b15 100644 --- a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx +++ b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx @@ -94,7 +94,8 @@ function getIsRestore(searchSessionId?: string) { export class MapEmbeddable extends Embeddable - implements ReferenceOrValueEmbeddable { + implements ReferenceOrValueEmbeddable +{ type = MAP_SAVED_OBJECT_TYPE; deferEmbeddableLoad = true; diff --git a/x-pack/plugins/maps/public/embeddable/types.ts b/x-pack/plugins/maps/public/embeddable/types.ts index 090a6c8d02043..7214aa78e04d3 100644 --- a/x-pack/plugins/maps/public/embeddable/types.ts +++ b/x-pack/plugins/maps/public/embeddable/types.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { IndexPattern } from '../../../../../src/plugins/data/common/index_patterns'; +import type { IndexPattern } from '../../../../../src/plugins/data/common'; import { Embeddable, EmbeddableInput, diff --git a/x-pack/plugins/maps/public/index.ts b/x-pack/plugins/maps/public/index.ts index 29cc20c706296..19850e004e6b8 100644 --- a/x-pack/plugins/maps/public/index.ts +++ b/x-pack/plugins/maps/public/index.ts @@ -18,11 +18,26 @@ export const plugin: PluginInitializer = ( }; export { MAP_SAVED_OBJECT_TYPE } from '../common/constants'; +export type { PreIndexedShape } from '../common/elasticsearch_util'; -export type { RenderTooltipContentParams } from './classes/tooltips/tooltip_property'; +export type { + ITooltipProperty, + RenderTooltipContentParams, +} from './classes/tooltips/tooltip_property'; -export { MapsStartApi } from './api'; +export type { MapsSetupApi, MapsStartApi } from './api'; export type { MapEmbeddable, MapEmbeddableInput, MapEmbeddableOutput } from './embeddable'; export type { EMSTermJoinConfig, SampleValuesConfig } from './ems_autosuggest'; + +export type { IVectorSource, GeoJsonWithMeta } from './classes/sources/vector_source/vector_source'; +export type { ImmutableSourceProperty, SourceEditorArgs } from './classes/sources/source'; +export type { Attribution } from '../common/descriptor_types'; +export type { + BoundsRequestMeta, + SourceTooltipConfig, +} from './classes/sources/vector_source/vector_source'; +export type { IField } from './classes/fields/field'; +export type { LayerWizard, RenderWizardArguments } from './classes/layers/layer_wizard_registry'; +export type { DataRequest } from './classes/util/data_request'; diff --git a/x-pack/plugins/maps/public/lazy_load_bundle/index.ts b/x-pack/plugins/maps/public/lazy_load_bundle/index.ts index 7157b145f828d..1cbf009ffd5fa 100644 --- a/x-pack/plugins/maps/public/lazy_load_bundle/index.ts +++ b/x-pack/plugins/maps/public/lazy_load_bundle/index.ts @@ -5,8 +5,7 @@ * 2.0. */ -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { IndexPatternsContract } from 'src/plugins/data/public/index_patterns'; +import { IndexPatternsContract } from 'src/plugins/data/public'; import { AppMountParameters } from 'kibana/public'; import { IContainer } from '../../../../../src/plugins/embeddable/public'; import { LayerDescriptor } from '../../common/descriptor_types'; diff --git a/x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_editor.tsx b/x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_editor.tsx index 8830c557f7b4a..f8f8e0ce59a4f 100644 --- a/x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_editor.tsx +++ b/x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_editor.tsx @@ -23,7 +23,7 @@ export function RegionMapEditor(props: VisEditorOptionsProps) { const query = getData().query; locator.navigate({ - ...extractLayerDescriptorParams((props.vis as unknown) as Vis), + ...extractLayerDescriptorParams(props.vis as unknown as Vis), filters: query.filterManager.getFilters(), query: query.queryString.getQuery(), timeRange: query.timefilter.timefilter.getTime(), diff --git a/x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_editor.tsx b/x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_editor.tsx index b177b34a537f9..60efb807795af 100644 --- a/x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_editor.tsx +++ b/x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_editor.tsx @@ -23,7 +23,7 @@ export function TileMapEditor(props: VisEditorOptionsProps) { const query = getData().query; locator.navigate({ - ...extractLayerDescriptorParams((props.vis as unknown) as Vis), + ...extractLayerDescriptorParams(props.vis as unknown as Vis), filters: query.filterManager.getFilters(), query: query.queryString.getQuery(), timeRange: query.timefilter.timefilter.getTime(), diff --git a/x-pack/plugins/maps/public/locators.test.ts b/x-pack/plugins/maps/public/locators.test.ts index 838d272120324..bfcd34c4ae710 100644 --- a/x-pack/plugins/maps/public/locators.test.ts +++ b/x-pack/plugins/maps/public/locators.test.ts @@ -65,7 +65,7 @@ describe('visualize url generator', () => { }, ]; const location = await locator.getLocation({ - initialLayers: (initialLayers as unknown) as LayerDescriptor[] & SerializableRecord, + initialLayers: initialLayers as unknown as LayerDescriptor[] & SerializableRecord, }); expect(location).toMatchObject({ diff --git a/x-pack/plugins/maps/public/locators.ts b/x-pack/plugins/maps/public/locators.ts index 9689be8c133d4..a447f8eb94ec2 100644 --- a/x-pack/plugins/maps/public/locators.ts +++ b/x-pack/plugins/maps/public/locators.ts @@ -99,11 +99,13 @@ export class MapsAppLocatorDefinition implements LocatorDefinition string; - }).encode_array(initialLayers); + const risonEncodedInitialLayers = ( + rison as unknown as { + encode_array: ( + initialLayers: (LayerDescriptor[] & SerializableRecord) | undefined + ) => string; + } + ).encode_array(initialLayers); path = `${path}&${INITIAL_LAYERS_KEY}=${encodeURIComponent(risonEncodedInitialLayers)}`; } @@ -138,7 +140,8 @@ export interface MapsAppTileMapLocatorDependencies { } export class MapsAppTileMapLocatorDefinition - implements LocatorDefinition { + implements LocatorDefinition +{ public readonly id = MAPS_APP_TILE_MAP_LOCATOR; constructor(protected readonly deps: MapsAppTileMapLocatorDependencies) {} @@ -158,7 +161,7 @@ export class MapsAppTileMapLocatorDefinition hash = true, } = params; const mapModules = await lazyLoadMapModules(); - const initialLayers = ([] as unknown) as LayerDescriptor[] & SerializableRecord; + const initialLayers = [] as unknown as LayerDescriptor[] & SerializableRecord; const tileMapLayerDescriptor = mapModules.createTileMapLayerDescriptor({ label, mapType, @@ -209,7 +212,8 @@ export interface MapsAppRegionMapLocatorDependencies { } export class MapsAppRegionMapLocatorDefinition - implements LocatorDefinition { + implements LocatorDefinition +{ public readonly id = MAPS_APP_REGION_MAP_LOCATOR; constructor(protected readonly deps: MapsAppRegionMapLocatorDependencies) {} @@ -232,7 +236,7 @@ export class MapsAppRegionMapLocatorDefinition hash = true, } = params; const mapModules = await lazyLoadMapModules(); - const initialLayers = ([] as unknown) as LayerDescriptor[] & SerializableRecord; + const initialLayers = [] as unknown as LayerDescriptor[] & SerializableRecord; const regionMapLayerDescriptor = mapModules.createRegionMapLayerDescriptor({ label, emsLayerId, diff --git a/x-pack/plugins/maps/public/plugin.ts b/x-pack/plugins/maps/public/plugin.ts index ae72c26ae7144..8f6e74adfc2fd 100644 --- a/x-pack/plugins/maps/public/plugin.ts +++ b/x-pack/plugins/maps/public/plugin.ts @@ -51,6 +51,7 @@ import { createLayerDescriptors, registerLayerWizard, registerSource, + MapsSetupApi, MapsStartApi, suggestEMSTermJoinConfig, } from './api'; @@ -130,14 +131,15 @@ export class MapsPlugin MapsPluginStart, MapsPluginSetupDependencies, MapsPluginStartDependencies - > { + > +{ readonly _initializerContext: PluginInitializerContext; constructor(initializerContext: PluginInitializerContext) { this._initializerContext = initializerContext; } - public setup(core: CoreSetup, plugins: MapsPluginSetupDependencies) { + public setup(core: CoreSetup, plugins: MapsPluginSetupDependencies): MapsSetupApi { registerLicensedFeatures(plugins.licensing); const config = this._initializerContext.config.get(); @@ -193,6 +195,11 @@ export class MapsPlugin plugins.expressions.registerFunction(createTileMapFn); plugins.expressions.registerRenderer(tileMapRenderer); plugins.visualizations.createBaseVisualization(tileMapVisType); + + return { + registerLayerWizard, + registerSource, + }; } public start(core: CoreStart, plugins: MapsPluginStartDependencies): MapsStartApi { @@ -210,8 +217,6 @@ export class MapsPlugin return { createLayerDescriptors, - registerLayerWizard, - registerSource, suggestEMSTermJoinConfig, }; } diff --git a/x-pack/plugins/maps/public/reducers/map/data_request_utils.test.ts b/x-pack/plugins/maps/public/reducers/map/data_request_utils.test.ts index abcdcd4ac99b3..f878b8be0d84b 100644 --- a/x-pack/plugins/maps/public/reducers/map/data_request_utils.test.ts +++ b/x-pack/plugins/maps/public/reducers/map/data_request_utils.test.ts @@ -25,69 +25,69 @@ describe('getDataRequest', () => { dataId: 'source', } as DataRequestDescriptor; test('Should return undefined if layer not found', () => { - const state = ({ + const state = { layerList: [], - } as unknown) as MapState; + } as unknown as MapState; expect(getDataRequest(state, 'layer1', 'source')).toBeUndefined(); }); test('Should return undefined if __dataRequests not provided for layer', () => { - const state = ({ + const state = { layerList: [{ id: 'layer1' }], - } as unknown) as MapState; + } as unknown as MapState; expect(getDataRequest(state, 'layer1', 'source')).toBeUndefined(); }); test('Should return undefined if no data requests matching dataRequestId', () => { - const state = ({ + const state = { layerList: [ { id: 'layer1', __dataRequests: [SOURCE_DATA_REQUEST_DESCRIPTOR], }, ], - } as unknown) as MapState; + } as unknown as MapState; expect(getDataRequest(state, 'layer1', 'join')).toBeUndefined(); }); test('Should return data request with dataRequestId match', () => { - const state = ({ + const state = { layerList: [ { id: 'layer1', __dataRequests: [SOURCE_DATA_REQUEST_DESCRIPTOR], }, ], - } as unknown) as MapState; + } as unknown as MapState; expect(getDataRequest(state, 'layer1', 'source')).toEqual(SOURCE_DATA_REQUEST_DESCRIPTOR); }); test('Should return undefined with dataRequestId match and requestToken mismatch', () => { - const state = ({ + const state = { layerList: [ { id: 'layer1', __dataRequests: [SOURCE_DATA_REQUEST_DESCRIPTOR], }, ], - } as unknown) as MapState; + } as unknown as MapState; expect(getDataRequest(state, 'layer1', 'source', Symbol('another_request'))).toBeUndefined(); }); test('Should return data request with dataRequestId match and requestToken match', () => { - const state = ({ + const state = { layerList: [ { id: 'layer1', __dataRequests: [SOURCE_DATA_REQUEST_DESCRIPTOR], }, ], - } as unknown) as MapState; + } as unknown as MapState; expect(getDataRequest(state, 'layer1', 'source', REQUEST_TOKEN)).toEqual( SOURCE_DATA_REQUEST_DESCRIPTOR @@ -102,23 +102,23 @@ describe('setDataRequest', () => { } as DataRequestDescriptor; test('Should return unmodified state if layer not found', () => { - const state = ({ + const state = { layerList: [], - } as unknown) as MapState; + } as unknown as MapState; const stateClone = _.cloneDeep(state); expect(setDataRequest(state, 'layer1', UPDATED_DATA_REQUEST_DESCRIPTOR)).toEqual(state); expect(state).toEqual(stateClone); }); test('Should add data request if data request not found', () => { - const state = ({ + const state = { layerList: [ { id: 'layer1', __dataRequests: [], }, ], - } as unknown) as MapState; + } as unknown as MapState; const stateClone = _.cloneDeep(state); expect(setDataRequest(state, 'layer1', UPDATED_DATA_REQUEST_DESCRIPTOR)).toEqual({ layerList: [ @@ -132,14 +132,14 @@ describe('setDataRequest', () => { }); test('Should update data request', () => { - const state = ({ + const state = { layerList: [ { id: 'layer1', __dataRequests: [{ dataId: 'source' }], }, ], - } as unknown) as MapState; + } as unknown as MapState; const stateClone = _.cloneDeep(state); expect(setDataRequest(state, 'layer1', UPDATED_DATA_REQUEST_DESCRIPTOR)).toEqual({ layerList: [ @@ -160,9 +160,9 @@ describe('startDataRequest', () => { } as DataRequestMeta; test('Should return unmodified state if layer not found', () => { - const state = ({ + const state = { layerList: [], - } as unknown) as MapState; + } as unknown as MapState; const stateClone = _.cloneDeep(state); expect(startDataRequest(state, 'layer1', 'source', REQUEST_TOKEN, DATA_META_AT_START)).toEqual( state @@ -171,13 +171,13 @@ describe('startDataRequest', () => { }); test('Should add data request if no data requests for dataRequestId', () => { - const state = ({ + const state = { layerList: [ { id: 'layer1', }, ], - } as unknown) as MapState; + } as unknown as MapState; const stateClone = _.cloneDeep(state); expect(startDataRequest(state, 'layer1', 'source', REQUEST_TOKEN, DATA_META_AT_START)).toEqual({ layerList: [ @@ -197,7 +197,7 @@ describe('startDataRequest', () => { }); test('Should update existing data request for onStartLoading event', () => { - const state = ({ + const state = { layerList: [ { id: 'layer1', @@ -210,7 +210,7 @@ describe('startDataRequest', () => { ], }, ], - } as unknown) as MapState; + } as unknown as MapState; const stateClone = _.cloneDeep(state); expect(startDataRequest(state, 'layer1', 'source', REQUEST_TOKEN, DATA_META_AT_START)).toEqual({ layerList: [ @@ -234,16 +234,16 @@ describe('stopDataRequest', () => { const REQUEST_TOKEN = Symbol('request'); test('Should return unmodified state if layer not found', () => { - const state = ({ + const state = { layerList: [], - } as unknown) as MapState; + } as unknown as MapState; const stateClone = _.cloneDeep(state); expect(stopDataRequest(state, 'layer1', 'source', REQUEST_TOKEN)).toEqual(state); expect(state).toEqual(stateClone); }); test('Should return unmodified state if data request not found (unmatching request token)', () => { - const state = ({ + const state = { layerList: [ { id: 'layer1', @@ -255,14 +255,14 @@ describe('stopDataRequest', () => { ], }, ], - } as unknown) as MapState; + } as unknown as MapState; const stateClone = _.cloneDeep(state); expect(stopDataRequest(state, 'layer1', 'source', REQUEST_TOKEN)).toEqual(state); expect(state).toEqual(stateClone); }); test('Should update data request with response meta and data', () => { - const state = ({ + const state = { layerList: [ { id: 'layer1', @@ -276,7 +276,7 @@ describe('stopDataRequest', () => { ], }, ], - } as unknown) as MapState; + } as unknown as MapState; const stateClone = _.cloneDeep(state); const reponseMeta = { responseProp1: 'response' } as DataRequestMeta; const data = { prop1: 'new data' }; @@ -306,30 +306,30 @@ describe('updateSourceDataRequest', () => { }; test('Should return unmodified state if layer not found', () => { - const state = ({ + const state = { layerList: [], - } as unknown) as MapState; + } as unknown as MapState; const stateClone = _.cloneDeep(state); expect(updateSourceDataRequest(state, 'layer1', NEW_DATA)).toEqual(state); expect(state).toEqual(stateClone); }); test('Should return unmodified state if source data request not found', () => { - const state = ({ + const state = { layerList: [ { id: 'layer1', __dataRequests: [], }, ], - } as unknown) as MapState; + } as unknown as MapState; const stateClone = _.cloneDeep(state); expect(updateSourceDataRequest(state, 'layer1', NEW_DATA)).toEqual(state); expect(state).toEqual(stateClone); }); test('Should update source data request', () => { - const state = ({ + const state = { layerList: [ { id: 'layer1', @@ -341,7 +341,7 @@ describe('updateSourceDataRequest', () => { ], }, ], - } as unknown) as MapState; + } as unknown as MapState; const stateClone = _.cloneDeep(state); expect(updateSourceDataRequest(state, 'layer1', NEW_DATA)).toEqual({ layerList: [ diff --git a/x-pack/plugins/maps/public/reducers/map/default_map_settings.ts b/x-pack/plugins/maps/public/reducers/map/default_map_settings.ts index c73bf46d4bc0c..ebde5481a13f5 100644 --- a/x-pack/plugins/maps/public/reducers/map/default_map_settings.ts +++ b/x-pack/plugins/maps/public/reducers/map/default_map_settings.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { euiThemeVars } from '@kbn/ui-shared-deps/theme'; +import { euiThemeVars } from '@kbn/ui-shared-deps-src/theme'; import { INITIAL_LOCATION, MAX_ZOOM, MIN_ZOOM } from '../../../common/constants'; import { MapSettings } from './types'; diff --git a/x-pack/plugins/maps/public/reducers/map/map.ts b/x-pack/plugins/maps/public/reducers/map/map.ts index 511d7ef8efb7c..c6b036032e181 100644 --- a/x-pack/plugins/maps/public/reducers/map/map.ts +++ b/x-pack/plugins/maps/public/reducers/map/map.ts @@ -219,14 +219,8 @@ export function map(state: MapState = DEFAULT_MAP_STATE, action: Record ); } diff --git a/x-pack/plugins/maps/public/selectors/map_selectors.test.ts b/x-pack/plugins/maps/public/selectors/map_selectors.test.ts index 1b95db84e4916..dc3c6dca46237 100644 --- a/x-pack/plugins/maps/public/selectors/map_selectors.test.ts +++ b/x-pack/plugins/maps/public/selectors/map_selectors.test.ts @@ -147,7 +147,7 @@ describe('areLayersLoaded', () => { isVisible?: boolean; showAtZoomLevel?: boolean; }) { - return ({ + return { hasErrors: () => { return hasErrors; }, @@ -160,12 +160,12 @@ describe('areLayersLoaded', () => { showAtZoomLevel: () => { return showAtZoomLevel; }, - } as unknown) as ILayer; + } as unknown as ILayer; } test('layers waiting for map to load should not be counted loaded', () => { const layerList: ILayer[] = []; - const waitingForMapReadyLayerList: LayerDescriptor[] = [({} as unknown) as LayerDescriptor]; + const waitingForMapReadyLayerList: LayerDescriptor[] = [{} as unknown as LayerDescriptor]; const zoom = 4; expect(areLayersLoaded.resultFunc(layerList, waitingForMapReadyLayerList, zoom)).toBe(false); }); @@ -216,14 +216,14 @@ describe('getQueryableUniqueIndexPatternIds', () => { isVisible?: boolean; indexPatterns?: string[]; }) { - return ({ + return { isVisible: () => { return isVisible; }, getQueryableIndexPatternIds: () => { return indexPatterns; }, - } as unknown) as ILayer; + } as unknown as ILayer; } function createWaitLayerDescriptorMock({ @@ -255,7 +255,8 @@ describe('getQueryableUniqueIndexPatternIds', () => { createLayerMock({ indexPatterns: ['foobar'], isVisible: false }), createLayerMock({ indexPatterns: ['bar'] }), ]; - const waitingForMapReadyLayerList: VectorLayerDescriptor[] = ([] as unknown) as VectorLayerDescriptor[]; + const waitingForMapReadyLayerList: VectorLayerDescriptor[] = + [] as unknown as VectorLayerDescriptor[]; expect( getQueryableUniqueIndexPatternIds.resultFunc(layerList, waitingForMapReadyLayerList) ).toEqual(['foo', 'bar']); @@ -269,12 +270,12 @@ describe('getQueryableUniqueIndexPatternIds', () => { createLayerMock({ indexPatterns: ['foobar'], isVisible: false }), createLayerMock({ indexPatterns: ['bar'] }), ]; - const waitingForMapReadyLayerList: VectorLayerDescriptor[] = ([ + const waitingForMapReadyLayerList: VectorLayerDescriptor[] = [ createWaitLayerDescriptorMock({ indexPatternId: 'foo' }), createWaitLayerDescriptorMock({ indexPatternId: 'barfoo', visible: false }), createWaitLayerDescriptorMock({ indexPatternId: 'fbr' }), createWaitLayerDescriptorMock({ indexPatternId: 'foo' }), - ] as unknown) as VectorLayerDescriptor[]; + ] as unknown as VectorLayerDescriptor[]; expect( getQueryableUniqueIndexPatternIds.resultFunc(layerList, waitingForMapReadyLayerList) ).toEqual(['foo', 'fbr']); diff --git a/x-pack/plugins/maps/public/trigger_actions/visualize_geo_field_action.ts b/x-pack/plugins/maps/public/trigger_actions/visualize_geo_field_action.ts index 184b5853e8fe2..1cbb3e92134b8 100644 --- a/x-pack/plugins/maps/public/trigger_actions/visualize_geo_field_action.ts +++ b/x-pack/plugins/maps/public/trigger_actions/visualize_geo_field_action.ts @@ -78,7 +78,7 @@ const getMapsLink = async (context: VisualizeFieldContext) => { const location = await locator.getLocation({ filters: getData().query.filterManager.getFilters(), query: getData().query.queryString.getQuery(), - initialLayers: (initialLayers as unknown) as LayerDescriptor[] & SerializableRecord, + initialLayers: initialLayers as unknown as LayerDescriptor[] & SerializableRecord, timeRange: getData().query.timefilter.timefilter.getTime(), }); diff --git a/x-pack/plugins/maps/server/data_indexing/indexing_routes.ts b/x-pack/plugins/maps/server/data_indexing/indexing_routes.ts index baba176286ee2..ab0e248521837 100644 --- a/x-pack/plugins/maps/server/data_indexing/indexing_routes.ts +++ b/x-pack/plugins/maps/server/data_indexing/indexing_routes.ts @@ -192,11 +192,10 @@ export function initIndexingRoutes({ async (context, request, response) => { const { index } = request.query; try { - const { - body: mappingsResp, - } = await context.core.elasticsearch.client.asCurrentUser.indices.getMapping({ - index: request.query.index, - }); + const { body: mappingsResp } = + await context.core.elasticsearch.client.asCurrentUser.indices.getMapping({ + index: request.query.index, + }); const isDrawingIndex = mappingsResp[index].mappings?._meta?.created_by === MAPS_NEW_VECTOR_LAYER_META_CREATED_BY; return response.ok({ diff --git a/x-pack/plugins/maps/server/index.ts b/x-pack/plugins/maps/server/index.ts index b57f9ec9c29b1..603273efe0d95 100644 --- a/x-pack/plugins/maps/server/index.ts +++ b/x-pack/plugins/maps/server/index.ts @@ -22,7 +22,8 @@ export const config: PluginConfigDescriptor = { preserveDrawingBuffer: true, }, schema: configSchema, - deprecations: () => [ + deprecations: ({ deprecate }) => [ + deprecate('enabled', '8.0.0'), ( completeConfig: Record, rootPath: string, diff --git a/x-pack/plugins/maps/server/kibana_server_services.ts b/x-pack/plugins/maps/server/kibana_server_services.ts index 6b59b460ad2c9..e3c612f415c4d 100644 --- a/x-pack/plugins/maps/server/kibana_server_services.ts +++ b/x-pack/plugins/maps/server/kibana_server_services.ts @@ -7,9 +7,10 @@ import { ElasticsearchClient, ISavedObjectsRepository } from 'kibana/server'; import { SavedObjectsClient } from '../../../../src/core/server'; -import { IndexPatternsCommonService } from '../../../../src/plugins/data/server'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { IndexPatternsServiceStart } from '../../../../src/plugins/data/server/index_patterns'; +import { + IndexPatternsCommonService, + IndexPatternsServiceStart, +} from '../../../../src/plugins/data/server'; let internalRepository: ISavedObjectsRepository; export const setInternalRepository = ( diff --git a/x-pack/plugins/maps/server/maps_telemetry/util.ts b/x-pack/plugins/maps/server/maps_telemetry/util.ts index ff9339fca76cb..27190c9b82142 100644 --- a/x-pack/plugins/maps/server/maps_telemetry/util.ts +++ b/x-pack/plugins/maps/server/maps_telemetry/util.ts @@ -10,6 +10,7 @@ import { ESGeoGridSourceDescriptor, ESSearchSourceDescriptor, LayerDescriptor, + VectorLayerDescriptor, } from '../../common/descriptor_types'; import { GRID_RESOLUTION, @@ -265,8 +266,7 @@ export function getTermJoinsPerCluster( ): TELEMETRY_TERM_JOIN_COUNTS_PER_CLUSTER { return getCountsByCluster(layerLists, (layerDescriptor: LayerDescriptor) => { return layerDescriptor.type === LAYER_TYPE.VECTOR && - layerDescriptor.joins && - layerDescriptor.joins.length + (layerDescriptor as VectorLayerDescriptor)?.joins?.length ? TELEMETRY_TERM_JOIN : null; }); diff --git a/x-pack/plugins/maps/server/mvt/get_tile.ts b/x-pack/plugins/maps/server/mvt/get_tile.ts index 41a2c550198f0..11e1af5a7d368 100644 --- a/x-pack/plugins/maps/server/mvt/get_tile.ts +++ b/x-pack/plugins/maps/server/mvt/get_tile.ts @@ -247,7 +247,7 @@ export async function getTile({ if ( isTotalHitsGreaterThan( - (countResponse.rawResponse.hits.total as unknown) as TotalHits, + countResponse.rawResponse.hits.total as unknown as TotalHits, requestBody.size ) ) { diff --git a/x-pack/plugins/metrics_entities/server/index.ts b/x-pack/plugins/metrics_entities/server/index.ts index b4d35eb90f486..c8f9d81347bdb 100644 --- a/x-pack/plugins/metrics_entities/server/index.ts +++ b/x-pack/plugins/metrics_entities/server/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { PluginInitializerContext } from '../../../../src/core/server'; +import { PluginConfigDescriptor, PluginInitializerContext } from '../../../../src/core/server'; import { ConfigSchema } from './config'; import { MetricsEntitiesPlugin } from './plugin'; @@ -13,7 +13,10 @@ import { MetricsEntitiesPlugin } from './plugin'; // This exports static code and TypeScript types, // as well as, Kibana Platform `plugin()` initializer. -export const config = { schema: ConfigSchema }; +export const config: PluginConfigDescriptor = { + deprecations: ({ deprecate }) => [deprecate('enabled', '8.0.0')], + schema: ConfigSchema, +}; export const plugin = (initializerContext: PluginInitializerContext): MetricsEntitiesPlugin => { return new MetricsEntitiesPlugin(initializerContext); }; diff --git a/x-pack/plugins/metrics_entities/server/plugin.ts b/x-pack/plugins/metrics_entities/server/plugin.ts index 73d4ffc6367fe..c2eb16a1f616e 100644 --- a/x-pack/plugins/metrics_entities/server/plugin.ts +++ b/x-pack/plugins/metrics_entities/server/plugin.ts @@ -25,7 +25,8 @@ import { MetricsEntitiesClient } from './services/metrics_entities_client'; import { deleteTransforms } from './routes/delete_transforms'; export class MetricsEntitiesPlugin - implements Plugin { + implements Plugin +{ private readonly logger: Logger; private kibanaVersion: string; diff --git a/x-pack/plugins/metrics_entities/server/routes/delete_transforms.ts b/x-pack/plugins/metrics_entities/server/routes/delete_transforms.ts index f5236e462dd81..531e90963add4 100644 --- a/x-pack/plugins/metrics_entities/server/routes/delete_transforms.ts +++ b/x-pack/plugins/metrics_entities/server/routes/delete_transforms.ts @@ -34,7 +34,11 @@ export const deleteTransforms = (router: IRouter): void => { // TODO: Validate for runtime that the module exists or not and throw before pushing the module name lower // TODO: Change modules to be part of the body and become an array of values // TODO: Wrap this in a try catch block and report errors - const { modules, prefix = '', suffix = '' } = request.body as { + const { + modules, + prefix = '', + suffix = '', + } = request.body as { modules: ModuleNames[]; prefix: string; suffix: string; diff --git a/x-pack/plugins/ml/common/constants/messages.test.ts b/x-pack/plugins/ml/common/constants/messages.test.ts index c46eba458d1d2..afebdf9f14cbe 100644 --- a/x-pack/plugins/ml/common/constants/messages.test.ts +++ b/x-pack/plugins/ml/common/constants/messages.test.ts @@ -24,18 +24,15 @@ describe('Constants: Messages parseMessages()', () => { heading: 'Job ID format is valid', id: 'job_id_valid', status: 'success', - text: - 'Lowercase alphanumeric (a-z and 0-9) characters, hyphens or underscores, starts and ends with an alphanumeric character, and is no more than 64 characters long.', - url: - 'https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/ml-put-job.html#ml-put-job-path-parms', + text: 'Lowercase alphanumeric (a-z and 0-9) characters, hyphens or underscores, starts and ends with an alphanumeric character, and is no more than 64 characters long.', + url: 'https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/ml-put-job.html#ml-put-job-path-parms', }, { heading: 'Detector functions', id: 'detectors_function_not_empty', status: 'success', text: 'Presence of detector functions validated in all detectors.', - url: - 'https://www.elastic.co/guide/en/machine-learning/mocked-test-branch/ml-ad-finding-anomalies.html#ml-ad-detectors', + url: 'https://www.elastic.co/guide/en/machine-learning/mocked-test-branch/ml-ad-finding-anomalies.html#ml-ad-detectors', }, { bucketSpan: '15m', @@ -43,8 +40,7 @@ describe('Constants: Messages parseMessages()', () => { id: 'success_bucket_span', status: 'success', text: 'Format of "15m" is valid and passed validation checks.', - url: - 'https://www.elastic.co/guide/en/machine-learning/mocked-test-branch/ml-ad-finding-anomalies.html#ml-ad-bucket-span', + url: 'https://www.elastic.co/guide/en/machine-learning/mocked-test-branch/ml-ad-finding-anomalies.html#ml-ad-bucket-span', }, { heading: 'Time range', @@ -57,8 +53,7 @@ describe('Constants: Messages parseMessages()', () => { id: 'success_mml', status: 'success', text: 'Valid and within the estimated model memory limit.', - url: - 'https://www.elastic.co/guide/en/machine-learning/mocked-test-branch/ml-ad-finding-anomalies.html#ml-ad-model-memory-limits', + url: 'https://www.elastic.co/guide/en/machine-learning/mocked-test-branch/ml-ad-finding-anomalies.html#ml-ad-model-memory-limits', }, ]); }); @@ -68,18 +63,15 @@ describe('Constants: Messages parseMessages()', () => { { id: 'job_id_invalid', status: 'error', - text: - 'Job ID is invalid. It can contain lowercase alphanumeric (a-z and 0-9) characters, hyphens or underscores and must start and end with an alphanumeric character.', - url: - 'https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/ml-put-job.html#ml-put-job-path-parms', + text: 'Job ID is invalid. It can contain lowercase alphanumeric (a-z and 0-9) characters, hyphens or underscores and must start and end with an alphanumeric character.', + url: 'https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/ml-put-job.html#ml-put-job-path-parms', }, { heading: 'Detector functions', id: 'detectors_function_not_empty', status: 'success', text: 'Presence of detector functions validated in all detectors.', - url: - 'https://www.elastic.co/guide/en/machine-learning/mocked-test-branch/ml-ad-finding-anomalies.html#ml-ad-detectors', + url: 'https://www.elastic.co/guide/en/machine-learning/mocked-test-branch/ml-ad-finding-anomalies.html#ml-ad-detectors', }, { bucketSpan: '15m', @@ -87,14 +79,12 @@ describe('Constants: Messages parseMessages()', () => { id: 'bucket_span_valid', status: 'success', text: 'Format of "15m" is valid.', - url: - 'https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/ml-put-job.html#put-analysisconfig', + url: 'https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/ml-put-job.html#put-analysisconfig', }, { id: 'skipped_extended_tests', status: 'warning', - text: - 'Skipped additional checks because the basic requirements of the job configuration were not met.', + text: 'Skipped additional checks because the basic requirements of the job configuration were not met.', }, ]); }); @@ -105,42 +95,34 @@ describe('Constants: Messages parseMessages()', () => { heading: 'Job ID format is valid', id: 'job_id_valid', status: 'success', - text: - 'Lowercase alphanumeric (a-z and 0-9) characters, hyphens or underscores, starts and ends with an alphanumeric character, and is no more than 64 characters long.', - url: - 'https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/ml-put-job.html#ml-put-job-path-parms', + text: 'Lowercase alphanumeric (a-z and 0-9) characters, hyphens or underscores, starts and ends with an alphanumeric character, and is no more than 64 characters long.', + url: 'https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/ml-put-job.html#ml-put-job-path-parms', }, { heading: 'Detector functions', id: 'detectors_function_not_empty', status: 'success', text: 'Presence of detector functions validated in all detectors.', - url: - 'https://www.elastic.co/guide/en/machine-learning/mocked-test-branch/ml-ad-finding-anomalies.html#ml-ad-detectors', + url: 'https://www.elastic.co/guide/en/machine-learning/mocked-test-branch/ml-ad-finding-anomalies.html#ml-ad-detectors', }, { id: 'cardinality_model_plot_high', status: 'warning', - text: - 'The estimated cardinality of undefined of fields relevant to creating model plots might result in resource intensive jobs.', + text: 'The estimated cardinality of undefined of fields relevant to creating model plots might result in resource intensive jobs.', }, { fieldName: 'order_id', id: 'cardinality_partition_field', status: 'warning', - text: - 'Cardinality of partition_field "order_id" is above 1000 and might result in high memory usage.', - url: - 'https://www.elastic.co/guide/en/machine-learning/mocked-test-branch/ml-ad-finding-anomalies.html#ml-ad-cardinality', + text: 'Cardinality of partition_field "order_id" is above 1000 and might result in high memory usage.', + url: 'https://www.elastic.co/guide/en/machine-learning/mocked-test-branch/ml-ad-finding-anomalies.html#ml-ad-cardinality', }, { heading: 'Bucket span', id: 'bucket_span_high', status: 'info', - text: - 'Bucket span is 1 day or more. Be aware that days are considered as UTC days, not local days.', - url: - 'https://www.elastic.co/guide/en/machine-learning/mocked-test-branch/ml-ad-finding-anomalies.html#ml-ad-bucket-span', + text: 'Bucket span is 1 day or more. Be aware that days are considered as UTC days, not local days.', + url: 'https://www.elastic.co/guide/en/machine-learning/mocked-test-branch/ml-ad-finding-anomalies.html#ml-ad-bucket-span', }, { bucketSpanCompareFactor: 25, @@ -148,36 +130,30 @@ describe('Constants: Messages parseMessages()', () => { id: 'time_range_short', minTimeSpanReadable: '2 hours', status: 'warning', - text: - 'The selected or available time range might be too short. The recommended minimum time range should be at least 2 hours and 25 times the bucket span.', + text: 'The selected or available time range might be too short. The recommended minimum time range should be at least 2 hours and 25 times the bucket span.', }, { id: 'success_influencers', status: 'success', text: 'Influencer configuration passed the validation checks.', - url: - 'https://www.elastic.co/guide/en/machine-learning/mocked-test-branch/ml-ad-finding-anomalies.html#ml-ad-influencers', + url: 'https://www.elastic.co/guide/en/machine-learning/mocked-test-branch/ml-ad-finding-anomalies.html#ml-ad-influencers', }, { id: 'half_estimated_mml_greater_than_mml', mml: '1MB', status: 'warning', - text: - 'The specified model memory limit is less than half of the estimated model memory limit and will likely hit the hard limit.', - url: - 'https://www.elastic.co/guide/en/machine-learning/mocked-test-branch/ml-ad-finding-anomalies.html#ml-ad-model-memory-limits', + text: 'The specified model memory limit is less than half of the estimated model memory limit and will likely hit the hard limit.', + url: 'https://www.elastic.co/guide/en/machine-learning/mocked-test-branch/ml-ad-finding-anomalies.html#ml-ad-model-memory-limits', }, { id: 'missing_summary_count_field_name', status: 'error', - text: - 'A job configured with a datafeed with aggregations must set summary_count_field_name; use doc_count or suitable alternative.', + text: 'A job configured with a datafeed with aggregations must set summary_count_field_name; use doc_count or suitable alternative.', }, { id: 'datafeed_preview_failed', status: 'error', - text: - 'The datafeed preview failed. This may be due to an error in the job or datafeed configurations.', + text: 'The datafeed preview failed. This may be due to an error in the job or datafeed configurations.', }, ]); }); diff --git a/x-pack/plugins/ml/common/types/data_frame_analytics.ts b/x-pack/plugins/ml/common/types/data_frame_analytics.ts index 0a43b15f412f9..1d2a75069dfe6 100644 --- a/x-pack/plugins/ml/common/types/data_frame_analytics.ts +++ b/x-pack/plugins/ml/common/types/data_frame_analytics.ts @@ -91,9 +91,11 @@ export interface DataFrameAnalyticsConfig { allow_lazy_start?: boolean; } -export type DataFrameAnalysisConfigType = typeof ANALYSIS_CONFIG_TYPE[keyof typeof ANALYSIS_CONFIG_TYPE]; +export type DataFrameAnalysisConfigType = + typeof ANALYSIS_CONFIG_TYPE[keyof typeof ANALYSIS_CONFIG_TYPE]; -export type DataFrameTaskStateType = typeof DATA_FRAME_TASK_STATE[keyof typeof DATA_FRAME_TASK_STATE]; +export type DataFrameTaskStateType = + typeof DATA_FRAME_TASK_STATE[keyof typeof DATA_FRAME_TASK_STATE]; interface ProgressSection { phase: string; diff --git a/x-pack/plugins/ml/common/types/locator.ts b/x-pack/plugins/ml/common/types/locator.ts index bfb953777d857..6c1ec2972854e 100644 --- a/x-pack/plugins/ml/common/types/locator.ts +++ b/x-pack/plugins/ml/common/types/locator.ts @@ -237,8 +237,7 @@ export type ExpandablePanels = export type ExplorationPageUrlState = { queryText: string; queryLanguage: SearchQueryLanguage; -} & Pick & - { [key in ExpandablePanels]: boolean }; +} & Pick & { [key in ExpandablePanels]: boolean }; /** * Union type of ML URL state based on page diff --git a/x-pack/plugins/ml/common/util/anomaly_utils.ts b/x-pack/plugins/ml/common/util/anomaly_utils.ts index d8da7b8252771..da4551b638d07 100644 --- a/x-pack/plugins/ml/common/util/anomaly_utils.ts +++ b/x-pack/plugins/ml/common/util/anomaly_utils.ts @@ -32,7 +32,8 @@ export const ENTITY_FIELD_OPERATIONS = { REMOVE: '-', } as const; -export type EntityFieldOperation = typeof ENTITY_FIELD_OPERATIONS[keyof typeof ENTITY_FIELD_OPERATIONS]; +export type EntityFieldOperation = + typeof ENTITY_FIELD_OPERATIONS[keyof typeof ENTITY_FIELD_OPERATIONS]; export interface EntityField { fieldName: string; diff --git a/x-pack/plugins/ml/common/util/job_utils.test.ts b/x-pack/plugins/ml/common/util/job_utils.test.ts index 4f5877703b8e3..5c651fb10f2f1 100644 --- a/x-pack/plugins/ml/common/util/job_utils.test.ts +++ b/x-pack/plugins/ml/common/util/job_utils.test.ts @@ -55,7 +55,7 @@ describe('ML - job utils', () => { describe('isTimeSeriesViewJob', () => { test('returns true when job has a single detector with a metric function', () => { - const job = ({ + const job = { analysis_config: { detectors: [ { @@ -65,13 +65,13 @@ describe('ML - job utils', () => { }, ], }, - } as unknown) as CombinedJob; + } as unknown as CombinedJob; expect(isTimeSeriesViewJob(job)).toBe(true); }); test('returns true when job has at least one detector with a metric function', () => { - const job = ({ + const job = { analysis_config: { detectors: [ { @@ -87,13 +87,13 @@ describe('ML - job utils', () => { }, ], }, - } as unknown) as CombinedJob; + } as unknown as CombinedJob; expect(isTimeSeriesViewJob(job)).toBe(true); }); test('returns false when job does not have at least one detector with a metric function', () => { - const job = ({ + const job = { analysis_config: { detectors: [ { @@ -109,13 +109,13 @@ describe('ML - job utils', () => { }, ], }, - } as unknown) as CombinedJob; + } as unknown as CombinedJob; expect(isTimeSeriesViewJob(job)).toBe(false); }); test('returns false when job has a single count by category detector', () => { - const job = ({ + const job = { analysis_config: { detectors: [ { @@ -125,14 +125,14 @@ describe('ML - job utils', () => { }, ], }, - } as unknown) as CombinedJob; + } as unknown as CombinedJob; expect(isTimeSeriesViewJob(job)).toBe(false); }); }); describe('isTimeSeriesViewDetector', () => { - const job = ({ + const job = { analysis_config: { detectors: [ { @@ -172,7 +172,7 @@ describe('ML - job utils', () => { }, }, }, - } as unknown) as CombinedJob; + } as unknown as CombinedJob; test('returns true for a detector with a metric function', () => { expect(isTimeSeriesViewDetector(job, 0)).toBe(true); @@ -196,7 +196,7 @@ describe('ML - job utils', () => { }); describe('isSourceDataChartableForDetector', () => { - const job = ({ + const job = { analysis_config: { detectors: [ { function: 'count' }, // 0 @@ -255,7 +255,7 @@ describe('ML - job utils', () => { }, }, }, - } as unknown) as CombinedJob; + } as unknown as CombinedJob; test('returns true for expected detectors', () => { expect(isSourceDataChartableForDetector(job, 0)).toBe(true); @@ -303,13 +303,13 @@ describe('ML - job utils', () => { }); describe('isModelPlotChartableForDetector', () => { - const job1 = ({ + const job1 = { analysis_config: { detectors: [{ function: 'count' }], }, - } as unknown) as Job; + } as unknown as Job; - const job2 = ({ + const job2 = { analysis_config: { detectors: [ { function: 'count' }, @@ -323,7 +323,7 @@ describe('ML - job utils', () => { model_plot_config: { enabled: true, }, - } as unknown) as Job; + } as unknown as Job; test('returns false when model plot is not enabled', () => { expect(isModelPlotChartableForDetector(job1, 0)).toBe(false); @@ -343,7 +343,7 @@ describe('ML - job utils', () => { }); describe('getPartitioningFieldNames', () => { - const job = ({ + const job = { analysis_config: { detectors: [ { @@ -371,7 +371,7 @@ describe('ML - job utils', () => { }, ], }, - } as unknown) as CombinedJob; + } as unknown as CombinedJob; test('returns empty array for a detector with no partitioning fields', () => { const resp = getPartitioningFieldNames(job, 0); @@ -396,7 +396,7 @@ describe('ML - job utils', () => { describe('isModelPlotEnabled', () => { test('returns true for a job in which model plot has been enabled', () => { - const job = ({ + const job = { analysis_config: { detectors: [ { @@ -409,13 +409,13 @@ describe('ML - job utils', () => { model_plot_config: { enabled: true, }, - } as unknown) as Job; + } as unknown as Job; expect(isModelPlotEnabled(job, 0)).toBe(true); }); test('returns expected values for a job in which model plot has been enabled with terms', () => { - const job = ({ + const job = { analysis_config: { detectors: [ { @@ -430,7 +430,7 @@ describe('ML - job utils', () => { enabled: true, terms: 'US,AAL', }, - } as unknown) as Job; + } as unknown as Job; expect( isModelPlotEnabled(job, 0, [ @@ -454,7 +454,7 @@ describe('ML - job utils', () => { }); test('returns true for jobs in which model plot has not been enabled', () => { - const job1 = ({ + const job1 = { analysis_config: { detectors: [ { @@ -467,8 +467,8 @@ describe('ML - job utils', () => { model_plot_config: { enabled: false, }, - } as unknown) as CombinedJob; - const job2 = ({} as unknown) as CombinedJob; + } as unknown as CombinedJob; + const job2 = {} as unknown as CombinedJob; expect(isModelPlotEnabled(job1, 0)).toBe(false); expect(isModelPlotEnabled(job2, 0)).toBe(false); @@ -476,9 +476,9 @@ describe('ML - job utils', () => { }); describe('isJobVersionGte', () => { - const job = ({ + const job = { job_version: '6.1.1', - } as unknown) as CombinedJob; + } as unknown as CombinedJob; test('returns true for later job version', () => { expect(isJobVersionGte(job, '6.1.0')).toBe(true); diff --git a/x-pack/plugins/ml/common/util/job_utils.ts b/x-pack/plugins/ml/common/util/job_utils.ts index 2cb803af38579..6d069cd4383ea 100644 --- a/x-pack/plugins/ml/common/util/job_utils.ts +++ b/x-pack/plugins/ml/common/util/job_utils.ts @@ -368,7 +368,7 @@ export function mlFunctionToESAggregation( } if (functionName === ML_JOB_AGGREGATION.MIN || functionName === ML_JOB_AGGREGATION.MAX) { - return (functionName as unknown) as ES_AGGREGATION; + return functionName as unknown as ES_AGGREGATION; } if (functionName === ML_JOB_AGGREGATION.RARE) { diff --git a/x-pack/plugins/ml/public/alerting/job_selector.tsx b/x-pack/plugins/ml/public/alerting/job_selector.tsx index 0ef7bba0ddbc5..bcedc5a12a500 100644 --- a/x-pack/plugins/ml/public/alerting/job_selector.tsx +++ b/x-pack/plugins/ml/public/alerting/job_selector.tsx @@ -63,10 +63,8 @@ export const JobSelectorControl: FC = ({ const fetchOptions = useCallback(async () => { try { - const { - jobIds: jobIdOptions, - groupIds: groupIdOptions, - } = await adJobsApiService.getAllJobAndGroupIds(); + const { jobIds: jobIdOptions, groupIds: groupIdOptions } = + await adJobsApiService.getAllJobAndGroupIds(); jobIdOptions.forEach((v) => { jobIds.add(v); diff --git a/x-pack/plugins/ml/public/alerting/jobs_health_rule/anomaly_detection_jobs_health_rule_trigger.tsx b/x-pack/plugins/ml/public/alerting/jobs_health_rule/anomaly_detection_jobs_health_rule_trigger.tsx index d8643c95ce92b..71ed2032225ee 100644 --- a/x-pack/plugins/ml/public/alerting/jobs_health_rule/anomaly_detection_jobs_health_rule_trigger.tsx +++ b/x-pack/plugins/ml/public/alerting/jobs_health_rule/anomaly_detection_jobs_health_rule_trigger.tsx @@ -22,7 +22,8 @@ import { ALL_JOBS_SELECTION } from '../../../common/constants/alerts'; import { BetaBadge } from '../beta_badge'; import { isDefined } from '../../../common/types/guards'; -export type MlAnomalyAlertTriggerProps = AlertTypeParamsExpressionProps; +export type MlAnomalyAlertTriggerProps = + AlertTypeParamsExpressionProps; const AnomalyDetectionJobsHealthRuleTrigger: FC = ({ alertParams, @@ -49,11 +50,10 @@ const AnomalyDetectionJobsHealthRuleTrigger: FC = ({ ); const onAlertParamChange = useCallback( - (param: T) => ( - update: MlAnomalyDetectionJobsHealthRuleParams[T] - ) => { - setAlertParams(param, update); - }, + (param: T) => + (update: MlAnomalyDetectionJobsHealthRuleParams[T]) => { + setAlertParams(param, update); + }, [] ); diff --git a/x-pack/plugins/ml/public/alerting/jobs_health_rule/tests_selection_control.tsx b/x-pack/plugins/ml/public/alerting/jobs_health_rule/tests_selection_control.tsx index c28699d9df7eb..f53c71313ffd5 100644 --- a/x-pack/plugins/ml/public/alerting/jobs_health_rule/tests_selection_control.tsx +++ b/x-pack/plugins/ml/public/alerting/jobs_health_rule/tests_selection_control.tsx @@ -45,9 +45,9 @@ export const TestsSelectionControl: FC = React.memo( return ( <> - {(Object.entries(uiConfig) as Array< - [JobsHealthTests, typeof uiConfig[JobsHealthTests]] - >).map(([name, conf], i) => { + {( + Object.entries(uiConfig) as Array<[JobsHealthTests, typeof uiConfig[JobsHealthTests]]> + ).map(([name, conf], i) => { return ( ; +export type MlAnomalyAlertTriggerProps = + AlertTypeParamsExpressionProps; const MlAnomalyAlertTrigger: FC = ({ alertParams, @@ -54,11 +55,10 @@ const MlAnomalyAlertTrigger: FC = ({ const [jobConfigs, setJobConfigs] = useState([]); const onAlertParamChange = useCallback( - (param: T) => ( - update: MlAnomalyDetectionAlertParams[T] - ) => { - setAlertParams(param, update); - }, + (param: T) => + (update: MlAnomalyDetectionAlertParams[T]) => { + setAlertParams(param, update); + }, [] ); diff --git a/x-pack/plugins/ml/public/application/components/chart_tooltip/chart_tooltip_service.test.ts b/x-pack/plugins/ml/public/application/components/chart_tooltip/chart_tooltip_service.test.ts index 09ddaad807ded..57d5e60ebbf3a 100644 --- a/x-pack/plugins/ml/public/application/components/chart_tooltip/chart_tooltip_service.test.ts +++ b/x-pack/plugins/ml/public/application/components/chart_tooltip/chart_tooltip_service.test.ts @@ -45,7 +45,7 @@ describe('ChartTooltipService', () => { expect(spy).toHaveBeenCalledWith({ isTooltipVisible: false, - tooltipData: ([] as unknown) as TooltipData, + tooltipData: [] as unknown as TooltipData, offset: { x: 0, y: 0 }, target: null, }); diff --git a/x-pack/plugins/ml/public/application/components/chart_tooltip/chart_tooltip_service.ts b/x-pack/plugins/ml/public/application/components/chart_tooltip/chart_tooltip_service.ts index 2d4d60e0901b2..4170d27f1211c 100644 --- a/x-pack/plugins/ml/public/application/components/chart_tooltip/chart_tooltip_service.ts +++ b/x-pack/plugins/ml/public/application/components/chart_tooltip/chart_tooltip_service.ts @@ -35,7 +35,7 @@ interface TooltipOffset { export const getChartTooltipDefaultState = (): ChartTooltipState => ({ isTooltipVisible: false, - tooltipData: ([] as unknown) as TooltipData, + tooltipData: [] as unknown as TooltipData, offset: { x: 0, y: 0 }, target: null, }); diff --git a/x-pack/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.test.tsx b/x-pack/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.test.tsx index 3a8956ac024f9..f9f8d9a370bab 100644 --- a/x-pack/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.test.tsx +++ b/x-pack/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.test.tsx @@ -21,12 +21,12 @@ jest.mock('./full_time_range_selector_service', () => ({ })); describe('FullTimeRangeSelector', () => { - const indexPattern = ({ + const indexPattern = { id: '0844fc70-5ab5-11e9-935e-836737467b0f', fields: [], title: 'test-index-pattern', timeFieldName: '@timestamp', - } as unknown) as IndexPattern; + } as unknown as IndexPattern; const query: Query = { language: 'kuery', diff --git a/x-pack/plugins/ml/public/application/components/import_export_jobs/export_jobs_flyout/jobs_export_service.ts b/x-pack/plugins/ml/public/application/components/import_export_jobs/export_jobs_flyout/jobs_export_service.ts index 7251b8393b6f6..dd8298669afc5 100644 --- a/x-pack/plugins/ml/public/application/components/import_export_jobs/export_jobs_flyout/jobs_export_service.ts +++ b/x-pack/plugins/ml/public/application/components/import_export_jobs/export_jobs_flyout/jobs_export_service.ts @@ -35,9 +35,8 @@ export class JobsExportService { } public async exportDataframeAnalyticsJobs(jobIds: string[]) { - const { - data_frame_analytics: configs, - } = await this._mlApiServices.dataFrameAnalytics.getDataFrameAnalytics(jobIds.join(','), true); + const { data_frame_analytics: configs } = + await this._mlApiServices.dataFrameAnalytics.getDataFrameAnalytics(jobIds.join(','), true); this._export(configs, 'data-frame-analytics'); } diff --git a/x-pack/plugins/ml/public/application/components/import_export_jobs/import_jobs_flyout/import_jobs_flyout.tsx b/x-pack/plugins/ml/public/application/components/import_export_jobs/import_jobs_flyout/import_jobs_flyout.tsx index dfe07b1984e11..b51c8ad987a35 100644 --- a/x-pack/plugins/ml/public/application/components/import_export_jobs/import_jobs_flyout/import_jobs_flyout.tsx +++ b/x-pack/plugins/ml/public/application/components/import_export_jobs/import_jobs_flyout/import_jobs_flyout.tsx @@ -257,7 +257,7 @@ export const ImportJobsFlyout: FC = ({ isDisabled }) => { }); const errorList = errors.map(extractErrorProperties); - displayErrorToast((errorList as unknown) as ErrorType, title); + displayErrorToast(errorList as unknown as ErrorType, title); }, []); const deleteJob = useCallback( diff --git a/x-pack/plugins/ml/public/application/components/import_export_jobs/import_jobs_flyout/jobs_import_service.ts b/x-pack/plugins/ml/public/application/components/import_export_jobs/import_jobs_flyout/jobs_import_service.ts index f767ac2f65cc8..088ade0cf7158 100644 --- a/x-pack/plugins/ml/public/application/components/import_export_jobs/import_jobs_flyout/jobs_import_service.ts +++ b/x-pack/plugins/ml/public/application/components/import_export_jobs/import_jobs_flyout/jobs_import_service.ts @@ -72,9 +72,7 @@ export class JobImportService { } }); } - public async readJobConfigs( - file: File - ): Promise<{ + public async readJobConfigs(file: File): Promise<{ jobs: ImportedAdJob[] | DataFrameAnalyticsConfig[]; jobIds: string[]; jobType: JobType | null; diff --git a/x-pack/plugins/ml/public/application/components/job_selector/job_selector_flyout.tsx b/x-pack/plugins/ml/public/application/components/job_selector/job_selector_flyout.tsx index d64e85e70f2eb..66c5bf13d8f1d 100644 --- a/x-pack/plugins/ml/public/application/components/job_selector/job_selector_flyout.tsx +++ b/x-pack/plugins/ml/public/application/components/job_selector/job_selector_flyout.tsx @@ -140,9 +140,8 @@ export const JobSelectorFlyoutContent: FC = ({ if (jobs.length === 0 || !flyoutEl.current) return; // get all cols in flyout table - const tableHeaderCols: NodeListOf = flyoutEl.current.querySelectorAll( - 'table thead th' - ); + const tableHeaderCols: NodeListOf = + flyoutEl.current.querySelectorAll('table thead th'); // get the width of the last col const derivedWidth = tableHeaderCols[tableHeaderCols.length - 1].offsetWidth - 16; const normalizedJobs = normalizeTimes(jobs, dateFormatTz, derivedWidth); diff --git a/x-pack/plugins/ml/public/application/components/validate_job/validate_job_view.test.js b/x-pack/plugins/ml/public/application/components/validate_job/validate_job_view.test.js index b82d69668745a..8ec83d8679e87 100644 --- a/x-pack/plugins/ml/public/application/components/validate_job/validate_job_view.test.js +++ b/x-pack/plugins/ml/public/application/components/validate_job/validate_job_view.test.js @@ -41,7 +41,7 @@ function prepareTest(messages) { }; const kibana = { services: { - notifications: { toasts: { addDanger: jest.fn() } }, + notifications: { toasts: { addDanger: jest.fn(), addError: jest.fn() } }, }, }; @@ -79,8 +79,7 @@ describe('ValidateJob', () => { fieldName: 'airline', id: 'over_field_low_cardinality', status: 'warning', - text: - 'Cardinality of over_field "airline" is low and therefore less suitable for population analysis.', + text: 'Cardinality of over_field "airline" is low and therefore less suitable for population analysis.', url: 'https://www.elastic.co/blog/sizing-machine-learning-with-elasticsearch', }, ], diff --git a/x-pack/plugins/ml/public/application/contexts/ml/__mocks__/index_pattern.ts b/x-pack/plugins/ml/public/application/contexts/ml/__mocks__/index_pattern.ts index f4dda614a16f7..9d53efad86d38 100644 --- a/x-pack/plugins/ml/public/application/contexts/ml/__mocks__/index_pattern.ts +++ b/x-pack/plugins/ml/public/application/contexts/ml/__mocks__/index_pattern.ts @@ -7,8 +7,8 @@ import { IndexPattern } from '../../../../../../../../src/plugins/data/public'; -export const indexPatternMock = ({ +export const indexPatternMock = { id: 'the-index-pattern-id', title: 'the-index-pattern-title', fields: [], -} as unknown) as IndexPattern; +} as unknown as IndexPattern; diff --git a/x-pack/plugins/ml/public/application/contexts/ml/__mocks__/index_patterns.ts b/x-pack/plugins/ml/public/application/contexts/ml/__mocks__/index_patterns.ts index eb4fc50d0588c..7dfbcf1675692 100644 --- a/x-pack/plugins/ml/public/application/contexts/ml/__mocks__/index_patterns.ts +++ b/x-pack/plugins/ml/public/application/contexts/ml/__mocks__/index_patterns.ts @@ -7,7 +7,7 @@ import { IndexPatternsContract } from '../../../../../../../../src/plugins/data/public'; -export const indexPatternsMock = (new (class { +export const indexPatternsMock = new (class { fieldFormats = []; config = {}; savedObjectsClient = {}; @@ -19,4 +19,4 @@ export const indexPatternsMock = (new (class { getIds = jest.fn(); getTitles = jest.fn(); make = jest.fn(); -})() as unknown) as IndexPatternsContract; +})() as unknown as IndexPatternsContract; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step_form.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step_form.tsx index 21243351dab7d..bc44ff059626c 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step_form.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/advanced_step/advanced_step_form.tsx @@ -181,9 +181,10 @@ export const AdvancedStepForm: FC = ({ const selectedNumTopClassesIsInvalid = isInvalidNumTopClasses(selectedNumTopClasses); - const mmlErrors = useMemo(() => getModelMemoryLimitErrors(modelMemoryLimitValidationResult), [ - modelMemoryLimitValidationResult, - ]); + const mmlErrors = useMemo( + () => getModelMemoryLimitErrors(modelMemoryLimitValidationResult), + [modelMemoryLimitValidationResult] + ); const isRegOrClassJob = jobType === ANALYSIS_CONFIG_TYPE.REGRESSION || jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/supported_fields_message.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/supported_fields_message.tsx index eae1d240be8af..08ed279ae777b 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/supported_fields_message.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/supported_fields_message.tsx @@ -68,10 +68,8 @@ interface Props { } export const SupportedFieldsMessage: FC = ({ jobType }) => { - const [ - sourceIndexContainsSupportedFields, - setSourceIndexContainsSupportedFields, - ] = useState(true); + const [sourceIndexContainsSupportedFields, setSourceIndexContainsSupportedFields] = + useState(true); const [sourceIndexFieldsCheckFailed, setSourceIndexFieldsCheckFailed] = useState(false); const { fields } = newJobCapsServiceAnalytics; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/runtime_mappings/runtime_mappings.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/runtime_mappings/runtime_mappings.tsx index ec85cc97ac6a6..95881fc328976 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/runtime_mappings/runtime_mappings.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/runtime_mappings/runtime_mappings.tsx @@ -75,22 +75,15 @@ interface Props { } export const RuntimeMappings: FC = ({ actions, state }) => { - const [isRuntimeMappingsEditorEnabled, setIsRuntimeMappingsEditorEnabled] = useState( - false - ); - const [ - isRuntimeMappingsEditorSwitchModalVisible, - setRuntimeMappingsEditorSwitchModalVisible, - ] = useState(false); + const [isRuntimeMappingsEditorEnabled, setIsRuntimeMappingsEditorEnabled] = + useState(false); + const [isRuntimeMappingsEditorSwitchModalVisible, setRuntimeMappingsEditorSwitchModalVisible] = + useState(false); - const [ - isRuntimeMappingsEditorApplyButtonEnabled, - setIsRuntimeMappingsEditorApplyButtonEnabled, - ] = useState(false); - const [ - advancedEditorRuntimeMappingsLastApplied, - setAdvancedEditorRuntimeMappingsLastApplied, - ] = useState(); + const [isRuntimeMappingsEditorApplyButtonEnabled, setIsRuntimeMappingsEditorApplyButtonEnabled] = + useState(false); + const [advancedEditorRuntimeMappingsLastApplied, setAdvancedEditorRuntimeMappingsLastApplied] = + useState(); const { setFormState } = actions; const { jobType, previousRuntimeMapping, runtimeMappings } = state.form; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts index b3034d910c7d1..60a5a548c8621 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts @@ -217,9 +217,10 @@ export const useIndexData = ( JSON.stringify([query, pagination, sortingColumns, combinedRuntimeMappings]), ]); - const dataLoader = useMemo(() => new DataLoader(indexPattern, toastNotifications), [ - indexPattern, - ]); + const dataLoader = useMemo( + () => new DataLoader(indexPattern, toastNotifications), + [indexPattern] + ); useEffect(() => { async function fetchColumnChartsData(fieldHistogramsQuery: Record) { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/page.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/page.tsx index 41bdc5b8ecf45..8bccbb5a90c29 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/page.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/page.tsx @@ -63,12 +63,8 @@ export const Page: FC = ({ jobId }) => { const { state } = createAnalyticsForm; const { isAdvancedEditorEnabled, disableSwitchToForm, isJobCreated } = state; const { jobType } = state.form; - const { - initiateWizard, - setJobClone, - switchToAdvancedEditor, - switchToForm, - } = createAnalyticsForm.actions; + const { initiateWizard, setJobClone, switchToAdvancedEditor, switchToForm } = + createAnalyticsForm.actions; useEffect(() => { initiateWizard(); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/column_data.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/column_data.tsx index d824a28bd9135..31b7db66f81ae 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/column_data.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/column_data.tsx @@ -35,9 +35,7 @@ export const ACTUAL_CLASS_ID = 'actual_class'; export const OTHER_CLASS_ID = 'other'; export const MAX_COLUMNS = 6; -export function getColumnData( - confusionMatrixData: ConfusionMatrix[] -): { +export function getColumnData(confusionMatrixData: ConfusionMatrix[]): { columns: ConfusionMatrixColumn[]; columnData: ConfusionMatrixColumnData[]; } { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_query_bar/exploration_query_bar.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_query_bar/exploration_query_bar.tsx index 4b772669729f6..1a5f1bad997e2 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_query_bar/exploration_query_bar.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_query_bar/exploration_query_bar.tsx @@ -13,7 +13,7 @@ import { debounce } from 'lodash'; import { fromKueryExpression, luceneStringToDsl, toElasticsearchQuery } from '@kbn/es-query'; import { estypes } from '@elastic/elasticsearch'; import { Dictionary } from '../../../../../../../common/types/common'; -import { IIndexPattern } from '../../../../../../../../../../src/plugins/data/common/index_patterns'; +import { IIndexPattern } from '../../../../../../../../../../src/plugins/data/common'; import { Query, QueryStringInput } from '../../../../../../../../../../src/plugins/data/public'; import { @@ -57,9 +57,10 @@ export const ExplorationQueryBar: FC = ({ const searchChangeHandler = (q: Query) => setSearchInput(q); - const regex = useMemo(() => new RegExp(`${filters?.columnId}\\s*:\\s*(true|false)`, 'g'), [ - filters?.columnId, - ]); + const regex = useMemo( + () => new RegExp(`${filters?.columnId}\\s*:\\s*(true|false)`, 'g'), + [filters?.columnId] + ); /** * Restoring state from the URL once on load. If a filter option is active diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/feature_importance/use_classification_path_data.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/feature_importance/use_classification_path_data.tsx index 5d61d8b3ef0c4..a956ffa934862 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/feature_importance/use_classification_path_data.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/feature_importance/use_classification_path_data.tsx @@ -295,23 +295,25 @@ export const buildClassificationDecisionPathData = ({ }): DecisionPathPlotData | undefined => { if (currentClass === undefined || !(Array.isArray(baselines) && baselines.length >= 2)) return []; - const mappedFeatureImportance: Array< - ExtendedFeatureImportance | undefined - > = featureImportance.map((feature) => { - const classFeatureImportance = Array.isArray(feature.classes) - ? feature.classes.find( - (c) => getStringBasedClassName(c.class_name) === getStringBasedClassName(currentClass) - ) - : feature; - if (classFeatureImportance && typeof classFeatureImportance[FEATURE_IMPORTANCE] === 'number') { - return { - [FEATURE_NAME]: feature[FEATURE_NAME], - [FEATURE_IMPORTANCE]: classFeatureImportance[FEATURE_IMPORTANCE], - absImportance: Math.abs(classFeatureImportance[FEATURE_IMPORTANCE] as number), - }; - } - return undefined; - }); + const mappedFeatureImportance: Array = + featureImportance.map((feature) => { + const classFeatureImportance = Array.isArray(feature.classes) + ? feature.classes.find( + (c) => getStringBasedClassName(c.class_name) === getStringBasedClassName(currentClass) + ) + : feature; + if ( + classFeatureImportance && + typeof classFeatureImportance[FEATURE_IMPORTANCE] === 'number' + ) { + return { + [FEATURE_NAME]: feature[FEATURE_NAME], + [FEATURE_IMPORTANCE]: classFeatureImportance[FEATURE_IMPORTANCE], + absImportance: Math.abs(classFeatureImportance[FEATURE_IMPORTANCE] as number), + }; + } + return undefined; + }); // get the baseline for the current class from the trained_models metadata const baselineClass = baselines.find( diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx index abd1870babfb9..816d5e1afb233 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx @@ -41,12 +41,8 @@ interface ExplorationProps { } export const OutlierExploration: FC = React.memo(({ jobId }) => { - const { - indexPattern, - indexPatternErrorMessage, - jobConfig, - needsDestIndexPattern, - } = useResultsViewConfig(jobId); + const { indexPattern, indexPatternErrorMessage, jobConfig, needsDestIndexPattern } = + useResultsViewConfig(jobId); const [pageUrlState, setPageUrlState] = useExplorationUrlState(); const [searchQuery, setSearchQuery] = useState(defaultSearchQuery); const outlierData = useOutlierData(indexPattern, jobConfig, searchQuery); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_action_name.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_action_name.tsx index 86734c87c5e16..a3f18801b88f9 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_action_name.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_action_name.tsx @@ -380,14 +380,14 @@ export function extractCloningConfig({ id, ...configToClone }: DeepReadonly): CloneDataFrameAnalyticsConfig { - return (cloneDeep({ + return cloneDeep({ ...configToClone, dest: { ...configToClone.dest, // Reset the destination index index: '', }, - }) as unknown) as CloneDataFrameAnalyticsConfig; + }) as unknown as CloneDataFrameAnalyticsConfig; } export const cloneActionNameText = i18n.translate( diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/common.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/common.ts index 30c59982c5d2b..1be9186a8468c 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/common.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/common.ts @@ -71,9 +71,11 @@ export function getDataFrameAnalyticsProgress(stats: DataFrameAnalyticsStats) { return undefined; } -export function getDataFrameAnalyticsProgressPhase( - stats: DataFrameAnalyticsStats -): { currentPhase: number; progress: number; totalPhases: number } { +export function getDataFrameAnalyticsProgressPhase(stats: DataFrameAnalyticsStats): { + currentPhase: number; + progress: number; + totalPhases: number; +} { let phase = 0; let progress = 0; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx index e054a195ac015..e302e6ae179e8 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx @@ -193,9 +193,8 @@ export const ModelsList: FC = () => { const modelIdsToFetch = models.map((model) => model.model_id); try { - const { - trained_model_stats: modelsStatsResponse, - } = await trainedModelsApiService.getTrainedModelStats(modelIdsToFetch); + const { trained_model_stats: modelsStatsResponse } = + await trainedModelsApiService.getTrainedModelStats(modelIdsToFetch); for (const { model_id: id, ...stats } of modelsStatsResponse) { const model = models.find((m) => m.model_id === id); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.test.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.test.ts index 967885a779b03..6d2aecec4ea68 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.test.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.test.ts @@ -124,7 +124,7 @@ describe('useCreateAnalyticsForm', () => { // invalid formats ("fake" TS casting to get valid TS and be able to run the tests) expect(validateAdvancedEditor(getMockState({ index: {} as SourceIndex })).isValid).toBe(false); expect( - validateAdvancedEditor(getMockState({ index: (undefined as unknown) as SourceIndex })).isValid + validateAdvancedEditor(getMockState({ index: undefined as unknown as SourceIndex })).isValid ).toBe(false); }); @@ -209,7 +209,7 @@ describe('validateMinMML', () => { }); test('should ignore empty parameters', () => { - expect(validateMinMML((undefined as unknown) as string)('')).toEqual(null); + expect(validateMinMML(undefined as unknown as string)('')).toEqual(null); }); }); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts index c02539fabfe41..9f629402b8608 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts @@ -84,9 +84,9 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => { const createAnalyticsJob = async () => { resetRequestMessages(); - const analyticsJobConfig = (isAdvancedEditorEnabled - ? jobConfig - : getJobConfigFromFormState(form)) as DataFrameAnalyticsConfig; + const analyticsJobConfig = ( + isAdvancedEditorEnabled ? jobConfig : getJobConfigFromFormState(form) + ) as DataFrameAnalyticsConfig; if (isAdvancedEditorEnabled) { destinationIndex = analyticsJobConfig.dest.index; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/job_map/use_fetch_analytics_map_data.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/job_map/use_fetch_analytics_map_data.ts index 965fd6000cf1c..6476cef3d8802 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/job_map/use_fetch_analytics_map_data.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/job_map/use_fetch_analytics_map_data.ts @@ -30,11 +30,8 @@ export const useFetchAnalyticsMapData = () => { const fetchAndSetElements = async (idToUse: string, treatAsRoot: boolean, type?: string) => { setIsLoading(true); // Pass in treatAsRoot flag - endpoint will take job or index to grab jobs created from it - const analyticsMap: AnalyticsMapReturnType = await ml.dataFrameAnalytics.getDataFrameAnalyticsMap( - idToUse, - treatAsRoot, - type - ); + const analyticsMap: AnalyticsMapReturnType = + await ml.dataFrameAnalytics.getDataFrameAnalyticsMap(idToUse, treatAsRoot, type); const { elements: nodeElements, details, error: fetchError } = analyticsMap; diff --git a/x-pack/plugins/ml/public/application/explorer/actions/load_explorer_data.ts b/x-pack/plugins/ml/public/application/explorer/actions/load_explorer_data.ts index 45afab0cce4fd..63d5855d96f41 100644 --- a/x-pack/plugins/ml/public/application/explorer/actions/load_explorer_data.ts +++ b/x-pack/plugins/ml/public/application/explorer/actions/load_explorer_data.ts @@ -56,13 +56,11 @@ const memoize = any>(func: T, context?: any) => { return memoizeOne(wrapWithLastRefreshArg(func, context) as any, memoizeIsEqual); }; -const memoizedLoadOverallAnnotations = memoize( - loadOverallAnnotations -); +const memoizedLoadOverallAnnotations = + memoize(loadOverallAnnotations); -const memoizedLoadAnnotationsTableData = memoize( - loadAnnotationsTableData -); +const memoizedLoadAnnotationsTableData = + memoize(loadAnnotationsTableData); const memoizedLoadFilteredTopInfluencers = memoize( loadFilteredTopInfluencers ); diff --git a/x-pack/plugins/ml/public/application/explorer/anomalies_map.tsx b/x-pack/plugins/ml/public/application/explorer/anomalies_map.tsx index d40f036a61089..28f346c0148c6 100644 --- a/x-pack/plugins/ml/public/application/explorer/anomalies_map.tsx +++ b/x-pack/plugins/ml/public/application/explorer/anomalies_map.tsx @@ -18,6 +18,7 @@ import { } from '@elastic/eui'; import { FIELD_ORIGIN, + LAYER_TYPE, SOURCE_TYPES, STYLE_TYPE, COLOR_MAP_TYPE, @@ -32,10 +33,8 @@ import { AnomaliesTableRecord } from '../../../common/types/anomalies'; const MAX_ENTITY_VALUES = 3; function getAnomalyRows(anomalies: AnomaliesTableRecord[], jobId: string) { - const anomalyRows: Record< - string, - { count: number; entityValue: string; max_severity: number } - > = {}; + const anomalyRows: Record = + {}; for (let i = 0; i < anomalies.length; i++) { const anomaly = anomalies[i]; const location = anomaly.entityValue; @@ -127,7 +126,7 @@ export const getChoroplethAnomaliesLayer = ( isTimeAware: true, }, visible: false, - type: 'VECTOR', + type: LAYER_TYPE.VECTOR, }; }; diff --git a/x-pack/plugins/ml/public/application/explorer/components/explorer_query_bar/explorer_query_bar.tsx b/x-pack/plugins/ml/public/application/explorer/components/explorer_query_bar/explorer_query_bar.tsx index 0756bf60500ef..6e8b5f762558f 100644 --- a/x-pack/plugins/ml/public/application/explorer/components/explorer_query_bar/explorer_query_bar.tsx +++ b/x-pack/plugins/ml/public/application/explorer/components/explorer_query_bar/explorer_query_bar.tsx @@ -10,7 +10,7 @@ import { EuiCode, EuiInputPopover } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { fromKueryExpression, luceneStringToDsl, toElasticsearchQuery } from '@kbn/es-query'; import { Query, QueryStringInput } from '../../../../../../../../src/plugins/data/public'; -import { IIndexPattern } from '../../../../../../../../src/plugins/data/common/index_patterns'; +import { IIndexPattern } from '../../../../../../../../src/plugins/data/common'; import { SEARCH_QUERY_LANGUAGE, ErrorMessage } from '../../../../../common/constants/search'; import { explorerService } from '../../explorer_dashboard_service'; import { InfluencersFilterQuery } from '../../../../../common/types/es_client'; diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.js index c714b388c826f..ddb46edc7b921 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.js @@ -79,8 +79,12 @@ function ExplorerChartContainer({ let isCancelled = false; const generateLink = async () => { if (!isCancelled && series.functionDescription !== ML_JOB_AGGREGATION.LAT_LONG) { - const singleMetricViewerLink = await getExploreSeriesLink(mlLocator, series, timefilter); - setExplorerSeriesLink(singleMetricViewerLink); + try { + const singleMetricViewerLink = await getExploreSeriesLink(mlLocator, series, timefilter); + setExplorerSeriesLink(singleMetricViewerLink); + } catch (error) { + setExplorerSeriesLink(''); + } } }; generateLink(); diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_dashboard_service.ts b/x-pack/plugins/ml/public/application/explorer/explorer_dashboard_service.ts index 1d4a277af0131..deb1beed2d146 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_dashboard_service.ts +++ b/x-pack/plugins/ml/public/application/explorer/explorer_dashboard_service.ts @@ -52,51 +52,49 @@ const explorerState$: Observable = explorerFilteredAction$.pipe( ); const explorerAppState$: Observable = explorerState$.pipe( - map( - (state: ExplorerState): ExplorerAppState => { - const appState: ExplorerAppState = { - mlExplorerFilter: {}, - mlExplorerSwimlane: {}, - }; - - if (state.selectedCells !== undefined) { - const swimlaneSelectedCells = state.selectedCells; - appState.mlExplorerSwimlane.selectedType = swimlaneSelectedCells.type; - appState.mlExplorerSwimlane.selectedLanes = swimlaneSelectedCells.lanes; - appState.mlExplorerSwimlane.selectedTimes = swimlaneSelectedCells.times; - appState.mlExplorerSwimlane.showTopFieldValues = swimlaneSelectedCells.showTopFieldValues; - } - - if (state.viewBySwimlaneFieldName !== undefined) { - appState.mlExplorerSwimlane.viewByFieldName = state.viewBySwimlaneFieldName; - } - - if (state.viewByFromPage !== undefined) { - appState.mlExplorerSwimlane.viewByFromPage = state.viewByFromPage; - } - - if (state.viewByPerPage !== undefined) { - appState.mlExplorerSwimlane.viewByPerPage = state.viewByPerPage; - } - - if (state.swimLaneSeverity !== undefined) { - appState.mlExplorerSwimlane.severity = state.swimLaneSeverity; - } - - if (state.showCharts !== undefined) { - appState.mlShowCharts = state.showCharts; - } - - if (state.filterActive) { - appState.mlExplorerFilter.influencersFilterQuery = state.influencersFilterQuery; - appState.mlExplorerFilter.filterActive = state.filterActive; - appState.mlExplorerFilter.filteredFields = state.filteredFields; - appState.mlExplorerFilter.queryString = state.queryString; - } - - return appState; + map((state: ExplorerState): ExplorerAppState => { + const appState: ExplorerAppState = { + mlExplorerFilter: {}, + mlExplorerSwimlane: {}, + }; + + if (state.selectedCells !== undefined) { + const swimlaneSelectedCells = state.selectedCells; + appState.mlExplorerSwimlane.selectedType = swimlaneSelectedCells.type; + appState.mlExplorerSwimlane.selectedLanes = swimlaneSelectedCells.lanes; + appState.mlExplorerSwimlane.selectedTimes = swimlaneSelectedCells.times; + appState.mlExplorerSwimlane.showTopFieldValues = swimlaneSelectedCells.showTopFieldValues; } - ), + + if (state.viewBySwimlaneFieldName !== undefined) { + appState.mlExplorerSwimlane.viewByFieldName = state.viewBySwimlaneFieldName; + } + + if (state.viewByFromPage !== undefined) { + appState.mlExplorerSwimlane.viewByFromPage = state.viewByFromPage; + } + + if (state.viewByPerPage !== undefined) { + appState.mlExplorerSwimlane.viewByPerPage = state.viewByPerPage; + } + + if (state.swimLaneSeverity !== undefined) { + appState.mlExplorerSwimlane.severity = state.swimLaneSeverity; + } + + if (state.showCharts !== undefined) { + appState.mlShowCharts = state.showCharts; + } + + if (state.filterActive) { + appState.mlExplorerFilter.influencersFilterQuery = state.influencersFilterQuery; + appState.mlExplorerFilter.filterActive = state.filterActive; + appState.mlExplorerFilter.filteredFields = state.filteredFields; + appState.mlExplorerFilter.queryString = state.queryString; + } + + return appState; + }), distinctUntilChanged(isEqual) ); diff --git a/x-pack/plugins/ml/public/application/explorer/hooks/use_explorer_url_state.ts b/x-pack/plugins/ml/public/application/explorer/hooks/use_explorer_url_state.ts index f77a730f10a12..421018abb854f 100644 --- a/x-pack/plugins/ml/public/application/explorer/hooks/use_explorer_url_state.ts +++ b/x-pack/plugins/ml/public/application/explorer/hooks/use_explorer_url_state.ts @@ -14,9 +14,8 @@ export function useExplorerUrlState() { * Originally `mlExplorerSwimlane` resided directly in the app URL state (`_a` URL state key). * With current URL structure it has been moved under the `explorer` key of the app state (_a). */ - const [legacyExplorerState] = usePageUrlState( - 'mlExplorerSwimlane' - ); + const [legacyExplorerState] = + usePageUrlState('mlExplorerSwimlane'); return usePageUrlState(ML_PAGES.ANOMALY_EXPLORER, { mlExplorerSwimlane: legacyExplorerState, diff --git a/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/check_selected_cells.ts b/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/check_selected_cells.ts index f7e7db9aad14c..e41ec55c6685c 100644 --- a/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/check_selected_cells.ts +++ b/x-pack/plugins/ml/public/application/explorer/reducers/explorer_reducer/check_selected_cells.ts @@ -20,13 +20,8 @@ interface SwimlanePoint { // If filter is active - selectedCell may not be available due to swimlane view by change to filter fieldName // Ok to keep cellSelection in this case export const checkSelectedCells = (state: ExplorerState) => { - const { - filterActive, - loading, - selectedCells, - viewBySwimlaneData, - viewBySwimlaneDataLoading, - } = state; + const { filterActive, loading, selectedCells, viewBySwimlaneData, viewBySwimlaneDataLoading } = + state; if (loading || viewBySwimlaneDataLoading) { return {}; diff --git a/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx b/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx index bc00c6a258a07..49bd00d888cf8 100644 --- a/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx +++ b/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx @@ -86,31 +86,16 @@ export function isViewBySwimLaneData(arg: any): arg is ViewBySwimLaneData { /** * Provides a custom tooltip for the anomaly swim lane chart. */ -const SwimLaneTooltip = (fieldName?: string): FC<{ values: TooltipValue[] }> => ({ values }) => { - const tooltipData: TooltipValue[] = []; - - if (values.length === 1 && fieldName) { - // Y-axis tooltip for viewBy swim lane - const [yAxis] = values; - // @ts-ignore - tooltipData.push({ skipHeader: true }); - tooltipData.push({ - label: fieldName, - value: yAxis.value, +const SwimLaneTooltip = + (fieldName?: string): FC<{ values: TooltipValue[] }> => + ({ values }) => { + const tooltipData: TooltipValue[] = []; + + if (values.length === 1 && fieldName) { + // Y-axis tooltip for viewBy swim lane + const [yAxis] = values; // @ts-ignore - seriesIdentifier: { - key: yAxis.value, - }, - }); - } else if (values.length === 3) { - // Cell tooltip - const [xAxis, yAxis, cell] = values; - - // Display date using same format as Kibana visualizations. - const formattedDate = formatHumanReadableDateTime(parseInt(xAxis.value, 10)); - tooltipData.push({ label: formattedDate } as TooltipValue); - - if (fieldName !== undefined) { + tooltipData.push({ skipHeader: true }); tooltipData.push({ label: fieldName, value: yAxis.value, @@ -119,22 +104,39 @@ const SwimLaneTooltip = (fieldName?: string): FC<{ values: TooltipValue[] }> => key: yAxis.value, }, }); + } else if (values.length === 3) { + // Cell tooltip + const [xAxis, yAxis, cell] = values; + + // Display date using same format as Kibana visualizations. + const formattedDate = formatHumanReadableDateTime(parseInt(xAxis.value, 10)); + tooltipData.push({ label: formattedDate } as TooltipValue); + + if (fieldName !== undefined) { + tooltipData.push({ + label: fieldName, + value: yAxis.value, + // @ts-ignore + seriesIdentifier: { + key: yAxis.value, + }, + }); + } + tooltipData.push({ + label: i18n.translate('xpack.ml.explorer.swimlane.maxAnomalyScoreLabel', { + defaultMessage: 'Max anomaly score', + }), + value: cell.formattedValue === '0' ? ' < 1' : cell.formattedValue, + color: cell.color, + // @ts-ignore + seriesIdentifier: { + key: cell.value, + }, + }); } - tooltipData.push({ - label: i18n.translate('xpack.ml.explorer.swimlane.maxAnomalyScoreLabel', { - defaultMessage: 'Max anomaly score', - }), - value: cell.formattedValue === '0' ? ' < 1' : cell.formattedValue, - color: cell.color, - // @ts-ignore - seriesIdentifier: { - key: cell.value, - }, - }); - } - return ; -}; + return ; + }; export interface SwimlaneProps { filterActive?: boolean; diff --git a/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/editor.test.tsx b/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/editor.test.tsx index 6489333e4e576..f6769abb610b8 100644 --- a/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/editor.test.tsx +++ b/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/editor.test.tsx @@ -16,7 +16,7 @@ import React from 'react'; import { CustomUrlEditor } from './editor'; import { TIME_RANGE_TYPE, URL_TYPE } from './constants'; import { CustomUrlSettings } from './utils'; -import { IIndexPattern } from '../../../../../../../../src/plugins/data/common/index_patterns'; +import { IIndexPattern } from '../../../../../../../../src/plugins/data/common'; function prepareTest(customUrl: CustomUrlSettings, setEditCustomUrlFn: (url: UrlConfig) => void) { const savedCustomUrls = [ diff --git a/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/editor.tsx b/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/editor.tsx index 164226a0aaaaa..e22eb1484df2e 100644 --- a/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/editor.tsx +++ b/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/editor.tsx @@ -29,7 +29,7 @@ import { isValidLabel } from '../../../util/custom_url_utils'; import { TIME_RANGE_TYPE, URL_TYPE } from './constants'; import { UrlConfig } from '../../../../../common/types/custom_urls'; -import { IIndexPattern } from '../../../../../../../../src/plugins/data/common/index_patterns'; +import { IIndexPattern } from '../../../../../../../../src/plugins/data/common'; function getLinkToOptions() { return [ diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_job_flyout.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_job_flyout.js index d7f42daf5f3c1..c57995d14e8f8 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_job_flyout.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_job_flyout.js @@ -193,8 +193,9 @@ export class EditJobFlyoutUI extends Component { let { jobModelMemoryLimitValidationError, jobGroupsValidationError } = this.state; if (jobDetails.jobModelMemoryLimit !== undefined) { - jobModelMemoryLimitValidationError = validateModelMemoryLimit(jobDetails.jobModelMemoryLimit) - .message; + jobModelMemoryLimitValidationError = validateModelMemoryLimit( + jobDetails.jobModelMemoryLimit + ).message; } if (jobDetails.jobGroups !== undefined) { diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx index da4c9b0b0cc00..ce93080558016 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx @@ -37,7 +37,7 @@ import { loadSavedDashboards, loadIndexPatterns } from '../edit_utils'; import { openCustomUrlWindow } from '../../../../../util/custom_url_utils'; import { Job } from '../../../../../../../common/types/anomaly_detection_jobs'; import { UrlConfig } from '../../../../../../../common/types/custom_urls'; -import { IIndexPattern } from '../../../../../../../../../../src/plugins/data/common/index_patterns'; +import { IIndexPattern } from '../../../../../../../../../../src/plugins/data/common'; import { MlKibanaReactContextValue } from '../../../../../contexts/kibana'; const MAX_NUMBER_DASHBOARDS = 1000; diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/reset_job_modal/open_jobs_warning_callout.tsx b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/reset_job_modal/open_jobs_warning_callout.tsx index 39e86cdf1f4ac..e61e4200a0056 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/reset_job_modal/open_jobs_warning_callout.tsx +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/reset_job_modal/open_jobs_warning_callout.tsx @@ -17,9 +17,10 @@ interface Props { } export const OpenJobsWarningCallout: FC = ({ jobs }) => { - const openJobsCount = useMemo(() => jobs.filter((j) => j.jobState !== JOB_STATE.CLOSED).length, [ - jobs, - ]); + const openJobsCount = useMemo( + () => jobs.filter((j) => j.jobState !== JOB_STATE.CLOSED).length, + [jobs] + ); if (openJobsCount === 0) { return null; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/categorization_job_creator.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/categorization_job_creator.ts index ccea4ddf52ea3..128a541ff9f96 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/categorization_job_creator.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/categorization_job_creator.ts @@ -119,12 +119,8 @@ export class CategorizationJobCreator extends JobCreator { } public async loadCategorizationFieldExamples() { - const { - examples, - sampleSize, - overallValidStatus, - validationChecks, - } = await this._examplesLoader.loadExamples(); + const { examples, sampleSize, overallValidStatus, validationChecks } = + await this._examplesLoader.loadExamples(); this._categoryFieldExamples = examples; this._validationChecks = validationChecks; this._overallValidStatus = overallValidStatus; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator_factory.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator_factory.ts index 7b9305e42dfdd..8c77ae5def102 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator_factory.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator_factory.ts @@ -16,34 +16,32 @@ import { RareJobCreator } from './rare_job_creator'; import { JOB_TYPE } from '../../../../../../common/constants/new_job'; -export const jobCreatorFactory = (jobType: JOB_TYPE) => ( - indexPattern: IndexPattern, - savedSearch: SavedSearchSavedObject | null, - query: object -) => { - let jc; - switch (jobType) { - case JOB_TYPE.SINGLE_METRIC: - jc = SingleMetricJobCreator; - break; - case JOB_TYPE.MULTI_METRIC: - jc = MultiMetricJobCreator; - break; - case JOB_TYPE.POPULATION: - jc = PopulationJobCreator; - break; - case JOB_TYPE.ADVANCED: - jc = AdvancedJobCreator; - break; - case JOB_TYPE.CATEGORIZATION: - jc = CategorizationJobCreator; - break; - case JOB_TYPE.RARE: - jc = RareJobCreator; - break; - default: - jc = SingleMetricJobCreator; - break; - } - return new jc(indexPattern, savedSearch, query); -}; +export const jobCreatorFactory = + (jobType: JOB_TYPE) => + (indexPattern: IndexPattern, savedSearch: SavedSearchSavedObject | null, query: object) => { + let jc; + switch (jobType) { + case JOB_TYPE.SINGLE_METRIC: + jc = SingleMetricJobCreator; + break; + case JOB_TYPE.MULTI_METRIC: + jc = MultiMetricJobCreator; + break; + case JOB_TYPE.POPULATION: + jc = PopulationJobCreator; + break; + case JOB_TYPE.ADVANCED: + jc = AdvancedJobCreator; + break; + case JOB_TYPE.CATEGORIZATION: + jc = CategorizationJobCreator; + break; + case JOB_TYPE.RARE: + jc = RareJobCreator; + break; + default: + jc = SingleMetricJobCreator; + break; + } + return new jc(indexPattern, savedSearch, query); + }; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/model_memory_estimator.test.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/model_memory_estimator.test.ts index 08de1e4bb2fd9..b4b0d4b4f6870 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/model_memory_estimator.test.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/model_memory_estimator.test.ts @@ -37,9 +37,9 @@ describe('delay', () => { isModelMemoryEstimationPayloadValid: true, } as JobValidator; wizardInitialized$ = new BehaviorSubject(false); - mockJobCreator = ({ + mockJobCreator = { wizardInitialized$, - } as unknown) as JobCreator; + } as unknown as JobCreator; modelMemoryEstimator = modelMemoryEstimatorProvider(mockJobCreator, mockJobValidator); }); afterEach(() => { @@ -106,9 +106,9 @@ describe('delay', () => { modelMemoryEstimator.updates$.subscribe(spy); - modelMemoryEstimator.update(({ + modelMemoryEstimator.update({ analysisConfig: { detectors: [] }, - } as unknown) as CalculatePayload); + } as unknown as CalculatePayload); // @ts-ignore mockJobValidator.isModelMemoryEstimationPayloadValid = false; clock.tick(601); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/common/datafeed_preview_flyout/datafeed_preview.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/common/datafeed_preview_flyout/datafeed_preview.tsx index c6d6e6789bd95..8f011f5d5691d 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/common/datafeed_preview_flyout/datafeed_preview.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/common/datafeed_preview_flyout/datafeed_preview.tsx @@ -29,9 +29,10 @@ export const DatafeedPreview: FC<{ jobs: { datafeedPreview }, } = useMlApiContext(); // the ace editor requires a fixed height - const editorHeight = useMemo(() => `${window.innerHeight - 230 - heightOffset}px`, [ - heightOffset, - ]); + const editorHeight = useMemo( + () => `${window.innerHeight - 230 - heightOffset}px`, + [heightOffset] + ); const [loading, setLoading] = useState(false); const [previewJsonString, setPreviewJsonString] = useState(''); const [outOfDate, setOutOfDate] = useState(false); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/common/json_editor_flyout/json_editor_flyout.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/common/json_editor_flyout/json_editor_flyout.tsx index b2e4a447e4c31..ce71cd80e45c0 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/common/json_editor_flyout/json_editor_flyout.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/common/json_editor_flyout/json_editor_flyout.tsx @@ -272,9 +272,10 @@ const Contents: FC<{ heightOffset?: number; }> = ({ title, value, editJson, onChange, heightOffset = 0 }) => { // the ace editor requires a fixed height - const editorHeight = useMemo(() => `${window.innerHeight - 230 - heightOffset}px`, [ - heightOffset, - ]); + const editorHeight = useMemo( + () => `${window.innerHeight - 230 - heightOffset}px`, + [heightOffset] + ); return ( diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/common/model_memory_limit/model_memory_limit_input.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/common/model_memory_limit/model_memory_limit_input.tsx index a670f90bc7667..b43e31d682ee8 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/common/model_memory_limit/model_memory_limit_input.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/common/model_memory_limit/model_memory_limit_input.tsx @@ -12,13 +12,8 @@ import { JobCreatorContext } from '../../job_creator_context'; import { Description } from './description'; export const ModelMemoryLimitInput: FC = () => { - const { - jobCreator, - jobCreatorUpdate, - jobCreatorUpdated, - jobValidator, - jobValidatorUpdated, - } = useContext(JobCreatorContext); + const { jobCreator, jobCreatorUpdate, jobCreatorUpdated, jobValidator, jobValidatorUpdated } = + useContext(JobCreatorContext); const [validation, setValidation] = useState(jobValidator.modelMemoryLimit); const [modelMemoryLimit, setModelMemoryLimit] = useState( jobCreator.modelMemoryLimit === null ? '' : jobCreator.modelMemoryLimit diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/frequency/frequency_input.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/frequency/frequency_input.tsx index 58cd6579f421d..948b83e49bc34 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/frequency/frequency_input.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/frequency/frequency_input.tsx @@ -13,13 +13,8 @@ import { calculateDatafeedFrequencyDefaultSeconds } from '../../../../../../../. import { useStringifiedValue } from '../hooks'; export const FrequencyInput: FC = () => { - const { - jobCreator, - jobCreatorUpdate, - jobCreatorUpdated, - jobValidator, - jobValidatorUpdated, - } = useContext(JobCreatorContext); + const { jobCreator, jobCreatorUpdate, jobCreatorUpdated, jobValidator, jobValidatorUpdated } = + useContext(JobCreatorContext); const [validation, setValidation] = useState(jobValidator.frequency); const { value: frequency, setValue: setFrequency } = useStringifiedValue(jobCreator.frequency); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/query_delay/query_delay_input.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/query_delay/query_delay_input.tsx index 290fe4a3c393d..055acc3731b6c 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/query_delay/query_delay_input.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/query_delay/query_delay_input.tsx @@ -13,9 +13,8 @@ import { useStringifiedValue } from '../hooks'; import { DEFAULT_QUERY_DELAY } from '../../../../../../../../../common/constants/new_job'; export const QueryDelayInput: FC = () => { - const { jobCreator, jobCreatorUpdate, jobValidator, jobValidatorUpdated } = useContext( - JobCreatorContext - ); + const { jobCreator, jobCreatorUpdate, jobValidator, jobValidatorUpdated } = + useContext(JobCreatorContext); const [validation, setValidation] = useState(jobValidator.queryDelay); const { value: queryDelay, setValue: setQueryDelay } = useStringifiedValue(jobCreator.queryDelay); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/scroll_size/scroll_size_input.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/scroll_size/scroll_size_input.tsx index c115970864429..0b8578b57167d 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/scroll_size/scroll_size_input.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/scroll_size/scroll_size_input.tsx @@ -12,9 +12,8 @@ import { JobCreatorContext } from '../../../job_creator_context'; import { Description } from './description'; export const ScrollSizeInput: FC = () => { - const { jobCreator, jobCreatorUpdate, jobValidator, jobValidatorUpdated } = useContext( - JobCreatorContext - ); + const { jobCreator, jobCreatorUpdate, jobValidator, jobValidatorUpdated } = + useContext(JobCreatorContext); const [validation, setValidation] = useState(jobValidator.scrollSize); const [scrollSizeString, setScrollSize] = useState( jobCreator.scrollSize === null ? '' : `${jobCreator.scrollSize}` diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/groups/groups_input.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/groups/groups_input.tsx index bb1baa1f35aa4..f325051d6f0a6 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/groups/groups_input.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/groups/groups_input.tsx @@ -13,9 +13,8 @@ import { tabColor } from '../../../../../../../../../common/util/group_color_uti import { Description } from './description'; export const GroupsInput: FC = () => { - const { jobCreator, jobCreatorUpdate, jobValidator, jobValidatorUpdated } = useContext( - JobCreatorContext - ); + const { jobCreator, jobCreatorUpdate, jobValidator, jobValidatorUpdated } = + useContext(JobCreatorContext); const { existingJobsAndGroups } = useContext(JobCreatorContext); const [selectedGroups, setSelectedGroups] = useState(jobCreator.groups); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/job_id/job_id_input.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/job_id/job_id_input.tsx index e2d022d7043f0..bc643fd4885c4 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/job_id/job_id_input.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/job_id/job_id_input.tsx @@ -11,9 +11,8 @@ import { JobCreatorContext } from '../../../job_creator_context'; import { Description } from './description'; export const JobIdInput: FC = () => { - const { jobCreator, jobCreatorUpdate, jobValidator, jobValidatorUpdated } = useContext( - JobCreatorContext - ); + const { jobCreator, jobCreatorUpdate, jobValidator, jobValidatorUpdated } = + useContext(JobCreatorContext); const [jobId, setJobId] = useState(jobCreator.jobId); const validation = useMemo(() => { diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_detector_modal/advanced_detector_modal.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_detector_modal/advanced_detector_modal.tsx index 237af335ffa51..f156233dfde85 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_detector_modal/advanced_detector_modal.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_detector_modal/advanced_detector_modal.tsx @@ -119,11 +119,10 @@ export const AdvancedDetectorModal: FC = ({ const eventRateField = fields.find((f) => f.id === EVENT_RATE_FIELD_ID); - const onOptionChange = (func: (p: EuiComboBoxOptionOption) => any) => ( - selectedOptions: EuiComboBoxOptionOption[] - ) => { - func(selectedOptions[0] || emptyOption); - }; + const onOptionChange = + (func: (p: EuiComboBoxOptionOption) => any) => (selectedOptions: EuiComboBoxOptionOption[]) => { + func(selectedOptions[0] || emptyOption); + }; function getAgg(title: string) { return aggs.find((a) => a.id === title) || null; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_view/detector_list.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_view/detector_list.tsx index b5cd24e076a7f..ba0c934f44753 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_view/detector_list.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_view/detector_list.tsx @@ -35,9 +35,12 @@ interface Props { } export const DetectorList: FC = ({ isActive, onEditJob, onDeleteJob }) => { - const { jobCreator: jc, jobCreatorUpdated, jobValidator, jobValidatorUpdated } = useContext( - JobCreatorContext - ); + const { + jobCreator: jc, + jobCreatorUpdated, + jobValidator, + jobValidatorUpdated, + } = useContext(JobCreatorContext); const jobCreator = jc as AdvancedJobCreator; const [detectors, setDetectors] = useState(jobCreator.detectors); const [validation, setValidation] = useState(jobValidator.duplicateDetectors); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/bucket_span/bucket_span.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/bucket_span/bucket_span.tsx index f92d9aa5f9a6c..8eb895f8110b7 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/bucket_span/bucket_span.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/bucket_span/bucket_span.tsx @@ -19,13 +19,8 @@ interface Props { } export const BucketSpan: FC = ({ setIsValid, hideEstimateButton = false }) => { - const { - jobCreator, - jobCreatorUpdate, - jobCreatorUpdated, - jobValidator, - jobValidatorUpdated, - } = useContext(JobCreatorContext); + const { jobCreator, jobCreatorUpdate, jobCreatorUpdated, jobValidator, jobValidatorUpdated } = + useContext(JobCreatorContext); const [bucketSpan, setBucketSpan] = useState(jobCreator.bucketSpan); const [validation, setValidation] = useState(jobValidator.bucketSpan); const [estimating, setEstimating] = useState(false); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/metric_selection_summary.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/metric_selection_summary.tsx index 9d84854245216..157f44aa3a6cb 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/metric_selection_summary.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/metric_selection_summary.tsx @@ -17,9 +17,12 @@ import { CategoryStoppedPartitions } from './category_stopped_partitions'; const DTR_IDX = 0; export const CategorizationDetectorsSummary: FC = () => { - const { jobCreator: jc, chartLoader, resultsLoader, chartInterval } = useContext( - JobCreatorContext - ); + const { + jobCreator: jc, + chartLoader, + resultsLoader, + chartInterval, + } = useContext(JobCreatorContext); const jobCreator = jc as CategorizationJobCreator; const [loadingData, setLoadingData] = useState(false); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/multi_metric_view/metric_selection_summary.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/multi_metric_view/metric_selection_summary.tsx index a4c344d16482b..d6cbd09419f0b 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/multi_metric_view/metric_selection_summary.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/multi_metric_view/metric_selection_summary.tsx @@ -16,9 +16,12 @@ import { ChartGrid } from './chart_grid'; import { getToastNotificationService } from '../../../../../../../services/toast_notification_service'; export const MultiMetricDetectorsSummary: FC = () => { - const { jobCreator: jc, chartLoader, resultsLoader, chartInterval } = useContext( - JobCreatorContext - ); + const { + jobCreator: jc, + chartLoader, + resultsLoader, + chartInterval, + } = useContext(JobCreatorContext); const jobCreator = jc as MultiMetricJobCreator; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/metric_selection_summary.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/metric_selection_summary.tsx index 191e58362d587..4da7e3f5ff22a 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/metric_selection_summary.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/metric_selection_summary.tsx @@ -21,9 +21,12 @@ import { getToastNotificationService } from '../../../../../../../services/toast type DetectorFieldValues = Record; export const PopulationDetectorsSummary: FC = () => { - const { jobCreator: jc, chartLoader, resultsLoader, chartInterval } = useContext( - JobCreatorContext - ); + const { + jobCreator: jc, + chartLoader, + resultsLoader, + chartInterval, + } = useContext(JobCreatorContext); const jobCreator = jc as PopulationJobCreator; const [aggFieldPairList, setAggFieldPairList] = useState( diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/single_metric_view/metric_selection_summary.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/single_metric_view/metric_selection_summary.tsx index ced94b2095f72..457be612c4fd4 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/single_metric_view/metric_selection_summary.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/single_metric_view/metric_selection_summary.tsx @@ -17,9 +17,12 @@ import { getToastNotificationService } from '../../../../../../../services/toast const DTR_IDX = 0; export const SingleMetricDetectorsSummary: FC = () => { - const { jobCreator: jc, chartLoader, resultsLoader, chartInterval } = useContext( - JobCreatorContext - ); + const { + jobCreator: jc, + chartLoader, + resultsLoader, + chartInterval, + } = useContext(JobCreatorContext); const jobCreator = jc as SingleMetricJobCreator; const [lineChartsData, setLineChartData] = useState({}); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/summary_step/summary.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/summary_step/summary.tsx index 5933ec7ff177f..b6658fd95e573 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/summary_step/summary.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/summary_step/summary.tsx @@ -50,9 +50,8 @@ export const SummaryStep: FC = ({ setCurrentStep, isCurrentStep }) => const navigateToPath = useNavigateToPath(); - const { jobCreator, jobValidator, jobValidatorUpdated, resultsLoader } = useContext( - JobCreatorContext - ); + const { jobCreator, jobValidator, jobValidatorUpdated, resultsLoader } = + useContext(JobCreatorContext); const [progress, setProgress] = useState(resultsLoader.progress); const [creatingJob, setCreatingJob] = useState(false); const [isValid, setIsValid] = useState(jobValidator.validationSummary.basic); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/time_range_step/time_range.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/time_range_step/time_range.tsx index 66c92d5c80851..4c13130ae4ce3 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/time_range_step/time_range.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/time_range_step/time_range.tsx @@ -26,13 +26,8 @@ export const TimeRangeStep: FC = ({ setCurrentStep, isCurrentStep }) const { services } = useMlKibana(); const mlContext = useMlContext(); - const { - jobCreator, - jobCreatorUpdate, - jobCreatorUpdated, - chartLoader, - chartInterval, - } = useContext(JobCreatorContext); + const { jobCreator, jobCreatorUpdate, jobCreatorUpdated, chartLoader, chartInterval } = + useContext(JobCreatorContext); const [timeRange, setTimeRange] = useState({ start: jobCreator.start, diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/recognize/components/job_item.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/recognize/components/job_item.tsx index 0daf4f28f33d3..39d3eb634e9ce 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/recognize/components/job_item.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/recognize/components/job_item.tsx @@ -173,10 +173,10 @@ export const JobItem: FC = memo( } ); -function getDatafeedStartedIcon({ - awaitingMlNodeAllocation, - success, -}: DatafeedResponse): { type: string; color: string } { +function getDatafeedStartedIcon({ awaitingMlNodeAllocation, success }: DatafeedResponse): { + type: string; + color: string; +} { if (awaitingMlNodeAllocation === true) { return { type: 'alert', color: 'warning' }; } diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/utils/new_job_utils.test.ts b/x-pack/plugins/ml/public/application/jobs/new_job/utils/new_job_utils.test.ts index 41f27500cea72..3f19f3137934e 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/utils/new_job_utils.test.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/utils/new_job_utils.test.ts @@ -6,19 +6,19 @@ */ import { IUiSettingsClient } from 'kibana/public'; -import { IIndexPattern } from '../../../../../../../../src/plugins/data/common/index_patterns'; +import { IIndexPattern } from '../../../../../../../../src/plugins/data/common'; import { SavedSearchSavedObject } from '../../../../../common/types/kibana'; import { createSearchItems } from './new_job_utils'; describe('createSearchItems', () => { const kibanaConfig = {} as IUiSettingsClient; - const indexPattern = ({ + const indexPattern = { fields: [], - } as unknown) as IIndexPattern; + } as unknown as IIndexPattern; - let savedSearch = ({} as unknown) as SavedSearchSavedObject; + let savedSearch = {} as unknown as SavedSearchSavedObject; beforeEach(() => { - savedSearch = ({ + savedSearch = { client: { http: { basePath: { @@ -53,7 +53,7 @@ describe('createSearchItems', () => { id: '7e252840-bd27-11ea-8a6c-75d1a0bd08ab', }, ], - } as unknown) as SavedSearchSavedObject; + } as unknown as SavedSearchSavedObject; }); test('should match index pattern', () => { diff --git a/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/utils.ts b/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/utils.ts index d4d7e9448be13..be8ce9e2512da 100644 --- a/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/utils.ts +++ b/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/utils.ts @@ -10,9 +10,10 @@ import { JOB_STATE, DATAFEED_STATE } from '../../../../../common/constants/state import { Group, GroupsDictionary } from './anomaly_detection_panel'; import { MlSummaryJobs, MlSummaryJob } from '../../../../../common/types/anomaly_detection_jobs'; -export function getGroupsFromJobs( - jobs: MlSummaryJobs -): { groups: GroupsDictionary; count: number } { +export function getGroupsFromJobs(jobs: MlSummaryJobs): { + groups: GroupsDictionary; + count: number; +} { const groups: any = { ungrouped: { id: 'ungrouped', diff --git a/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx b/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx index 541449a9c7530..8c704ef4240a0 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx @@ -97,10 +97,8 @@ export const TimeSeriesExplorerUrlStateManager: FC { const { toasts } = useNotifications(); const toastNotificationService = useToastNotificationService(); - const [ - timeSeriesExplorerUrlState, - setTimeSeriesExplorerUrlState, - ] = useTimeSeriesExplorerUrlState(); + const [timeSeriesExplorerUrlState, setTimeSeriesExplorerUrlState] = + useTimeSeriesExplorerUrlState(); const [globalState, setGlobalState] = useUrlState('_g'); const [lastRefresh, setLastRefresh] = useState(0); const previousRefresh = usePrevious(lastRefresh); diff --git a/x-pack/plugins/ml/public/application/services/anomaly_explorer_charts_service.test.ts b/x-pack/plugins/ml/public/application/services/anomaly_explorer_charts_service.test.ts index 5b4a5ce87a008..9d0f68b1e8bed 100644 --- a/x-pack/plugins/ml/public/application/services/anomaly_explorer_charts_service.test.ts +++ b/x-pack/plugins/ml/public/application/services/anomaly_explorer_charts_service.test.ts @@ -85,8 +85,8 @@ describe('AnomalyExplorerChartsService', () => { }; const anomalyExplorerService = new AnomalyExplorerChartsService( timefilterMock, - (mlApiServicesMock as unknown) as MlApiServices, - (mlResultsServiceMock as unknown) as MlResultsService + mlApiServicesMock as unknown as MlApiServices, + mlResultsServiceMock as unknown as MlResultsService ); const timeRange = { @@ -103,7 +103,7 @@ describe('AnomalyExplorerChartsService', () => { test('should return anomaly data without explorer service', async () => { const anomalyData = (await anomalyExplorerService.getAnomalyData( undefined, - (combinedJobRecords as unknown) as Record, + combinedJobRecords as unknown as Record, 1000, mockAnomalyChartRecords, timeRange.earliestMs, @@ -119,7 +119,7 @@ describe('AnomalyExplorerChartsService', () => { const anomalyData = (await anomalyExplorerService.getAnomalyData( undefined, // @ts-ignore - (combinedJobRecords as unknown) as Record, + combinedJobRecords as unknown as Record, 1000, [], timeRange.earliestMs, @@ -140,7 +140,7 @@ describe('AnomalyExplorerChartsService', () => { const anomalyData = (await anomalyExplorerService.getAnomalyData( undefined, - (combinedJobRecords as unknown) as Record, + combinedJobRecords as unknown as Record, 1000, mockAnomalyChartRecordsClone, timeRange.earliestMs, diff --git a/x-pack/plugins/ml/public/application/services/dashboard_service.test.ts b/x-pack/plugins/ml/public/application/services/dashboard_service.test.ts index 185db920bae77..9fb4a22a4942b 100644 --- a/x-pack/plugins/ml/public/application/services/dashboard_service.test.ts +++ b/x-pack/plugins/ml/public/application/services/dashboard_service.test.ts @@ -23,9 +23,9 @@ jest.mock('@elastic/eui', () => { describe('DashboardService', () => { const mockSavedObjectClient = savedObjectsServiceMock.createStartContract().client; - const dashboardUrlGenerator = ({ + const dashboardUrlGenerator = { createUrl: jest.fn(), - } as unknown) as DashboardUrlGenerator; + } as unknown as DashboardUrlGenerator; const dashboardService = dashboardServiceProvider( mockSavedObjectClient, '8.0.0', @@ -48,7 +48,7 @@ describe('DashboardService', () => { // act dashboardService.attachPanels( 'test-dashboard', - ({ + { title: 'ML Test', hits: 0, description: '', @@ -92,7 +92,7 @@ describe('DashboardService', () => { kibanaSavedObjectMeta: { searchSourceJSON: '{"query":{"language":"kuery","query":""},"filter":[]}', }, - } as unknown) as DashboardSavedObject, + } as unknown as DashboardSavedObject, [{ title: 'Test title', type: 'test-panel', embeddableConfig: { testConfig: '' } }] ); // assert diff --git a/x-pack/plugins/ml/public/application/services/field_format_service.ts b/x-pack/plugins/ml/public/application/services/field_format_service.ts index 7b73985ea5d98..18b489682318e 100644 --- a/x-pack/plugins/ml/public/application/services/field_format_service.ts +++ b/x-pack/plugins/ml/public/application/services/field_format_service.ts @@ -105,9 +105,8 @@ class FieldFormatService { if (dtr.field_name !== undefined && esAgg !== 'cardinality') { const field = fieldList.getByName(dtr.field_name); if (field !== undefined) { - formatsByDetector[dtr.detector_index!] = indexPatternData.getFormatterForField( - field - ); + formatsByDetector[dtr.detector_index!] = + indexPatternData.getFormatterForField(field); } } }); diff --git a/x-pack/plugins/ml/public/application/services/http_service.ts b/x-pack/plugins/ml/public/application/services/http_service.ts index c90e2c993f450..8d156fb11f815 100644 --- a/x-pack/plugins/ml/public/application/services/http_service.ts +++ b/x-pack/plugins/ml/public/application/services/http_service.ts @@ -16,9 +16,10 @@ function getResultHeaders(headers: HeadersInit) { }; } -function getFetchOptions( - options: HttpFetchOptionsWithPath -): { path: string; fetchOptions: HttpFetchOptions } { +function getFetchOptions(options: HttpFetchOptionsWithPath): { + path: string; + fetchOptions: HttpFetchOptions; +} { if (!options.path) { throw new Error('URL path is missing'); } @@ -121,9 +122,10 @@ export class HttpService { } as HeadersInit; } - private getFetchOptions( - options: HttpFetchOptionsWithPath - ): { path: string; fetchOptions: HttpFetchOptions } { + private getFetchOptions(options: HttpFetchOptionsWithPath): { + path: string; + fetchOptions: HttpFetchOptions; + } { if (!options.path) { throw new Error('URL path is missing'); } diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts index 7a75e1a2bdbc0..883e5d499c3d4 100644 --- a/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts +++ b/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts @@ -93,7 +93,7 @@ export function basePath() { * Temp solution to allow {@link ml} service to use http from * the dependency_cache. */ -const proxyHttpStart = new Proxy(({} as unknown) as HttpStart, { +const proxyHttpStart = new Proxy({} as unknown as HttpStart, { get(obj, prop: keyof HttpStart) { try { return getHttp()[prop]; diff --git a/x-pack/plugins/ml/public/application/services/new_job_capabilities/new_job_capabilities._service.test.ts b/x-pack/plugins/ml/public/application/services/new_job_capabilities/new_job_capabilities._service.test.ts index 4c0ee7b67a994..8c515255927b4 100644 --- a/x-pack/plugins/ml/public/application/services/new_job_capabilities/new_job_capabilities._service.test.ts +++ b/x-pack/plugins/ml/public/application/services/new_job_capabilities/new_job_capabilities._service.test.ts @@ -20,10 +20,10 @@ jest.mock('../ml_api_service', () => ({ }, })); -const indexPattern = ({ +const indexPattern = { id: 'cloudwatch-*', title: 'cloudwatch-*', -} as unknown) as IndexPattern; +} as unknown as IndexPattern; describe('new_job_capabilities_service', () => { describe('cloudwatch newJobCaps()', () => { diff --git a/x-pack/plugins/ml/public/application/services/new_job_capabilities/new_job_capabilities_service.ts b/x-pack/plugins/ml/public/application/services/new_job_capabilities/new_job_capabilities_service.ts index 9324b3c0f0824..c17f379355cea 100644 --- a/x-pack/plugins/ml/public/application/services/new_job_capabilities/new_job_capabilities_service.ts +++ b/x-pack/plugins/ml/public/application/services/new_job_capabilities/new_job_capabilities_service.ts @@ -51,9 +51,8 @@ class NewJobCapsService extends NewJobCapabilitiesServiceBase { addEventRateField(aggs, allFields); } - const { fieldsPreferringKeyword, fieldsPreferringText } = processTextAndKeywordFields( - allFields - ); + const { fieldsPreferringKeyword, fieldsPreferringText } = + processTextAndKeywordFields(allFields); const catFields = fieldsPreferringText.filter( (f) => f.type === ES_FIELD_TYPES.KEYWORD || f.type === ES_FIELD_TYPES.TEXT ); diff --git a/x-pack/plugins/ml/public/application/services/new_job_capabilities/remove_nested_field_children.test.ts b/x-pack/plugins/ml/public/application/services/new_job_capabilities/remove_nested_field_children.test.ts index e960095b5b2db..eaf30d9894f60 100644 --- a/x-pack/plugins/ml/public/application/services/new_job_capabilities/remove_nested_field_children.test.ts +++ b/x-pack/plugins/ml/public/application/services/new_job_capabilities/remove_nested_field_children.test.ts @@ -12,10 +12,10 @@ import { IndexPattern } from '../../../../../../../src/plugins/data/public'; // ensures it can be lazily loaded by the jest.mock function below. import nestedFieldIndexResponse from '../__mocks__/nested_field_index_response.json'; -const indexPattern = ({ +const indexPattern = { id: 'nested-field-index', title: 'nested-field-index', -} as unknown) as IndexPattern; +} as unknown as IndexPattern; describe('removeNestedFieldChildren', () => { describe('cloudwatch newJobCapsAnalytics()', () => { diff --git a/x-pack/plugins/ml/public/application/services/results_service/results_service.d.ts b/x-pack/plugins/ml/public/application/services/results_service/results_service.d.ts index 1848b13cb5a1f..f40db03ab4460 100644 --- a/x-pack/plugins/ml/public/application/services/results_service/results_service.d.ts +++ b/x-pack/plugins/ml/public/application/services/results_service/results_service.d.ts @@ -13,9 +13,7 @@ import { EntityField } from '../../../../common/util/anomaly_utils'; import { RuntimeMappings } from '../../../../common/types/fields'; type RecordForInfluencer = AnomalyRecordDoc; -export function resultsServiceProvider( - mlApiServices: MlApiServices -): { +export function resultsServiceProvider(mlApiServices: MlApiServices): { getScoresByBucket( jobIds: string[], earliestMs: number, diff --git a/x-pack/plugins/ml/public/application/services/usage_collection.test.ts b/x-pack/plugins/ml/public/application/services/usage_collection.test.ts index 5262b67136b71..2c16b028bd277 100644 --- a/x-pack/plugins/ml/public/application/services/usage_collection.test.ts +++ b/x-pack/plugins/ml/public/application/services/usage_collection.test.ts @@ -13,9 +13,9 @@ describe('usage_collection', () => { let usageCollection: jest.Mocked; beforeEach(() => { - usageCollection = ({ + usageCollection = { reportUiCounter: jest.fn(), - } as unknown) as jest.Mocked; + } as unknown as jest.Mocked; }); afterEach(() => { diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/series_controls/series_controls.tsx b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/series_controls/series_controls.tsx index 16c248e948619..af657d5653fff 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/series_controls/series_controls.tsx +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/series_controls/series_controls.tsx @@ -112,9 +112,8 @@ export const SeriesControls: FC = ({ return getControlsForDetector(selectedDetectorIndex, selectedEntities, selectedJobId); }, [selectedDetectorIndex, selectedEntities, selectedJobId]); - const [storageFieldsConfig, setStorageFieldsConfig] = useStorage( - ML_ENTITY_FIELDS_CONFIG - ); + const [storageFieldsConfig, setStorageFieldsConfig] = + useStorage(ML_ENTITY_FIELDS_CONFIG); // Merge the default config with the one from the local storage const resultFieldsConfig = useMemo(() => { diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js index c314fe259f7f9..87131583e44eb 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js @@ -962,13 +962,8 @@ class TimeseriesChartIntl extends Component { } drawContextElements(cxtGroup, cxtWidth, cxtChartHeight, swlHeight) { - const { - bounds, - contextChartData, - contextForecastData, - modelPlotEnabled, - annotationData, - } = this.props; + const { bounds, contextChartData, contextForecastData, modelPlotEnabled, annotationData } = + this.props; const data = contextChartData; const showFocusChartTooltip = this.showFocusChartTooltip.bind(this); diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js index a92d29baf93c1..454bb0b489837 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js @@ -264,12 +264,8 @@ export class TimeSeriesExplorer extends React.Component { * Gets focus data for the current component state/ */ getFocusData(selection) { - const { - selectedJobId, - selectedForecastId, - selectedDetectorIndex, - functionDescription, - } = this.props; + const { selectedJobId, selectedForecastId, selectedDetectorIndex, functionDescription } = + this.props; const { modelPlotEnabled } = this.state; const selectedJob = mlJobService.getJob(selectedJobId); if (isMetricDetector(selectedJob, selectedDetectorIndex) && functionDescription === undefined) { diff --git a/x-pack/plugins/ml/public/application/util/chart_utils.d.ts b/x-pack/plugins/ml/public/application/util/chart_utils.d.ts index ee85525ec00f4..bfb3b03d8024a 100644 --- a/x-pack/plugins/ml/public/application/util/chart_utils.d.ts +++ b/x-pack/plugins/ml/public/application/util/chart_utils.d.ts @@ -9,9 +9,7 @@ import type { ChartType } from '../explorer/explorer_constants'; export declare function numTicksForDateFormat(axisWidth: number, dateFormat: string): number; export declare function getChartType(config: any): ChartType; -export declare function chartLimits( - data: any[] -): { +export declare function chartLimits(data: any[]): { min: number; max: number; }; diff --git a/x-pack/plugins/ml/public/application/util/chart_utils.test.js b/x-pack/plugins/ml/public/application/util/chart_utils.test.js index cac0da49b1099..41f4fe76109a5 100644 --- a/x-pack/plugins/ml/public/application/util/chart_utils.test.js +++ b/x-pack/plugins/ml/public/application/util/chart_utils.test.js @@ -291,52 +291,20 @@ describe('ML - chart utils', () => { const tickValues = getTickValues(1486656000000, 14400000, 1486606500000, 1486719900000); expect(tickValues).toEqual([ - 1486612800000, - 1486627200000, - 1486641600000, - 1486656000000, - 1486670400000, - 1486684800000, - 1486699200000, - 1486713600000, + 1486612800000, 1486627200000, 1486641600000, 1486656000000, 1486670400000, 1486684800000, + 1486699200000, 1486713600000, ]); }); test('filebeat sample data', () => { const tickValues = getTickValues(1486080000000, 14400000, 1485860400000, 1486314000000); expect(tickValues).toEqual([ - 1485864000000, - 1485878400000, - 1485892800000, - 1485907200000, - 1485921600000, - 1485936000000, - 1485950400000, - 1485964800000, - 1485979200000, - 1485993600000, - 1486008000000, - 1486022400000, - 1486036800000, - 1486051200000, - 1486065600000, - 1486080000000, - 1486094400000, - 1486108800000, - 1486123200000, - 1486137600000, - 1486152000000, - 1486166400000, - 1486180800000, - 1486195200000, - 1486209600000, - 1486224000000, - 1486238400000, - 1486252800000, - 1486267200000, - 1486281600000, - 1486296000000, - 1486310400000, + 1485864000000, 1485878400000, 1485892800000, 1485907200000, 1485921600000, 1485936000000, + 1485950400000, 1485964800000, 1485979200000, 1485993600000, 1486008000000, 1486022400000, + 1486036800000, 1486051200000, 1486065600000, 1486080000000, 1486094400000, 1486108800000, + 1486123200000, 1486137600000, 1486152000000, 1486166400000, 1486180800000, 1486195200000, + 1486209600000, 1486224000000, 1486238400000, 1486252800000, 1486267200000, 1486281600000, + 1486296000000, 1486310400000, ]); }); diff --git a/x-pack/plugins/ml/public/application/util/custom_url_utils.ts b/x-pack/plugins/ml/public/application/util/custom_url_utils.ts index 243fe65b24f5e..0a764318d601d 100644 --- a/x-pack/plugins/ml/public/application/util/custom_url_utils.ts +++ b/x-pack/plugins/ml/public/application/util/custom_url_utils.ts @@ -138,59 +138,58 @@ export const isRisonObject = (value: RisonValue): value is RisonObject => { return value !== null && typeof value === 'object'; }; -const getQueryStringResultProvider = ( - record: CustomUrlAnomalyRecordDoc, - getResultTokenValue: GetResultTokenValue -) => (resultPrefix: string, queryString: string, resultPostfix: string): string => { - const URL_LENGTH_LIMIT = 2000; - - let availableCharactersLeft = URL_LENGTH_LIMIT - resultPrefix.length - resultPostfix.length; - - // URL template might contain encoded characters - const queryFields = queryString - // Split query string by AND operator. - .split(/\sand\s/i) - // Get property name from `influencerField:$influencerField$` string. - .map((v) => String(v.split(/:(.+)?\$/)[0]).trim()); - - const queryParts: string[] = []; - const joinOperator = ' AND '; - - fieldsLoop: for (let i = 0; i < queryFields.length; i++) { - const field = queryFields[i]; - // Use lodash get to allow nested JSON fields to be retrieved. - let tokenValues: string[] | string | null = get(record, field) || null; - if (tokenValues === null) { - continue; - } - tokenValues = Array.isArray(tokenValues) ? tokenValues : [tokenValues]; - - // Create a pair `influencerField:value`. - // In cases where there are multiple influencer field values for an anomaly - // combine values with OR operator e.g. `(influencerField:value or influencerField:another_value)`. - let result = ''; - for (let j = 0; j < tokenValues.length; j++) { - const part = `${j > 0 ? ' OR ' : ''}${field}:"${getResultTokenValue(tokenValues[j])}"`; - - // Build up a URL string which is not longer than the allowed length and isn't corrupted by invalid query. - if (availableCharactersLeft < part.length) { - if (result.length > 0) { - queryParts.push(j > 0 ? `(${result})` : result); - } - break fieldsLoop; +const getQueryStringResultProvider = + (record: CustomUrlAnomalyRecordDoc, getResultTokenValue: GetResultTokenValue) => + (resultPrefix: string, queryString: string, resultPostfix: string): string => { + const URL_LENGTH_LIMIT = 2000; + + let availableCharactersLeft = URL_LENGTH_LIMIT - resultPrefix.length - resultPostfix.length; + + // URL template might contain encoded characters + const queryFields = queryString + // Split query string by AND operator. + .split(/\sand\s/i) + // Get property name from `influencerField:$influencerField$` string. + .map((v) => String(v.split(/:(.+)?\$/)[0]).trim()); + + const queryParts: string[] = []; + const joinOperator = ' AND '; + + fieldsLoop: for (let i = 0; i < queryFields.length; i++) { + const field = queryFields[i]; + // Use lodash get to allow nested JSON fields to be retrieved. + let tokenValues: string[] | string | null = get(record, field) || null; + if (tokenValues === null) { + continue; } + tokenValues = Array.isArray(tokenValues) ? tokenValues : [tokenValues]; + + // Create a pair `influencerField:value`. + // In cases where there are multiple influencer field values for an anomaly + // combine values with OR operator e.g. `(influencerField:value or influencerField:another_value)`. + let result = ''; + for (let j = 0; j < tokenValues.length; j++) { + const part = `${j > 0 ? ' OR ' : ''}${field}:"${getResultTokenValue(tokenValues[j])}"`; + + // Build up a URL string which is not longer than the allowed length and isn't corrupted by invalid query. + if (availableCharactersLeft < part.length) { + if (result.length > 0) { + queryParts.push(j > 0 ? `(${result})` : result); + } + break fieldsLoop; + } - result += part; + result += part; - availableCharactersLeft -= result.length; - } + availableCharactersLeft -= result.length; + } - if (result.length > 0) { - queryParts.push(tokenValues.length > 1 ? `(${result})` : result); + if (result.length > 0) { + queryParts.push(tokenValues.length > 1 ? `(${result})` : result); + } } - } - return queryParts.join(joinOperator); -}; + return queryParts.join(joinOperator); + }; /** * Builds a Kibana dashboard or Discover URL from the supplied config, with any diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_embeddable.tsx b/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_embeddable.tsx index 298abd4dcc241..65d26b844e960 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_embeddable.tsx +++ b/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_embeddable.tsx @@ -21,7 +21,7 @@ import { AnomalyChartsEmbeddableOutput, AnomalyChartsServices, } from '..'; -import type { IndexPattern } from '../../../../../../src/plugins/data/common/index_patterns'; +import type { IndexPattern } from '../../../../../../src/plugins/data/common'; import { EmbeddableLoading } from '../common/components/embeddable_loading_fallback'; export const getDefaultExplorerChartsPanelTitle = (jobIds: JobId[]) => i18n.translate('xpack.ml.anomalyChartsEmbeddable.title', { diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_embeddable_factory.test.ts b/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_embeddable_factory.test.ts index 441ac145e1bd4..40d9e54082c35 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_embeddable_factory.test.ts +++ b/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_embeddable_factory.test.ts @@ -35,7 +35,7 @@ describe('AnomalyChartsEmbeddableFactory', () => { } as AnomalyChartsEmbeddableInput); // assert - const mockCalls = ((AnomalyChartsEmbeddable as unknown) as jest.Mock) + const mockCalls = (AnomalyChartsEmbeddable as unknown as jest.Mock) .mock.calls[0]; const input = mockCalls[0]; const createServices = mockCalls[1]; diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_embeddable_factory.ts b/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_embeddable_factory.ts index 4788d809f016f..b95c2b18128fe 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_embeddable_factory.ts +++ b/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_embeddable_factory.ts @@ -25,7 +25,8 @@ import { import { AnomalyExplorerChartsService } from '../../application/services/anomaly_explorer_charts_service'; export class AnomalyChartsEmbeddableFactory - implements EmbeddableFactoryDefinition { + implements EmbeddableFactoryDefinition +{ public readonly type = ANOMALY_EXPLORER_CHARTS_EMBEDDABLE_TYPE; public readonly grouping = [ diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_charts/embeddable_anomaly_charts_container.test.tsx b/x-pack/plugins/ml/public/embeddables/anomaly_charts/embeddable_anomaly_charts_container.test.tsx index 7e4e91eb2ad0e..55e07da9011fb 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_charts/embeddable_anomaly_charts_container.test.tsx +++ b/x-pack/plugins/ml/public/embeddables/anomaly_charts/embeddable_anomaly_charts_container.test.tsx @@ -90,7 +90,7 @@ describe('EmbeddableAnomalyChartsContainer', () => { id: 'test-explorer-charts-embeddable', } as Partial); - trigger = ({ exec: jest.fn() } as unknown) as jest.Mocked; + trigger = { exec: jest.fn() } as unknown as jest.Mocked; const mlStartMock = createMlStartDepsMock(); mlStartMock.uiActions.getTrigger.mockReturnValue(trigger); @@ -110,7 +110,7 @@ describe('EmbeddableAnomalyChartsContainer', () => { ]); }); - services = ([ + services = [ coreStartMock, mlStartMock, { @@ -118,7 +118,7 @@ describe('EmbeddableAnomalyChartsContainer', () => { anomalyExplorerChartsService: createAnomalyExplorerChartsServiceMock(), mlResultsService: createMlResultsServiceMock(), }, - ] as unknown) as EmbeddableAnomalyChartsContainerProps['services']; + ] as unknown as EmbeddableAnomalyChartsContainerProps['services']; }); test('should render explorer charts with a valid embeddable input', async () => { @@ -149,9 +149,9 @@ describe('EmbeddableAnomalyChartsContainer', () => { defaultOptions ); - const calledWith = ((ExplorerAnomaliesContainer as unknown) as jest.Mock< - typeof ExplorerAnomaliesContainer - >).mock.calls[0][0]; + const calledWith = ( + ExplorerAnomaliesContainer as unknown as jest.Mock + ).mock.calls[0][0]; expect(calledWith).toMatchSnapshot(); }); diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_charts/embeddable_anomaly_charts_container.tsx b/x-pack/plugins/ml/public/embeddables/anomaly_charts/embeddable_anomaly_charts_container.tsx index 3222ab582f109..698fcfd55583e 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_charts/embeddable_anomaly_charts_container.tsx +++ b/x-pack/plugins/ml/public/embeddables/anomaly_charts/embeddable_anomaly_charts_container.tsx @@ -59,9 +59,10 @@ export const EmbeddableAnomalyChartsContainer: FC share.url.locators.get(ML_APP_LOCATOR)!, [ - share, - ]); + const mlLocator = useMemo( + () => share.url.locators.get(ML_APP_LOCATOR)!, + [share] + ); const timeBuckets = useMemo(() => { return new TimeBuckets({ @@ -82,7 +83,11 @@ export const EmbeddableAnomalyChartsContainer: FC { ]); }); - services = ([ + services = [ coreStartMock, mlStartMock, { @@ -140,7 +140,7 @@ describe('useAnomalyChartsInputResolver', () => { anomalyExplorerService: anomalyExplorerChartsServiceMock, mlResultsService: createMlResultsServiceMock(), }, - ] as unknown) as EmbeddableAnomalyChartsContainerProps['services']; + ] as unknown as EmbeddableAnomalyChartsContainerProps['services']; onInputChange = jest.fn(); }); diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_charts/use_anomaly_charts_input_resolver.ts b/x-pack/plugins/ml/public/embeddables/anomaly_charts/use_anomaly_charts_input_resolver.ts index 78874ea4299ff..86772dac40dc0 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_charts/use_anomaly_charts_input_resolver.ts +++ b/x-pack/plugins/ml/public/embeddables/anomaly_charts/use_anomaly_charts_input_resolver.ts @@ -123,10 +123,9 @@ export function useAnomalyChartsInputResolver( ), }).pipe( switchMap(({ combinedJobs, anomalyChartRecords }) => { - const combinedJobRecords: Record< - string, - CombinedJob - > = (combinedJobs as CombinedJob[]).reduce((acc, job) => { + const combinedJobRecords: Record = ( + combinedJobs as CombinedJob[] + ).reduce((acc, job) => { return { ...acc, [job.job_id]: job }; }, {}); diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable_factory.test.tsx b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable_factory.test.tsx index 258dc9412cedd..f318d61014027 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable_factory.test.tsx +++ b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable_factory.test.tsx @@ -34,7 +34,7 @@ describe('AnomalySwimlaneEmbeddableFactory', () => { } as AnomalySwimlaneEmbeddableInput); // assert - const mockCalls = ((AnomalySwimlaneEmbeddable as unknown) as jest.Mock) + const mockCalls = (AnomalySwimlaneEmbeddable as unknown as jest.Mock) .mock.calls[0]; const input = mockCalls[0]; const createServices = mockCalls[1]; diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable_factory.ts b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable_factory.ts index bc45e075710c5..1d7e2ddf4d495 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable_factory.ts +++ b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable_factory.ts @@ -24,7 +24,8 @@ import { } from '..'; export class AnomalySwimlaneEmbeddableFactory - implements EmbeddableFactoryDefinition { + implements EmbeddableFactoryDefinition +{ public readonly type = ANOMALY_SWIMLANE_EMBEDDABLE_TYPE; public readonly grouping = [ diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/embeddable_swim_lane_container.test.tsx b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/embeddable_swim_lane_container.test.tsx index 00f4da09bbe0e..c48113d74a9e0 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/embeddable_swim_lane_container.test.tsx +++ b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/embeddable_swim_lane_container.test.tsx @@ -55,15 +55,15 @@ describe('ExplorerSwimlaneContainer', () => { id: 'test-swimlane-embeddable', } as Partial); - trigger = ({ exec: jest.fn() } as unknown) as jest.Mocked; + trigger = { exec: jest.fn() } as unknown as jest.Mocked; const mlStartMock = createMlStartDepsMock(); mlStartMock.uiActions.getTrigger.mockReturnValue(trigger); - services = ([ + services = [ createCoreStartMock(), mlStartMock, - ] as unknown) as ExplorerSwimlaneContainerProps['services']; + ] as unknown as ExplorerSwimlaneContainerProps['services']; }); test('should render a swimlane with a valid embeddable input', async () => { @@ -106,7 +106,7 @@ describe('ExplorerSwimlaneContainer', () => { defaultOptions ); - const calledWith = ((SwimlaneContainer as unknown) as jest.Mock).mock + const calledWith = (SwimlaneContainer as unknown as jest.Mock).mock .calls[0][0]; expect(calledWith).toMatchObject({ diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/embeddable_swim_lane_container.tsx b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/embeddable_swim_lane_container.tsx index d671bff90b31f..daa2858659cae 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/embeddable_swim_lane_container.tsx +++ b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/embeddable_swim_lane_container.tsx @@ -54,22 +54,15 @@ export const EmbeddableSwimLaneContainer: FC = ( const [selectedCells, setSelectedCells] = useState(); - const [ - swimlaneType, - swimlaneData, - perPage, - setPerPage, - timeBuckets, - isLoading, - error, - ] = useSwimlaneInputResolver( - embeddableInput, - onInputChange, - refresh, - services, - chartWidth, - fromPage - ); + const [swimlaneType, swimlaneData, perPage, setPerPage, timeBuckets, isLoading, error] = + useSwimlaneInputResolver( + embeddableInput, + onInputChange, + refresh, + services, + chartWidth, + fromPage + ); useEffect(() => { onOutputChange({ diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/swimlane_input_resolver.test.ts b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/swimlane_input_resolver.test.ts index 01b1e3acf7f95..28aae4bcc0a55 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/swimlane_input_resolver.test.ts +++ b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/swimlane_input_resolver.test.ts @@ -32,14 +32,14 @@ describe('useSwimlaneInputResolver', () => { refresh = new Subject(); services = [ { - uiSettings: ({ + uiSettings: { get: jest.fn(() => { return null; }), - } as unknown) as IUiSettingsClient, + } as unknown as IUiSettingsClient, } as CoreStart, - (null as unknown) as MlStartDependencies, - ({ + null as unknown as MlStartDependencies, + { anomalyTimelineService: { setTimeRange: jest.fn(), loadOverallData: jest.fn(() => @@ -74,7 +74,7 @@ describe('useSwimlaneInputResolver', () => { ]); }), }, - } as unknown) as AnomalySwimlaneServices, + } as unknown as AnomalySwimlaneServices, ]; onInputChange = jest.fn(); }); diff --git a/x-pack/plugins/ml/public/embeddables/types.ts b/x-pack/plugins/ml/public/embeddables/types.ts index 436eee0698708..7c93fb31e9a6c 100644 --- a/x-pack/plugins/ml/public/embeddables/types.ts +++ b/x-pack/plugins/ml/public/embeddables/types.ts @@ -27,7 +27,7 @@ import { ANOMALY_SWIMLANE_EMBEDDABLE_TYPE, } from './constants'; import { MlResultsService } from '../application/services/results_service'; -import { IndexPattern } from '../../../../../src/plugins/data/common/index_patterns/index_patterns'; +import { IndexPattern } from '../../../../../src/plugins/data/common'; export interface AnomalySwimlaneEmbeddableCustomInput { jobIds: JobId[]; diff --git a/x-pack/plugins/ml/public/locator/ml_locator.test.ts b/x-pack/plugins/ml/public/locator/ml_locator.test.ts index 97bb6cb2966f2..3b736a9af4e3e 100644 --- a/x-pack/plugins/ml/public/locator/ml_locator.test.ts +++ b/x-pack/plugins/ml/public/locator/ml_locator.test.ts @@ -80,8 +80,7 @@ describe('MlUrlGenerator', () => { expect(location).toMatchObject({ app: 'ml', - path: - '/jobs/new_job/step/job_type?index=3da93760-e0af-11ea-9ad3-3bcfc330e42a&_g=(time:(from:now-30m,to:now))', + path: '/jobs/new_job/step/job_type?index=3da93760-e0af-11ea-9ad3-3bcfc330e42a&_g=(time:(from:now-30m,to:now))', state: {}, }); }); @@ -112,8 +111,7 @@ describe('MlUrlGenerator', () => { expect(location).toMatchObject({ app: 'ml', - path: - "/explorer?_g=(ml:(jobIds:!(fq_single_1)),refreshInterval:(pause:!f,value:0),time:(from:'2019-02-07T00:00:00.000Z',mode:absolute,to:'2020-08-13T17:15:00.000Z'))&_a=(explorer:(mlExplorerFilter:(),mlExplorerSwimlane:(viewByFromPage:2,viewByPerPage:20),query:(analyze_wildcard:!t,query:'*')))", + path: "/explorer?_g=(ml:(jobIds:!(fq_single_1)),refreshInterval:(pause:!f,value:0),time:(from:'2019-02-07T00:00:00.000Z',mode:absolute,to:'2020-08-13T17:15:00.000Z'))&_a=(explorer:(mlExplorerFilter:(),mlExplorerSwimlane:(viewByFromPage:2,viewByPerPage:20),query:(analyze_wildcard:!t,query:'*')))", state: {}, }); }); @@ -133,8 +131,7 @@ describe('MlUrlGenerator', () => { expect(location).toMatchObject({ app: 'ml', - path: - "/explorer?_g=(ml:(jobIds:!(fq_single_1,logs_categorization_1)),time:(from:'2019-02-07T00:00:00.000Z',mode:absolute,to:'2020-08-13T17:15:00.000Z'))&_a=(explorer:(mlExplorerFilter:(),mlExplorerSwimlane:()))", + path: "/explorer?_g=(ml:(jobIds:!(fq_single_1,logs_categorization_1)),time:(from:'2019-02-07T00:00:00.000Z',mode:absolute,to:'2020-08-13T17:15:00.000Z'))&_a=(explorer:(mlExplorerFilter:(),mlExplorerSwimlane:()))", state: {}, }); }); @@ -164,8 +161,7 @@ describe('MlUrlGenerator', () => { expect(location).toMatchObject({ app: 'ml', - path: - "/timeseriesexplorer?_g=(ml:(jobIds:!(logs_categorization_1)),refreshInterval:(pause:!f,value:0),time:(from:'2020-07-12T00:39:02.912Z',mode:absolute,to:'2020-07-22T15:52:18.613Z'))&_a=(timeseriesexplorer:(mlTimeSeriesExplorer:(),query:(query_string:(analyze_wildcard:!t,query:'*'))))", + path: "/timeseriesexplorer?_g=(ml:(jobIds:!(logs_categorization_1)),refreshInterval:(pause:!f,value:0),time:(from:'2020-07-12T00:39:02.912Z',mode:absolute,to:'2020-07-22T15:52:18.613Z'))&_a=(timeseriesexplorer:(mlTimeSeriesExplorer:(),query:(query_string:(analyze_wildcard:!t,query:'*'))))", state: {}, }); }); @@ -199,8 +195,7 @@ describe('MlUrlGenerator', () => { expect(location).toMatchObject({ app: 'ml', - path: - "/timeseriesexplorer?_g=(ml:(jobIds:!(logs_categorization_1)),refreshInterval:(pause:!f,value:0),time:(from:'2020-07-12T00:39:02.912Z',mode:absolute,to:'2020-07-22T15:52:18.613Z'))&_a=(timeseriesexplorer:(mlTimeSeriesExplorer:(detectorIndex:0,entities:(mlcategory:'2'),zoom:(from:'2020-07-20T23:58:29.367Z',to:'2020-07-21T11:00:13.173Z')),query:(query_string:(analyze_wildcard:!t,query:'*'))))", + path: "/timeseriesexplorer?_g=(ml:(jobIds:!(logs_categorization_1)),refreshInterval:(pause:!f,value:0),time:(from:'2020-07-12T00:39:02.912Z',mode:absolute,to:'2020-07-22T15:52:18.613Z'))&_a=(timeseriesexplorer:(mlTimeSeriesExplorer:(detectorIndex:0,entities:(mlcategory:'2'),zoom:(from:'2020-07-20T23:58:29.367Z',to:'2020-07-21T11:00:13.173Z')),query:(query_string:(analyze_wildcard:!t,query:'*'))))", state: {}, }); }); @@ -230,8 +225,7 @@ describe('MlUrlGenerator', () => { expect(location).toMatchObject({ app: 'ml', - path: - "/data_frame_analytics?_a=(data_frame_analytics:(queryText:'id:grid_regression_1'))", + path: "/data_frame_analytics?_a=(data_frame_analytics:(queryText:'id:grid_regression_1'))", state: {}, }); }); @@ -246,8 +240,7 @@ describe('MlUrlGenerator', () => { expect(location).toMatchObject({ app: 'ml', - path: - "/data_frame_analytics?_a=(data_frame_analytics:(queryText:'groups:(group_1%20or%20group_2)'))", + path: "/data_frame_analytics?_a=(data_frame_analytics:(queryText:'groups:(group_1%20or%20group_2)'))", state: {}, }); }); @@ -265,8 +258,7 @@ describe('MlUrlGenerator', () => { expect(location).toMatchObject({ app: 'ml', - path: - '/data_frame_analytics/exploration?_g=(ml:(analysisType:regression,jobId:grid_regression_1))', + path: '/data_frame_analytics/exploration?_g=(ml:(analysisType:regression,jobId:grid_regression_1))', state: {}, }); }); @@ -326,8 +318,7 @@ describe('MlUrlGenerator', () => { expect(location).toMatchObject({ app: 'ml', - path: - '/jobs/new_job/datavisualizer?index=3da93760-e0af-11ea-9ad3-3bcfc330e42a&_g=(time:(from:now-30m,to:now))', + path: '/jobs/new_job/datavisualizer?index=3da93760-e0af-11ea-9ad3-3bcfc330e42a&_g=(time:(from:now-30m,to:now))', state: {}, }); }); diff --git a/x-pack/plugins/ml/public/mocks.ts b/x-pack/plugins/ml/public/mocks.ts index c48e56974f488..6dc2253c94000 100644 --- a/x-pack/plugins/ml/public/mocks.ts +++ b/x-pack/plugins/ml/public/mocks.ts @@ -6,33 +6,17 @@ */ import { MlPluginSetup, MlPluginStart } from './plugin'; +import { sharePluginMock } from '../../../../src/plugins/share/public/mocks'; + const createSetupContract = (): jest.Mocked => { return { - locator: { - getLocation: jest.fn(), - getUrl: jest.fn(), - useUrl: jest.fn(), - navigate: jest.fn(), - extract: jest.fn(), - inject: jest.fn(), - telemetry: jest.fn(), - migrations: {}, - }, + locator: sharePluginMock.createLocator(), }; }; const createStartContract = (): jest.Mocked => { return { - locator: { - getLocation: jest.fn(), - getUrl: jest.fn(), - useUrl: jest.fn(), - navigate: jest.fn(), - extract: jest.fn(), - inject: jest.fn(), - telemetry: jest.fn(), - migrations: {}, - }, + locator: sharePluginMock.createLocator(), }; }; diff --git a/x-pack/plugins/ml/public/plugin.ts b/x-pack/plugins/ml/public/plugin.ts index 3316bdd5011ef..60767ecc4c43e 100644 --- a/x-pack/plugins/ml/public/plugin.ts +++ b/x-pack/plugins/ml/public/plugin.ts @@ -151,12 +151,8 @@ export class MlPlugin implements Plugin { // register various ML plugin features which require a full license // note including registerFeature in register_helper would cause the page bundle size to increase significantly - const { - registerEmbeddables, - registerMlUiActions, - registerSearchLinks, - registerMlAlerts, - } = await import('./register_helper'); + const { registerEmbeddables, registerMlUiActions, registerSearchLinks, registerMlAlerts } = + await import('./register_helper'); const mlEnabled = isMlEnabled(license); const fullLicense = isFullLicense(license); diff --git a/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts b/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts index e4c1e0fe53f01..1d6c635231b6e 100644 --- a/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts +++ b/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts @@ -389,24 +389,27 @@ export function alertingServiceProvider(mlClient: MlClient, datafeedsService: Da const formatter = getResultsFormatter(params.resultType, !!previewTimeInterval); - return (previewTimeInterval - ? (result as { - alerts_over_time: { - buckets: Array< - { - doc_count: number; - key: number; - key_as_string: string; - } & AggResultsResponse - >; - }; - }).alerts_over_time.buckets - // Filter out empty buckets - .filter((v) => v.doc_count > 0 && v[resultsLabel.aggGroupLabel].doc_count > 0) - // Map response - .map(formatter) - : // @ts-expect-error - [formatter(result as AggResultsResponse)] + return ( + previewTimeInterval + ? ( + result as { + alerts_over_time: { + buckets: Array< + { + doc_count: number; + key: number; + key_as_string: string; + } & AggResultsResponse + >; + }; + } + ).alerts_over_time.buckets + // Filter out empty buckets + .filter((v) => v.doc_count > 0 && v[resultsLabel.aggGroupLabel].doc_count > 0) + // Map response + .map(formatter) + : // @ts-expect-error + [formatter(result as AggResultsResponse)] ).filter(isDefined); }; @@ -584,8 +587,8 @@ export function alertingServiceProvider(mlClient: MlClient, datafeedsService: Da should: [ { match_phrase: { - [r.topInfluencers![0].influencer_field_name]: r.topInfluencers![0] - .influencer_field_value, + [r.topInfluencers![0].influencer_field_name]: + r.topInfluencers![0].influencer_field_value, }, }, ], diff --git a/x-pack/plugins/ml/server/lib/alerts/jobs_health_service.test.ts b/x-pack/plugins/ml/server/lib/alerts/jobs_health_service.test.ts index b9d36968c9798..2790ce423c1e7 100644 --- a/x-pack/plugins/ml/server/lib/alerts/jobs_health_service.test.ts +++ b/x-pack/plugins/ml/server/lib/alerts/jobs_health_service.test.ts @@ -21,7 +21,7 @@ const MOCK_DATE_NOW = 1487076708000; function getDefaultExecutorOptions( overrides: DeepPartial = {} ): JobsHealthExecutorOptions { - return ({ + return { state: {}, startedAt: new Date('2021-08-12T13:13:39.396Z'), previousStartedAt: new Date('2021-08-12T13:13:27.396Z'), @@ -42,37 +42,37 @@ function getDefaultExecutorOptions( schedule: { interval: '10s' }, }, ...overrides, - } as unknown) as JobsHealthExecutorOptions; + } as unknown as JobsHealthExecutorOptions; } describe('JobsHealthService', () => { - const mlClient = ({ + const mlClient = { getJobs: jest.fn().mockImplementation(({ job_id: jobIds = [] }) => { let jobs: MlJob[] = []; if (jobIds.some((v: string) => v === 'test_group')) { jobs = [ - ({ + { job_id: 'test_job_01', analysis_config: { bucket_span: '1h' }, - } as unknown) as MlJob, - ({ + } as unknown as MlJob, + { job_id: 'test_job_02', analysis_config: { bucket_span: '15m' }, - } as unknown) as MlJob, - ({ + } as unknown as MlJob, + { job_id: 'test_job_03', analysis_config: { bucket_span: '8m' }, - } as unknown) as MlJob, + } as unknown as MlJob, ]; } if (jobIds[0]?.startsWith('test_job_')) { jobs = [ - ({ + { job_id: jobIds[0], analysis_config: { bucket_span: '1h' }, - } as unknown) as MlJob, + } as unknown as MlJob, ]; } @@ -119,9 +119,9 @@ describe('JobsHealthService', () => { }, }); }), - } as unknown) as jest.Mocked; + } as unknown as jest.Mocked; - const datafeedsService = ({ + const datafeedsService = { getDatafeedByJobId: jest.fn().mockImplementation((jobIds: string[]) => { return Promise.resolve( jobIds.map((j) => { @@ -133,9 +133,9 @@ describe('JobsHealthService', () => { }) ); }), - } as unknown) as jest.Mocked; + } as unknown as jest.Mocked; - const annotationService = ({ + const annotationService = { getDelayedDataAnnotations: jest.fn().mockImplementation(({ jobIds }: { jobIds: string[] }) => { return Promise.resolve( jobIds.map((jobId) => { @@ -150,19 +150,19 @@ describe('JobsHealthService', () => { }) ); }), - } as unknown) as jest.Mocked; + } as unknown as jest.Mocked; - const jobAuditMessagesService = ({ + const jobAuditMessagesService = { getJobsErrorMessages: jest.fn().mockImplementation((jobIds: string) => { return Promise.resolve([]); }), - } as unknown) as jest.Mocked; + } as unknown as jest.Mocked; - const logger = ({ + const logger = { warn: jest.fn(), info: jest.fn(), debug: jest.fn(), - } as unknown) as jest.Mocked; + } as unknown as jest.Mocked; const getFieldsFormatRegistry = jest.fn().mockImplementation(() => { return Promise.resolve({ diff --git a/x-pack/plugins/ml/server/lib/alerts/jobs_health_service.ts b/x-pack/plugins/ml/server/lib/alerts/jobs_health_service.ts index 4e881f3daf46a..70a8e4a777b69 100644 --- a/x-pack/plugins/ml/server/lib/alerts/jobs_health_service.ts +++ b/x-pack/plugins/ml/server/lib/alerts/jobs_health_service.ts @@ -352,14 +352,10 @@ export function jobsHealthServiceProvider( 'memory_status' ); - const { - count: hardLimitCount, - jobsString: hardLimitJobsString, - } = getJobsAlertingMessageValues(hardLimitJobs); - const { - count: softLimitCount, - jobsString: softLimitJobsString, - } = getJobsAlertingMessageValues(softLimitJobs); + const { count: hardLimitCount, jobsString: hardLimitJobsString } = + getJobsAlertingMessageValues(hardLimitJobs); + const { count: softLimitCount, jobsString: softLimitJobsString } = + getJobsAlertingMessageValues(softLimitJobs); let message = ''; diff --git a/x-pack/plugins/ml/server/lib/capabilities/check_capabilities.test.ts b/x-pack/plugins/ml/server/lib/capabilities/check_capabilities.test.ts index 452bb803997d7..920e1f703422d 100644 --- a/x-pack/plugins/ml/server/lib/capabilities/check_capabilities.test.ts +++ b/x-pack/plugins/ml/server/lib/capabilities/check_capabilities.test.ts @@ -24,21 +24,21 @@ const mlLicenseBasic = { const mlIsEnabled = async () => true; const mlIsNotEnabled = async () => false; -const mlClientNonUpgrade = ({ +const mlClientNonUpgrade = { info: async () => ({ body: { upgrade_mode: false, }, }), -} as unknown) as MlClient; +} as unknown as MlClient; -const mlClientUpgrade = ({ +const mlClientUpgrade = { info: async () => ({ body: { upgrade_mode: true, }, }), -} as unknown) as MlClient; +} as unknown as MlClient; describe('check_capabilities', () => { describe('getCapabilities() - right number of capabilities', () => { @@ -63,12 +63,8 @@ describe('check_capabilities', () => { mlLicense, mlIsEnabled ); - const { - capabilities, - upgradeInProgress, - mlFeatureEnabledInSpace, - isPlatinumOrTrialLicense, - } = await getCapabilities(); + const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace, isPlatinumOrTrialLicense } = + await getCapabilities(); expect(upgradeInProgress).toBe(false); expect(mlFeatureEnabledInSpace).toBe(true); expect(isPlatinumOrTrialLicense).toBe(true); @@ -114,12 +110,8 @@ describe('check_capabilities', () => { mlLicense, mlIsEnabled ); - const { - capabilities, - upgradeInProgress, - mlFeatureEnabledInSpace, - isPlatinumOrTrialLicense, - } = await getCapabilities(); + const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace, isPlatinumOrTrialLicense } = + await getCapabilities(); expect(upgradeInProgress).toBe(false); expect(mlFeatureEnabledInSpace).toBe(true); expect(isPlatinumOrTrialLicense).toBe(true); @@ -163,12 +155,8 @@ describe('check_capabilities', () => { mlLicense, mlIsEnabled ); - const { - capabilities, - upgradeInProgress, - mlFeatureEnabledInSpace, - isPlatinumOrTrialLicense, - } = await getCapabilities(); + const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace, isPlatinumOrTrialLicense } = + await getCapabilities(); expect(upgradeInProgress).toBe(true); expect(mlFeatureEnabledInSpace).toBe(true); expect(isPlatinumOrTrialLicense).toBe(true); @@ -212,12 +200,8 @@ describe('check_capabilities', () => { mlLicense, mlIsEnabled ); - const { - capabilities, - upgradeInProgress, - mlFeatureEnabledInSpace, - isPlatinumOrTrialLicense, - } = await getCapabilities(); + const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace, isPlatinumOrTrialLicense } = + await getCapabilities(); expect(upgradeInProgress).toBe(true); expect(mlFeatureEnabledInSpace).toBe(true); expect(isPlatinumOrTrialLicense).toBe(true); @@ -261,12 +245,8 @@ describe('check_capabilities', () => { mlLicense, mlIsNotEnabled ); - const { - capabilities, - upgradeInProgress, - mlFeatureEnabledInSpace, - isPlatinumOrTrialLicense, - } = await getCapabilities(); + const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace, isPlatinumOrTrialLicense } = + await getCapabilities(); expect(upgradeInProgress).toBe(false); expect(mlFeatureEnabledInSpace).toBe(false); expect(isPlatinumOrTrialLicense).toBe(true); @@ -311,12 +291,8 @@ describe('check_capabilities', () => { mlLicenseBasic, mlIsNotEnabled ); - const { - capabilities, - upgradeInProgress, - mlFeatureEnabledInSpace, - isPlatinumOrTrialLicense, - } = await getCapabilities(); + const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace, isPlatinumOrTrialLicense } = + await getCapabilities(); expect(upgradeInProgress).toBe(false); expect(mlFeatureEnabledInSpace).toBe(false); diff --git a/x-pack/plugins/ml/server/lib/ml_client/ml_client.ts b/x-pack/plugins/ml/server/lib/ml_client/ml_client.ts index 3829c975d057d..8fa8f71e82d81 100644 --- a/x-pack/plugins/ml/server/lib/ml_client/ml_client.ts +++ b/x-pack/plugins/ml/server/lib/ml_client/ml_client.ts @@ -258,7 +258,7 @@ export function getMlClient( // this should use DataFrameAnalyticsStats, but needs a refactor to move DataFrameAnalyticsStats to common await jobIdsCheck('data-frame-analytics', p, true); try { - const { body } = ((await mlClient.getDataFrameAnalyticsStats(...p)) as unknown) as { + const { body } = (await mlClient.getDataFrameAnalyticsStats(...p)) as unknown as { body: { data_frame_analytics: DataFrameAnalyticsConfig[] }; }; const jobs = await jobSavedObjectService.filterJobsForSpace( diff --git a/x-pack/plugins/ml/server/lib/sample_data_sets/sample_data_sets.ts b/x-pack/plugins/ml/server/lib/sample_data_sets/sample_data_sets.ts index 7c2fe29885c6e..91c4327f953ff 100644 --- a/x-pack/plugins/ml/server/lib/sample_data_sets/sample_data_sets.ts +++ b/x-pack/plugins/ml/server/lib/sample_data_sets/sample_data_sets.ts @@ -18,8 +18,7 @@ export function initSampleDataSets(mlLicense: MlLicense, plugins: PluginsSetup) addAppLinksToSampleDataset('ecommerce', [ { - path: - '/app/ml/modules/check_view_or_create?id=sample_data_ecommerce&index=ff959d40-b880-11e8-a6d9-e546fe2bba5f', + path: '/app/ml/modules/check_view_or_create?id=sample_data_ecommerce&index=ff959d40-b880-11e8-a6d9-e546fe2bba5f', label: sampleDataLinkLabel, icon: 'machineLearningApp', }, @@ -27,8 +26,7 @@ export function initSampleDataSets(mlLicense: MlLicense, plugins: PluginsSetup) addAppLinksToSampleDataset('logs', [ { - path: - '/app/ml/modules/check_view_or_create?id=sample_data_weblogs&index=90943e30-9a47-11e8-b64d-95841ca0b247', + path: '/app/ml/modules/check_view_or_create?id=sample_data_weblogs&index=90943e30-9a47-11e8-b64d-95841ca0b247', label: sampleDataLinkLabel, icon: 'machineLearningApp', }, diff --git a/x-pack/plugins/ml/server/mocks.ts b/x-pack/plugins/ml/server/mocks.ts index e50f78a0fd99d..93a2180efa4ca 100644 --- a/x-pack/plugins/ml/server/mocks.ts +++ b/x-pack/plugins/ml/server/mocks.ts @@ -13,14 +13,14 @@ import { createAlertingServiceProviderMock } from './shared_services/providers/_ import { MlPluginSetup } from './plugin'; const createSetupContract = () => - (({ + ({ jobServiceProvider: createJobServiceProviderMock(), anomalyDetectorsProvider: createAnomalyDetectorsProviderMock(), mlSystemProvider: createMockMlSystemProvider(), modulesProvider: createModulesProviderMock(), resultsServiceProvider: createResultsServiceProviderMock(), alertingServiceProvider: createAlertingServiceProviderMock(), - } as unknown) as jest.Mocked); + } as unknown as jest.Mocked); const createStartContract = () => jest.fn(); diff --git a/x-pack/plugins/ml/server/models/annotation_service/annotation.ts b/x-pack/plugins/ml/server/models/annotation_service/annotation.ts index 9552735a57d35..c6ed72de18d05 100644 --- a/x-pack/plugins/ml/server/models/annotation_service/annotation.ts +++ b/x-pack/plugins/ml/server/models/annotation_service/annotation.ts @@ -393,11 +393,13 @@ export function annotationProvider({ asInternalUser }: IScopedClusterClient) { const { body } = await asInternalUser.search(params); - const annotations = (body.aggregations!.by_job as estypes.AggregationsTermsAggregate<{ - key: string; - doc_count: number; - latest_delayed: Pick, 'hits'>; - }>).buckets.map((bucket) => { + const annotations = ( + body.aggregations!.by_job as estypes.AggregationsTermsAggregate<{ + key: string; + doc_count: number; + latest_delayed: Pick, 'hits'>; + }> + ).buckets.map((bucket) => { return bucket.latest_delayed.hits.hits[0]._source!; }); diff --git a/x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.test.ts b/x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.test.ts index de5514ed1e18f..6ffb74131bf6e 100644 --- a/x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.test.ts +++ b/x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.test.ts @@ -12,15 +12,19 @@ import { BucketSpanEstimatorData } from '../../../common/types/job_service'; import { estimateBucketSpanFactory } from './bucket_span_estimator'; +jest.mock('../../lib/log', () => ({ + mlLog: { warn: jest.fn() }, +})); + const callAs = { search: () => Promise.resolve({ body: {} }), cluster: { getSettings: () => Promise.resolve({ body: {} }) }, }; -const mlClusterClient = ({ +const mlClusterClient = { asCurrentUser: callAs, asInternalUser: callAs, -} as unknown) as IScopedClusterClient; +} as unknown as IScopedClusterClient; // mock configuration to be passed to the estimator const formConfig: BucketSpanEstimatorData = { diff --git a/x-pack/plugins/ml/server/models/calculate_model_memory_limit/calculate_model_memory_limit.ts b/x-pack/plugins/ml/server/models/calculate_model_memory_limit/calculate_model_memory_limit.ts index 1cefa48cf6c8c..760faa8d530fc 100644 --- a/x-pack/plugins/ml/server/models/calculate_model_memory_limit/calculate_model_memory_limit.ts +++ b/x-pack/plugins/ml/server/models/calculate_model_memory_limit/calculate_model_memory_limit.ts @@ -154,7 +154,8 @@ export function calculateModelMemoryLimitProvider( ): Promise { const { body: info } = await mlClient.info(); const maxModelMemoryLimit = info.limits.max_model_memory_limit?.toUpperCase(); - const effectiveMaxModelMemoryLimit = info.limits.effective_max_model_memory_limit?.toUpperCase(); + const effectiveMaxModelMemoryLimit = + info.limits.effective_max_model_memory_limit?.toUpperCase(); const { overallCardinality, maxBucketCardinality } = await getCardinalities( analysisConfig, diff --git a/x-pack/plugins/ml/server/models/data_frame_analytics/analytics_manager.ts b/x-pack/plugins/ml/server/models/data_frame_analytics/analytics_manager.ts index 41b8e6dfe96c4..dc39a8005b99e 100644 --- a/x-pack/plugins/ml/server/models/data_frame_analytics/analytics_manager.ts +++ b/x-pack/plugins/ml/server/models/data_frame_analytics/analytics_manager.ts @@ -205,9 +205,7 @@ export class AnalyticsManager { } } - private getAnalyticsModelElements( - analyticsId: string - ): { + private getAnalyticsModelElements(analyticsId: string): { modelElement?: AnalyticsMapNodeElement; modelDetails?: any; edgeElement?: AnalyticsMapEdgeElement; @@ -623,9 +621,8 @@ export class AnalyticsManager { rootIndexNodeId = `${rootIndex}-${JOB_MAP_NODE_TYPES.INDEX}`; // Fetch inference model for incoming job id and add node and edge - const { modelElement, modelDetails, edgeElement } = this.getAnalyticsModelElements( - analyticsId - ); + const { modelElement, modelDetails, edgeElement } = + this.getAnalyticsModelElements(analyticsId); if (isAnalyticsMapNodeElement(modelElement)) { result.elements.push(modelElement); result.details[modelElement.data.id] = modelDetails; diff --git a/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.test.ts b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.test.ts index fd7bdfbe1c5c8..8ddb805af2033 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.test.ts +++ b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.test.ts @@ -13,21 +13,21 @@ import { JobSavedObjectService } from '../../saved_objects'; const callAs = () => Promise.resolve({ body: {} }); -const mlClusterClient = ({ +const mlClusterClient = { asCurrentUser: callAs, asInternalUser: callAs, -} as unknown) as IScopedClusterClient; +} as unknown as IScopedClusterClient; -const mlClient = (callAs as unknown) as MlClient; +const mlClient = callAs as unknown as MlClient; describe('ML - data recognizer', () => { const dr = new DataRecognizer( mlClusterClient, mlClient, - ({ + { find: jest.fn(), bulkCreate: jest.fn(), - } as unknown) as SavedObjectsClientContract, + } as unknown as SavedObjectsClientContract, {} as JobSavedObjectService, { headers: { authorization: '' } } as KibanaRequest ); @@ -37,7 +37,7 @@ describe('ML - data recognizer', () => { // arrange const prefix = 'pre-'; const testJobId = 'test-job'; - const moduleConfig = ({ + const moduleConfig = { jobs: [ { id: `${prefix}${testJobId}`, @@ -54,7 +54,7 @@ describe('ML - data recognizer', () => { }, }, ], - } as unknown) as Module; + } as unknown as Module; const jobOverrides = [ { analysis_limits: { diff --git a/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts index 803bd0ae4cb3a..f9c609803217e 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts +++ b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts @@ -589,9 +589,8 @@ export class DataRecognizer { const jobStatsJobs: JobStat[] = []; if (body.jobs && body.jobs.length > 0) { const foundJobIds = body.jobs.map((job) => job.job_id); - const latestBucketTimestampsByJob = await this._resultsService.getLatestBucketTimestampByJob( - foundJobIds - ); + const latestBucketTimestampsByJob = + await this._resultsService.getLatestBucketTimestampByJob(foundJobIds); body.jobs.forEach((job) => { const jobStat = { @@ -1121,8 +1120,8 @@ export class DataRecognizer { if (estimateMML && this._jobsForModelMemoryEstimation.length > 0) { try { // Checks if all jobs in the module have the same time field configured - const firstJobTimeField = this._jobsForModelMemoryEstimation[0].job.config.data_description - .time_field; + const firstJobTimeField = + this._jobsForModelMemoryEstimation[0].job.config.data_description.time_field; const isSameTimeFields = this._jobsForModelMemoryEstimation.every( ({ job }) => job.config.data_description.time_field === firstJobTimeField ); @@ -1131,10 +1130,10 @@ export class DataRecognizer { // In case of time range is not provided and the time field is the same // set the fallback range for all jobs // as there may not be a common query, we use a match_all - const { - start: fallbackStart, - end: fallbackEnd, - } = await this._getFallbackTimeRange(firstJobTimeField, { match_all: {} }); + const { start: fallbackStart, end: fallbackEnd } = await this._getFallbackTimeRange( + firstJobTimeField, + { match_all: {} } + ); start = fallbackStart; end = fallbackEnd; } diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_high_count_logon_events_for_a_source_ip.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_high_count_logon_events_for_a_source_ip.json index 0fe4b7805d077..cdf219152c7fd 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_high_count_logon_events_for_a_source_ip.json +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_auth/ml/auth_high_count_logon_events_for_a_source_ip.json @@ -1,6 +1,6 @@ { "job_type": "anomaly_detector", - "description": "Security: Authentication - looks for an unusually large spike in successful authentication events events from a particular source IP address. This can be due to password spraying, user enumeration or brute force activity.", + "description": "Security: Authentication - looks for an unusually large spike in successful authentication events from a particular source IP address. This can be due to password spraying, user enumeration or brute force activity.", "groups": [ "security", "authentication" diff --git a/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts b/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts index 8baa7a5d78c6a..e58b849cddfac 100644 --- a/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts +++ b/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts @@ -304,46 +304,44 @@ export const getHistogramsForFields = async ( const aggsPath = getSamplerAggregationsResponsePath(samplerShardSize); const aggregations = aggsPath.length > 0 ? get(body.aggregations, aggsPath) : body.aggregations; - const chartsData: ChartData[] = fields.map( - (field): ChartData => { - const fieldName = field.fieldName; - const fieldType = field.type; - const id = stringHash(field.fieldName); - - if (fieldType === KBN_FIELD_TYPES.NUMBER || fieldType === KBN_FIELD_TYPES.DATE) { - if (aggIntervals[id] === undefined) { - return { - type: 'numeric', - data: [], - interval: 0, - stats: [0, 0], - id: fieldName, - }; - } + const chartsData: ChartData[] = fields.map((field): ChartData => { + const fieldName = field.fieldName; + const fieldType = field.type; + const id = stringHash(field.fieldName); + if (fieldType === KBN_FIELD_TYPES.NUMBER || fieldType === KBN_FIELD_TYPES.DATE) { + if (aggIntervals[id] === undefined) { return { - data: aggregations[`${id}_histogram`].buckets, - interval: aggIntervals[id].interval, - stats: [aggIntervals[id].min, aggIntervals[id].max], type: 'numeric', - id: fieldName, - }; - } else if (fieldType === KBN_FIELD_TYPES.STRING || fieldType === KBN_FIELD_TYPES.BOOLEAN) { - return { - type: fieldType === KBN_FIELD_TYPES.STRING ? 'ordinal' : 'boolean', - cardinality: - fieldType === KBN_FIELD_TYPES.STRING ? aggregations[`${id}_cardinality`].value : 2, - data: aggregations[`${id}_terms`].buckets, + data: [], + interval: 0, + stats: [0, 0], id: fieldName, }; } return { - type: 'unsupported', + data: aggregations[`${id}_histogram`].buckets, + interval: aggIntervals[id].interval, + stats: [aggIntervals[id].min, aggIntervals[id].max], + type: 'numeric', + id: fieldName, + }; + } else if (fieldType === KBN_FIELD_TYPES.STRING || fieldType === KBN_FIELD_TYPES.BOOLEAN) { + return { + type: fieldType === KBN_FIELD_TYPES.STRING ? 'ordinal' : 'boolean', + cardinality: + fieldType === KBN_FIELD_TYPES.STRING ? aggregations[`${id}_cardinality`].value : 2, + data: aggregations[`${id}_terms`].buckets, id: fieldName, }; } - ); + + return { + type: 'unsupported', + id: fieldName, + }; + }); return chartsData; }; diff --git a/x-pack/plugins/ml/server/models/job_service/new_job/categorization/examples.ts b/x-pack/plugins/ml/server/models/job_service/new_job/categorization/examples.ts index abda62097dfb7..4f87e4698c032 100644 --- a/x-pack/plugins/ml/server/models/job_service/new_job/categorization/examples.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job/categorization/examples.ts @@ -136,7 +136,12 @@ export function categorizationExamplesProvider({ }); const lengths = examples.map((e) => e.length); - const sumLengths = lengths.map(((s) => (a: number) => (s += a))(0)); + const sumLengths = lengths.map( + ( + (s) => (a: number) => + (s += a) + )(0) + ); const tokensPerExample: Token[][] = examples.map((e) => []); diff --git a/x-pack/plugins/ml/server/models/job_service/new_job_caps/field_service.ts b/x-pack/plugins/ml/server/models/job_service/new_job_caps/field_service.ts index fad388f791f08..b838165826da1 100644 --- a/x-pack/plugins/ml/server/models/job_service/new_job_caps/field_service.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job_caps/field_service.ts @@ -113,9 +113,8 @@ class FieldsService { this._mlClusterClient, this._savedObjectsClient ); - const rollupConfigs: - | estypes.RollupGetRollupCapabilitiesRollupCapabilitySummary[] - | null = await rollupService.getRollupJobs(); + const rollupConfigs: estypes.RollupGetRollupCapabilitiesRollupCapabilitySummary[] | null = + await rollupService.getRollupJobs(); // if a rollup index has been specified, yet there are no // rollup configs, return with no results diff --git a/x-pack/plugins/ml/server/models/job_validation/job_validation.test.ts b/x-pack/plugins/ml/server/models/job_validation/job_validation.test.ts index e890020eb726d..83140d9655d38 100644 --- a/x-pack/plugins/ml/server/models/job_validation/job_validation.test.ts +++ b/x-pack/plugins/ml/server/models/job_validation/job_validation.test.ts @@ -22,12 +22,12 @@ const callAs = { const authHeader: AuthorizationHeader = {}; -const mlClusterClient = ({ +const mlClusterClient = { asCurrentUser: callAs, asInternalUser: callAs, -} as unknown) as IScopedClusterClient; +} as unknown as IScopedClusterClient; -const mlClient = ({ +const mlClient = { info: () => Promise.resolve({ body: { @@ -38,16 +38,16 @@ const mlClient = ({ }, }), previewDatafeed: () => Promise.resolve({ body: [{}] }), -} as unknown) as MlClient; +} as unknown as MlClient; // Note: The tests cast `payload` as any // so we can simulate possible runtime payloads // that don't satisfy the TypeScript specs. describe('ML - validateJob', () => { it('basic validation messages', async () => { - const payload = ({ + const payload = { job: { analysis_config: { detectors: [] } }, - } as unknown) as ValidateJobPayload; + } as unknown as ValidateJobPayload; return validateJob(mlClusterClient, mlClient, payload, authHeader).then((messages) => { const ids = messages.map((m) => m.id); @@ -63,12 +63,12 @@ describe('ML - validateJob', () => { const jobIdTests = (testIds: string[], messageId: string) => { const promises = testIds.map(async (id) => { - const payload = ({ + const payload = { job: { analysis_config: { detectors: [] }, job_id: id, }, - } as unknown) as ValidateJobPayload; + } as unknown as ValidateJobPayload; return validateJob(mlClusterClient, mlClient, payload, authHeader).catch(() => { new Error('Promise should not fail for jobIdTests.'); }); @@ -86,9 +86,9 @@ describe('ML - validateJob', () => { }; const jobGroupIdTest = (testIds: string[], messageId: string) => { - const payload = ({ + const payload = { job: { analysis_config: { detectors: [] }, groups: testIds }, - } as unknown) as ValidateJobPayload; + } as unknown as ValidateJobPayload; return validateJob(mlClusterClient, mlClient, payload, authHeader).then((messages) => { const ids = messages.map((m) => m.id); @@ -127,9 +127,9 @@ describe('ML - validateJob', () => { const bucketSpanFormatTests = (testFormats: string[], messageId: string) => { const promises = testFormats.map((format) => { - const payload = ({ + const payload = { job: { analysis_config: { bucket_span: format, detectors: [] } }, - } as unknown) as ValidateJobPayload; + } as unknown as ValidateJobPayload; return validateJob(mlClusterClient, mlClient, payload, authHeader).catch(() => { new Error('Promise should not fail for bucketSpanFormatTests.'); }); @@ -155,9 +155,9 @@ describe('ML - validateJob', () => { }); it('at least one detector function is empty', async () => { - const payload = ({ + const payload = { job: { analysis_config: { detectors: [] as Array<{ function?: string }> } }, - } as unknown) as ValidateJobPayload; + } as unknown as ValidateJobPayload; payload.job.analysis_config.detectors.push({ function: 'count', }); @@ -176,9 +176,9 @@ describe('ML - validateJob', () => { }); it('detector function is not empty', async () => { - const payload = ({ + const payload = { job: { analysis_config: { detectors: [] as Array<{ function?: string }> } }, - } as unknown) as ValidateJobPayload; + } as unknown as ValidateJobPayload; payload.job.analysis_config.detectors.push({ function: 'count', }); @@ -190,10 +190,10 @@ describe('ML - validateJob', () => { }); it('invalid index fields', async () => { - const payload = ({ + const payload = { job: { analysis_config: { detectors: [] } }, fields: {}, - } as unknown) as ValidateJobPayload; + } as unknown as ValidateJobPayload; return validateJob(mlClusterClient, mlClient, payload, authHeader).then((messages) => { const ids = messages.map((m) => m.id); @@ -202,10 +202,10 @@ describe('ML - validateJob', () => { }); it('valid index fields', async () => { - const payload = ({ + const payload = { job: { analysis_config: { detectors: [] } }, fields: { testField: {} }, - } as unknown) as ValidateJobPayload; + } as unknown as ValidateJobPayload; return validateJob(mlClusterClient, mlClient, payload, authHeader).then((messages) => { const ids = messages.map((m) => m.id); @@ -426,10 +426,10 @@ describe('ML - validateJob', () => { fields: { testField: {} }, }; - const mlClientEmptyDatafeedPreview = ({ + const mlClientEmptyDatafeedPreview = { ...mlClient, previewDatafeed: () => Promise.resolve({ body: [] }), - } as unknown) as MlClient; + } as unknown as MlClient; return validateJob(mlClusterClient, mlClientEmptyDatafeedPreview, payload, authHeader).then( (messages) => { @@ -468,10 +468,10 @@ describe('ML - validateJob', () => { fields: { testField: {} }, }; - const mlClientEmptyDatafeedPreview = ({ + const mlClientEmptyDatafeedPreview = { ...mlClient, previewDatafeed: () => Promise.reject({}), - } as unknown) as MlClient; + } as unknown as MlClient; return validateJob(mlClusterClient, mlClientEmptyDatafeedPreview, payload, authHeader).then( (messages) => { diff --git a/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.test.ts b/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.test.ts index c49defce76f70..c49768d55047a 100644 --- a/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.test.ts +++ b/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.test.ts @@ -36,10 +36,10 @@ const mlClusterClientFactory = ( fieldCaps: () => Promise.reject({ body: {} }), }; - return ({ + return { asCurrentUser: fail === false ? callAs : callAsFail, asInternalUser: fail === false ? callAs : callAsFail, - } as unknown) as IScopedClusterClient; + } as unknown as IScopedClusterClient; }; describe('ML - validateCardinality', () => { @@ -75,10 +75,10 @@ describe('ML - validateCardinality', () => { }); it('called with non-valid job argument #4, missing data_description', (done) => { - const job = ({ + const job = { analysis_config: {}, datafeed_config: { indices: [] }, - } as unknown) as CombinedJob; + } as unknown as CombinedJob; validateCardinality(mlClusterClientFactory(mockResponses), job).then( () => done(new Error('Promise should not resolve for this test without valid job argument.')), () => done() @@ -86,11 +86,11 @@ describe('ML - validateCardinality', () => { }); it('called with non-valid job argument #5, missing data_description.time_field', (done) => { - const job = ({ + const job = { analysis_config: {}, data_description: {}, datafeed_config: { indices: [] }, - } as unknown) as CombinedJob; + } as unknown as CombinedJob; validateCardinality(mlClusterClientFactory(mockResponses), job).then( () => done(new Error('Promise should not resolve for this test without valid job argument.')), () => done() @@ -98,11 +98,11 @@ describe('ML - validateCardinality', () => { }); it('called with non-valid job argument #6, missing analysis_config.influencers', (done) => { - const job = ({ + const job = { analysis_config: {}, datafeed_config: { indices: [] }, data_description: { time_field: '@timestamp' }, - } as unknown) as CombinedJob; + } as unknown as CombinedJob; validateCardinality(mlClusterClientFactory(mockResponses), job).then( () => done(new Error('Promise should not resolve for this test without valid job argument.')), () => done() @@ -110,13 +110,13 @@ describe('ML - validateCardinality', () => { }); it('minimum job configuration to pass cardinality check code', () => { - const job = ({ + const job = { analysis_config: { detectors: [], influencers: [] }, data_description: { time_field: '@timestamp' }, datafeed_config: { indices: [], }, - } as unknown) as CombinedJob; + } as unknown as CombinedJob; return validateCardinality(mlClusterClientFactory(mockResponses), job).then((messages) => { const ids = messages.map((m) => m.id); @@ -150,7 +150,7 @@ describe('ML - validateCardinality', () => { mockCardinality.search.aggregations.airline_cardinality.value = cardinality; return validateCardinality( mlClusterClientFactory(mockCardinality), - (job as unknown) as CombinedJob + job as unknown as CombinedJob ).then((messages) => { const ids = messages.map((m) => m.id); test(ids); @@ -162,7 +162,7 @@ describe('ML - validateCardinality', () => { job.analysis_config.detectors[0].partition_field_name = '_source'; return validateCardinality( mlClusterClientFactory(mockResponses), - (job as unknown) as CombinedJob + job as unknown as CombinedJob ).then((messages) => { const ids = messages.map((m) => m.id); expect(ids).toStrictEqual(['field_not_aggregatable']); @@ -173,7 +173,7 @@ describe('ML - validateCardinality', () => { const job = getJobConfig('partition_field_name'); return validateCardinality( mlClusterClientFactory(mockResponses), - (job as unknown) as CombinedJob + job as unknown as CombinedJob ).then((messages) => { const ids = messages.map((m) => m.id); expect(ids).toStrictEqual(['success_cardinality']); @@ -182,7 +182,7 @@ describe('ML - validateCardinality', () => { it('field not aggregatable', () => { const job = getJobConfig('partition_field_name'); - return validateCardinality(mlClusterClientFactory({}), (job as unknown) as CombinedJob).then( + return validateCardinality(mlClusterClientFactory({}), job as unknown as CombinedJob).then( (messages) => { const ids = messages.map((m) => m.id); expect(ids).toStrictEqual(['field_not_aggregatable']); @@ -199,7 +199,7 @@ describe('ML - validateCardinality', () => { return validateCardinality( mlClusterClientFactory(mockCardinality), - (job as unknown) as CombinedJob + job as unknown as CombinedJob ).then((messages) => { const ids = messages.map((m) => m.id); expect(ids).toStrictEqual(['cardinality_no_results']); @@ -215,7 +215,7 @@ describe('ML - validateCardinality', () => { return validateCardinality( mlClusterClientFactory(mockCardinality), - (job as unknown) as CombinedJob + job as unknown as CombinedJob ).then((messages) => { const ids = messages.map((m) => m.id); expect(ids).toStrictEqual(['cardinality_field_not_exists']); @@ -230,7 +230,7 @@ describe('ML - validateCardinality', () => { }); return validateCardinality( mlClusterClientFactory({}, true), - (job as unknown) as CombinedJob + job as unknown as CombinedJob ).then((messages) => { const ids = messages.map((m) => m.id); expect(ids).toStrictEqual(['fields_not_aggregatable']); @@ -281,7 +281,7 @@ describe('ML - validateCardinality', () => { const cardinality = 10000; it(`disabled model_plot, over field cardinality of ${cardinality} doesn't trigger a warning`, () => { - const job = (getJobConfig('over_field_name') as unknown) as CombinedJob; + const job = getJobConfig('over_field_name') as unknown as CombinedJob; job.model_plot_config = { enabled: false }; const mockCardinality = cloneDeep(mockResponses); mockCardinality.search.aggregations.airline_cardinality.value = cardinality; @@ -292,7 +292,7 @@ describe('ML - validateCardinality', () => { }); it(`enabled model_plot, over field cardinality of ${cardinality} triggers a model plot warning`, () => { - const job = (getJobConfig('over_field_name') as unknown) as CombinedJob; + const job = getJobConfig('over_field_name') as unknown as CombinedJob; job.model_plot_config = { enabled: true }; const mockCardinality = cloneDeep(mockResponses); mockCardinality.search.aggregations.airline_cardinality.value = cardinality; @@ -303,7 +303,7 @@ describe('ML - validateCardinality', () => { }); it(`disabled model_plot, by field cardinality of ${cardinality} triggers a field cardinality warning`, () => { - const job = (getJobConfig('by_field_name') as unknown) as CombinedJob; + const job = getJobConfig('by_field_name') as unknown as CombinedJob; job.model_plot_config = { enabled: false }; const mockCardinality = cloneDeep(mockResponses); mockCardinality.search.aggregations.airline_cardinality.value = cardinality; @@ -314,7 +314,7 @@ describe('ML - validateCardinality', () => { }); it(`enabled model_plot, by field cardinality of ${cardinality} triggers a model plot warning and field cardinality warning`, () => { - const job = (getJobConfig('by_field_name') as unknown) as CombinedJob; + const job = getJobConfig('by_field_name') as unknown as CombinedJob; job.model_plot_config = { enabled: true }; const mockCardinality = cloneDeep(mockResponses); mockCardinality.search.aggregations.airline_cardinality.value = cardinality; @@ -325,7 +325,7 @@ describe('ML - validateCardinality', () => { }); it(`enabled model_plot with terms, by field cardinality of ${cardinality} triggers just field cardinality warning`, () => { - const job = (getJobConfig('by_field_name') as unknown) as CombinedJob; + const job = getJobConfig('by_field_name') as unknown as CombinedJob; job.model_plot_config = { enabled: true, terms: 'AAL,AAB' }; const mockCardinality = cloneDeep(mockResponses); mockCardinality.search.aggregations.airline_cardinality.value = cardinality; diff --git a/x-pack/plugins/ml/server/models/job_validation/validate_datafeed_preview.ts b/x-pack/plugins/ml/server/models/job_validation/validate_datafeed_preview.ts index e009dcf49fdab..4ae94229a930b 100644 --- a/x-pack/plugins/ml/server/models/job_validation/validate_datafeed_preview.ts +++ b/x-pack/plugins/ml/server/models/job_validation/validate_datafeed_preview.ts @@ -17,7 +17,7 @@ export async function validateDatafeedPreview( ): Promise { const { datafeed_config: datafeed, ...tempJob } = job; try { - const { body } = ((await mlClient.previewDatafeed( + const { body } = (await mlClient.previewDatafeed( { body: { job_config: tempJob, @@ -26,7 +26,7 @@ export async function validateDatafeedPreview( }, authHeader // previewDatafeed response type is incorrect - )) as unknown) as { body: unknown[] }; + )) as unknown as { body: unknown[] }; if (Array.isArray(body) === false || body.length === 0) { return [{ id: 'datafeed_preview_no_documents' }]; diff --git a/x-pack/plugins/ml/server/models/job_validation/validate_influencers.test.ts b/x-pack/plugins/ml/server/models/job_validation/validate_influencers.test.ts index c53e0b58e06c3..434e5588c1bfa 100644 --- a/x-pack/plugins/ml/server/models/job_validation/validate_influencers.test.ts +++ b/x-pack/plugins/ml/server/models/job_validation/validate_influencers.test.ts @@ -11,14 +11,14 @@ import { validateInfluencers } from './validate_influencers'; describe('ML - validateInfluencers', () => { it('called without arguments throws an error', (done) => { - validateInfluencers((undefined as unknown) as CombinedJob).then( + validateInfluencers(undefined as unknown as CombinedJob).then( () => done(new Error('Promise should not resolve for this test without job argument.')), () => done() ); }); it('called with non-valid job argument #1, missing analysis_config', (done) => { - validateInfluencers(({} as unknown) as CombinedJob).then( + validateInfluencers({} as unknown as CombinedJob).then( () => done(new Error('Promise should not resolve for this test without valid job argument.')), () => done() ); @@ -30,7 +30,7 @@ describe('ML - validateInfluencers', () => { datafeed_config: { indices: [] }, data_description: { time_field: '@timestamp' }, }; - validateInfluencers((job as unknown) as CombinedJob).then( + validateInfluencers(job as unknown as CombinedJob).then( () => done(new Error('Promise should not resolve for this test without valid job argument.')), () => done() ); @@ -42,7 +42,7 @@ describe('ML - validateInfluencers', () => { datafeed_config: { indices: [] }, data_description: { time_field: '@timestamp' }, }; - validateInfluencers((job as unknown) as CombinedJob).then( + validateInfluencers(job as unknown as CombinedJob).then( () => done(new Error('Promise should not resolve for this test without valid job argument.')), () => done() ); @@ -52,13 +52,13 @@ describe('ML - validateInfluencers', () => { influencers?: string[], detectors?: CombinedJob['analysis_config']['detectors'] ) => CombinedJob = (influencers = [], detectors = []) => - (({ + ({ analysis_config: { detectors, influencers }, data_description: { time_field: '@timestamp' }, datafeed_config: { indices: [], }, - } as unknown) as CombinedJob); + } as unknown as CombinedJob); it('success_influencer', () => { const job = getJobConfig(['airline']); diff --git a/x-pack/plugins/ml/server/models/job_validation/validate_model_memory_limit.test.ts b/x-pack/plugins/ml/server/models/job_validation/validate_model_memory_limit.test.ts index 823d4c0adda49..452e12b5a51ba 100644 --- a/x-pack/plugins/ml/server/models/job_validation/validate_model_memory_limit.test.ts +++ b/x-pack/plugins/ml/server/models/job_validation/validate_model_memory_limit.test.ts @@ -86,10 +86,10 @@ describe('ML - validateModelMemoryLimit', () => { fieldCaps: () => Promise.resolve({ body: fieldCapsResponse }), }; - return ({ + return { asCurrentUser: callAs, asInternalUser: callAs, - } as unknown) as IScopedClusterClient; + } as unknown as IScopedClusterClient; }; const getMockMlClient = ({ @@ -105,7 +105,7 @@ describe('ML - validateModelMemoryLimit', () => { }; function getJobConfig(influencers: string[] = [], detectors: Detector[] = []) { - return ({ + return { analysis_config: { detectors, influencers }, data_description: { time_field: '@timestamp' }, datafeed_config: { @@ -114,7 +114,7 @@ describe('ML - validateModelMemoryLimit', () => { analysis_limits: { model_memory_limit: '20mb', }, - } as unknown) as CombinedJob; + } as unknown as CombinedJob; } // create a specified number of mock detectors diff --git a/x-pack/plugins/ml/server/models/job_validation/validate_time_range.test.ts b/x-pack/plugins/ml/server/models/job_validation/validate_time_range.test.ts index 91a98a5338c71..5983604ae576c 100644 --- a/x-pack/plugins/ml/server/models/job_validation/validate_time_range.test.ts +++ b/x-pack/plugins/ml/server/models/job_validation/validate_time_range.test.ts @@ -27,14 +27,14 @@ const mlClusterClientFactory = (response: any): IScopedClusterClient => { fieldCaps: () => Promise.resolve({ body: response.fieldCaps }), search: () => Promise.resolve({ body: response.search }), }; - return ({ + return { asCurrentUser: callAs, asInternalUser: callAs, - } as unknown) as IScopedClusterClient; + } as unknown as IScopedClusterClient; }; function getMinimalValidJob() { - return ({ + return { analysis_config: { bucket_span: '15m', detectors: [], @@ -44,14 +44,14 @@ function getMinimalValidJob() { datafeed_config: { indices: [], }, - } as unknown) as CombinedJob; + } as unknown as CombinedJob; } describe('ML - isValidTimeField', () => { it('called without job config argument triggers Promise rejection', (done) => { isValidTimeField( mlClusterClientFactory(mockSearchResponse), - (undefined as unknown) as CombinedJob + undefined as unknown as CombinedJob ).then( () => done(new Error('Promise should not resolve for this test without job argument.')), () => done() @@ -94,7 +94,7 @@ describe('ML - validateTimeRange', () => { it('called without arguments', (done) => { validateTimeRange( mlClusterClientFactory(mockSearchResponse), - (undefined as unknown) as CombinedJob + undefined as unknown as CombinedJob ).then( () => done(new Error('Promise should not resolve for this test without job argument.')), () => done() @@ -102,9 +102,9 @@ describe('ML - validateTimeRange', () => { }); it('called with non-valid job argument #2, missing datafeed_config', (done) => { - validateTimeRange(mlClusterClientFactory(mockSearchResponse), ({ + validateTimeRange(mlClusterClientFactory(mockSearchResponse), { analysis_config: {}, - } as unknown) as CombinedJob).then( + } as unknown as CombinedJob).then( () => done(new Error('Promise should not resolve for this test without valid job argument.')), () => done() ); @@ -114,7 +114,7 @@ describe('ML - validateTimeRange', () => { const job = { analysis_config: {}, datafeed_config: {} }; validateTimeRange( mlClusterClientFactory(mockSearchResponse), - (job as unknown) as CombinedJob + job as unknown as CombinedJob ).then( () => done(new Error('Promise should not resolve for this test without valid job argument.')), () => done() @@ -125,7 +125,7 @@ describe('ML - validateTimeRange', () => { const job = { analysis_config: {}, datafeed_config: { indices: [] } }; validateTimeRange( mlClusterClientFactory(mockSearchResponse), - (job as unknown) as CombinedJob + job as unknown as CombinedJob ).then( () => done(new Error('Promise should not resolve for this test without valid job argument.')), () => done() @@ -136,7 +136,7 @@ describe('ML - validateTimeRange', () => { const job = { analysis_config: {}, data_description: {}, datafeed_config: { indices: [] } }; validateTimeRange( mlClusterClientFactory(mockSearchResponse), - (job as unknown) as CombinedJob + job as unknown as CombinedJob ).then( () => done(new Error('Promise should not resolve for this test without valid job argument.')), () => done() diff --git a/x-pack/plugins/ml/server/plugin.ts b/x-pack/plugins/ml/server/plugin.ts index 4dea3cc072ca5..3876193cfbe39 100644 --- a/x-pack/plugins/ml/server/plugin.ts +++ b/x-pack/plugins/ml/server/plugin.ts @@ -67,7 +67,8 @@ export type MlPluginSetup = SharedServices; export type MlPluginStart = void; export class MlServerPlugin - implements Plugin { + implements Plugin +{ private log: Logger; private mlLicense: MlLicense; private capabilities: CapabilitiesStart | null = null; diff --git a/x-pack/plugins/ml/server/routes/apidoc_scripts/schema_extractor.ts b/x-pack/plugins/ml/server/routes/apidoc_scripts/schema_extractor.ts index f94aaffc41a7b..2422feaf895a4 100644 --- a/x-pack/plugins/ml/server/routes/apidoc_scripts/schema_extractor.ts +++ b/x-pack/plugins/ml/server/routes/apidoc_scripts/schema_extractor.ts @@ -81,7 +81,7 @@ export function extractDocumentation( * @param type */ function getTypeMembers(type: ts.Type): ts.Symbol[] | undefined { - const argsOfType = checker.getTypeArguments((type as unknown) as ts.TypeReference); + const argsOfType = checker.getTypeArguments(type as unknown as ts.TypeReference); let members = type.getProperties(); @@ -99,7 +99,7 @@ export function extractDocumentation( function resolveTypeProperties(type: ts.Type): ts.Symbol[] { let props = type.getProperties(); - const typeArguments = checker.getTypeArguments((type as unknown) as ts.TypeReference); + const typeArguments = checker.getTypeArguments(type as unknown as ts.TypeReference); if (type.aliasTypeArguments) { // @ts-ignores diff --git a/x-pack/plugins/ml/server/routes/job_service.ts b/x-pack/plugins/ml/server/routes/job_service.ts index 81ef1818383b1..da115a224d19e 100644 --- a/x-pack/plugins/ml/server/routes/job_service.ts +++ b/x-pack/plugins/ml/server/routes/job_service.ts @@ -889,14 +889,8 @@ export function jobServiceRoutes({ router, routeGuard }: RouteInitialization) { routeGuard.fullLicenseAPIGuard(async ({ client, mlClient, request, response }) => { try { const { revertModelSnapshot } = jobServiceProvider(client, mlClient); - const { - jobId, - snapshotId, - replay, - end, - deleteInterveningResults, - calendarEvents, - } = request.body; + const { jobId, snapshotId, replay, end, deleteInterveningResults, calendarEvents } = + request.body; const resp = await revertModelSnapshot( jobId, snapshotId, diff --git a/x-pack/plugins/ml/server/saved_objects/authorization.ts b/x-pack/plugins/ml/server/saved_objects/authorization.ts index 58828056548fe..a012fec8029c6 100644 --- a/x-pack/plugins/ml/server/saved_objects/authorization.ts +++ b/x-pack/plugins/ml/server/saved_objects/authorization.ts @@ -25,9 +25,8 @@ export function authorizationProvider(authorization: SecurityPluginSetup['authz' // If spaces are disabled, then this will check privileges globally instead. // SO, if spaces are disabled, then you don't technically need to perform this check, but I included it here // for completeness. - const checkPrivilegesDynamicallyWithRequest = authorization.checkPrivilegesDynamicallyWithRequest( - request - ); + const checkPrivilegesDynamicallyWithRequest = + authorization.checkPrivilegesDynamicallyWithRequest(request); const createMLJobAuthorizationAction = authorization.actions.savedObject.get( ML_SAVED_OBJECT_TYPE, 'create' diff --git a/x-pack/plugins/ml/server/saved_objects/checks.ts b/x-pack/plugins/ml/server/saved_objects/checks.ts index 48ceaefb23939..de4aca2992ee8 100644 --- a/x-pack/plugins/ml/server/saved_objects/checks.ts +++ b/x-pack/plugins/ml/server/saved_objects/checks.ts @@ -51,11 +51,10 @@ export function checksFactory( // load all non-space jobs and datafeeds const { body: adJobs } = await client.asInternalUser.ml.getJobs(); const { body: datafeeds } = await client.asInternalUser.ml.getDatafeeds(); - const { - body: dfaJobs, - } = ((await client.asInternalUser.ml.getDataFrameAnalytics()) as unknown) as { - body: { data_frame_analytics: DataFrameAnalyticsConfig[] }; - }; + const { body: dfaJobs } = + (await client.asInternalUser.ml.getDataFrameAnalytics()) as unknown as { + body: { data_frame_analytics: DataFrameAnalyticsConfig[] }; + }; const savedObjectsStatus: JobSavedObjectStatus[] = jobObjects.map( ({ attributes, namespaces }) => { diff --git a/x-pack/plugins/ml/server/saved_objects/initialization/initialization.ts b/x-pack/plugins/ml/server/saved_objects/initialization/initialization.ts index 174224d130c28..c55ed3d40a676 100644 --- a/x-pack/plugins/ml/server/saved_objects/initialization/initialization.ts +++ b/x-pack/plugins/ml/server/saved_objects/initialization/initialization.ts @@ -25,7 +25,7 @@ export function jobSavedObjectsInitializationFactory( security: SecurityPluginSetup | undefined, spacesEnabled: boolean ) { - const client = (core.elasticsearch.client as unknown) as IScopedClusterClient; + const client = core.elasticsearch.client as unknown as IScopedClusterClient; /** * Check whether ML saved objects exist. diff --git a/x-pack/plugins/ml/server/saved_objects/initialization/space_overrides/space_overrides.test.ts b/x-pack/plugins/ml/server/saved_objects/initialization/space_overrides/space_overrides.test.ts index a9e1246df5eb3..72cce2ff1c13a 100644 --- a/x-pack/plugins/ml/server/saved_objects/initialization/space_overrides/space_overrides.test.ts +++ b/x-pack/plugins/ml/server/saved_objects/initialization/space_overrides/space_overrides.test.ts @@ -59,9 +59,9 @@ const callAs = { }, }; -const mlClusterClient = ({ +const mlClusterClient = { asInternalUser: callAs, -} as unknown) as IScopedClusterClient; +} as unknown as IScopedClusterClient; describe('ML - job initialization', () => { describe('createJobSpaceOverrides', () => { diff --git a/x-pack/plugins/ml/server/saved_objects/service.ts b/x-pack/plugins/ml/server/saved_objects/service.ts index da7d11776ee53..ff8856457ca4f 100644 --- a/x-pack/plugins/ml/server/saved_objects/service.ts +++ b/x-pack/plugins/ml/server/saved_objects/service.ts @@ -244,7 +244,7 @@ export function jobSavedObjectServiceFactory( return []; } const jobIds = await getIds(jobType, key); - return list.filter((j) => jobIds.includes((j[field] as unknown) as string)); + return list.filter((j) => jobIds.includes(j[field] as unknown as string)); } async function filterJobsForSpace(jobType: JobType, list: T[], field: keyof T): Promise { diff --git a/x-pack/plugins/ml/server/shared_services/license_checks/license_checks.ts b/x-pack/plugins/ml/server/shared_services/license_checks/license_checks.ts index 25cd8a7c6d8b6..e1501a71c2ea4 100644 --- a/x-pack/plugins/ml/server/shared_services/license_checks/license_checks.ts +++ b/x-pack/plugins/ml/server/shared_services/license_checks/license_checks.ts @@ -10,9 +10,10 @@ import { InsufficientFullLicenseError, InsufficientBasicLicenseError } from './e export type LicenseCheck = () => void; -export function licenseChecks( - mlLicense: MlLicense -): { isFullLicense: LicenseCheck; isMinimumLicense: LicenseCheck } { +export function licenseChecks(mlLicense: MlLicense): { + isFullLicense: LicenseCheck; + isMinimumLicense: LicenseCheck; +} { return { isFullLicense() { if (mlLicense.isFullLicense() === false) { diff --git a/x-pack/plugins/monitoring/server/lib/ccs_utils.test.js b/x-pack/plugins/monitoring/common/ccs_utils.test.js similarity index 100% rename from x-pack/plugins/monitoring/server/lib/ccs_utils.test.js rename to x-pack/plugins/monitoring/common/ccs_utils.test.js diff --git a/x-pack/plugins/monitoring/server/lib/ccs_utils.ts b/x-pack/plugins/monitoring/common/ccs_utils.ts similarity index 96% rename from x-pack/plugins/monitoring/server/lib/ccs_utils.ts rename to x-pack/plugins/monitoring/common/ccs_utils.ts index 1d899456913b9..7efe6e43ddbbd 100644 --- a/x-pack/plugins/monitoring/server/lib/ccs_utils.ts +++ b/x-pack/plugins/monitoring/common/ccs_utils.ts @@ -6,7 +6,8 @@ */ import { isFunction, get } from 'lodash'; -import type { MonitoringConfig } from '../config'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import type { MonitoringConfig } from '../server/config'; type Config = Partial & { get?: (key: string) => any; diff --git a/x-pack/plugins/monitoring/common/types/alerts.ts b/x-pack/plugins/monitoring/common/types/alerts.ts index 17bbffce19a18..1f68b0c55a046 100644 --- a/x-pack/plugins/monitoring/common/types/alerts.ts +++ b/x-pack/plugins/monitoring/common/types/alerts.ts @@ -48,12 +48,16 @@ export interface CommonAlertParams { duration: string; threshold?: number; limit?: string; + filterQuery?: string; + filterQueryText?: string; [key: string]: unknown; } export interface ThreadPoolRejectionsAlertParams { threshold: number; duration: string; + filterQuery?: string; + filterQueryText?: string; } export interface AlertEnableAction { diff --git a/x-pack/plugins/monitoring/public/alerts/alert_form.test.tsx b/x-pack/plugins/monitoring/public/alerts/alert_form.test.tsx index a300d9a3772bc..8752a4118fb6a 100644 --- a/x-pack/plugins/monitoring/public/alerts/alert_form.test.tsx +++ b/x-pack/plugins/monitoring/public/alerts/alert_form.test.tsx @@ -118,7 +118,7 @@ describe('alert_form', () => { const KibanaReactContext = createKibanaReactContext(Legacy.shims.kibanaServices); - const initialAlert = ({ + const initialAlert = { name: 'test', alertTypeId: alertType.id, params: {}, @@ -131,7 +131,7 @@ describe('alert_form', () => { muteAll: false, enabled: false, mutedInstanceIds: [], - } as unknown) as Alert; + } as unknown as Alert; wrapper = mountWithIntl( @@ -201,7 +201,7 @@ describe('alert_form', () => { actionTypeRegistry.has.mockReturnValue(true); actionTypeRegistry.get.mockReturnValue(actionType); - const initialAlert = ({ + const initialAlert = { name: 'test', alertTypeId: alertType.id, params: {}, @@ -223,7 +223,7 @@ describe('alert_form', () => { muteAll: false, enabled: false, mutedInstanceIds: [], - } as unknown) as Alert; + } as unknown as Alert; const KibanaReactContext = createKibanaReactContext(Legacy.shims.kibanaServices); diff --git a/x-pack/plugins/monitoring/public/alerts/alerts_dropdown.tsx b/x-pack/plugins/monitoring/public/alerts/alerts_dropdown.tsx index 8ea073d6132d6..261685a532882 100644 --- a/x-pack/plugins/monitoring/public/alerts/alerts_dropdown.tsx +++ b/x-pack/plugins/monitoring/public/alerts/alerts_dropdown.tsx @@ -21,9 +21,8 @@ import { MonitoringStartPluginDependencies } from '../types'; export const AlertsDropdown: React.FC<{}> = () => { const $injector = Legacy.shims.getAngularInjector(); const alertsEnableModalProvider: any = $injector.get('enableAlertsModal'); - const { navigateToApp } = useKibana< - MonitoringStartPluginDependencies['core'] - >().services.application; + const { navigateToApp } = + useKibana().services.application; const [isPopoverOpen, setIsPopoverOpen] = useState(false); diff --git a/x-pack/plugins/monitoring/public/alerts/ccr_read_exceptions_alert/index.tsx b/x-pack/plugins/monitoring/public/alerts/ccr_read_exceptions_alert/index.tsx index 1de9a175026a6..64eab04cbd5ce 100644 --- a/x-pack/plugins/monitoring/public/alerts/ccr_read_exceptions_alert/index.tsx +++ b/x-pack/plugins/monitoring/public/alerts/ccr_read_exceptions_alert/index.tsx @@ -15,6 +15,7 @@ import { RULE_REQUIRES_APP_CONTEXT, } from '../../../common/constants'; import { AlertTypeParams } from '../../../../alerting/common'; +import { MonitoringConfig } from '../../types'; interface ValidateOptions extends AlertTypeParams { duration: string; @@ -36,7 +37,9 @@ const validate = (inputValues: ValidateOptions): ValidationResult => { return validationResult; }; -export function createCCRReadExceptionsAlertType(): AlertTypeModel { +export function createCCRReadExceptionsAlertType( + config: MonitoringConfig +): AlertTypeModel { return { id: RULE_CCR_READ_EXCEPTIONS, description: RULE_DETAILS[RULE_CCR_READ_EXCEPTIONS].description, @@ -45,7 +48,11 @@ export function createCCRReadExceptionsAlertType(): AlertTypeModel ( - + ), validate, defaultActionMessage: '{{context.internalFullMessage}}', diff --git a/x-pack/plugins/monitoring/public/alerts/components/param_details_form/expression.tsx b/x-pack/plugins/monitoring/public/alerts/components/param_details_form/expression.tsx index df17ce1a911a0..827eed955d535 100644 --- a/x-pack/plugins/monitoring/public/alerts/components/param_details_form/expression.tsx +++ b/x-pack/plugins/monitoring/public/alerts/components/param_details_form/expression.tsx @@ -5,14 +5,23 @@ * 2.0. */ -import React, { Fragment } from 'react'; -import { EuiForm, EuiSpacer } from '@elastic/eui'; +import React, { Fragment, useCallback } from 'react'; +import { EuiForm, EuiFormRow, EuiSpacer } from '@elastic/eui'; +import { DataPublicPluginStart } from 'src/plugins/data/public'; +import { debounce } from 'lodash'; +import { i18n } from '@kbn/i18n'; import { CommonAlertParamDetails } from '../../../../common/types/alerts'; import { AlertParamDuration } from '../../flyout_expressions/alert_param_duration'; import { AlertParamType } from '../../../../common/enums'; import { AlertParamPercentage } from '../../flyout_expressions/alert_param_percentage'; import { AlertParamNumber } from '../../flyout_expressions/alert_param_number'; import { AlertParamTextField } from '../../flyout_expressions/alert_param_textfield'; +import { MonitoringConfig } from '../../../types'; +import { useDerivedIndexPattern } from './use_derived_index_pattern'; +import { KueryBar } from '../../../components/kuery_bar'; +import { convertKueryToElasticSearchQuery } from '../../../lib/kuery'; + +const FILTER_TYPING_DEBOUNCE_MS = 500; export interface Props { alertParams: { [property: string]: any }; @@ -20,10 +29,14 @@ export interface Props { setAlertProperty: (property: string, value: any) => void; errors: { [key: string]: string[] }; paramDetails: CommonAlertParamDetails; + data: DataPublicPluginStart; + config?: MonitoringConfig; } export const Expression: React.FC = (props) => { - const { alertParams, paramDetails, setAlertParams, errors } = props; + const { alertParams, paramDetails, setAlertParams, errors, config, data } = props; + + const { derivedIndexPattern } = useDerivedIndexPattern(data, config); const alertParamsUi = Object.keys(paramDetails).map((alertParamName) => { const details = paramDetails[alertParamName]; @@ -77,10 +90,44 @@ export const Expression: React.FC = (props) => { } }); + const onFilterChange = useCallback( + (filter: string) => { + setAlertParams('filterQueryText', filter); + setAlertParams( + 'filterQuery', + convertKueryToElasticSearchQuery(filter, derivedIndexPattern) || '' + ); + }, + [setAlertParams, derivedIndexPattern] + ); + + /* eslint-disable-next-line react-hooks/exhaustive-deps */ + const debouncedOnFilterChange = useCallback(debounce(onFilterChange, FILTER_TYPING_DEBOUNCE_MS), [ + onFilterChange, + ]); + return ( - {alertParamsUi} - + + {alertParamsUi} + + + + + + ); }; diff --git a/x-pack/plugins/monitoring/public/alerts/components/param_details_form/use_derived_index_pattern.tsx b/x-pack/plugins/monitoring/public/alerts/components/param_details_form/use_derived_index_pattern.tsx new file mode 100644 index 0000000000000..1a4d88d690b84 --- /dev/null +++ b/x-pack/plugins/monitoring/public/alerts/components/param_details_form/use_derived_index_pattern.tsx @@ -0,0 +1,44 @@ +/* + * 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, useState } from 'react'; +import { DataPublicPluginStart, IFieldType, IIndexPattern } from 'src/plugins/data/public'; +import { + INDEX_PATTERN_BEATS, + INDEX_PATTERN_ELASTICSEARCH, + INDEX_PATTERN_KIBANA, + INDEX_PATTERN_LOGSTASH, +} from '../../../../common/constants'; +import { prefixIndexPattern } from '../../../../common/ccs_utils'; +import { MonitoringConfig } from '../../../types'; + +const INDEX_PATTERNS = `${INDEX_PATTERN_ELASTICSEARCH},${INDEX_PATTERN_KIBANA},${INDEX_PATTERN_LOGSTASH},${INDEX_PATTERN_BEATS}`; + +export const useDerivedIndexPattern = ( + data: DataPublicPluginStart, + config?: MonitoringConfig +): { loading: boolean; derivedIndexPattern: IIndexPattern } => { + const indexPattern = prefixIndexPattern(config || ({} as MonitoringConfig), INDEX_PATTERNS, '*'); + const [loading, setLoading] = useState(true); + const [fields, setFields] = useState([]); + useEffect(() => { + (async function fetchData() { + const result = await data.indexPatterns.getFieldsForWildcard({ + pattern: indexPattern, + }); + setFields(result); + setLoading(false); + })(); + }, [indexPattern, data.indexPatterns]); + return { + loading, + derivedIndexPattern: { + title: indexPattern, + fields, + }, + }; +}; diff --git a/x-pack/plugins/monitoring/public/alerts/cpu_usage_alert/cpu_usage_alert.tsx b/x-pack/plugins/monitoring/public/alerts/cpu_usage_alert/cpu_usage_alert.tsx index ec7a583ec2ba1..f0e0a413435f9 100644 --- a/x-pack/plugins/monitoring/public/alerts/cpu_usage_alert/cpu_usage_alert.tsx +++ b/x-pack/plugins/monitoring/public/alerts/cpu_usage_alert/cpu_usage_alert.tsx @@ -11,8 +11,11 @@ import { AlertTypeModel } from '../../../../triggers_actions_ui/public/types'; import { RULE_CPU_USAGE, RULE_DETAILS, RULE_REQUIRES_APP_CONTEXT } from '../../../common/constants'; import { validate, MonitoringAlertTypeParams } from '../components/param_details_form/validation'; import { Expression, Props } from '../components/param_details_form/expression'; +import { MonitoringConfig } from '../../types'; -export function createCpuUsageAlertType(): AlertTypeModel { +export function createCpuUsageAlertType( + config: MonitoringConfig +): AlertTypeModel { return { id: RULE_CPU_USAGE, description: RULE_DETAILS[RULE_CPU_USAGE].description, @@ -21,7 +24,11 @@ export function createCpuUsageAlertType(): AlertTypeModel ( - + ), validate, defaultActionMessage: '{{context.internalFullMessage}}', diff --git a/x-pack/plugins/monitoring/public/alerts/disk_usage_alert/index.tsx b/x-pack/plugins/monitoring/public/alerts/disk_usage_alert/index.tsx index 779945da0c9e0..5f9f9536ae567 100644 --- a/x-pack/plugins/monitoring/public/alerts/disk_usage_alert/index.tsx +++ b/x-pack/plugins/monitoring/public/alerts/disk_usage_alert/index.tsx @@ -16,8 +16,11 @@ import { RULE_DETAILS, RULE_REQUIRES_APP_CONTEXT, } from '../../../common/constants'; +import { MonitoringConfig } from '../../types'; -export function createDiskUsageAlertType(): AlertTypeModel { +export function createDiskUsageAlertType( + config: MonitoringConfig +): AlertTypeModel { return { id: RULE_DISK_USAGE, description: RULE_DETAILS[RULE_DISK_USAGE].description, @@ -26,7 +29,11 @@ export function createDiskUsageAlertType(): AlertTypeModel ( - + ), validate, defaultActionMessage: '{{context.internalFullMessage}}', diff --git a/x-pack/plugins/monitoring/public/alerts/large_shard_size_alert/index.tsx b/x-pack/plugins/monitoring/public/alerts/large_shard_size_alert/index.tsx index e0f595abe7602..afaf20d60d882 100644 --- a/x-pack/plugins/monitoring/public/alerts/large_shard_size_alert/index.tsx +++ b/x-pack/plugins/monitoring/public/alerts/large_shard_size_alert/index.tsx @@ -15,6 +15,7 @@ import { RULE_REQUIRES_APP_CONTEXT, } from '../../../common/constants'; import { AlertTypeParams } from '../../../../alerting/common'; +import { MonitoringConfig } from '../../types'; interface ValidateOptions extends AlertTypeParams { indexPattern: string; @@ -36,7 +37,9 @@ const validate = (inputValues: ValidateOptions): ValidationResult => { return validationResult; }; -export function createLargeShardSizeAlertType(): AlertTypeModel { +export function createLargeShardSizeAlertType( + config: MonitoringConfig +): AlertTypeModel { return { id: RULE_LARGE_SHARD_SIZE, description: RULE_DETAILS[RULE_LARGE_SHARD_SIZE].description, @@ -45,7 +48,11 @@ export function createLargeShardSizeAlertType(): AlertTypeModel return `${docLinks.links.monitoring.alertsKibanaLargeShardSize}`; }, alertParamsExpression: (props: Props) => ( - + ), validate, defaultActionMessage: '{{context.internalFullMessage}}', diff --git a/x-pack/plugins/monitoring/public/alerts/legacy_alert/expression.tsx b/x-pack/plugins/monitoring/public/alerts/legacy_alert/expression.tsx new file mode 100644 index 0000000000000..fe6adf66c1d4f --- /dev/null +++ b/x-pack/plugins/monitoring/public/alerts/legacy_alert/expression.tsx @@ -0,0 +1,56 @@ +/* + * 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, { useCallback } from 'react'; +import { debounce } from 'lodash'; +import { EuiSpacer, EuiForm, EuiFormRow } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { useDerivedIndexPattern } from '../components/param_details_form/use_derived_index_pattern'; +import { convertKueryToElasticSearchQuery } from '../../lib/kuery'; +import { KueryBar } from '../../components/kuery_bar'; +import { Props } from '../components/param_details_form/expression'; + +const FILTER_TYPING_DEBOUNCE_MS = 500; + +export const Expression = ({ alertParams, config, setAlertParams, data }: Props) => { + const { derivedIndexPattern } = useDerivedIndexPattern(data, config); + const onFilterChange = useCallback( + (filter: string) => { + setAlertParams('filterQueryText', filter); + setAlertParams( + 'filterQuery', + convertKueryToElasticSearchQuery(filter, derivedIndexPattern) || '' + ); + }, + [setAlertParams, derivedIndexPattern] + ); + + /* eslint-disable-next-line react-hooks/exhaustive-deps */ + const debouncedOnFilterChange = useCallback(debounce(onFilterChange, FILTER_TYPING_DEBOUNCE_MS), [ + onFilterChange, + ]); + return ( + + + + + + + ); +}; diff --git a/x-pack/plugins/monitoring/public/alerts/legacy_alert/legacy_alert.tsx b/x-pack/plugins/monitoring/public/alerts/legacy_alert/legacy_alert.tsx index cac4873bc0c79..a6c22035c5a5a 100644 --- a/x-pack/plugins/monitoring/public/alerts/legacy_alert/legacy_alert.tsx +++ b/x-pack/plugins/monitoring/public/alerts/legacy_alert/legacy_alert.tsx @@ -5,9 +5,7 @@ * 2.0. */ -import React, { Fragment } from 'react'; -import { i18n } from '@kbn/i18n'; -import { EuiTextColor, EuiSpacer } from '@elastic/eui'; +import React from 'react'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { AlertTypeModel } from '../../../../triggers_actions_ui/public/types'; import { @@ -15,8 +13,11 @@ import { LEGACY_RULE_DETAILS, RULE_REQUIRES_APP_CONTEXT, } from '../../../common/constants'; +import { MonitoringConfig } from '../../types'; +import { Expression } from './expression'; +import { Props } from '../components/param_details_form/expression'; -export function createLegacyAlertTypes(): AlertTypeModel[] { +export function createLegacyAlertTypes(config: MonitoringConfig): AlertTypeModel[] { return LEGACY_RULES.map((legacyAlert) => { return { id: legacyAlert, @@ -25,17 +26,7 @@ export function createLegacyAlertTypes(): AlertTypeModel[] { documentationUrl(docLinks) { return `${docLinks.links.monitoring.alertsKibanaClusterAlerts}`; }, - alertParamsExpression: () => ( - - - - {i18n.translate('xpack.monitoring.alerts.legacyAlert.expressionText', { - defaultMessage: 'There is nothing to configure.', - })} - - - - ), + alertParamsExpression: (props: Props) => , defaultActionMessage: '{{context.internalFullMessage}}', validate: () => ({ errors: {} }), requiresAppContext: RULE_REQUIRES_APP_CONTEXT, diff --git a/x-pack/plugins/monitoring/public/alerts/lib/alerts_toast.tsx b/x-pack/plugins/monitoring/public/alerts/lib/alerts_toast.tsx index 026f172147192..d752ec154089b 100644 --- a/x-pack/plugins/monitoring/public/alerts/lib/alerts_toast.tsx +++ b/x-pack/plugins/monitoring/public/alerts/lib/alerts_toast.tsx @@ -93,11 +93,8 @@ const showDisabledWatcherClusterAlertsError = () => { }; export const showAlertsToast = (response: EnableAlertResponse) => { - const { - isSufficientlySecure, - hasPermanentEncryptionKey, - disabledWatcherClusterAlerts, - } = response; + const { isSufficientlySecure, hasPermanentEncryptionKey, disabledWatcherClusterAlerts } = + response; if (isSufficientlySecure === false || hasPermanentEncryptionKey === false) { showTlsAndEncryptionError(); diff --git a/x-pack/plugins/monitoring/public/alerts/memory_usage_alert/index.tsx b/x-pack/plugins/monitoring/public/alerts/memory_usage_alert/index.tsx index 3e55b6d5454ff..2fe0c9b77c0eb 100644 --- a/x-pack/plugins/monitoring/public/alerts/memory_usage_alert/index.tsx +++ b/x-pack/plugins/monitoring/public/alerts/memory_usage_alert/index.tsx @@ -16,8 +16,11 @@ import { RULE_DETAILS, RULE_REQUIRES_APP_CONTEXT, } from '../../../common/constants'; +import { MonitoringConfig } from '../../types'; -export function createMemoryUsageAlertType(): AlertTypeModel { +export function createMemoryUsageAlertType( + config: MonitoringConfig +): AlertTypeModel { return { id: RULE_MEMORY_USAGE, description: RULE_DETAILS[RULE_MEMORY_USAGE].description, @@ -26,7 +29,11 @@ export function createMemoryUsageAlertType(): AlertTypeModel ( - + ), validate, defaultActionMessage: '{{context.internalFullMessage}}', diff --git a/x-pack/plugins/monitoring/public/alerts/thread_pool_rejections_alert/index.tsx b/x-pack/plugins/monitoring/public/alerts/thread_pool_rejections_alert/index.tsx index 7fd9438e1cea3..e8a15ad835581 100644 --- a/x-pack/plugins/monitoring/public/alerts/thread_pool_rejections_alert/index.tsx +++ b/x-pack/plugins/monitoring/public/alerts/thread_pool_rejections_alert/index.tsx @@ -13,6 +13,7 @@ import { Expression, Props } from '../components/param_details_form/expression'; import { AlertTypeModel } from '../../../../triggers_actions_ui/public/types'; import { CommonAlertParamDetails } from '../../../common/types/alerts'; import { RULE_REQUIRES_APP_CONTEXT } from '../../../common/constants'; +import { MonitoringConfig } from '../../types'; interface ThreadPoolTypes { [key: string]: unknown; @@ -26,7 +27,8 @@ interface ThreadPoolRejectionAlertDetails { export function createThreadPoolRejectionsAlertType( alertId: string, - threadPoolAlertDetails: ThreadPoolRejectionAlertDetails + threadPoolAlertDetails: ThreadPoolRejectionAlertDetails, + config: MonitoringConfig ): AlertTypeModel { return { id: alertId, @@ -38,7 +40,7 @@ export function createThreadPoolRejectionsAlertType( alertParamsExpression: (props: Props) => ( <> - + ), validate: (inputValues: ThreadPoolTypes) => { diff --git a/x-pack/plugins/monitoring/public/application/global_state_context.tsx b/x-pack/plugins/monitoring/public/application/global_state_context.tsx index a244840e004fd..6c952f80eff57 100644 --- a/x-pack/plugins/monitoring/public/application/global_state_context.tsx +++ b/x-pack/plugins/monitoring/public/application/global_state_context.tsx @@ -34,10 +34,9 @@ export const GlobalStateProvider: React.FC = ({ }) => { // TODO: remove fakeAngularRootScope and fakeAngularLocation when angular is removed const fakeAngularRootScope: Partial = { - $on: ( - name: string, - listener: (event: ng.IAngularEvent, ...args: any[]) => any - ): (() => void) => () => {}, + $on: + (name: string, listener: (event: ng.IAngularEvent, ...args: any[]) => any): (() => void) => + () => {}, $applyAsync: () => {}, }; diff --git a/x-pack/plugins/monitoring/public/application/hooks/use_charts.tsx b/x-pack/plugins/monitoring/public/application/hooks/use_charts.tsx new file mode 100644 index 0000000000000..25d7f139e5bbe --- /dev/null +++ b/x-pack/plugins/monitoring/public/application/hooks/use_charts.tsx @@ -0,0 +1,81 @@ +/* + * 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 { useContext, useState, useEffect, useRef } from 'react'; +import { useHistory } from 'react-router-dom'; +import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; +import { MonitoringTimeContainer } from '../hooks/use_monitoring_time'; + +export function useCharts() { + const { services } = useKibana<{ data: any }>(); + const history = useHistory(); + const { handleTimeChange } = useContext(MonitoringTimeContainer.Context); + + const [zoomInLevel, setZoomInLevel] = useState(0); + + // We need something to know when the onBrush event was fired because the pop state event + // is also fired when the onBrush event is fired (although only on the first onBrush event) and + // causing the zoomInLevel to change. + // In Angular, this was handled by removing the listener before updating the state and adding + // it again after some milliseconds, but the same trick didn't work in React. + const [onBrushHappened, _setOnBrushHappened] = useState(false); + + const onBrushHappenedRef = useRef(onBrushHappened); + + const setOnBrushHappened = (data: boolean) => { + onBrushHappenedRef.current = data; + _setOnBrushHappened(data); + }; + + useEffect(() => { + const popstateHandler = () => { + if (onBrushHappenedRef.current) { + setOnBrushHappened(false); + } else { + setZoomInLevel((currentZoomInLevel) => { + if (currentZoomInLevel > 0) { + return currentZoomInLevel - 1; + } + return 0; + }); + } + }; + + window.addEventListener('popstate', popstateHandler); + return () => window.removeEventListener('popstate', popstateHandler); + }, []); + + const onBrush = ({ xaxis }: any) => { + const { to, from } = xaxis; + const timezone = services.uiSettings?.get('dateFormat:tz'); + const offset = getOffsetInMS(timezone); + const fromTime = moment(from - offset); + const toTime = moment(to - offset); + handleTimeChange(fromTime.toISOString(), toTime.toISOString()); + setOnBrushHappened(true); + setZoomInLevel(zoomInLevel + 1); + }; + + const zoomInfo = { + zoomOutHandler: () => history.goBack(), + showZoomOutBtn: () => zoomInLevel > 0, + }; + + return { + onBrush, + zoomInfo, + }; +} + +const getOffsetInMS = (timezone: string) => { + if (timezone === 'Browser') { + return 0; + } + const offsetInMinutes = moment.tz(timezone).utcOffset(); + const offsetInMS = offsetInMinutes * 1 * 60 * 1000; + return offsetInMS; +}; diff --git a/x-pack/plugins/monitoring/public/application/hooks/use_monitoring_time.ts b/x-pack/plugins/monitoring/public/application/hooks/use_monitoring_time.ts index 8a343a5c61cd6..e512f90d76e69 100644 --- a/x-pack/plugins/monitoring/public/application/hooks/use_monitoring_time.ts +++ b/x-pack/plugins/monitoring/public/application/hooks/use_monitoring_time.ts @@ -4,9 +4,11 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { useCallback, useState } from 'react'; +import { useCallback, useState, useContext } from 'react'; import createContainer from 'constate'; import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; +import { Legacy } from '../../legacy_shims'; +import { GlobalStateContext } from '../../application/global_state_context'; interface TimeOptions { from: string; @@ -25,6 +27,8 @@ const DEFAULT_REFRESH_INTERVAL_PAUSE = false; export const useMonitoringTime = () => { const { services } = useKibana<{ data: any }>(); + const state = useContext(GlobalStateContext); + const defaultTimeRange = { ...DEFAULT_TIMERANGE, ...services.data?.query.timefilter.timefilter.getTime(), @@ -39,8 +43,14 @@ export const useMonitoringTime = () => { const handleTimeChange = useCallback( (start: string, end: string) => { setTimeRange({ ...currentTimerange, from: start, to: end }); + state.time = { + from: start, + to: end, + }; + Legacy.shims.timefilter.setTime(state.time); + state.save?.(); }, - [currentTimerange, setTimeRange] + [currentTimerange, setTimeRange, state] ); return { diff --git a/x-pack/plugins/monitoring/public/application/index.tsx b/x-pack/plugins/monitoring/public/application/index.tsx index 8d6c718d77ebb..8cd5bc3088acc 100644 --- a/x-pack/plugins/monitoring/public/application/index.tsx +++ b/x-pack/plugins/monitoring/public/application/index.tsx @@ -18,6 +18,9 @@ import { GlobalStateProvider } from './global_state_context'; import { ExternalConfigContext, ExternalConfig } from './external_config_context'; import { createPreserveQueryHistory } from './preserve_query_history'; import { RouteInit } from './route_init'; +import { NoDataPage } from './pages/no_data'; +import { ElasticsearchOverviewPage } from './pages/elasticsearch/overview'; +import { CODE_PATH_ELASTICSEARCH } from '../../common/constants'; import { MonitoringTimeContainer } from './hooks/use_monitoring_time'; import { BreadcrumbContainer } from './hooks/use_breadcrumbs'; @@ -52,7 +55,7 @@ const MonitoringApp: React.FC<{ - + + + {/* ElasticSearch Views */} + = () => { - return
No data page
; -}; - const Home: React.FC<{}> = () => { return
Home page (Cluster listing)
; }; diff --git a/x-pack/plugins/monitoring/public/application/pages/cluster/overview_page.tsx b/x-pack/plugins/monitoring/public/application/pages/cluster/overview_page.tsx index e1b3624b1edda..9bf0bf6d14795 100644 --- a/x-pack/plugins/monitoring/public/application/pages/cluster/overview_page.tsx +++ b/x-pack/plugins/monitoring/public/application/pages/cluster/overview_page.tsx @@ -51,10 +51,8 @@ export const ClusterOverview: React.FC<{}> = () => { { id: 'clusterName', label: clusters[0].cluster_name, - disabled: false, - description: clusters[0].cluster_name, - onClick: () => {}, testSubj: 'clusterName', + route: '/overview', }, ]; } diff --git a/x-pack/plugins/monitoring/public/application/pages/elasticsearch/elasticsearch_template.tsx b/x-pack/plugins/monitoring/public/application/pages/elasticsearch/elasticsearch_template.tsx new file mode 100644 index 0000000000000..13e21912df896 --- /dev/null +++ b/x-pack/plugins/monitoring/public/application/pages/elasticsearch/elasticsearch_template.tsx @@ -0,0 +1,69 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { includes } from 'lodash'; +import { PageTemplate } from '../page_template'; +import { TabMenuItem, PageTemplateProps } from '../page_template'; +import { ML_SUPPORTED_LICENSES } from '../../../../common/constants'; + +interface ElasticsearchTemplateProps extends PageTemplateProps { + cluster: any; +} + +export const ElasticsearchTemplate: React.FC = ({ + cluster, + ...props +}) => { + const tabs: TabMenuItem[] = [ + { + id: 'overview', + label: i18n.translate('xpack.monitoring.esNavigation.overviewLinkText', { + defaultMessage: 'Overview', + }), + route: '/elasticsearch', + }, + { + id: 'nodes', + label: i18n.translate('xpack.monitoring.esNavigation.nodesLinkText', { + defaultMessage: 'Nodes', + }), + route: '/elasticsearch/nodes', + }, + { + id: 'indices', + label: i18n.translate('xpack.monitoring.esNavigation.indicesLinkText', { + defaultMessage: 'Indices', + }), + route: '/elasticsearch/indices', + }, + ]; + + if (mlIsSupported(cluster.license)) { + tabs.push({ + id: 'ml', + label: i18n.translate('xpack.monitoring.esNavigation.jobsLinkText', { + defaultMessage: 'Machine learning jobs', + }), + route: '/elasticsearch/ml_jobs', + }); + } + + if (cluster.isCcrEnabled) { + tabs.push({ + id: 'ccr', + label: i18n.translate('xpack.monitoring.esNavigation.ccrLinkText', { + defaultMessage: 'CCR', + }), + route: '/elasticsearch/ccr', + }); + } + + return ; +}; + +const mlIsSupported = (license: any) => includes(ML_SUPPORTED_LICENSES, license.type); diff --git a/x-pack/plugins/monitoring/public/application/pages/elasticsearch/overview.tsx b/x-pack/plugins/monitoring/public/application/pages/elasticsearch/overview.tsx new file mode 100644 index 0000000000000..4e885229b436a --- /dev/null +++ b/x-pack/plugins/monitoring/public/application/pages/elasticsearch/overview.tsx @@ -0,0 +1,97 @@ +/* + * 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, { useContext, useState, useCallback } from 'react'; +import { i18n } from '@kbn/i18n'; +import { find } from 'lodash'; +import { ElasticsearchTemplate } from './elasticsearch_template'; +import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; +import { GlobalStateContext } from '../../global_state_context'; +import { ElasticsearchOverview } from '../../../components/elasticsearch'; +import { ComponentProps } from '../../route_init'; +import { useCharts } from '../../hooks/use_charts'; + +export const ElasticsearchOverviewPage: React.FC = ({ clusters }) => { + const globalState = useContext(GlobalStateContext); + const { zoomInfo, onBrush } = useCharts(); + const { services } = useKibana<{ data: any }>(); + const clusterUuid = globalState.cluster_uuid; + const ccs = globalState.ccs; + const cluster = find(clusters, { + cluster_uuid: clusterUuid, + }); + const [data, setData] = useState(null); + const [showShardActivityHistory, setShowShardActivityHistory] = useState(false); + const toggleShardActivityHistory = () => { + setShowShardActivityHistory(!showShardActivityHistory); + }; + const filterShardActivityData = (shardActivity: any) => { + return shardActivity.filter((row: any) => { + return showShardActivityHistory || row.stage !== 'DONE'; + }); + }; + + const title = i18n.translate('xpack.monitoring.elasticsearch.overview.title', { + defaultMessage: 'Elasticsearch', + }); + + const pageTitle = i18n.translate('xpack.monitoring.elasticsearch.overview.pageTitle', { + defaultMessage: 'Elasticsearch overview', + }); + + const getPageData = useCallback(async () => { + const bounds = services.data?.query.timefilter.timefilter.getBounds(); + const url = `../api/monitoring/v1/clusters/${clusterUuid}/elasticsearch`; + + const response = await services.http?.fetch(url, { + method: 'POST', + body: JSON.stringify({ + ccs, + timeRange: { + min: bounds.min.toISOString(), + max: bounds.max.toISOString(), + }, + }), + }); + + setData(response); + }, [ccs, clusterUuid, services.data?.query.timefilter.timefilter, services.http]); + + const renderOverview = (overviewData: any) => { + if (overviewData === null) { + return null; + } + const { clusterStatus, metrics, shardActivity, logs } = overviewData || {}; + const shardActivityData = shardActivity && filterShardActivityData(shardActivity); // no filter on data = null + + return ( + + ); + }; + + return ( + +
{renderOverview(data)}
+
+ ); +}; diff --git a/x-pack/plugins/monitoring/public/application/pages/no_data/enabler.ts b/x-pack/plugins/monitoring/public/application/pages/no_data/enabler.ts new file mode 100644 index 0000000000000..6225e3863b2f7 --- /dev/null +++ b/x-pack/plugins/monitoring/public/application/pages/no_data/enabler.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +// From x-pack/plugins/monitoring/public/lib/elasticsearch_settings/enabler.js +export class Enabler { + http: any; + updateModel: any; + + constructor(http: any, updateModel: (properties: any) => void) { + this.http = http; + this.updateModel = updateModel; + } + + async enableCollectionInterval() { + try { + this.updateModel({ isCollectionIntervalUpdating: true }); + + await this.http.fetch('../api/monitoring/v1/elasticsearch_settings/set/collection_interval', { + method: 'PUT', + }); + this.updateModel({ + isCollectionIntervalUpdated: true, + isCollectionIntervalUpdating: false, + }); + } catch (err) { + this.updateModel({ + errors: (err as any).data, + isCollectionIntervalUpdated: false, + isCollectionIntervalUpdating: false, + }); + } + } + + async enableCollectionEnabled() { + try { + this.updateModel({ isCollectionEnabledUpdating: true }); + await this.http.fetch('../api/monitoring/v1/elasticsearch_settings/set/collection_enabled', { + method: 'PUT', + }); + + this.updateModel({ + isCollectionEnabledUpdated: true, + isCollectionEnabledUpdating: false, + }); + } catch (err) { + this.updateModel({ + errors: (err as any).data, + isCollectionEnabledUpdated: false, + isCollectionEnabledUpdating: false, + }); + } + } +} diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/migrations/index.ts b/x-pack/plugins/monitoring/public/application/pages/no_data/index.ts similarity index 83% rename from x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/migrations/index.ts rename to x-pack/plugins/monitoring/public/application/pages/no_data/index.ts index 0b8c698bc3ea7..7fa176d0e6adf 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/migrations/index.ts +++ b/x-pack/plugins/monitoring/public/application/pages/no_data/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { timelinesMigrations } from './timelines'; +export { NoDataPage } from './no_data_page'; diff --git a/x-pack/plugins/monitoring/public/application/pages/no_data/no_data_page.tsx b/x-pack/plugins/monitoring/public/application/pages/no_data/no_data_page.tsx new file mode 100644 index 0000000000000..b05bd783b2ff2 --- /dev/null +++ b/x-pack/plugins/monitoring/public/application/pages/no_data/no_data_page.tsx @@ -0,0 +1,240 @@ +/* + * 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, { useCallback, useContext, useState } from 'react'; +import { Redirect } from 'react-router-dom'; + +import { i18n } from '@kbn/i18n'; +// @ts-ignore +import { NoData } from '../../../components/no_data'; +import { PageTemplate } from '../page_template'; +import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; +import { CODE_PATH_LICENSE, STANDALONE_CLUSTER_CLUSTER_UUID } from '../../../../common/constants'; +import { Legacy } from '../../../legacy_shims'; +import { Enabler } from './enabler'; +import { BreadcrumbContainer } from '../../hooks/use_breadcrumbs'; +import { initSetupModeState } from '../../setup_mode/setup_mode'; +import { GlobalStateContext } from '../../global_state_context'; + +const CODE_PATHS = [CODE_PATH_LICENSE]; + +interface NoDataPageSetupDeps { + http: any; + data: any; +} + +interface SettingsChecker { + message: string; + api: string; + next?: SettingsChecker; +} + +const clusterCheckers: SettingsChecker[] = [ + { + message: i18n.translate('xpack.monitoring.noData.checker.clusterSettings', { + defaultMessage: 'Checking cluster settings API on production cluster', + }), + api: '../api/monitoring/v1/elasticsearch_settings/check/cluster', + }, + { + message: i18n.translate('xpack.monitoring.noData.checker.nodesSettings', { + defaultMessage: 'Checking nodes settings API on production cluster', + }), + api: '../api/monitoring/v1/elasticsearch_settings/check/nodes', + }, +]; + +export const NoDataPage = () => { + const title = i18n.translate('xpack.monitoring.noData.routeTitle', { + defaultMessage: 'Setup Monitoring', + }); + + const { services } = useKibana(); + const [shouldRedirect, setShouldRedirect] = useState(false); + + const [model, setModel] = useState({ + errors: [], // errors can happen from trying to check or set ES settings + checkMessage: null, // message to show while waiting for api response + isLoading: true, // flag for in-progress state of checking for no data reason + isCollectionEnabledUpdating: false, // flags to indicate whether to show a spinner while waiting for ajax + isCollectionEnabledUpdated: false, + isCollectionIntervalUpdating: false, + isCollectionIntervalUpdated: false, + } as any); + + const { update: updateBreadcrumbs } = useContext(BreadcrumbContainer.Context); + updateBreadcrumbs([ + { + 'data-test-subj': 'breadcrumbClusters', + text: 'Clusters', + href: '#/home', + ignoreGlobalState: true, + }, + ]); + + const globalState = useContext(GlobalStateContext); + initSetupModeState(globalState, services.http); + + // From x-pack/plugins/monitoring/public/views/no_data/model_updater.js + const updateModel = useCallback( + (properties: any) => { + setModel((previousModel: any) => { + const updated = { ...previousModel }; + const keys = Object.keys(properties); + + keys.forEach((key) => { + if (Array.isArray(updated[key])) { + updated[key].push(properties[key]); + } else { + updated[key] = properties[key]; + } + }); + + return updated; + }); + }, + [setModel] + ); + + const getPageData = useCallback(async () => { + let catchReason; + try { + const clusters = await getClusters(services); + + if (clusters && clusters.length) { + setShouldRedirect(true); + return; + } + } catch (err) { + if (err && err.status === 503) { + catchReason = { + property: 'custom', + message: err.data.message, + }; + } + } + + if (catchReason) { + updateModel({ reason: catchReason }); + } else { + await startChecks(clusterCheckers, services.http, updateModel); + } + }, [services, updateModel]); + + const enabler = new Enabler(services.http, updateModel); + + return ( + + {shouldRedirect ? ( + + ) : ( + + )} + + ); +}; + +async function getClusters(services: NoDataPageSetupDeps): Promise { + const url = '../api/monitoring/v1/clusters'; + const bounds = services.data?.query.timefilter.timefilter.getBounds(); + const min = bounds.min.toISOString(); + const max = bounds.max.toISOString(); + + const response = await services.http?.fetch(url, { + method: 'POST', + body: JSON.stringify({ + css: undefined, + timeRange: { + min, + max, + }, + codePaths: CODE_PATHS, + }), + }); + + return formatClusters(response); +} + +// From x-pack/plugins/monitoring/public/lib/elasticsearch_settings/start_checks.js +const mapCheckers = (_checkers: SettingsChecker[]) => { + return _checkers.map((current, checkerIndex) => { + const next = _checkers[checkerIndex + 1]; + if (next !== undefined) { + current.next = next; + } + + return current; + }); +}; + +// From x-pack/plugins/monitoring/public/lib/elasticsearch_settings/start_checks.js +function startChecks( + checkers: SettingsChecker[], + http: { fetch: any }, + updateModel: (properties: any) => void +) { + const runCheck = async (currentChecker: SettingsChecker): Promise => { + updateModel({ checkMessage: currentChecker.message }); + + const { found, reason, error, errorReason } = await executeCheck(currentChecker, http); + + if (error) { + updateModel({ errors: errorReason }); + if (currentChecker.next) { + return runCheck(currentChecker.next); + } + } else if (found) { + return updateModel({ + reason, + isLoading: false, + checkMessage: null, + }); + } else if (currentChecker.next) { + return runCheck(currentChecker.next); + } + + // dead end + updateModel({ + reason: null, + isLoading: false, + checkMessage: null, + }); + }; + + const _checkers = mapCheckers(checkers); + return runCheck(_checkers[0]); +} + +async function executeCheck(checker: SettingsChecker, http: { fetch: any }): Promise { + try { + const response = await http.fetch(checker.api, { + method: 'GET', + }); + const { found, reason } = response; + + return { found, reason }; + } catch (err: any) { + const { data } = err; + + return { + error: true, + found: false, + errorReason: data, + }; + } +} + +function formatClusters(clusters: any): any[] { + return clusters.map(formatCluster); +} + +function formatCluster(cluster: any) { + if (cluster.cluster_uuid === STANDALONE_CLUSTER_CLUSTER_UUID) { + cluster.cluster_name = 'Standalone Cluster'; + } + return cluster; +} diff --git a/x-pack/plugins/monitoring/public/application/pages/page_template.tsx b/x-pack/plugins/monitoring/public/application/pages/page_template.tsx index 9ce717b37051b..7c6a6c56a1322 100644 --- a/x-pack/plugins/monitoring/public/application/pages/page_template.tsx +++ b/x-pack/plugins/monitoring/public/application/pages/page_template.tsx @@ -7,24 +7,26 @@ import { EuiTab, EuiTabs } from '@elastic/eui'; import React, { useContext, useState, useEffect } from 'react'; +import { useHistory } from 'react-router-dom'; import { useTitle } from '../hooks/use_title'; import { MonitoringToolbar } from '../../components/shared/toolbar'; import { MonitoringTimeContainer } from '../hooks/use_monitoring_time'; import { PageLoading } from '../../components'; +import { getSetupModeState, isSetupModeFeatureEnabled } from '../setup_mode/setup_mode'; +import { SetupModeFeature } from '../../../common/enums'; export interface TabMenuItem { id: string; label: string; - description: string; - disabled: boolean; - onClick: () => void; - testSubj: string; + testSubj?: string; + route: string; } -interface PageTemplateProps { +export interface PageTemplateProps { title: string; pageTitle?: string; tabs?: TabMenuItem[]; getPageData?: () => Promise; + product?: string; } export const PageTemplate: React.FC = ({ @@ -32,12 +34,14 @@ export const PageTemplate: React.FC = ({ pageTitle, tabs, getPageData, + product, children, }) => { useTitle('', title); const { currentTimerange } = useContext(MonitoringTimeContainer.Context); const [loaded, setLoaded] = useState(false); + const history = useHistory(); useEffect(() => { getPageData?.() @@ -55,6 +59,10 @@ export const PageTemplate: React.FC = ({ }); }; + const createHref = (route: string) => history.createHref({ pathname: route }); + + const isTabSelected = (route: string) => history.location.pathname === route; + return (
@@ -64,10 +72,11 @@ export const PageTemplate: React.FC = ({ return ( {item.label} @@ -79,3 +88,31 @@ export const PageTemplate: React.FC = ({
); }; + +function isDisabledTab(product: string | undefined) { + const setupMode = getSetupModeState(); + if (!isSetupModeFeatureEnabled(SetupModeFeature.MetricbeatMigration)) { + return false; + } + + if (!setupMode.data) { + return false; + } + + if (!product) { + return false; + } + + const data = setupMode.data[product] || {}; + if (data.totalUniqueInstanceCount === 0) { + return true; + } + if ( + data.totalUniqueInternallyCollectedCount === 0 && + data.totalUniqueFullyMigratedCount === 0 && + data.totalUniquePartiallyMigratedCount === 0 + ) { + return true; + } + return false; +} diff --git a/x-pack/plugins/monitoring/public/application/route_init.tsx b/x-pack/plugins/monitoring/public/application/route_init.tsx index cf3b0c6646d0f..8a9a906dbd563 100644 --- a/x-pack/plugins/monitoring/public/application/route_init.tsx +++ b/x-pack/plugins/monitoring/public/application/route_init.tsx @@ -10,9 +10,12 @@ import { useClusters } from './hooks/use_clusters'; import { GlobalStateContext } from './global_state_context'; import { getClusterFromClusters } from '../lib/get_cluster_from_clusters'; +export interface ComponentProps { + clusters: []; +} interface RouteInitProps { path: string; - component: React.ComponentType; + component: React.ComponentType; codePaths: string[]; fetchAllClusters: boolean; unsetGlobalState?: boolean; @@ -58,7 +61,12 @@ export const RouteInit: React.FC = ({ } } - return loaded ? : null; + const Component = component; + return loaded ? ( + + + + ) : null; }; const isExpired = (license: any): boolean => { diff --git a/x-pack/plugins/monitoring/public/components/chart/get_chart_options.js b/x-pack/plugins/monitoring/public/components/chart/get_chart_options.js index 5cf983829b5e0..641125dd3e943 100644 --- a/x-pack/plugins/monitoring/public/components/chart/get_chart_options.js +++ b/x-pack/plugins/monitoring/public/components/chart/get_chart_options.js @@ -10,8 +10,17 @@ import { merge } from 'lodash'; import { CHART_LINE_COLOR, CHART_TEXT_COLOR } from '../../../common/constants'; export async function getChartOptions(axisOptions) { - const $injector = Legacy.shims.getAngularInjector(); - const timezone = $injector.get('config').get('dateFormat:tz'); + let timezone; + try { + const $injector = Legacy.shims.getAngularInjector(); + timezone = $injector.get('config').get('dateFormat:tz'); + } catch (error) { + if (error.message === 'Angular has been removed.') { + timezone = Legacy.shims.uiSettings?.get('dateFormat:tz'); + } else { + throw error; + } + } const opts = { legend: { show: false, diff --git a/x-pack/plugins/uptime/common/runtime_types/overview_filters/index.ts b/x-pack/plugins/monitoring/public/components/elasticsearch/index.d.ts similarity index 77% rename from x-pack/plugins/uptime/common/runtime_types/overview_filters/index.ts rename to x-pack/plugins/monitoring/public/components/elasticsearch/index.d.ts index ed97b8902c879..4460b8432134b 100644 --- a/x-pack/plugins/uptime/common/runtime_types/overview_filters/index.ts +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/index.d.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { OverviewFiltersType, OverviewFilters } from './overview_filters'; +export const ElasticsearchOverview: FunctionComponent; diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js b/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js index 18f1ea383c3f2..8a4a8a3c63d9d 100644 --- a/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js @@ -298,15 +298,8 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid, aler }; export function ElasticsearchNodes({ clusterStatus, showCgroupMetricsElasticsearch, ...props }) { - const { - sorting, - pagination, - onTableChange, - clusterUuid, - setupMode, - fetchMoreData, - alerts, - } = props; + const { sorting, pagination, onTableChange, clusterUuid, setupMode, fetchMoreData, alerts } = + props; const columns = getColumns(showCgroupMetricsElasticsearch, setupMode, clusterUuid, alerts); diff --git a/x-pack/plugins/monitoring/public/components/kuery_bar/autocomplete_field.tsx b/x-pack/plugins/monitoring/public/components/kuery_bar/autocomplete_field.tsx new file mode 100644 index 0000000000000..522256ea49b98 --- /dev/null +++ b/x-pack/plugins/monitoring/public/components/kuery_bar/autocomplete_field.tsx @@ -0,0 +1,316 @@ +/* + * 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 { EuiFieldSearch, EuiOutsideClickDetector, EuiPanel } from '@elastic/eui'; +import React from 'react'; +import { QuerySuggestion } from '../../../../../../src/plugins/data/public'; +import { euiStyled } from '../../../../../../src/plugins/kibana_react/common'; +import { composeStateUpdaters } from '../../lib/typed_react'; +import { SuggestionItem } from './suggestion_item'; + +interface AutocompleteFieldProps { + isLoadingSuggestions: boolean; + isValid: boolean; + loadSuggestions: (value: string, cursorPosition: number, maxCount?: number) => void; + onSubmit?: (value: string) => void; + onChange?: (value: string) => void; + placeholder?: string; + suggestions: QuerySuggestion[]; + value: string; + disabled?: boolean; + autoFocus?: boolean; + 'aria-label'?: string; +} + +interface AutocompleteFieldState { + areSuggestionsVisible: boolean; + isFocused: boolean; + selectedIndex: number | null; +} + +export class AutocompleteField extends React.Component< + AutocompleteFieldProps, + AutocompleteFieldState +> { + public readonly state: AutocompleteFieldState = { + areSuggestionsVisible: false, + isFocused: false, + selectedIndex: null, + }; + + private inputElement: HTMLInputElement | null = null; + + public render() { + const { + suggestions, + isLoadingSuggestions, + isValid, + placeholder, + value, + disabled, + 'aria-label': ariaLabel, + } = this.props; + const { areSuggestionsVisible, selectedIndex } = this.state; + + return ( + + + + {areSuggestionsVisible && !isLoadingSuggestions && suggestions.length > 0 ? ( + + {suggestions.map((suggestion, suggestionIndex) => ( + + ))} + + ) : null} + + + ); + } + + public componentDidMount() { + if (this.inputElement && this.props.autoFocus) { + this.inputElement.focus(); + } + } + + public componentDidUpdate(prevProps: AutocompleteFieldProps) { + const hasNewValue = prevProps.value !== this.props.value; + const hasNewSuggestions = prevProps.suggestions !== this.props.suggestions; + + if (hasNewValue) { + this.updateSuggestions(); + } + + if (hasNewValue && this.props.value === '') { + this.submit(); + } + + if (hasNewSuggestions && this.state.isFocused) { + this.showSuggestions(); + } + } + + private handleChangeInputRef = (element: HTMLInputElement | null) => { + this.inputElement = element; + }; + + private handleChange = (evt: React.ChangeEvent) => { + this.changeValue(evt.currentTarget.value); + }; + + private handleKeyDown = (evt: React.KeyboardEvent) => { + const { suggestions } = this.props; + + switch (evt.key) { + case 'ArrowUp': + evt.preventDefault(); + if (suggestions.length > 0) { + this.setState( + composeStateUpdaters(withSuggestionsVisible, withPreviousSuggestionSelected) + ); + } + break; + case 'ArrowDown': + evt.preventDefault(); + if (suggestions.length > 0) { + this.setState(composeStateUpdaters(withSuggestionsVisible, withNextSuggestionSelected)); + } else { + this.updateSuggestions(); + } + break; + case 'Enter': + evt.preventDefault(); + if (this.state.selectedIndex !== null) { + this.applySelectedSuggestion(); + } else { + this.submit(); + } + break; + case 'Escape': + evt.preventDefault(); + this.setState(withSuggestionsHidden); + break; + } + }; + + private handleKeyUp = (evt: React.KeyboardEvent) => { + switch (evt.key) { + case 'ArrowLeft': + case 'ArrowRight': + case 'Home': + case 'End': + this.updateSuggestions(); + break; + } + }; + + private handleFocus = () => { + this.setState(composeStateUpdaters(withSuggestionsVisible, withFocused)); + }; + + private handleBlur = () => { + this.setState(composeStateUpdaters(withSuggestionsHidden, withUnfocused)); + }; + + private selectSuggestionAt = (index: number) => () => { + this.setState(withSuggestionAtIndexSelected(index)); + }; + + private applySelectedSuggestion = () => { + if (this.state.selectedIndex !== null) { + this.applySuggestionAt(this.state.selectedIndex)(); + } + }; + + private applySuggestionAt = (index: number) => () => { + const { value, suggestions } = this.props; + const selectedSuggestion = suggestions[index]; + + if (!selectedSuggestion) { + return; + } + + const newValue = + value.substr(0, selectedSuggestion.start) + + selectedSuggestion.text + + value.substr(selectedSuggestion.end); + + this.setState(withSuggestionsHidden); + this.changeValue(newValue); + this.focusInputElement(); + }; + + private changeValue = (value: string) => { + const { onChange } = this.props; + + if (onChange) { + onChange(value); + } + }; + + private focusInputElement = () => { + if (this.inputElement) { + this.inputElement.focus(); + } + }; + + private showSuggestions = () => { + this.setState(withSuggestionsVisible); + }; + + private submit = () => { + const { isValid, onSubmit, value } = this.props; + + if (isValid && onSubmit) { + onSubmit(value); + } + + this.setState(withSuggestionsHidden); + }; + + private updateSuggestions = () => { + const inputCursorPosition = this.inputElement ? this.inputElement.selectionStart || 0 : 0; + this.props.loadSuggestions(this.props.value, inputCursorPosition, 200); + }; +} + +const withPreviousSuggestionSelected = ( + state: AutocompleteFieldState, + props: AutocompleteFieldProps +): AutocompleteFieldState => ({ + ...state, + selectedIndex: + props.suggestions.length === 0 + ? null + : state.selectedIndex !== null + ? (state.selectedIndex + props.suggestions.length - 1) % props.suggestions.length + : Math.max(props.suggestions.length - 1, 0), +}); + +const withNextSuggestionSelected = ( + state: AutocompleteFieldState, + props: AutocompleteFieldProps +): AutocompleteFieldState => ({ + ...state, + selectedIndex: + props.suggestions.length === 0 + ? null + : state.selectedIndex !== null + ? (state.selectedIndex + 1) % props.suggestions.length + : 0, +}); + +const withSuggestionAtIndexSelected = + (suggestionIndex: number) => + (state: AutocompleteFieldState, props: AutocompleteFieldProps): AutocompleteFieldState => ({ + ...state, + selectedIndex: + props.suggestions.length === 0 + ? null + : suggestionIndex >= 0 && suggestionIndex < props.suggestions.length + ? suggestionIndex + : 0, + }); + +const withSuggestionsVisible = (state: AutocompleteFieldState) => ({ + ...state, + areSuggestionsVisible: true, +}); + +const withSuggestionsHidden = (state: AutocompleteFieldState) => ({ + ...state, + areSuggestionsVisible: false, + selectedIndex: null, +}); + +const withFocused = (state: AutocompleteFieldState) => ({ + ...state, + isFocused: true, +}); + +const withUnfocused = (state: AutocompleteFieldState) => ({ + ...state, + isFocused: false, +}); + +const AutocompleteContainer = euiStyled.div` + position: relative; +`; + +const SuggestionsPanel = euiStyled(EuiPanel).attrs(() => ({ + paddingSize: 'none', + hasShadow: true, +}))` + position: absolute; + width: 100%; + margin-top: 2px; + overflow-x: hidden; + overflow-y: scroll; + z-index: ${(props) => props.theme.eui.euiZLevel1}; + max-height: 322px; +`; diff --git a/x-pack/plugins/monitoring/public/components/kuery_bar/index.tsx b/x-pack/plugins/monitoring/public/components/kuery_bar/index.tsx new file mode 100644 index 0000000000000..ca0a8122772f3 --- /dev/null +++ b/x-pack/plugins/monitoring/public/components/kuery_bar/index.tsx @@ -0,0 +1,98 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +import React, { useEffect, useState } from 'react'; +import { WithKueryAutocompletion } from './with_kuery_autocompletion'; +import { AutocompleteField } from './autocomplete_field'; +import { esKuery, IIndexPattern, QuerySuggestion } from '../../../../../../src/plugins/data/public'; + +type LoadSuggestionsFn = ( + e: string, + p: number, + m?: number, + transform?: (s: QuerySuggestion[]) => QuerySuggestion[] +) => void; +export type CurryLoadSuggestionsType = (loadSuggestions: LoadSuggestionsFn) => LoadSuggestionsFn; + +interface Props { + derivedIndexPattern: IIndexPattern; + onSubmit: (query: string) => void; + onChange?: (query: string) => void; + value?: string | null; + placeholder?: string; + curryLoadSuggestions?: CurryLoadSuggestionsType; +} + +function validateQuery(query: string) { + try { + esKuery.fromKueryExpression(query); + } catch (err) { + return false; + } + return true; +} + +export const KueryBar = ({ + derivedIndexPattern, + onSubmit, + onChange, + value, + placeholder, + curryLoadSuggestions = defaultCurryLoadSuggestions, +}: Props) => { + const [draftQuery, setDraftQuery] = useState(value || ''); + const [isValid, setValidation] = useState(true); + + // This ensures that if value changes out side this component it will update. + useEffect(() => { + if (value) { + setDraftQuery(value); + } + }, [value]); + + const handleChange = (query: string) => { + setValidation(validateQuery(query)); + setDraftQuery(query); + if (onChange) { + onChange(query); + } + }; + + const filteredDerivedIndexPattern = { + ...derivedIndexPattern, + fields: derivedIndexPattern.fields, + }; + + const defaultPlaceholder = i18n.translate('xpack.monitoring.alerts.kqlSearchFieldPlaceholder', { + defaultMessage: 'Search for monitoring data', + }); + + return ( + + {({ isLoadingSuggestions, loadSuggestions, suggestions }) => ( + + )} + + ); +}; + +const defaultCurryLoadSuggestions: CurryLoadSuggestionsType = + (loadSuggestions) => + (...args) => + loadSuggestions(...args); diff --git a/x-pack/plugins/monitoring/public/components/kuery_bar/suggestion_item.tsx b/x-pack/plugins/monitoring/public/components/kuery_bar/suggestion_item.tsx new file mode 100644 index 0000000000000..3681bf26987cc --- /dev/null +++ b/x-pack/plugins/monitoring/public/components/kuery_bar/suggestion_item.tsx @@ -0,0 +1,119 @@ +/* + * 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 { EuiIcon } from '@elastic/eui'; +import { transparentize } from 'polished'; +import React from 'react'; +import { euiStyled } from '../../../../../../src/plugins/kibana_react/common'; +import { QuerySuggestion, QuerySuggestionTypes } from '../../../../../../src/plugins/data/public'; + +interface Props { + isSelected?: boolean; + onClick?: React.MouseEventHandler; + onMouseEnter?: React.MouseEventHandler; + suggestion: QuerySuggestion; +} + +export const SuggestionItem: React.FC = (props) => { + const { isSelected, onClick, onMouseEnter, suggestion } = props; + + return ( + + + + + {suggestion.text} + {suggestion.description} + + ); +}; + +SuggestionItem.defaultProps = { + isSelected: false, +}; + +const SuggestionItemContainer = euiStyled.div<{ + isSelected?: boolean; +}>` + display: flex; + flex-direction: row; + font-size: ${(props) => props.theme.eui.euiFontSizeS}; + height: ${(props) => props.theme.eui.euiSizeXL}; + white-space: nowrap; + background-color: ${(props) => + props.isSelected ? props.theme.eui.euiColorLightestShade : 'transparent'}; +`; + +const SuggestionItemField = euiStyled.div` + align-items: center; + cursor: pointer; + display: flex; + flex-direction: row; + height: ${(props) => props.theme.eui.euiSizeXL}; + padding: ${(props) => props.theme.eui.euiSizeXS}; +`; + +const SuggestionItemIconField = euiStyled(SuggestionItemField)<{ + suggestionType: QuerySuggestionTypes; +}>` + background-color: ${(props) => + transparentize(0.9, getEuiIconColor(props.theme, props.suggestionType))}; + color: ${(props) => getEuiIconColor(props.theme, props.suggestionType)}; + flex: 0 0 auto; + justify-content: center; + width: ${(props) => props.theme.eui.euiSizeXL}; +`; + +const SuggestionItemTextField = euiStyled(SuggestionItemField)` + flex: 2 0 0; + font-family: ${(props) => props.theme.eui.euiCodeFontFamily}; +`; + +const SuggestionItemDescriptionField = euiStyled(SuggestionItemField)` + flex: 3 0 0; + + p { + display: inline; + + span { + font-family: ${(props) => props.theme.eui.euiCodeFontFamily}; + } + } +`; + +const getEuiIconType = (suggestionType: QuerySuggestionTypes) => { + switch (suggestionType) { + case QuerySuggestionTypes.Field: + return 'kqlField'; + case QuerySuggestionTypes.Value: + return 'kqlValue'; + case QuerySuggestionTypes.RecentSearch: + return 'search'; + case QuerySuggestionTypes.Conjunction: + return 'kqlSelector'; + case QuerySuggestionTypes.Operator: + return 'kqlOperand'; + default: + return 'empty'; + } +}; + +const getEuiIconColor = (theme: any, suggestionType: QuerySuggestionTypes): string => { + switch (suggestionType) { + case QuerySuggestionTypes.Field: + return theme?.eui.euiColorVis7; + case QuerySuggestionTypes.Value: + return theme?.eui.euiColorVis0; + case QuerySuggestionTypes.Operator: + return theme?.eui.euiColorVis1; + case QuerySuggestionTypes.Conjunction: + return theme?.eui.euiColorVis2; + case QuerySuggestionTypes.RecentSearch: + default: + return theme?.eui.euiColorMediumShade; + } +}; diff --git a/x-pack/plugins/monitoring/public/components/kuery_bar/with_kuery_autocompletion.tsx b/x-pack/plugins/monitoring/public/components/kuery_bar/with_kuery_autocompletion.tsx new file mode 100644 index 0000000000000..8d79bf4039846 --- /dev/null +++ b/x-pack/plugins/monitoring/public/components/kuery_bar/with_kuery_autocompletion.tsx @@ -0,0 +1,111 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { QuerySuggestion, IIndexPattern, DataPublicPluginStart } from 'src/plugins/data/public'; +import { + withKibana, + KibanaReactContextValue, + KibanaServices, +} from '../../../../../../src/plugins/kibana_react/public'; +import { RendererFunction } from '../../lib/typed_react'; + +interface WithKueryAutocompletionLifecycleProps { + kibana: KibanaReactContextValue<{ data: DataPublicPluginStart } & KibanaServices>; + children: RendererFunction<{ + isLoadingSuggestions: boolean; + loadSuggestions: (expression: string, cursorPosition: number, maxSuggestions?: number) => void; + suggestions: QuerySuggestion[]; + }>; + indexPattern: IIndexPattern; +} + +interface WithKueryAutocompletionLifecycleState { + // lacking cancellation support in the autocompletion api, + // this is used to keep older, slower requests from clobbering newer ones + currentRequest: { + expression: string; + cursorPosition: number; + } | null; + suggestions: QuerySuggestion[]; +} + +class WithKueryAutocompletionComponent extends React.Component< + WithKueryAutocompletionLifecycleProps, + WithKueryAutocompletionLifecycleState +> { + public readonly state: WithKueryAutocompletionLifecycleState = { + currentRequest: null, + suggestions: [], + }; + + public render() { + const { currentRequest, suggestions } = this.state; + + return this.props.children({ + isLoadingSuggestions: currentRequest !== null, + loadSuggestions: this.loadSuggestions, + suggestions, + }); + } + + private loadSuggestions = async ( + expression: string, + cursorPosition: number, + maxSuggestions?: number, + transformSuggestions?: (s: QuerySuggestion[]) => QuerySuggestion[] + ) => { + const { indexPattern } = this.props; + const language = 'kuery'; + const hasQuerySuggestions = + this.props.kibana.services.data?.autocomplete.hasQuerySuggestions(language); + + if (!hasQuerySuggestions) { + return; + } + + this.setState({ + currentRequest: { + expression, + cursorPosition, + }, + suggestions: [], + }); + + const suggestions = + (await this.props.kibana.services.data.autocomplete.getQuerySuggestions({ + language, + query: expression, + selectionStart: cursorPosition, + selectionEnd: cursorPosition, + indexPatterns: [indexPattern], + boolFilter: [], + })) || []; + + const transformedSuggestions = transformSuggestions + ? transformSuggestions(suggestions) + : suggestions; + + this.setState((state) => + state.currentRequest && + state.currentRequest.expression !== expression && + state.currentRequest.cursorPosition !== cursorPosition + ? state // ignore this result, since a newer request is in flight + : { + ...state, + currentRequest: null, + suggestions: maxSuggestions + ? transformedSuggestions.slice(0, maxSuggestions) + : transformedSuggestions, + } + ); + }; +} + +export const WithKueryAutocompletion = withKibana( + WithKueryAutocompletionComponent +); diff --git a/x-pack/plugins/monitoring/public/components/logs/logs.js b/x-pack/plugins/monitoring/public/components/logs/logs.js index 409b773a24856..3021240a157d3 100644 --- a/x-pack/plugins/monitoring/public/components/logs/logs.js +++ b/x-pack/plugins/monitoring/public/components/logs/logs.js @@ -16,9 +16,18 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { Reason } from './reason'; const getFormattedDateTimeLocal = (timestamp) => { - const injector = Legacy.shims.getAngularInjector(); - const timezone = injector.get('config').get('dateFormat:tz'); - return formatDateTimeLocal(timestamp, timezone); + try { + const injector = Legacy.shims.getAngularInjector(); + const timezone = injector.get('config').get('dateFormat:tz'); + return formatDateTimeLocal(timestamp, timezone); + } catch (error) { + if (error.message === 'Angular has been removed.') { + const timezone = Legacy.shims.uiSettings?.get('dateFormat:tz'); + return formatDateTimeLocal(timestamp, timezone); + } else { + throw error; + } + } }; const columnTimestampTitle = i18n.translate('xpack.monitoring.logs.listing.timestampTitle', { diff --git a/x-pack/plugins/monitoring/public/components/logstash/pipeline_listing/pipeline_listing.js b/x-pack/plugins/monitoring/public/components/logstash/pipeline_listing/pipeline_listing.js index 3c019c9356a74..50a4d3f8cf32b 100644 --- a/x-pack/plugins/monitoring/public/components/logstash/pipeline_listing/pipeline_listing.js +++ b/x-pack/plugins/monitoring/public/components/logstash/pipeline_listing/pipeline_listing.js @@ -129,15 +129,8 @@ export class PipelineListing extends Component { } render() { - const { - data, - sorting, - pagination, - onTableChange, - fetchMoreData, - upgradeMessage, - className, - } = this.props; + const { data, sorting, pagination, onTableChange, fetchMoreData, upgradeMessage, className } = + this.props; const sortingOptions = sorting || { field: 'id', direction: 'asc' }; if (sortingOptions.field === 'name') { diff --git a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/plugin_statement.js b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/plugin_statement.js index b39f4c530eb57..9d36daa6860f8 100644 --- a/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/plugin_statement.js +++ b/x-pack/plugins/monitoring/public/components/logstash/pipeline_viewer/views/plugin_statement.js @@ -24,11 +24,8 @@ function getInputStatementMetrics({ latestEventsPerSecond }) { } function getProcessorStatementMetrics(processorVertex) { - const { - latestMillisPerEvent, - latestEventsPerSecond, - percentOfTotalProcessorTime, - } = processorVertex; + const { latestMillisPerEvent, latestEventsPerSecond, percentOfTotalProcessorTime } = + processorVertex; return [ ; } diff --git a/x-pack/plugins/monitoring/public/lib/get_safe_for_external_link.test.ts b/x-pack/plugins/monitoring/public/lib/get_safe_for_external_link.test.ts index b25374eec18f9..cef1a61117a67 100644 --- a/x-pack/plugins/monitoring/public/lib/get_safe_for_external_link.test.ts +++ b/x-pack/plugins/monitoring/public/lib/get_safe_for_external_link.test.ts @@ -31,8 +31,7 @@ describe('getSafeForExternalLink', () => { it('should work with existing query string', async () => { const location = { - hash: - '#/overview?_g=(cluster_uuid:ae2womLaSMmZBioEQ9wFjw,refreshInterval:(pause:!t,value:10000),time:(from:now-1h,to:now))', + hash: '#/overview?_g=(cluster_uuid:ae2womLaSMmZBioEQ9wFjw,refreshInterval:(pause:!t,value:10000),time:(from:now-1h,to:now))', }; expect(getSafeForExternalLink('#/overview', {}, location)).toBe( '#/overview?_g=(cluster_uuid:ae2womLaSMmZBioEQ9wFjw,refreshInterval:(pause:!t,value:10000),time:(from:now-1h,to:now))' diff --git a/x-pack/plugins/monitoring/public/lib/kuery.ts b/x-pack/plugins/monitoring/public/lib/kuery.ts new file mode 100644 index 0000000000000..19706d7664c22 --- /dev/null +++ b/x-pack/plugins/monitoring/public/lib/kuery.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { esKuery, IIndexPattern } from '../../../../../src/plugins/data/public'; + +export const convertKueryToElasticSearchQuery = ( + kueryExpression: string, + indexPattern: IIndexPattern +) => { + try { + return kueryExpression + ? JSON.stringify( + esKuery.toElasticsearchQuery(esKuery.fromKueryExpression(kueryExpression), indexPattern) + ) + : ''; + } catch (err) { + return ''; + } +}; diff --git a/x-pack/plugins/monitoring/public/lib/setup_mode.tsx b/x-pack/plugins/monitoring/public/lib/setup_mode.tsx index f622f2944a31a..fca7f94731bc5 100644 --- a/x-pack/plugins/monitoring/public/lib/setup_mode.tsx +++ b/x-pack/plugins/monitoring/public/lib/setup_mode.tsx @@ -159,6 +159,8 @@ export const disableElasticsearchInternalCollection = async () => { }; export const toggleSetupMode = (inSetupMode: boolean) => { + if (isReactMigrationEnabled()) return setupModeReact.toggleSetupMode(inSetupMode); + checkAngularState(); const globalState = angularState.injector.get('globalState'); diff --git a/x-pack/plugins/monitoring/public/lib/typed_react.tsx b/x-pack/plugins/monitoring/public/lib/typed_react.tsx new file mode 100644 index 0000000000000..b5b7a440c117c --- /dev/null +++ b/x-pack/plugins/monitoring/public/lib/typed_react.tsx @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { omit } from 'lodash'; +import React from 'react'; +import { InferableComponentEnhancerWithProps, ConnectedComponent } from 'react-redux'; + +export type RendererResult = React.ReactElement | null; +export type RendererFunction = (args: RenderArgs) => Result; + +export type ChildFunctionRendererProps = { + children: RendererFunction; + initializeOnMount?: boolean; + resetOnUnmount?: boolean; +} & RenderArgs; + +interface ChildFunctionRendererOptions { + onInitialize?: (props: RenderArgs) => void; + onCleanup?: (props: RenderArgs) => void; +} + +export const asChildFunctionRenderer = ( + hoc: InferableComponentEnhancerWithProps, + { onInitialize, onCleanup }: ChildFunctionRendererOptions = {} +): ConnectedComponent< + React.ComponentClass<{}>, + { + children: RendererFunction; + initializeOnMount?: boolean; + resetOnUnmount?: boolean; + } & OwnProps +> => + hoc( + class ChildFunctionRenderer extends React.Component> { + public displayName = 'ChildFunctionRenderer'; + + public componentDidMount() { + if (this.props.initializeOnMount && onInitialize) { + onInitialize(this.getRendererArgs()); + } + } + + public componentWillUnmount() { + if (this.props.resetOnUnmount && onCleanup) { + onCleanup(this.getRendererArgs()); + } + } + + public render() { + return (this.props.children as ChildFunctionRendererProps['children'])( + this.getRendererArgs() + ); + } + + private getRendererArgs = () => + omit(this.props, ['children', 'initializeOnMount', 'resetOnUnmount']) as Pick< + ChildFunctionRendererProps, + keyof InjectedProps + >; + } as any + ); + +export type StateUpdater = ( + prevState: Readonly, + prevProps: Readonly +) => State | null; + +export type PropsOfContainer = Container extends InferableComponentEnhancerWithProps< + infer InjectedProps, + any +> + ? InjectedProps + : never; + +export function composeStateUpdaters(...updaters: Array>) { + return (state: State, props: Props) => + updaters.reduce((currentState, updater) => updater(currentState, props) || currentState, state); +} diff --git a/x-pack/plugins/monitoring/public/plugin.ts b/x-pack/plugins/monitoring/public/plugin.ts index 6f625194287ba..aee5072947531 100644 --- a/x-pack/plugins/monitoring/public/plugin.ts +++ b/x-pack/plugins/monitoring/public/plugin.ts @@ -28,14 +28,6 @@ import { RULE_THREAD_POOL_WRITE_REJECTIONS, RULE_DETAILS, } from '../common/constants'; -import { createCpuUsageAlertType } from './alerts/cpu_usage_alert'; -import { createMissingMonitoringDataAlertType } from './alerts/missing_monitoring_data_alert'; -import { createLegacyAlertTypes } from './alerts/legacy_alert'; -import { createDiskUsageAlertType } from './alerts/disk_usage_alert'; -import { createThreadPoolRejectionsAlertType } from './alerts/thread_pool_rejections_alert'; -import { createMemoryUsageAlertType } from './alerts/memory_usage_alert'; -import { createCCRReadExceptionsAlertType } from './alerts/ccr_read_exceptions_alert'; -import { createLargeShardSizeAlertType } from './alerts/large_shard_size_alert'; import { setConfig } from './external_config'; interface MonitoringSetupPluginDependencies { @@ -49,10 +41,11 @@ const HASH_CHANGE = 'hashchange'; export class MonitoringPlugin implements - Plugin { + Plugin +{ constructor(private initializerContext: PluginInitializerContext) {} - public setup( + public async setup( core: CoreSetup, plugins: MonitoringSetupPluginDependencies ) { @@ -85,7 +78,7 @@ export class MonitoringPlugin }); } - this.registerAlerts(plugins); + await this.registerAlerts(plugins, monitoring); const app: App = { id, @@ -151,7 +144,6 @@ export class MonitoringPlugin }; core.application.register(app); - return true; } public start(core: CoreStart, plugins: any) {} @@ -191,29 +183,48 @@ export class MonitoringPlugin ]; } - private registerAlerts(plugins: MonitoringSetupPluginDependencies) { + private async registerAlerts( + plugins: MonitoringSetupPluginDependencies, + config: MonitoringConfig + ) { const { triggersActionsUi: { ruleTypeRegistry }, } = plugins; - ruleTypeRegistry.register(createCpuUsageAlertType()); - ruleTypeRegistry.register(createDiskUsageAlertType()); - ruleTypeRegistry.register(createMemoryUsageAlertType()); + + const { createCpuUsageAlertType } = await import('./alerts/cpu_usage_alert'); + const { createMissingMonitoringDataAlertType } = await import( + './alerts/missing_monitoring_data_alert' + ); + const { createLegacyAlertTypes } = await import('./alerts/legacy_alert'); + const { createDiskUsageAlertType } = await import('./alerts/disk_usage_alert'); + const { createThreadPoolRejectionsAlertType } = await import( + './alerts/thread_pool_rejections_alert' + ); + const { createMemoryUsageAlertType } = await import('./alerts/memory_usage_alert'); + const { createCCRReadExceptionsAlertType } = await import('./alerts/ccr_read_exceptions_alert'); + const { createLargeShardSizeAlertType } = await import('./alerts/large_shard_size_alert'); + + ruleTypeRegistry.register(createCpuUsageAlertType(config)); + ruleTypeRegistry.register(createDiskUsageAlertType(config)); + ruleTypeRegistry.register(createMemoryUsageAlertType(config)); ruleTypeRegistry.register(createMissingMonitoringDataAlertType()); ruleTypeRegistry.register( createThreadPoolRejectionsAlertType( RULE_THREAD_POOL_SEARCH_REJECTIONS, - RULE_DETAILS[RULE_THREAD_POOL_SEARCH_REJECTIONS] + RULE_DETAILS[RULE_THREAD_POOL_SEARCH_REJECTIONS], + config ) ); ruleTypeRegistry.register( createThreadPoolRejectionsAlertType( RULE_THREAD_POOL_WRITE_REJECTIONS, - RULE_DETAILS[RULE_THREAD_POOL_WRITE_REJECTIONS] + RULE_DETAILS[RULE_THREAD_POOL_WRITE_REJECTIONS], + config ) ); - ruleTypeRegistry.register(createCCRReadExceptionsAlertType()); - ruleTypeRegistry.register(createLargeShardSizeAlertType()); - const legacyAlertTypes = createLegacyAlertTypes(); + ruleTypeRegistry.register(createCCRReadExceptionsAlertType(config)); + ruleTypeRegistry.register(createLargeShardSizeAlertType(config)); + const legacyAlertTypes = createLegacyAlertTypes(config); for (const legacyAlertType of legacyAlertTypes) { ruleTypeRegistry.register(legacyAlertType); } diff --git a/x-pack/plugins/monitoring/server/alerts/ccr_read_exceptions_rule.ts b/x-pack/plugins/monitoring/server/alerts/ccr_read_exceptions_rule.ts index 587b3a69fb768..e3a3537ea2eaf 100644 --- a/x-pack/plugins/monitoring/server/alerts/ccr_read_exceptions_rule.ts +++ b/x-pack/plugins/monitoring/server/alerts/ccr_read_exceptions_rule.ts @@ -88,7 +88,8 @@ export class CCRReadExceptionsRule extends BaseRule { esIndexPattern, startMs, endMs, - Globals.app.config.ui.max_bucket_size + Globals.app.config.ui.max_bucket_size, + params.filterQuery ); return stats.map((stat) => { @@ -120,12 +121,8 @@ export class CCRReadExceptionsRule extends BaseRule { } protected getUiMessage(alertState: AlertState, item: AlertData): AlertMessage { - const { - remoteCluster, - followerIndex, - shardId, - lastReadException, - } = item.meta as CCRReadExceptionsUIMeta; + const { remoteCluster, followerIndex, shardId, lastReadException } = + item.meta as CCRReadExceptionsUIMeta; return { text: i18n.translate('xpack.monitoring.alerts.ccrReadExceptions.ui.firingMessage', { defaultMessage: `Follower index #start_link{followerIndex}#end_link is reporting CCR read exceptions on remote cluster: {remoteCluster} at #absolute`, @@ -282,7 +279,7 @@ export class CCRReadExceptionsRule extends BaseRule { state: AlertingDefaults.ALERT_STATE.firing, remoteCluster, followerIndex, - /* continue to send "remoteClusters" and "followerIndices" values for users still using it though + /* continue to send "remoteClusters" and "followerIndices" values for users still using it though we have replaced it with "remoteCluster" and "followerIndex" in the template due to alerts per index instead of all indices see https://github.com/elastic/kibana/issues/100136#issuecomment-865229431 */ diff --git a/x-pack/plugins/monitoring/server/alerts/cluster_health_rule.ts b/x-pack/plugins/monitoring/server/alerts/cluster_health_rule.ts index 7fac3b74a1b66..b9b9b90845eea 100644 --- a/x-pack/plugins/monitoring/server/alerts/cluster_health_rule.ts +++ b/x-pack/plugins/monitoring/server/alerts/cluster_health_rule.ts @@ -73,7 +73,12 @@ export class ClusterHealthRule extends BaseRule { if (availableCcs) { esIndexPattern = getCcsIndexPattern(esIndexPattern, availableCcs); } - const healths = await fetchClusterHealth(esClient, clusters, esIndexPattern); + const healths = await fetchClusterHealth( + esClient, + clusters, + esIndexPattern, + params.filterQuery + ); return healths.map((clusterHealth) => { const shouldFire = clusterHealth.health !== AlertClusterHealthType.Green; const severity = diff --git a/x-pack/plugins/monitoring/server/alerts/cpu_usage_rule.ts b/x-pack/plugins/monitoring/server/alerts/cpu_usage_rule.ts index 2e57a3c22de1b..7e38efcb819ea 100644 --- a/x-pack/plugins/monitoring/server/alerts/cpu_usage_rule.ts +++ b/x-pack/plugins/monitoring/server/alerts/cpu_usage_rule.ts @@ -76,7 +76,8 @@ export class CpuUsageRule extends BaseRule { esIndexPattern, startMs, endMs, - Globals.app.config.ui.max_bucket_size + Globals.app.config.ui.max_bucket_size, + params.filterQuery ); return stats.map((stat) => { if (Globals.app.config.ui.container.elasticsearch.enabled) { @@ -203,7 +204,7 @@ export class CpuUsageRule extends BaseRule { internalShortMessage, internalFullMessage: Globals.app.isCloud ? internalShortMessage : internalFullMessage, state: AlertingDefaults.ALERT_STATE.firing, - /* continue to send "nodes" and "count" values for users before https://github.com/elastic/kibana/pull/102544 + /* continue to send "nodes" and "count" values for users before https://github.com/elastic/kibana/pull/102544 see https://github.com/elastic/kibana/issues/100136#issuecomment-865229431 */ nodes: `${firingNode.nodeName}:${firingNode.cpuUsage}`, diff --git a/x-pack/plugins/monitoring/server/alerts/disk_usage_rule.ts b/x-pack/plugins/monitoring/server/alerts/disk_usage_rule.ts index ae3025c1db92c..bac70baebb4e2 100644 --- a/x-pack/plugins/monitoring/server/alerts/disk_usage_rule.ts +++ b/x-pack/plugins/monitoring/server/alerts/disk_usage_rule.ts @@ -72,7 +72,8 @@ export class DiskUsageRule extends BaseRule { clusters, esIndexPattern, duration as string, - Globals.app.config.ui.max_bucket_size + Globals.app.config.ui.max_bucket_size, + params.filterQuery ); return stats.map((stat) => { @@ -212,7 +213,7 @@ export class DiskUsageRule extends BaseRule { internalShortMessage, internalFullMessage: Globals.app.isCloud ? internalShortMessage : internalFullMessage, state: AlertingDefaults.ALERT_STATE.firing, - /* continue to send "nodes" and "count" values for users before https://github.com/elastic/kibana/pull/102544 + /* continue to send "nodes" and "count" values for users before https://github.com/elastic/kibana/pull/102544 see https://github.com/elastic/kibana/issues/100136#issuecomment-865229431 */ nodes: `${firingNode.nodeName}:${firingNode.diskUsage}`, diff --git a/x-pack/plugins/monitoring/server/alerts/elasticsearch_version_mismatch_rule.ts b/x-pack/plugins/monitoring/server/alerts/elasticsearch_version_mismatch_rule.ts index 6a5abcb4975f4..352cac531f8e8 100644 --- a/x-pack/plugins/monitoring/server/alerts/elasticsearch_version_mismatch_rule.ts +++ b/x-pack/plugins/monitoring/server/alerts/elasticsearch_version_mismatch_rule.ts @@ -66,7 +66,8 @@ export class ElasticsearchVersionMismatchRule extends BaseRule { esClient, clusters, esIndexPattern, - Globals.app.config.ui.max_bucket_size + Globals.app.config.ui.max_bucket_size, + params.filterQuery ); return elasticsearchVersions.map((elasticsearchVersion) => { diff --git a/x-pack/plugins/monitoring/server/alerts/kibana_version_mismatch_rule.ts b/x-pack/plugins/monitoring/server/alerts/kibana_version_mismatch_rule.ts index 90275ea4d23a8..6d9410ed0e5a0 100644 --- a/x-pack/plugins/monitoring/server/alerts/kibana_version_mismatch_rule.ts +++ b/x-pack/plugins/monitoring/server/alerts/kibana_version_mismatch_rule.ts @@ -79,7 +79,8 @@ export class KibanaVersionMismatchRule extends BaseRule { esClient, clusters, kibanaIndexPattern, - Globals.app.config.ui.max_bucket_size + Globals.app.config.ui.max_bucket_size, + params.filterQuery ); return kibanaVersions.map((kibanaVersion) => { diff --git a/x-pack/plugins/monitoring/server/alerts/large_shard_size_rule.ts b/x-pack/plugins/monitoring/server/alerts/large_shard_size_rule.ts index 86f96daa3b21d..b0370a23159d7 100644 --- a/x-pack/plugins/monitoring/server/alerts/large_shard_size_rule.ts +++ b/x-pack/plugins/monitoring/server/alerts/large_shard_size_rule.ts @@ -75,7 +75,8 @@ export class LargeShardSizeRule extends BaseRule { esIndexPattern, threshold!, shardIndexPatterns, - Globals.app.config.ui.max_bucket_size + Globals.app.config.ui.max_bucket_size, + params.filterQuery ); return stats.map((stat) => { @@ -211,7 +212,7 @@ export class LargeShardSizeRule extends BaseRule { internalShortMessage, internalFullMessage, state: AlertingDefaults.ALERT_STATE.firing, - /* continue to send "shardIndices" values for users still using it though + /* continue to send "shardIndices" values for users still using it though we have replaced it with shardIndex in the template due to alerts per index instead of all indices see https://github.com/elastic/kibana/issues/100136#issuecomment-865229431 */ diff --git a/x-pack/plugins/monitoring/server/alerts/license_expiration_rule.test.ts b/x-pack/plugins/monitoring/server/alerts/license_expiration_rule.test.ts index b8d00cac5c888..b29a5fc4661d7 100644 --- a/x-pack/plugins/monitoring/server/alerts/license_expiration_rule.test.ts +++ b/x-pack/plugins/monitoring/server/alerts/license_expiration_rule.test.ts @@ -140,8 +140,7 @@ describe('LicenseExpirationRule', () => { ui: { isFiring: true, message: { - text: - 'The license for this cluster expires in #relative at #absolute. #start_linkPlease update your license.#end_link', + text: 'The license for this cluster expires in #relative at #absolute. #start_linkPlease update your license.#end_link', tokens: [ { startToken: '#relative', diff --git a/x-pack/plugins/monitoring/server/alerts/license_expiration_rule.ts b/x-pack/plugins/monitoring/server/alerts/license_expiration_rule.ts index 67ea8bd57b491..c26929b05ab26 100644 --- a/x-pack/plugins/monitoring/server/alerts/license_expiration_rule.ts +++ b/x-pack/plugins/monitoring/server/alerts/license_expiration_rule.ts @@ -87,7 +87,7 @@ export class LicenseExpirationRule extends BaseRule { if (availableCcs) { esIndexPattern = getCcsIndexPattern(esIndexPattern, availableCcs); } - const licenses = await fetchLicenses(esClient, clusters, esIndexPattern); + const licenses = await fetchLicenses(esClient, clusters, esIndexPattern, params.filterQuery); return licenses.map((license) => { const { clusterUuid, type, expiryDateMS, status, ccs } = license; diff --git a/x-pack/plugins/monitoring/server/alerts/logstash_version_mismatch_rule.ts b/x-pack/plugins/monitoring/server/alerts/logstash_version_mismatch_rule.ts index 0f9ad4dd4b117..e59ed9efbefb2 100644 --- a/x-pack/plugins/monitoring/server/alerts/logstash_version_mismatch_rule.ts +++ b/x-pack/plugins/monitoring/server/alerts/logstash_version_mismatch_rule.ts @@ -66,7 +66,8 @@ export class LogstashVersionMismatchRule extends BaseRule { esClient, clusters, logstashIndexPattern, - Globals.app.config.ui.max_bucket_size + Globals.app.config.ui.max_bucket_size, + params.filterQuery ); return logstashVersions.map((logstashVersion) => { diff --git a/x-pack/plugins/monitoring/server/alerts/memory_usage_rule.ts b/x-pack/plugins/monitoring/server/alerts/memory_usage_rule.ts index 384610e659d47..d94e1234ce813 100644 --- a/x-pack/plugins/monitoring/server/alerts/memory_usage_rule.ts +++ b/x-pack/plugins/monitoring/server/alerts/memory_usage_rule.ts @@ -82,7 +82,8 @@ export class MemoryUsageRule extends BaseRule { esIndexPattern, startMs, endMs, - Globals.app.config.ui.max_bucket_size + Globals.app.config.ui.max_bucket_size, + params.filterQuery ); return stats.map((stat) => { @@ -223,7 +224,7 @@ export class MemoryUsageRule extends BaseRule { internalShortMessage, internalFullMessage: Globals.app.isCloud ? internalShortMessage : internalFullMessage, state: AlertingDefaults.ALERT_STATE.firing, - /* continue to send "nodes" and "count" values for users before https://github.com/elastic/kibana/pull/102544 + /* continue to send "nodes" and "count" values for users before https://github.com/elastic/kibana/pull/102544 see https://github.com/elastic/kibana/issues/100136#issuecomment-865229431 */ nodes: `${firingNode.nodeName}:${firingNode.memoryUsage.toFixed(2)}`, diff --git a/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_rule.test.ts b/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_rule.test.ts index 88ddc7c04884c..66259b9fffac9 100644 --- a/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_rule.test.ts +++ b/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_rule.test.ts @@ -144,8 +144,7 @@ describe('MissingMonitoringDataRule', () => { ui: { isFiring: true, message: { - text: - 'For the past an hour, we have not detected any monitoring data from the Elasticsearch node: esName1, starting at #absolute', + text: 'For the past an hour, we have not detected any monitoring data from the Elasticsearch node: esName1, starting at #absolute', nextSteps: [ { text: '#start_linkView all Elasticsearch nodes#end_link', diff --git a/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_rule.ts b/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_rule.ts index 32e4ff738c71b..1b45b19fe89f8 100644 --- a/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_rule.ts +++ b/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_rule.ts @@ -75,7 +75,8 @@ export class MissingMonitoringDataRule extends BaseRule { indexPattern, Globals.app.config.ui.max_bucket_size, now, - now - limit - LIMIT_BUFFER + now - limit - LIMIT_BUFFER, + params.filterQuery ); return missingData.map((missing) => { return { @@ -198,7 +199,7 @@ export class MissingMonitoringDataRule extends BaseRule { internalShortMessage, internalFullMessage: Globals.app.isCloud ? internalShortMessage : internalFullMessage, state: AlertingDefaults.ALERT_STATE.firing, - /* continue to send "nodes" and "count" values for users before https://github.com/elastic/kibana/pull/102544 + /* continue to send "nodes" and "count" values for users before https://github.com/elastic/kibana/pull/102544 see https://github.com/elastic/kibana/issues/100136#issuecomment-865229431 */ nodes: `node: ${firingNode.nodeName}`, diff --git a/x-pack/plugins/monitoring/server/alerts/nodes_changed_rule.test.ts b/x-pack/plugins/monitoring/server/alerts/nodes_changed_rule.test.ts index 199e50c80ef4c..5145a4e8476af 100644 --- a/x-pack/plugins/monitoring/server/alerts/nodes_changed_rule.test.ts +++ b/x-pack/plugins/monitoring/server/alerts/nodes_changed_rule.test.ts @@ -277,8 +277,7 @@ describe('NodesChangedAlert', () => { ui: { isFiring: true, message: { - text: - "Elasticsearch nodes 'newNodeName' added to this cluster. Elasticsearch nodes 'removedNodeName' removed from this cluster. Elasticsearch nodes 'test' restarted in this cluster.", + text: "Elasticsearch nodes 'newNodeName' added to this cluster. Elasticsearch nodes 'removedNodeName' removed from this cluster. Elasticsearch nodes 'test' restarted in this cluster.", }, severity: 'warning', triggeredMS: 1, diff --git a/x-pack/plugins/monitoring/server/alerts/nodes_changed_rule.ts b/x-pack/plugins/monitoring/server/alerts/nodes_changed_rule.ts index 90bd70f32c8cb..6645466f30c73 100644 --- a/x-pack/plugins/monitoring/server/alerts/nodes_changed_rule.ts +++ b/x-pack/plugins/monitoring/server/alerts/nodes_changed_rule.ts @@ -114,7 +114,8 @@ export class NodesChangedRule extends BaseRule { const nodesFromClusterStats = await fetchNodesFromClusterStats( esClient, clusters, - esIndexPattern + esIndexPattern, + params.filterQuery ); return nodesFromClusterStats.map((nodes) => { const { removed, added, restarted } = getNodeStates(nodes); diff --git a/x-pack/plugins/monitoring/server/alerts/thread_pool_rejections_rule_base.ts b/x-pack/plugins/monitoring/server/alerts/thread_pool_rejections_rule_base.ts index c478b2f687c02..678f8b429167f 100644 --- a/x-pack/plugins/monitoring/server/alerts/thread_pool_rejections_rule_base.ts +++ b/x-pack/plugins/monitoring/server/alerts/thread_pool_rejections_rule_base.ts @@ -86,7 +86,8 @@ export class ThreadPoolRejectionsRuleBase extends BaseRule { esIndexPattern, Globals.app.config.ui.max_bucket_size, this.threadPoolType, - duration + duration, + params.filterQuery ); return stats.map((stat) => { @@ -257,7 +258,7 @@ export class ThreadPoolRejectionsRuleBase extends BaseRule { internalFullMessage: Globals.app.isCloud ? internalShortMessage : internalFullMessage, threadPoolType: type, state: AlertingDefaults.ALERT_STATE.firing, - /* continue to send "count" value for users before https://github.com/elastic/kibana/pull/102544 + /* continue to send "count" value for users before https://github.com/elastic/kibana/pull/102544 see https://github.com/elastic/kibana/issues/100136#issuecomment-865229431 */ count: 1, diff --git a/x-pack/plugins/monitoring/server/deprecations.test.js b/x-pack/plugins/monitoring/server/deprecations.test.js index 2931f704a4478..4c12979e97804 100644 --- a/x-pack/plugins/monitoring/server/deprecations.test.js +++ b/x-pack/plugins/monitoring/server/deprecations.test.js @@ -10,12 +10,13 @@ import { deprecations as deprecationsModule } from './deprecations'; describe('monitoring plugin deprecations', function () { let transformDeprecations; + const deprecate = jest.fn(() => jest.fn()); const rename = jest.fn(() => jest.fn()); const renameFromRoot = jest.fn(() => jest.fn()); const fromPath = 'monitoring'; beforeAll(function () { - const deprecations = deprecationsModule({ rename, renameFromRoot }); + const deprecations = deprecationsModule({ deprecate, rename, renameFromRoot }); transformDeprecations = (settings, fromPath, addDeprecation = noop) => { deprecations.forEach((deprecation) => deprecation(settings, fromPath, addDeprecation)); }; diff --git a/x-pack/plugins/monitoring/server/deprecations.ts b/x-pack/plugins/monitoring/server/deprecations.ts index 3e4d1627b0ae2..cb09bbdb5a87c 100644 --- a/x-pack/plugins/monitoring/server/deprecations.ts +++ b/x-pack/plugins/monitoring/server/deprecations.ts @@ -18,10 +18,12 @@ import { CLUSTER_ALERTS_ADDRESS_CONFIG_KEY } from '../common/constants'; * @return {Array} array of rename operations and callback function for rename logging */ export const deprecations = ({ + deprecate, rename, renameFromRoot, }: ConfigDeprecationFactory): ConfigDeprecation[] => { return [ + deprecate('enabled', '8.0.0'), // This order matters. The "blanket rename" needs to happen at the end renameFromRoot('xpack.monitoring.max_bucket_size', 'monitoring.ui.max_bucket_size'), renameFromRoot('xpack.monitoring.min_interval_seconds', 'monitoring.ui.min_interval_seconds'), diff --git a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_es_usage.test.ts b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_es_usage.test.ts index b7d616a5a3cc7..4f53ae9b84edf 100644 --- a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_es_usage.test.ts +++ b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_es_usage.test.ts @@ -11,7 +11,7 @@ import { fetchESUsage } from './fetch_es_usage'; describe('fetchESUsage', () => { const clusterUuid = '1abcde2'; const index = '.monitoring-es-*'; - const callCluster = ({ + const callCluster = { search: jest.fn().mockImplementation(() => ({ body: { hits: { @@ -40,7 +40,7 @@ describe('fetchESUsage', () => { }, }, })), - } as unknown) as ElasticsearchClient; + } as unknown as ElasticsearchClient; it('should return usage data for Elasticsearch', async () => { const result = await fetchESUsage(callCluster, clusterUuid, index); @@ -52,7 +52,7 @@ describe('fetchESUsage', () => { }); it('should handle some indices coming from Metricbeat', async () => { - const customCallCluster = ({ + const customCallCluster = { search: jest.fn().mockImplementation(() => ({ body: { hits: { @@ -81,7 +81,7 @@ describe('fetchESUsage', () => { }, }, })), - } as unknown) as ElasticsearchClient; + } as unknown as ElasticsearchClient; const result = await fetchESUsage(customCallCluster, clusterUuid, index); expect(result).toStrictEqual({ count: 10, @@ -91,7 +91,7 @@ describe('fetchESUsage', () => { }); it('should handle no monitoring data', async () => { - const customCallCluster = ({ + const customCallCluster = { search: jest.fn().mockImplementation(() => ({ body: { hits: { @@ -99,7 +99,7 @@ describe('fetchESUsage', () => { }, }, })), - } as unknown) as ElasticsearchClient; + } as unknown as ElasticsearchClient; const result = await fetchESUsage(customCallCluster, clusterUuid, index); expect(result).toStrictEqual({ count: 0, diff --git a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_license_type.test.ts b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_license_type.test.ts index e4d801cfac0c3..89050963c9201 100644 --- a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_license_type.test.ts +++ b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_license_type.test.ts @@ -11,7 +11,7 @@ import { fetchLicenseType } from './fetch_license_type'; describe('fetchLicenseType', () => { const clusterUuid = '1abcde2'; const availableCcs: string[] = []; - const callCluster = ({ + const callCluster = { search: jest.fn().mockImplementation(() => ({ body: { hits: { @@ -27,7 +27,7 @@ describe('fetchLicenseType', () => { }, }, })), - } as unknown) as ElasticsearchClient; + } as unknown as ElasticsearchClient; it('should get the license type', async () => { const result = await fetchLicenseType(callCluster, availableCcs, clusterUuid); @@ -35,7 +35,7 @@ describe('fetchLicenseType', () => { }); it('should handle no license data', async () => { - const customCallCluster = ({ + const customCallCluster = { search: jest.fn().mockImplementation(() => ({ body: { hits: { @@ -43,7 +43,7 @@ describe('fetchLicenseType', () => { }, }, })), - } as unknown) as ElasticsearchClient; + } as unknown as ElasticsearchClient; const result = await fetchLicenseType(customCallCluster, availableCcs, clusterUuid); expect(result).toStrictEqual(null); }); diff --git a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_stack_product_usage.test.ts b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_stack_product_usage.test.ts index a8650c375a0df..4239617a39b11 100644 --- a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_stack_product_usage.test.ts +++ b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_stack_product_usage.test.ts @@ -37,7 +37,7 @@ describe('fetchStackProductUsage', () => { }, }, })); - const callCluster = ({ search: searchMock } as unknown) as ElasticsearchClient; + const callCluster = { search: searchMock } as unknown as ElasticsearchClient; await fetchStackProductUsage( config, callCluster, @@ -63,7 +63,7 @@ describe('fetchStackProductUsage', () => { }); it('should get the usage data', async () => { - const callCluster = ({ + const callCluster = { search: jest.fn().mockImplementation(() => ({ body: { aggregations: { @@ -84,7 +84,7 @@ describe('fetchStackProductUsage', () => { }, }, })), - } as unknown) as ElasticsearchClient; + } as unknown as ElasticsearchClient; const result = await fetchStackProductUsage( config, @@ -103,7 +103,7 @@ describe('fetchStackProductUsage', () => { }); it('should handle both collection types', async () => { - const callCluster = ({ + const callCluster = { search: jest.fn().mockImplementation(() => ({ body: { aggregations: { @@ -127,7 +127,7 @@ describe('fetchStackProductUsage', () => { }, }, })), - } as unknown) as ElasticsearchClient; + } as unknown as ElasticsearchClient; const result = await fetchStackProductUsage( config, diff --git a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_stack_product_usage.ts b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_stack_product_usage.ts index 527ed503c8faf..0d3aab8283688 100644 --- a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_stack_product_usage.ts +++ b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_stack_product_usage.ts @@ -10,7 +10,7 @@ import { ElasticsearchClient } from 'src/core/server'; import { estypes } from '@elastic/elasticsearch'; import { MonitoringConfig } from '../../../config'; // @ts-ignore -import { prefixIndexPattern } from '../../../lib/ccs_utils'; +import { prefixIndexPattern } from '../../../../common/ccs_utils'; import { StackProductUsage } from '../types'; interface ESResponse { diff --git a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/get_stack_products_usage.test.ts b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/get_stack_products_usage.test.ts index 928d7efcdd0d1..78e1e98def5a2 100644 --- a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/get_stack_products_usage.test.ts +++ b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/get_stack_products_usage.test.ts @@ -16,7 +16,7 @@ describe('getStackProductsUsage', () => { }; const clusterUuid = '1abcde2'; const availableCcs: string[] = []; - const callCluster = ({ + const callCluster = { search: jest.fn().mockImplementation(() => ({ body: { hits: { @@ -24,7 +24,7 @@ describe('getStackProductsUsage', () => { }, }, })), - } as unknown) as ElasticsearchClient; + } as unknown as ElasticsearchClient; it('should get all stack products', async () => { const result = await getStackProductsUsage(config, callCluster, availableCcs, clusterUuid); diff --git a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/get_stack_products_usage.ts b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/get_stack_products_usage.ts index 7cce1b392112f..25a1892a9f38d 100644 --- a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/get_stack_products_usage.ts +++ b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/get_stack_products_usage.ts @@ -12,7 +12,7 @@ import { MonitoringConfig } from '../../../config'; // @ts-ignore import { getIndexPatterns } from '../../../lib/cluster/get_index_patterns'; // @ts-ignore -import { prefixIndexPattern } from '../../../lib/ccs_utils'; +import { prefixIndexPattern } from '../../../../common/ccs_utils'; import { INDEX_PATTERN_ELASTICSEARCH, INDEX_PATTERN_KIBANA, diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_ccr_read_exceptions.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_ccr_read_exceptions.ts index 560751d1297d5..e7a5923207d60 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_ccr_read_exceptions.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_ccr_read_exceptions.ts @@ -14,7 +14,8 @@ export async function fetchCCRReadExceptions( index: string, startMs: number, endMs: number, - size: number + size: number, + filterQuery?: string ): Promise { const params = { index, @@ -93,6 +94,15 @@ export async function fetchCCRReadExceptions( }, }; + try { + if (filterQuery) { + const filterQueryObject = JSON.parse(filterQuery); + params.body.query.bool.filter.push(filterQueryObject); + } + } catch (e) { + // meh + } + const { body: response } = await esClient.search(params); const stats: CCRReadExceptionsStats[] = []; // @ts-expect-error declare aggegations type explicitly diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_health.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_health.ts index 85bfbd9dbd049..b2004f0c7c710 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_health.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_health.ts @@ -11,7 +11,8 @@ import { ElasticsearchSource, ElasticsearchResponse } from '../../../common/type export async function fetchClusterHealth( esClient: ElasticsearchClient, clusters: AlertCluster[], - index: string + index: string, + filterQuery?: string ): Promise { const params = { index, @@ -59,6 +60,15 @@ export async function fetchClusterHealth( }, }; + try { + if (filterQuery) { + const filterQueryObject = JSON.parse(filterQuery); + params.body.query.bool.filter.push(filterQueryObject); + } + } catch (e) { + // meh + } + const result = await esClient.search(params); const response: ElasticsearchResponse = result.body as ElasticsearchResponse; return (response.hits?.hits ?? []).map((hit) => { diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.test.ts index 90cd456f18037..8f0083f1f533f 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.test.ts @@ -201,7 +201,9 @@ describe('fetchCpuUsageNodeStats', () => { {} as estypes.SearchResponse ); }); - await fetchCpuUsageNodeStats(esClient, clusters, index, startMs, endMs, size); + const filterQuery = + '{"bool":{"should":[{"exists":{"field":"cluster_uuid"}}],"minimum_should_match":1}}'; + await fetchCpuUsageNodeStats(esClient, clusters, index, startMs, endMs, size, filterQuery); expect(params).toStrictEqual({ index: '.monitoring-es-*', filter_path: ['aggregations'], @@ -213,6 +215,9 @@ describe('fetchCpuUsageNodeStats', () => { { terms: { cluster_uuid: ['abc123'] } }, { term: { type: 'node_stats' } }, { range: { timestamp: { format: 'epoch_millis', gte: 0, lte: 0 } } }, + { + bool: { should: [{ exists: { field: 'cluster_uuid' } }], minimum_should_match: 1 }, + }, ], }, }, diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.ts index 6f7d27916a7b1..2ad42870e9958 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.ts @@ -29,7 +29,8 @@ export async function fetchCpuUsageNodeStats( index: string, startMs: number, endMs: number, - size: number + size: number, + filterQuery?: string ): Promise { // Using pure MS didn't seem to work well with the date_histogram interval // but minutes does @@ -140,6 +141,15 @@ export async function fetchCpuUsageNodeStats( }, }; + try { + if (filterQuery) { + const filterQueryObject = JSON.parse(filterQuery); + params.body.query.bool.filter.push(filterQueryObject); + } + } catch (e) { + // meh + } + const { body: response } = await esClient.search(params); const stats: AlertCpuUsageNodeStats[] = []; const clusterBuckets = get( diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_disk_usage_node_stats.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_disk_usage_node_stats.ts index 70f05991d4229..2d4872c0bd895 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_disk_usage_node_stats.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_disk_usage_node_stats.ts @@ -14,7 +14,8 @@ export async function fetchDiskUsageNodeStats( clusters: AlertCluster[], index: string, duration: string, - size: number + size: number, + filterQuery?: string ): Promise { const clustersIds = clusters.map((cluster) => cluster.clusterUuid); const params = { @@ -99,6 +100,15 @@ export async function fetchDiskUsageNodeStats( }, }; + try { + if (filterQuery) { + const filterQueryObject = JSON.parse(filterQuery); + params.body.query.bool.filter.push(filterQueryObject); + } + } catch (e) { + // meh + } + const { body: response } = await esClient.search(params); const stats: AlertDiskUsageNodeStats[] = []; // @ts-expect-error declare type for aggregations explicitly diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_elasticsearch_versions.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_elasticsearch_versions.ts index f2f311ac870a5..6ca2e89048df9 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_elasticsearch_versions.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_elasticsearch_versions.ts @@ -12,7 +12,8 @@ export async function fetchElasticsearchVersions( esClient: ElasticsearchClient, clusters: AlertCluster[], index: string, - size: number + size: number, + filterQuery?: string ): Promise { const params = { index, @@ -60,6 +61,15 @@ export async function fetchElasticsearchVersions( }, }; + try { + if (filterQuery) { + const filterQueryObject = JSON.parse(filterQuery); + params.body.query.bool.filter.push(filterQueryObject); + } + } catch (e) { + // meh + } + const result = await esClient.search(params); const response: ElasticsearchResponse = result.body as ElasticsearchResponse; return (response.hits?.hits ?? []).map((hit) => { diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_index_shard_size.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_index_shard_size.ts index 7e7ea5e6bfdd2..98bb546b43ab9 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_index_shard_size.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_index_shard_size.ts @@ -35,7 +35,8 @@ export async function fetchIndexShardSize( index: string, threshold: number, shardIndexPatterns: string, - size: number + size: number, + filterQuery?: string ): Promise { const params = { index, @@ -104,6 +105,15 @@ export async function fetchIndexShardSize( }, }; + try { + if (filterQuery) { + const filterQueryObject = JSON.parse(filterQuery); + params.body.query.bool.must.push(filterQueryObject); + } + } catch (e) { + // meh + } + const { body: response } = await esClient.search(params); // @ts-expect-error declare aggegations type explicitly const { buckets: clusterBuckets } = response.aggregations?.clusters; diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_kibana_versions.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_kibana_versions.ts index e57b45e2570fa..71813f3a526de 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_kibana_versions.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_kibana_versions.ts @@ -16,7 +16,8 @@ export async function fetchKibanaVersions( esClient: ElasticsearchClient, clusters: AlertCluster[], index: string, - size: number + size: number, + filterQuery?: string ): Promise { const params = { index, @@ -89,6 +90,15 @@ export async function fetchKibanaVersions( }, }; + try { + if (filterQuery) { + const filterQueryObject = JSON.parse(filterQuery); + params.body.query.bool.filter.push(filterQueryObject); + } + } catch (e) { + // meh + } + const { body: response } = await esClient.search(params); const indexName = get(response, 'aggregations.index.buckets[0].key', ''); const clusterList = get(response, 'aggregations.cluster.buckets', []) as ESAggResponse[]; diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.ts index 38ff82cf29832..b7bdf2fb6be72 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.ts @@ -11,7 +11,8 @@ import { ElasticsearchSource } from '../../../common/types/es'; export async function fetchLicenses( esClient: ElasticsearchClient, clusters: AlertCluster[], - index: string + index: string, + filterQuery?: string ): Promise { const params = { index, @@ -59,6 +60,15 @@ export async function fetchLicenses( }, }; + try { + if (filterQuery) { + const filterQueryObject = JSON.parse(filterQuery); + params.body.query.bool.filter.push(filterQueryObject); + } + } catch (e) { + // meh + } + const { body: response } = await esClient.search(params); return ( response?.hits?.hits.map((hit) => { diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_logstash_versions.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_logstash_versions.ts index 774ee2551ec07..112c2fe798b10 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_logstash_versions.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_logstash_versions.ts @@ -16,7 +16,8 @@ export async function fetchLogstashVersions( esClient: ElasticsearchClient, clusters: AlertCluster[], index: string, - size: number + size: number, + filterQuery?: string ): Promise { const params = { index, @@ -89,6 +90,15 @@ export async function fetchLogstashVersions( }, }; + try { + if (filterQuery) { + const filterQueryObject = JSON.parse(filterQuery); + params.body.query.bool.filter.push(filterQueryObject); + } + } catch (e) { + // meh + } + const { body: response } = await esClient.search(params); const indexName = get(response, 'aggregations.index.buckets[0].key', ''); const clusterList = get(response, 'aggregations.cluster.buckets', []) as ESAggResponse[]; diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_memory_usage_node_stats.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_memory_usage_node_stats.ts index f34a8dcff1db7..9403ae5d79a70 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_memory_usage_node_stats.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_memory_usage_node_stats.ts @@ -15,7 +15,8 @@ export async function fetchMemoryUsageNodeStats( index: string, startMs: number, endMs: number, - size: number + size: number, + filterQuery?: string ): Promise { const clustersIds = clusters.map((cluster) => cluster.clusterUuid); const params = { @@ -92,6 +93,15 @@ export async function fetchMemoryUsageNodeStats( }, }; + try { + if (filterQuery) { + const filterQueryObject = JSON.parse(filterQuery); + params.body.query.bool.filter.push(filterQueryObject); + } + } catch (e) { + // meh + } + const { body: response } = await esClient.search(params); const stats: AlertMemoryUsageNodeStats[] = []; // @ts-expect-error declare type for aggregations explicitly diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.ts index 856ca7c919885..cdf0f21b52b09 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.ts @@ -47,7 +47,8 @@ export async function fetchMissingMonitoringData( index: string, size: number, nowInMs: number, - startMs: number + startMs: number, + filterQuery?: string ): Promise { const endMs = nowInMs; const params = { @@ -117,6 +118,15 @@ export async function fetchMissingMonitoringData( }, }; + try { + if (filterQuery) { + const filterQueryObject = JSON.parse(filterQuery); + params.body.query.bool.filter.push(filterQueryObject); + } + } catch (e) { + // meh + } + const { body: response } = await esClient.search(params); const clusterBuckets = get( response, diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_nodes_from_cluster_stats.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_nodes_from_cluster_stats.ts index dcc8e6516c69b..3dc3e315318fc 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_nodes_from_cluster_stats.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_nodes_from_cluster_stats.ts @@ -26,7 +26,8 @@ function formatNode( export async function fetchNodesFromClusterStats( esClient: ElasticsearchClient, clusters: AlertCluster[], - index: string + index: string, + filterQuery?: string ): Promise { const params = { index, @@ -88,6 +89,15 @@ export async function fetchNodesFromClusterStats( }, }; + try { + if (filterQuery) { + const filterQueryObject = JSON.parse(filterQuery); + params.body.query.bool.filter.push(filterQueryObject); + } + } catch (e) { + // meh + } + const { body: response } = await esClient.search(params); const nodes: AlertClusterStatsNodes[] = []; // @ts-expect-error declare type for aggregations explicitly diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_thread_pool_rejections_stats.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_thread_pool_rejections_stats.ts index 132f7692a7579..0d1d052b5f866 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_thread_pool_rejections_stats.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_thread_pool_rejections_stats.ts @@ -36,7 +36,8 @@ export async function fetchThreadPoolRejectionStats( index: string, size: number, threadType: string, - duration: string + duration: string, + filterQuery?: string ): Promise { const clustersIds = clusters.map((cluster) => cluster.clusterUuid); const params = { @@ -94,6 +95,15 @@ export async function fetchThreadPoolRejectionStats( }, }; + try { + if (filterQuery) { + const filterQueryObject = JSON.parse(filterQuery); + params.body.query.bool.filter.push(filterQueryObject); + } + } catch (e) { + // meh + } + const { body: response } = await esClient.search(params); const stats: AlertThreadPoolRejectionsStats[] = []; // @ts-expect-error declare type for aggregations explicitly diff --git a/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_stats.ts b/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_stats.ts index 6eb21165d7256..a2201ca958e35 100644 --- a/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_stats.ts +++ b/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_stats.ts @@ -12,7 +12,7 @@ import { createQuery } from '../create_query'; // @ts-ignore import { ElasticsearchMetric } from '../metrics'; // @ts-ignore -import { parseCrossClusterPrefix } from '../ccs_utils'; +import { parseCrossClusterPrefix } from '../../../common/ccs_utils'; import { getClustersState } from './get_clusters_state'; import { ElasticsearchResponse, ElasticsearchModifiedSource } from '../../../common/types/es'; import { LegacyRequest } from '../../types'; diff --git a/x-pack/plugins/monitoring/server/lib/cluster/get_index_patterns.ts b/x-pack/plugins/monitoring/server/lib/cluster/get_index_patterns.ts index d908d6180772e..ccfe380edec09 100644 --- a/x-pack/plugins/monitoring/server/lib/cluster/get_index_patterns.ts +++ b/x-pack/plugins/monitoring/server/lib/cluster/get_index_patterns.ts @@ -6,7 +6,7 @@ */ import { LegacyServer } from '../../types'; -import { prefixIndexPattern } from '../ccs_utils'; +import { prefixIndexPattern } from '../../../common/ccs_utils'; import { INDEX_PATTERN_ELASTICSEARCH, INDEX_PATTERN_KIBANA, diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/normalize_shard_objects.ts b/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/normalize_shard_objects.ts index 511935f615cd8..6aa84a0809e96 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/normalize_shard_objects.ts +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/normalize_shard_objects.ts @@ -100,9 +100,8 @@ export function normalizeIndexShards(indices: Index[], index: Index) { const { primaryShards: primary, replicaShards: replica } = countShards(assignedShardBuckets); - const { primaryShards: unassignedPrimary, replicaShards: unassignedReplica } = countShards( - unassignedShardBuckets - ); + const { primaryShards: unassignedPrimary, replicaShards: unassignedReplica } = + countShards(unassignedShardBuckets); let status = 'green'; if (unassignedReplica > 0) { diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch_settings/cluster.test.ts b/x-pack/plugins/monitoring/server/lib/elasticsearch_settings/cluster.test.ts index 966a327e3084c..8b2ea8459e26c 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch_settings/cluster.test.ts +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch_settings/cluster.test.ts @@ -24,7 +24,7 @@ describe('Elasticsearch Cluster Settings', () => { }; const getReq = (response: ClusterGetSettingsResponse) => { - return ({ + return { server: { newPlatform: { setup: { @@ -45,7 +45,7 @@ describe('Elasticsearch Cluster Settings', () => { }, }, }, - } as unknown) as LegacyRequest; + } as unknown as LegacyRequest; }; it('should find default collection interval reason', async () => { diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch_settings/nodes.test.ts b/x-pack/plugins/monitoring/server/lib/elasticsearch_settings/nodes.test.ts index 7e1b93e50f5aa..1e8bae5f1d4dd 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch_settings/nodes.test.ts +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch_settings/nodes.test.ts @@ -10,7 +10,7 @@ import { LegacyRequest } from '../../types'; describe('Elasticsearch Nodes Settings', () => { const getReq = (response?: any) => { - return ({ + return { server: { newPlatform: { setup: { @@ -31,7 +31,7 @@ describe('Elasticsearch Nodes Settings', () => { }, }, }, - } as unknown) as LegacyRequest; + } as unknown as LegacyRequest; }; it('should return { found: false } given no response from ES', async () => { diff --git a/x-pack/plugins/monitoring/server/lib/logs/init_infra_source.ts b/x-pack/plugins/monitoring/server/lib/logs/init_infra_source.ts index c0fa931676870..727e47b62bc92 100644 --- a/x-pack/plugins/monitoring/server/lib/logs/init_infra_source.ts +++ b/x-pack/plugins/monitoring/server/lib/logs/init_infra_source.ts @@ -6,7 +6,7 @@ */ // @ts-ignore -import { prefixIndexPattern } from '../ccs_utils'; +import { prefixIndexPattern } from '../../../common/ccs_utils'; import { INFRA_SOURCE_ID } from '../../../common/constants'; import { MonitoringConfig } from '../../config'; import { InfraPluginSetup } from '../../../../infra/server'; diff --git a/x-pack/plugins/monitoring/server/lib/logstash/get_node_info.test.ts b/x-pack/plugins/monitoring/server/lib/logstash/get_node_info.test.ts index ea2eac7febb6d..cee6c144c866e 100644 --- a/x-pack/plugins/monitoring/server/lib/logstash/get_node_info.test.ts +++ b/x-pack/plugins/monitoring/server/lib/logstash/get_node_info.test.ts @@ -188,7 +188,7 @@ describe('get_logstash_info', () => { const callWithRequest = jest.fn().mockReturnValue({ then: jest.fn(), }); - const req = ({ + const req = { server: { plugins: { elasticsearch: { @@ -198,7 +198,7 @@ describe('get_logstash_info', () => { }, }, }, - } as unknown) as LegacyRequest; + } as unknown as LegacyRequest; await getNodeInfo(req, '.monitoring-logstash-*', { clusterUuid: STANDALONE_CLUSTER_CLUSTER_UUID, logstashUuid: 'logstash_uuid', diff --git a/x-pack/plugins/monitoring/server/lib/logstash/get_paginated_pipelines.ts b/x-pack/plugins/monitoring/server/lib/logstash/get_paginated_pipelines.ts index ee41e12ea322b..b09e22a0906f8 100644 --- a/x-pack/plugins/monitoring/server/lib/logstash/get_paginated_pipelines.ts +++ b/x-pack/plugins/monitoring/server/lib/logstash/get_paginated_pipelines.ts @@ -67,7 +67,7 @@ export async function getPaginatedPipelines({ const sortField = sort.field; const config = req.server.config(); // TODO type config - const size = (config.get('monitoring.ui.max_bucket_size') as unknown) as number; + const size = config.get('monitoring.ui.max_bucket_size') as unknown as number; let pipelines = await getLogstashPipelineIds({ req, lsIndexPattern, diff --git a/x-pack/plugins/monitoring/server/lib/metrics/__snapshots__/metrics.test.js.snap b/x-pack/plugins/monitoring/server/lib/metrics/__snapshots__/metrics.test.js.snap index 674e826b579e5..56d923da56202 100644 --- a/x-pack/plugins/monitoring/server/lib/metrics/__snapshots__/metrics.test.js.snap +++ b/x-pack/plugins/monitoring/server/lib/metrics/__snapshots__/metrics.test.js.snap @@ -3787,6 +3787,7 @@ Object { "format": "0,0.[00]", "getDateHistogramSubAggs": [Function], "label": "Pipeline Throughput", + "mbField": "logstash.node.stats.pipelines.events.out", "timestampField": "logstash_stats.timestamp", "units": "e/s", "uuidField": "logstash_stats.logstash.uuid", diff --git a/x-pack/plugins/monitoring/server/lib/metrics/logstash/metrics.js b/x-pack/plugins/monitoring/server/lib/metrics/logstash/metrics.js index cd518804eeb67..a140cd7bcc370 100644 --- a/x-pack/plugins/monitoring/server/lib/metrics/logstash/metrics.js +++ b/x-pack/plugins/monitoring/server/lib/metrics/logstash/metrics.js @@ -439,6 +439,7 @@ export const metrics = { }), logstash_node_pipeline_throughput: new LogstashPipelineThroughputMetric({ uuidField: 'logstash_stats.logstash.uuid', // TODO: add comment explaining why + mbField: 'logstash.node.stats.pipelines.events.out', field: 'logstash_stats.pipelines.events.out', label: pipelineThroughputLabel, description: pipelineThroughputDescription, diff --git a/x-pack/plugins/monitoring/server/lib/setup/collection/get_collection_status.js b/x-pack/plugins/monitoring/server/lib/setup/collection/get_collection_status.js index b9ce355b44b62..7853b8074f375 100644 --- a/x-pack/plugins/monitoring/server/lib/setup/collection/get_collection_status.js +++ b/x-pack/plugins/monitoring/server/lib/setup/collection/get_collection_status.js @@ -522,9 +522,8 @@ export const getCollectionStatus = async ( productStatus.totalUniqueInternallyCollectedCount = Object.keys( internalCollectorsUuidsMap ).length; - productStatus.totalUniquePartiallyMigratedCount = Object.keys( - partiallyMigratedUuidsMap - ).length; + productStatus.totalUniquePartiallyMigratedCount = + Object.keys(partiallyMigratedUuidsMap).length; productStatus.totalUniqueFullyMigratedCount = Object.keys(fullyMigratedUuidsMap).length; productStatus.byUuid = { ...productStatus.byUuid, @@ -620,9 +619,8 @@ export const getCollectionStatus = async ( productStatus.totalUniqueInternallyCollectedCount = Object.keys( internalCollectorsUuidsMap ).length; - productStatus.totalUniquePartiallyMigratedCount = Object.keys( - partiallyMigratedUuidsMap - ).length; + productStatus.totalUniquePartiallyMigratedCount = + Object.keys(partiallyMigratedUuidsMap).length; productStatus.totalUniqueFullyMigratedCount = Object.keys(fullyMigratedUuidsMap).length; productStatus.byUuid = { ...productStatus.byUuid, diff --git a/x-pack/plugins/monitoring/server/plugin.ts b/x-pack/plugins/monitoring/server/plugin.ts index 1d6af9f080dc0..18eb8fd4d4ddb 100644 --- a/x-pack/plugins/monitoring/server/plugin.ts +++ b/x-pack/plugins/monitoring/server/plugin.ts @@ -66,7 +66,8 @@ const wrapError = (error: any): CustomHttpResponseOptions => { }; export class MonitoringPlugin - implements Plugin { + implements Plugin +{ private readonly initializerContext: PluginInitializerContext; private readonly log: Logger; private readonly getLogger: (...scopes: string[]) => Logger; @@ -206,7 +207,7 @@ export class MonitoringPlugin } } - async start(coreStart: CoreStart, { licensing }: PluginsStart) { + start(coreStart: CoreStart, { licensing }: PluginsStart) { const config = this.config!; this.cluster = instantiateClient( config.ui.elasticsearch, diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/alerts/enable.ts b/x-pack/plugins/monitoring/server/routes/api/v1/alerts/enable.ts index ef6e03e7c999c..6724819c30d56 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/alerts/enable.ts +++ b/x-pack/plugins/monitoring/server/routes/api/v1/alerts/enable.ts @@ -38,10 +38,8 @@ export function enableAlertsRoute(server: LegacyServer, npRoute: RouteDependenci const alerts = AlertsFactory.getAll(); if (alerts.length) { - const { - isSufficientlySecure, - hasPermanentEncryptionKey, - } = await AlertingSecurity.getSecurityHealth(context, npRoute.encryptedSavedObjects); + const { isSufficientlySecure, hasPermanentEncryptionKey } = + await AlertingSecurity.getSecurityHealth(context, npRoute.encryptedSavedObjects); if (!isSufficientlySecure || !hasPermanentEncryptionKey) { server.log.info( diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/apm/instance.js b/x-pack/plugins/monitoring/server/routes/api/v1/apm/instance.js index 4884b8151f61f..a0b00167101fe 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/apm/instance.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/apm/instance.js @@ -6,7 +6,7 @@ */ import { schema } from '@kbn/config-schema'; -import { prefixIndexPattern } from '../../../../lib/ccs_utils'; +import { prefixIndexPattern } from '../../../../../common/ccs_utils'; import { getMetrics } from '../../../../lib/details/get_metrics'; import { metricSet } from './metric_set_instance'; import { handleError } from '../../../../lib/errors'; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/apm/instances.js b/x-pack/plugins/monitoring/server/routes/api/v1/apm/instances.js index 53afa4c3f01b4..95f378ff5b98d 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/apm/instances.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/apm/instances.js @@ -6,7 +6,7 @@ */ import { schema } from '@kbn/config-schema'; -import { prefixIndexPattern } from '../../../../lib/ccs_utils'; +import { prefixIndexPattern } from '../../../../../common/ccs_utils'; import { getStats, getApms } from '../../../../lib/apm'; import { handleError } from '../../../../lib/errors'; import { INDEX_PATTERN_BEATS } from '../../../../../common/constants'; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/apm/overview.js b/x-pack/plugins/monitoring/server/routes/api/v1/apm/overview.js index 7a772594b4bc2..ea7f3f41b842e 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/apm/overview.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/apm/overview.js @@ -6,7 +6,7 @@ */ import { schema } from '@kbn/config-schema'; -import { prefixIndexPattern } from '../../../../lib/ccs_utils'; +import { prefixIndexPattern } from '../../../../../common/ccs_utils'; import { getMetrics } from '../../../../lib/details/get_metrics'; import { metricSet } from './metric_set_overview'; import { handleError } from '../../../../lib/errors'; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/beats/beat_detail.js b/x-pack/plugins/monitoring/server/routes/api/v1/beats/beat_detail.js index 919efe98f3df3..851380fede77d 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/beats/beat_detail.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/beats/beat_detail.js @@ -6,7 +6,7 @@ */ import { schema } from '@kbn/config-schema'; -import { prefixIndexPattern } from '../../../../lib/ccs_utils'; +import { prefixIndexPattern } from '../../../../../common/ccs_utils'; import { getBeatSummary } from '../../../../lib/beats'; import { getMetrics } from '../../../../lib/details/get_metrics'; import { handleError } from '../../../../lib/errors'; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/beats/beats.js b/x-pack/plugins/monitoring/server/routes/api/v1/beats/beats.js index 57b24e59e66ab..fa35ccb9371c2 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/beats/beats.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/beats/beats.js @@ -6,7 +6,7 @@ */ import { schema } from '@kbn/config-schema'; -import { prefixIndexPattern } from '../../../../lib/ccs_utils'; +import { prefixIndexPattern } from '../../../../../common/ccs_utils'; import { getStats, getBeats } from '../../../../lib/beats'; import { handleError } from '../../../../lib/errors'; import { INDEX_PATTERN_BEATS } from '../../../../../common/constants'; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/beats/overview.js b/x-pack/plugins/monitoring/server/routes/api/v1/beats/overview.js index 5f1bb1778bc9a..4abf46b3ad1ce 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/beats/overview.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/beats/overview.js @@ -6,7 +6,7 @@ */ import { schema } from '@kbn/config-schema'; -import { prefixIndexPattern } from '../../../../lib/ccs_utils'; +import { prefixIndexPattern } from '../../../../../common/ccs_utils'; import { getMetrics } from '../../../../lib/details/get_metrics'; import { getLatestStats, getStats } from '../../../../lib/beats'; import { handleError } from '../../../../lib/errors'; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr.ts b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr.ts index 73b646126ce98..898cfc82463d9 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr.ts +++ b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr.ts @@ -11,7 +11,7 @@ import { get, groupBy } from 'lodash'; // @ts-ignore import { handleError } from '../../../../lib/errors/handle_error'; // @ts-ignore -import { prefixIndexPattern } from '../../../../lib/ccs_utils'; +import { prefixIndexPattern } from '../../../../../common/ccs_utils'; import { INDEX_PATTERN_ELASTICSEARCH } from '../../../../../common/constants'; import { ElasticsearchResponse, diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr_shard.ts b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr_shard.ts index 5ecb84d97618b..d07a660222407 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr_shard.ts +++ b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr_shard.ts @@ -10,7 +10,7 @@ import { schema } from '@kbn/config-schema'; // @ts-ignore import { handleError } from '../../../../lib/errors/handle_error'; // @ts-ignore -import { prefixIndexPattern } from '../../../../lib/ccs_utils'; +import { prefixIndexPattern } from '../../../../../common/ccs_utils'; // @ts-ignore import { getMetrics } from '../../../../lib/details/get_metrics'; import { INDEX_PATTERN_ELASTICSEARCH } from '../../../../../common/constants'; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/index_detail.js b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/index_detail.js index 89ca911f44268..e99ae04ab282c 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/index_detail.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/index_detail.js @@ -12,7 +12,7 @@ import { getIndexSummary } from '../../../../lib/elasticsearch/indices'; import { getMetrics } from '../../../../lib/details/get_metrics'; import { getShardAllocation, getShardStats } from '../../../../lib/elasticsearch/shards'; import { handleError } from '../../../../lib/errors/handle_error'; -import { prefixIndexPattern } from '../../../../lib/ccs_utils'; +import { prefixIndexPattern } from '../../../../../common/ccs_utils'; import { metricSet } from './metric_set_index_detail'; import { INDEX_PATTERN_ELASTICSEARCH } from '../../../../../common/constants'; import { getLogs } from '../../../../lib/logs/get_logs'; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/indices.js b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/indices.js index 8099ecf3462cc..76e769ac030ba 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/indices.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/indices.js @@ -10,7 +10,7 @@ import { getClusterStats } from '../../../../lib/cluster/get_cluster_stats'; import { getClusterStatus } from '../../../../lib/cluster/get_cluster_status'; import { getIndices } from '../../../../lib/elasticsearch/indices'; import { handleError } from '../../../../lib/errors/handle_error'; -import { prefixIndexPattern } from '../../../../lib/ccs_utils'; +import { prefixIndexPattern } from '../../../../../common/ccs_utils'; import { INDEX_PATTERN_ELASTICSEARCH } from '../../../../../common/constants'; import { getIndicesUnassignedShardStats } from '../../../../lib/elasticsearch/shards/get_indices_unassigned_shard_stats'; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ml_jobs.js b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ml_jobs.js index e23c23f7a819d..5853cc3d6ee9d 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ml_jobs.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ml_jobs.js @@ -10,7 +10,7 @@ import { getClusterStats } from '../../../../lib/cluster/get_cluster_stats'; import { getClusterStatus } from '../../../../lib/cluster/get_cluster_status'; import { getMlJobs } from '../../../../lib/elasticsearch/get_ml_jobs'; import { handleError } from '../../../../lib/errors/handle_error'; -import { prefixIndexPattern } from '../../../../lib/ccs_utils'; +import { prefixIndexPattern } from '../../../../../common/ccs_utils'; import { INDEX_PATTERN_ELASTICSEARCH } from '../../../../../common/constants'; import { getIndicesUnassignedShardStats } from '../../../../lib/elasticsearch/shards/get_indices_unassigned_shard_stats'; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/node_detail.js b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/node_detail.js index 2122f8ceb2215..5f77d0394a4f1 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/node_detail.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/node_detail.js @@ -12,7 +12,7 @@ import { getNodeSummary } from '../../../../lib/elasticsearch/nodes'; import { getShardStats, getShardAllocation } from '../../../../lib/elasticsearch/shards'; import { getMetrics } from '../../../../lib/details/get_metrics'; import { handleError } from '../../../../lib/errors/handle_error'; -import { prefixIndexPattern } from '../../../../lib/ccs_utils'; +import { prefixIndexPattern } from '../../../../../common/ccs_utils'; import { metricSets } from './metric_set_node_detail'; import { INDEX_PATTERN_ELASTICSEARCH } from '../../../../../common/constants'; import { getLogs } from '../../../../lib/logs/get_logs'; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/nodes.js b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/nodes.js index db12e28916b65..7ea2e6e1e1440 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/nodes.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/nodes.js @@ -11,7 +11,7 @@ import { getClusterStatus } from '../../../../lib/cluster/get_cluster_status'; import { getNodes } from '../../../../lib/elasticsearch/nodes'; import { getNodesShardCount } from '../../../../lib/elasticsearch/shards/get_nodes_shard_count'; import { handleError } from '../../../../lib/errors/handle_error'; -import { prefixIndexPattern } from '../../../../lib/ccs_utils'; +import { prefixIndexPattern } from '../../../../../common/ccs_utils'; import { INDEX_PATTERN_ELASTICSEARCH } from '../../../../../common/constants'; import { getPaginatedNodes } from '../../../../lib/elasticsearch/nodes/get_nodes/get_paginated_nodes'; import { LISTING_METRICS_NAMES } from '../../../../lib/elasticsearch/nodes/get_nodes/nodes_listing_metrics'; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/overview.js b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/overview.js index c76513df721ba..a0fc524768eb9 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/overview.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/overview.js @@ -11,7 +11,7 @@ import { getClusterStatus } from '../../../../lib/cluster/get_cluster_status'; import { getLastRecovery } from '../../../../lib/elasticsearch/get_last_recovery'; import { getMetrics } from '../../../../lib/details/get_metrics'; import { handleError } from '../../../../lib/errors/handle_error'; -import { prefixIndexPattern } from '../../../../lib/ccs_utils'; +import { prefixIndexPattern } from '../../../../../common/ccs_utils'; import { metricSet } from './metric_set_overview'; import { INDEX_PATTERN_ELASTICSEARCH } from '../../../../../common/constants'; import { getLogs } from '../../../../lib/logs'; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch_settings/check/internal_monitoring.ts b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch_settings/check/internal_monitoring.ts index d05d60866d119..3cd2b8b73b315 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch_settings/check/internal_monitoring.ts +++ b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch_settings/check/internal_monitoring.ts @@ -14,7 +14,7 @@ import { INDEX_PATTERN_LOGSTASH, } from '../../../../../../common/constants'; // @ts-ignore -import { prefixIndexPattern } from '../../../../../lib/ccs_utils'; +import { prefixIndexPattern } from '../../../../../../common/ccs_utils'; // @ts-ignore import { handleError } from '../../../../../lib/errors'; import { RouteDependencies, LegacyServer } from '../../../../../types'; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/kibana/instance.ts b/x-pack/plugins/monitoring/server/routes/api/v1/kibana/instance.ts index d16f568b475b4..613ca39275c2d 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/kibana/instance.ts +++ b/x-pack/plugins/monitoring/server/routes/api/v1/kibana/instance.ts @@ -12,7 +12,7 @@ import { handleError } from '../../../../lib/errors'; // @ts-ignore import { getMetrics } from '../../../../lib/details/get_metrics'; // @ts-ignore -import { prefixIndexPattern } from '../../../../lib/ccs_utils'; +import { prefixIndexPattern } from '../../../../../common/ccs_utils'; // @ts-ignore import { metricSet } from './metric_set_instance'; import { INDEX_PATTERN_KIBANA } from '../../../../../common/constants'; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/kibana/instances.js b/x-pack/plugins/monitoring/server/routes/api/v1/kibana/instances.js index 59618f0a217b5..f9b3498cd684e 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/kibana/instances.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/kibana/instances.js @@ -6,7 +6,7 @@ */ import { schema } from '@kbn/config-schema'; -import { prefixIndexPattern } from '../../../../lib/ccs_utils'; +import { prefixIndexPattern } from '../../../../../common/ccs_utils'; import { getKibanaClusterStatus } from './_get_kibana_cluster_status'; import { getKibanas } from '../../../../lib/kibana/get_kibanas'; import { handleError } from '../../../../lib/errors'; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/kibana/overview.js b/x-pack/plugins/monitoring/server/routes/api/v1/kibana/overview.js index cca36d2aad1a7..f9a9443c3533b 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/kibana/overview.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/kibana/overview.js @@ -6,7 +6,7 @@ */ import { schema } from '@kbn/config-schema'; -import { prefixIndexPattern } from '../../../../lib/ccs_utils'; +import { prefixIndexPattern } from '../../../../../common/ccs_utils'; import { getKibanaClusterStatus } from './_get_kibana_cluster_status'; import { getMetrics } from '../../../../lib/details/get_metrics'; import { metricSet } from './metric_set_overview'; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/logstash/node.js b/x-pack/plugins/monitoring/server/routes/api/v1/logstash/node.js index b81b4ea796c63..d3ecea95430ca 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/logstash/node.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/logstash/node.js @@ -9,7 +9,7 @@ import { schema } from '@kbn/config-schema'; import { getNodeInfo } from '../../../../lib/logstash/get_node_info'; import { handleError } from '../../../../lib/errors'; import { getMetrics } from '../../../../lib/details/get_metrics'; -import { prefixIndexPattern } from '../../../../lib/ccs_utils'; +import { prefixIndexPattern } from '../../../../../common/ccs_utils'; import { metricSets } from './metric_set_node'; import { INDEX_PATTERN_LOGSTASH } from '../../../../../common/constants'; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/logstash/nodes.js b/x-pack/plugins/monitoring/server/routes/api/v1/logstash/nodes.js index 74b89ab41be92..051fb7d38fd41 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/logstash/nodes.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/logstash/nodes.js @@ -9,7 +9,7 @@ import { schema } from '@kbn/config-schema'; import { getClusterStatus } from '../../../../lib/logstash/get_cluster_status'; import { getNodes } from '../../../../lib/logstash/get_nodes'; import { handleError } from '../../../../lib/errors'; -import { prefixIndexPattern } from '../../../../lib/ccs_utils'; +import { prefixIndexPattern } from '../../../../../common/ccs_utils'; import { INDEX_PATTERN_LOGSTASH } from '../../../../../common/constants'; /* diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/logstash/overview.js b/x-pack/plugins/monitoring/server/routes/api/v1/logstash/overview.js index 23dd64a1afb74..89a6a93fb207d 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/logstash/overview.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/logstash/overview.js @@ -9,7 +9,7 @@ import { schema } from '@kbn/config-schema'; import { getClusterStatus } from '../../../../lib/logstash/get_cluster_status'; import { getMetrics } from '../../../../lib/details/get_metrics'; import { handleError } from '../../../../lib/errors'; -import { prefixIndexPattern } from '../../../../lib/ccs_utils'; +import { prefixIndexPattern } from '../../../../../common/ccs_utils'; import { metricSet } from './metric_set_overview'; import { INDEX_PATTERN_LOGSTASH } from '../../../../../common/constants'; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/logstash/pipeline.js b/x-pack/plugins/monitoring/server/routes/api/v1/logstash/pipeline.js index b68a708b1f208..6b81059f0c256 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/logstash/pipeline.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/logstash/pipeline.js @@ -10,7 +10,7 @@ import { handleError } from '../../../../lib/errors'; import { getPipelineVersions } from '../../../../lib/logstash/get_pipeline_versions'; import { getPipeline } from '../../../../lib/logstash/get_pipeline'; import { getPipelineVertex } from '../../../../lib/logstash/get_pipeline_vertex'; -import { prefixIndexPattern } from '../../../../lib/ccs_utils'; +import { prefixIndexPattern } from '../../../../../common/ccs_utils'; import { INDEX_PATTERN_LOGSTASH } from '../../../../../common/constants'; function getPipelineVersion(versions, pipelineHash) { @@ -32,8 +32,7 @@ export function logstashPipelineRoute(server) { */ server.route({ method: 'POST', - path: - '/api/monitoring/v1/clusters/{clusterUuid}/logstash/pipeline/{pipelineId}/{pipelineHash?}', + path: '/api/monitoring/v1/clusters/{clusterUuid}/logstash/pipeline/{pipelineId}/{pipelineHash?}', config: { validate: { params: schema.object({ diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/logstash/pipelines/cluster_pipeline_ids.js b/x-pack/plugins/monitoring/server/routes/api/v1/logstash/pipelines/cluster_pipeline_ids.js index c881ff7b3d23c..7f14b74da207d 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/logstash/pipelines/cluster_pipeline_ids.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/logstash/pipelines/cluster_pipeline_ids.js @@ -7,7 +7,7 @@ import { schema } from '@kbn/config-schema'; import { handleError } from '../../../../../lib/errors'; -import { prefixIndexPattern } from '../../../../../lib/ccs_utils'; +import { prefixIndexPattern } from '../../../../../../common/ccs_utils'; import { INDEX_PATTERN_LOGSTASH } from '../../../../../../common/constants'; import { getLogstashPipelineIds } from '../../../../../lib/logstash/get_pipeline_ids'; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/logstash/pipelines/cluster_pipelines.js b/x-pack/plugins/monitoring/server/routes/api/v1/logstash/pipelines/cluster_pipelines.js index 1f7a5e1d436b1..b7d86e86e7a07 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/logstash/pipelines/cluster_pipelines.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/logstash/pipelines/cluster_pipelines.js @@ -8,7 +8,7 @@ import { schema } from '@kbn/config-schema'; import { getClusterStatus } from '../../../../../lib/logstash/get_cluster_status'; import { handleError } from '../../../../../lib/errors'; -import { prefixIndexPattern } from '../../../../../lib/ccs_utils'; +import { prefixIndexPattern } from '../../../../../../common/ccs_utils'; import { INDEX_PATTERN_LOGSTASH } from '../../../../../../common/constants'; import { getPaginatedPipelines } from '../../../../../lib/logstash/get_paginated_pipelines'; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/logstash/pipelines/node_pipelines.js b/x-pack/plugins/monitoring/server/routes/api/v1/logstash/pipelines/node_pipelines.js index 47b8fd81a4d44..f31e88b5b8b08 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/logstash/pipelines/node_pipelines.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/logstash/pipelines/node_pipelines.js @@ -8,7 +8,7 @@ import { schema } from '@kbn/config-schema'; import { getNodeInfo } from '../../../../../lib/logstash/get_node_info'; import { handleError } from '../../../../../lib/errors'; -import { prefixIndexPattern } from '../../../../../lib/ccs_utils'; +import { prefixIndexPattern } from '../../../../../../common/ccs_utils'; import { INDEX_PATTERN_LOGSTASH } from '../../../../../../common/constants'; import { getPaginatedPipelines } from '../../../../../lib/logstash/get_paginated_pipelines'; diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_all_stats.test.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_all_stats.test.ts index 8933833bb068a..b2841213a12d3 100644 --- a/x-pack/plugins/monitoring/server/telemetry_collection/get_all_stats.test.ts +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_all_stats.test.ts @@ -15,7 +15,7 @@ import { LogstashStatsByClusterUuid } from './get_logstash_stats'; describe('get_all_stats', () => { const timestamp = Date.now(); const searchMock = sinon.stub(); - const callCluster = ({ search: searchMock } as unknown) as ElasticsearchClient; + const callCluster = { search: searchMock } as unknown as ElasticsearchClient; afterEach(() => { searchMock.reset(); }); @@ -194,8 +194,8 @@ describe('get_all_stats', () => { describe('handleAllStats', () => { it('handles response', () => { const clusters = handleAllStats(esClusters as ESClusterStats[], { - kibana: (kibanaStats as unknown) as KibanaStats, - logstash: (logstashStats as unknown) as LogstashStatsByClusterUuid, + kibana: kibanaStats as unknown as KibanaStats, + logstash: logstashStats as unknown as LogstashStatsByClusterUuid, beats: {}, }); diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_beats_stats.test.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_beats_stats.test.ts index 36477cb48cd89..fd3b4b0446f81 100644 --- a/x-pack/plugins/monitoring/server/telemetry_collection/get_beats_stats.test.ts +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_beats_stats.test.ts @@ -25,7 +25,7 @@ describe('Get Beats Stats', () => { const start = new Date().toISOString(); const end = new Date().toISOString(); const searchMock = sinon.stub(); - const callCluster = ({ search: searchMock } as unknown) as ElasticsearchClient; + const callCluster = { search: searchMock } as unknown as ElasticsearchClient; beforeEach(() => { const getStub = { get: sinon.stub() }; diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_cluster_uuids.test.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_cluster_uuids.test.ts index 7e7ec23b69daf..4b6c578c8b7e1 100644 --- a/x-pack/plugins/monitoring/server/telemetry_collection/get_cluster_uuids.test.ts +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_cluster_uuids.test.ts @@ -15,7 +15,7 @@ import { describe('get_cluster_uuids', () => { const searchMock = sinon.stub(); - const callCluster = ({ search: searchMock } as unknown) as ElasticsearchClient; + const callCluster = { search: searchMock } as unknown as ElasticsearchClient; afterEach(() => { searchMock.reset(); diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_es_stats.test.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_es_stats.test.ts index 3b5f654be4222..f2d9f343bf1f0 100644 --- a/x-pack/plugins/monitoring/server/telemetry_collection/get_es_stats.test.ts +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_es_stats.test.ts @@ -15,7 +15,7 @@ import { describe('get_es_stats', () => { const searchMock = sinon.stub(); - const client = ({ search: searchMock } as unknown) as ElasticsearchClient; + const client = { search: searchMock } as unknown as ElasticsearchClient; const body = { hits: { hits: [ diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_high_level_stats.test.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_high_level_stats.test.ts index 00a14f7b12f7e..edaf73e43b73b 100644 --- a/x-pack/plugins/monitoring/server/telemetry_collection/get_high_level_stats.test.ts +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_high_level_stats.test.ts @@ -15,7 +15,7 @@ import { describe('get_high_level_stats', () => { const searchMock = sinon.stub(); - const callCluster = ({ search: searchMock } as unknown) as ElasticsearchClient; + const callCluster = { search: searchMock } as unknown as ElasticsearchClient; const product = 'xyz'; const cloudName = 'bare-metal'; const start = new Date().toISOString(); diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_high_level_stats.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_high_level_stats.ts index 77aa6478e96fe..5f14ebb815bab 100644 --- a/x-pack/plugins/monitoring/server/telemetry_collection/get_high_level_stats.ts +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_high_level_stats.ts @@ -211,7 +211,7 @@ export function mapToList(map: Map, keyName: string): T[] { const list: T[] = []; for (const [key, count] of map) { - list.push(({ [keyName]: key, count } as unknown) as T); + list.push({ [keyName]: key, count } as unknown as T); } return list; diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_licenses.test.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_licenses.test.ts index 98f1daf57a85a..90d5d0e5a7fe2 100644 --- a/x-pack/plugins/monitoring/server/telemetry_collection/get_licenses.test.ts +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_licenses.test.ts @@ -11,7 +11,7 @@ import { getLicenses, handleLicenses, fetchLicenses } from './get_licenses'; describe('get_licenses', () => { const searchMock = sinon.stub(); - const client = ({ search: searchMock } as unknown) as ElasticsearchClient; + const client = { search: searchMock } as unknown as ElasticsearchClient; const body = { hits: { hits: [ diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_logstash_stats.test.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_logstash_stats.test.ts index cd77d28f6c430..d093cfbf3a7fb 100644 --- a/x-pack/plugins/monitoring/server/telemetry_collection/get_logstash_stats.test.ts +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_logstash_stats.test.ts @@ -35,7 +35,7 @@ const getBaseOptions = () => ({ describe('Get Logstash Stats', () => { const clusterUuids = ['aCluster', 'bCluster', 'cCluster']; const searchMock = sinon.stub(); - const callCluster = ({ search: searchMock } as unknown) as ElasticsearchClient; + const callCluster = { search: searchMock } as unknown as ElasticsearchClient; beforeEach(() => { searchMock.returns(Promise.resolve({ body: {} })); diff --git a/x-pack/plugins/monitoring/server/types.ts b/x-pack/plugins/monitoring/server/types.ts index 425c7f239138a..416d1ac7c3d86 100644 --- a/x-pack/plugins/monitoring/server/types.ts +++ b/x-pack/plugins/monitoring/server/types.ts @@ -142,9 +142,7 @@ export interface LegacyServer { }; }; elasticsearch: { - getCluster: ( - name: string - ) => { + getCluster: (name: string) => { callWithRequest: (req: any, endpoint: string, params: any) => Promise; }; }; diff --git a/x-pack/plugins/observability/common/utils/join_by_key/index.ts b/x-pack/plugins/observability/common/utils/join_by_key/index.ts index 91c10e5c550d8..e03fe6af8c2f0 100644 --- a/x-pack/plugins/observability/common/utils/join_by_key/index.ts +++ b/x-pack/plugins/observability/common/utils/join_by_key/index.ts @@ -25,10 +25,9 @@ import { isEqual, pull, merge, castArray } from 'lodash'; */ type JoinedReturnType, U extends UnionToIntersection> = Array< - Partial & - { - [k in keyof T]: T[k]; - } + Partial & { + [k in keyof T]: T[k]; + } >; type ArrayOrSingle = T | T[]; diff --git a/x-pack/plugins/observability/public/application/application.test.tsx b/x-pack/plugins/observability/public/application/application.test.tsx index 3b276df08e5af..6b5863c8b122a 100644 --- a/x-pack/plugins/observability/public/application/application.test.tsx +++ b/x-pack/plugins/observability/public/application/application.test.tsx @@ -18,14 +18,14 @@ describe('renderApp', () => { const originalConsole = global.console; beforeAll(() => { // mocks console to avoid poluting the test output - global.console = ({ error: jest.fn() } as unknown) as typeof console; + global.console = { error: jest.fn() } as unknown as typeof console; }); afterAll(() => { global.console = originalConsole; }); it('renders', async () => { - const plugins = ({ + const plugins = { usageCollection: { reportUiCounter: () => {} }, data: { query: { @@ -34,8 +34,8 @@ describe('renderApp', () => { }, }, }, - } as unknown) as ObservabilityPublicPluginsStart; - const core = ({ + } as unknown as ObservabilityPublicPluginsStart; + const core = { application: { currentAppId$: new Observable(), navigateToUrl: () => {} }, chrome: { docTitle: { change: () => {} }, @@ -45,13 +45,13 @@ describe('renderApp', () => { i18n: { Context: ({ children }: { children: React.ReactNode }) => children }, uiSettings: { get: () => false }, http: { basePath: { prepend: (path: string) => path } }, - } as unknown) as CoreStart; + } as unknown as CoreStart; const config = { unsafe: { alertingExperience: { enabled: true }, cases: { enabled: true } } }; - const params = ({ + const params = { element: window.document.createElement('div'), history: createMemoryHistory(), setHeaderActionMenu: () => {}, - } as unknown) as AppMountParameters; + } as unknown as AppMountParameters; expect(() => { const unmount = renderApp({ diff --git a/x-pack/plugins/observability/public/components/app/cases/callout/callout.tsx b/x-pack/plugins/observability/public/components/app/cases/callout/callout.tsx index 4cb3875f75acb..5aa637c8f806d 100644 --- a/x-pack/plugins/observability/public/components/app/cases/callout/callout.tsx +++ b/x-pack/plugins/observability/public/components/app/cases/callout/callout.tsx @@ -29,11 +29,10 @@ function CallOutComponent({ showCallOut, handleDismissCallout, }: CallOutProps) { - const handleCallOut = useCallback(() => handleDismissCallout(id, type), [ - handleDismissCallout, - id, - type, - ]); + const handleCallOut = useCallback( + () => handleDismissCallout(id, type), + [handleDismissCallout, id, type] + ); return showCallOut && !isEmpty(messages) ? ( diff --git a/x-pack/plugins/observability/public/components/app/cases/create/index.tsx b/x-pack/plugins/observability/public/components/app/cases/create/index.tsx index 14bf503c81121..f4749fed5116d 100644 --- a/x-pack/plugins/observability/public/components/app/cases/create/index.tsx +++ b/x-pack/plugins/observability/public/components/app/cases/create/index.tsx @@ -24,10 +24,10 @@ export const Create = React.memo(() => { [casesUrl, navigateToUrl] ); - const handleSetIsCancel = useCallback(() => navigateToUrl(`${casesUrl}`), [ - casesUrl, - navigateToUrl, - ]); + const handleSetIsCancel = useCallback( + () => navigateToUrl(`${casesUrl}`), + [casesUrl, navigateToUrl] + ); return ( diff --git a/x-pack/plugins/observability/public/components/app/news_feed/index.test.tsx b/x-pack/plugins/observability/public/components/app/news_feed/index.test.tsx index 156170fdc633d..97b5dbc679839 100644 --- a/x-pack/plugins/observability/public/components/app/news_feed/index.test.tsx +++ b/x-pack/plugins/observability/public/components/app/news_feed/index.test.tsx @@ -16,12 +16,10 @@ const newsFeedItems = [ en: 'Elastic introduces OpenTelemetry integration', }, description: { - en: - 'We are pleased to announce the availability of the Elastic OpenTelemetry integration — available on Elastic Cloud, or when you download Elastic APM.', + en: 'We are pleased to announce the availability of the Elastic OpenTelemetry integration — available on Elastic Cloud, or when you download Elastic APM.', }, link_url: { - en: - 'https://www.elastic.co/blog/elastic-apm-opentelemetry-integration?blade=observabilitysolutionfeed', + en: 'https://www.elastic.co/blog/elastic-apm-opentelemetry-integration?blade=observabilitysolutionfeed', }, image_url: { en: 'foo.png', @@ -32,12 +30,10 @@ const newsFeedItems = [ en: 'Kubernetes observability tutorial: Log monitoring and analysis', }, description: { - en: - 'Learn how Elastic Observability makes it easy to monitor and detect anomalies in millions of logs from thousands of containers running hundreds of microservices — while Kubernetes scales applications with changing pod counts. All from a single UI.', + en: 'Learn how Elastic Observability makes it easy to monitor and detect anomalies in millions of logs from thousands of containers running hundreds of microservices — while Kubernetes scales applications with changing pod counts. All from a single UI.', }, link_url: { - en: - 'https://www.elastic.co/blog/kubernetes-observability-tutorial-k8s-log-monitoring-and-analysis-elastic-stack?blade=observabilitysolutionfeed', + en: 'https://www.elastic.co/blog/kubernetes-observability-tutorial-k8s-log-monitoring-and-analysis-elastic-stack?blade=observabilitysolutionfeed', }, image_url: null, }, @@ -46,12 +42,10 @@ const newsFeedItems = [ en: 'Kubernetes observability tutorial: K8s cluster setup and demo app deployment', }, description: { - en: - 'This blog will walk you through configuring the environment you will be using for the Kubernetes observability tutorial blog series. We will be deploying Elasticsearch Service, a Minikube single-node Kubernetes cluster setup, and a demo app.', + en: 'This blog will walk you through configuring the environment you will be using for the Kubernetes observability tutorial blog series. We will be deploying Elasticsearch Service, a Minikube single-node Kubernetes cluster setup, and a demo app.', }, link_url: { - en: - 'https://www.elastic.co/blog/kubernetes-observability-tutorial-k8s-cluster-setup-demo-app-deployment?blade=observabilitysolutionfeed', + en: 'https://www.elastic.co/blog/kubernetes-observability-tutorial-k8s-cluster-setup-demo-app-deployment?blade=observabilitysolutionfeed', }, image_url: { en: null, diff --git a/x-pack/plugins/observability/public/components/app/section/apm/index.test.tsx b/x-pack/plugins/observability/public/components/app/section/apm/index.test.tsx index 16eb8dd24d3c2..8b1480246210a 100644 --- a/x-pack/plugins/observability/public/components/app/section/apm/index.test.tsx +++ b/x-pack/plugins/observability/public/components/app/section/apm/index.test.tsx @@ -37,14 +37,14 @@ describe('APMSection', () => { }, } as HasDataContextValue); jest.spyOn(pluginContext, 'usePluginContext').mockImplementation(() => ({ - core: ({ + core: { uiSettings: { get: jest.fn() }, http: { basePath: { prepend: jest.fn() } }, - } as unknown) as CoreStart, + } as unknown as CoreStart, appMountParameters: {} as AppMountParameters, config: { unsafe: { alertingExperience: { enabled: true }, cases: { enabled: true } } }, observabilityRuleTypeRegistry: createObservabilityRuleTypeRegistryMock(), - plugins: ({ + plugins: { data: { query: { timefilter: { @@ -57,7 +57,7 @@ describe('APMSection', () => { }, }, }, - } as unknown) as ObservabilityPublicPluginsStart, + } as unknown as ObservabilityPublicPluginsStart, ObservabilityPageTemplate: KibanaPageTemplate, })); }); diff --git a/x-pack/plugins/observability/public/components/app/section/helper.test.ts b/x-pack/plugins/observability/public/components/app/section/helper.test.ts index 0dc430cfa4a75..43c30acbacdc1 100644 --- a/x-pack/plugins/observability/public/components/app/section/helper.test.ts +++ b/x-pack/plugins/observability/public/components/app/section/helper.test.ts @@ -10,12 +10,12 @@ import { History } from 'history'; describe('Chart helper', () => { describe('onBrushEnd', () => { - const history = ({ + const history = { push: jest.fn(), location: { search: '', }, - } as unknown) as History; + } as unknown as History; it("doesn't push a new history when x is not defined", () => { onBrushEnd({ x: undefined, history }); expect(history.push).not.toBeCalled(); diff --git a/x-pack/plugins/observability/public/components/app/section/ux/index.test.tsx b/x-pack/plugins/observability/public/components/app/section/ux/index.test.tsx index 61bce8aaf845d..19bc20f5da605 100644 --- a/x-pack/plugins/observability/public/components/app/section/ux/index.test.tsx +++ b/x-pack/plugins/observability/public/components/app/section/ux/index.test.tsx @@ -37,13 +37,13 @@ describe('UXSection', () => { }, } as HasDataContextValue); jest.spyOn(pluginContext, 'usePluginContext').mockImplementation(() => ({ - core: ({ + core: { uiSettings: { get: jest.fn() }, http: { basePath: { prepend: jest.fn() } }, - } as unknown) as CoreStart, + } as unknown as CoreStart, appMountParameters: {} as AppMountParameters, config: { unsafe: { alertingExperience: { enabled: true }, cases: { enabled: true } } }, - plugins: ({ + plugins: { data: { query: { timefilter: { @@ -56,7 +56,7 @@ describe('UXSection', () => { }, }, }, - } as unknown) as ObservabilityPublicPluginsStart, + } as unknown as ObservabilityPublicPluginsStart, observabilityRuleTypeRegistry: createObservabilityRuleTypeRegistryMock(), ObservabilityPageTemplate: KibanaPageTemplate, })); 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 5c07b4626cf19..dd2e8aa03e928 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 @@ -13,9 +13,9 @@ import { createKibanaReactContext } from '../../../../../../../../src/plugins/ki import { CoreVitalItem } from '../core_vital_item'; import { LCP_HELP_LABEL, LCP_LABEL } from '../translations'; -const KibanaReactContext = createKibanaReactContext(({ +const KibanaReactContext = createKibanaReactContext({ uiSettings: { get: () => {}, get$: () => new Observable() }, -} as unknown) as Partial); +} as unknown as Partial); export default { title: 'app/RumDashboard/CoreVitalItem', diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts index bd8e158b2d4ab..706c58609b7cb 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts @@ -356,8 +356,7 @@ describe('Lens Attribute', () => { min: 0, }, name: 'divide', - text: - "count(kql='transaction.type: page-load and processor.event: transaction and transaction.type : *') / overall_sum(count(kql='transaction.type: page-load and processor.event: transaction and transaction.type : *'))", + text: "count(kql='transaction.type: page-load and processor.event: transaction and transaction.type : *') / overall_sum(count(kql='transaction.type: page-load and processor.event: transaction and transaction.type : *'))", type: 'function', }, }, @@ -564,8 +563,7 @@ describe('Lens Attribute', () => { min: 0, }, name: 'divide', - text: - "count(kql='transaction.type: page-load and processor.event: transaction and transaction.type : *') / overall_sum(count(kql='transaction.type: page-load and processor.event: transaction and transaction.type : *'))", + text: "count(kql='transaction.type: page-load and processor.event: transaction and transaction.type : *') / overall_sum(count(kql='transaction.type: page-load and processor.event: transaction and transaction.type : *'))", type: 'function', }, }, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts index 800152d6978f0..2778edc94838e 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts @@ -111,7 +111,8 @@ export class LensAttributes { if (operationType) { seriesConfig.yAxisColumns.forEach((yAxisColumn) => { if (typeof yAxisColumn.operationType !== undefined) { - yAxisColumn.operationType = operationType as FieldBasedIndexPatternColumn['operationType']; + yAxisColumn.operationType = + operationType as FieldBasedIndexPatternColumn['operationType']; } }); } @@ -330,14 +331,8 @@ export class LensAttributes { layerConfig: LayerConfig; colIndex?: number; }) { - const { - fieldMeta, - columnType, - fieldName, - columnLabel, - timeScale, - columnFilters, - } = this.getFieldMeta(sourceField, layerConfig); + const { fieldMeta, columnType, fieldName, columnLabel, timeScale, columnFilters } = + this.getFieldMeta(sourceField, layerConfig); const { type: fieldType } = fieldMeta ?? {}; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_columns/overall_column.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_columns/overall_column.tsx index 05764517bc36f..44b40944dfd07 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_columns/overall_column.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_columns/overall_column.tsx @@ -83,13 +83,13 @@ export function getDistributionInPercentageColumn({ isBucketed: false, scale: 'ratio', params: { - tinymathAst: ({ + tinymathAst: { type: 'function', name: 'divide', args: [`${yAxisColId}X0`, `${yAxisColId}X3`], location: { min: 0, max: 30 }, text: lensFormula, - } as unknown) as TinymathAST, + } as unknown as TinymathAST, }, references: [`${yAxisColId}X0`, `${yAxisColId}X3`], customLabel: true, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute.ts index 73a722642f69b..596e7af4378ec 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute.ts @@ -127,8 +127,7 @@ export const sampleAttribute = { min: 0, }, name: 'divide', - text: - "count(kql='transaction.type: page-load and processor.event: transaction and transaction.type : *') / overall_sum(count(kql='transaction.type: page-load and processor.event: transaction and transaction.type : *'))", + text: "count(kql='transaction.type: page-load and processor.event: transaction and transaction.type : *') / overall_sum(count(kql='transaction.type: page-load and processor.event: transaction and transaction.type : *'))", type: 'function', }, }, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/utils.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/utils.ts index 1af4e83ed1f54..c7d2d21581e7a 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/utils.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/utils.ts @@ -7,7 +7,7 @@ import rison, { RisonValue } from 'rison-node'; import type { SeriesUrl, UrlFilter } from '../types'; import type { AllSeries, AllShortSeries } from '../hooks/use_series_storage'; -import { IndexPattern } from '../../../../../../../../src/plugins/data/common/index_patterns'; +import { IndexPattern } from '../../../../../../../../src/plugins/data/common'; import { esFilters, ExistsFilter } from '../../../../../../../../src/plugins/data/public'; import { URL_KEYS } from './constants/url_constants'; import { PersistableFilter } from '../../../../../../lens/common'; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.tsx index bfa457ee4025f..706ba546b2848 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/header.tsx @@ -101,7 +101,7 @@ export function ExploratoryViewHeader({ seriesId, lensAttributes }: Props) { {isSaveOpen && lensAttributes && ( setIsSaveOpen(false)} onSave={() => {}} /> diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_app_index_pattern.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_app_index_pattern.tsx index e508990ea25a4..88818665bbe2a 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_app_index_pattern.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_app_index_pattern.tsx @@ -101,7 +101,7 @@ export function IndexPatternContextProvider({ children }: ProviderProps) { export const useAppIndexPatternContext = (dataType?: AppDataType) => { const { loading, hasAppData, loadIndexPattern, indexPatterns } = useContext( - (IndexPatternContext as unknown) as Context + IndexPatternContext as unknown as Context ); if (dataType && !indexPatterns?.[dataType] && !loading) { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/rtl_helpers.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/rtl_helpers.tsx index bad9f0d7ff415..bc77a0520925f 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/rtl_helpers.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/rtl_helpers.tsx @@ -34,10 +34,7 @@ import * as useValuesListHook from '../../../hooks/use_values_list'; import indexPatternData from './configurations/test_data/test_index_pattern.json'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { setIndexPatterns } from '../../../../../../../src/plugins/data/public/services'; -import { - IndexPattern, - IndexPatternsContract, -} from '../../../../../../../src/plugins/data/common/index_patterns/index_patterns'; +import { IndexPattern, IndexPatternsContract } from '../../../../../../../src/plugins/data/common'; import { createStubIndexPattern } from '../../../../../../../src/plugins/data/common/stubs'; import { AppDataType, UrlFilter } from './types'; import { dataPluginMock } from '../../../../../../../src/plugins/data/public/mocks'; @@ -80,19 +77,19 @@ interface RenderRouterOptions extends KibanaProviderOptions(key: string): T { if (key === 'timepicker:quickRanges') { - return ([ + return [ { display: 'Today', from: 'now/d', to: 'now/d', }, - ] as unknown) as T; + ] as unknown as T; } - return ('MMM D, YYYY @ HH:mm:ss.SSS' as unknown) as T; + return 'MMM D, YYYY @ HH:mm:ss.SSS' as unknown as T; } function setSetting$(key: string): T { - return (of('MMM D, YYYY @ HH:mm:ss.SSS') as unknown) as T; + return of('MMM D, YYYY @ HH:mm:ss.SSS') as unknown as T; } /* default mock core */ @@ -134,10 +131,10 @@ export function MockKibanaProvider>({ }: MockKibanaProviderProps) { const indexPattern = mockIndexPattern; - setIndexPatterns(({ + setIndexPatterns({ ...[indexPattern], get: async () => indexPattern, - } as unknown) as IndexPatternsContract); + } as unknown as IndexPatternsContract); return ( @@ -239,7 +236,7 @@ export const mockAppIndexPattern = () => { loading: false, hasAppData: { ux: true } as any, loadIndexPattern, - indexPatterns: ({ ux: mockIndexPattern } as unknown) as Record, + indexPatterns: { ux: mockIndexPattern } as unknown as Record, }); return { spy, loadIndexPattern }; }; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx index 0c620abf56e8a..7962bf2b924f7 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx @@ -36,13 +36,8 @@ export function ReportDefinitionCol({ const { reportDefinitions: selectedReportDefinitions = {}, selectedMetricField } = series ?? {}; - const { - definitionFields, - defaultSeriesType, - hasOperationType, - yAxisColumns, - metricOptions, - } = seriesConfig; + const { definitionFields, defaultSeriesType, hasOperationType, yAxisColumns, metricOptions } = + seriesConfig; const onChange = (field: string, value?: string[]) => { if (!value?.[0]) { 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 1152ba32960ed..921b5c63293d9 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 @@ -19,9 +19,9 @@ const values = [ { label: 'apm server', count: 2 }, ]; -const KibanaReactContext = createKibanaReactContext(({ +const KibanaReactContext = createKibanaReactContext({ uiSettings: { get: () => {}, get$: () => new Observable() }, -} as unknown) as Partial); +} as unknown as Partial); export default { title: 'app/Shared/FieldValueSuggestions', diff --git a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_selection.tsx b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_selection.tsx index f713af9768229..aca29c4723688 100644 --- a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_selection.tsx +++ b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_selection.tsx @@ -71,6 +71,7 @@ export function FieldValueSelection({ selectedValue, excludedValue, compressed = true, + allowExclusions = true, onChange: onSelectionChange, }: FieldValueSelectionProps) { const [options, setOptions] = useState(() => @@ -142,7 +143,7 @@ export function FieldValueSelection({ .filter((opt) => opt?.checked === 'off') .map(({ label: labelN }) => labelN); - return isEqual(selectedValue ?? [], currSelected) && isEqual(excludedValue, currExcluded); + return isEqual(selectedValue ?? [], currSelected) && isEqual(excludedValue ?? [], currExcluded); }; return ( @@ -174,7 +175,7 @@ export function FieldValueSelection({ options={options} onChange={onChange} isLoading={loading && !query && options.length === 0} - allowExclusions={true} + allowExclusions={allowExclusions} > {(list, search) => (
@@ -190,6 +191,13 @@ export function FieldValueSelection({ )} ); } diff --git a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/types.ts b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/types.ts index d857b39b074ac..046f98748cdf2 100644 --- a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/types.ts +++ b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/types.ts @@ -24,6 +24,9 @@ interface CommonProps { asFilterButton?: boolean; showCount?: boolean; allowAllValuesSelection?: boolean; + cardinalityField?: string; + required?: boolean; + allowExclusions?: boolean; } export type FieldValueSuggestionsProps = CommonProps & { diff --git a/x-pack/plugins/observability/public/context/has_data_context.test.tsx b/x-pack/plugins/observability/public/context/has_data_context.test.tsx index b51091e7eb973..36f7bfb990222 100644 --- a/x-pack/plugins/observability/public/context/has_data_context.test.tsx +++ b/x-pack/plugins/observability/public/context/has_data_context.test.tsx @@ -74,7 +74,7 @@ describe('HasDataContextProvider', () => { absoluteEnd: new Date(relativeEnd).valueOf(), })); jest.spyOn(pluginContext, 'usePluginContext').mockReturnValue({ - core: ({ http: { get: jest.fn() } } as unknown) as CoreStart, + core: { http: { get: jest.fn() } } as unknown as CoreStart, } as PluginContextValue); }); @@ -515,7 +515,7 @@ describe('HasDataContextProvider', () => { describe('with alerts', () => { beforeAll(() => { jest.spyOn(pluginContext, 'usePluginContext').mockReturnValue({ - core: ({ + core: { http: { get: async () => { return { @@ -526,7 +526,7 @@ describe('HasDataContextProvider', () => { }; }, }, - } as unknown) as CoreStart, + } as unknown as CoreStart, } as PluginContextValue); }); diff --git a/x-pack/plugins/observability/public/data_handler.test.ts b/x-pack/plugins/observability/public/data_handler.test.ts index b7ad282c2cf93..e0b8494d4dbed 100644 --- a/x-pack/plugins/observability/public/data_handler.test.ts +++ b/x-pack/plugins/observability/public/data_handler.test.ts @@ -30,7 +30,7 @@ describe('registerDataHandler', () => { const originalConsole = global.console; beforeAll(() => { // mocks console to avoid polluting the test output - global.console = ({ error: jest.fn() } as unknown) as typeof console; + global.console = { error: jest.fn() } as unknown as typeof console; }); afterAll(() => { diff --git a/x-pack/plugins/observability/public/hooks/use_breadcrumbs.test.tsx b/x-pack/plugins/observability/public/hooks/use_breadcrumbs.test.tsx index d033ecc2069cd..e5efca1d67a25 100644 --- a/x-pack/plugins/observability/public/hooks/use_breadcrumbs.test.tsx +++ b/x-pack/plugins/observability/public/hooks/use_breadcrumbs.test.tsx @@ -14,11 +14,11 @@ import { useBreadcrumbs } from './use_breadcrumbs'; const setBreadcrumbs = jest.fn(); const setTitle = jest.fn(); -const kibanaServices = ({ +const kibanaServices = { application: { getUrlForApp: () => {}, navigateToApp: () => {} }, chrome: { setBreadcrumbs, docTitle: { change: setTitle } }, uiSettings: { get: () => true }, -} as unknown) as Partial; +} as unknown as Partial; const KibanaContext = createKibanaReactContext(kibanaServices); function Wrapper({ children }: { children?: ReactNode }) { @@ -42,7 +42,7 @@ describe('useBreadcrumbs', () => { + { ...kibanaServices, chrome: { docTitle: {} } } as unknown as Partial } > {children} diff --git a/x-pack/plugins/observability/public/hooks/use_es_search.ts b/x-pack/plugins/observability/public/hooks/use_es_search.ts index 27c4081a99775..70ffdbba22c58 100644 --- a/x-pack/plugins/observability/public/hooks/use_es_search.ts +++ b/x-pack/plugins/observability/public/hooks/use_es_search.ts @@ -12,7 +12,7 @@ import { useKibana } from '../../../../../src/plugins/kibana_react/public'; import { isCompleteResponse } from '../../../../../src/plugins/data/common'; import { useFetcher } from './use_fetcher'; -export const useEsSearch = ( +export const useEsSearch = ( params: TParams, fnDeps: any[] ) => { @@ -43,7 +43,7 @@ export const useEsSearch = ( const { rawResponse } = response as any; - return { data: rawResponse as ESSearchResponse, loading }; + return { data: rawResponse as ESSearchResponse, loading }; }; export function createEsParams(params: T): T { diff --git a/x-pack/plugins/observability/public/hooks/use_route_params.tsx b/x-pack/plugins/observability/public/hooks/use_route_params.tsx index b2ca3787070af..7752dda76ff75 100644 --- a/x-pack/plugins/observability/public/hooks/use_route_params.tsx +++ b/x-pack/plugins/observability/public/hooks/use_route_params.tsx @@ -46,8 +46,8 @@ export function useRouteParams(pathName: T): Rout console.error(PathReporter.report(pathResult)[0]); } - return ({ + return { query: isLeft(queryResult) ? {} : queryResult.right, path: isLeft(pathResult) ? {} : pathResult.right, - } as unknown) as RouteParams; + } as unknown as RouteParams; } diff --git a/x-pack/plugins/observability/public/hooks/use_time_range.test.ts b/x-pack/plugins/observability/public/hooks/use_time_range.test.ts index 7a241401722cf..bf513d8a1a99a 100644 --- a/x-pack/plugins/observability/public/hooks/use_time_range.test.ts +++ b/x-pack/plugins/observability/public/hooks/use_time_range.test.ts @@ -25,7 +25,7 @@ describe('useTimeRange', () => { core: {} as CoreStart, appMountParameters: {} as AppMountParameters, config: { unsafe: { alertingExperience: { enabled: true }, cases: { enabled: true } } }, - plugins: ({ + plugins: { data: { query: { timefilter: { @@ -38,7 +38,7 @@ describe('useTimeRange', () => { }, }, }, - } as unknown) as ObservabilityPublicPluginsStart, + } as unknown as ObservabilityPublicPluginsStart, observabilityRuleTypeRegistry: createObservabilityRuleTypeRegistryMock(), ObservabilityPageTemplate: () => null, })); @@ -68,7 +68,7 @@ describe('useTimeRange', () => { core: {} as CoreStart, appMountParameters: {} as AppMountParameters, config: { unsafe: { alertingExperience: { enabled: true }, cases: { enabled: true } } }, - plugins: ({ + plugins: { data: { query: { timefilter: { @@ -81,7 +81,7 @@ describe('useTimeRange', () => { }, }, }, - } as unknown) as ObservabilityPublicPluginsStart, + } as unknown as ObservabilityPublicPluginsStart, observabilityRuleTypeRegistry: createObservabilityRuleTypeRegistryMock(), ObservabilityPageTemplate: () => null, })); diff --git a/x-pack/plugins/observability/public/hooks/use_values_list.ts b/x-pack/plugins/observability/public/hooks/use_values_list.ts index 46d89a062f072..5aa7dd672cfda 100644 --- a/x-pack/plugins/observability/public/hooks/use_values_list.ts +++ b/x-pack/plugins/observability/public/hooks/use_values_list.ts @@ -18,6 +18,7 @@ export interface Props { filters?: ESFilter[]; time?: { from: string; to: string }; keepHistory?: boolean; + cardinalityField?: string; } export interface ListItem { @@ -32,6 +33,7 @@ export const useValuesList = ({ filters, time, keepHistory, + cardinalityField, }: Props): { values: ListItem[]; loading?: boolean } => { const [debouncedQuery, setDebounceQuery] = useState(query); const [values, setValues] = useState([]); @@ -93,9 +95,20 @@ export const useValuesList = ({ values: { terms: { field: sourceField, - size: 100, + size: 50, ...(query ? { include: includeClause } : {}), }, + ...(cardinalityField + ? { + aggs: { + count: { + cardinality: { + field: cardinalityField, + }, + }, + }, + } + : {}), }, }, }, @@ -105,10 +118,20 @@ export const useValuesList = ({ useEffect(() => { const newValues = - data?.aggregations?.values.buckets.map(({ key: value, doc_count: count }) => ({ - count, - label: String(value), - })) ?? []; + data?.aggregations?.values.buckets.map( + ({ key: value, doc_count: count, count: aggsCount }) => { + if (aggsCount) { + return { + count: aggsCount.value, + label: String(value), + }; + } + return { + count, + label: String(value), + }; + } + ) ?? []; if (keepHistory && query) { setValues((prevState) => { diff --git a/x-pack/plugins/observability/public/index.ts b/x-pack/plugins/observability/public/index.ts index 380190aa7f223..710bed3adb890 100644 --- a/x-pack/plugins/observability/public/index.ts +++ b/x-pack/plugins/observability/public/index.ts @@ -60,6 +60,7 @@ export { export const LazyAlertsFlyout = lazy(() => import('./pages/alerts/alerts_flyout')); export { useFetcher, FETCH_STATUS } from './hooks/use_fetcher'; +export { useEsSearch, createEsParams } from './hooks/use_es_search'; export * from './typings'; diff --git a/x-pack/plugins/observability/public/pages/alerts/alerts_flyout/alerts_flyout.stories.tsx b/x-pack/plugins/observability/public/pages/alerts/alerts_flyout/alerts_flyout.stories.tsx index f507802a1e7d1..a0cc697a7a510 100644 --- a/x-pack/plugins/observability/public/pages/alerts/alerts_flyout/alerts_flyout.stories.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/alerts_flyout/alerts_flyout.stories.tsx @@ -39,11 +39,11 @@ export default { {' '} '' } }, }, - } as unknown) as PluginContextValue + } as unknown as PluginContextValue } > diff --git a/x-pack/plugins/observability/public/pages/alerts/alerts_flyout/index.tsx b/x-pack/plugins/observability/public/pages/alerts/alerts_flyout/index.tsx index fc246641101e3..9bad3908df4a5 100644 --- a/x-pack/plugins/observability/public/pages/alerts/alerts_flyout/index.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/alerts_flyout/index.tsx @@ -54,8 +54,10 @@ type AlertsFlyoutProps = { } & EuiFlyoutProps; const ALERT_DURATION: typeof ALERT_DURATION_TYPED = ALERT_DURATION_NON_TYPED; -const ALERT_EVALUATION_THRESHOLD: typeof ALERT_EVALUATION_THRESHOLD_TYPED = ALERT_EVALUATION_THRESHOLD_NON_TYPED; -const ALERT_EVALUATION_VALUE: typeof ALERT_EVALUATION_VALUE_TYPED = ALERT_EVALUATION_VALUE_NON_TYPED; +const ALERT_EVALUATION_THRESHOLD: typeof ALERT_EVALUATION_THRESHOLD_TYPED = + ALERT_EVALUATION_THRESHOLD_NON_TYPED; +const ALERT_EVALUATION_VALUE: typeof ALERT_EVALUATION_VALUE_TYPED = + ALERT_EVALUATION_VALUE_NON_TYPED; const ALERT_UUID: typeof ALERT_UUID_TYPED = ALERT_UUID_NON_TYPED; const ALERT_RULE_CATEGORY: typeof ALERT_RULE_CATEGORY_TYPED = ALERT_RULE_CATEGORY_NON_TYPED; const ALERT_RULE_NAME: typeof ALERT_RULE_NAME_TYPED = ALERT_RULE_NAME_NON_TYPED; diff --git a/x-pack/plugins/observability/public/pages/alerts/alerts_table_t_grid.tsx b/x-pack/plugins/observability/public/pages/alerts/alerts_table_t_grid.tsx index bca8c8095511e..2df3053c380cb 100644 --- a/x-pack/plugins/observability/public/pages/alerts/alerts_table_t_grid.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/alerts_table_t_grid.tsx @@ -168,15 +168,18 @@ function ObservabilityActions({ application: { capabilities }, } = useKibana().services; - const parseObservabilityAlert = useMemo(() => parseAlert(observabilityRuleTypeRegistry), [ - observabilityRuleTypeRegistry, - ]); - const alertDataConsumer = useMemo(() => get(dataFieldEs, ALERT_RULE_CONSUMER, [''])[0], [ - dataFieldEs, - ]); - const alertDataProducer = useMemo(() => get(dataFieldEs, ALERT_RULE_PRODUCER, [''])[0], [ - dataFieldEs, - ]); + const parseObservabilityAlert = useMemo( + () => parseAlert(observabilityRuleTypeRegistry), + [observabilityRuleTypeRegistry] + ); + const alertDataConsumer = useMemo( + () => get(dataFieldEs, ALERT_RULE_CONSUMER, [''])[0], + [dataFieldEs] + ); + const alertDataProducer = useMemo( + () => get(dataFieldEs, ALERT_RULE_PRODUCER, [''])[0], + [dataFieldEs] + ); const alert = parseObservabilityAlert(dataFieldEs); const { prepend } = core.http.basePath; @@ -277,6 +280,7 @@ function ObservabilityActions({ iconType="boxesHorizontal" aria-label="More" onClick={() => toggleActionsPopover(eventId)} + data-test-subj="alerts-table-row-action-more" /> } isOpen={openActionsPopoverId === eventId} diff --git a/x-pack/plugins/observability/public/pages/alerts/default_cell_actions.tsx b/x-pack/plugins/observability/public/pages/alerts/default_cell_actions.tsx index 9c93f05196a1c..5ad4804f88d5e 100644 --- a/x-pack/plugins/observability/public/pages/alerts/default_cell_actions.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/default_cell_actions.tsx @@ -35,54 +35,48 @@ const useKibanaServices = () => { /** actions common to all cells (e.g. copy to clipboard) */ const commonCellActions: TGridCellAction[] = [ - ({ data, pageSize }: { data: TimelineNonEcsData[][]; pageSize: number }) => ({ - rowIndex, - columnId, - Component, - }) => { - const { timelines } = useKibanaServices(); + ({ data, pageSize }: { data: TimelineNonEcsData[][]; pageSize: number }) => + ({ rowIndex, columnId, Component }) => { + const { timelines } = useKibanaServices(); - const value = getMappedNonEcsValue({ - data: data[getPageRowIndex(rowIndex, pageSize)], - fieldName: columnId, - }); + const value = getMappedNonEcsValue({ + data: data[getPageRowIndex(rowIndex, pageSize)], + fieldName: columnId, + }); - return ( - <> - {timelines.getHoverActions().getCopyButton({ - Component, - field: columnId, - isHoverAction: false, - ownFocus: false, - showTooltip: false, - value, - })} - - ); - }, + return ( + <> + {timelines.getHoverActions().getCopyButton({ + Component, + field: columnId, + isHoverAction: false, + ownFocus: false, + showTooltip: false, + value, + })} + + ); + }, ]; /** actions for adding filters to the search bar */ const buildFilterCellActions = (addToQuery: (value: string) => void): TGridCellAction[] => [ - ({ data, pageSize }: { data: TimelineNonEcsData[][]; pageSize: number }) => ({ - rowIndex, - columnId, - Component, - }) => { - const value = getMappedNonEcsValue({ - data: data[getPageRowIndex(rowIndex, pageSize)], - fieldName: columnId, - }); + ({ data, pageSize }: { data: TimelineNonEcsData[][]; pageSize: number }) => + ({ rowIndex, columnId, Component }) => { + const value = getMappedNonEcsValue({ + data: data[getPageRowIndex(rowIndex, pageSize)], + fieldName: columnId, + }); - return ( - - ); - }, + return ( + + ); + }, ]; /** returns the default actions shown in `EuiDataGrid` cells */ diff --git a/x-pack/plugins/observability/public/pages/alerts/parse_alert.ts b/x-pack/plugins/observability/public/pages/alerts/parse_alert.ts index 6b4240c9ad346..e4d3b08c00fae 100644 --- a/x-pack/plugins/observability/public/pages/alerts/parse_alert.ts +++ b/x-pack/plugins/observability/public/pages/alerts/parse_alert.ts @@ -29,21 +29,21 @@ const ALERT_STATUS: typeof ALERT_STATUS_TYPED = ALERT_STATUS_NON_TYPED; const ALERT_RULE_TYPE_ID: typeof ALERT_RULE_TYPE_ID_TYPED = ALERT_RULE_TYPE_ID_NON_TYPED; const ALERT_RULE_NAME: typeof ALERT_RULE_NAME_TYPED = ALERT_RULE_NAME_NON_TYPED; -export const parseAlert = (observabilityRuleTypeRegistry: ObservabilityRuleTypeRegistry) => ( - alert: Record -): TopAlert => { - const parsedFields = parseTechnicalFields(alert); - const formatter = observabilityRuleTypeRegistry.getFormatter(parsedFields[ALERT_RULE_TYPE_ID]!); - const formatted = { - link: undefined, - reason: parsedFields[ALERT_RULE_NAME] ?? '', - ...(formatter?.({ fields: parsedFields, formatters: { asDuration, asPercent } }) ?? {}), - }; +export const parseAlert = + (observabilityRuleTypeRegistry: ObservabilityRuleTypeRegistry) => + (alert: Record): TopAlert => { + const parsedFields = parseTechnicalFields(alert); + const formatter = observabilityRuleTypeRegistry.getFormatter(parsedFields[ALERT_RULE_TYPE_ID]!); + const formatted = { + link: undefined, + reason: parsedFields[ALERT_RULE_NAME] ?? '', + ...(formatter?.({ fields: parsedFields, formatters: { asDuration, asPercent } }) ?? {}), + }; - return { - ...formatted, - fields: parsedFields, - active: parsedFields[ALERT_STATUS] === ALERT_STATUS_ACTIVE, - start: new Date(parsedFields[ALERT_START] ?? 0).getTime(), + return { + ...formatted, + fields: parsedFields, + active: parsedFields[ALERT_STATUS] === ALERT_STATUS_ACTIVE, + start: new Date(parsedFields[ALERT_START] ?? 0).getTime(), + }; }; -}; diff --git a/x-pack/plugins/observability/public/pages/alerts/workflow_status_filter.test.tsx b/x-pack/plugins/observability/public/pages/alerts/workflow_status_filter.test.tsx index cc16f1c5a44a1..f7441742ff387 100644 --- a/x-pack/plugins/observability/public/pages/alerts/workflow_status_filter.test.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/workflow_status_filter.test.tsx @@ -28,7 +28,7 @@ describe('StatusFilter', () => { const props = { onChange, status }; const { getByTestId } = render(); - const button = getByTestId(`WorkflowStatusFilter ${status} button`); + const button = getByTestId(`workflow-status-filter-${status}-button`); const input = button.querySelector('input') as Element; Simulate.change(input); diff --git a/x-pack/plugins/observability/public/pages/alerts/workflow_status_filter.tsx b/x-pack/plugins/observability/public/pages/alerts/workflow_status_filter.tsx index 475ba17a9d2f5..20073e9937b4f 100644 --- a/x-pack/plugins/observability/public/pages/alerts/workflow_status_filter.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/workflow_status_filter.tsx @@ -21,7 +21,7 @@ const options: Array = label: i18n.translate('xpack.observability.alerts.workflowStatusFilter.openButtonLabel', { defaultMessage: 'Open', }), - 'data-test-subj': 'WorkflowStatusFilter open button', + 'data-test-subj': 'workflow-status-filter-open-button', }, { id: 'acknowledged', @@ -31,14 +31,14 @@ const options: Array = defaultMessage: 'Acknowledged', } ), - 'data-test-subj': 'WorkflowStatusFilter acknowledged button', + 'data-test-subj': 'workflow-status-filter-acknowledged-button', }, { id: 'closed', label: i18n.translate('xpack.observability.alerts.workflowStatusFilter.closedButtonLabel', { defaultMessage: 'Closed', }), - 'data-test-subj': 'WorkflowStatusFilter closed button', + 'data-test-subj': 'workflow-status-filter-closed-button', }, ]; diff --git a/x-pack/plugins/observability/public/pages/cases/cases.stories.tsx b/x-pack/plugins/observability/public/pages/cases/cases.stories.tsx index 1eaaa42f7cc10..a9c83fa650394 100644 --- a/x-pack/plugins/observability/public/pages/cases/cases.stories.tsx +++ b/x-pack/plugins/observability/public/pages/cases/cases.stories.tsx @@ -19,7 +19,7 @@ export default { component: AllCasesPage, decorators: [ (Story: ComponentType) => { - const KibanaReactContext = createKibanaReactContext(({ + const KibanaReactContext = createKibanaReactContext({ application: { capabilities: { [casesFeatureId]: { read_cases: true } }, getUrlForApp: () => '', @@ -31,11 +31,11 @@ export default { ELASTIC_WEBSITE_URL: 'https://www.elastic.co/', }, uiSettings: { get: () => true }, - } as unknown) as Partial); + } as unknown as Partial); - const pluginContextValue = ({ + const pluginContextValue = { ObservabilityPageTemplate: EuiPageTemplate, - } as unknown) as PluginContextValue; + } as unknown as PluginContextValue; return ( 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 a5dda681b26ad..c1a446435cb22 100644 --- a/x-pack/plugins/observability/public/pages/landing/landing.stories.tsx +++ b/x-pack/plugins/observability/public/pages/landing/landing.stories.tsx @@ -18,14 +18,14 @@ export default { component: LandingPage, decorators: [ (Story: ComponentType) => { - const KibanaReactContext = createKibanaReactContext(({ + const KibanaReactContext = createKibanaReactContext({ application: { getUrlForApp: () => '', navigateToUrl: () => {} }, chrome: { docTitle: { change: () => {} }, setBreadcrumbs: () => {} }, uiSettings: { get: () => true }, observability: { ObservabilityPageTemplate: EuiPageTemplate }, - } as unknown) as Partial); + } as unknown as Partial); - const pluginContextValue = ({ + const pluginContextValue = { appMountParameters: { setHeaderActionMenu: () => {} }, core: { http: { @@ -35,7 +35,7 @@ export default { }, }, ObservabilityPageTemplate: EuiPageTemplate, - } as unknown) as PluginContextValue; + } as unknown as PluginContextValue; return ( diff --git a/x-pack/plugins/observability/public/pages/overview/mock/news_feed.mock.ts b/x-pack/plugins/observability/public/pages/overview/mock/news_feed.mock.ts index 8b6758747f3fc..0b25b3134392c 100644 --- a/x-pack/plugins/observability/public/pages/overview/mock/news_feed.mock.ts +++ b/x-pack/plugins/observability/public/pages/overview/mock/news_feed.mock.ts @@ -13,13 +13,11 @@ export const newsFeedFetchData = async () => { en: 'Elastic introduces OpenTelemetry integration', }, description: { - en: - 'We are pleased to announce the availability of the Elastic OpenTelemetry integration — available on Elastic Cloud, or when you download Elastic APM.', + en: 'We are pleased to announce the availability of the Elastic OpenTelemetry integration — available on Elastic Cloud, or when you download Elastic APM.', }, link_text: null, link_url: { - en: - 'https://www.elastic.co/blog/elastic-apm-opentelemetry-integration?blade=observabilitysolutionfeed', + en: 'https://www.elastic.co/blog/elastic-apm-opentelemetry-integration?blade=observabilitysolutionfeed', }, languages: null, badge: null, @@ -33,13 +31,11 @@ export const newsFeedFetchData = async () => { en: 'Kubernetes observability tutorial: Log monitoring and analysis', }, description: { - en: - 'Learn how Elastic Observability makes it easy to monitor and detect anomalies in millions of logs from thousands of containers running hundreds of microservices — while Kubernetes scales applications with changing pod counts. All from a single UI.', + en: 'Learn how Elastic Observability makes it easy to monitor and detect anomalies in millions of logs from thousands of containers running hundreds of microservices — while Kubernetes scales applications with changing pod counts. All from a single UI.', }, link_text: null, link_url: { - en: - 'https://www.elastic.co/blog/kubernetes-observability-tutorial-k8s-log-monitoring-and-analysis-elastic-stack?blade=observabilitysolutionfeed', + en: 'https://www.elastic.co/blog/kubernetes-observability-tutorial-k8s-log-monitoring-and-analysis-elastic-stack?blade=observabilitysolutionfeed', }, languages: null, badge: null, @@ -53,13 +49,11 @@ export const newsFeedFetchData = async () => { en: 'Kubernetes observability tutorial: K8s cluster setup and demo app deployment', }, description: { - en: - 'This blog will walk you through configuring the environment you will be using for the Kubernetes observability tutorial blog series. We will be deploying Elasticsearch Service, a Minikube single-node Kubernetes cluster setup, and a demo app.', + en: 'This blog will walk you through configuring the environment you will be using for the Kubernetes observability tutorial blog series. We will be deploying Elasticsearch Service, a Minikube single-node Kubernetes cluster setup, and a demo app.', }, link_text: null, link_url: { - en: - 'https://www.elastic.co/blog/kubernetes-observability-tutorial-k8s-cluster-setup-demo-app-deployment?blade=observabilitysolutionfeed', + en: 'https://www.elastic.co/blog/kubernetes-observability-tutorial-k8s-cluster-setup-demo-app-deployment?blade=observabilitysolutionfeed', }, languages: null, badge: null, 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 829e1a0cc6970..3c389a0ee20b2 100644 --- a/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx +++ b/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx @@ -47,32 +47,32 @@ const withCore = makeDecorator({ wrapper: (storyFn, context, { options }) => { unregisterAll(); - const KibanaReactContext = createKibanaReactContext(({ + const KibanaReactContext = createKibanaReactContext({ application: { getUrlForApp: () => '' }, chrome: { docTitle: { change: () => {} } }, uiSettings: { get: () => [] }, usageCollection: { reportUiCounter: () => {} }, - } as unknown) as Partial); + } as unknown as Partial); return ( {}, - } as unknown) as AppMountParameters, + } as unknown as AppMountParameters, config: { unsafe: { alertingExperience: { enabled: true }, cases: { enabled: true } }, }, core: options as CoreStart, - plugins: ({ + plugins: { data: { query: { timefilter: { timefilter: { setTime: () => {}, getTime: () => ({}) } }, }, }, - } as unknown) as ObservabilityPublicPluginsStart, + } as unknown as ObservabilityPublicPluginsStart, observabilityRuleTypeRegistry: createObservabilityRuleTypeRegistryMock(), ObservabilityPageTemplate: KibanaPageTemplate, }} @@ -85,7 +85,7 @@ const withCore = makeDecorator({ }, }); -const core = ({ +const core = { http: { basePath: { prepend: (link: string) => `http://localhost:5601${link}`, @@ -160,25 +160,25 @@ const core = ({ return euiSettings[key]; }, }, -} as unknown) as CoreStart; +} as unknown as CoreStart; -const coreWithAlerts = ({ +const coreWithAlerts = { ...core, http: { ...core.http, get: alertsFetchData, }, -} as unknown) as CoreStart; +} as unknown as CoreStart; -const coreWithNewsFeed = ({ +const coreWithNewsFeed = { ...core, http: { ...core.http, get: newsFeedFetchData, }, -} as unknown) as CoreStart; +} as unknown as CoreStart; -const coreAlertsThrowsError = ({ +const coreAlertsThrowsError = { ...core, http: { ...core.http, @@ -186,7 +186,7 @@ const coreAlertsThrowsError = ({ throw new Error('Error fetching Alerts data'); }, }, -} as unknown) as CoreStart; +} as unknown as CoreStart; storiesOf('app/Overview', module) .addDecorator(withCore(core)) diff --git a/x-pack/plugins/observability/public/plugin.ts b/x-pack/plugins/observability/public/plugin.ts index 7d11050f14d15..118f0783f9688 100644 --- a/x-pack/plugins/observability/public/plugin.ts +++ b/x-pack/plugins/observability/public/plugin.ts @@ -69,7 +69,8 @@ export class Plugin ObservabilityPublicStart, ObservabilityPublicPluginsSetup, ObservabilityPublicPluginsStart - > { + > +{ private readonly appUpdater$ = new BehaviorSubject(() => ({})); private readonly navigationRegistry = createNavigationRegistry(); diff --git a/x-pack/plugins/observability/public/services/get_news_feed.test.ts b/x-pack/plugins/observability/public/services/get_news_feed.test.ts index bcb53ad5bede4..336772e871fcc 100644 --- a/x-pack/plugins/observability/public/services/get_news_feed.test.ts +++ b/x-pack/plugins/observability/public/services/get_news_feed.test.ts @@ -12,26 +12,26 @@ describe('getNewsFeed', () => { const originalConsole = global.console; beforeAll(() => { // mocks console to avoid poluting the test output - global.console = ({ error: jest.fn() } as unknown) as typeof console; + global.console = { error: jest.fn() } as unknown as typeof console; }); afterAll(() => { global.console = originalConsole; }); it('Returns empty array when api throws exception', async () => { - const core = ({ + const core = { http: { get: async () => { throw new Error('Boom'); }, }, - } as unknown) as CoreStart; + } as unknown as CoreStart; const newsFeed = await getNewsFeed({ core }); expect(newsFeed.items).toEqual([]); }); it('Returns array with the news feed', async () => { - const core = ({ + const core = { http: { get: async () => { return { @@ -41,13 +41,11 @@ describe('getNewsFeed', () => { en: 'Elastic introduces OpenTelemetry integration', }, description: { - en: - 'We are pleased to announce the availability of the Elastic OpenTelemetry integration — available on Elastic Cloud, or when you download Elastic APM.', + en: 'We are pleased to announce the availability of the Elastic OpenTelemetry integration — available on Elastic Cloud, or when you download Elastic APM.', }, link_text: null, link_url: { - en: - 'https://www.elastic.co/blog/elastic-apm-opentelemetry-integration?blade=observabilitysolutionfeed', + en: 'https://www.elastic.co/blog/elastic-apm-opentelemetry-integration?blade=observabilitysolutionfeed', }, languages: null, badge: null, @@ -61,13 +59,11 @@ describe('getNewsFeed', () => { en: 'Kubernetes observability tutorial: Log monitoring and analysis', }, description: { - en: - 'Learn how Elastic Observability makes it easy to monitor and detect anomalies in millions of logs from thousands of containers running hundreds of microservices — while Kubernetes scales applications with changing pod counts. All from a single UI.', + en: 'Learn how Elastic Observability makes it easy to monitor and detect anomalies in millions of logs from thousands of containers running hundreds of microservices — while Kubernetes scales applications with changing pod counts. All from a single UI.', }, link_text: null, link_url: { - en: - 'https://www.elastic.co/blog/kubernetes-observability-tutorial-k8s-log-monitoring-and-analysis-elastic-stack?blade=observabilitysolutionfeed', + en: 'https://www.elastic.co/blog/kubernetes-observability-tutorial-k8s-log-monitoring-and-analysis-elastic-stack?blade=observabilitysolutionfeed', }, languages: null, badge: null, @@ -78,17 +74,14 @@ describe('getNewsFeed', () => { }, { title: { - en: - 'Kubernetes observability tutorial: K8s cluster setup and demo app deployment', + en: 'Kubernetes observability tutorial: K8s cluster setup and demo app deployment', }, description: { - en: - 'This blog will walk you through configuring the environment you will be using for the Kubernetes observability tutorial blog series. We will be deploying Elasticsearch Service, a Minikube single-node Kubernetes cluster setup, and a demo app.', + en: 'This blog will walk you through configuring the environment you will be using for the Kubernetes observability tutorial blog series. We will be deploying Elasticsearch Service, a Minikube single-node Kubernetes cluster setup, and a demo app.', }, link_text: null, link_url: { - en: - 'https://www.elastic.co/blog/kubernetes-observability-tutorial-k8s-cluster-setup-demo-app-deployment?blade=observabilitysolutionfeed', + en: 'https://www.elastic.co/blog/kubernetes-observability-tutorial-k8s-cluster-setup-demo-app-deployment?blade=observabilitysolutionfeed', }, languages: null, badge: null, @@ -101,7 +94,7 @@ describe('getNewsFeed', () => { }; }, }, - } as unknown) as CoreStart; + } as unknown as CoreStart; const newsFeed = await getNewsFeed({ core }); expect(newsFeed.items.length).toEqual(3); diff --git a/x-pack/plugins/observability/public/services/get_observability_alerts.test.ts b/x-pack/plugins/observability/public/services/get_observability_alerts.test.ts index b46e8c0cbe533..dc0fbd474131d 100644 --- a/x-pack/plugins/observability/public/services/get_observability_alerts.test.ts +++ b/x-pack/plugins/observability/public/services/get_observability_alerts.test.ts @@ -14,27 +14,27 @@ describe('getObservabilityAlerts', () => { const originalConsole = global.console; beforeAll(() => { // mocks console to avoid poluting the test output - global.console = ({ error: jest.fn() } as unknown) as typeof console; + global.console = { error: jest.fn() } as unknown as typeof console; }); afterAll(() => { global.console = originalConsole; }); it('Returns empty array when api throws exception', async () => { - const core = ({ + const core = { http: { get: async () => { throw new Error('Boom'); }, basePath, }, - } as unknown) as CoreStart; + } as unknown as CoreStart; expect(getObservabilityAlerts({ core })).rejects.toThrow('Boom'); }); it('Returns empty array when api return undefined', async () => { - const core = ({ + const core = { http: { get: async () => { return { @@ -43,14 +43,14 @@ describe('getObservabilityAlerts', () => { }, basePath, }, - } as unknown) as CoreStart; + } as unknown as CoreStart; const alerts = await getObservabilityAlerts({ core }); expect(alerts).toEqual([]); }); it('Returns empty array when alerts are not allowed based on consumer type', async () => { - const core = ({ + const core = { http: { get: async () => { return { @@ -65,13 +65,13 @@ describe('getObservabilityAlerts', () => { }, basePath, }, - } as unknown) as CoreStart; + } as unknown as CoreStart; const alerts = await getObservabilityAlerts({ core }); expect(alerts).toEqual([]); }); it('Shows alerts from Observability and Alerts', async () => { - const core = ({ + const core = { http: { get: async () => { return { @@ -87,7 +87,7 @@ describe('getObservabilityAlerts', () => { }, basePath, }, - } as unknown) as CoreStart; + } as unknown as CoreStart; const alerts = await getObservabilityAlerts({ core }); expect(alerts).toEqual([ diff --git a/x-pack/plugins/observability/public/update_global_navigation.test.tsx b/x-pack/plugins/observability/public/update_global_navigation.test.tsx index a24f8d0bbeb7e..f575771e1bafe 100644 --- a/x-pack/plugins/observability/public/update_global_navigation.test.tsx +++ b/x-pack/plugins/observability/public/update_global_navigation.test.tsx @@ -18,22 +18,22 @@ import { casesFeatureId } from '../common'; import { updateGlobalNavigation } from './update_global_navigation'; // Used in updater callback -const app = ({} as unknown) as App; +const app = {} as unknown as App; describe('updateGlobalNavigation', () => { describe('when no observability apps are enabled', () => { it('hides the overview link', () => { - const capabilities = ({ + const capabilities = { navLinks: { apm: false, logs: false, metrics: false, uptime: false }, - } as unknown) as ApplicationStart['capabilities']; + } as unknown as ApplicationStart['capabilities']; const config = { unsafe: { alertingExperience: { enabled: true }, cases: { enabled: true } }, } as ConfigSchema; const deepLinks: AppDeepLink[] = []; const callback = jest.fn(); - const updater$ = ({ + const updater$ = { next: (cb: AppUpdater) => callback(cb(app)), - } as unknown) as Subject; + } as unknown as Subject; updateGlobalNavigation({ capabilities, config, deepLinks, updater$ }); @@ -46,17 +46,17 @@ describe('updateGlobalNavigation', () => { describe('when one observability app is enabled', () => { it('shows the overview link', () => { - const capabilities = ({ + const capabilities = { navLinks: { apm: true, logs: false, metrics: false, uptime: false }, - } as unknown) as ApplicationStart['capabilities']; + } as unknown as ApplicationStart['capabilities']; const config = { unsafe: { alertingExperience: { enabled: true }, cases: { enabled: true } }, } as ConfigSchema; const deepLinks: AppDeepLink[] = []; const callback = jest.fn(); - const updater$ = ({ + const updater$ = { next: (cb: AppUpdater) => callback(cb(app)), - } as unknown) as Subject; + } as unknown as Subject; updateGlobalNavigation({ capabilities, config, deepLinks, updater$ }); @@ -68,10 +68,10 @@ describe('updateGlobalNavigation', () => { describe('when cases are enabled', () => { it('shows the cases deep link', () => { - const capabilities = ({ + const capabilities = { [casesFeatureId]: { read_cases: true }, navLinks: { apm: true, logs: false, metrics: false, uptime: false }, - } as unknown) as ApplicationStart['capabilities']; + } as unknown as ApplicationStart['capabilities']; const config = { unsafe: { alertingExperience: { enabled: true }, cases: { enabled: true } }, } as ConfigSchema; @@ -85,9 +85,9 @@ describe('updateGlobalNavigation', () => { }, ]; const callback = jest.fn(); - const updater$ = ({ + const updater$ = { next: (cb: AppUpdater) => callback(cb(app)), - } as unknown) as Subject; + } as unknown as Subject; updateGlobalNavigation({ capabilities, config, deepLinks, updater$ }); @@ -108,10 +108,10 @@ describe('updateGlobalNavigation', () => { describe('when cases are disabled', () => { it('hides the cases deep link', () => { - const capabilities = ({ + const capabilities = { [casesFeatureId]: { read_cases: true }, navLinks: { apm: true, logs: false, metrics: false, uptime: false }, - } as unknown) as ApplicationStart['capabilities']; + } as unknown as ApplicationStart['capabilities']; const config = { unsafe: { alertingExperience: { enabled: true }, cases: { enabled: false } }, } as ConfigSchema; @@ -125,9 +125,9 @@ describe('updateGlobalNavigation', () => { }, ]; const callback = jest.fn(); - const updater$ = ({ + const updater$ = { next: (cb: AppUpdater) => callback(cb(app)), - } as unknown) as Subject; + } as unknown as Subject; updateGlobalNavigation({ capabilities, config, deepLinks, updater$ }); @@ -148,10 +148,10 @@ describe('updateGlobalNavigation', () => { describe('with no case read capabilities', () => { it('hides the cases deep link', () => { - const capabilities = ({ + const capabilities = { [casesFeatureId]: { read_cases: false }, navLinks: { apm: true, logs: false, metrics: false, uptime: false }, - } as unknown) as ApplicationStart['capabilities']; + } as unknown as ApplicationStart['capabilities']; const config = { unsafe: { alertingExperience: { enabled: true }, cases: { enabled: true } }, } as ConfigSchema; @@ -165,9 +165,9 @@ describe('updateGlobalNavigation', () => { }, ]; const callback = jest.fn(); - const updater$ = ({ + const updater$ = { next: (cb: AppUpdater) => callback(cb(app)), - } as unknown) as Subject; + } as unknown as Subject; updateGlobalNavigation({ capabilities, config, deepLinks, updater$ }); @@ -188,10 +188,10 @@ describe('updateGlobalNavigation', () => { describe('when alerts are enabled', () => { it('shows the alerts deep link', () => { - const capabilities = ({ + const capabilities = { [casesFeatureId]: { read_cases: true }, navLinks: { apm: true, logs: false, metrics: false, uptime: false }, - } as unknown) as ApplicationStart['capabilities']; + } as unknown as ApplicationStart['capabilities']; const config = { unsafe: { alertingExperience: { enabled: true }, cases: { enabled: true } }, } as ConfigSchema; @@ -205,9 +205,9 @@ describe('updateGlobalNavigation', () => { }, ]; const callback = jest.fn(); - const updater$ = ({ + const updater$ = { next: (cb: AppUpdater) => callback(cb(app)), - } as unknown) as Subject; + } as unknown as Subject; updateGlobalNavigation({ capabilities, config, deepLinks, updater$ }); @@ -228,10 +228,10 @@ describe('updateGlobalNavigation', () => { describe('when alerts are disabled', () => { it('hides the alerts deep link', () => { - const capabilities = ({ + const capabilities = { [casesFeatureId]: { read_cases: true }, navLinks: { apm: true, logs: false, metrics: false, uptime: false }, - } as unknown) as ApplicationStart['capabilities']; + } as unknown as ApplicationStart['capabilities']; const config = { unsafe: { alertingExperience: { enabled: false }, cases: { enabled: false } }, } as ConfigSchema; @@ -245,9 +245,9 @@ describe('updateGlobalNavigation', () => { }, ]; const callback = jest.fn(); - const updater$ = ({ + const updater$ = { next: (cb: AppUpdater) => callback(cb(app)), - } as unknown) as Subject; + } as unknown as Subject; updateGlobalNavigation({ capabilities, config, deepLinks, updater$ }); diff --git a/x-pack/plugins/observability/public/utils/test_helper.tsx b/x-pack/plugins/observability/public/utils/test_helper.tsx index ce71a5640515b..eb7efbd90c6ba 100644 --- a/x-pack/plugins/observability/public/utils/test_helper.tsx +++ b/x-pack/plugins/observability/public/utils/test_helper.tsx @@ -20,9 +20,9 @@ import { ObservabilityPublicPluginsStart } from '../plugin'; import { EuiThemeProvider } from '../../../../../src/plugins/kibana_react/common'; import { createObservabilityRuleTypeRegistryMock } from '../rules/observability_rule_type_registry_mock'; -const appMountParameters = ({ setHeaderActionMenu: () => {} } as unknown) as AppMountParameters; +const appMountParameters = { setHeaderActionMenu: () => {} } as unknown as AppMountParameters; -export const core = ({ +export const core = { http: { basePath: { prepend: jest.fn(), @@ -32,13 +32,13 @@ export const core = ({ get: (key: string) => true, get$: (key: string) => of(true), }, -} as unknown) as CoreStart; +} as unknown as CoreStart; const config = { unsafe: { alertingExperience: { enabled: true }, cases: { enabled: true } } }; -const plugins = ({ +const plugins = { data: { query: { timefilter: { timefilter: { setTime: jest.fn() } } } }, -} as unknown) as ObservabilityPublicPluginsStart; +} as unknown as ObservabilityPublicPluginsStart; const observabilityRuleTypeRegistry = createObservabilityRuleTypeRegistryMock(); diff --git a/x-pack/plugins/observability/server/index.ts b/x-pack/plugins/observability/server/index.ts index 9a62602859c54..97a17b0d11153 100644 --- a/x-pack/plugins/observability/server/index.ts +++ b/x-pack/plugins/observability/server/index.ts @@ -9,7 +9,7 @@ /* eslint-disable @kbn/eslint/no_export_all */ import { schema, TypeOf } from '@kbn/config-schema'; -import { PluginInitializerContext } from 'src/core/server'; +import { PluginConfigDescriptor, PluginInitializerContext } from 'src/core/server'; import { ObservabilityPlugin, ObservabilityPluginSetup } from './plugin'; import { createOrUpdateIndex, Mappings } from './utils/create_or_update_index'; import { ScopedAnnotationsClient } from './lib/annotations/bootstrap_annotations'; @@ -18,9 +18,9 @@ export { rangeQuery, kqlQuery } from './utils/queries'; export * from './types'; -export const config = { +export const config: PluginConfigDescriptor = { exposeToBrowser: { - unsafe: { alertingExperience: { enabled: true }, cases: { enabled: true } }, + unsafe: true, }, schema: schema.object({ enabled: schema.boolean({ defaultValue: true }), @@ -33,6 +33,7 @@ export const config = { cases: schema.object({ enabled: schema.boolean({ defaultValue: false }) }), }), }), + deprecations: ({ deprecate }) => [deprecate('enabled', '8.0.0')], }; export type ObservabilityConfig = TypeOf; diff --git a/x-pack/plugins/osquery/common/format_errors.test.ts b/x-pack/plugins/osquery/common/format_errors.test.ts index 149bae85fec8a..9d920de4653c4 100644 --- a/x-pack/plugins/osquery/common/format_errors.test.ts +++ b/x-pack/plugins/osquery/common/format_errors.test.ts @@ -59,7 +59,7 @@ describe('utils', () => { }); test('will use message before context if it is set', () => { - const context: t.Context = ([{ key: 'some string key' }] as unknown) as t.Context; + const context: t.Context = [{ key: 'some string key' }] as unknown as t.Context; const validationError1: t.ValidationError = { value: 'Some existing error 1', context, @@ -71,7 +71,7 @@ describe('utils', () => { }); test('will use context entry of a single string', () => { - const context: t.Context = ([{ key: 'some string key' }] as unknown) as t.Context; + const context: t.Context = [{ key: 'some string key' }] as unknown as t.Context; const validationError1: t.ValidationError = { value: 'Some existing error 1', context, @@ -82,10 +82,10 @@ describe('utils', () => { }); test('will use two context entries of two strings', () => { - const context: t.Context = ([ + const context: t.Context = [ { key: 'some string key 1' }, { key: 'some string key 2' }, - ] as unknown) as t.Context; + ] as unknown as t.Context; const validationError1: t.ValidationError = { value: 'Some existing error 1', context, @@ -98,10 +98,7 @@ describe('utils', () => { }); test('will filter out and not use any strings of numbers', () => { - const context: t.Context = ([ - { key: '5' }, - { key: 'some string key 2' }, - ] as unknown) as t.Context; + const context: t.Context = [{ key: '5' }, { key: 'some string key 2' }] as unknown as t.Context; const validationError1: t.ValidationError = { value: 'Some existing error 1', context, @@ -114,10 +111,10 @@ describe('utils', () => { }); test('will filter out and not use null', () => { - const context: t.Context = ([ + const context: t.Context = [ { key: null }, { key: 'some string key 2' }, - ] as unknown) as t.Context; + ] as unknown as t.Context; const validationError1: t.ValidationError = { value: 'Some existing error 1', context, @@ -130,10 +127,7 @@ describe('utils', () => { }); test('will filter out and not use empty strings', () => { - const context: t.Context = ([ - { key: '' }, - { key: 'some string key 2' }, - ] as unknown) as t.Context; + const context: t.Context = [{ key: '' }, { key: 'some string key 2' }] as unknown as t.Context; const validationError1: t.ValidationError = { value: 'Some existing error 1', context, @@ -146,10 +140,10 @@ describe('utils', () => { }); test('will use a name context if it cannot find a keyContext', () => { - const context: t.Context = ([ + const context: t.Context = [ { key: '' }, { key: '', type: { name: 'someName' } }, - ] as unknown) as t.Context; + ] as unknown as t.Context; const validationError1: t.ValidationError = { value: 'Some existing error 1', context, @@ -160,7 +154,7 @@ describe('utils', () => { }); test('will return an empty string if name does not exist but type does', () => { - const context: t.Context = ([{ key: '' }, { key: '', type: {} }] as unknown) as t.Context; + const context: t.Context = [{ key: '' }, { key: '', type: {} }] as unknown as t.Context; const validationError1: t.ValidationError = { value: 'Some existing error 1', context, @@ -171,10 +165,7 @@ describe('utils', () => { }); test('will stringify an error value', () => { - const context: t.Context = ([ - { key: '' }, - { key: 'some string key 2' }, - ] as unknown) as t.Context; + const context: t.Context = [{ key: '' }, { key: 'some string key 2' }] as unknown as t.Context; const validationError1: t.ValidationError = { value: { foo: 'some error' }, context, diff --git a/x-pack/plugins/osquery/common/utility_types.ts b/x-pack/plugins/osquery/common/utility_types.ts index 3c13e6af837bc..a7cea8d8cdd6f 100644 --- a/x-pack/plugins/osquery/common/utility_types.ts +++ b/x-pack/plugins/osquery/common/utility_types.ts @@ -25,7 +25,7 @@ export const stringEnum = (enumObj: T, enumName = 'enum') => Object.values(enumObj).includes(u) ? runtimeTypes.success(u as T[keyof T]) : runtimeTypes.failure(u, c), - (a) => (a as unknown) as string + (a) => a as unknown as string ); /** 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 index 2de5ab11664ae..ecc0f361b76a0 100644 --- a/x-pack/plugins/osquery/public/action_results/action_agents_status.tsx +++ b/x-pack/plugins/osquery/public/action_results/action_agents_status.tsx @@ -26,9 +26,10 @@ const ActionAgentsStatusComponent: React.FC = ({ agentIds, }) => { const [isLive, setIsLive] = useState(true); - const expired = useMemo(() => (!expirationDate ? false : new Date(expirationDate) < new Date()), [ - expirationDate, - ]); + const expired = useMemo( + () => (!expirationDate ? false : new Date(expirationDate) < new Date()), + [expirationDate] + ); const { // @ts-expect-error update types data: { aggregations }, 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 083d0193be2a2..9da9ac72f273a 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 @@ -38,9 +38,10 @@ const ActionResultsSummaryComponent: React.FC = ({ const [pageIndex, setPageIndex] = useState(0); // @ts-expect-error update types const [pageSize, setPageSize] = useState(50); - const expired = useMemo(() => (!expirationDate ? false : new Date(expirationDate) < new Date()), [ - expirationDate, - ]); + const expired = useMemo( + () => (!expirationDate ? false : new Date(expirationDate) < new Date()), + [expirationDate] + ); const [isLive, setIsLive] = useState(true); const { data: hasActionResultsPrivileges } = useActionResultsPrivileges(); const { diff --git a/x-pack/plugins/osquery/public/agents/agents_table.tsx b/x-pack/plugins/osquery/public/agents/agents_table.tsx index 8a40cb171070d..f9363a4d0f120 100644 --- a/x-pack/plugins/osquery/public/agents/agents_table.tsx +++ b/x-pack/plugins/osquery/public/agents/agents_table.tsx @@ -61,9 +61,11 @@ const AgentsTableComponent: React.FC = ({ agentSelection, onCh // grouping related const osqueryPolicyData = useOsqueryPolicies(); - const { loading: groupsLoading, totalCount: totalNumAgents, groups } = useAgentGroups( - osqueryPolicyData - ); + const { + loading: groupsLoading, + totalCount: totalNumAgents, + groups, + } = useAgentGroups(osqueryPolicyData); const grouper = useMemo(() => new AgentGrouper(), []); const { isLoading: agentsLoading, data: agents } = useAllAgents( osqueryPolicyData, diff --git a/x-pack/plugins/osquery/public/agents/use_osquery_policies.ts b/x-pack/plugins/osquery/public/agents/use_osquery_policies.ts index 4b9ff931f3a91..cf777092da5cf 100644 --- a/x-pack/plugins/osquery/public/agents/use_osquery_policies.ts +++ b/x-pack/plugins/osquery/public/agents/use_osquery_policies.ts @@ -31,8 +31,8 @@ export const useOsqueryPolicies = () => { }), } ); - return useMemo(() => ({ osqueryPoliciesLoading, osqueryPolicies }), [ - osqueryPoliciesLoading, - osqueryPolicies, - ]); + return useMemo( + () => ({ osqueryPoliciesLoading, osqueryPolicies }), + [osqueryPoliciesLoading, osqueryPolicies] + ); }; diff --git a/x-pack/plugins/osquery/public/common/page_paths.ts b/x-pack/plugins/osquery/public/common/page_paths.ts index 8df1006da181a..b1971f9d6ee41 100644 --- a/x-pack/plugins/osquery/public/common/page_paths.ts +++ b/x-pack/plugins/osquery/public/common/page_paths.ts @@ -42,10 +42,9 @@ export const PAGE_ROUTING_PATHS = { export const pagePathGetters: { [key in StaticPage]: () => string; -} & - { - [key in DynamicPage]: (values: DynamicPagePathValues) => string; - } = { +} & { + [key in DynamicPage]: (values: DynamicPagePathValues) => string; +} = { base: () => '/', overview: () => '/', live_queries: () => '/live_queries', diff --git a/x-pack/plugins/osquery/public/editor/osquery_highlight_rules.ts b/x-pack/plugins/osquery/public/editor/osquery_highlight_rules.ts index 49571f5662fa1..eda9401efd3a2 100644 --- a/x-pack/plugins/osquery/public/editor/osquery_highlight_rules.ts +++ b/x-pack/plugins/osquery/public/editor/osquery_highlight_rules.ts @@ -105,7 +105,7 @@ const dataTypes = [ ].join('|'); // This is gross, but the types exported by brace are lagging and incorrect: https://github.com/thlorenz/brace/issues/182 -((ace as unknown) as AceInterface).define( +(ace as unknown as AceInterface).define( 'ace/mode/osquery_highlight_rules', ['require', 'exports', 'ace/mode/sql_highlight_rules'], function (acequire, exports) { diff --git a/x-pack/plugins/osquery/public/editor/osquery_mode.ts b/x-pack/plugins/osquery/public/editor/osquery_mode.ts index 6f526280ea879..d417d6b5137bf 100644 --- a/x-pack/plugins/osquery/public/editor/osquery_mode.ts +++ b/x-pack/plugins/osquery/public/editor/osquery_mode.ts @@ -11,7 +11,7 @@ import 'brace/ext/language_tools'; import { AceInterface } from './ace_types'; import './osquery_highlight_rules'; -((ace as unknown) as AceInterface).define( +(ace as unknown as AceInterface).define( 'ace/mode/osquery', ['require', 'exports', 'ace/mode/sql', 'ace/mode/osquery_highlight_rules'], function (acequire, exports) { diff --git a/x-pack/plugins/osquery/public/fleet_integration/lazy_osquery_managed_policy_create_import_extension.tsx b/x-pack/plugins/osquery/public/fleet_integration/lazy_osquery_managed_policy_create_import_extension.tsx index 95220e8251707..a32f0b862250e 100644 --- a/x-pack/plugins/osquery/public/fleet_integration/lazy_osquery_managed_policy_create_import_extension.tsx +++ b/x-pack/plugins/osquery/public/fleet_integration/lazy_osquery_managed_policy_create_import_extension.tsx @@ -8,13 +8,12 @@ import { lazy } from 'react'; import { PackagePolicyCreateExtensionComponent } from '../../../fleet/public'; -export const LazyOsqueryManagedPolicyCreateImportExtension = lazy( - async () => { +export const LazyOsqueryManagedPolicyCreateImportExtension = + lazy(async () => { const { OsqueryManagedPolicyCreateImportExtension } = await import( './osquery_managed_policy_create_import_extension' ); return { default: OsqueryManagedPolicyCreateImportExtension, }; - } -); + }); 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 69b02dee8b9f7..4e569d4cf58d8 100644 --- a/x-pack/plugins/osquery/public/live_queries/form/index.tsx +++ b/x-pack/plugins/osquery/public/live_queries/form/index.tsx @@ -143,9 +143,10 @@ const LiveQueryFormComponent: React.FC = ({ return 'incomplete'; }, [agentSelected, isError, isLoading, isSuccess, form]); - const resultsStatus = useMemo(() => (queryStatus === 'complete' ? 'incomplete' : 'disabled'), [ - queryStatus, - ]); + const resultsStatus = useMemo( + () => (queryStatus === 'complete' ? 'incomplete' : 'disabled'), + [queryStatus] + ); const queryComponentProps = useMemo( () => ({ diff --git a/x-pack/plugins/osquery/public/results/results_table.tsx b/x-pack/plugins/osquery/public/results/results_table.tsx index c0760b9399ba1..0e683acf660a2 100644 --- a/x-pack/plugins/osquery/public/results/results_table.tsx +++ b/x-pack/plugins/osquery/public/results/results_table.tsx @@ -113,34 +113,36 @@ const ResultsTableComponent: React.FC = ({ }); const [visibleColumns, setVisibleColumns] = useState([]); - const columnVisibility = useMemo(() => ({ visibleColumns, setVisibleColumns }), [ - visibleColumns, - setVisibleColumns, - ]); + const columnVisibility = useMemo( + () => ({ visibleColumns, setVisibleColumns }), + [visibleColumns, setVisibleColumns] + ); const renderCellValue: EuiDataGridProps['renderCellValue'] = useMemo( - () => ({ rowIndex, columnId }) => { - // eslint-disable-next-line react-hooks/rules-of-hooks - const data = useContext(DataContext); - - // @ts-expect-error update types - const value = data[rowIndex % pagination.pageSize]?.fields[columnId]; + () => + ({ rowIndex, columnId }) => { + // eslint-disable-next-line react-hooks/rules-of-hooks + const data = useContext(DataContext); - if (columnId === 'agent.name') { // @ts-expect-error update types - const agentIdValue = data[rowIndex % pagination.pageSize]?.fields['agent.id']; + const value = data[rowIndex % pagination.pageSize]?.fields[columnId]; - return {value}; - } + if (columnId === 'agent.name') { + // @ts-expect-error update types + const agentIdValue = data[rowIndex % pagination.pageSize]?.fields['agent.id']; - return !isEmpty(value) ? value : '-'; - }, + return {value}; + } + + return !isEmpty(value) ? value : '-'; + }, [getFleetAppUrl, pagination.pageSize] ); - const tableSorting = useMemo(() => ({ columns: sortingColumns, onSort: setSortingColumns }), [ - sortingColumns, - ]); + const tableSorting = useMemo( + () => ({ columns: sortingColumns, onSort: setSortingColumns }), + [sortingColumns] + ); const tablePagination = useMemo( () => ({ diff --git a/x-pack/plugins/osquery/public/saved_queries/form/use_saved_query_form.tsx b/x-pack/plugins/osquery/public/saved_queries/form/use_saved_query_form.tsx index 8cfceec643bac..eed16d84278b8 100644 --- a/x-pack/plugins/osquery/public/saved_queries/form/use_saved_query_form.tsx +++ b/x-pack/plugins/osquery/public/saved_queries/form/use_saved_query_form.tsx @@ -34,9 +34,10 @@ export const useSavedQueryForm = ({ defaultValue, handleSubmit }: UseSavedQueryF if (defaultValue && defaultValue.id) res.delete(defaultValue.id); return res; }, [ids, defaultValue]); - const formSchema = useMemo>(() => createFormSchema(idSet), [ - idSet, - ]); + const formSchema = useMemo>( + () => createFormSchema(idSet), + [idSet] + ); return useForm({ id: SAVED_QUERY_FORM_ID + uuid.v4(), schema: formSchema, diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/form/index.tsx b/x-pack/plugins/osquery/public/scheduled_query_groups/form/index.tsx index 3598a9fd2e44c..d7cee7e23bf62 100644 --- a/x-pack/plugins/osquery/public/scheduled_query_groups/form/index.tsx +++ b/x-pack/plugins/osquery/public/scheduled_query_groups/form/index.tsx @@ -280,9 +280,10 @@ const ScheduledQueryGroupFormComponent: React.FC = }; }, [agentPoliciesById, policyId]); - const handleNameChange = useCallback((newName: string) => setFieldValue('name', newName), [ - setFieldValue, - ]); + const handleNameChange = useCallback( + (newName: string) => setFieldValue('name', newName), + [setFieldValue] + ); const handleSaveClick = useCallback(() => { if (currentPolicy.agentCount) { diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/form/queries_field.tsx b/x-pack/plugins/osquery/public/scheduled_query_groups/form/queries_field.tsx index 15acfcf4fbdc7..d4e8592b65bcb 100644 --- a/x-pack/plugins/osquery/public/scheduled_query_groups/form/queries_field.tsx +++ b/x-pack/plugins/osquery/public/scheduled_query_groups/form/queries_field.tsx @@ -243,9 +243,10 @@ const QueriesFieldComponent: React.FC = ({ [handleNameChange, integrationPackageVersion, scheduledQueryGroupId, setValue] ); - const tableData = useMemo(() => (field.value.length ? field.value[0].streams : []), [ - field.value, - ]); + const tableData = useMemo( + () => (field.value.length ? field.value[0].streams : []), + [field.value] + ); const uniqueQueryIds = useMemo( () => diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/queries/ecs_mapping_editor_field.tsx b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/ecs_mapping_editor_field.tsx index 3b19176d0f581..9f203d7bf751f 100644 --- a/x-pack/plugins/osquery/public/scheduled_query_groups/queries/ecs_mapping_editor_field.tsx +++ b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/ecs_mapping_editor_field.tsx @@ -330,65 +330,64 @@ interface ECSMappingEditorFormProps { onDelete?: (key: string) => void; } -const getEcsFieldValidator = (editForm: boolean) => ( - args: ValidationFuncArg -) => { - const fieldRequiredError = fieldValidators.emptyField( - i18n.translate( - 'xpack.osquery.scheduledQueryGroup.queryFlyoutForm.ecsFieldRequiredErrorMessage', - { - defaultMessage: 'ECS field is required.', - } - ) - )(args); - - // @ts-expect-error update types - if (fieldRequiredError && ((!editForm && args.formData['value.field'].length) || editForm)) { - return fieldRequiredError; - } - - return undefined; -}; - -const getOsqueryResultFieldValidator = ( - osquerySchemaOptions: OsquerySchemaOption[], - editForm: boolean -) => ( - args: ValidationFuncArg -) => { - const fieldRequiredError = fieldValidators.emptyField( - i18n.translate( - 'xpack.osquery.scheduledQueryGroup.queryFlyoutForm.osqueryResultFieldRequiredErrorMessage', - { - defaultMessage: 'Osquery result is required.', - } - ) - )(args); - - if (fieldRequiredError && ((!editForm && args.formData.key.length) || editForm)) { - return fieldRequiredError; - } +const getEcsFieldValidator = + (editForm: boolean) => + (args: ValidationFuncArg) => { + const fieldRequiredError = fieldValidators.emptyField( + i18n.translate( + 'xpack.osquery.scheduledQueryGroup.queryFlyoutForm.ecsFieldRequiredErrorMessage', + { + defaultMessage: 'ECS field is required.', + } + ) + )(args); - if (!args.value.length) return; + // @ts-expect-error update types + if (fieldRequiredError && ((!editForm && args.formData['value.field'].length) || editForm)) { + return fieldRequiredError; + } - const osqueryColumnExists = find(osquerySchemaOptions, ['label', args.value]); + return undefined; + }; - return !osqueryColumnExists - ? { - code: 'ERR_FIELD_FORMAT', - path: args.path, - message: i18n.translate( - 'xpack.osquery.scheduledQueryGroup.queryFlyoutForm.osqueryResultFieldValueMissingErrorMessage', - { - defaultMessage: 'The current query does not return a {columnName} field', - values: { - columnName: args.value, - }, - } - ), - } - : undefined; -}; +const getOsqueryResultFieldValidator = + (osquerySchemaOptions: OsquerySchemaOption[], editForm: boolean) => + ( + args: ValidationFuncArg + ) => { + const fieldRequiredError = fieldValidators.emptyField( + i18n.translate( + 'xpack.osquery.scheduledQueryGroup.queryFlyoutForm.osqueryResultFieldRequiredErrorMessage', + { + defaultMessage: 'Osquery result is required.', + } + ) + )(args); + + if (fieldRequiredError && ((!editForm && args.formData.key.length) || editForm)) { + return fieldRequiredError; + } + + if (!args.value.length) return; + + const osqueryColumnExists = find(osquerySchemaOptions, ['label', args.value]); + + return !osqueryColumnExists + ? { + code: 'ERR_FIELD_FORMAT', + path: args.path, + message: i18n.translate( + 'xpack.osquery.scheduledQueryGroup.queryFlyoutForm.osqueryResultFieldValueMissingErrorMessage', + { + defaultMessage: 'The current query does not return a {columnName} field', + values: { + columnName: args.value, + }, + } + ), + } + : undefined; + }; const FORM_DEFAULT_VALUE = { key: '', 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 3eb299cf5fa15..f3bd150c8d3c3 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 @@ -61,7 +61,7 @@ export const createFormSchema = (ids: Set) => ({ version: { defaultValue: [], type: FIELD_TYPES.COMBO_BOX, - label: (( + label: ( ) => ({ /> - ) as unknown) as string, + ) as unknown as string, validations: [], }, }); diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/queries/use_scheduled_query_group_query_form.tsx b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/use_scheduled_query_group_query_form.tsx index dc206a6b104d3..5881612e18219 100644 --- a/x-pack/plugins/osquery/public/scheduled_query_groups/queries/use_scheduled_query_group_query_form.tsx +++ b/x-pack/plugins/osquery/public/scheduled_query_groups/queries/use_scheduled_query_group_query_form.tsx @@ -48,9 +48,10 @@ export const useScheduledQueryGroupQueryForm = ({ new Set(xor(uniqueQueryIds, defaultValue?.id.value ? [defaultValue.id.value] : [])), [uniqueQueryIds, defaultValue] ); - const formSchema = useMemo>(() => createFormSchema(idSet), [ - idSet, - ]); + const formSchema = useMemo>( + () => createFormSchema(idSet), + [idSet] + ); return useForm({ id: FORM_ID + uuid.v4(), diff --git a/x-pack/plugins/osquery/public/scheduled_query_groups/scheduled_query_group_queries_status_table.tsx b/x-pack/plugins/osquery/public/scheduled_query_groups/scheduled_query_group_queries_status_table.tsx index f94b4ba1f8592..15547b2a4cca9 100644 --- a/x-pack/plugins/osquery/public/scheduled_query_groups/scheduled_query_group_queries_status_table.tsx +++ b/x-pack/plugins/osquery/public/scheduled_query_groups/scheduled_query_group_queries_status_table.tsx @@ -408,11 +408,10 @@ const ScheduledQueryLastResults: React.FC = ({ logsIndexPattern, }); - const handleErrorsToggle = useCallback(() => toggleErrors({ queryId, interval }), [ - queryId, - interval, - toggleErrors, - ]); + const handleErrorsToggle = useCallback( + () => toggleErrors({ queryId, interval }), + [queryId, interval, toggleErrors] + ); useEffect(() => { const fetchLogsIndexPattern = async () => { @@ -529,171 +528,171 @@ interface ScheduledQueryGroupQueriesStatusTableProps { scheduledQueryGroupName: string; } -const ScheduledQueryGroupQueriesStatusTableComponent: React.FC = ({ - agentIds, - data, - scheduledQueryGroupName, -}) => { - const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState< - Record> - >({}); - - const renderQueryColumn = useCallback( - (query: string) => ( - - {query} - - ), - [] - ); +const ScheduledQueryGroupQueriesStatusTableComponent: React.FC = + ({ agentIds, data, scheduledQueryGroupName }) => { + const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState< + Record> + >({}); + + const renderQueryColumn = useCallback( + (query: string) => ( + + {query} + + ), + [] + ); - const toggleErrors = useCallback( - ({ queryId, interval }: { queryId: string; interval: number }) => { - const itemIdToExpandedRowMapValues = { ...itemIdToExpandedRowMap }; - if (itemIdToExpandedRowMapValues[queryId]) { - delete itemIdToExpandedRowMapValues[queryId]; - } else { - itemIdToExpandedRowMapValues[queryId] = ( - - ); - } - setItemIdToExpandedRowMap(itemIdToExpandedRowMapValues); - }, - [agentIds, itemIdToExpandedRowMap, scheduledQueryGroupName] - ); + const toggleErrors = useCallback( + ({ queryId, interval }: { queryId: string; interval: number }) => { + const itemIdToExpandedRowMapValues = { ...itemIdToExpandedRowMap }; + if (itemIdToExpandedRowMapValues[queryId]) { + delete itemIdToExpandedRowMapValues[queryId]; + } else { + itemIdToExpandedRowMapValues[queryId] = ( + + ); + } + setItemIdToExpandedRowMap(itemIdToExpandedRowMapValues); + }, + [agentIds, itemIdToExpandedRowMap, scheduledQueryGroupName] + ); - const renderLastResultsColumn = useCallback( - (item) => ( - - ), - [agentIds, itemIdToExpandedRowMap, scheduledQueryGroupName, toggleErrors] - ); + const renderLastResultsColumn = useCallback( + (item) => ( + + ), + [agentIds, itemIdToExpandedRowMap, scheduledQueryGroupName, toggleErrors] + ); - const renderDiscoverResultsAction = useCallback( - (item) => ( - - ), - [agentIds, scheduledQueryGroupName] - ); + const renderDiscoverResultsAction = useCallback( + (item) => ( + + ), + [agentIds, scheduledQueryGroupName] + ); - const renderLensResultsAction = useCallback( - (item) => ( - - ), - [agentIds, scheduledQueryGroupName] - ); + const renderLensResultsAction = useCallback( + (item) => ( + + ), + [agentIds, scheduledQueryGroupName] + ); - const getItemId = useCallback( - (item: OsqueryManagerPackagePolicyInputStream) => get('vars.id.value', item), - [] - ); + const getItemId = useCallback( + (item: OsqueryManagerPackagePolicyInputStream) => get('vars.id.value', item), + [] + ); - const columns = useMemo( - () => [ - { - field: 'vars.id.value', - name: i18n.translate('xpack.osquery.scheduledQueryGroup.queriesTable.idColumnTitle', { - defaultMessage: 'ID', - }), - width: '15%', - }, - { - field: 'vars.interval.value', - name: i18n.translate('xpack.osquery.scheduledQueryGroup.queriesTable.intervalColumnTitle', { - defaultMessage: 'Interval (s)', - }), - width: '80px', - }, - { - field: 'vars.query.value', - name: i18n.translate('xpack.osquery.scheduledQueryGroup.queriesTable.queryColumnTitle', { - defaultMessage: 'Query', - }), - render: renderQueryColumn, - width: '20%', - }, - { - name: i18n.translate( - 'xpack.osquery.scheduledQueryGroup.queriesTable.lastResultsColumnTitle', - { - defaultMessage: 'Last results', - } - ), - render: renderLastResultsColumn, - }, - { - name: i18n.translate( - 'xpack.osquery.scheduledQueryGroup.queriesTable.viewResultsColumnTitle', - { - defaultMessage: 'View results', - } - ), - width: '90px', - actions: [ - { - render: renderDiscoverResultsAction, - }, - { - render: renderLensResultsAction, - }, - ], - }, - ], - [ - renderQueryColumn, - renderLastResultsColumn, - renderDiscoverResultsAction, - renderLensResultsAction, - ] - ); + const columns = useMemo( + () => [ + { + field: 'vars.id.value', + name: i18n.translate('xpack.osquery.scheduledQueryGroup.queriesTable.idColumnTitle', { + defaultMessage: 'ID', + }), + width: '15%', + }, + { + field: 'vars.interval.value', + name: i18n.translate( + 'xpack.osquery.scheduledQueryGroup.queriesTable.intervalColumnTitle', + { + defaultMessage: 'Interval (s)', + } + ), + width: '80px', + }, + { + field: 'vars.query.value', + name: i18n.translate('xpack.osquery.scheduledQueryGroup.queriesTable.queryColumnTitle', { + defaultMessage: 'Query', + }), + render: renderQueryColumn, + width: '20%', + }, + { + name: i18n.translate( + 'xpack.osquery.scheduledQueryGroup.queriesTable.lastResultsColumnTitle', + { + defaultMessage: 'Last results', + } + ), + render: renderLastResultsColumn, + }, + { + name: i18n.translate( + 'xpack.osquery.scheduledQueryGroup.queriesTable.viewResultsColumnTitle', + { + defaultMessage: 'View results', + } + ), + width: '90px', + actions: [ + { + render: renderDiscoverResultsAction, + }, + { + render: renderLensResultsAction, + }, + ], + }, + ], + [ + renderQueryColumn, + renderLastResultsColumn, + renderDiscoverResultsAction, + renderLensResultsAction, + ] + ); - const sorting = useMemo( - () => ({ - sort: { - field: 'vars.id.value' as keyof OsqueryManagerPackagePolicyInputStream, - direction: 'asc' as const, - }, - }), - [] - ); + const sorting = useMemo( + () => ({ + sort: { + field: 'vars.id.value' as keyof OsqueryManagerPackagePolicyInputStream, + direction: 'asc' as const, + }, + }), + [] + ); - return ( - - items={data} - itemId={getItemId} - columns={columns} - sorting={sorting} - itemIdToExpandedRowMap={itemIdToExpandedRowMap} - isExpandable - /> - ); -}; + return ( + + items={data} + itemId={getItemId} + columns={columns} + sorting={sorting} + itemIdToExpandedRowMap={itemIdToExpandedRowMap} + isExpandable + /> + ); + }; export const ScheduledQueryGroupQueriesStatusTable = React.memo( ScheduledQueryGroupQueriesStatusTableComponent diff --git a/x-pack/plugins/osquery/server/index.ts b/x-pack/plugins/osquery/server/index.ts index 30bc5ed5bd835..385515c285093 100644 --- a/x-pack/plugins/osquery/server/index.ts +++ b/x-pack/plugins/osquery/server/index.ts @@ -5,11 +5,12 @@ * 2.0. */ -import { PluginInitializerContext } from '../../../../src/core/server'; +import { PluginConfigDescriptor, PluginInitializerContext } from '../../../../src/core/server'; import { OsqueryPlugin } from './plugin'; -import { ConfigSchema } from './config'; +import { ConfigSchema, ConfigType } from './config'; -export const config = { +export const config: PluginConfigDescriptor = { + deprecations: ({ deprecate }) => [deprecate('enabled', '8.0.0')], schema: ConfigSchema, exposeToBrowser: { enabled: true, diff --git a/x-pack/plugins/osquery/server/routes/pack/create_pack_route.ts b/x-pack/plugins/osquery/server/routes/pack/create_pack_route.ts index d067ffb0f4c7c..3707c3d3e91ec 100644 --- a/x-pack/plugins/osquery/server/routes/pack/create_pack_route.ts +++ b/x-pack/plugins/osquery/server/routes/pack/create_pack_route.ts @@ -31,8 +31,12 @@ export const createPackRoute = (router: IRouter) => { name: savedQuery.name, })); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { attributes, references: _, ...restSO } = await savedObjectsClient.create( + const { + attributes, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + references: _, + ...restSO + } = await savedObjectsClient.create( packSavedObjectType, { name, diff --git a/x-pack/plugins/osquery/server/routes/privileges_check/privileges_check_route.ts b/x-pack/plugins/osquery/server/routes/privileges_check/privileges_check_route.ts index d9683d23deb13..bbfdfc0ab044a 100644 --- a/x-pack/plugins/osquery/server/routes/privileges_check/privileges_check_route.ts +++ b/x-pack/plugins/osquery/server/routes/privileges_check/privileges_check_route.ts @@ -20,9 +20,8 @@ export const privilegesCheckRoute = (router: IRouter, osqueryContext: OsqueryApp }, async (context, request, response) => { if (osqueryContext.security.authz.mode.useRbacForRequest(request)) { - const checkPrivileges = osqueryContext.security.authz.checkPrivilegesDynamicallyWithRequest( - request - ); + const checkPrivileges = + osqueryContext.security.authz.checkPrivilegesDynamicallyWithRequest(request); const { hasAllRequested } = await checkPrivileges({ elasticsearch: { cluster: [], diff --git a/x-pack/plugins/osquery/server/utils/build_validation/route_validation.ts b/x-pack/plugins/osquery/server/utils/build_validation/route_validation.ts index caba0eb9f0152..9a7129ea0e176 100644 --- a/x-pack/plugins/osquery/server/utils/build_validation/route_validation.ts +++ b/x-pack/plugins/osquery/server/utils/build_validation/route_validation.ts @@ -27,34 +27,30 @@ type RequestValidationResult = error: RouteValidationError; }; -export const buildRouteValidation = >( - schema: T -): RouteValidationFunction => ( - inputValue: unknown, - validationResult: RouteValidationResultFactory -) => - pipe( - schema.decode(inputValue), - (decoded) => exactCheck(inputValue, decoded), - fold>( - (errors: rt.Errors) => validationResult.badRequest(formatErrors(errors).join()), - (validatedInput: A) => validationResult.ok(validatedInput) - ) - ); +export const buildRouteValidation = + >(schema: T): RouteValidationFunction => + (inputValue: unknown, validationResult: RouteValidationResultFactory) => + pipe( + schema.decode(inputValue), + (decoded) => exactCheck(inputValue, decoded), + fold>( + (errors: rt.Errors) => validationResult.badRequest(formatErrors(errors).join()), + (validatedInput: A) => validationResult.ok(validatedInput) + ) + ); -export const buildRouteValidationWithExcess = < - T extends rt.InterfaceType | GenericIntersectionC | rt.PartialType, - A = rt.TypeOf ->( - schema: T -): RouteValidationFunction => ( - inputValue: unknown, - validationResult: RouteValidationResultFactory -) => - pipe( - excess(schema).decode(inputValue), - fold>( - (errors: rt.Errors) => validationResult.badRequest(formatErrors(errors).join()), - (validatedInput: A) => validationResult.ok(validatedInput) - ) - ); +export const buildRouteValidationWithExcess = + < + T extends rt.InterfaceType | GenericIntersectionC | rt.PartialType, + A = rt.TypeOf + >( + schema: T + ): RouteValidationFunction => + (inputValue: unknown, validationResult: RouteValidationResultFactory) => + pipe( + excess(schema).decode(inputValue), + fold>( + (errors: rt.Errors) => validationResult.badRequest(formatErrors(errors).join()), + (validatedInput: A) => validationResult.ok(validatedInput) + ) + ); diff --git a/x-pack/plugins/osquery/server/utils/runtime_types.ts b/x-pack/plugins/osquery/server/utils/runtime_types.ts index 08e6345e0751f..8daf1ce4debef 100644 --- a/x-pack/plugins/osquery/server/utils/runtime_types.ts +++ b/x-pack/plugins/osquery/server/utils/runtime_types.ts @@ -30,11 +30,10 @@ export const throwErrors = (createError: ErrorFactory) => (errors: rt.Errors) => throw createError(failure(errors).join('\n')); }; -export const decodeOrThrow = ( - runtimeType: rt.Type, - createError: ErrorFactory = createPlainError -) => (inputValue: I) => - pipe(runtimeType.decode(inputValue), fold(throwErrors(createError), identity)); +export const decodeOrThrow = + (runtimeType: rt.Type, createError: ErrorFactory = createPlainError) => + (inputValue: I) => + pipe(runtimeType.decode(inputValue), fold(throwErrors(createError), identity)); const getProps = ( codec: diff --git a/x-pack/plugins/remote_clusters/public/application/store/actions/detail_panel.js b/x-pack/plugins/remote_clusters/public/application/store/actions/detail_panel.js index 1c35ddc784cf0..faa3769ff9e64 100644 --- a/x-pack/plugins/remote_clusters/public/application/store/actions/detail_panel.js +++ b/x-pack/plugins/remote_clusters/public/application/store/actions/detail_panel.js @@ -9,23 +9,25 @@ import { extractQueryParams } from '../../../shared_imports'; import { getRouter } from '../../services'; import { OPEN_DETAIL_PANEL, CLOSE_DETAIL_PANEL } from '../action_types'; -export const openDetailPanel = ({ name }) => (dispatch) => { - const { history } = getRouter(); - const search = history.location.search; - const { cluster: clusterName } = extractQueryParams(search); +export const openDetailPanel = + ({ name }) => + (dispatch) => { + const { history } = getRouter(); + const search = history.location.search; + const { cluster: clusterName } = extractQueryParams(search); - if (clusterName !== name) { - // Allow the user to share a deep link to this job. - history.replace({ - search: `?cluster=${name}`, - }); - } + if (clusterName !== name) { + // Allow the user to share a deep link to this job. + history.replace({ + search: `?cluster=${name}`, + }); + } - dispatch({ - type: OPEN_DETAIL_PANEL, - payload: { clusterName: name }, - }); -}; + dispatch({ + type: OPEN_DETAIL_PANEL, + payload: { clusterName: name }, + }); + }; export const closeDetailPanel = () => (dispatch) => { dispatch({ diff --git a/x-pack/plugins/remote_clusters/public/application/store/actions/edit_cluster.js b/x-pack/plugins/remote_clusters/public/application/store/actions/edit_cluster.js index 14a6775583f9d..38e4854f655d5 100644 --- a/x-pack/plugins/remote_clusters/public/application/store/actions/edit_cluster.js +++ b/x-pack/plugins/remote_clusters/public/application/store/actions/edit_cluster.js @@ -97,14 +97,16 @@ export const editCluster = (cluster) => async (dispatch) => { } }; -export const startEditingCluster = ({ clusterName }) => (dispatch) => { - dispatch(loadClusters()); - - dispatch({ - type: EDIT_CLUSTER_START, - payload: { clusterName }, - }); -}; +export const startEditingCluster = + ({ clusterName }) => + (dispatch) => { + dispatch(loadClusters()); + + dispatch({ + type: EDIT_CLUSTER_START, + payload: { clusterName }, + }); + }; export const stopEditingCluster = () => (dispatch) => { // Load the clusters to refresh the one we just edited. diff --git a/x-pack/plugins/remote_clusters/public/plugin.ts b/x-pack/plugins/remote_clusters/public/plugin.ts index 540a8b40a6208..be9e1bcceb219 100644 --- a/x-pack/plugins/remote_clusters/public/plugin.ts +++ b/x-pack/plugins/remote_clusters/public/plugin.ts @@ -22,7 +22,8 @@ export interface RemoteClustersPluginSetup { } export class RemoteClustersUIPlugin - implements Plugin { + implements Plugin +{ constructor(private readonly initializerContext: PluginInitializerContext) {} setup( diff --git a/x-pack/plugins/remote_clusters/server/config.ts b/x-pack/plugins/remote_clusters/server/config.ts index e0fadea5d41f7..8f379ec5613c8 100644 --- a/x-pack/plugins/remote_clusters/server/config.ts +++ b/x-pack/plugins/remote_clusters/server/config.ts @@ -18,6 +18,7 @@ export const configSchema = schema.object({ export type ConfigType = TypeOf; export const config: PluginConfigDescriptor = { + deprecations: ({ deprecate }) => [deprecate('enabled', '8.0.0')], schema: configSchema, exposeToBrowser: { ui: true, diff --git a/x-pack/plugins/remote_clusters/server/plugin.ts b/x-pack/plugins/remote_clusters/server/plugin.ts index 01cb0fa892316..b13773c27034a 100644 --- a/x-pack/plugins/remote_clusters/server/plugin.ts +++ b/x-pack/plugins/remote_clusters/server/plugin.ts @@ -26,7 +26,8 @@ export interface RemoteClustersPluginSetup { } export class RemoteClustersServerPlugin - implements Plugin { + implements Plugin +{ licenseStatus: LicenseStatus; log: Logger; config: ConfigType; diff --git a/x-pack/plugins/remote_clusters/server/routes/api/delete_route.ts b/x-pack/plugins/remote_clusters/server/routes/api/delete_route.ts index 18aa9154fba42..ac5a89139d52a 100644 --- a/x-pack/plugins/remote_clusters/server/routes/api/delete_route.ts +++ b/x-pack/plugins/remote_clusters/server/routes/api/delete_route.ts @@ -74,11 +74,10 @@ export const register = (deps: RouteDependencies): void => { try { const body = serializeCluster({ name, hasDeprecatedProxySetting }); - const { - body: updateClusterResponse, - } = await clusterClient.asCurrentUser.cluster.putSettings({ - body, - }); + const { body: updateClusterResponse } = + await clusterClient.asCurrentUser.cluster.putSettings({ + body, + }); const acknowledged = get(updateClusterResponse, 'acknowledged'); const cluster = get(updateClusterResponse, `persistent.cluster.remote.${name}`); diff --git a/x-pack/plugins/reporting/public/lib/license_check.test.ts b/x-pack/plugins/reporting/public/lib/license_check.test.ts index 8f46ca5616f19..d3966e04a6604 100644 --- a/x-pack/plugins/reporting/public/lib/license_check.test.ts +++ b/x-pack/plugins/reporting/public/lib/license_check.test.ts @@ -43,7 +43,7 @@ describe('License check', () => { }); it('shows and enables links if state is not known', () => { - expect(checkLicense(({ state: 'PONYFOO' } as unknown) as LicenseCheck)).toEqual({ + expect(checkLicense({ state: 'PONYFOO' } as unknown as LicenseCheck)).toEqual({ enableLinks: true, showLinks: true, message: '', diff --git a/x-pack/plugins/reporting/public/lib/reporting_api_client/reporting_api_client.test.ts b/x-pack/plugins/reporting/public/lib/reporting_api_client/reporting_api_client.test.ts index b2ae90ec1af7c..e477f00f307d9 100644 --- a/x-pack/plugins/reporting/public/lib/reporting_api_client/reporting_api_client.test.ts +++ b/x-pack/plugins/reporting/public/lib/reporting_api_client/reporting_api_client.test.ts @@ -202,8 +202,7 @@ describe('ReportingAPIClient', () => { expect(httpClient.post).toHaveBeenCalledWith( expect.stringContaining('/pdf'), expect.objectContaining({ - body: - '{"jobParams":"(browserTimezone:UTC,objectType:something,title:\'some title\',version:\'some version\')"}', + body: '{"jobParams":"(browserTimezone:UTC,objectType:something,title:\'some title\',version:\'some version\')"}', }) ); }); diff --git a/x-pack/plugins/reporting/public/lib/stream_handler.test.ts b/x-pack/plugins/reporting/public/lib/stream_handler.test.ts index 518c8ef11857a..1bb8c3229407d 100644 --- a/x-pack/plugins/reporting/public/lib/stream_handler.test.ts +++ b/x-pack/plugins/reporting/public/lib/stream_handler.test.ts @@ -30,7 +30,7 @@ const coreSetup = coreMock.createSetup(); const jobQueueClientMock = new ReportingAPIClient(coreSetup.http, coreSetup.uiSettings, '7.15.0'); jobQueueClientMock.findForJobIds = async () => mockJobsFound; jobQueueClientMock.getInfo = () => - Promise.resolve(({ content: 'this is the completed report data' } as unknown) as Job); + Promise.resolve({ content: 'this is the completed report data' } as unknown as Job); jobQueueClientMock.getError = () => Promise.resolve('this is the failed report error'); jobQueueClientMock.getManagementLink = () => '/#management'; jobQueueClientMock.getDownloadLink = () => '/reporting/download/job-123'; @@ -38,13 +38,13 @@ jobQueueClientMock.getDownloadLink = () => '/reporting/download/job-123'; const mockShowDanger = stub(); const mockShowSuccess = stub(); const mockShowWarning = stub(); -const notificationsMock = ({ +const notificationsMock = { toasts: { addDanger: mockShowDanger, addSuccess: mockShowSuccess, addWarning: mockShowWarning, }, -} as unknown) as NotificationsStart; +} as unknown as NotificationsStart; describe('stream handler', () => { afterEach(() => { diff --git a/x-pack/plugins/reporting/public/management/report_listing.test.tsx b/x-pack/plugins/reporting/public/management/report_listing.test.tsx index 8d4ebb0c894a0..bdb8bdbbe5d5e 100644 --- a/x-pack/plugins/reporting/public/management/report_listing.test.tsx +++ b/x-pack/plugins/reporting/public/management/report_listing.test.tsx @@ -207,11 +207,11 @@ const mockJobs: ReportApiJSON[] = [ }), ]; -const reportingAPIClient = ({ +const reportingAPIClient = { list: jest.fn(() => Promise.resolve(mockJobs.map((j) => new Job(j)))), total: jest.fn(() => Promise.resolve(18)), migrateReportingIndicesIlmPolicy: jest.fn(), -} as unknown) as DeeplyMockedKeys; +} as unknown as DeeplyMockedKeys; const validCheck = { check: () => ({ @@ -302,15 +302,15 @@ describe('ReportListing', () => { navLinks: {}, management: { data: { index_lifecycle_management: true } }, }; - ilmLocator = ({ + ilmLocator = { getUrl: jest.fn(), - } as unknown) as LocatorPublic; + } as unknown as LocatorPublic; - urlService = ({ + urlService = { locators: { get: () => ilmLocator, }, - } as unknown) as SharePluginSetup['url']; + } as unknown as SharePluginSetup['url']; await runSetup(); }); @@ -326,11 +326,11 @@ describe('ReportListing', () => { it('subscribes to license changes, and unsubscribes on dismount', async () => { const unsubscribeMock = jest.fn(); - const subMock = ({ + const subMock = { subscribe: jest.fn().mockReturnValue({ unsubscribe: unsubscribeMock, }), - } as unknown) as Observable; + } as unknown as Observable; await runSetup({ license$: subMock }); @@ -343,15 +343,15 @@ describe('ReportListing', () => { describe('ILM policy', () => { beforeEach(async () => { httpService = httpServiceMock.createSetupContract(); - ilmLocator = ({ + ilmLocator = { getUrl: jest.fn(), - } as unknown) as LocatorPublic; + } as unknown as LocatorPublic; - urlService = ({ + urlService = { locators: { get: () => ilmLocator, }, - } as unknown) as SharePluginSetup['url']; + } as unknown as SharePluginSetup['url']; await runSetup(); }); diff --git a/x-pack/plugins/reporting/public/management/report_listing.tsx b/x-pack/plugins/reporting/public/management/report_listing.tsx index c3a05042681c3..6b46778011250 100644 --- a/x-pack/plugins/reporting/public/management/report_listing.tsx +++ b/x-pack/plugins/reporting/public/management/report_listing.tsx @@ -144,9 +144,11 @@ class ReportListingUi extends Component { } private licenseHandler = (license: ILicense) => { - const { enableLinks, showLinks, message: badLicenseMessage } = checkLicense( - license.check('reporting', 'basic') - ); + const { + enableLinks, + showLinks, + message: badLicenseMessage, + } = checkLicense(license.check('reporting', 'basic')); this.setState({ enableLinks, diff --git a/x-pack/plugins/reporting/public/panel_actions/get_csv_panel_action.test.ts b/x-pack/plugins/reporting/public/panel_actions/get_csv_panel_action.test.ts index 654d46cdfbcb1..bedf310725ae2 100644 --- a/x-pack/plugins/reporting/public/panel_actions/get_csv_panel_action.test.ts +++ b/x-pack/plugins/reporting/public/panel_actions/get_csv_panel_action.test.ts @@ -11,8 +11,10 @@ import { CoreStart } from 'src/core/public'; import type { SearchSource } from 'src/plugins/data/common'; import type { SavedSearch } from 'src/plugins/discover/public'; import { coreMock } from '../../../../../src/core/public/mocks'; +import { dataPluginMock } from '../../../../../src/plugins/data/public/mocks'; import type { ILicense, LicensingPluginSetup } from '../../../licensing/public'; import { ReportingAPIClient } from '../lib/reporting_api_client'; +import type { ReportingPublicPluginStartDendencies } from '../plugin'; import type { ActionContext } from './get_csv_panel_action'; import { ReportingCsvPanelAction } from './get_csv_panel_action'; @@ -25,8 +27,8 @@ describe('GetCsvReportPanelAction', () => { let context: ActionContext; let mockLicense$: (state?: LicenseResults) => Rx.Observable; let mockSearchSource: SearchSource; - let mockStartServicesPayload: [CoreStart, object, unknown]; - let mockStartServices$: Rx.Subject; + let mockStartServicesPayload: [CoreStart, ReportingPublicPluginStartDendencies, unknown]; + let mockStartServices$: Rx.Observable; beforeAll(() => { if (typeof window.URL.revokeObjectURL === 'undefined') { @@ -43,29 +45,32 @@ describe('GetCsvReportPanelAction', () => { jest.spyOn(apiClient, 'createImmediateReport'); mockLicense$ = (state: LicenseResults = 'valid') => { - return (Rx.of({ + return Rx.of({ check: jest.fn().mockImplementation(() => ({ state })), - }) as unknown) as LicensingPluginSetup['license$']; + }) as unknown as LicensingPluginSetup['license$']; }; - mockStartServices$ = new Rx.Subject<[CoreStart, object, unknown]>(); mockStartServicesPayload = [ - ({ + { + ...core, application: { capabilities: { dashboard: { downloadCsv: true } } }, - } as unknown) as CoreStart, - {}, + } as unknown as CoreStart, + { + data: dataPluginMock.createStartContract(), + } as ReportingPublicPluginStartDendencies, null, ]; + mockStartServices$ = Rx.from(Promise.resolve(mockStartServicesPayload)); - mockSearchSource = ({ + mockSearchSource = { createCopy: () => mockSearchSource, removeField: jest.fn(), setField: jest.fn(), getField: jest.fn(), getSerializedFields: jest.fn().mockImplementation(() => ({})), - } as unknown) as SearchSource; + } as unknown as SearchSource; - context = ({ + context = { embeddable: { type: 'search', getSavedSearch: () => { @@ -81,7 +86,7 @@ describe('GetCsvReportPanelAction', () => { }, }), }, - } as unknown) as ActionContext; + } as unknown as ActionContext; }); it('translates empty embeddable context into job params', async () => { @@ -93,7 +98,7 @@ describe('GetCsvReportPanelAction', () => { usesUiCapabilities: true, }); - mockStartServices$.next(mockStartServicesPayload); + await mockStartServices$.pipe(first()).toPromise(); await panel.execute(context); @@ -108,18 +113,18 @@ describe('GetCsvReportPanelAction', () => { }); it('translates embeddable context into job params', async () => { - mockSearchSource = ({ + mockSearchSource = { createCopy: () => mockSearchSource, removeField: jest.fn(), setField: jest.fn(), getField: jest.fn(), getSerializedFields: jest.fn().mockImplementation(() => ({ testData: 'testDataValue' })), - } as unknown) as SearchSource; + } as unknown as SearchSource; context.embeddable.getSavedSearch = () => { - return ({ + return { searchSource: mockSearchSource, columns: ['column_a', 'column_b'], - } as unknown) as SavedSearch; + } as unknown as SavedSearch; }; const panel = new ReportingCsvPanelAction({ @@ -130,7 +135,7 @@ describe('GetCsvReportPanelAction', () => { usesUiCapabilities: true, }); - mockStartServices$.next(mockStartServicesPayload); + await mockStartServices$.pipe(first()).toPromise(); await panel.execute(context); @@ -153,7 +158,7 @@ describe('GetCsvReportPanelAction', () => { usesUiCapabilities: true, }); - mockStartServices$.next(mockStartServicesPayload); + await mockStartServices$.pipe(first()).toPromise(); await panel.execute(context); @@ -169,7 +174,7 @@ describe('GetCsvReportPanelAction', () => { usesUiCapabilities: true, }); - mockStartServices$.next(mockStartServicesPayload); + await mockStartServices$.pipe(first()).toPromise(); await panel.execute(context); @@ -187,7 +192,7 @@ describe('GetCsvReportPanelAction', () => { usesUiCapabilities: true, }); - mockStartServices$.next(mockStartServicesPayload); + await mockStartServices$.pipe(first()).toPromise(); await panel.execute(context); @@ -204,14 +209,13 @@ describe('GetCsvReportPanelAction', () => { usesUiCapabilities: true, }); - mockStartServices$.next(mockStartServicesPayload); - + await mockStartServices$.pipe(first()).toPromise(); await licenseMock$.pipe(first()).toPromise(); expect(await plugin.isCompatible(context)).toEqual(false); }); - it('sets a display and icon type', () => { + it('sets a display and icon type', async () => { const panel = new ReportingCsvPanelAction({ core, apiClient, @@ -220,7 +224,7 @@ describe('GetCsvReportPanelAction', () => { usesUiCapabilities: true, }); - mockStartServices$.next(mockStartServicesPayload); + await mockStartServices$.pipe(first()).toPromise(); expect(panel.getIconType()).toMatchInlineSnapshot(`"document"`); expect(panel.getDisplayName()).toMatchInlineSnapshot(`"Download CSV"`); @@ -228,25 +232,28 @@ describe('GetCsvReportPanelAction', () => { describe('Application UI Capabilities', () => { it(`doesn't allow downloads when UI capability is not enabled`, async () => { + mockStartServicesPayload = [ + { application: { capabilities: {} } } as unknown as CoreStart, + { + data: dataPluginMock.createStartContract(), + } as ReportingPublicPluginStartDendencies, + null, + ]; + const startServices$ = Rx.from(Promise.resolve(mockStartServicesPayload)); const plugin = new ReportingCsvPanelAction({ core, apiClient, license$: mockLicense$(), - startServices$: mockStartServices$, + startServices$, usesUiCapabilities: true, }); - mockStartServices$.next([ - ({ application: { capabilities: {} } } as unknown) as CoreStart, - {}, - null, - ]); + await startServices$.pipe(first()).toPromise(); expect(await plugin.isCompatible(context)).toEqual(false); }); it(`allows downloads when license is valid and UI capability is enabled`, async () => { - mockStartServices$ = new Rx.Subject(); const plugin = new ReportingCsvPanelAction({ core, apiClient, @@ -255,7 +262,7 @@ describe('GetCsvReportPanelAction', () => { usesUiCapabilities: true, }); - mockStartServices$.next(mockStartServicesPayload); + await mockStartServices$.pipe(first()).toPromise(); expect(await plugin.isCompatible(context)).toEqual(true); }); diff --git a/x-pack/plugins/reporting/public/panel_actions/get_csv_panel_action.tsx b/x-pack/plugins/reporting/public/panel_actions/get_csv_panel_action.tsx index eb14e32160869..ef32e64741765 100644 --- a/x-pack/plugins/reporting/public/panel_actions/get_csv_panel_action.tsx +++ b/x-pack/plugins/reporting/public/panel_actions/get_csv_panel_action.tsx @@ -7,7 +7,8 @@ import { i18n } from '@kbn/i18n'; import * as Rx from 'rxjs'; -import type { CoreSetup, IUiSettingsClient, NotificationsSetup } from 'src/core/public'; +import { first } from 'rxjs/operators'; +import type { CoreSetup, NotificationsSetup } from 'src/core/public'; import { CoreStart } from 'src/core/public'; import type { ISearchEmbeddable, SavedSearch } from '../../../../../src/plugins/discover/public'; import { @@ -22,6 +23,7 @@ import type { LicensingPluginSetup } from '../../../licensing/public'; import { CSV_REPORTING_ACTION } from '../../common/constants'; import { checkLicense } from '../lib/license_check'; import { ReportingAPIClient } from '../lib/reporting_api_client'; +import type { ReportingPublicPluginStartDendencies } from '../plugin'; function isSavedSearchEmbeddable( embeddable: IEmbeddable | ISearchEmbeddable @@ -36,7 +38,7 @@ export interface ActionContext { interface Params { apiClient: ReportingAPIClient; core: CoreSetup; - startServices$: Rx.Observable<[CoreStart, object, unknown]>; + startServices$: Rx.Observable<[CoreStart, ReportingPublicPluginStartDendencies, unknown]>; license$: LicensingPluginSetup['license$']; usesUiCapabilities: boolean; } @@ -47,16 +49,16 @@ export class ReportingCsvPanelAction implements ActionDefinition public readonly id = CSV_REPORTING_ACTION; private licenseHasDownloadCsv: boolean = false; private capabilityHasDownloadCsv: boolean = false; - private uiSettings: IUiSettingsClient; private notifications: NotificationsSetup; private apiClient: ReportingAPIClient; + private startServices$: Params['startServices$']; constructor({ core, startServices$, license$, usesUiCapabilities, apiClient }: Params) { this.isDownloading = false; - this.uiSettings = core.uiSettings; this.notifications = core.notifications; this.apiClient = apiClient; + this.startServices$ = startServices$; license$.subscribe((license) => { const results = license.check('reporting', 'basic'); @@ -65,7 +67,7 @@ export class ReportingCsvPanelAction implements ActionDefinition }); if (usesUiCapabilities) { - startServices$.subscribe(([{ application }]) => { + this.startServices$.subscribe(([{ application }]) => { this.capabilityHasDownloadCsv = application.capabilities.dashboard?.downloadCsv === true; }); } else { @@ -84,11 +86,12 @@ export class ReportingCsvPanelAction implements ActionDefinition } public async getSearchSource(savedSearch: SavedSearch, embeddable: ISearchEmbeddable) { + const [{ uiSettings }, { data }] = await this.startServices$.pipe(first()).toPromise(); const { getSharingData } = await loadSharingDataHelpers(); return await getSharingData( savedSearch.searchSource, - savedSearch, // TODO: get unsaved state (using embeddale.searchScope): https://github.com/elastic/kibana/issues/43977 - this.uiSettings + savedSearch, // TODO: get unsaved state (using embeddable.searchScope): https://github.com/elastic/kibana/issues/43977 + { uiSettings, data } ); } diff --git a/x-pack/plugins/reporting/public/plugin.ts b/x-pack/plugins/reporting/public/plugin.ts index b010acd45c296..7fd6047470a0e 100644 --- a/x-pack/plugins/reporting/public/plugin.ts +++ b/x-pack/plugins/reporting/public/plugin.ts @@ -8,6 +8,7 @@ import { i18n } from '@kbn/i18n'; import * as Rx from 'rxjs'; import { catchError, filter, map, mergeMap, takeUntil } from 'rxjs/operators'; +import type { DataPublicPluginStart } from 'src/plugins/data/public'; import { CoreSetup, CoreStart, @@ -77,6 +78,7 @@ export interface ReportingPublicPluginSetupDendencies { export interface ReportingPublicPluginStartDendencies { home: HomePublicPluginStart; + data: DataPublicPluginStart; management: ManagementStart; licensing: LicensingPluginStart; uiActions: UiActionsStart; @@ -90,7 +92,8 @@ export class ReportingPublicPlugin ReportingStart, ReportingPublicPluginSetupDendencies, ReportingPublicPluginStartDendencies - > { + > +{ private kibanaVersion: string; private apiClient?: ReportingAPIClient; private readonly stop$ = new Rx.ReplaySubject(1); @@ -133,7 +136,10 @@ export class ReportingPublicPlugin return this.contract; } - public setup(core: CoreSetup, setupDeps: ReportingPublicPluginSetupDendencies) { + public setup( + core: CoreSetup, + setupDeps: ReportingPublicPluginSetupDendencies + ) { const { getStartServices, uiSettings } = core; const { home, diff --git a/x-pack/plugins/reporting/public/redirect/redirect_app.tsx b/x-pack/plugins/reporting/public/redirect/redirect_app.tsx index 3024404dc07bf..cf027e2a46196 100644 --- a/x-pack/plugins/reporting/public/redirect/redirect_app.tsx +++ b/x-pack/plugins/reporting/public/redirect/redirect_app.tsx @@ -44,7 +44,7 @@ export const RedirectApp: FunctionComponent = ({ share }) => { useEffect(() => { try { - const locatorParams = ((window as unknown) as Record)[ + const locatorParams = (window as unknown as Record)[ REPORTING_REDIRECT_LOCATOR_STORE_KEY ]; diff --git a/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx b/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx index 0b31083a0fe8d..610781f3b6ea0 100644 --- a/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx +++ b/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx @@ -14,45 +14,47 @@ import { checkLicense } from '../lib/license_check'; import { ReportingAPIClient } from '../lib/reporting_api_client'; import { ScreenCapturePanelContent } from './screen_capture_panel_content_lazy'; -const getJobParams = ( - apiClient: ReportingAPIClient, - opts: JobParamsProviderOptions, - type: 'png' | 'pngV2' | 'printablePdf' | 'printablePdfV2' -) => () => { - const { - objectType, - sharingData: { title, layout, locatorParams }, - } = opts; +const getJobParams = + ( + apiClient: ReportingAPIClient, + opts: JobParamsProviderOptions, + type: 'png' | 'pngV2' | 'printablePdf' | 'printablePdfV2' + ) => + () => { + const { + objectType, + sharingData: { title, layout, locatorParams }, + } = opts; - const baseParams = { - objectType, - layout, - title, - }; + const baseParams = { + objectType, + layout, + title, + }; - if (type === 'printablePdfV2') { - // multi locator for PDF V2 - return { ...baseParams, locatorParams: [locatorParams] }; - } else if (type === 'pngV2') { - // single locator for PNG V2 - return { ...baseParams, locatorParams }; - } + if (type === 'printablePdfV2') { + // multi locator for PDF V2 + return { ...baseParams, locatorParams: [locatorParams] }; + } else if (type === 'pngV2') { + // single locator for PNG V2 + return { ...baseParams, locatorParams }; + } - // Relative URL must have URL prefix (Spaces ID prefix), but not server basePath - // Replace hashes with original RISON values. - const relativeUrl = opts.shareableUrl.replace( - window.location.origin + apiClient.getServerBasePath(), - '' - ); + // Relative URL must have URL prefix (Spaces ID prefix), but not server basePath + // Replace hashes with original RISON values. + const relativeUrl = opts.shareableUrl.replace( + window.location.origin + apiClient.getServerBasePath(), + '' + ); - if (type === 'printablePdf') { - // multi URL for PDF - return { ...baseParams, relativeUrls: [relativeUrl] }; - } + if (type === 'printablePdf') { + // multi URL for PDF + return { ...baseParams, relativeUrls: [relativeUrl] }; + } - // single URL for PNG - return { ...baseParams, relativeUrl }; -}; + // single URL for PNG + return { ...baseParams, relativeUrl }; + }; export const reportingScreenshotShareProvider = ({ apiClient, @@ -113,7 +115,7 @@ export const reportingScreenshotShareProvider = ({ return []; } - const { sharingData } = (shareOpts as unknown) as { sharingData: ReportingSharingData }; + const { sharingData } = shareOpts as unknown as { sharingData: ReportingSharingData }; const shareActions = []; const pngPanelTitle = i18n.translate('xpack.reporting.shareContextMenu.pngReportsButtonLabel', { 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 c87c202c0ec1f..2170b50f195b4 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 @@ -7,9 +7,9 @@ import apm from 'elastic-apm-node'; import { i18n } from '@kbn/i18n'; +import { getDataPath } from '@kbn/utils'; import del from 'del'; import fs from 'fs'; -import os from 'os'; import path from 'path'; import puppeteer from 'puppeteer'; import * as Rx from 'rxjs'; @@ -59,7 +59,7 @@ export class HeadlessChromiumDriverFactory { logger.warning(`Enabling the Chromium sandbox provides an additional layer of protection.`); } - this.userDataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'chromium-')); + this.userDataDir = fs.mkdtempSync(path.join(getDataPath(), 'chromium-')); this.getChromiumArgs = (viewport: ViewportConfig) => args({ userDataDir: this.userDataDir, diff --git a/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/metrics.ts b/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/metrics.ts index e319cbd9df72b..1659f28dea9b0 100644 --- a/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/metrics.ts +++ b/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/metrics.ts @@ -37,9 +37,9 @@ interface PerformanceMetrics { } function normalizeMetrics({ metrics }: Metrics) { - return (Object.fromEntries( + return Object.fromEntries( metrics.map(({ name, value }) => [name, value]) - ) as unknown) as NormalizedMetrics; + ) as unknown as NormalizedMetrics; } function getCpuUsage(start: NormalizedMetrics, end: NormalizedMetrics) { diff --git a/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/start_logs.ts b/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/start_logs.ts index 9cfd0949eba99..aa27e46b85acb 100644 --- a/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/start_logs.ts +++ b/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/start_logs.ts @@ -10,9 +10,10 @@ import { spawn } from 'child_process'; import del from 'del'; import { mkdtempSync } from 'fs'; import { uniq } from 'lodash'; -import os, { tmpdir } from 'os'; +import os from 'os'; import { join } from 'path'; import { createInterface } from 'readline'; +import { getDataPath } from '@kbn/utils'; import { fromEvent, merge, of, timer } from 'rxjs'; import { catchError, map, reduce, takeUntil, tap } from 'rxjs/operators'; import { ReportingCore } from '../../../'; @@ -61,7 +62,7 @@ export const browserStartLogs = ( const config = core.getConfig(); const proxy = config.get('capture', 'browser', 'chromium', 'proxy'); const disableSandbox = config.get('capture', 'browser', 'chromium', 'disableSandbox'); - const userDataDir = mkdtempSync(join(tmpdir(), 'chromium-')); + const userDataDir = mkdtempSync(join(getDataPath(), 'chromium-')); const platform = process.platform; const architecture = os.arch(); diff --git a/x-pack/plugins/reporting/server/browsers/download/download.test.ts b/x-pack/plugins/reporting/server/browsers/download/download.test.ts index ff6e5a8273002..688a746826e54 100644 --- a/x-pack/plugins/reporting/server/browsers/download/download.test.ts +++ b/x-pack/plugins/reporting/server/browsers/download/download.test.ts @@ -30,11 +30,11 @@ class ReadableOf extends Readable { jest.mock('axios'); const request: jest.Mock = jest.requireMock('axios').request; -const mockLogger = ({ +const mockLogger = { error: jest.fn(), warn: jest.fn(), info: jest.fn(), -} as unknown) as LevelLogger; +} as unknown as LevelLogger; test('downloads the url to the path', async () => { const BODY = 'abdcefg'; diff --git a/x-pack/plugins/reporting/server/browsers/download/ensure_downloaded.test.ts b/x-pack/plugins/reporting/server/browsers/download/ensure_downloaded.test.ts index 1605a73c0130b..344735d6180b6 100644 --- a/x-pack/plugins/reporting/server/browsers/download/ensure_downloaded.test.ts +++ b/x-pack/plugins/reporting/server/browsers/download/ensure_downloaded.test.ts @@ -20,11 +20,11 @@ describe('ensureBrowserDownloaded', () => { let logger: jest.Mocked; beforeEach(() => { - logger = ({ + logger = { debug: jest.fn(), error: jest.fn(), warning: jest.fn(), - } as unknown) as typeof logger; + } as unknown as typeof logger; (md5 as jest.MockedFunction).mockImplementation( async (path) => diff --git a/x-pack/plugins/reporting/server/config/create_config.test.ts b/x-pack/plugins/reporting/server/config/create_config.test.ts index e78c7f2a88a2b..e042142a54a0f 100644 --- a/x-pack/plugins/reporting/server/config/create_config.test.ts +++ b/x-pack/plugins/reporting/server/config/create_config.test.ts @@ -21,11 +21,11 @@ describe('Reporting server createConfig$', () => { mockInitContext = coreMock.createPluginInitializerContext( createMockConfigSchema({ kibanaServer: {} }) ); - mockLogger = ({ + mockLogger = { warn: jest.fn(), debug: jest.fn(), clone: jest.fn().mockImplementation(() => mockLogger), - } as unknown) as LevelLogger; + } as unknown as LevelLogger; }); afterEach(() => { diff --git a/x-pack/plugins/reporting/server/config/index.test.ts b/x-pack/plugins/reporting/server/config/index.test.ts index b9665759f9f52..464387ebf90c7 100644 --- a/x-pack/plugins/reporting/server/config/index.test.ts +++ b/x-pack/plugins/reporting/server/config/index.test.ts @@ -21,7 +21,9 @@ const applyReportingDeprecations = (settings: Record = {}) => { deprecation, path: CONFIG_PATH, })), - () => ({ message }) => deprecationMessages.push(message) + () => + ({ message }) => + deprecationMessages.push(message) ); return { messages: deprecationMessages, diff --git a/x-pack/plugins/reporting/server/deprecations/reporting_role.test.ts b/x-pack/plugins/reporting/server/deprecations/reporting_role.test.ts index d8ab0ac0c568a..5b0719bf6e6b6 100644 --- a/x-pack/plugins/reporting/server/deprecations/reporting_role.test.ts +++ b/x-pack/plugins/reporting/server/deprecations/reporting_role.test.ts @@ -22,7 +22,7 @@ beforeEach(async () => { esClient.asCurrentUser.security.getUser = jest.fn().mockResolvedValue({ body: { xyz: { username: 'normal_user', roles: ['data_analyst'] } }, }); - context = ({ esClient } as unknown) as GetDeprecationsContext; + context = { esClient } as unknown as GetDeprecationsContext; }); test('logs no deprecations when setup has no issues', async () => { diff --git a/x-pack/plugins/reporting/server/export_types/common/pdf/index.test.ts b/x-pack/plugins/reporting/server/export_types/common/pdf/index.test.ts index 3349552490a39..e336e5f124a2f 100644 --- a/x-pack/plugins/reporting/server/export_types/common/pdf/index.test.ts +++ b/x-pack/plugins/reporting/server/export_types/common/pdf/index.test.ts @@ -26,7 +26,7 @@ describe('PdfMaker', () => { pdf.addImage(imageBase64, { title: 'second viz', description: '❄️' }), ]).toEqual([undefined, undefined]); - const { _layout: testLayout, _title: testTitle } = (pdf as unknown) as { + const { _layout: testLayout, _title: testTitle } = pdf as unknown as { _layout: object; _title: string; }; @@ -56,7 +56,7 @@ describe('PdfMaker', () => { expect(pdf.setTitle('the finest PDF in the world')).toBe(undefined); expect(pdf.addImage(imageBase64, { title: 'cool times', description: '☃️' })).toBe(undefined); - const { _layout: testLayout, _title: testTitle } = (pdf as unknown) as { + const { _layout: testLayout, _title: testTitle } = pdf as unknown as { _layout: object; _title: string; }; diff --git a/x-pack/plugins/reporting/server/export_types/common/pdf/index.ts b/x-pack/plugins/reporting/server/export_types/common/pdf/index.ts index 0fc4e497e16a7..0cd054d3e3709 100644 --- a/x-pack/plugins/reporting/server/export_types/common/pdf/index.ts +++ b/x-pack/plugins/reporting/server/export_types/common/pdf/index.ts @@ -59,10 +59,10 @@ export class PdfMaker { // inject a page break for every 2 groups on the page if (groupCount > 0 && groupCount % this._layout.groupCount === 0) { contents = [ - ({ + { text: '', pageBreak: 'after', - } as ContentText) as Content, + } as ContentText as Content, ].concat(contents); } this._content.push(contents); diff --git a/x-pack/plugins/reporting/server/export_types/common/set_force_now.ts b/x-pack/plugins/reporting/server/export_types/common/set_force_now.ts index ee7d613f1b8e1..b4f4b1b0ace05 100644 --- a/x-pack/plugins/reporting/server/export_types/common/set_force_now.ts +++ b/x-pack/plugins/reporting/server/export_types/common/set_force_now.ts @@ -11,12 +11,14 @@ import type { LocatorParams } from '../../../common/types'; * Add `forceNow` to {@link LocatorParams['params']} to enable clients to set the time appropriately when * reporting navigates to the page in Chromium. */ -export const setForceNow = (forceNow: string) => (locator: LocatorParams): LocatorParams => { - return { - ...locator, - params: { - ...locator.params, - forceNow, - }, +export const setForceNow = + (forceNow: string) => + (locator: LocatorParams): LocatorParams => { + return { + ...locator, + params: { + ...locator.params, + forceNow, + }, + }; }; -}; diff --git a/x-pack/plugins/reporting/server/export_types/csv/create_job.ts b/x-pack/plugins/reporting/server/export_types/csv/create_job.ts index b6160daa86f85..be02bbc529269 100644 --- a/x-pack/plugins/reporting/server/export_types/csv/create_job.ts +++ b/x-pack/plugins/reporting/server/export_types/csv/create_job.ts @@ -21,10 +21,10 @@ export const createJobFnFactory: CreateJobFnFactory< ); const savedObjectsClient = context.core.savedObjects.client; - const indexPatternSavedObject = ((await savedObjectsClient.get( + const indexPatternSavedObject = (await savedObjectsClient.get( 'index-pattern', jobParams.indexPatternId - )) as unknown) as IndexPatternSavedObjectDeprecatedCSV; + )) as unknown as IndexPatternSavedObjectDeprecatedCSV; return { isDeprecated: true, diff --git a/x-pack/plugins/reporting/server/export_types/csv/execute_job.test.ts b/x-pack/plugins/reporting/server/export_types/csv/execute_job.test.ts index 3f51b4a23b584..5032eaab46e84 100644 --- a/x-pack/plugins/reporting/server/export_types/csv/execute_job.test.ts +++ b/x-pack/plugins/reporting/server/export_types/csv/execute_job.test.ts @@ -74,7 +74,7 @@ describe('CSV Execute Job', function () { beforeEach(async function () { content = ''; - stream = ({ write: jest.fn((chunk) => (content += chunk)) } as unknown) as typeof stream; + stream = { write: jest.fn((chunk) => (content += chunk)) } as unknown as typeof stream; configGetStub = sinon.stub(); configGetStub.withArgs('queue', 'timeout').returns(moment.duration('2m')); configGetStub.withArgs('index').returns('.reporting-foo-test'); @@ -85,7 +85,7 @@ describe('CSV Execute Job', function () { mockReportingCore = await createMockReportingCore(createMockConfigSchema()); mockReportingCore.getUiSettingsServiceFactory = () => - Promise.resolve((mockUiSettingsClient as unknown) as IUiSettingsClient); + Promise.resolve(mockUiSettingsClient as unknown as IUiSettingsClient); mockReportingCore.setConfig(mockReportingConfig); mockEsClient = (await mockReportingCore.getEsClient()).asScoped({} as any) @@ -1058,7 +1058,7 @@ describe('CSV Execute Job', function () { beforeEach(async function () { mockReportingCore.getUiSettingsServiceFactory = () => - Promise.resolve((mockUiSettingsClient as unknown) as IUiSettingsClient); + Promise.resolve(mockUiSettingsClient as unknown as IUiSettingsClient); configGetStub.withArgs('csv', 'maxSizeBytes').returns(18); mockEsClient.search.mockResolvedValueOnce({ diff --git a/x-pack/plugins/reporting/server/export_types/csv/execute_job.ts b/x-pack/plugins/reporting/server/export_types/csv/execute_job.ts index 0ae0adb2fc1de..9ce0bb6d6de00 100644 --- a/x-pack/plugins/reporting/server/export_types/csv/execute_job.ts +++ b/x-pack/plugins/reporting/server/export_types/csv/execute_job.ts @@ -11,37 +11,36 @@ import { decryptJobHeaders } from '../common'; import { createGenerateCsv } from './generate_csv'; import { TaskPayloadDeprecatedCSV } from './types'; -export const runTaskFnFactory: RunTaskFnFactory< - RunTaskFn -> = function executeJobFactoryFn(reporting, parentLogger) { - const config = reporting.getConfig(); +export const runTaskFnFactory: RunTaskFnFactory> = + function executeJobFactoryFn(reporting, parentLogger) { + const config = reporting.getConfig(); - return async function runTask(jobId, job, cancellationToken, stream) { - const elasticsearch = await reporting.getEsClient(); - const logger = parentLogger.clone([jobId]); - const generateCsv = createGenerateCsv(logger); + return async function runTask(jobId, job, cancellationToken, stream) { + const elasticsearch = await reporting.getEsClient(); + const logger = parentLogger.clone([jobId]); + const generateCsv = createGenerateCsv(logger); - const encryptionKey = config.get('encryptionKey'); - const headers = await decryptJobHeaders(encryptionKey, job.headers, logger); - const fakeRequest = reporting.getFakeRequest({ headers }, job.spaceId, logger); - const uiSettingsClient = await reporting.getUiSettingsClient(fakeRequest, logger); - const { asCurrentUser: elasticsearchClient } = elasticsearch.asScoped(fakeRequest); + const encryptionKey = config.get('encryptionKey'); + const headers = await decryptJobHeaders(encryptionKey, job.headers, logger); + const fakeRequest = reporting.getFakeRequest({ headers }, job.spaceId, logger); + const uiSettingsClient = await reporting.getUiSettingsClient(fakeRequest, logger); + const { asCurrentUser: elasticsearchClient } = elasticsearch.asScoped(fakeRequest); - const { maxSizeReached, csvContainsFormulas, warnings } = await generateCsv( - job, - config, - uiSettingsClient, - elasticsearchClient, - cancellationToken, - stream - ); + const { maxSizeReached, csvContainsFormulas, warnings } = await generateCsv( + job, + config, + uiSettingsClient, + elasticsearchClient, + cancellationToken, + stream + ); - // @TODO: Consolidate these one-off warnings into the warnings array (max-size reached and csv contains formulas) - return { - content_type: CONTENT_TYPE_CSV, - max_size_reached: maxSizeReached, - csv_contains_formulas: csvContainsFormulas, - warnings, + // @TODO: Consolidate these one-off warnings into the warnings array (max-size reached and csv contains formulas) + return { + content_type: CONTENT_TYPE_CSV, + max_size_reached: maxSizeReached, + csv_contains_formulas: csvContainsFormulas, + warnings, + }; }; }; -}; diff --git a/x-pack/plugins/reporting/server/export_types/csv/generate_csv/check_cells_for_formulas.test.ts b/x-pack/plugins/reporting/server/export_types/csv/generate_csv/check_cells_for_formulas.test.ts index b8fe85980cac3..021cb6b1349e1 100644 --- a/x-pack/plugins/reporting/server/export_types/csv/generate_csv/check_cells_for_formulas.test.ts +++ b/x-pack/plugins/reporting/server/export_types/csv/generate_csv/check_cells_for_formulas.test.ts @@ -87,7 +87,7 @@ describe(`Check CSV Injected values`, () => { { _doc: 'foo-bar', // need to assert non-string values still return false - value: (nonRow as unknown) as string, + value: nonRow as unknown as string, title: 'nice', }, ['_doc', 'value', 'title'] diff --git a/x-pack/plugins/reporting/server/export_types/csv_searchsource/create_job.ts b/x-pack/plugins/reporting/server/export_types/csv_searchsource/create_job.ts index 36569eb865d11..a2d89be1e0008 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_searchsource/create_job.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_searchsource/create_job.ts @@ -8,10 +8,9 @@ import { CreateJobFn, CreateJobFnFactory } from '../../types'; import { JobParamsCSV, TaskPayloadCSV } from './types'; -export const createJobFnFactory: CreateJobFnFactory< - CreateJobFn -> = function createJobFactoryFn() { - return async function createJob(jobParams) { - return jobParams; +export const createJobFnFactory: CreateJobFnFactory> = + function createJobFactoryFn() { + return async function createJob(jobParams) { + return jobParams; + }; }; -}; diff --git a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.test.ts b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.test.ts index cb5670f6eafd9..f393661e4c490 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.test.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.test.ts @@ -80,15 +80,15 @@ const mockSearchSourceGetFieldDefault = jest.fn().mockImplementation((key: strin } }); -const mockFieldFormatsRegistry = ({ +const mockFieldFormatsRegistry = { deserialize: jest .fn() .mockImplementation(() => ({ id: 'string', convert: jest.fn().mockImplementation(identity) })), -} as unknown) as FieldFormatsRegistry; +} as unknown as FieldFormatsRegistry; beforeEach(async () => { content = ''; - stream = ({ write: jest.fn((chunk) => (content += chunk)) } as unknown) as typeof stream; + stream = { write: jest.fn((chunk) => (content += chunk)) } as unknown as typeof stream; mockEsClient = elasticsearchServiceMock.createScopedClusterClient(); mockDataClient = dataPluginMock.createStartContract().search.asScoped({} as any); mockDataClient.search = mockDataClientSearchDefault; diff --git a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/max_size_string_builder.test.ts b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/max_size_string_builder.test.ts index 27d3719f71f93..25c1007e5e7b2 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/max_size_string_builder.test.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/max_size_string_builder.test.ts @@ -14,7 +14,7 @@ let stream: jest.Mocked; describe('MaxSizeStringBuilder', function () { beforeEach(() => { content = ''; - stream = ({ write: jest.fn((chunk) => (content += chunk)) } as unknown) as typeof stream; + stream = { write: jest.fn((chunk) => (content += chunk)) } as unknown as typeof stream; }); describe('tryAppend', function () { diff --git a/x-pack/plugins/reporting/server/export_types/png/create_job/index.ts b/x-pack/plugins/reporting/server/export_types/png/create_job/index.ts index 68d0d6da5e1d6..f18c0fe2778bd 100644 --- a/x-pack/plugins/reporting/server/export_types/png/create_job/index.ts +++ b/x-pack/plugins/reporting/server/export_types/png/create_job/index.ts @@ -9,15 +9,14 @@ import { CreateJobFn, CreateJobFnFactory } from '../../../types'; import { validateUrls } from '../../common'; import { JobParamsPNG, TaskPayloadPNG } from '../types'; -export const createJobFnFactory: CreateJobFnFactory< - CreateJobFn -> = function createJobFactoryFn() { - return async function createJob(jobParams) { - validateUrls([jobParams.relativeUrl]); +export const createJobFnFactory: CreateJobFnFactory> = + function createJobFactoryFn() { + return async function createJob(jobParams) { + validateUrls([jobParams.relativeUrl]); - return { - ...jobParams, - forceNow: new Date().toISOString(), + return { + ...jobParams, + forceNow: new Date().toISOString(), + }; }; }; -}; diff --git a/x-pack/plugins/reporting/server/export_types/png/execute_job/index.test.ts b/x-pack/plugins/reporting/server/export_types/png/execute_job/index.test.ts index 25f0f8e1004b3..ed4709d501b43 100644 --- a/x-pack/plugins/reporting/server/export_types/png/execute_job/index.test.ts +++ b/x-pack/plugins/reporting/server/export_types/png/execute_job/index.test.ts @@ -25,9 +25,9 @@ let content: string; let mockReporting: ReportingCore; let stream: jest.Mocked; -const cancellationToken = ({ +const cancellationToken = { on: jest.fn(), -} as unknown) as CancellationToken; +} as unknown as CancellationToken; const mockLoggerFactory = { get: jest.fn().mockImplementation(() => ({ @@ -48,7 +48,7 @@ const getBasePayload = (baseObj: any) => baseObj as TaskPayloadPNG; beforeEach(async () => { content = ''; - stream = ({ write: jest.fn((chunk) => (content += chunk)) } as unknown) as typeof stream; + stream = { write: jest.fn((chunk) => (content += chunk)) } as unknown as typeof stream; const mockReportingConfig = createMockConfigSchema({ index: '.reporting-2018.10.10', diff --git a/x-pack/plugins/reporting/server/export_types/png/execute_job/index.ts b/x-pack/plugins/reporting/server/export_types/png/execute_job/index.ts index 34fa745832b8b..fba6ea7b491c6 100644 --- a/x-pack/plugins/reporting/server/export_types/png/execute_job/index.ts +++ b/x-pack/plugins/reporting/server/export_types/png/execute_job/index.ts @@ -20,50 +20,49 @@ import { } from '../../common'; import { TaskPayloadPNG } from '../types'; -export const runTaskFnFactory: RunTaskFnFactory< - RunTaskFn -> = function executeJobFactoryFn(reporting, parentLogger) { - const config = reporting.getConfig(); - const encryptionKey = config.get('encryptionKey'); +export const runTaskFnFactory: RunTaskFnFactory> = + function executeJobFactoryFn(reporting, parentLogger) { + const config = reporting.getConfig(); + const encryptionKey = config.get('encryptionKey'); - return async function runTask(jobId, job, cancellationToken, stream) { - const apmTrans = apm.startTransaction('reporting execute_job png', 'reporting'); - const apmGetAssets = apmTrans?.startSpan('get_assets', 'setup'); - let apmGeneratePng: { end: () => void } | null | undefined; + return async function runTask(jobId, job, cancellationToken, stream) { + const apmTrans = apm.startTransaction('reporting execute_job png', 'reporting'); + const apmGetAssets = apmTrans?.startSpan('get_assets', 'setup'); + let apmGeneratePng: { end: () => void } | null | undefined; - const generatePngObservable = await generatePngObservableFactory(reporting); - const jobLogger = parentLogger.clone([PNG_JOB_TYPE, 'execute', jobId]); - const process$: Rx.Observable = Rx.of(1).pipe( - mergeMap(() => decryptJobHeaders(encryptionKey, job.headers, jobLogger)), - map((decryptedHeaders) => omitBlockedHeaders(decryptedHeaders)), - map((filteredHeaders) => getConditionalHeaders(config, filteredHeaders)), - mergeMap((conditionalHeaders) => { - const urls = getFullUrls(config, job); - const hashUrl = urls[0]; - if (apmGetAssets) apmGetAssets.end(); + const generatePngObservable = await generatePngObservableFactory(reporting); + const jobLogger = parentLogger.clone([PNG_JOB_TYPE, 'execute', jobId]); + const process$: Rx.Observable = Rx.of(1).pipe( + mergeMap(() => decryptJobHeaders(encryptionKey, job.headers, jobLogger)), + map((decryptedHeaders) => omitBlockedHeaders(decryptedHeaders)), + map((filteredHeaders) => getConditionalHeaders(config, filteredHeaders)), + mergeMap((conditionalHeaders) => { + const urls = getFullUrls(config, job); + const hashUrl = urls[0]; + if (apmGetAssets) apmGetAssets.end(); - apmGeneratePng = apmTrans?.startSpan('generate_png_pipeline', 'execute'); - return generatePngObservable( - jobLogger, - hashUrl, - job.browserTimezone, - conditionalHeaders, - job.layout - ); - }), - tap(({ buffer }) => stream.write(buffer)), - map(({ warnings }) => ({ - content_type: 'image/png', - warnings, - })), - catchError((err) => { - jobLogger.error(err); - return Rx.throwError(err); - }), - finalize(() => apmGeneratePng?.end()) - ); + apmGeneratePng = apmTrans?.startSpan('generate_png_pipeline', 'execute'); + return generatePngObservable( + jobLogger, + hashUrl, + job.browserTimezone, + conditionalHeaders, + job.layout + ); + }), + tap(({ buffer }) => stream.write(buffer)), + map(({ warnings }) => ({ + content_type: 'image/png', + warnings, + })), + catchError((err) => { + jobLogger.error(err); + return Rx.throwError(err); + }), + finalize(() => apmGeneratePng?.end()) + ); - const stop$ = Rx.fromEventPattern(cancellationToken.on); - return process$.pipe(takeUntil(stop$)).toPromise(); + const stop$ = Rx.fromEventPattern(cancellationToken.on); + return process$.pipe(takeUntil(stop$)).toPromise(); + }; }; -}; diff --git a/x-pack/plugins/reporting/server/export_types/png_v2/create_job.ts b/x-pack/plugins/reporting/server/export_types/png_v2/create_job.ts index b71f09310681c..ea2c4ed5910c9 100644 --- a/x-pack/plugins/reporting/server/export_types/png_v2/create_job.ts +++ b/x-pack/plugins/reporting/server/export_types/png_v2/create_job.ts @@ -8,14 +8,13 @@ import { CreateJobFn, CreateJobFnFactory } from '../../types'; import { JobParamsPNGV2, TaskPayloadPNGV2 } from './types'; -export const createJobFnFactory: CreateJobFnFactory< - CreateJobFn -> = function createJobFactoryFn() { - return async function createJob({ locatorParams, ...jobParams }) { - return { - ...jobParams, - locatorParams: [locatorParams], - forceNow: new Date().toISOString(), +export const createJobFnFactory: CreateJobFnFactory> = + function createJobFactoryFn() { + return async function createJob({ locatorParams, ...jobParams }) { + return { + ...jobParams, + locatorParams: [locatorParams], + forceNow: new Date().toISOString(), + }; }; }; -}; diff --git a/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.test.ts b/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.test.ts index b654e5cc9377e..e57eab382468c 100644 --- a/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.test.ts +++ b/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.test.ts @@ -26,9 +26,9 @@ let content: string; let mockReporting: ReportingCore; let stream: jest.Mocked; -const cancellationToken = ({ +const cancellationToken = { on: jest.fn(), -} as unknown) as CancellationToken; +} as unknown as CancellationToken; const mockLoggerFactory = { get: jest.fn().mockImplementation(() => ({ @@ -49,7 +49,7 @@ const getBasePayload = (baseObj: unknown) => baseObj as TaskPayloadPNGV2; beforeEach(async () => { content = ''; - stream = ({ write: jest.fn((chunk) => (content += chunk)) } as unknown) as typeof stream; + stream = { write: jest.fn((chunk) => (content += chunk)) } as unknown as typeof stream; const mockReportingConfig = createMockConfigSchema({ index: '.reporting-2018.10.10', diff --git a/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.ts b/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.ts index 5e3b3117f4bb5..5c2cc66d3d3aa 100644 --- a/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.ts +++ b/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.ts @@ -21,51 +21,50 @@ import { import { getFullRedirectAppUrl } from '../common/v2/get_full_redirect_app_url'; import { TaskPayloadPNGV2 } from './types'; -export const runTaskFnFactory: RunTaskFnFactory< - RunTaskFn -> = function executeJobFactoryFn(reporting, parentLogger) { - const config = reporting.getConfig(); - const encryptionKey = config.get('encryptionKey'); +export const runTaskFnFactory: RunTaskFnFactory> = + function executeJobFactoryFn(reporting, parentLogger) { + const config = reporting.getConfig(); + const encryptionKey = config.get('encryptionKey'); - return async function runTask(jobId, job, cancellationToken, stream) { - const apmTrans = apm.startTransaction('reporting execute_job pngV2', 'reporting'); - const apmGetAssets = apmTrans?.startSpan('get_assets', 'setup'); - let apmGeneratePng: { end: () => void } | null | undefined; + return async function runTask(jobId, job, cancellationToken, stream) { + const apmTrans = apm.startTransaction('reporting execute_job pngV2', 'reporting'); + const apmGetAssets = apmTrans?.startSpan('get_assets', 'setup'); + let apmGeneratePng: { end: () => void } | null | undefined; - const generatePngObservable = await generatePngObservableFactory(reporting); - const jobLogger = parentLogger.clone([PNG_JOB_TYPE_V2, 'execute', jobId]); - const process$: Rx.Observable = Rx.of(1).pipe( - mergeMap(() => decryptJobHeaders(encryptionKey, job.headers, jobLogger)), - map((decryptedHeaders) => omitBlockedHeaders(decryptedHeaders)), - map((filteredHeaders) => getConditionalHeaders(config, filteredHeaders)), - mergeMap((conditionalHeaders) => { - const url = getFullRedirectAppUrl(config, job.spaceId); - const [locatorParams] = job.locatorParams.map(setForceNow(job.forceNow)); + const generatePngObservable = await generatePngObservableFactory(reporting); + const jobLogger = parentLogger.clone([PNG_JOB_TYPE_V2, 'execute', jobId]); + const process$: Rx.Observable = Rx.of(1).pipe( + mergeMap(() => decryptJobHeaders(encryptionKey, job.headers, jobLogger)), + map((decryptedHeaders) => omitBlockedHeaders(decryptedHeaders)), + map((filteredHeaders) => getConditionalHeaders(config, filteredHeaders)), + mergeMap((conditionalHeaders) => { + const url = getFullRedirectAppUrl(config, job.spaceId); + const [locatorParams] = job.locatorParams.map(setForceNow(job.forceNow)); - apmGetAssets?.end(); + apmGetAssets?.end(); - apmGeneratePng = apmTrans?.startSpan('generate_png_pipeline', 'execute'); - return generatePngObservable( - jobLogger, - [url, locatorParams], - job.browserTimezone, - conditionalHeaders, - job.layout - ); - }), - tap(({ buffer }) => stream.write(buffer)), - map(({ warnings }) => ({ - content_type: 'image/png', - warnings, - })), - catchError((err) => { - jobLogger.error(err); - return Rx.throwError(err); - }), - finalize(() => apmGeneratePng?.end()) - ); + apmGeneratePng = apmTrans?.startSpan('generate_png_pipeline', 'execute'); + return generatePngObservable( + jobLogger, + [url, locatorParams], + job.browserTimezone, + conditionalHeaders, + job.layout + ); + }), + tap(({ buffer }) => stream.write(buffer)), + map(({ warnings }) => ({ + content_type: 'image/png', + warnings, + })), + catchError((err) => { + jobLogger.error(err); + return Rx.throwError(err); + }), + finalize(() => apmGeneratePng?.end()) + ); - const stop$ = Rx.fromEventPattern(cancellationToken.on); - return process$.pipe(takeUntil(stop$)).toPromise(); + const stop$ = Rx.fromEventPattern(cancellationToken.on); + return process$.pipe(takeUntil(stop$)).toPromise(); + }; }; -}; diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.test.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.test.ts index 98c00287aa196..02f9c93929ea1 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.test.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.test.ts @@ -21,9 +21,9 @@ let content: string; let mockReporting: ReportingCore; let stream: jest.Mocked; -const cancellationToken = ({ +const cancellationToken = { on: jest.fn(), -} as unknown) as CancellationToken; +} as unknown as CancellationToken; const mockLoggerFactory = { get: jest.fn().mockImplementation(() => ({ @@ -44,7 +44,7 @@ const getBasePayload = (baseObj: any) => baseObj as TaskPayloadPDF; beforeEach(async () => { content = ''; - stream = ({ write: jest.fn((chunk) => (content += chunk)) } as unknown) as typeof stream; + stream = { write: jest.fn((chunk) => (content += chunk)) } as unknown as typeof stream; const reportingConfig = { 'server.basePath': '/sbp', diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.ts index 22e4a588e976d..f4b95e0e20e51 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.ts @@ -21,63 +21,62 @@ import { import { generatePdfObservableFactory } from '../lib/generate_pdf'; import { TaskPayloadPDF } from '../types'; -export const runTaskFnFactory: RunTaskFnFactory< - RunTaskFn -> = function executeJobFactoryFn(reporting, parentLogger) { - const config = reporting.getConfig(); - const encryptionKey = config.get('encryptionKey'); +export const runTaskFnFactory: RunTaskFnFactory> = + function executeJobFactoryFn(reporting, parentLogger) { + const config = reporting.getConfig(); + const encryptionKey = config.get('encryptionKey'); - return async function runTask(jobId, job, cancellationToken, stream) { - const jobLogger = parentLogger.clone([PDF_JOB_TYPE, 'execute-job', jobId]); - const apmTrans = apm.startTransaction('reporting execute_job pdf', 'reporting'); - const apmGetAssets = apmTrans?.startSpan('get_assets', 'setup'); - let apmGeneratePdf: { end: () => void } | null | undefined; + return async function runTask(jobId, job, cancellationToken, stream) { + const jobLogger = parentLogger.clone([PDF_JOB_TYPE, 'execute-job', jobId]); + const apmTrans = apm.startTransaction('reporting execute_job pdf', 'reporting'); + const apmGetAssets = apmTrans?.startSpan('get_assets', 'setup'); + let apmGeneratePdf: { end: () => void } | null | undefined; - const generatePdfObservable = await generatePdfObservableFactory(reporting); + const generatePdfObservable = await generatePdfObservableFactory(reporting); - const process$: Rx.Observable = Rx.of(1).pipe( - mergeMap(() => decryptJobHeaders(encryptionKey, job.headers, jobLogger)), - map((decryptedHeaders) => omitBlockedHeaders(decryptedHeaders)), - map((filteredHeaders) => getConditionalHeaders(config, filteredHeaders)), - mergeMap((conditionalHeaders) => - getCustomLogo(reporting, conditionalHeaders, job.spaceId, jobLogger) - ), - mergeMap(({ logo, conditionalHeaders }) => { - const urls = getFullUrls(config, job); + const process$: Rx.Observable = Rx.of(1).pipe( + mergeMap(() => decryptJobHeaders(encryptionKey, job.headers, jobLogger)), + map((decryptedHeaders) => omitBlockedHeaders(decryptedHeaders)), + map((filteredHeaders) => getConditionalHeaders(config, filteredHeaders)), + mergeMap((conditionalHeaders) => + getCustomLogo(reporting, conditionalHeaders, job.spaceId, jobLogger) + ), + mergeMap(({ logo, conditionalHeaders }) => { + const urls = getFullUrls(config, job); - const { browserTimezone, layout, title } = job; - apmGetAssets?.end(); + const { browserTimezone, layout, title } = job; + apmGetAssets?.end(); - apmGeneratePdf = apmTrans?.startSpan('generate_pdf_pipeline', 'execute'); - return generatePdfObservable( - jobLogger, - title, - urls, - browserTimezone, - conditionalHeaders, - layout, - logo - ); - }), - tap(({ buffer }) => { - apmGeneratePdf?.end(); - if (buffer) { - stream.write(buffer); - } - }), - map(({ warnings }) => ({ - content_type: 'application/pdf', - warnings, - })), - catchError((err) => { - jobLogger.error(err); - return Rx.throwError(err); - }) - ); + apmGeneratePdf = apmTrans?.startSpan('generate_pdf_pipeline', 'execute'); + return generatePdfObservable( + jobLogger, + title, + urls, + browserTimezone, + conditionalHeaders, + layout, + logo + ); + }), + tap(({ buffer }) => { + apmGeneratePdf?.end(); + if (buffer) { + stream.write(buffer); + } + }), + map(({ warnings }) => ({ + content_type: 'application/pdf', + warnings, + })), + catchError((err) => { + jobLogger.error(err); + return Rx.throwError(err); + }) + ); - const stop$ = Rx.fromEventPattern(cancellationToken.on); + const stop$ = Rx.fromEventPattern(cancellationToken.on); - apmTrans?.end(); - return process$.pipe(takeUntil(stop$)).toPromise(); + apmTrans?.end(); + return process$.pipe(takeUntil(stop$)).toPromise(); + }; }; -}; diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/create_job.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/create_job.ts index 9effb4a29fe55..427b29765080e 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/create_job.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/create_job.ts @@ -8,13 +8,12 @@ import { CreateJobFn, CreateJobFnFactory } from '../../types'; import { JobParamsPDFV2, TaskPayloadPDFV2 } from './types'; -export const createJobFnFactory: CreateJobFnFactory< - CreateJobFn -> = function createJobFactoryFn() { - return async function createJob(jobParams) { - return { - ...jobParams, - forceNow: new Date().toISOString(), +export const createJobFnFactory: CreateJobFnFactory> = + function createJobFactoryFn() { + return async function createJob(jobParams) { + return { + ...jobParams, + forceNow: new Date().toISOString(), + }; }; }; -}; diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.test.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.test.ts index 4d398956faba4..197bd3866b8f6 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.test.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.test.ts @@ -22,9 +22,9 @@ let content: string; let mockReporting: ReportingCore; let stream: jest.Mocked; -const cancellationToken = ({ +const cancellationToken = { on: jest.fn(), -} as unknown) as CancellationToken; +} as unknown as CancellationToken; const mockLoggerFactory = { get: jest.fn().mockImplementation(() => ({ @@ -49,7 +49,7 @@ const getBasePayload = (baseObj: any) => beforeEach(async () => { content = ''; - stream = ({ write: jest.fn((chunk) => (content += chunk)) } as unknown) as typeof stream; + stream = { write: jest.fn((chunk) => (content += chunk)) } as unknown as typeof stream; const reportingConfig = { 'server.basePath': '/sbp', diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.ts index f2cf8026c901e..e44f5e98fa4fe 100644 --- a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.ts +++ b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.ts @@ -21,63 +21,62 @@ import { import { generatePdfObservableFactory } from './lib/generate_pdf'; import { TaskPayloadPDFV2 } from './types'; -export const runTaskFnFactory: RunTaskFnFactory< - RunTaskFn -> = function executeJobFactoryFn(reporting, parentLogger) { - const config = reporting.getConfig(); - const encryptionKey = config.get('encryptionKey'); +export const runTaskFnFactory: RunTaskFnFactory> = + function executeJobFactoryFn(reporting, parentLogger) { + const config = reporting.getConfig(); + const encryptionKey = config.get('encryptionKey'); - return async function runTask(jobId, job, cancellationToken, stream) { - const jobLogger = parentLogger.clone([PDF_JOB_TYPE_V2, 'execute-job', jobId]); - const apmTrans = apm.startTransaction('reporting execute_job pdf_v2', 'reporting'); - const apmGetAssets = apmTrans?.startSpan('get_assets', 'setup'); - let apmGeneratePdf: { end: () => void } | null | undefined; + return async function runTask(jobId, job, cancellationToken, stream) { + const jobLogger = parentLogger.clone([PDF_JOB_TYPE_V2, 'execute-job', jobId]); + const apmTrans = apm.startTransaction('reporting execute_job pdf_v2', 'reporting'); + const apmGetAssets = apmTrans?.startSpan('get_assets', 'setup'); + let apmGeneratePdf: { end: () => void } | null | undefined; - const generatePdfObservable = await generatePdfObservableFactory(reporting); + const generatePdfObservable = await generatePdfObservableFactory(reporting); - const process$: Rx.Observable = Rx.of(1).pipe( - mergeMap(() => decryptJobHeaders(encryptionKey, job.headers, jobLogger)), - map((decryptedHeaders) => omitBlockedHeaders(decryptedHeaders)), - map((filteredHeaders) => getConditionalHeaders(config, filteredHeaders)), - mergeMap((conditionalHeaders) => - getCustomLogo(reporting, conditionalHeaders, job.spaceId, jobLogger) - ), - mergeMap(({ logo, conditionalHeaders }) => { - const { browserTimezone, layout, title, locatorParams } = job; - apmGetAssets?.end(); + const process$: Rx.Observable = Rx.of(1).pipe( + mergeMap(() => decryptJobHeaders(encryptionKey, job.headers, jobLogger)), + map((decryptedHeaders) => omitBlockedHeaders(decryptedHeaders)), + map((filteredHeaders) => getConditionalHeaders(config, filteredHeaders)), + mergeMap((conditionalHeaders) => + getCustomLogo(reporting, conditionalHeaders, job.spaceId, jobLogger) + ), + mergeMap(({ logo, conditionalHeaders }) => { + const { browserTimezone, layout, title, locatorParams } = job; + apmGetAssets?.end(); - apmGeneratePdf = apmTrans?.startSpan('generate_pdf_pipeline', 'execute'); - return generatePdfObservable( - jobLogger, - job, - title, - locatorParams.map(setForceNow(job.forceNow)), - browserTimezone, - conditionalHeaders, - layout, - logo - ); - }), - tap(({ buffer, warnings }) => { - apmGeneratePdf?.end(); + apmGeneratePdf = apmTrans?.startSpan('generate_pdf_pipeline', 'execute'); + return generatePdfObservable( + jobLogger, + job, + title, + locatorParams.map(setForceNow(job.forceNow)), + browserTimezone, + conditionalHeaders, + layout, + logo + ); + }), + tap(({ buffer, warnings }) => { + apmGeneratePdf?.end(); - if (buffer) { - stream.write(buffer); - } - }), - map(({ warnings }) => ({ - content_type: 'application/pdf', - warnings, - })), - catchError((err) => { - jobLogger.error(err); - return Rx.throwError(err); - }) - ); + if (buffer) { + stream.write(buffer); + } + }), + map(({ warnings }) => ({ + content_type: 'application/pdf', + warnings, + })), + catchError((err) => { + jobLogger.error(err); + return Rx.throwError(err); + }) + ); - const stop$ = Rx.fromEventPattern(cancellationToken.on); + const stop$ = Rx.fromEventPattern(cancellationToken.on); - apmTrans?.end(); - return process$.pipe(takeUntil(stop$)).toPromise(); + apmTrans?.end(); + return process$.pipe(takeUntil(stop$)).toPromise(); + }; }; -}; diff --git a/x-pack/plugins/reporting/server/lib/check_license.test.ts b/x-pack/plugins/reporting/server/lib/check_license.test.ts index 1cabf44b8664f..59743ce62639a 100644 --- a/x-pack/plugins/reporting/server/lib/check_license.test.ts +++ b/x-pack/plugins/reporting/server/lib/check_license.test.ts @@ -14,16 +14,16 @@ describe('check_license', () => { let license: ILicense; beforeEach(() => { - exportTypesRegistry = ({ + exportTypesRegistry = { getAll: () => [], - } as unknown) as ExportTypesRegistry; + } as unknown as ExportTypesRegistry; }); describe('license information is not ready', () => { beforeEach(() => { - exportTypesRegistry = ({ + exportTypesRegistry = { getAll: () => [{ id: 'csv' }], - } as unknown) as ExportTypesRegistry; + } as unknown as ExportTypesRegistry; }); it('should set management.showLinks to true', () => { @@ -52,9 +52,9 @@ describe('check_license', () => { license = { type: undefined, } as ILicense; - exportTypesRegistry = ({ + exportTypesRegistry = { getAll: () => [{ id: 'csv' }], - } as unknown) as ExportTypesRegistry; + } as unknown as ExportTypesRegistry; }); it('should set management.showLinks to true', () => { @@ -86,9 +86,9 @@ describe('check_license', () => { describe('& license is > basic', () => { beforeEach(() => { license.type = 'gold'; - exportTypesRegistry = ({ + exportTypesRegistry = { getAll: () => [{ id: 'pdf', validLicenses: ['gold'], jobType: 'printable_pdf' }], - } as unknown) as ExportTypesRegistry; + } as unknown as ExportTypesRegistry; }); describe('& license is active', () => { @@ -147,9 +147,9 @@ describe('check_license', () => { describe('& license is basic', () => { beforeEach(() => { license.type = 'basic'; - exportTypesRegistry = ({ + exportTypesRegistry = { getAll: () => [{ id: 'pdf', validLicenses: ['gold'], jobType: 'printable_pdf' }], - } as unknown) as ExportTypesRegistry; + } as unknown as ExportTypesRegistry; }); describe('& license is active', () => { diff --git a/x-pack/plugins/reporting/server/lib/screenshots/observable.ts b/x-pack/plugins/reporting/server/lib/screenshots/observable.ts index d6b22189b3405..1c599f2189cb5 100644 --- a/x-pack/plugins/reporting/server/lib/screenshots/observable.ts +++ b/x-pack/plugins/reporting/server/lib/screenshots/observable.ts @@ -117,23 +117,21 @@ export function getScreenshots$( return setup$.pipe( takeUntil(exit$), - mergeMap( - async (data: ScreenSetupData): Promise => { - checkPageIsOpen(driver); // re-check that the browser has not closed + mergeMap(async (data: ScreenSetupData): Promise => { + checkPageIsOpen(driver); // re-check that the browser has not closed - const elements = data.elementsPositionAndAttributes - ? data.elementsPositionAndAttributes - : getDefaultElementPosition(layout.getViewport(1)); - const screenshots = await getScreenshots(driver, elements, logger); - const { timeRange, error: setupError } = data; - return { - timeRange, - screenshots, - error: setupError, - elementsPositionAndAttributes: elements, - }; - } - ) + const elements = data.elementsPositionAndAttributes + ? data.elementsPositionAndAttributes + : getDefaultElementPosition(layout.getViewport(1)); + const screenshots = await getScreenshots(driver, elements, logger); + const { timeRange, error: setupError } = data; + return { + timeRange, + screenshots, + error: setupError, + elementsPositionAndAttributes: elements, + }; + }) ); }), take(urlsOrUrlLocatorTuples.length), diff --git a/x-pack/plugins/reporting/server/lib/tasks/execute_report.test.ts b/x-pack/plugins/reporting/server/lib/tasks/execute_report.test.ts index 99045050120c1..df662d963d0ed 100644 --- a/x-pack/plugins/reporting/server/lib/tasks/execute_report.test.ts +++ b/x-pack/plugins/reporting/server/lib/tasks/execute_report.test.ts @@ -54,20 +54,20 @@ describe('Execute Report Task', () => { const task = new ExecuteReportTask(mockReporting, configType, logger); const taskDef = task.getTaskDefinition(); - const taskRunner = taskDef.createTaskRunner(({ + const taskRunner = taskDef.createTaskRunner({ taskInstance: { id: 'random-task-id', params: { index: 'cool-reporting-index', id: 'cool-reporting-id' }, }, - } as unknown) as RunContext); + } as unknown as RunContext); expect(taskRunner).toHaveProperty('run'); expect(taskRunner).toHaveProperty('cancel'); }); it('Max Concurrency is 0 if pollEnabled is false', () => { - const queueConfig = ({ + const queueConfig = { queue: { pollEnabled: false, timeout: 55000 }, - } as unknown) as ReportingConfigType['queue']; + } as unknown as ReportingConfigType['queue']; const task = new ExecuteReportTask(mockReporting, { ...configType, ...queueConfig }, logger); expect(task.getStatus()).toBe('uninitialized'); diff --git a/x-pack/plugins/reporting/server/lib/tasks/monitor_report.test.ts b/x-pack/plugins/reporting/server/lib/tasks/monitor_report.test.ts index fb9b49ab9e265..d737c7032855b 100644 --- a/x-pack/plugins/reporting/server/lib/tasks/monitor_report.test.ts +++ b/x-pack/plugins/reporting/server/lib/tasks/monitor_report.test.ts @@ -53,12 +53,12 @@ describe('Execute Report Task', () => { const task = new MonitorReportsTask(mockReporting, configType, logger); const taskDef = task.getTaskDefinition(); - const taskRunner = taskDef.createTaskRunner(({ + const taskRunner = taskDef.createTaskRunner({ taskInstance: { id: 'random-task-id', params: { index: 'cool-reporting-index', id: 'cool-reporting-id' }, }, - } as unknown) as RunContext); + } as unknown as RunContext); expect(taskRunner).toHaveProperty('run'); expect(taskRunner).toHaveProperty('cancel'); }); diff --git a/x-pack/plugins/reporting/server/plugin.test.ts b/x-pack/plugins/reporting/server/plugin.test.ts index ce3b8aabcaa8d..9a2acc4a51202 100644 --- a/x-pack/plugins/reporting/server/plugin.test.ts +++ b/x-pack/plugins/reporting/server/plugin.test.ts @@ -36,16 +36,16 @@ describe('Reporting Plugin', () => { initContext = coreMock.createPluginInitializerContext(configSchema); coreSetup = coreMock.createSetup(configSchema); coreStart = coreMock.createStart(); - pluginSetup = ({ + pluginSetup = { licensing: {}, features: featuresPluginMock.createSetup(), usageCollection: { makeUsageCollector: jest.fn(), registerCollector: jest.fn(), }, - taskManager: ({ + taskManager: { registerTaskDefinitions: jest.fn(), - } as unknown) as TaskManagerSetupContract, + } as unknown as TaskManagerSetupContract, security: { authc: { getCurrentUser: () => ({ @@ -55,12 +55,12 @@ describe('Reporting Plugin', () => { }), }, }, - } as unknown) as any; - pluginStart = ({ + } as unknown as any; + pluginStart = { data: { fieldFormats: {}, }, - } as unknown) as any; + } as unknown as any; }); it('has a sync setup process', () => { diff --git a/x-pack/plugins/reporting/server/plugin.ts b/x-pack/plugins/reporting/server/plugin.ts index 0090afb855ee9..07d61ff1630fc 100644 --- a/x-pack/plugins/reporting/server/plugin.ts +++ b/x-pack/plugins/reporting/server/plugin.ts @@ -24,7 +24,8 @@ import type { import { registerReportingUsageCollector } from './usage'; export class ReportingPlugin - implements Plugin { + implements Plugin +{ private logger: LevelLogger; private reportingCore?: ReportingCore; diff --git a/x-pack/plugins/reporting/server/routes/deprecations.ts b/x-pack/plugins/reporting/server/routes/deprecations.ts index d1d8302e394c3..4a519b7c199f8 100644 --- a/x-pack/plugins/reporting/server/routes/deprecations.ts +++ b/x-pack/plugins/reporting/server/routes/deprecations.ts @@ -37,11 +37,11 @@ export const registerDeprecationsRoutes = (reporting: ReportingCore, logger: Log const { body } = await elasticsearch.client.asCurrentUser.security.hasPrivileges({ body: { index: [ - ({ + { privileges: ['manage'], // required to do anything with the reporting indices names: [store.getReportingIndexPattern()], allow_restricted_indices: true, - } as unknown) as SecurityHasPrivilegesIndexPrivilegesCheck, // TODO: Needed until `allow_restricted_indices` is added to the types. + } as unknown as SecurityHasPrivilegesIndexPrivilegesCheck, // TODO: Needed until `allow_restricted_indices` is added to the types. ], }, }); diff --git a/x-pack/plugins/reporting/server/routes/diagnostic/browser.ts b/x-pack/plugins/reporting/server/routes/diagnostic/browser.ts index 268cabd9be55f..7793fc658c535 100644 --- a/x-pack/plugins/reporting/server/routes/diagnostic/browser.ts +++ b/x-pack/plugins/reporting/server/routes/diagnostic/browser.ts @@ -19,8 +19,7 @@ const logsToHelpMap = { { defaultMessage: `The browser couldn't start properly due to missing system dependencies. Please see {url}`, values: { - url: - 'https://www.elastic.co/guide/en/kibana/current/reporting-troubleshooting.html#reporting-troubleshooting-system-dependencies', + url: 'https://www.elastic.co/guide/en/kibana/current/reporting-troubleshooting.html#reporting-troubleshooting-system-dependencies', }, } ), @@ -30,8 +29,7 @@ const logsToHelpMap = { { defaultMessage: `The browser couldn't locate a default font. Please see {url} to fix this issue.`, values: { - url: - 'https://www.elastic.co/guide/en/kibana/current/reporting-troubleshooting.html#reporting-troubleshooting-system-dependencies', + url: 'https://www.elastic.co/guide/en/kibana/current/reporting-troubleshooting.html#reporting-troubleshooting-system-dependencies', }, } ), @@ -39,8 +37,7 @@ const logsToHelpMap = { 'No usable sandbox': i18n.translate('xpack.reporting.diagnostic.noUsableSandbox', { defaultMessage: `Unable to use Chromium sandbox. This can be disabled at your own risk with 'xpack.reporting.capture.browser.chromium.disableSandbox'. Please see {url}`, values: { - url: - 'https://www.elastic.co/guide/en/kibana/current/reporting-troubleshooting.html#reporting-troubleshooting-sandbox-dependency', + url: 'https://www.elastic.co/guide/en/kibana/current/reporting-troubleshooting.html#reporting-troubleshooting-sandbox-dependency', }, }), }; diff --git a/x-pack/plugins/reporting/server/routes/lib/authorized_user_pre_routing.test.ts b/x-pack/plugins/reporting/server/routes/lib/authorized_user_pre_routing.test.ts index 0cff81539e52e..e2ff52036764b 100644 --- a/x-pack/plugins/reporting/server/routes/lib/authorized_user_pre_routing.test.ts +++ b/x-pack/plugins/reporting/server/routes/lib/authorized_user_pre_routing.test.ts @@ -17,9 +17,9 @@ let mockCore: ReportingCore; const mockReportingConfig = createMockConfigSchema({ roles: { enabled: false } }); const getMockContext = () => - (({ + ({ core: coreMock.createRequestHandlerContext(), - } as unknown) as ReportingRequestHandlerContext); + } as unknown as ReportingRequestHandlerContext); const getMockRequest = () => ({ @@ -28,11 +28,11 @@ const getMockRequest = () => } as KibanaRequest); const getMockResponseFactory = () => - (({ + ({ ...httpServerMock.createResponseFactory(), forbidden: (obj: unknown) => obj, unauthorized: (obj: unknown) => obj, - } as unknown) as KibanaResponseFactory); + } as unknown as KibanaResponseFactory); describe('authorized_user_pre_routing', function () { beforeEach(async () => { @@ -41,11 +41,11 @@ describe('authorized_user_pre_routing', function () { it('should return from handler with a "false" user when security plugin is not found', async function () { mockCore.getPluginSetupDeps = () => - (({ + ({ // @ts-ignore ...mockCore.pluginSetupDeps, security: undefined, // disable security - } as unknown) as ReportingInternalSetup); + } as unknown as ReportingInternalSetup); const mockResponseFactory = httpServerMock.createResponseFactory() as KibanaResponseFactory; let handlerCalled = false; @@ -60,7 +60,7 @@ describe('authorized_user_pre_routing', function () { it('should return from handler with a "false" user when security is disabled', async function () { mockCore.getPluginSetupDeps = () => - (({ + ({ // @ts-ignore ...mockCore.pluginSetupDeps, security: { @@ -68,7 +68,7 @@ describe('authorized_user_pre_routing', function () { isEnabled: () => false, }, }, // disable security - } as unknown) as ReportingInternalSetup); + } as unknown as ReportingInternalSetup); const mockResponseFactory = httpServerMock.createResponseFactory() as KibanaResponseFactory; let handlerCalled = false; @@ -83,14 +83,14 @@ describe('authorized_user_pre_routing', function () { it('should return with 401 when security is enabled and the request is unauthenticated', async function () { mockCore.getPluginSetupDeps = () => - (({ + ({ // @ts-ignore ...mockCore.pluginSetupDeps, security: { license: { isEnabled: () => true }, authc: { getCurrentUser: () => null }, }, - } as unknown) as ReportingInternalSetup); + } as unknown as ReportingInternalSetup); const mockHandler = () => { throw new Error('Handler callback should not be called'); }; @@ -115,14 +115,14 @@ describe('authorized_user_pre_routing', function () { it(`should return with 403 when security is enabled but user doesn't have the allowed role`, async function () { mockCore.getPluginSetupDeps = () => - (({ + ({ // @ts-ignore ...mockCore.pluginSetupDeps, security: { license: { isEnabled: () => true }, authc: { getCurrentUser: () => ({ username: 'friendlyuser', roles: ['cowboy'] }) }, }, - } as unknown) as ReportingInternalSetup); + } as unknown as ReportingInternalSetup); const mockResponseFactory = getMockResponseFactory(); const mockHandler = () => { @@ -139,7 +139,7 @@ describe('authorized_user_pre_routing', function () { it('should return from handler when security is enabled and user has explicitly allowed role', function (done) { mockCore.getPluginSetupDeps = () => - (({ + ({ // @ts-ignore ...mockCore.pluginSetupDeps, security: { @@ -148,7 +148,7 @@ describe('authorized_user_pre_routing', function () { getCurrentUser: () => ({ username: 'friendlyuser', roles: ['reporting_user'] }), }, }, - } as unknown) as ReportingInternalSetup); + } as unknown as ReportingInternalSetup); const mockResponseFactory = getMockResponseFactory(); authorizedUserPreRouting(mockCore, (user) => { diff --git a/x-pack/plugins/reporting/server/routes/lib/get_document_payload.test.ts b/x-pack/plugins/reporting/server/routes/lib/get_document_payload.test.ts index 1d84534879277..d2ed0b86e2cce 100644 --- a/x-pack/plugins/reporting/server/routes/lib/get_document_payload.test.ts +++ b/x-pack/plugins/reporting/server/routes/lib/get_document_payload.test.ts @@ -34,9 +34,9 @@ describe('getDocumentPayload', () => { }) as ContentStream ); - (jobsQueryFactory as jest.MockedFunction).mockReturnValue(({ + (jobsQueryFactory as jest.MockedFunction).mockReturnValue({ getError: jest.fn(async () => 'Some error'), - } as unknown) as ReturnType); + } as unknown as ReturnType); }); describe('when the report is completed', () => { diff --git a/x-pack/plugins/reporting/server/routes/lib/job_response_handler.test.ts b/x-pack/plugins/reporting/server/routes/lib/job_response_handler.test.ts index 3c803b3c39fcc..221f21c75b58c 100644 --- a/x-pack/plugins/reporting/server/routes/lib/job_response_handler.test.ts +++ b/x-pack/plugins/reporting/server/routes/lib/job_response_handler.test.ts @@ -30,26 +30,26 @@ beforeEach(async () => { const schema = createMockConfigSchema(); core = await createMockReportingCore(schema); getDocumentPayload = jest.fn(); - jobsQuery = ({ + jobsQuery = { delete: jest.fn(), get: jest.fn(), - } as unknown) as typeof jobsQuery; - response = ({ + } as unknown as typeof jobsQuery; + response = { badRequest: jest.fn(), custom: jest.fn(), customError: jest.fn(), notFound: jest.fn(), ok: jest.fn(), unauthorized: jest.fn(), - } as unknown) as typeof response; + } as unknown as typeof response; write = jest.fn((_chunk, _encoding, callback) => callback()); (getContentStream as jest.MockedFunction).mockResolvedValue( new Writable({ write }) as ContentStream ); - (getDocumentPayloadFactory as jest.MockedFunction< - typeof getDocumentPayloadFactory - >).mockReturnValue(getDocumentPayload); + ( + getDocumentPayloadFactory as jest.MockedFunction + ).mockReturnValue(getDocumentPayload); (jobsQueryFactory as jest.MockedFunction).mockReturnValue(jobsQuery); }); @@ -143,9 +143,9 @@ describe('downloadJobResponseHandler', () => { jobsQuery.get.mockResolvedValueOnce({ jobtype: PDF_JOB_TYPE } as UnwrapPromise< ReturnType >); - getDocumentPayload.mockResolvedValueOnce(({ + getDocumentPayload.mockResolvedValueOnce({ contentType: 'image/jpeg', - } as unknown) as UnwrapPromise>); + } as unknown as UnwrapPromise>); await downloadJobResponseHandler( core, response, @@ -161,14 +161,14 @@ describe('downloadJobResponseHandler', () => { jobsQuery.get.mockResolvedValueOnce({ jobtype: PDF_JOB_TYPE } as UnwrapPromise< ReturnType >); - getDocumentPayload.mockResolvedValueOnce(({ + getDocumentPayload.mockResolvedValueOnce({ content: new Readable(), contentType: 'application/pdf', headers: { 'Content-Length': 10, }, statusCode: 200, - } as unknown) as UnwrapPromise>); + } as unknown as UnwrapPromise>); await downloadJobResponseHandler( core, response, @@ -191,12 +191,12 @@ describe('downloadJobResponseHandler', () => { jobsQuery.get.mockResolvedValueOnce({ jobtype: PDF_JOB_TYPE } as UnwrapPromise< ReturnType >); - getDocumentPayload.mockResolvedValueOnce(({ + getDocumentPayload.mockResolvedValueOnce({ content: 'Error message.', contentType: 'application/json', headers: {}, statusCode: 500, - } as unknown) as UnwrapPromise>); + } as unknown as UnwrapPromise>); await downloadJobResponseHandler( core, response, diff --git a/x-pack/plugins/reporting/server/routes/lib/request_handler.test.ts b/x-pack/plugins/reporting/server/routes/lib/request_handler.test.ts index 3e7aae35c07fd..622097f8dbd32 100644 --- a/x-pack/plugins/reporting/server/routes/lib/request_handler.test.ts +++ b/x-pack/plugins/reporting/server/routes/lib/request_handler.test.ts @@ -26,9 +26,9 @@ jest.mock('../../lib/crypto', () => ({ })); const getMockContext = () => - (({ + ({ core: coreMock.createRequestHandlerContext(), - } as unknown) as ReportingRequestHandlerContext); + } as unknown as ReportingRequestHandlerContext); const getMockRequest = () => ({ @@ -37,11 +37,11 @@ const getMockRequest = () => } as KibanaRequest); const getMockResponseFactory = () => - (({ + ({ ...httpServerMock.createResponseFactory(), forbidden: (obj: unknown) => obj, unauthorized: (obj: unknown) => obj, - } as unknown) as KibanaResponseFactory); + } as unknown as KibanaResponseFactory); const mockLogger = createMockLevelLogger(); @@ -64,13 +64,13 @@ describe('Handle request to generate', () => { beforeEach(async () => { reportingCore = await createMockReportingCore(createMockConfigSchema({})); reportingCore.getStore = () => - Promise.resolve(({ + Promise.resolve({ addReport: jest .fn() .mockImplementation( (report) => new Report({ ...report, _index: '.reporting-foo-index-234' }) ), - } as unknown) as ReportingStore); + } as unknown as ReportingStore); mockRequest = getMockRequest(); mockResponseFactory = getMockResponseFactory(); @@ -140,7 +140,7 @@ describe('Handle request to generate', () => { }); test('provides a default kibana version field for older POST URLs', async () => { - ((mockJobParams as unknown) as { version?: string }).version = undefined; + (mockJobParams as unknown as { version?: string }).version = undefined; const report = await requestHandler.enqueueJob('printablePdf', mockJobParams); const { _id, created_at: _created_at, ...snapObj } = report; @@ -170,10 +170,10 @@ describe('Handle request to generate', () => { }); test('generates the download path', async () => { - const response = ((await requestHandler.handleGenerateRequest( + const response = (await requestHandler.handleGenerateRequest( 'csv', mockJobParams - )) as unknown) as { body: { job: ReportApiJSON } }; + )) as unknown as { body: { job: ReportApiJSON } }; const { id, created_at: _created_at, ...snapObj } = response.body.job; expect(snapObj).toMatchInlineSnapshot(` Object { diff --git a/x-pack/plugins/reporting/server/routes/management/jobs.test.ts b/x-pack/plugins/reporting/server/routes/management/jobs.test.ts index c14976f616c7b..02a0ddc94a043 100644 --- a/x-pack/plugins/reporting/server/routes/management/jobs.test.ts +++ b/x-pack/plugins/reporting/server/routes/management/jobs.test.ts @@ -131,7 +131,7 @@ describe('GET /api/reporting/jobs/download', () => { it('fails on unauthenticated users', async () => { // @ts-ignore - core.pluginSetupDeps = ({ + core.pluginSetupDeps = { // @ts-ignore ...core.pluginSetupDeps, security: { @@ -142,7 +142,7 @@ describe('GET /api/reporting/jobs/download', () => { getCurrentUser: () => undefined, }, }, - } as unknown) as ReportingInternalSetup; + } as unknown as ReportingInternalSetup; registerJobInfoRoutes(core); await server.start(); @@ -305,7 +305,7 @@ describe('GET /api/reporting/jobs/download', () => { const deprecatedConfig = createMockConfigSchema({ roles: { enabled: true } }); core = await createMockReportingCore(deprecatedConfig, mockSetupDeps); // @ts-ignore - core.pluginSetupDeps = ({ + core.pluginSetupDeps = { // @ts-ignore ...core.pluginSetupDeps, security: { @@ -320,7 +320,7 @@ describe('GET /api/reporting/jobs/download', () => { }), }, }, - } as unknown) as ReportingInternalSetup; + } as unknown as ReportingInternalSetup; registerJobInfoRoutes(core); await server.start(); diff --git a/x-pack/plugins/reporting/server/services.ts b/x-pack/plugins/reporting/server/services.ts index 5851a7066266d..2184658865920 100644 --- a/x-pack/plugins/reporting/server/services.ts +++ b/x-pack/plugins/reporting/server/services.ts @@ -8,6 +8,5 @@ import { createGetterSetter } from '../../../../src/plugins/kibana_utils/server'; import { PluginStart as DataPluginStart } from '../../../../src/plugins/data/server'; -export const [getFieldFormats, setFieldFormats] = createGetterSetter< - DataPluginStart['fieldFormats'] ->('FieldFormats'); +export const [getFieldFormats, setFieldFormats] = + createGetterSetter('FieldFormats'); diff --git a/x-pack/plugins/reporting/server/test_helpers/create_mock_browserdriverfactory.ts b/x-pack/plugins/reporting/server/test_helpers/create_mock_browserdriverfactory.ts index 24aa068eca0b1..8cd0a63f860e8 100644 --- a/x-pack/plugins/reporting/server/test_helpers/create_mock_browserdriverfactory.ts +++ b/x-pack/plugins/reporting/server/test_helpers/create_mock_browserdriverfactory.ts @@ -122,7 +122,7 @@ export const createMockBrowserDriverFactory = async ( const binaryPath = '/usr/local/share/common/secure/super_awesome_binary'; const mockBrowserDriverFactory = chromium.createDriverFactory(core, binaryPath, logger); - const mockPage = ({ setViewport: () => {} } as unknown) as Page; + const mockPage = { setViewport: () => {} } as unknown as Page; const mockBrowserDriver = new HeadlessChromiumDriver(core, mockPage, { inspect: true, networkPolicy: captureConfig.networkPolicy, diff --git a/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts b/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts index 8ad2e62d98c52..9e58d6d4efa41 100644 --- a/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts +++ b/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts @@ -28,9 +28,9 @@ import { ReportingStore } from '../lib'; import { setFieldFormats } from '../services'; import { createMockLevelLogger } from './create_mock_levellogger'; -(initializeBrowserDriverFactory as jest.Mock< - Promise ->).mockImplementation(() => Promise.resolve({} as HeadlessChromiumDriverFactory)); +( + initializeBrowserDriverFactory as jest.Mock> +).mockImplementation(() => Promise.resolve({} as HeadlessChromiumDriverFactory)); (chromium as any).createDriverFactory.mockImplementation(() => ({})); @@ -141,11 +141,11 @@ export const createMockReportingCore = async ( setupDepsMock: ReportingInternalSetup | undefined = undefined, startDepsMock: ReportingInternalStart | undefined = undefined ) => { - const mockReportingCore = ({ + const mockReportingCore = { getConfig: () => createMockConfig(config), getEsClient: () => startDepsMock?.esClient, getDataService: () => startDepsMock?.data, - } as unknown) as ReportingCore; + } as unknown as ReportingCore; if (!setupDepsMock) { setupDepsMock = createMockPluginSetup({}); diff --git a/x-pack/plugins/reporting/server/usage/get_reporting_usage.ts b/x-pack/plugins/reporting/server/usage/get_reporting_usage.ts index 8a1b2532cbcaa..9aba7841162c2 100644 --- a/x-pack/plugins/reporting/server/usage/get_reporting_usage.ts +++ b/x-pack/plugins/reporting/server/usage/get_reporting_usage.ts @@ -184,26 +184,24 @@ export async function getReportingUsage( return esClient .search(params) .then(({ body: response }) => handleResponse(response)) - .then( - (usage: Partial): ReportingUsageType => { - // Allow this to explicitly throw an exception if/when this config is deprecated, - // because we shouldn't collect browserType in that case! - const browserType = config.get('capture', 'browser', 'type'); - - const exportTypesHandler = getExportTypesHandler(exportTypesRegistry); - const availability = exportTypesHandler.getAvailability( - featureAvailability - ) as FeatureAvailabilityMap; - - const { last7Days, ...all } = usage; - - return { - available: true, - browser_type: browserType, - enabled: true, - last7Days: getExportStats(last7Days, availability, exportTypesHandler), - ...getExportStats(all, availability, exportTypesHandler), - }; - } - ); + .then((usage: Partial): ReportingUsageType => { + // Allow this to explicitly throw an exception if/when this config is deprecated, + // because we shouldn't collect browserType in that case! + const browserType = config.get('capture', 'browser', 'type'); + + const exportTypesHandler = getExportTypesHandler(exportTypesRegistry); + const availability = exportTypesHandler.getAvailability( + featureAvailability + ) as FeatureAvailabilityMap; + + const { last7Days, ...all } = usage; + + return { + available: true, + browser_type: browserType, + enabled: true, + last7Days: getExportStats(last7Days, availability, exportTypesHandler), + ...getExportStats(all, availability, exportTypesHandler), + }; + }); } diff --git a/x-pack/plugins/reporting/server/usage/reporting_usage_collector.test.ts b/x-pack/plugins/reporting/server/usage/reporting_usage_collector.test.ts index 92e85d3532c15..e69e56d6272d5 100644 --- a/x-pack/plugins/reporting/server/usage/reporting_usage_collector.test.ts +++ b/x-pack/plugins/reporting/server/usage/reporting_usage_collector.test.ts @@ -38,22 +38,24 @@ function getMockUsageCollection() { }; } -const getLicenseMock = (licenseType = 'platinum') => () => { - return Promise.resolve({ - isAvailable: () => true, - license: { getType: () => licenseType }, - } as FeaturesAvailability); -}; +const getLicenseMock = + (licenseType = 'platinum') => + () => { + return Promise.resolve({ + isAvailable: () => true, + license: { getType: () => licenseType }, + } as FeaturesAvailability); + }; function getPluginsMock( { license, usageCollection = getMockUsageCollection() } = { license: 'platinum' } ) { - return ({ + return { licensing: { license$: Rx.of(getLicenseMock(license)) }, usageCollection, elasticsearch: {}, security: {}, - } as unknown) as ReportingSetupDeps & { usageCollection: UsageCollectionSetup }; + } as unknown as ReportingSetupDeps & { usageCollection: UsageCollectionSetup }; } const getResponseMock = (base = {}) => base; diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/components/field_chooser.js b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/components/field_chooser.js index b5dd2f2afa7bc..91eee6b6b8577 100644 --- a/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/components/field_chooser.js +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/components/field_chooser.js @@ -63,15 +63,8 @@ export class FieldChooser extends Component { }; render() { - const { - buttonLabel, - columns, - fields, - selectedFields, - prompt, - onSelectField, - dataTestSubj, - } = this.props; + const { buttonLabel, columns, fields, selectedFields, prompt, onSelectField, dataTestSubj } = + this.props; const { isOpen, searchValue } = this.state; diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps_config/validate_date_histogram_interval.js b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps_config/validate_date_histogram_interval.js index cd39a22588b36..756c758370301 100644 --- a/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps_config/validate_date_histogram_interval.js +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps_config/validate_date_histogram_interval.js @@ -8,11 +8,8 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { search } from '../../../../../../../../src/plugins/data/public'; -const { - InvalidEsIntervalFormatError, - InvalidEsCalendarIntervalError, - parseEsInterval, -} = search.aggs; +const { InvalidEsIntervalFormatError, InvalidEsCalendarIntervalError, parseEsInterval } = + search.aggs; export function validateDateHistogramInterval(dateHistogramInterval) { if (!dateHistogramInterval || !dateHistogramInterval.trim()) { diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps_config/validate_rollup_delay.js b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps_config/validate_rollup_delay.js index 7f8af7131fb25..d29814c05cf2e 100644 --- a/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps_config/validate_rollup_delay.js +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps_config/validate_rollup_delay.js @@ -8,11 +8,8 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { search } from '../../../../../../../../src/plugins/data/public'; -const { - InvalidEsIntervalFormatError, - InvalidEsCalendarIntervalError, - parseEsInterval, -} = search.aggs; +const { InvalidEsIntervalFormatError, InvalidEsCalendarIntervalError, parseEsInterval } = + search.aggs; export function validateRollupDelay(rollupDelay) { // This field is optional, so if nothing has been provided we can skip validation. diff --git a/x-pack/plugins/rollup/public/crud_app/store/actions/detail_panel.js b/x-pack/plugins/rollup/public/crud_app/store/actions/detail_panel.js index acc222dac8777..c2d0c72c71bcc 100644 --- a/x-pack/plugins/rollup/public/crud_app/store/actions/detail_panel.js +++ b/x-pack/plugins/rollup/public/crud_app/store/actions/detail_panel.js @@ -9,23 +9,25 @@ import { extractQueryParams } from '../../../shared_imports'; import { getRouter } from '../../services'; import { OPEN_DETAIL_PANEL, CLOSE_DETAIL_PANEL } from '../action_types'; -export const openDetailPanel = ({ panelType, jobId }) => (dispatch) => { - const { history } = getRouter(); - const search = history.location.search; - const { job: deepLinkedJobId } = extractQueryParams(search); +export const openDetailPanel = + ({ panelType, jobId }) => + (dispatch) => { + const { history } = getRouter(); + const search = history.location.search; + const { job: deepLinkedJobId } = extractQueryParams(search); - if (deepLinkedJobId !== jobId) { - // Allow the user to share a deep link to this job. - history.replace({ - search: `?job=${jobId}`, - }); - } + if (deepLinkedJobId !== jobId) { + // Allow the user to share a deep link to this job. + history.replace({ + search: `?job=${jobId}`, + }); + } - dispatch({ - type: OPEN_DETAIL_PANEL, - payload: { panelType, jobId }, - }); -}; + dispatch({ + type: OPEN_DETAIL_PANEL, + payload: { panelType, jobId }, + }); + }; export const closeDetailPanel = () => (dispatch) => { dispatch({ diff --git a/x-pack/plugins/rollup/public/crud_app/store/actions/table_state.js b/x-pack/plugins/rollup/public/crud_app/store/actions/table_state.js index 47b163c065e87..457954af42356 100644 --- a/x-pack/plugins/rollup/public/crud_app/store/actions/table_state.js +++ b/x-pack/plugins/rollup/public/crud_app/store/actions/table_state.js @@ -7,30 +7,38 @@ import { FILTER_CHANGED, PAGE_CHANGED, PAGE_SIZE_CHANGED, SORT_CHANGED } from '../action_types'; -export const filterChanged = ({ filter }) => (dispatch) => { - dispatch({ - type: FILTER_CHANGED, - payload: { filter }, - }); -}; +export const filterChanged = + ({ filter }) => + (dispatch) => { + dispatch({ + type: FILTER_CHANGED, + payload: { filter }, + }); + }; -export const pageChanged = ({ pageNumber }) => (dispatch) => { - dispatch({ - type: PAGE_CHANGED, - payload: { pageNumber }, - }); -}; +export const pageChanged = + ({ pageNumber }) => + (dispatch) => { + dispatch({ + type: PAGE_CHANGED, + payload: { pageNumber }, + }); + }; -export const pageSizeChanged = ({ pageSize }) => (dispatch) => { - dispatch({ - type: PAGE_SIZE_CHANGED, - payload: { pageSize }, - }); -}; +export const pageSizeChanged = + ({ pageSize }) => + (dispatch) => { + dispatch({ + type: PAGE_SIZE_CHANGED, + payload: { pageSize }, + }); + }; -export const sortChanged = ({ sortField, isSortAscending }) => (dispatch) => { - dispatch({ - type: SORT_CHANGED, - payload: { sortField, isSortAscending }, - }); -}; +export const sortChanged = + ({ sortField, isSortAscending }) => + (dispatch) => { + dispatch({ + type: SORT_CHANGED, + payload: { sortField, isSortAscending }, + }); + }; diff --git a/x-pack/plugins/rollup/public/kibana_services.ts b/x-pack/plugins/rollup/public/kibana_services.ts index 55500601df246..0bfce1e041f20 100644 --- a/x-pack/plugins/rollup/public/kibana_services.ts +++ b/x-pack/plugins/rollup/public/kibana_services.ts @@ -32,9 +32,10 @@ export function setFatalErrors(newFatalErrors: FatalErrorsSetup) { fatalErrors = newFatalErrors; } -export const [getUiStatsReporter, setUiStatsReporter] = createGetterSetter< - (type: UiCounterMetricType, eventNames: string | string[], count?: number) => void ->('uiMetric'); +export const [getUiStatsReporter, setUiStatsReporter] = + createGetterSetter< + (type: UiCounterMetricType, eventNames: string | string[], count?: number) => void + >('uiMetric'); // default value if usageCollection is not available setUiStatsReporter(() => {}); diff --git a/x-pack/plugins/rollup/public/test/client_integration/helpers/setup_context.tsx b/x-pack/plugins/rollup/public/test/client_integration/helpers/setup_context.tsx index 5bee4145698aa..c5e5ed1bdd2e3 100644 --- a/x-pack/plugins/rollup/public/test/client_integration/helpers/setup_context.tsx +++ b/x-pack/plugins/rollup/public/test/client_integration/helpers/setup_context.tsx @@ -15,10 +15,11 @@ const services = { setBreadcrumbs: startMock.chrome.setBreadcrumbs, }; -const wrapComponent = (Component: FunctionComponent) => (props: any) => ( - - - -); +const wrapComponent = (Component: FunctionComponent) => (props: any) => + ( + + + + ); export { wrapComponent }; diff --git a/x-pack/plugins/rollup/public/test/client_integration/job_create_metrics.test.js b/x-pack/plugins/rollup/public/test/client_integration/job_create_metrics.test.js index 97dbc90b84cc2..35861586b68f3 100644 --- a/x-pack/plugins/rollup/public/test/client_integration/job_create_metrics.test.js +++ b/x-pack/plugins/rollup/public/test/client_integration/job_create_metrics.test.js @@ -192,9 +192,8 @@ describe('Create Rollup Job, step 5: Metrics', () => { rows: [firstRow], } = table.getMetaData('rollupJobMetricsFieldList'); const columnWithMetricsCheckboxes = 2; - const metricsCheckboxes = firstRow.columns[columnWithMetricsCheckboxes].reactWrapper.find( - 'input' - ); + const metricsCheckboxes = + firstRow.columns[columnWithMetricsCheckboxes].reactWrapper.find('input'); expect(metricsCheckboxes.length).toBe( numericTypeMetrics.length + 1 /* add one for select all */ ); @@ -216,9 +215,8 @@ describe('Create Rollup Job, step 5: Metrics', () => { rows: [firstRow], } = table.getMetaData('rollupJobMetricsFieldList'); const columnWithMetricsCheckboxes = 2; - const metricsCheckboxes = firstRow.columns[columnWithMetricsCheckboxes].reactWrapper.find( - 'input' - ); + const metricsCheckboxes = + firstRow.columns[columnWithMetricsCheckboxes].reactWrapper.find('input'); expect(metricsCheckboxes.length).toBe( dateTypeMetrics.length + 1 /* add one for select all */ ); diff --git a/x-pack/plugins/rollup/server/index.ts b/x-pack/plugins/rollup/server/index.ts index aa96f3ae0aac3..e77e0e6f15d72 100644 --- a/x-pack/plugins/rollup/server/index.ts +++ b/x-pack/plugins/rollup/server/index.ts @@ -13,5 +13,6 @@ export const plugin = (pluginInitializerContext: PluginInitializerContext) => new RollupPlugin(pluginInitializerContext); export const config: PluginConfigDescriptor = { + deprecations: ({ deprecate }) => [deprecate('enabled', '8.0.0')], schema: configSchema, }; diff --git a/x-pack/plugins/rollup/server/types.ts b/x-pack/plugins/rollup/server/types.ts index c774644da46ce..a3e826fefa0bf 100644 --- a/x-pack/plugins/rollup/server/types.ts +++ b/x-pack/plugins/rollup/server/types.ts @@ -7,7 +7,7 @@ import { IRouter } from 'src/core/server'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; -import { VisTypeTimeseriesSetup } from 'src/plugins/vis_type_timeseries/server'; +import { VisTypeTimeseriesSetup } from 'src/plugins/vis_types/timeseries/server'; import { getCapabilitiesForRollupIndices } from 'src/plugins/data/server'; import { IndexManagementPluginSetup } from '../../index_management/server'; diff --git a/x-pack/plugins/rollup/tsconfig.json b/x-pack/plugins/rollup/tsconfig.json index fbe323b2549ea..252c27a66fba2 100644 --- a/x-pack/plugins/rollup/tsconfig.json +++ b/x-pack/plugins/rollup/tsconfig.json @@ -22,7 +22,7 @@ { "path": "../../../src/plugins/home/tsconfig.json" }, { "path": "../index_management/tsconfig.json" }, { "path": "../../../src/plugins/usage_collection/tsconfig.json" }, - { "path": "../../../src/plugins/vis_type_timeseries/tsconfig.json" }, + { "path": "../../../src/plugins/vis_types/timeseries/tsconfig.json" }, // required bundles { "path": "../../../src/plugins/kibana_utils/tsconfig.json" }, { "path": "../../../src/plugins/kibana_react/tsconfig.json" }, diff --git a/x-pack/plugins/rule_registry/common/field_map/runtime_type_from_fieldmap.ts b/x-pack/plugins/rule_registry/common/field_map/runtime_type_from_fieldmap.ts index 11dff18721522..2ed92232c0db1 100644 --- a/x-pack/plugins/rule_registry/common/field_map/runtime_type_from_fieldmap.ts +++ b/x-pack/plugins/rule_registry/common/field_map/runtime_type_from_fieldmap.ts @@ -133,8 +133,8 @@ export function runtimeTypeFromFieldMap( const required = pickBy(fieldMap, (field) => field.required); - return (t.intersection([ + return t.intersection([ t.exact(t.partial(mapToType(fieldMap))), t.type(mapToType(required)), - ]) as unknown) as FieldMapType; + ]) as unknown as FieldMapType; } diff --git a/x-pack/plugins/rule_registry/common/pick_with_patterns/index.ts b/x-pack/plugins/rule_registry/common/pick_with_patterns/index.ts index f8a88957fceb5..3abcefd1399ce 100644 --- a/x-pack/plugins/rule_registry/common/pick_with_patterns/index.ts +++ b/x-pack/plugins/rule_registry/common/pick_with_patterns/index.ts @@ -62,5 +62,5 @@ export function pickWithPatterns< }) ); - return (pick(map, matchedFields) as unknown) as PickWithPatterns; + return pick(map, matchedFields) as unknown as PickWithPatterns; } diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts b/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts index d8e3a3bae7b02..5f65cda456a16 100644 --- a/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts +++ b/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts @@ -48,8 +48,9 @@ const getSafeSortIds: typeof getSafeSortIdsTyped = getSafeSortIdsNonTyped; const isValidFeatureId: typeof isValidFeatureIdTyped = isValidFeatureIdNonTyped; // TODO: Fix typings https://github.com/elastic/kibana/issues/101776 -type NonNullableProps = Omit & - { [K in Props]-?: NonNullable }; +type NonNullableProps = Omit & { + [K in Props]-?: NonNullable; +}; type AlertType = { _index: string; _id: string } & NonNullableProps< ParsedTechnicalFields, typeof ALERT_RULE_TYPE_ID | typeof ALERT_RULE_CONSUMER | typeof SPACE_IDS @@ -393,8 +394,8 @@ export class AlertsClient { undefined, esQuery == null ? { query: ``, language: 'kuery' } : esQuery, [ - (authzFilter as unknown) as Filter, - ({ term: { [SPACE_IDS]: alertSpaceId } } as unknown) as Filter, + authzFilter as unknown as Filter, + { term: { [SPACE_IDS]: alertSpaceId } } as unknown as Filter, ], config ); diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client_factory.test.ts b/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client_factory.test.ts index 1187d4675787b..41ef5e4edb0d1 100644 --- a/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client_factory.test.ts +++ b/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client_factory.test.ts @@ -26,10 +26,10 @@ const alertsClientFactoryParams: AlertsClientFactoryProps = { getAlertingAuthorization: (_: KibanaRequest) => alertingAuthMock, securityPluginSetup, esClient: {} as ElasticsearchClient, - ruleDataService: (ruleDataPluginServiceMock.create() as unknown) as RuleDataPluginService, + ruleDataService: ruleDataPluginServiceMock.create() as unknown as RuleDataPluginService, }; -const fakeRequest = ({ +const fakeRequest = { app: {}, headers: {}, getBasePath: () => '', @@ -43,7 +43,7 @@ const fakeRequest = ({ url: '/', }, }, -} as unknown) as Request; +} as unknown as Request; const auditLogger = { log: jest.fn(), diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/tests/bulk_update.test.ts b/x-pack/plugins/rule_registry/server/alert_data_client/tests/bulk_update.test.ts index 7b81235083548..2be1f6875cd7e 100644 --- a/x-pack/plugins/rule_registry/server/alert_data_client/tests/bulk_update.test.ts +++ b/x-pack/plugins/rule_registry/server/alert_data_client/tests/bulk_update.test.ts @@ -33,7 +33,7 @@ const alertsClientParams: jest.Mocked = { authorization: alertingAuthMock, esClient: esClientMock, auditLogger, - ruleDataService: (ruleDataPluginServiceMock.create() as unknown) as RuleDataPluginService, + ruleDataService: ruleDataPluginServiceMock.create() as unknown as RuleDataPluginService, }; const DEFAULT_SPACE = 'test_default_space_id'; diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/tests/find_alerts.test.ts b/x-pack/plugins/rule_registry/server/alert_data_client/tests/find_alerts.test.ts index f103fd5778e83..b94a3b96312e4 100644 --- a/x-pack/plugins/rule_registry/server/alert_data_client/tests/find_alerts.test.ts +++ b/x-pack/plugins/rule_registry/server/alert_data_client/tests/find_alerts.test.ts @@ -32,7 +32,7 @@ const alertsClientParams: jest.Mocked = { authorization: alertingAuthMock, esClient: esClientMock, auditLogger, - ruleDataService: (ruleDataPluginServiceMock.create() as unknown) as RuleDataPluginService, + ruleDataService: ruleDataPluginServiceMock.create() as unknown as RuleDataPluginService, }; const DEFAULT_SPACE = 'test_default_space_id'; diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/tests/get.test.ts b/x-pack/plugins/rule_registry/server/alert_data_client/tests/get.test.ts index 8f9d55392e6b9..320e9f8a5fb1c 100644 --- a/x-pack/plugins/rule_registry/server/alert_data_client/tests/get.test.ts +++ b/x-pack/plugins/rule_registry/server/alert_data_client/tests/get.test.ts @@ -33,7 +33,7 @@ const alertsClientParams: jest.Mocked = { authorization: alertingAuthMock, esClient: esClientMock, auditLogger, - ruleDataService: (ruleDataPluginServiceMock.create() as unknown) as RuleDataPluginService, + ruleDataService: ruleDataPluginServiceMock.create() as unknown as RuleDataPluginService, }; const DEFAULT_SPACE = 'test_default_space_id'; diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/tests/update.test.ts b/x-pack/plugins/rule_registry/server/alert_data_client/tests/update.test.ts index b71bddff53696..922011dcb5271 100644 --- a/x-pack/plugins/rule_registry/server/alert_data_client/tests/update.test.ts +++ b/x-pack/plugins/rule_registry/server/alert_data_client/tests/update.test.ts @@ -32,7 +32,7 @@ const alertsClientParams: jest.Mocked = { authorization: alertingAuthMock, esClient: esClientMock, auditLogger, - ruleDataService: (ruleDataPluginServiceMock.create() as unknown) as RuleDataPluginService, + ruleDataService: ruleDataPluginServiceMock.create() as unknown as RuleDataPluginService, }; const DEFAULT_SPACE = 'test_default_space_id'; diff --git a/x-pack/plugins/rule_registry/server/config.ts b/x-pack/plugins/rule_registry/server/config.ts index 481c5fe3cce8b..62f29a9e06294 100644 --- a/x-pack/plugins/rule_registry/server/config.ts +++ b/x-pack/plugins/rule_registry/server/config.ts @@ -6,8 +6,10 @@ */ import { schema, TypeOf } from '@kbn/config-schema'; +import { PluginConfigDescriptor } from 'src/core/server'; -export const config = { +export const config: PluginConfigDescriptor = { + deprecations: ({ deprecate }) => [deprecate('enabled', '8.0.0')], schema: schema.object({ enabled: schema.boolean({ defaultValue: true }), write: schema.object({ diff --git a/x-pack/plugins/rule_registry/server/plugin.ts b/x-pack/plugins/rule_registry/server/plugin.ts index 2329b90898ca6..5d1994cfd3e6d 100644 --- a/x-pack/plugins/rule_registry/server/plugin.ts +++ b/x-pack/plugins/rule_registry/server/plugin.ts @@ -50,7 +50,8 @@ export class RuleRegistryPlugin RuleRegistryPluginStartContract, RuleRegistryPluginSetupDependencies, RuleRegistryPluginStartDependencies - > { + > +{ private readonly config: RuleRegistryPluginConfig; private readonly legacyConfig: SharedGlobalConfig; private readonly logger: Logger; diff --git a/x-pack/plugins/rule_registry/server/routes/__mocks__/request_context.ts b/x-pack/plugins/rule_registry/server/routes/__mocks__/request_context.ts index 0555b3320ad91..4521ba770d806 100644 --- a/x-pack/plugins/rule_registry/server/routes/__mocks__/request_context.ts +++ b/x-pack/plugins/rule_registry/server/routes/__mocks__/request_context.ts @@ -19,7 +19,7 @@ const createRequestContextMock = ( clients: ReturnType = createMockClients() ) => { const coreContext = coreMock.createRequestHandlerContext(); - return ({ + return { rac: { getAlertsClient: jest.fn(() => clients.rac) }, core: { ...coreContext, @@ -29,7 +29,7 @@ const createRequestContextMock = ( }, savedObjects: { client: clients.savedObjectsClient }, }, - } as unknown) as RacRequestHandlerContext; + } as unknown as RacRequestHandlerContext; }; const createTools = () => { diff --git a/x-pack/plugins/rule_registry/server/routes/utils/route_validation.ts b/x-pack/plugins/rule_registry/server/routes/utils/route_validation.ts index 8e74760d6d15f..680b67949bde5 100644 --- a/x-pack/plugins/rule_registry/server/routes/utils/route_validation.ts +++ b/x-pack/plugins/rule_registry/server/routes/utils/route_validation.ts @@ -40,17 +40,17 @@ type RequestValidationResult = * * TODO: Figure out a way to move this function into a package rather than copying it/forking it within plugins */ -export const buildRouteValidation = >( - schema: T -): RouteValidationFunction => ( - inputValue: unknown, - validationResult: RouteValidationResultFactory -): RequestValidationResult => - pipe( - schema.decode(inputValue), - (decoded) => exactCheck(inputValue, decoded), - fold>( - (errors: rt.Errors) => validationResult.badRequest(formatErrors(errors).join()), - (validatedInput: A) => validationResult.ok(validatedInput) - ) - ); +export const buildRouteValidation = + >(schema: T): RouteValidationFunction => + ( + inputValue: unknown, + validationResult: RouteValidationResultFactory + ): RequestValidationResult => + pipe( + schema.decode(inputValue), + (decoded) => exactCheck(inputValue, decoded), + fold>( + (errors: rt.Errors) => validationResult.badRequest(formatErrors(errors).join()), + (validatedInput: A) => validationResult.ok(validatedInput) + ) + ); diff --git a/x-pack/plugins/rule_registry/server/rule_data_client/types.ts b/x-pack/plugins/rule_registry/server/rule_data_client/types.ts index 3e8b6b3413c6f..0595dbeea6dc6 100644 --- a/x-pack/plugins/rule_registry/server/rule_data_client/types.ts +++ b/x-pack/plugins/rule_registry/server/rule_data_client/types.ts @@ -27,9 +27,7 @@ export interface IRuleDataReader { ESSearchResponse>, TSearchRequest> >; - getDynamicIndexPattern( - target?: string - ): Promise<{ + getDynamicIndexPattern(target?: string): Promise<{ title: string; timeFieldName: string; fields: FieldDescriptor[]; diff --git a/x-pack/plugins/rule_registry/server/rule_data_plugin_service/utils.ts b/x-pack/plugins/rule_registry/server/rule_data_plugin_service/utils.ts index 10f2f546046b7..d550ec19d3ed2 100644 --- a/x-pack/plugins/rule_registry/server/rule_data_plugin_service/utils.ts +++ b/x-pack/plugins/rule_registry/server/rule_data_plugin_service/utils.ts @@ -14,10 +14,10 @@ export const incrementIndexName = (oldIndex: string) => { return baseIndexString + String(newIndexNumber).padStart(6, '0'); }; -export const joinWith = (separator: string) => ( - ...items: Array -): string => { - return items.filter(Boolean).map(String).join(separator); -}; +export const joinWith = + (separator: string) => + (...items: Array): string => { + return items.filter(Boolean).map(String).join(separator); + }; export const joinWithDash = joinWith('-'); diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts index 48f3a81a00af2..1acbc0c3f43bd 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts @@ -113,200 +113,200 @@ export type WrappedLifecycleRuleState = AlertTypeS trackedAlerts: Record; }; -export const createLifecycleExecutor = ( - logger: Logger, - ruleDataClient: PublicContract -) => < - Params extends AlertTypeParams = never, - State extends AlertTypeState = never, - InstanceState extends AlertInstanceState = never, - InstanceContext extends AlertInstanceContext = never, - ActionGroupIds extends string = never ->( - wrappedExecutor: LifecycleRuleExecutor< - Params, - State, - InstanceState, - InstanceContext, - ActionGroupIds - > -) => async ( - options: AlertExecutorOptions< - Params, - WrappedLifecycleRuleState, - InstanceState, - InstanceContext, - ActionGroupIds - > -): Promise> => { - const { - services: { alertInstanceFactory }, - state: previousState, - } = options; - - const state = getOrElse( - (): WrappedLifecycleRuleState => ({ - wrapped: previousState as State, - trackedAlerts: {}, - }) - )(wrappedStateRt().decode(previousState)); - - const commonRuleFields = getCommonAlertFields(options); +export const createLifecycleExecutor = + (logger: Logger, ruleDataClient: PublicContract) => + < + Params extends AlertTypeParams = never, + State extends AlertTypeState = never, + InstanceState extends AlertInstanceState = never, + InstanceContext extends AlertInstanceContext = never, + ActionGroupIds extends string = never + >( + wrappedExecutor: LifecycleRuleExecutor< + Params, + State, + InstanceState, + InstanceContext, + ActionGroupIds + > + ) => + async ( + options: AlertExecutorOptions< + Params, + WrappedLifecycleRuleState, + InstanceState, + InstanceContext, + ActionGroupIds + > + ): Promise> => { + const { + services: { alertInstanceFactory }, + state: previousState, + } = options; + + const state = getOrElse( + (): WrappedLifecycleRuleState => ({ + wrapped: previousState as State, + trackedAlerts: {}, + }) + )(wrappedStateRt().decode(previousState)); - const currentAlerts: Record = {}; + const commonRuleFields = getCommonAlertFields(options); - const lifecycleAlertServices: LifecycleAlertServices< - InstanceState, - InstanceContext, - ActionGroupIds - > = { - alertWithLifecycle: ({ id, fields }) => { - currentAlerts[id] = fields; - return alertInstanceFactory(id); - }, - }; + const currentAlerts: Record = {}; - const nextWrappedState = await wrappedExecutor({ - ...options, - state: state.wrapped != null ? state.wrapped : ({} as State), - services: { - ...options.services, - ...lifecycleAlertServices, - }, - }); + const lifecycleAlertServices: LifecycleAlertServices< + InstanceState, + InstanceContext, + ActionGroupIds + > = { + alertWithLifecycle: ({ id, fields }) => { + currentAlerts[id] = fields; + return alertInstanceFactory(id); + }, + }; + + const nextWrappedState = await wrappedExecutor({ + ...options, + state: state.wrapped != null ? state.wrapped : ({} as State), + services: { + ...options.services, + ...lifecycleAlertServices, + }, + }); - const currentAlertIds = Object.keys(currentAlerts); - const trackedAlertIds = Object.keys(state.trackedAlerts); - const newAlertIds = currentAlertIds.filter((alertId) => !trackedAlertIds.includes(alertId)); - const allAlertIds = [...new Set(currentAlertIds.concat(trackedAlertIds))]; - - const trackedAlertStates = Object.values(state.trackedAlerts); - - logger.debug( - `Tracking ${allAlertIds.length} alerts (${newAlertIds.length} new, ${trackedAlertStates.length} previous)` - ); - - const trackedAlertsDataMap: Record< - string, - { indexName: string; fields: Partial } - > = {}; - - if (trackedAlertStates.length) { - const { hits } = await ruleDataClient.getReader().search({ - body: { - query: { - bool: { - filter: [ - { - term: { - [ALERT_RULE_UUID]: commonRuleFields[ALERT_RULE_UUID], + const currentAlertIds = Object.keys(currentAlerts); + const trackedAlertIds = Object.keys(state.trackedAlerts); + const newAlertIds = currentAlertIds.filter((alertId) => !trackedAlertIds.includes(alertId)); + const allAlertIds = [...new Set(currentAlertIds.concat(trackedAlertIds))]; + + const trackedAlertStates = Object.values(state.trackedAlerts); + + logger.debug( + `Tracking ${allAlertIds.length} alerts (${newAlertIds.length} new, ${trackedAlertStates.length} previous)` + ); + + const trackedAlertsDataMap: Record< + string, + { indexName: string; fields: Partial } + > = {}; + + if (trackedAlertStates.length) { + const { hits } = await ruleDataClient.getReader().search({ + body: { + query: { + bool: { + filter: [ + { + term: { + [ALERT_RULE_UUID]: commonRuleFields[ALERT_RULE_UUID], + }, }, - }, - { - terms: { - [ALERT_UUID]: trackedAlertStates.map( - (trackedAlertState) => trackedAlertState.alertUuid - ), + { + terms: { + [ALERT_UUID]: trackedAlertStates.map( + (trackedAlertState) => trackedAlertState.alertUuid + ), + }, }, - }, - ], + ], + }, + }, + size: trackedAlertStates.length, + collapse: { + field: ALERT_UUID, + }, + _source: false, + fields: [{ field: '*', include_unmapped: true }], + sort: { + [TIMESTAMP]: 'desc' as const, }, }, - size: trackedAlertStates.length, - collapse: { - field: ALERT_UUID, - }, - _source: false, - fields: [{ field: '*', include_unmapped: true }], - sort: { - [TIMESTAMP]: 'desc' as const, - }, - }, - allow_no_indices: true, - }); - - hits.hits.forEach((hit) => { - const fields = parseTechnicalFields(hit.fields); - const indexName = hit._index; - const alertId = fields[ALERT_INSTANCE_ID]; - trackedAlertsDataMap[alertId] = { - indexName, - fields, - }; - }); - } - - const makeEventsDataMapFor = (alertIds: string[]) => - alertIds.map((alertId) => { - const alertData = trackedAlertsDataMap[alertId]; - const currentAlertData = currentAlerts[alertId]; - - if (!alertData) { - logger.warn(`Could not find alert data for ${alertId}`); - } - - const isNew = !state.trackedAlerts[alertId]; - const isRecovered = !currentAlerts[alertId]; - const isActive = !isRecovered; - - const { alertUuid, started } = state.trackedAlerts[alertId] ?? { - alertUuid: v4(), - started: commonRuleFields[TIMESTAMP], - }; - - const event: ParsedTechnicalFields = { - ...alertData?.fields, - ...commonRuleFields, - ...currentAlertData, - [ALERT_DURATION]: (options.startedAt.getTime() - new Date(started).getTime()) * 1000, - - [ALERT_INSTANCE_ID]: alertId, - [ALERT_START]: started, - [ALERT_UUID]: alertUuid, - [ALERT_STATUS]: isRecovered ? ALERT_STATUS_RECOVERED : ALERT_STATUS_ACTIVE, - [ALERT_WORKFLOW_STATUS]: alertData?.fields[ALERT_WORKFLOW_STATUS] ?? 'open', - [EVENT_KIND]: 'signal', - [EVENT_ACTION]: isNew ? 'open' : isActive ? 'active' : 'close', - [VERSION]: ruleDataClient.kibanaVersion, - ...(isRecovered ? { [ALERT_END]: commonRuleFields[TIMESTAMP] } : {}), - }; - - return { - indexName: alertData?.indexName, - event, - }; - }); - - const trackedEventsToIndex = makeEventsDataMapFor(trackedAlertIds); - const newEventsToIndex = makeEventsDataMapFor(newAlertIds); - const allEventsToIndex = [...trackedEventsToIndex, ...newEventsToIndex]; - - if (allEventsToIndex.length > 0 && ruleDataClient.isWriteEnabled()) { - logger.debug(`Preparing to index ${allEventsToIndex.length} alerts.`); - - await ruleDataClient.getWriter().bulk({ - body: allEventsToIndex.flatMap(({ event, indexName }) => [ - indexName - ? { index: { _id: event[ALERT_UUID]!, _index: indexName, require_alias: false } } - : { index: { _id: event[ALERT_UUID]! } }, - event, - ]), - }); - } - - const nextTrackedAlerts = Object.fromEntries( - allEventsToIndex - .filter(({ event }) => event[ALERT_STATUS] !== 'closed') - .map(({ event }) => { - const alertId = event[ALERT_INSTANCE_ID]!; - const alertUuid = event[ALERT_UUID]!; - const started = new Date(event[ALERT_START]!).toISOString(); - return [alertId, { alertId, alertUuid, started }]; - }) - ); - - return { - wrapped: nextWrappedState ?? ({} as State), - trackedAlerts: ruleDataClient.isWriteEnabled() ? nextTrackedAlerts : {}, + allow_no_indices: true, + }); + + hits.hits.forEach((hit) => { + const fields = parseTechnicalFields(hit.fields); + const indexName = hit._index; + const alertId = fields[ALERT_INSTANCE_ID]; + trackedAlertsDataMap[alertId] = { + indexName, + fields, + }; + }); + } + + const makeEventsDataMapFor = (alertIds: string[]) => + alertIds.map((alertId) => { + const alertData = trackedAlertsDataMap[alertId]; + const currentAlertData = currentAlerts[alertId]; + + if (!alertData) { + logger.warn(`Could not find alert data for ${alertId}`); + } + + const isNew = !state.trackedAlerts[alertId]; + const isRecovered = !currentAlerts[alertId]; + const isActive = !isRecovered; + + const { alertUuid, started } = state.trackedAlerts[alertId] ?? { + alertUuid: v4(), + started: commonRuleFields[TIMESTAMP], + }; + + const event: ParsedTechnicalFields = { + ...alertData?.fields, + ...commonRuleFields, + ...currentAlertData, + [ALERT_DURATION]: (options.startedAt.getTime() - new Date(started).getTime()) * 1000, + + [ALERT_INSTANCE_ID]: alertId, + [ALERT_START]: started, + [ALERT_UUID]: alertUuid, + [ALERT_STATUS]: isRecovered ? ALERT_STATUS_RECOVERED : ALERT_STATUS_ACTIVE, + [ALERT_WORKFLOW_STATUS]: alertData?.fields[ALERT_WORKFLOW_STATUS] ?? 'open', + [EVENT_KIND]: 'signal', + [EVENT_ACTION]: isNew ? 'open' : isActive ? 'active' : 'close', + [VERSION]: ruleDataClient.kibanaVersion, + ...(isRecovered ? { [ALERT_END]: commonRuleFields[TIMESTAMP] } : {}), + }; + + return { + indexName: alertData?.indexName, + event, + }; + }); + + const trackedEventsToIndex = makeEventsDataMapFor(trackedAlertIds); + const newEventsToIndex = makeEventsDataMapFor(newAlertIds); + const allEventsToIndex = [...trackedEventsToIndex, ...newEventsToIndex]; + + if (allEventsToIndex.length > 0 && ruleDataClient.isWriteEnabled()) { + logger.debug(`Preparing to index ${allEventsToIndex.length} alerts.`); + + await ruleDataClient.getWriter().bulk({ + body: allEventsToIndex.flatMap(({ event, indexName }) => [ + indexName + ? { index: { _id: event[ALERT_UUID]!, _index: indexName, require_alias: false } } + : { index: { _id: event[ALERT_UUID]! } }, + event, + ]), + }); + } + + const nextTrackedAlerts = Object.fromEntries( + allEventsToIndex + .filter(({ event }) => event[ALERT_STATUS] !== ALERT_STATUS_RECOVERED) + .map(({ event }) => { + const alertId = event[ALERT_INSTANCE_ID]!; + const alertUuid = event[ALERT_UUID]!; + const started = new Date(event[ALERT_START]!).toISOString(); + return [alertId, { alertId, alertUuid, started }]; + }) + ); + + return { + wrapped: nextWrappedState ?? ({} as State), + trackedAlerts: ruleDataClient.isWriteEnabled() ? nextTrackedAlerts : {}, + }; }; -}; diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_executor_mock.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_executor_mock.ts index c519674569a51..9a694b40d3228 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_executor_mock.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_executor_mock.ts @@ -15,21 +15,24 @@ import { AlertExecutorOptionsWithExtraServices } from '../types'; import { LifecycleAlertServices, LifecycleRuleExecutor } from './create_lifecycle_executor'; -export const createLifecycleRuleExecutorMock = < - Params extends AlertTypeParams = never, - State extends AlertTypeState = never, - InstanceState extends AlertInstanceState = never, - InstanceContext extends AlertInstanceContext = never, - ActionGroupIds extends string = never ->( - executor: LifecycleRuleExecutor -) => async ( - options: AlertExecutorOptionsWithExtraServices< - Params, - State, - InstanceState, - InstanceContext, - ActionGroupIds, - LifecycleAlertServices - > -) => await executor(options); +export const createLifecycleRuleExecutorMock = + < + Params extends AlertTypeParams = never, + State extends AlertTypeState = never, + InstanceState extends AlertInstanceState = never, + InstanceContext extends AlertInstanceContext = never, + ActionGroupIds extends string = never + >( + executor: LifecycleRuleExecutor + ) => + async ( + options: AlertExecutorOptionsWithExtraServices< + Params, + State, + InstanceState, + InstanceContext, + ActionGroupIds, + LifecycleAlertServices + > + ) => + await executor(options); diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts index 6ff418b2ebe47..3fa567b8aca96 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts @@ -25,7 +25,7 @@ function createRule() { const ruleDataClientMock = createRuleDataClientMock(); const factory = createLifecycleRuleTypeFactory({ - ruleDataClient: (ruleDataClientMock as unknown) as RuleDataClient, + ruleDataClient: ruleDataClientMock as unknown as RuleDataClient, logger: loggerMock.create(), }); diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type_factory.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type_factory.ts index 9a809efdb5789..29e3a1902c868 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type_factory.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type_factory.ts @@ -15,31 +15,27 @@ import { import { AlertTypeWithExecutor } from '../types'; import { LifecycleAlertService, createLifecycleExecutor } from './create_lifecycle_executor'; -export const createLifecycleRuleTypeFactory = ({ - logger, - ruleDataClient, -}: { - logger: Logger; - ruleDataClient: IRuleDataClient; -}) => < - TParams extends AlertTypeParams, - TAlertInstanceContext extends AlertInstanceContext, - TServices extends { - alertWithLifecycle: LifecycleAlertService, TAlertInstanceContext, string>; - } ->( - type: AlertTypeWithExecutor, TParams, TAlertInstanceContext, TServices> -): AlertTypeWithExecutor, TParams, TAlertInstanceContext, any> => { - const createBoundLifecycleExecutor = createLifecycleExecutor(logger, ruleDataClient); - const executor = createBoundLifecycleExecutor< - TParams, - AlertTypeState, - AlertInstanceState, - TAlertInstanceContext, - string - >(type.executor as any); - return { - ...type, - executor: executor as any, +export const createLifecycleRuleTypeFactory = + ({ logger, ruleDataClient }: { logger: Logger; ruleDataClient: IRuleDataClient }) => + < + TParams extends AlertTypeParams, + TAlertInstanceContext extends AlertInstanceContext, + TServices extends { + alertWithLifecycle: LifecycleAlertService, TAlertInstanceContext, string>; + } + >( + type: AlertTypeWithExecutor, TParams, TAlertInstanceContext, TServices> + ): AlertTypeWithExecutor, TParams, TAlertInstanceContext, any> => { + const createBoundLifecycleExecutor = createLifecycleExecutor(logger, ruleDataClient); + const executor = createBoundLifecycleExecutor< + TParams, + AlertTypeState, + AlertInstanceState, + TAlertInstanceContext, + string + >(type.executor as any); + return { + ...type, + executor: executor as any, + }; }; -}; diff --git a/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_factory.ts b/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_factory.ts index 963843b7bb6d3..837d0378703f7 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_factory.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_factory.ts @@ -9,45 +9,44 @@ import { ALERT_INSTANCE_ID, VERSION } from '@kbn/rule-data-utils'; import { getCommonAlertFields } from './get_common_alert_fields'; import { CreatePersistenceRuleTypeFactory } from './persistence_types'; -export const createPersistenceRuleTypeFactory: CreatePersistenceRuleTypeFactory = ({ - logger, - ruleDataClient, -}) => (type) => { - return { - ...type, - executor: async (options) => { - const state = await type.executor({ - ...options, - services: { - ...options.services, - alertWithPersistence: async (alerts, refresh) => { - const numAlerts = alerts.length; - logger.debug(`Found ${numAlerts} alerts.`); +export const createPersistenceRuleTypeFactory: CreatePersistenceRuleTypeFactory = + ({ logger, ruleDataClient }) => + (type) => { + return { + ...type, + executor: async (options) => { + const state = await type.executor({ + ...options, + services: { + ...options.services, + alertWithPersistence: async (alerts, refresh) => { + const numAlerts = alerts.length; + logger.debug(`Found ${numAlerts} alerts.`); - if (ruleDataClient.isWriteEnabled() && numAlerts) { - const commonRuleFields = getCommonAlertFields(options); + if (ruleDataClient.isWriteEnabled() && numAlerts) { + const commonRuleFields = getCommonAlertFields(options); - const response = await ruleDataClient.getWriter().bulk({ - body: alerts.flatMap((alert) => [ - { index: {} }, - { - [ALERT_INSTANCE_ID]: alert.id, - [VERSION]: ruleDataClient.kibanaVersion, - ...commonRuleFields, - ...alert.fields, - }, - ]), - refresh, - }); - return response; - } else { - logger.debug('Writing is disabled.'); - } + const response = await ruleDataClient.getWriter().bulk({ + body: alerts.flatMap((alert) => [ + { index: {} }, + { + [ALERT_INSTANCE_ID]: alert.id, + [VERSION]: ruleDataClient.kibanaVersion, + ...commonRuleFields, + ...alert.fields, + }, + ]), + refresh, + }); + return response; + } else { + logger.debug('Writing is disabled.'); + } + }, }, - }, - }); + }); - return state; - }, + return state; + }, + }; }; -}; diff --git a/x-pack/plugins/rule_registry/server/utils/with_rule_data_client_factory.ts b/x-pack/plugins/rule_registry/server/utils/with_rule_data_client_factory.ts index 0bee5bdf53a69..365744c8d6842 100644 --- a/x-pack/plugins/rule_registry/server/utils/with_rule_data_client_factory.ts +++ b/x-pack/plugins/rule_registry/server/utils/with_rule_data_client_factory.ts @@ -9,34 +9,36 @@ import { AlertInstanceContext, AlertTypeParams, AlertTypeState } from '../../../ import { IRuleDataClient } from '../rule_data_client'; import { AlertTypeWithExecutor } from '../types'; -export const withRuleDataClientFactory = (ruleDataClient: IRuleDataClient) => < - TState extends AlertTypeState, - TParams extends AlertTypeParams, - TAlertInstanceContext extends AlertInstanceContext, - TServices extends Record = {} ->( - type: AlertTypeWithExecutor< +export const withRuleDataClientFactory = + (ruleDataClient: IRuleDataClient) => + < + TState extends AlertTypeState, + TParams extends AlertTypeParams, + TAlertInstanceContext extends AlertInstanceContext, + TServices extends Record = {} + >( + type: AlertTypeWithExecutor< + TState, + TParams, + TAlertInstanceContext, + TServices & { ruleDataClient: IRuleDataClient } + > + ): AlertTypeWithExecutor< TState, TParams, TAlertInstanceContext, TServices & { ruleDataClient: IRuleDataClient } - > -): AlertTypeWithExecutor< - TState, - TParams, - TAlertInstanceContext, - TServices & { ruleDataClient: IRuleDataClient } -> => { - return { - ...type, - executor: (options) => { - return type.executor({ - ...options, - services: { - ...options.services, - ruleDataClient, - }, - }); - }, + > => { + return { + ...type, + executor: (options) => { + return type.executor({ + ...options, + services: { + ...options.services, + ruleDataClient, + }, + }); + }, + }; }; -}; diff --git a/x-pack/plugins/runtime_fields/public/components/runtime_field_form/runtime_field_form.tsx b/x-pack/plugins/runtime_fields/public/components/runtime_field_form/runtime_field_form.tsx index 023b620522282..d3376d872e4c5 100644 --- a/x-pack/plugins/runtime_fields/public/components/runtime_field_form/runtime_field_form.tsx +++ b/x-pack/plugins/runtime_fields/public/components/runtime_field_form/runtime_field_form.tsx @@ -69,20 +69,20 @@ export interface Props { }; } -const createNameNotAllowedValidator = ( - namesNotAllowed: string[] -): ValidationFunc<{}, string, string> => ({ value }) => { - if (namesNotAllowed.includes(value)) { - return { - message: i18n.translate( - 'xpack.runtimeFields.runtimeFieldsEditor.existRuntimeFieldNamesValidationErrorMessage', - { - defaultMessage: 'There is already a field with this name.', - } - ), - }; - } -}; +const createNameNotAllowedValidator = + (namesNotAllowed: string[]): ValidationFunc<{}, string, string> => + ({ value }) => { + if (namesNotAllowed.includes(value)) { + return { + message: i18n.translate( + 'xpack.runtimeFields.runtimeFieldsEditor.existRuntimeFieldNamesValidationErrorMessage', + { + defaultMessage: 'There is already a field with this name.', + } + ), + }; + } + }; /** * Dynamically retrieve the config for the "name" field, adding diff --git a/x-pack/plugins/runtime_fields/public/load_editor.tsx b/x-pack/plugins/runtime_fields/public/load_editor.tsx index d24c994fb561e..0cea90f33a54d 100644 --- a/x-pack/plugins/runtime_fields/public/load_editor.tsx +++ b/x-pack/plugins/runtime_fields/public/load_editor.tsx @@ -18,47 +18,46 @@ export interface OpenRuntimeFieldEditorProps { ctx?: RuntimeFieldEditorFlyoutContentProps['ctx']; } -export const getRuntimeFieldEditorLoader = ( - coreSetup: CoreSetup -) => async (): Promise => { - const { RuntimeFieldEditorFlyoutContent } = await import('./components'); - const [core] = await coreSetup.getStartServices(); - const { uiSettings, overlays, docLinks } = core; - const { Provider: KibanaReactContextProvider } = createKibanaReactContext({ uiSettings }); - - let overlayRef: OverlayRef | null = null; - - const openEditor = ({ onSave, defaultValue, ctx }: OpenRuntimeFieldEditorProps) => { - const closeEditor = () => { - if (overlayRef) { - overlayRef.close(); - overlayRef = null; - } +export const getRuntimeFieldEditorLoader = + (coreSetup: CoreSetup) => async (): Promise => { + const { RuntimeFieldEditorFlyoutContent } = await import('./components'); + const [core] = await coreSetup.getStartServices(); + const { uiSettings, overlays, docLinks } = core; + const { Provider: KibanaReactContextProvider } = createKibanaReactContext({ uiSettings }); + + let overlayRef: OverlayRef | null = null; + + const openEditor = ({ onSave, defaultValue, ctx }: OpenRuntimeFieldEditorProps) => { + const closeEditor = () => { + if (overlayRef) { + overlayRef.close(); + overlayRef = null; + } + }; + + const onSaveField = (field: RuntimeField) => { + closeEditor(); + onSave(field); + }; + + overlayRef = overlays.openFlyout( + toMountPoint( + + overlayRef?.close()} + docLinks={docLinks} + defaultValue={defaultValue} + ctx={ctx} + /> + + ) + ); + + return closeEditor; }; - const onSaveField = (field: RuntimeField) => { - closeEditor(); - onSave(field); + return { + openEditor, }; - - overlayRef = overlays.openFlyout( - toMountPoint( - - overlayRef?.close()} - docLinks={docLinks} - defaultValue={defaultValue} - ctx={ctx} - /> - - ) - ); - - return closeEditor; - }; - - return { - openEditor, }; -}; diff --git a/x-pack/plugins/runtime_fields/public/plugin.ts b/x-pack/plugins/runtime_fields/public/plugin.ts index 5627b1270138a..32b3981e90caa 100644 --- a/x-pack/plugins/runtime_fields/public/plugin.ts +++ b/x-pack/plugins/runtime_fields/public/plugin.ts @@ -11,7 +11,8 @@ import { PluginSetup, PluginStart, SetupPlugins, StartPlugins } from './types'; import { getRuntimeFieldEditorLoader } from './load_editor'; export class RuntimeFieldsPlugin - implements Plugin { + implements Plugin +{ public setup(core: CoreSetup, plugins: SetupPlugins): PluginSetup { return { loadEditor: getRuntimeFieldEditorLoader(core), diff --git a/x-pack/plugins/saved_objects_tagging/public/components/assign_flyout/assign_flyout.tsx b/x-pack/plugins/saved_objects_tagging/public/components/assign_flyout/assign_flyout.tsx index ea11f204b62cd..b0c18a8ca5fad 100644 --- a/x-pack/plugins/saved_objects_tagging/public/components/assign_flyout/assign_flyout.tsx +++ b/x-pack/plugins/saved_objects_tagging/public/components/assign_flyout/assign_flyout.tsx @@ -75,8 +75,9 @@ export const AssignFlyout: FC = ({ [getKey(result)]: getObjectStatus(result, tagIds), }; }, {} as AssignmentStatusMap); - const assignedCount = Object.values(fetchedStatus).filter((status) => status !== 'none') - .length; + const assignedCount = Object.values(fetchedStatus).filter( + (status) => status !== 'none' + ).length; setResults(sortByStatusAndTitle(fetched, fetchedStatus)); setOverrides({}); diff --git a/x-pack/plugins/saved_objects_tagging/public/components/assign_flyout/open_assign_flyout.tsx b/x-pack/plugins/saved_objects_tagging/public/components/assign_flyout/open_assign_flyout.tsx index e8803203e9460..9d8628a5f32e7 100644 --- a/x-pack/plugins/saved_objects_tagging/public/components/assign_flyout/open_assign_flyout.tsx +++ b/x-pack/plugins/saved_objects_tagging/public/components/assign_flyout/open_assign_flyout.tsx @@ -38,28 +38,30 @@ const LazyAssignFlyout = React.lazy(() => import('./assign_flyout').then(({ AssignFlyout }) => ({ default: AssignFlyout })) ); -export const getAssignFlyoutOpener = ({ - overlays, - notifications, - tagCache, - assignmentService, - assignableTypes, -}: GetAssignFlyoutOpenerOptions): AssignFlyoutOpener => async ({ tagIds }) => { - const flyout = overlays.openFlyout( - toMountPoint( - }> - flyout.close()} - /> - - ), - { size: 'm', maxWidth: 600 } - ); +export const getAssignFlyoutOpener = + ({ + overlays, + notifications, + tagCache, + assignmentService, + assignableTypes, + }: GetAssignFlyoutOpenerOptions): AssignFlyoutOpener => + async ({ tagIds }) => { + const flyout = overlays.openFlyout( + toMountPoint( + }> + flyout.close()} + /> + + ), + { size: 'm', maxWidth: 600 } + ); - return flyout; -}; + return flyout; + }; diff --git a/x-pack/plugins/saved_objects_tagging/public/components/edition_modal/create_modal.tsx b/x-pack/plugins/saved_objects_tagging/public/components/edition_modal/create_modal.tsx index dbb962623b089..298d50f51a2a1 100644 --- a/x-pack/plugins/saved_objects_tagging/public/components/edition_modal/create_modal.tsx +++ b/x-pack/plugins/saved_objects_tagging/public/components/edition_modal/create_modal.tsx @@ -44,12 +44,13 @@ export const CreateTagModal: FC = ({ ); const setField = useCallback( - (field: T) => (value: TagAttributes[T]) => { - setTagAttributes((current) => ({ - ...current, - [field]: value, - })); - }, + (field: T) => + (value: TagAttributes[T]) => { + setTagAttributes((current) => ({ + ...current, + [field]: value, + })); + }, [] ); diff --git a/x-pack/plugins/saved_objects_tagging/public/components/edition_modal/edit_modal.tsx b/x-pack/plugins/saved_objects_tagging/public/components/edition_modal/edit_modal.tsx index ddd4d615ee957..1f6bab129e6d0 100644 --- a/x-pack/plugins/saved_objects_tagging/public/components/edition_modal/edit_modal.tsx +++ b/x-pack/plugins/saved_objects_tagging/public/components/edition_modal/edit_modal.tsx @@ -35,12 +35,13 @@ export const EditTagModal: FC = ({ tag, onSave, onClose, tagC const [tagAttributes, setTagAttributes] = useState(getAttributes(tag)); const setField = useCallback( - (field: T) => (value: TagAttributes[T]) => { - setTagAttributes((current) => ({ - ...current, - [field]: value, - })); - }, + (field: T) => + (value: TagAttributes[T]) => { + setTagAttributes((current) => ({ + ...current, + [field]: value, + })); + }, [] ); diff --git a/x-pack/plugins/saved_objects_tagging/public/components/edition_modal/open_modal.tsx b/x-pack/plugins/saved_objects_tagging/public/components/edition_modal/open_modal.tsx index ebf8d21bc3804..8efc527d06b0e 100644 --- a/x-pack/plugins/saved_objects_tagging/public/components/edition_modal/open_modal.tsx +++ b/x-pack/plugins/saved_objects_tagging/public/components/edition_modal/open_modal.tsx @@ -38,61 +38,56 @@ const LazyEditTagModal = React.lazy(() => import('./edit_modal').then(({ EditTagModal }) => ({ default: EditTagModal })) ); -export const getCreateModalOpener = ({ - overlays, - tagClient, -}: GetModalOpenerOptions): CreateModalOpener => async ({ - onCreate, - defaultValues, -}: OpenCreateModalOptions) => { - const modal = overlays.openModal( - toMountPoint( - }> - { - modal.close(); - }} - onSave={(tag) => { - modal.close(); - onCreate(tag); - }} - tagClient={tagClient} - /> - - ) - ); - return modal; -}; +export const getCreateModalOpener = + ({ overlays, tagClient }: GetModalOpenerOptions): CreateModalOpener => + async ({ onCreate, defaultValues }: OpenCreateModalOptions) => { + const modal = overlays.openModal( + toMountPoint( + }> + { + modal.close(); + }} + onSave={(tag) => { + modal.close(); + onCreate(tag); + }} + tagClient={tagClient} + /> + + ) + ); + return modal; + }; interface OpenEditModalOptions { tagId: string; onUpdate: (tag: Tag) => void; } -export const getEditModalOpener = ({ overlays, tagClient }: GetModalOpenerOptions) => async ({ - tagId, - onUpdate, -}: OpenEditModalOptions) => { - const tag = await tagClient.get(tagId); +export const getEditModalOpener = + ({ overlays, tagClient }: GetModalOpenerOptions) => + async ({ tagId, onUpdate }: OpenEditModalOptions) => { + const tag = await tagClient.get(tagId); - const modal = overlays.openModal( - toMountPoint( - }> - { - modal.close(); - }} - onSave={(saved) => { - modal.close(); - onUpdate(saved); - }} - tagClient={tagClient} - /> - - ) - ); + const modal = overlays.openModal( + toMountPoint( + }> + { + modal.close(); + }} + onSave={(saved) => { + modal.close(); + onUpdate(saved); + }} + tagClient={tagClient} + /> + + ) + ); - return modal; -}; + return modal; + }; diff --git a/x-pack/plugins/saved_objects_tagging/public/management/tag_management_page.tsx b/x-pack/plugins/saved_objects_tagging/public/management/tag_management_page.tsx index e36d1a8afc08f..f93a9541db039 100644 --- a/x-pack/plugins/saved_objects_tagging/public/management/tag_management_page.tsx +++ b/x-pack/plugins/saved_objects_tagging/public/management/tag_management_page.tsx @@ -74,10 +74,10 @@ export const TagManagementPage: FC = ({ fetchTags(); }); - const createModalOpener = useMemo(() => getCreateModalOpener({ overlays, tagClient }), [ - overlays, - tagClient, - ]); + const createModalOpener = useMemo( + () => getCreateModalOpener({ overlays, tagClient }), + [overlays, tagClient] + ); const tableActions = useMemo(() => { return getTableActions({ diff --git a/x-pack/plugins/saved_objects_tagging/public/plugin.test.ts b/x-pack/plugins/saved_objects_tagging/public/plugin.test.ts index b51ca04d8c435..0afd875b52832 100644 --- a/x-pack/plugins/saved_objects_tagging/public/plugin.test.ts +++ b/x-pack/plugins/saved_objects_tagging/public/plugin.test.ts @@ -16,7 +16,7 @@ import { TagsCache } from './services'; import { tagsCacheMock } from './services/tags/tags_cache.mock'; jest.mock('./services/tags/tags_cache'); -const MockedTagsCache = (TagsCache as unknown) as jest.Mock>; +const MockedTagsCache = TagsCache as unknown as jest.Mock>; describe('SavedObjectTaggingPlugin', () => { let plugin: SavedObjectTaggingPlugin; diff --git a/x-pack/plugins/saved_objects_tagging/public/plugin.ts b/x-pack/plugins/saved_objects_tagging/public/plugin.ts index 243ef686eed1e..50525ad86efc2 100644 --- a/x-pack/plugins/saved_objects_tagging/public/plugin.ts +++ b/x-pack/plugins/saved_objects_tagging/public/plugin.ts @@ -22,7 +22,8 @@ interface SetupDeps { } export class SavedObjectTaggingPlugin - implements Plugin<{}, SavedObjectTaggingPluginStart, SetupDeps, {}> { + implements Plugin<{}, SavedObjectTaggingPluginStart, SetupDeps, {}> +{ private tagClient?: TagsClient; private tagCache?: TagsCache; private assignmentService?: TagAssignmentService; diff --git a/x-pack/plugins/saved_objects_tagging/server/config.ts b/x-pack/plugins/saved_objects_tagging/server/config.ts index f4f0bd1cf1aa0..183779aa6f229 100644 --- a/x-pack/plugins/saved_objects_tagging/server/config.ts +++ b/x-pack/plugins/saved_objects_tagging/server/config.ts @@ -16,6 +16,7 @@ const configSchema = schema.object({ export type SavedObjectsTaggingConfigType = TypeOf; export const config: PluginConfigDescriptor = { + deprecations: ({ deprecate }) => [deprecate('enabled', '8.0.0')], schema: configSchema, exposeToBrowser: { cache_refresh_interval: true, diff --git a/x-pack/plugins/saved_objects_tagging/server/services/assignments/assignment_service.ts b/x-pack/plugins/saved_objects_tagging/server/services/assignments/assignment_service.ts index 9314be841411b..222de82b35f6f 100644 --- a/x-pack/plugins/saved_objects_tagging/server/services/assignments/assignment_service.ts +++ b/x-pack/plugins/saved_objects_tagging/server/services/assignments/assignment_service.ts @@ -54,9 +54,8 @@ export class AssignmentService { types, maxResults = 100, }: FindAssignableObjectsOptions): Promise { - const searchedTypes = (types - ? types.filter((type) => taggableTypes.includes(type)) - : taggableTypes + const searchedTypes = ( + types ? types.filter((type) => taggableTypes.includes(type)) : taggableTypes ).filter((type) => this.typeRegistry.getType(type) !== undefined); const assignableTypes = await this.getAssignableTypes(searchedTypes); diff --git a/x-pack/plugins/searchprofiler/public/application/app.tsx b/x-pack/plugins/searchprofiler/public/application/app.tsx index 459b3e6e04b8d..b7c11e6fcf4c5 100644 --- a/x-pack/plugins/searchprofiler/public/application/app.tsx +++ b/x-pack/plugins/searchprofiler/public/application/app.tsx @@ -35,13 +35,8 @@ import { Targets } from './types'; export const App = () => { const { getLicenseStatus, notifications } = useAppContext(); - const { - activeTab, - currentResponse, - highlightDetails, - pristine, - profiling, - } = useProfilerReadContext(); + const { activeTab, currentResponse, highlightDetails, pristine, profiling } = + useProfilerReadContext(); const dispatch = useProfilerActionContext(); @@ -58,9 +53,10 @@ export const App = () => { [dispatch] ); - const onHighlight = useCallback((value) => dispatch({ type: 'setHighlightDetails', value }), [ - dispatch, - ]); + const onHighlight = useCallback( + (value) => dispatch({ type: 'setHighlightDetails', value }), + [dispatch] + ); const renderLicenseWarning = () => { return !getLicenseStatus().valid ? ( diff --git a/x-pack/plugins/searchprofiler/public/application/components/profile_tree/__jest__/fixtures/breakdown.ts b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/__jest__/fixtures/breakdown.ts index eea31892fe966..ff812daf89c55 100644 --- a/x-pack/plugins/searchprofiler/public/application/components/profile_tree/__jest__/fixtures/breakdown.ts +++ b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/__jest__/fixtures/breakdown.ts @@ -42,16 +42,14 @@ export const normalized = [ time: 6273, relative: '0.1', color: '#f5f5f5', - tip: - 'The time taken to create the Scoring object, which is later used to execute the actual scoring of each doc.', + tip: 'The time taken to create the Scoring object, which is later used to execute the actual scoring of each doc.', }, { key: 'create_weight', time: 1852, relative: '0.0', color: '#f5f5f5', - tip: - 'The time taken to create the Weight object, which holds temporary information during scoring.', + tip: 'The time taken to create the Weight object, which holds temporary information during scoring.', }, { key: 'build_scorer_count', time: 2, relative: 0, color: '#f5f5f5', tip: '' }, { key: 'create_weight_count', time: 1, relative: 0, color: '#f5f5f5', tip: '' }, @@ -68,8 +66,7 @@ export const normalized = [ time: 0, relative: '0.0', color: '#f5f5f5', - tip: - 'The time taken to execute a secondary, more precise scoring phase (used by phrase queries).', + tip: 'The time taken to execute a secondary, more precise scoring phase (used by phrase queries).', }, { key: 'match_count', time: 0, relative: 0, color: '#f5f5f5', tip: '' }, ]; diff --git a/x-pack/plugins/searchprofiler/public/application/components/profile_tree/__jest__/fixtures/normalize_times.ts b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/__jest__/fixtures/normalize_times.ts index b7d6a61f9cf54..b0d31939b677b 100644 --- a/x-pack/plugins/searchprofiler/public/application/components/profile_tree/__jest__/fixtures/normalize_times.ts +++ b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/__jest__/fixtures/normalize_times.ts @@ -16,16 +16,14 @@ export const inputTimes = [ time: 401690, relative: '89.8', color: '#feb6b6', - tip: - 'The time taken to create the Weight object, which holds temporary information during scoring.', + tip: 'The time taken to create the Weight object, which holds temporary information during scoring.', }, { key: 'build_scorer', time: 45672, relative: '10.2', color: '#f6eeee', - tip: - 'The time taken to create the Scoring object, which is later used to execute the actual scoring of each doc.', + tip: 'The time taken to create the Scoring object, which is later used to execute the actual scoring of each doc.', }, { key: 'build_scorer_count', @@ -53,8 +51,7 @@ export const inputTimes = [ time: 0, relative: '0.0', color: '#f5f5f5', - tip: - 'The time taken to execute a secondary, more precise scoring phase (used by phrase queries).', + tip: 'The time taken to execute a secondary, more precise scoring phase (used by phrase queries).', }, { key: 'match_count', @@ -110,16 +107,14 @@ export const inputTimes = [ time: 190989, relative: '99.2', color: '#ffb0b0', - tip: - 'The time taken to create the Weight object, which holds temporary information during scoring.', + tip: 'The time taken to create the Weight object, which holds temporary information during scoring.', }, { key: 'build_scorer', time: 1510, relative: '0.8', color: '#f5f4f4', - tip: - 'The time taken to create the Scoring object, which is later used to execute the actual scoring of each doc.', + tip: 'The time taken to create the Scoring object, which is later used to execute the actual scoring of each doc.', }, { key: 'build_scorer_count', @@ -147,8 +142,7 @@ export const inputTimes = [ time: 0, relative: '0.0', color: '#f5f5f5', - tip: - 'The time taken to execute a secondary, more precise scoring phase (used by phrase queries).', + tip: 'The time taken to execute a secondary, more precise scoring phase (used by phrase queries).', }, { key: 'match_count', @@ -208,16 +202,14 @@ export const inputTimes = [ time: 162016, relative: '99.6', color: '#ffafaf', - tip: - 'The time taken to create the Weight object, which holds temporary information during scoring.', + tip: 'The time taken to create the Weight object, which holds temporary information during scoring.', }, { key: 'build_scorer', time: 589, relative: '0.4', color: '#f5f5f5', - tip: - 'The time taken to create the Scoring object, which is later used to execute the actual scoring of each doc.', + tip: 'The time taken to create the Scoring object, which is later used to execute the actual scoring of each doc.', }, { key: 'build_scorer_count', @@ -245,8 +237,7 @@ export const inputTimes = [ time: 0, relative: '0.0', color: '#f5f5f5', - tip: - 'The time taken to execute a secondary, more precise scoring phase (used by phrase queries).', + tip: 'The time taken to execute a secondary, more precise scoring phase (used by phrase queries).', }, { key: 'match_count', @@ -306,16 +297,14 @@ export const inputTimes = [ time: 32522, relative: '92.5', color: '#feb4b4', - tip: - 'The time taken to create the Scoring object, which is later used to execute the actual scoring of each doc.', + tip: 'The time taken to create the Scoring object, which is later used to execute the actual scoring of each doc.', }, { key: 'create_weight', time: 2645, relative: '7.5', color: '#f6f0f0', - tip: - 'The time taken to create the Weight object, which holds temporary information during scoring.', + tip: 'The time taken to create the Weight object, which holds temporary information during scoring.', }, { key: 'build_scorer_count', @@ -343,8 +332,7 @@ export const inputTimes = [ time: 0, relative: '0.0', color: '#f5f5f5', - tip: - 'The time taken to execute a secondary, more precise scoring phase (used by phrase queries).', + tip: 'The time taken to execute a secondary, more precise scoring phase (used by phrase queries).', }, { key: 'match_count', @@ -417,16 +405,14 @@ export const normalizedTimes = [ time: 401690, relative: '89.8', color: '#feb6b6', - tip: - 'The time taken to create the Weight object, which holds temporary information during scoring.', + tip: 'The time taken to create the Weight object, which holds temporary information during scoring.', }, { key: 'build_scorer', time: 45672, relative: '10.2', color: '#f6eeee', - tip: - 'The time taken to create the Scoring object, which is later used to execute the actual scoring of each doc.', + tip: 'The time taken to create the Scoring object, which is later used to execute the actual scoring of each doc.', }, { key: 'build_scorer_count', @@ -454,8 +440,7 @@ export const normalizedTimes = [ time: 0, relative: '0.0', color: '#f5f5f5', - tip: - 'The time taken to execute a secondary, more precise scoring phase (used by phrase queries).', + tip: 'The time taken to execute a secondary, more precise scoring phase (used by phrase queries).', }, { key: 'match_count', @@ -511,16 +496,14 @@ export const normalizedTimes = [ time: 190989, relative: '99.2', color: '#ffb0b0', - tip: - 'The time taken to create the Weight object, which holds temporary information during scoring.', + tip: 'The time taken to create the Weight object, which holds temporary information during scoring.', }, { key: 'build_scorer', time: 1510, relative: '0.8', color: '#f5f4f4', - tip: - 'The time taken to create the Scoring object, which is later used to execute the actual scoring of each doc.', + tip: 'The time taken to create the Scoring object, which is later used to execute the actual scoring of each doc.', }, { key: 'build_scorer_count', @@ -548,8 +531,7 @@ export const normalizedTimes = [ time: 0, relative: '0.0', color: '#f5f5f5', - tip: - 'The time taken to execute a secondary, more precise scoring phase (used by phrase queries).', + tip: 'The time taken to execute a secondary, more precise scoring phase (used by phrase queries).', }, { key: 'match_count', @@ -611,16 +593,14 @@ export const normalizedTimes = [ time: 162016, relative: '99.6', color: '#ffafaf', - tip: - 'The time taken to create the Weight object, which holds temporary information during scoring.', + tip: 'The time taken to create the Weight object, which holds temporary information during scoring.', }, { key: 'build_scorer', time: 589, relative: '0.4', color: '#f5f5f5', - tip: - 'The time taken to create the Scoring object, which is later used to execute the actual scoring of each doc.', + tip: 'The time taken to create the Scoring object, which is later used to execute the actual scoring of each doc.', }, { key: 'build_scorer_count', @@ -648,8 +628,7 @@ export const normalizedTimes = [ time: 0, relative: '0.0', color: '#f5f5f5', - tip: - 'The time taken to execute a secondary, more precise scoring phase (used by phrase queries).', + tip: 'The time taken to execute a secondary, more precise scoring phase (used by phrase queries).', }, { key: 'match_count', @@ -711,16 +690,14 @@ export const normalizedTimes = [ time: 32522, relative: '92.5', color: '#feb4b4', - tip: - 'The time taken to create the Scoring object, which is later used to execute the actual scoring of each doc.', + tip: 'The time taken to create the Scoring object, which is later used to execute the actual scoring of each doc.', }, { key: 'create_weight', time: 2645, relative: '7.5', color: '#f6f0f0', - tip: - 'The time taken to create the Weight object, which holds temporary information during scoring.', + tip: 'The time taken to create the Weight object, which holds temporary information during scoring.', }, { key: 'build_scorer_count', @@ -748,8 +725,7 @@ export const normalizedTimes = [ time: 0, relative: '0.0', color: '#f5f5f5', - tip: - 'The time taken to execute a secondary, more precise scoring phase (used by phrase queries).', + tip: 'The time taken to execute a secondary, more precise scoring phase (used by phrase queries).', }, { key: 'match_count', diff --git a/x-pack/plugins/searchprofiler/public/application/components/profile_tree/__jest__/fixtures/processed_search_response.ts b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/__jest__/fixtures/processed_search_response.ts index c2a9bfa6506e5..aa2e2f459ec40 100644 --- a/x-pack/plugins/searchprofiler/public/application/components/profile_tree/__jest__/fixtures/processed_search_response.ts +++ b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/__jest__/fixtures/processed_search_response.ts @@ -48,16 +48,14 @@ const search1 = { time: 40061, relative: '68.7', color: '#fcc5c5', - tip: - 'The time taken to create the Scoring object, which is later used to execute the actual scoring of each doc.', + tip: 'The time taken to create the Scoring object, which is later used to execute the actual scoring of each doc.', }, { key: 'create_weight', time: 8238, relative: '14.1', color: '#f6ebeb', - tip: - 'The time taken to create the Weight object, which holds temporary information during scoring.', + tip: 'The time taken to create the Weight object, which holds temporary information during scoring.', }, { key: 'next_doc', @@ -134,8 +132,7 @@ const search1 = { time: 0, relative: '0.0', color: '#f5f5f5', - tip: - 'The time taken to execute a secondary, more precise scoring phase (used by phrase queries).', + tip: 'The time taken to execute a secondary, more precise scoring phase (used by phrase queries).', }, { key: 'match_count', @@ -198,8 +195,7 @@ const search1Child = { time: 24059, relative: '81.3', color: '#fdbcbc', - tip: - 'The time taken to create the Scoring object, which is later used to execute the actual scoring of each doc.', + tip: 'The time taken to create the Scoring object, which is later used to execute the actual scoring of each doc.', }, { key: 'next_doc', @@ -213,8 +209,7 @@ const search1Child = { time: 1586, relative: '5.4', color: '#f6f1f1', - tip: - 'The time taken to create the Weight object, which holds temporary information during scoring.', + tip: 'The time taken to create the Weight object, which holds temporary information during scoring.', }, { key: 'advance', @@ -270,8 +265,7 @@ const search1Child = { time: 0, relative: '0.0', color: '#f5f5f5', - tip: - 'The time taken to execute a secondary, more precise scoring phase (used by phrase queries).', + tip: 'The time taken to execute a secondary, more precise scoring phase (used by phrase queries).', }, { key: 'match_count', diff --git a/x-pack/plugins/security/common/constants.ts b/x-pack/plugins/security/common/constants.ts index 6d808f2305c91..77e93b856d206 100644 --- a/x-pack/plugins/security/common/constants.ts +++ b/x-pack/plugins/security/common/constants.ts @@ -51,7 +51,8 @@ export const NEXT_URL_QUERY_STRING_PARAMETER = 'next'; * - Must contain only letters, numbers, spaces, punctuation and printable symbols. * - Must not contain leading or trailing spaces. */ -export const NAME_REGEX = /^(?! )[a-zA-Z0-9 !"#$%&'()*+,\-./\\:;<=>?@\[\]^_`{|}~]*[a-zA-Z0-9!"#$%&'()*+,\-./\\:;<=>?@\[\]^_`{|}~]$/; +export const NAME_REGEX = + /^(?! )[a-zA-Z0-9 !"#$%&'()*+,\-./\\:;<=>?@\[\]^_`{|}~]*[a-zA-Z0-9!"#$%&'()*+,\-./\\:;<=>?@\[\]^_`{|}~]$/; /** * Maximum length of usernames and role names. diff --git a/x-pack/plugins/security/public/account_management/account_management_app.ts b/x-pack/plugins/security/public/account_management/account_management_app.ts index e94f8862f1e59..69a5155fa51a7 100644 --- a/x-pack/plugins/security/public/account_management/account_management_app.ts +++ b/x-pack/plugins/security/public/account_management/account_management_app.ts @@ -29,15 +29,9 @@ export const accountManagementApp = Object.freeze({ navLinkStatus: AppNavLinkStatus.hidden, appRoute: '/security/account', async mount({ element }: AppMountParameters) { - const [ - [coreStart], - { renderAccountManagementPage }, - { UserAPIClient }, - ] = await Promise.all([ - getStartServices(), - import('./account_management_page'), - import('../management'), - ]); + const [[coreStart], { renderAccountManagementPage }, { UserAPIClient }] = await Promise.all( + [getStartServices(), import('./account_management_page'), import('../management')] + ); coreStart.chrome.setBreadcrumbs([{ text: title }]); diff --git a/x-pack/plugins/security/public/authentication/overwritten_session/overwritten_session_app.test.ts b/x-pack/plugins/security/public/authentication/overwritten_session/overwritten_session_app.test.ts index 9ea6293612968..37a46a0dcad8a 100644 --- a/x-pack/plugins/security/public/authentication/overwritten_session/overwritten_session_app.test.ts +++ b/x-pack/plugins/security/public/authentication/overwritten_session/overwritten_session_app.test.ts @@ -58,8 +58,9 @@ describe('overwrittenSessionApp', () => { history: scopedHistoryMock.create(), }); - const mockRenderApp = jest.requireMock('./overwritten_session_page') - .renderOverwrittenSessionPage; + const mockRenderApp = jest.requireMock( + './overwritten_session_page' + ).renderOverwrittenSessionPage; expect(mockRenderApp).toHaveBeenCalledTimes(1); expect(mockRenderApp).toHaveBeenCalledWith(coreStartMock.i18n, containerMock, { authc: authcMock, diff --git a/x-pack/plugins/security/public/components/token_field.tsx b/x-pack/plugins/security/public/components/token_field.tsx index 98eee9352937c..38a8e45cbb5b5 100644 --- a/x-pack/plugins/security/public/components/token_field.tsx +++ b/x-pack/plugins/security/public/components/token_field.tsx @@ -22,7 +22,7 @@ import type { FunctionComponent, ReactElement } from 'react'; import React from 'react'; import { i18n } from '@kbn/i18n'; -import { euiThemeVars } from '@kbn/ui-shared-deps/theme'; +import { euiThemeVars } from '@kbn/ui-shared-deps-src/theme'; export interface TokenFieldProps extends Omit { value: string; diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.tsx index 9b05876c419d9..dcf2a7bfe5165 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.tsx +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.tsx @@ -111,15 +111,8 @@ export class APIKeysGridPage extends Component { } public renderContent() { - const { - isLoadingApp, - isLoadingTable, - areApiKeysEnabled, - isAdmin, - canManage, - error, - apiKeys, - } = this.state; + const { isLoadingApp, isLoadingTable, areApiKeysEnabled, isAdmin, canManage, error, apiKeys } = + this.state; if (!apiKeys) { if (isLoadingApp) { @@ -620,11 +613,8 @@ export class APIKeysGridPage extends Component { private async checkPrivileges() { try { - const { - isAdmin, - canManage, - areApiKeysEnabled, - } = await this.props.apiKeysAPIClient.checkPrivileges(); + const { isAdmin, canManage, areApiKeysEnabled } = + await this.props.apiKeysAPIClient.checkPrivileges(); this.setState({ isAdmin, canManage, areApiKeysEnabled }); if (!canManage || !areApiKeysEnabled) { diff --git a/x-pack/plugins/security/public/management/management_service.test.ts b/x-pack/plugins/security/public/management/management_service.test.ts index e969c8fcd0dbc..b0f35e76b6dfa 100644 --- a/x-pack/plugins/security/public/management/management_service.test.ts +++ b/x-pack/plugins/security/public/management/management_service.test.ts @@ -88,7 +88,7 @@ describe('ManagementService', () => { const { fatalErrors, getStartServices } = coreMock.createSetup(); const licenseSubject = new BehaviorSubject( - (initialFeatures as unknown) as SecurityLicenseFeatures + initialFeatures as unknown as SecurityLicenseFeatures ); const license = licenseMock.create(); license.features$ = licenseSubject; @@ -116,7 +116,7 @@ describe('ManagementService', () => { const getMockedApp = (id: string) => { // All apps are enabled by default. let enabled = true; - return ({ + return { id, get enabled() { return enabled; @@ -127,7 +127,7 @@ describe('ManagementService', () => { disable: jest.fn().mockImplementation(() => { enabled = false; }), - } as unknown) as jest.Mocked; + } as unknown as jest.Mocked; }; mockSection.getApp = jest.fn().mockImplementation((id) => mockApps.get(id)); const mockApps = new Map>([ @@ -155,7 +155,7 @@ describe('ManagementService', () => { return { mockApps, updateFeatures(features: Partial) { - licenseSubject.next((features as unknown) as SecurityLicenseFeatures); + licenseSubject.next(features as unknown as SecurityLicenseFeatures); }, }; } diff --git a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/role_selector/role_template_editor.test.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/role_selector/role_template_editor.test.tsx index a4a388e20e66b..844d7b48321c5 100644 --- a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/role_selector/role_template_editor.test.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/role_selector/role_template_editor.test.tsx @@ -26,9 +26,9 @@ describe('RoleTemplateEditor', () => { }; const wrapper = mountWithIntl(); - (wrapper - .find('EuiFieldText[data-test-subj="roleTemplateSourceEditor"]') - .props() as any).onChange({ target: { value: 'new_script' } }); + ( + wrapper.find('EuiFieldText[data-test-subj="roleTemplateSourceEditor"]').props() as any + ).onChange({ target: { value: 'new_script' } }); expect(props.onChange).toHaveBeenCalledWith({ template: { @@ -89,9 +89,9 @@ describe('RoleTemplateEditor', () => { }; const wrapper = mountWithIntl(); - (wrapper - .find('EuiComboBox[data-test-subj="roleMappingsFormTemplateType"]') - .props() as any).onChange('stored'); + ( + wrapper.find('EuiComboBox[data-test-subj="roleMappingsFormTemplateType"]').props() as any + ).onChange('stored'); expect(props.onChange).toHaveBeenCalledWith({ template: { diff --git a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/services/role_mapping_validation.test.ts b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/services/role_mapping_validation.test.ts index 85029de671c3f..5ab690b8253bb 100644 --- a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/services/role_mapping_validation.test.ts +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/services/role_mapping_validation.test.ts @@ -27,7 +27,7 @@ describe('validateRoleMappingName', () => { describe('validateRoleMappingRoles', () => { it('requires a value', () => { - expect(validateRoleMappingRoles(({ roles: [] } as unknown) as RoleMapping)) + expect(validateRoleMappingRoles({ roles: [] } as unknown as RoleMapping)) .toMatchInlineSnapshot(` Object { "error": "At least one role is required.", @@ -39,7 +39,7 @@ describe('validateRoleMappingRoles', () => { describe('validateRoleMappingRoleTemplates', () => { it('requires a value', () => { - expect(validateRoleMappingRoleTemplates(({ role_templates: [] } as unknown) as RoleMapping)) + expect(validateRoleMappingRoleTemplates({ role_templates: [] } as unknown as RoleMapping)) .toMatchInlineSnapshot(` Object { "error": "At least one role template is required.", @@ -61,7 +61,7 @@ describe('validateRoleMappingRules', () => { // more exhaustive testing is done in other unit tests it('requires rules to be valid', () => { - expect(validateRoleMappingRules(({ rules: { something: [] } } as unknown) as RoleMapping)) + expect(validateRoleMappingRules({ rules: { something: [] } } as unknown as RoleMapping)) .toMatchInlineSnapshot(` Object { "error": "Unknown rule type: something.", @@ -74,11 +74,11 @@ describe('validateRoleMappingRules', () => { describe('validateRoleMappingForSave', () => { it('fails if the role mapping is missing a name', () => { expect( - validateRoleMappingForSave(({ + validateRoleMappingForSave({ enabled: true, roles: ['superuser'], rules: { field: { username: '*' } }, - } as unknown) as RoleMapping) + } as unknown as RoleMapping) ).toMatchInlineSnapshot(` Object { "error": "Name is required.", @@ -89,12 +89,12 @@ describe('validateRoleMappingForSave', () => { it('fails if the role mapping is missing rules', () => { expect( - validateRoleMappingForSave(({ + validateRoleMappingForSave({ name: 'foo', enabled: true, roles: ['superuser'], rules: {}, - } as unknown) as RoleMapping) + } as unknown as RoleMapping) ).toMatchInlineSnapshot(` Object { "error": "At least one rule is required.", @@ -105,13 +105,13 @@ describe('validateRoleMappingForSave', () => { it('fails if the role mapping is missing both roles and templates', () => { expect( - validateRoleMappingForSave(({ + validateRoleMappingForSave({ name: 'foo', enabled: true, roles: [], role_templates: [], rules: { field: { username: '*' } }, - } as unknown) as RoleMapping) + } as unknown as RoleMapping) ).toMatchInlineSnapshot(` Object { "error": "At least one role is required.", @@ -122,13 +122,13 @@ describe('validateRoleMappingForSave', () => { it('validates a correct role mapping using role templates', () => { expect( - validateRoleMappingForSave(({ + validateRoleMappingForSave({ name: 'foo', enabled: true, roles: [], role_templates: [{ template: { id: 'foo' } }], rules: { field: { username: '*' } }, - } as unknown) as RoleMapping) + } as unknown as RoleMapping) ).toMatchInlineSnapshot(` Object { "isInvalid": false, @@ -138,12 +138,12 @@ describe('validateRoleMappingForSave', () => { it('validates a correct role mapping using roles', () => { expect( - validateRoleMappingForSave(({ + validateRoleMappingForSave({ name: 'foo', enabled: true, roles: ['superuser'], rules: { field: { username: '*' } }, - } as unknown) as RoleMapping) + } as unknown as RoleMapping) ).toMatchInlineSnapshot(` Object { "isInvalid": false, diff --git a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/services/role_mapping_validation.ts b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/services/role_mapping_validation.ts index d934e48df059e..7340277158c84 100644 --- a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/services/role_mapping_validation.ts +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/services/role_mapping_validation.ts @@ -70,10 +70,8 @@ export function validateRoleMappingRules({ rules }: Pick): export function validateRoleMappingForSave(roleMapping: RoleMapping): ValidationResult { const { isInvalid: isNameInvalid, error: nameError } = validateRoleMappingName(roleMapping); const { isInvalid: areRolesInvalid, error: rolesError } = validateRoleMappingRoles(roleMapping); - const { - isInvalid: areRoleTemplatesInvalid, - error: roleTemplatesError, - } = validateRoleMappingRoleTemplates(roleMapping); + const { isInvalid: areRoleTemplatesInvalid, error: roleTemplatesError } = + validateRoleMappingRoleTemplates(roleMapping); const { isInvalid: areRulesInvalid, error: rulesError } = validateRoleMappingRules(roleMapping); diff --git a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/services/role_template_type.ts b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/services/role_template_type.ts index 0735e03f819cb..ce16c2516f176 100644 --- a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/services/role_template_type.ts +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/services/role_template_type.ts @@ -18,7 +18,7 @@ export function isStoredRoleTemplate( return ( roleMappingTemplate.template != null && roleMappingTemplate.template.hasOwnProperty('id') && - typeof ((roleMappingTemplate as unknown) as StoredRoleTemplate).template.id === 'string' + typeof (roleMappingTemplate as unknown as StoredRoleTemplate).template.id === 'string' ); } @@ -28,7 +28,7 @@ export function isInlineRoleTemplate( return ( roleMappingTemplate.template != null && roleMappingTemplate.template.hasOwnProperty('source') && - typeof ((roleMappingTemplate as unknown) as InlineRoleTemplate).template.source === 'string' + typeof (roleMappingTemplate as unknown as InlineRoleTemplate).template.source === 'string' ); } diff --git a/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.tsx b/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.tsx index 60999c6b0a799..ec386d75228e8 100644 --- a/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.tsx @@ -440,10 +440,8 @@ export class RoleMappingsGridPage extends Component { private async checkPrivileges() { try { - const { - canManageRoleMappings, - hasCompatibleRealms, - } = await this.props.roleMappingsAPI.checkRoleMappingFeatures(); + const { canManageRoleMappings, hasCompatibleRealms } = + await this.props.roleMappingsAPI.checkRoleMappingFeatures(); this.setState({ loadState: canManageRoleMappings ? this.state.loadState : 'permissionDenied', diff --git a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx index 3a66b6d80c615..51aaa988da2a4 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx @@ -184,10 +184,8 @@ function useRole( privileges: [], }; - const { - allowRoleDocumentLevelSecurity, - allowRoleFieldLevelSecurity, - } = license.getFeatures(); + const { allowRoleDocumentLevelSecurity, allowRoleFieldLevelSecurity } = + license.getFeatures(); if (allowRoleFieldLevelSecurity) { emptyOption.field_security = { diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privilege_form.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privilege_form.tsx index a7d7d2403943c..01592636154d6 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privilege_form.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privilege_form.tsx @@ -189,12 +189,8 @@ export class IndexPrivilegeForm extends Component { }; private getFieldLevelControls = () => { - const { - allowFieldLevelSecurity, - allowDocumentLevelSecurity, - indexPrivilege, - isRoleReadOnly, - } = this.props; + const { allowFieldLevelSecurity, allowDocumentLevelSecurity, indexPrivilege, isRoleReadOnly } = + this.props; if (!allowFieldLevelSecurity) { return null; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/__fixtures__/index.ts b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/__fixtures__/index.ts index b43a6bee82e27..3fab9d86e4dd3 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/__fixtures__/index.ts +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/__fixtures__/index.ts @@ -38,16 +38,16 @@ export function getDisplayedFeaturePrivileges(wrapper: ReactWrapper) { const subFeatureForm = featureControls.find(SubFeatureForm); if (subFeatureForm.length > 0) { - const independentPrivileges = (subFeatureForm.find( - EuiCheckbox - ) as ReactWrapper).reduce((acc2, checkbox) => { + const independentPrivileges = ( + subFeatureForm.find(EuiCheckbox) as ReactWrapper + ).reduce((acc2, checkbox) => { const { id: privilegeId, checked } = checkbox.props(); return checked ? [...acc2, privilegeId] : acc2; }, [] as string[]); - const mutuallyExclusivePrivileges = (subFeatureForm.find( - EuiButtonGroup - ) as ReactWrapper).reduce((acc2, subPrivButtonGroup) => { + const mutuallyExclusivePrivileges = ( + subFeatureForm.find(EuiButtonGroup) as ReactWrapper + ).reduce((acc2, subPrivButtonGroup) => { const { idSelected: selectedSubPrivilege } = subPrivButtonGroup.props(); return selectedSubPrivilege && selectedSubPrivilege !== 'none' ? [...acc2, selectedSubPrivilege] diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.tsx index 50d40fbdc4751..02e497fda1516 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.tsx @@ -266,10 +266,11 @@ export class FeatureTable extends Component { return null; } - const selectedPrivilegeId = this.props.privilegeCalculator.getDisplayedPrimaryFeaturePrivilegeId( - feature.id, - this.props.privilegeIndex - ); + const selectedPrivilegeId = + this.props.privilegeCalculator.getDisplayedPrimaryFeaturePrivilegeId( + feature.id, + this.props.privilegeIndex + ); const options = primaryFeaturePrivileges.map((privilege) => { return { diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/sub_feature_form.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/sub_feature_form.tsx index 494f3ec7d9acb..d123773fed1c7 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/sub_feature_form.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/sub_feature_form.tsx @@ -99,11 +99,12 @@ export const SubFeatureForm = (props: Props) => { privilegeGroup: SubFeaturePrivilegeGroup, index: number ) { - const firstSelectedPrivilege = props.privilegeCalculator.getSelectedMutuallyExclusiveSubFeaturePrivilege( - props.featureId, - privilegeGroup, - props.privilegeIndex - ); + const firstSelectedPrivilege = + props.privilegeCalculator.getSelectedMutuallyExclusiveSubFeaturePrivilege( + props.featureId, + privilegeGroup, + props.privilegeIndex + ); const options = [ ...privilegeGroup.privileges.map((privilege, privilegeIndex) => { diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.test.tsx index 9312a2eb78018..dd1e4f265266a 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.test.tsx @@ -375,7 +375,7 @@ describe('PrivilegeSpaceForm', () => { const onChange = jest.fn(); - const canCustomize = (Symbol('can customize') as unknown) as boolean; + const canCustomize = Symbol('can customize') as unknown as boolean; const wrapper = mountWithIntl( { spaces: ['foo'], }, ]; - const collection = kibanaPrivileges.createCollectionFromRoleKibanaPrivileges( - assignedPrivileges - ); + const collection = + kibanaPrivileges.createCollectionFromRoleKibanaPrivileges(assignedPrivileges); expect( collection.grantsPrivilege( diff --git a/x-pack/plugins/security/public/management/users/edit_user/user_form.tsx b/x-pack/plugins/security/public/management/users/edit_user/user_form.tsx index 8101c09d64907..8a420314e1aa3 100644 --- a/x-pack/plugins/security/public/management/users/edit_user/user_form.tsx +++ b/x-pack/plugins/security/public/management/users/edit_user/user_form.tsx @@ -75,9 +75,10 @@ export const UserForm: FunctionComponent = ({ }) => { const { services } = useKibana(); - const [rolesState, getRoles] = useAsyncFn(() => new RolesAPIClient(services.http!).getRoles(), [ - services.http, - ]); + const [rolesState, getRoles] = useAsyncFn( + () => new RolesAPIClient(services.http!).getRoles(), + [services.http] + ); // eslint-disable-next-line react-hooks/exhaustive-deps const getUsersThrottled = useCallback( diff --git a/x-pack/plugins/security/public/plugin.test.tsx b/x-pack/plugins/security/public/plugin.test.tsx index bcc69bbfbd853..258b0ef9ec6f5 100644 --- a/x-pack/plugins/security/public/plugin.test.tsx +++ b/x-pack/plugins/security/public/plugin.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import BroadcastChannel from 'broadcast-channel'; +import { enforceOptions } from 'broadcast-channel'; import { Observable } from 'rxjs'; import type { CoreSetup } from 'src/core/public'; @@ -22,10 +22,10 @@ import { SecurityPlugin } from './plugin'; describe('Security Plugin', () => { beforeAll(() => { - BroadcastChannel.enforceOptions({ type: 'simulate' }); + enforceOptions({ type: 'simulate' }); }); afterAll(() => { - BroadcastChannel.enforceOptions(null); + enforceOptions(null); }); describe('#setup', () => { diff --git a/x-pack/plugins/security/public/plugin.tsx b/x-pack/plugins/security/public/plugin.tsx index fbb282ee246f9..78144f0717164 100644 --- a/x-pack/plugins/security/public/plugin.tsx +++ b/x-pack/plugins/security/public/plugin.tsx @@ -55,7 +55,8 @@ export class SecurityPlugin SecurityPluginStart, PluginSetupDependencies, PluginStartDependencies - > { + > +{ private sessionTimeout!: SessionTimeout; private readonly authenticationService = new AuthenticationService(); private readonly navControlService = new SecurityNavControlService(); diff --git a/x-pack/plugins/security/server/__snapshots__/prompt_page.test.tsx.snap b/x-pack/plugins/security/server/__snapshots__/prompt_page.test.tsx.snap index 2ee2337fc9aeb..5ab79e72d7274 100644 --- a/x-pack/plugins/security/server/__snapshots__/prompt_page.test.tsx.snap +++ b/x-pack/plugins/security/server/__snapshots__/prompt_page.test.tsx.snap @@ -1,5 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`PromptPage renders as expected with additional scripts 1`] = `"ElasticMockedFonts

Some Title

Some Body
Action#1
Action#2
"`; +exports[`PromptPage renders as expected with additional scripts 1`] = `"ElasticMockedFonts

Some Title

Some Body
Action#1
Action#2
"`; -exports[`PromptPage renders as expected without additional scripts 1`] = `"ElasticMockedFonts

Some Title

Some Body
Action#1
Action#2
"`; +exports[`PromptPage renders as expected without additional scripts 1`] = `"ElasticMockedFonts

Some Title

Some Body
Action#1
Action#2
"`; diff --git a/x-pack/plugins/security/server/anonymous_access/anonymous_access_service.test.ts b/x-pack/plugins/security/server/anonymous_access/anonymous_access_service.test.ts index 3089ac580a39b..5bbf6b317e3c5 100644 --- a/x-pack/plugins/security/server/anonymous_access/anonymous_access_service.test.ts +++ b/x-pack/plugins/security/server/anonymous_access/anonymous_access_service.test.ts @@ -223,9 +223,7 @@ describe('AnonymousAccessService', () => { ); expect(startParams.capabilities.resolveCapabilities).toHaveBeenCalledTimes(1); - expect( - startParams.capabilities.resolveCapabilities - ).toHaveBeenCalledWith( + expect(startParams.capabilities.resolveCapabilities).toHaveBeenCalledWith( expect.objectContaining({ headers: { authorization: 'Basic dXNlcjpwYXNzd29yZA==' } }), { useDefaultCapabilities: false } ); @@ -245,9 +243,7 @@ describe('AnonymousAccessService', () => { resolvedCapabilities ); expect(startParams.capabilities.resolveCapabilities).toHaveBeenCalledTimes(1); - expect( - startParams.capabilities.resolveCapabilities - ).toHaveBeenCalledWith( + expect(startParams.capabilities.resolveCapabilities).toHaveBeenCalledWith( expect.objectContaining({ headers: { authorization: 'Basic dXNlcjpwYXNzd29yZA==' } }), { useDefaultCapabilities: false } ); diff --git a/x-pack/plugins/security/server/anonymous_access/anonymous_access_service.ts b/x-pack/plugins/security/server/anonymous_access/anonymous_access_service.ts index ece0c63350995..3d8601ab4734a 100644 --- a/x-pack/plugins/security/server/anonymous_access/anonymous_access_service.ts +++ b/x-pack/plugins/security/server/anonymous_access/anonymous_access_service.ts @@ -165,7 +165,7 @@ export class AnonymousAccessService { * anonymous service account credentials. */ private createFakeAnonymousRequest({ authenticateRequest }: { authenticateRequest: boolean }) { - return KibanaRequest.from(({ + return KibanaRequest.from({ headers: authenticateRequest && this.httpAuthorizationHeader ? { authorization: this.httpAuthorizationHeader.toString() } @@ -177,6 +177,6 @@ export class AnonymousAccessService { route: { settings: {} }, url: { href: '/' }, raw: { req: { url: '/' } }, - } as unknown) as Request); + } as unknown as Request); } } diff --git a/x-pack/plugins/security/server/audit/index.mock.ts b/x-pack/plugins/security/server/audit/index.mock.ts index f8816f07ed3ae..15fb4c42c3516 100644 --- a/x-pack/plugins/security/server/audit/index.mock.ts +++ b/x-pack/plugins/security/server/audit/index.mock.ts @@ -10,11 +10,11 @@ import type { SecurityAuditLogger } from './security_audit_logger'; export const securityAuditLoggerMock = { create() { - return ({ + return { savedObjectsAuthorizationFailure: jest.fn(), savedObjectsAuthorizationSuccess: jest.fn(), accessAgreementAcknowledged: jest.fn(), - } as unknown) as jest.Mocked; + } as unknown as jest.Mocked; }, }; diff --git a/x-pack/plugins/security/server/authentication/__snapshots__/unauthenticated_page.test.tsx.snap b/x-pack/plugins/security/server/authentication/__snapshots__/unauthenticated_page.test.tsx.snap index 2e7f3d49e478f..8e6d6a2677787 100644 --- a/x-pack/plugins/security/server/authentication/__snapshots__/unauthenticated_page.test.tsx.snap +++ b/x-pack/plugins/security/server/authentication/__snapshots__/unauthenticated_page.test.tsx.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`UnauthenticatedPage renders as expected 1`] = `"ElasticMockedFonts
"`; +exports[`UnauthenticatedPage renders as expected 1`] = `"ElasticMockedFonts

We couldn't log you in

We hit an authentication error. Please check your credentials and try again. If you still can't log in, contact your system administrator.

"`; diff --git a/x-pack/plugins/security/server/authentication/authentication_service.test.ts b/x-pack/plugins/security/server/authentication/authentication_service.test.ts index a7ef6b34616c3..3a11a21cfe1c3 100644 --- a/x-pack/plugins/security/server/authentication/authentication_service.test.ts +++ b/x-pack/plugins/security/server/authentication/authentication_service.test.ts @@ -147,8 +147,8 @@ describe('AuthenticationService', () => { service.start(mockStartAuthenticationParams); authHandler = mockSetupAuthenticationParams.http.registerAuth.mock.calls[0][0]; - authenticate = jest.requireMock('./authenticator').Authenticator.mock.instances[0] - .authenticate; + authenticate = + jest.requireMock('./authenticator').Authenticator.mock.instances[0].authenticate; }); it('returns error if license is not available.', async () => { @@ -331,8 +331,8 @@ describe('AuthenticationService', () => { service.setup(mockSetupAuthenticationParams); service.start(mockStartAuthenticationParams); - getServerBaseURL = jest.requireMock('./authenticator').Authenticator.mock.calls[0][0] - .getServerBaseURL; + getServerBaseURL = + jest.requireMock('./authenticator').Authenticator.mock.calls[0][0].getServerBaseURL; }); it('falls back to legacy server config if `public` config is not specified', async () => { diff --git a/x-pack/plugins/security/server/authentication/authenticator.test.ts b/x-pack/plugins/security/server/authentication/authenticator.test.ts index b2e33feb8cce2..ce97c142f5584 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.test.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.test.ts @@ -120,8 +120,8 @@ describe('Authenticator', () => { describe('#options.urls.loggedOut', () => { it('points to /login if provider requires login form', () => { - const authenticationProviderMock = jest.requireMock(`./providers/basic`) - .BasicAuthenticationProvider; + const authenticationProviderMock = + jest.requireMock(`./providers/basic`).BasicAuthenticationProvider; authenticationProviderMock.mockClear(); new Authenticator(getMockOptions()); const getLoggedOutURL = authenticationProviderMock.mock.calls[0][0].urls.loggedOut; @@ -140,8 +140,8 @@ describe('Authenticator', () => { }); it('points to /login if login selector is enabled', () => { - const authenticationProviderMock = jest.requireMock(`./providers/saml`) - .SAMLAuthenticationProvider; + const authenticationProviderMock = + jest.requireMock(`./providers/saml`).SAMLAuthenticationProvider; authenticationProviderMock.mockClear(); new Authenticator( getMockOptions({ @@ -165,8 +165,8 @@ describe('Authenticator', () => { }); it('points to /security/logged_out if login selector is NOT enabled', () => { - const authenticationProviderMock = jest.requireMock(`./providers/saml`) - .SAMLAuthenticationProvider; + const authenticationProviderMock = + jest.requireMock(`./providers/saml`).SAMLAuthenticationProvider; authenticationProviderMock.mockClear(); new Authenticator( getMockOptions({ diff --git a/x-pack/plugins/security/server/authentication/providers/anonymous.ts b/x-pack/plugins/security/server/authentication/providers/anonymous.ts index 2efd26a9d2e19..fdb58f52f9f2e 100644 --- a/x-pack/plugins/security/server/authentication/providers/anonymous.ts +++ b/x-pack/plugins/security/server/authentication/providers/anonymous.ts @@ -136,9 +136,8 @@ export class AnonymousAuthenticationProvider extends BaseAuthenticationProvider this.logger.debug('Anonymous requests will be authenticated via username and password.'); } - this.httpAuthorizationHeader = AnonymousAuthenticationProvider.createHTTPAuthorizationHeader( - credentials - ); + this.httpAuthorizationHeader = + AnonymousAuthenticationProvider.createHTTPAuthorizationHeader(credentials); } /** diff --git a/x-pack/plugins/security/server/authentication/providers/oidc.test.ts b/x-pack/plugins/security/server/authentication/providers/oidc.test.ts index 444a7f3e50a25..66af74f86c7c2 100644 --- a/x-pack/plugins/security/server/authentication/providers/oidc.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/oidc.test.ts @@ -352,8 +352,7 @@ describe('OIDCAuthenticationProvider', () => { describe('implicit flow', () => { defineAuthenticationFlowTests(() => ({ request: httpServerMock.createKibanaRequest({ - path: - '/api/security/oidc/callback?authenticationResponseURI=http://kibana/api/security/oidc/implicit#id_token=sometoken', + path: '/api/security/oidc/callback?authenticationResponseURI=http://kibana/api/security/oidc/implicit#id_token=sometoken', }), attempt: { type: OIDCLogin.LoginWithImplicitFlow, diff --git a/x-pack/plugins/security/server/authentication/providers/saml.ts b/x-pack/plugins/security/server/authentication/providers/saml.ts index 6eab0c5dc4875..4b8d30d8a2f18 100644 --- a/x-pack/plugins/security/server/authentication/providers/saml.ts +++ b/x-pack/plugins/security/server/authentication/providers/saml.ts @@ -576,7 +576,11 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider { // user usually doesn't have `cluster:admin/xpack/security/saml/prepare`. // We can replace generic `transport.request` with a dedicated API method call once // https://github.com/elastic/elasticsearch/issues/67189 is resolved. - const { id: requestId, redirect, realm } = ( + const { + id: requestId, + redirect, + realm, + } = ( await this.options.client.asInternalUser.transport.request({ method: 'POST', path: '/_security/saml/prepare', diff --git a/x-pack/plugins/security/server/authorization/__snapshots__/reset_session_page.test.tsx.snap b/x-pack/plugins/security/server/authorization/__snapshots__/reset_session_page.test.tsx.snap index 8d31770cd9385..97cfcd47ade8d 100644 --- a/x-pack/plugins/security/server/authorization/__snapshots__/reset_session_page.test.tsx.snap +++ b/x-pack/plugins/security/server/authorization/__snapshots__/reset_session_page.test.tsx.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`ResetSessionPage renders as expected 1`] = `"ElasticMockedFonts

You do not have permission to access the requested page

Either go back to the previous page or log in as a different user.

"`; +exports[`ResetSessionPage renders as expected 1`] = `"ElasticMockedFonts

You do not have permission to access the requested page

Either go back to the previous page or log in as a different user.

"`; diff --git a/x-pack/plugins/security/server/authorization/actions/actions.mock.ts b/x-pack/plugins/security/server/authorization/actions/actions.mock.ts index ba627f08c00ca..a18c5fbf20064 100644 --- a/x-pack/plugins/security/server/authorization/actions/actions.mock.ts +++ b/x-pack/plugins/security/server/authorization/actions/actions.mock.ts @@ -23,7 +23,7 @@ jest.mock('./alerting'); jest.mock('./cases'); const create = (versionNumber: string) => { - const t = ({ + const t = { api: new ApiActions(versionNumber), app: new AppActions(versionNumber), login: 'login:', @@ -33,7 +33,7 @@ const create = (versionNumber: string) => { space: new SpaceActions(versionNumber), ui: new UIActions(versionNumber), version: `version:${versionNumber}`, - } as unknown) as jest.Mocked; + } as unknown as jest.Mocked; return t; }; diff --git a/x-pack/plugins/security/server/config.ts b/x-pack/plugins/security/server/config.ts index 90fccf4bc6c26..af5703cff158c 100644 --- a/x-pack/plugins/security/server/config.ts +++ b/x-pack/plugins/security/server/config.ts @@ -329,20 +329,22 @@ export function createConfig( } const isUsingLegacyProvidersFormat = Array.isArray(config.authc.providers); - const providers = (isUsingLegacyProvidersFormat - ? [...new Set(config.authc.providers as Array)].reduce( - (legacyProviders, providerType, order) => { - legacyProviders[providerType] = { - [providerType]: - providerType === 'saml' || providerType === 'oidc' - ? { enabled: true, showInSelector: true, order, ...config.authc[providerType] } - : { enabled: true, showInSelector: true, order }, - }; - return legacyProviders; - }, - {} as Record - ) - : config.authc.providers) as ProvidersConfigType; + const providers = ( + isUsingLegacyProvidersFormat + ? [...new Set(config.authc.providers as Array)].reduce( + (legacyProviders, providerType, order) => { + legacyProviders[providerType] = { + [providerType]: + providerType === 'saml' || providerType === 'oidc' + ? { enabled: true, showInSelector: true, order, ...config.authc[providerType] } + : { enabled: true, showInSelector: true, order }, + }; + return legacyProviders; + }, + {} as Record + ) + : config.authc.providers + ) as ProvidersConfigType; // Remove disabled providers and sort the rest. const sortedProviders: Array<{ diff --git a/x-pack/plugins/security/server/config_deprecations.test.ts b/x-pack/plugins/security/server/config_deprecations.test.ts index 4518679755156..18f864012cb87 100644 --- a/x-pack/plugins/security/server/config_deprecations.test.ts +++ b/x-pack/plugins/security/server/config_deprecations.test.ts @@ -20,7 +20,9 @@ const applyConfigDeprecations = (settings: Record = {}) => { deprecation, path: 'xpack.security', })), - () => ({ message }) => deprecationMessages.push(message) + () => + ({ message }) => + deprecationMessages.push(message) ); return { messages: deprecationMessages, diff --git a/x-pack/plugins/security/server/elasticsearch/elasticsearch_service.test.ts b/x-pack/plugins/security/server/elasticsearch/elasticsearch_service.test.ts index 15af59fdae7d1..65fe1de0c962c 100644 --- a/x-pack/plugins/security/server/elasticsearch/elasticsearch_service.test.ts +++ b/x-pack/plugins/security/server/elasticsearch/elasticsearch_service.test.ts @@ -38,7 +38,7 @@ describe('ElasticsearchService', () => { let mockStatusSubject: BehaviorSubject; let mockLicenseSubject: BehaviorSubject; beforeEach(() => { - mockLicenseSubject = new BehaviorSubject(({} as unknown) as SecurityLicenseFeatures); + mockLicenseSubject = new BehaviorSubject({} as unknown as SecurityLicenseFeatures); mockLicense = licenseMock.create(); mockLicense.isEnabled.mockReturnValue(false); mockLicense.features$ = mockLicenseSubject; @@ -134,7 +134,7 @@ describe('ElasticsearchService', () => { expect(mockHandler).toHaveBeenCalledTimes(4); // New changes still trigger handler once again and reset retry timer. - mockLicenseSubject.next(({} as unknown) as SecurityLicenseFeatures); + mockLicenseSubject.next({} as unknown as SecurityLicenseFeatures); expect(mockHandler).toHaveBeenCalledTimes(5); // Retry timer is reset. @@ -167,7 +167,7 @@ describe('ElasticsearchService', () => { expect(mockHandler).toHaveBeenCalledTimes(1); // New changes should immediately call handler. - mockLicenseSubject.next(({} as unknown) as SecurityLicenseFeatures); + mockLicenseSubject.next({} as unknown as SecurityLicenseFeatures); expect(mockHandler).toHaveBeenCalledTimes(2); // Retry timeout should have been cancelled. diff --git a/x-pack/plugins/security/server/plugin.test.ts b/x-pack/plugins/security/server/plugin.test.ts index 7df717ddefef9..eb88aba1c0e1b 100644 --- a/x-pack/plugins/security/server/plugin.test.ts +++ b/x-pack/plugins/security/server/plugin.test.ts @@ -44,11 +44,11 @@ describe('Security Plugin', () => { protocol: 'https', }); - mockSetupDependencies = ({ + mockSetupDependencies = { licensing: { license$: of({}), featureUsage: { register: jest.fn() } }, features: featuresPluginMock.createSetup(), taskManager: taskManagerMock.createSetup(), - } as unknown) as PluginSetupDependencies; + } as unknown as PluginSetupDependencies; mockCoreStart = coreMock.createStart(); diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts index 1873ca42324c0..e3da0716f29ee 100644 --- a/x-pack/plugins/security/server/plugin.ts +++ b/x-pack/plugins/security/server/plugin.ts @@ -121,7 +121,8 @@ export interface PluginStartDependencies { * Represents Security Plugin instance that will be managed by the Kibana plugin system. */ export class SecurityPlugin - implements Plugin { + implements Plugin +{ private readonly logger: Logger; private authorizationSetup?: AuthorizationServiceSetupInternal; private auditSetup?: AuditServiceSetup; @@ -326,10 +327,10 @@ export class SecurityPlugin authz: { actions: this.authorizationSetup.actions, checkPrivilegesWithRequest: this.authorizationSetup.checkPrivilegesWithRequest, - checkPrivilegesDynamicallyWithRequest: this.authorizationSetup - .checkPrivilegesDynamicallyWithRequest, - checkSavedObjectsPrivilegesWithRequest: this.authorizationSetup - .checkSavedObjectsPrivilegesWithRequest, + checkPrivilegesDynamicallyWithRequest: + this.authorizationSetup.checkPrivilegesDynamicallyWithRequest, + checkSavedObjectsPrivilegesWithRequest: + this.authorizationSetup.checkSavedObjectsPrivilegesWithRequest, mode: this.authorizationSetup.mode, }, @@ -386,10 +387,10 @@ export class SecurityPlugin authz: { actions: this.authorizationSetup!.actions, checkPrivilegesWithRequest: this.authorizationSetup!.checkPrivilegesWithRequest, - checkPrivilegesDynamicallyWithRequest: this.authorizationSetup! - .checkPrivilegesDynamicallyWithRequest, - checkSavedObjectsPrivilegesWithRequest: this.authorizationSetup! - .checkSavedObjectsPrivilegesWithRequest, + checkPrivilegesDynamicallyWithRequest: + this.authorizationSetup!.checkPrivilegesDynamicallyWithRequest, + checkSavedObjectsPrivilegesWithRequest: + this.authorizationSetup!.checkSavedObjectsPrivilegesWithRequest, mode: this.authorizationSetup!.mode, }, }); diff --git a/x-pack/plugins/security/server/prompt_page.tsx b/x-pack/plugins/security/server/prompt_page.tsx index 338d39b29e534..eb26e1a4380ae 100644 --- a/x-pack/plugins/security/server/prompt_page.tsx +++ b/x-pack/plugins/security/server/prompt_page.tsx @@ -18,7 +18,8 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { I18nProvider } from '@kbn/i18n/react'; -import * as UiSharedDeps from '@kbn/ui-shared-deps'; +import UiSharedDepsNpm from '@kbn/ui-shared-deps-npm'; +import UiSharedDepsSrc from '@kbn/ui-shared-deps-src'; import type { IBasePath } from 'src/core/server'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths @@ -51,8 +52,8 @@ export function PromptPage({ const uiPublicURL = `${basePath.serverBasePath}/ui`; const regularBundlePath = `${basePath.serverBasePath}/${buildNumber}/bundles`; const styleSheetPaths = [ - `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.baseCssDistFilename}`, - `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.lightCssDistFilename}`, + `${regularBundlePath}/kbn-ui-shared-deps-src/${UiSharedDepsSrc.cssDistFilename}`, + `${regularBundlePath}/kbn-ui-shared-deps-npm/${UiSharedDepsNpm.lightCssDistFilename}`, `${basePath.serverBasePath}/node_modules/@kbn/ui-framework/dist/kui_light.css`, `${basePath.serverBasePath}/ui/legacy_light_theme.css`, ]; diff --git a/x-pack/plugins/security/server/routes/api_keys/create.test.ts b/x-pack/plugins/security/server/routes/api_keys/create.test.ts index a86481b8016e8..67c0571b2b075 100644 --- a/x-pack/plugins/security/server/routes/api_keys/create.test.ts +++ b/x-pack/plugins/security/server/routes/api_keys/create.test.ts @@ -22,9 +22,9 @@ describe('Create API Key route', () => { function getMockContext( licenseCheckResult: { state: string; message?: string } = { state: 'valid' } ) { - return ({ + return { licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } }, - } as unknown) as SecurityRequestHandlerContext; + } as unknown as SecurityRequestHandlerContext; } let routeHandler: RequestHandler; diff --git a/x-pack/plugins/security/server/routes/api_keys/enabled.test.ts b/x-pack/plugins/security/server/routes/api_keys/enabled.test.ts index 6a477d2600d3e..b46cdb80c966f 100644 --- a/x-pack/plugins/security/server/routes/api_keys/enabled.test.ts +++ b/x-pack/plugins/security/server/routes/api_keys/enabled.test.ts @@ -22,9 +22,9 @@ describe('API keys enabled', () => { function getMockContext( licenseCheckResult: { state: string; message?: string } = { state: 'valid' } ) { - return ({ + return { licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } }, - } as unknown) as SecurityRequestHandlerContext; + } as unknown as SecurityRequestHandlerContext; } let routeHandler: RequestHandler; diff --git a/x-pack/plugins/security/server/routes/api_keys/get.ts b/x-pack/plugins/security/server/routes/api_keys/get.ts index 7862401e1b80c..8982e4cf8c5d6 100644 --- a/x-pack/plugins/security/server/routes/api_keys/get.ts +++ b/x-pack/plugins/security/server/routes/api_keys/get.ts @@ -30,9 +30,10 @@ export function defineGetApiKeysRoutes({ router }: RouteDefinitionParams) { createLicensedRouteHandler(async (context, request, response) => { try { const isAdmin = request.query.isAdmin === 'true'; - const apiResponse = await context.core.elasticsearch.client.asCurrentUser.security.getApiKey<{ - api_keys: ApiKey[]; - }>({ owner: !isAdmin }); + const apiResponse = + await context.core.elasticsearch.client.asCurrentUser.security.getApiKey<{ + api_keys: ApiKey[]; + }>({ owner: !isAdmin }); const validKeys = apiResponse.body.api_keys.filter(({ invalidated }) => !invalidated); diff --git a/x-pack/plugins/security/server/routes/authentication/common.test.ts b/x-pack/plugins/security/server/routes/authentication/common.test.ts index 8320f88d1242a..6a318c953bcf5 100644 --- a/x-pack/plugins/security/server/routes/authentication/common.test.ts +++ b/x-pack/plugins/security/server/routes/authentication/common.test.ts @@ -38,11 +38,11 @@ describe('Common authentication routes', () => { authc = authenticationServiceMock.createStart(); routeParamsMock.getAuthenticationService.mockReturnValue(authc); - mockContext = ({ + mockContext = { licensing: { license: { check: jest.fn().mockReturnValue({ check: 'valid' }) }, }, - } as unknown) as SecurityRequestHandlerContext; + } as unknown as SecurityRequestHandlerContext; defineCommonRoutes(routeParamsMock); }); diff --git a/x-pack/plugins/security/server/routes/authorization/privileges/get.test.ts b/x-pack/plugins/security/server/routes/authorization/privileges/get.test.ts index 26afb602afc90..2bf3eea2ebda5 100644 --- a/x-pack/plugins/security/server/routes/authorization/privileges/get.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/privileges/get.test.ts @@ -66,9 +66,9 @@ describe('GET privileges', () => { query: includeActions ? { includeActions: 'true' } : undefined, headers, }); - const mockContext = ({ + const mockContext = { licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } }, - } as unknown) as SecurityRequestHandlerContext; + } as unknown as SecurityRequestHandlerContext; const response = await handler(mockContext, mockRequest, kibanaResponseFactory); expect(response.status).toBe(asserts.statusCode); diff --git a/x-pack/plugins/security/server/routes/authorization/privileges/get_builtin.ts b/x-pack/plugins/security/server/routes/authorization/privileges/get_builtin.ts index 29849497e02ec..a016a68d842ef 100644 --- a/x-pack/plugins/security/server/routes/authorization/privileges/get_builtin.ts +++ b/x-pack/plugins/security/server/routes/authorization/privileges/get_builtin.ts @@ -11,9 +11,8 @@ export function defineGetBuiltinPrivilegesRoutes({ router }: RouteDefinitionPara router.get( { path: '/internal/security/esPrivileges/builtin', validate: false }, async (context, request, response) => { - const { - body: privileges, - } = await context.core.elasticsearch.client.asCurrentUser.security.getBuiltinPrivileges(); + const { body: privileges } = + await context.core.elasticsearch.client.asCurrentUser.security.getBuiltinPrivileges(); // Exclude the `none` privilege, as it doesn't make sense as an option within the Kibana UI privileges.cluster = privileges.cluster.filter((privilege) => privilege !== 'none'); diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get.ts b/x-pack/plugins/security/server/routes/authorization/roles/get.ts index 075a8d133f1e6..6e010b69a3711 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get.ts @@ -22,11 +22,10 @@ export function defineGetRolesRoutes({ router, authz }: RouteDefinitionParams) { }, createLicensedRouteHandler(async (context, request, response) => { try { - const { - body: elasticsearchRoles, - } = await context.core.elasticsearch.client.asCurrentUser.security.getRole({ - name: request.params.name, - }); + const { body: elasticsearchRoles } = + await context.core.elasticsearch.client.asCurrentUser.security.getRole({ + name: request.params.name, + }); const elasticsearchRole = elasticsearchRoles[request.params.name]; if (elasticsearchRole) { diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get_all.ts b/x-pack/plugins/security/server/routes/authorization/roles/get_all.ts index be0880a06d59d..ba5133b780d5e 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get_all.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get_all.ts @@ -16,11 +16,10 @@ export function defineGetAllRolesRoutes({ router, authz }: RouteDefinitionParams { path: '/api/security/role', validate: false }, createLicensedRouteHandler(async (context, request, response) => { try { - const { - body: elasticsearchRoles, - } = await context.core.elasticsearch.client.asCurrentUser.security.getRole< - Record - >(); + const { body: elasticsearchRoles } = + await context.core.elasticsearch.client.asCurrentUser.security.getRole< + Record + >(); // Transform elasticsearch roles into Kibana roles and return in a list sorted by the role name. return response.ok({ diff --git a/x-pack/plugins/security/server/routes/authorization/roles/put.ts b/x-pack/plugins/security/server/routes/authorization/roles/put.ts index 5a0934721e20c..af69db1f6bd43 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/put.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/put.ts @@ -62,12 +62,11 @@ export function definePutRolesRoutes({ const { name } = request.params; try { - const { - body: rawRoles, - } = await context.core.elasticsearch.client.asCurrentUser.security.getRole( - { name: request.params.name }, - { ignore: [404] } - ); + const { body: rawRoles } = + await context.core.elasticsearch.client.asCurrentUser.security.getRole( + { name: request.params.name }, + { ignore: [404] } + ); const body = transformPutPayloadToElasticsearchRole( request.body, diff --git a/x-pack/plugins/security/server/routes/authorization/spaces/share_saved_object_permissions.test.ts b/x-pack/plugins/security/server/routes/authorization/spaces/share_saved_object_permissions.test.ts index 11849a1f50bce..43865a0a6b66b 100644 --- a/x-pack/plugins/security/server/routes/authorization/spaces/share_saved_object_permissions.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/spaces/share_saved_object_permissions.test.ts @@ -20,11 +20,11 @@ describe('Share Saved Object Permissions', () => { let router: jest.Mocked; let routeParamsMock: DeeplyMockedKeys; - const mockContext = ({ + const mockContext = { licensing: { license: { check: jest.fn().mockReturnValue({ state: 'valid' }) }, }, - } as unknown) as SecurityRequestHandlerContext; + } as unknown as SecurityRequestHandlerContext; beforeEach(() => { routeParamsMock = routeDefinitionParamsMock.create(); @@ -53,9 +53,9 @@ describe('Share Saved Object Permissions', () => { it('returns `true` when the user is authorized globally', async () => { const checkPrivilegesWithRequest = jest.fn().mockResolvedValue({ hasAllRequested: true }); - routeParamsMock.authz.checkPrivilegesWithRequest.mockReturnValue(({ + routeParamsMock.authz.checkPrivilegesWithRequest.mockReturnValue({ globally: checkPrivilegesWithRequest, - } as unknown) as CheckPrivileges); + } as unknown as CheckPrivileges); const request = httpServerMock.createKibanaRequest({ query: { @@ -83,9 +83,9 @@ describe('Share Saved Object Permissions', () => { it('returns `false` when the user is not authorized globally', async () => { const checkPrivilegesWithRequest = jest.fn().mockResolvedValue({ hasAllRequested: false }); - routeParamsMock.authz.checkPrivilegesWithRequest.mockReturnValue(({ + routeParamsMock.authz.checkPrivilegesWithRequest.mockReturnValue({ globally: checkPrivilegesWithRequest, - } as unknown) as CheckPrivileges); + } as unknown as CheckPrivileges); const request = httpServerMock.createKibanaRequest({ query: { diff --git a/x-pack/plugins/security/server/routes/index.mock.ts b/x-pack/plugins/security/server/routes/index.mock.ts index 956de8c4036d3..a92884c1dab75 100644 --- a/x-pack/plugins/security/server/routes/index.mock.ts +++ b/x-pack/plugins/security/server/routes/index.mock.ts @@ -17,7 +17,7 @@ import type { RouteDefinitionParams } from './'; export const routeDefinitionParamsMock = { create: (config: Record = {}) => - (({ + ({ router: httpServiceMock.createRouter(), basePath: httpServiceMock.createBasePath(), csp: httpServiceMock.createSetupContract().csp, @@ -32,5 +32,5 @@ export const routeDefinitionParamsMock = { getFeatureUsageService: jest.fn(), getSession: jest.fn().mockReturnValue(sessionMock.create()), getAuthenticationService: jest.fn().mockReturnValue(authenticationServiceMock.createStart()), - } as unknown) as DeeplyMockedKeys), + } as unknown as DeeplyMockedKeys), }; diff --git a/x-pack/plugins/security/server/routes/indices/get_fields.ts b/x-pack/plugins/security/server/routes/indices/get_fields.ts index d0370a93b14c3..ebfb2b9b3fb95 100644 --- a/x-pack/plugins/security/server/routes/indices/get_fields.ts +++ b/x-pack/plugins/security/server/routes/indices/get_fields.ts @@ -18,15 +18,14 @@ export function defineGetFieldsRoutes({ router }: RouteDefinitionParams) { }, async (context, request, response) => { try { - const { - body: indexMappings, - } = await context.core.elasticsearch.client.asCurrentUser.indices.getFieldMapping({ - index: request.params.query, - fields: '*', - allow_no_indices: false, - include_defaults: true, - filter_path: '*.mappings.*.mapping.*.type', - }); + const { body: indexMappings } = + await context.core.elasticsearch.client.asCurrentUser.indices.getFieldMapping({ + index: request.params.query, + fields: '*', + allow_no_indices: false, + include_defaults: true, + filter_path: '*.mappings.*.mapping.*.type', + }); // The flow is the following (see response format at https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-get-field-mapping.html): // 1. Iterate over all matched indices. @@ -40,9 +39,11 @@ export function defineGetFieldsRoutes({ router }: RouteDefinitionParams) { const mappingValues = Object.values( // `FieldMapping` type from `TypeFieldMappings` --> `GetFieldMappingResponse` is not correct and // doesn't have any properties. - (indexMapping.mappings[fieldName] as { - mapping: Record; - }).mapping + ( + indexMapping.mappings[fieldName] as { + mapping: Record; + } + ).mapping ); const hasMapping = mappingValues.length > 0; diff --git a/x-pack/plugins/security/server/routes/role_mapping/delete.ts b/x-pack/plugins/security/server/routes/role_mapping/delete.ts index c7266353eb235..9d23fbaf1e265 100644 --- a/x-pack/plugins/security/server/routes/role_mapping/delete.ts +++ b/x-pack/plugins/security/server/routes/role_mapping/delete.ts @@ -23,11 +23,10 @@ export function defineRoleMappingDeleteRoutes({ router }: RouteDefinitionParams) }, createLicensedRouteHandler(async (context, request, response) => { try { - const { - body: deleteResponse, - } = await context.core.elasticsearch.client.asCurrentUser.security.deleteRoleMapping({ - name: request.params.name, - }); + const { body: deleteResponse } = + await context.core.elasticsearch.client.asCurrentUser.security.deleteRoleMapping({ + name: request.params.name, + }); return response.ok({ body: deleteResponse }); } catch (error) { const wrappedError = wrapError(error); diff --git a/x-pack/plugins/security/server/routes/role_mapping/get.ts b/x-pack/plugins/security/server/routes/role_mapping/get.ts index 257b4210b13f7..1039abdd8667a 100644 --- a/x-pack/plugins/security/server/routes/role_mapping/get.ts +++ b/x-pack/plugins/security/server/routes/role_mapping/get.ts @@ -28,9 +28,10 @@ export function defineRoleMappingGetRoutes(params: RouteDefinitionParams) { const expectSingleEntity = typeof request.params.name === 'string'; try { - const roleMappingsResponse = await context.core.elasticsearch.client.asCurrentUser.security.getRoleMapping( - { name: request.params.name } - ); + const roleMappingsResponse = + await context.core.elasticsearch.client.asCurrentUser.security.getRoleMapping({ + name: request.params.name, + }); const mappings = Object.entries(roleMappingsResponse.body).map(([name, mapping]) => { return { diff --git a/x-pack/plugins/security/server/routes/role_mapping/post.ts b/x-pack/plugins/security/server/routes/role_mapping/post.ts index c76a169137053..5cc6c5ae48d3c 100644 --- a/x-pack/plugins/security/server/routes/role_mapping/post.ts +++ b/x-pack/plugins/security/server/routes/role_mapping/post.ts @@ -44,9 +44,11 @@ export function defineRoleMappingPostRoutes({ router }: RouteDefinitionParams) { }, createLicensedRouteHandler(async (context, request, response) => { try { - const saveResponse = await context.core.elasticsearch.client.asCurrentUser.security.putRoleMapping( - { name: request.params.name, body: request.body } - ); + const saveResponse = + await context.core.elasticsearch.client.asCurrentUser.security.putRoleMapping({ + name: request.params.name, + body: request.body, + }); return response.ok({ body: saveResponse.body }); } catch (error) { const wrappedError = wrapError(error); diff --git a/x-pack/plugins/security/server/routes/session_management/extend.test.ts b/x-pack/plugins/security/server/routes/session_management/extend.test.ts index 4286cbfd0ed30..5af59a6386754 100644 --- a/x-pack/plugins/security/server/routes/session_management/extend.test.ts +++ b/x-pack/plugins/security/server/routes/session_management/extend.test.ts @@ -42,7 +42,7 @@ describe('Extend session routes', () => { it('always returns 302.', async () => { await expect( routeHandler( - ({} as unknown) as SecurityRequestHandlerContext, + {} as unknown as SecurityRequestHandlerContext, httpServerMock.createKibanaRequest(), kibanaResponseFactory ) diff --git a/x-pack/plugins/security/server/routes/session_management/info.test.ts b/x-pack/plugins/security/server/routes/session_management/info.test.ts index 4c420f3b14346..f133a5ab31961 100644 --- a/x-pack/plugins/security/server/routes/session_management/info.test.ts +++ b/x-pack/plugins/security/server/routes/session_management/info.test.ts @@ -53,11 +53,7 @@ describe('Info session routes', () => { const request = httpServerMock.createKibanaRequest(); await expect( - routeHandler( - ({} as unknown) as SecurityRequestHandlerContext, - request, - kibanaResponseFactory - ) + routeHandler({} as unknown as SecurityRequestHandlerContext, request, kibanaResponseFactory) ).rejects.toThrowError(unhandledException); expect(session.get).toHaveBeenCalledWith(request); @@ -142,7 +138,7 @@ describe('Info session routes', () => { await expect( routeHandler( - ({} as unknown) as SecurityRequestHandlerContext, + {} as unknown as SecurityRequestHandlerContext, httpServerMock.createKibanaRequest(), kibanaResponseFactory ) @@ -159,7 +155,7 @@ describe('Info session routes', () => { await expect( routeHandler( - ({} as unknown) as SecurityRequestHandlerContext, + {} as unknown as SecurityRequestHandlerContext, httpServerMock.createKibanaRequest(), kibanaResponseFactory ) diff --git a/x-pack/plugins/security/server/routes/session_management/invalidate.test.ts b/x-pack/plugins/security/server/routes/session_management/invalidate.test.ts index 215a66a3364e5..ebe795788329c 100644 --- a/x-pack/plugins/security/server/routes/session_management/invalidate.test.ts +++ b/x-pack/plugins/security/server/routes/session_management/invalidate.test.ts @@ -115,7 +115,7 @@ describe('Invalidate sessions routes', () => { }); await expect( routeHandler( - ({} as unknown) as SecurityRequestHandlerContext, + {} as unknown as SecurityRequestHandlerContext, mockRequest, kibanaResponseFactory ) @@ -138,7 +138,7 @@ describe('Invalidate sessions routes', () => { const mockRequest = httpServerMock.createKibanaRequest({ body: { match: 'all' } }); await expect( routeHandler( - ({} as unknown) as SecurityRequestHandlerContext, + {} as unknown as SecurityRequestHandlerContext, mockRequest, kibanaResponseFactory ) diff --git a/x-pack/plugins/security/server/routes/users/get.ts b/x-pack/plugins/security/server/routes/users/get.ts index 81502044ef94d..78f8056164eec 100644 --- a/x-pack/plugins/security/server/routes/users/get.ts +++ b/x-pack/plugins/security/server/routes/users/get.ts @@ -22,11 +22,10 @@ export function defineGetUserRoutes({ router }: RouteDefinitionParams) { createLicensedRouteHandler(async (context, request, response) => { try { const username = request.params.username; - const { - body: users, - } = await context.core.elasticsearch.client.asCurrentUser.security.getUser({ - username, - }); + const { body: users } = + await context.core.elasticsearch.client.asCurrentUser.security.getUser({ + username, + }); if (!users[username]) { return response.notFound(); diff --git a/x-pack/plugins/security/server/routes/views/access_agreement.test.ts b/x-pack/plugins/security/server/routes/views/access_agreement.test.ts index 2679616b2be16..9d88526a85116 100644 --- a/x-pack/plugins/security/server/routes/views/access_agreement.test.ts +++ b/x-pack/plugins/security/server/routes/views/access_agreement.test.ts @@ -45,11 +45,11 @@ describe('Access agreement view routes', () => { allowAccessAgreement: true, } as SecurityLicenseFeatures); - mockContext = ({ + mockContext = { licensing: { license: { check: jest.fn().mockReturnValue({ check: 'valid' }) }, }, - } as unknown) as SecurityRequestHandlerContext; + } as unknown as SecurityRequestHandlerContext; defineAccessAgreementRoutes(routeParamsMock); }); diff --git a/x-pack/plugins/security/server/routes/views/capture_url.test.ts b/x-pack/plugins/security/server/routes/views/capture_url.test.ts index 6c4de7a719fe1..6fdb01ad13a8c 100644 --- a/x-pack/plugins/security/server/routes/views/capture_url.test.ts +++ b/x-pack/plugins/security/server/routes/views/capture_url.test.ts @@ -53,7 +53,7 @@ describe('Capture URL view routes', () => { const request = httpServerMock.createKibanaRequest(); const responseFactory = httpResourcesMock.createResponseFactory(); - await routeHandler(({} as unknown) as SecurityRequestHandlerContext, request, responseFactory); + await routeHandler({} as unknown as SecurityRequestHandlerContext, request, responseFactory); expect(responseFactory.renderAnonymousCoreApp).toHaveBeenCalledWith(); }); diff --git a/x-pack/plugins/security/server/routes/views/logged_out.test.ts b/x-pack/plugins/security/server/routes/views/logged_out.test.ts index af973d1e060db..9b0eea0c74634 100644 --- a/x-pack/plugins/security/server/routes/views/logged_out.test.ts +++ b/x-pack/plugins/security/server/routes/views/logged_out.test.ts @@ -25,12 +25,10 @@ describe('LoggedOut view routes', () => { defineLoggedOutRoutes(routeParamsMock); - const [ - loggedOutRouteConfig, - loggedOutRouteHandler, - ] = routeParamsMock.httpResources.register.mock.calls.find( - ([{ path }]) => path === '/security/logged_out' - )!; + const [loggedOutRouteConfig, loggedOutRouteHandler] = + routeParamsMock.httpResources.register.mock.calls.find( + ([{ path }]) => path === '/security/logged_out' + )!; routeConfig = loggedOutRouteConfig; routeHandler = loggedOutRouteHandler; diff --git a/x-pack/plugins/security/server/saved_objects/ensure_authorized.test.ts b/x-pack/plugins/security/server/saved_objects/ensure_authorized.test.ts index b9ab7cef7f15b..0f6ae5e282609 100644 --- a/x-pack/plugins/security/server/saved_objects/ensure_authorized.test.ts +++ b/x-pack/plugins/security/server/saved_objects/ensure_authorized.test.ts @@ -23,11 +23,12 @@ describe('ensureAuthorized', () => { jest .spyOn(actions.savedObject, 'get') .mockImplementation((type: string, action: string) => `mock-saved_object:${type}/${action}`); - const errors = ({ + const errors = { decorateForbiddenError: jest.fn().mockImplementation((err) => err), decorateGeneralError: jest.fn().mockImplementation((err) => err), - } as unknown) as jest.Mocked; - const checkSavedObjectsPrivilegesAsCurrentUser: jest.MockedFunction = jest.fn(); + } as unknown as jest.Mocked; + const checkSavedObjectsPrivilegesAsCurrentUser: jest.MockedFunction = + jest.fn(); return { actions, errors, checkSavedObjectsPrivilegesAsCurrentUser }; } diff --git a/x-pack/plugins/security/server/saved_objects/index.ts b/x-pack/plugins/security/server/saved_objects/index.ts index ad04ab706c2d4..c9c467f1f8465 100644 --- a/x-pack/plugins/security/server/saved_objects/index.ts +++ b/x-pack/plugins/security/server/saved_objects/index.ts @@ -45,13 +45,14 @@ export function setupSavedObjects({ getSpacesService, }: SetupSavedObjectsParams) { savedObjects.setClientFactoryProvider( - (repositoryFactory) => ({ request, includedHiddenTypes }) => { - return new SavedObjectsClient( - authz.mode.useRbacForRequest(request) - ? repositoryFactory.createInternalRepository(includedHiddenTypes) - : repositoryFactory.createScopedRepository(request, includedHiddenTypes) - ); - } + (repositoryFactory) => + ({ request, includedHiddenTypes }) => { + return new SavedObjectsClient( + authz.mode.useRbacForRequest(request) + ? repositoryFactory.createInternalRepository(includedHiddenTypes) + : repositoryFactory.createScopedRepository(request, includedHiddenTypes) + ); + } ); savedObjects.addClientWrapper(Number.MAX_SAFE_INTEGER - 1, 'security', ({ client, request }) => { @@ -61,9 +62,8 @@ export function setupSavedObjects({ legacyAuditLogger, auditLogger: audit.asScoped(request), baseClient: client, - checkSavedObjectsPrivilegesAsCurrentUser: authz.checkSavedObjectsPrivilegesWithRequest( - request - ), + checkSavedObjectsPrivilegesAsCurrentUser: + authz.checkSavedObjectsPrivilegesWithRequest(request), errors: SavedObjectsClient.errors, getSpacesService, }) diff --git a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts index 96f8c5ff02d24..0d7d8cfa480fa 100644 --- a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts +++ b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts @@ -12,6 +12,7 @@ import type { SavedObject, SavedObjectReferenceWithContext, SavedObjectsClientContract, + SavedObjectsResolveResponse, SavedObjectsUpdateObjectsSpacesResponseObject, } from 'src/core/server'; import { httpServerMock, savedObjectsClientMock } from 'src/core/server/mocks'; @@ -48,12 +49,12 @@ const createSecureSavedObjectsClientWrapperOptions = () => { const forbiddenError = new Error('Mock ForbiddenError'); const generalError = new Error('Mock GeneralError'); - const errors = ({ + const errors = { decorateForbiddenError: jest.fn().mockReturnValue(forbiddenError), decorateGeneralError: jest.fn().mockReturnValue(generalError), createBadRequestError: jest.fn().mockImplementation((message) => new Error(message)), isNotFoundError: jest.fn().mockReturnValue(false), - } as unknown) as jest.Mocked; + } as unknown as jest.Mocked; const getSpacesService = jest.fn().mockReturnValue({ namespaceToSpaceId: (namespace?: string) => (namespace ? namespace : 'default'), }); @@ -97,9 +98,9 @@ const expectForbiddenError = async (fn: Function, args: Record, act await expect(fn.bind(client)(...Object.values(args))).rejects.toThrowError( clientOpts.forbiddenError ); - const getCalls = (clientOpts.actions.savedObject.get as jest.MockedFunction< - SavedObjectActions['get'] - >).mock.calls; + const getCalls = ( + clientOpts.actions.savedObject.get as jest.MockedFunction + ).mock.calls; const actions = clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mock.calls[0][0]; const spaceId = args.options?.namespaces ? args.options?.namespaces[0] @@ -125,9 +126,9 @@ const expectForbiddenError = async (fn: Function, args: Record, act const expectSuccess = async (fn: Function, args: Record, action?: string) => { const result = await fn.bind(client)(...Object.values(args)); - const getCalls = (clientOpts.actions.savedObject.get as jest.MockedFunction< - SavedObjectActions['get'] - >).mock.calls; + const getCalls = ( + clientOpts.actions.savedObject.get as jest.MockedFunction + ).mock.calls; const ACTION = getCalls[0][1]; const types = getCalls.map((x) => x[0]); const spaceIds = args.options?.namespaces || [args.options?.namespace || 'default']; @@ -154,9 +155,9 @@ const expectPrivilegeCheck = async ( ); await expect(fn.bind(client)(...Object.values(args))).rejects.toThrow(); // test is simpler with error case - const getResults = (clientOpts.actions.savedObject.get as jest.MockedFunction< - SavedObjectActions['get'] - >).mock.results; + const getResults = ( + clientOpts.actions.savedObject.get as jest.MockedFunction + ).mock.results; const actions = getResults.map((x) => x.value); expect(clientOpts.checkSavedObjectsPrivilegesAsCurrentUser).toHaveBeenCalledTimes(1); @@ -465,6 +466,103 @@ describe('#bulkGet', () => { }); }); +describe('#bulkResolve', () => { + const obj1 = Object.freeze({ type: 'foo', id: 'foo-id' }); + const obj2 = Object.freeze({ type: 'bar', id: 'bar-id' }); + const namespace = 'some-ns'; + + test(`throws decorated GeneralError when hasPrivileges rejects promise`, async () => { + const objects = [obj1]; + await expectGeneralError(client.bulkResolve, { objects }); + }); + + test(`throws decorated ForbiddenError when unauthorized`, async () => { + const objects = [obj1, obj2]; + const options = { namespace }; + await expectForbiddenError(client.bulkResolve, { objects, options }, 'bulk_resolve'); + }); + + test(`returns result of baseClient.bulkResolve when authorized`, async () => { + const apiCallReturnValue = { resolved_objects: [] }; + clientOpts.baseClient.bulkResolve.mockResolvedValue(apiCallReturnValue); + + const objects = [obj1, obj2]; + const options = { namespace }; + const result = await expectSuccess(client.bulkResolve, { objects, options }, 'bulk_resolve'); + expect(result).toEqual(apiCallReturnValue); + }); + + test(`checks privileges for user, actions, and namespace`, async () => { + const objects = [obj1, obj2]; + const options = { namespace }; + await expectPrivilegeCheck(client.bulkResolve, { objects, options }, namespace); + }); + + test(`filters namespaces that the user doesn't have access to`, async () => { + const objects = [obj1, obj2]; + const options = { namespace }; + + clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockImplementationOnce( + getMockCheckPrivilegesSuccess // privilege check for authorization + ); + clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockImplementation( + getMockCheckPrivilegesFailure // privilege check for namespace filtering + ); + + clientOpts.baseClient.bulkResolve.mockResolvedValue({ + resolved_objects: [ + // omit other fields from the SavedObjectsResolveResponse such as outcome, as they are not needed for this test case + { saved_object: { namespaces: ['*'] } } as unknown as SavedObjectsResolveResponse, + { saved_object: { namespaces: [namespace] } } as unknown as SavedObjectsResolveResponse, + { + saved_object: { namespaces: ['some-other-namespace', namespace] }, + } as unknown as SavedObjectsResolveResponse, + ], + }); + + const result = await client.bulkResolve(objects, options); + expect(result).toEqual({ + resolved_objects: [ + { saved_object: { namespaces: ['*'] } }, + { saved_object: { namespaces: [namespace] } }, + { saved_object: { namespaces: [namespace, '?'] } }, + ], + }); + + expect(clientOpts.checkSavedObjectsPrivilegesAsCurrentUser).toHaveBeenCalledTimes(2); + expect(clientOpts.checkSavedObjectsPrivilegesAsCurrentUser).toHaveBeenLastCalledWith( + 'login:', + ['some-other-namespace'] + // when we check what namespaces to redact, we don't check privileges for '*', only actual space IDs + // we don't check privileges for authorizedNamespaces either, as that was already checked earlier in the operation + ); + }); + + test(`adds audit event when successful`, async () => { + const apiCallReturnValue = { + resolved_objects: [ + { saved_object: obj1 } as unknown as SavedObjectsResolveResponse, + { saved_object: obj2 } as unknown as SavedObjectsResolveResponse, + ], + }; + clientOpts.baseClient.bulkResolve.mockResolvedValue(apiCallReturnValue); + const objects = [obj1, obj2]; + const options = { namespace }; + await expectSuccess(client.bulkResolve, { objects, options }, 'bulk_resolve'); + expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(2); + expectAuditEvent('saved_object_resolve', 'success', obj1); + expectAuditEvent('saved_object_resolve', 'success', obj2); + }); + + test(`adds audit event when not successful`, async () => { + clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockRejectedValue(new Error()); + await expect(() => client.bulkResolve([obj1, obj2], { namespace })).rejects.toThrow(); + expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(2); + expectAuditEvent('saved_object_resolve', 'failure', obj1); + expectAuditEvent('saved_object_resolve', 'failure', obj2); + }); +}); + describe('#bulkUpdate', () => { const obj1 = Object.freeze({ type: 'foo', id: 'foo-id', attributes: { some: 'attr' } }); const obj2 = Object.freeze({ type: 'bar', id: 'bar-id', attributes: { other: 'attr' } }); @@ -698,9 +796,7 @@ describe('#find', () => { expect(clientOpts.baseClient.find).not.toHaveBeenCalled(); expect(clientOpts.legacyAuditLogger.savedObjectsAuthorizationFailure).toHaveBeenCalledTimes(1); - expect( - clientOpts.legacyAuditLogger.savedObjectsAuthorizationFailure - ).toHaveBeenCalledWith( + expect(clientOpts.legacyAuditLogger.savedObjectsAuthorizationFailure).toHaveBeenCalledWith( USERNAME, 'find', [type1], diff --git a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts index 11eca287cd4f5..b0428be87a4f2 100644 --- a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts +++ b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts @@ -11,6 +11,7 @@ import type { SavedObjectsBaseOptions, SavedObjectsBulkCreateObject, SavedObjectsBulkGetObject, + SavedObjectsBulkResolveObject, SavedObjectsBulkUpdateObject, SavedObjectsCheckConflictsObject, SavedObjectsClientContract, @@ -356,6 +357,78 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra return await this.redactSavedObjectNamespaces(savedObject, [options.namespace]); } + public async bulkResolve( + objects: SavedObjectsBulkResolveObject[], + options: SavedObjectsBaseOptions = {} + ) { + try { + const args = { objects, options }; + await this.legacyEnsureAuthorized( + this.getUniqueObjectTypes(objects), + 'bulk_get', + options.namespace, + { args, auditAction: 'bulk_resolve' } + ); + } catch (error) { + objects.forEach(({ type, id }) => + this.auditLogger.log( + savedObjectEvent({ + action: SavedObjectAction.RESOLVE, + savedObject: { type, id }, + error, + }) + ) + ); + throw error; + } + + const response = await this.baseClient.bulkResolve(objects, options); + + response.resolved_objects.forEach(({ saved_object: { error, type, id } }) => { + if (!error) { + this.auditLogger.log( + savedObjectEvent({ + action: SavedObjectAction.RESOLVE, + savedObject: { type, id }, + }) + ); + } + }); + + // the generic redactSavedObjectsNamespaces function cannot be used here due to the nested structure of the + // resolved objects, so we handle redaction in a bespoke manner for bulkResolve + + if (this.getSpacesService() === undefined) { + return response; + } + + const previouslyAuthorizedSpaceIds = [ + this.getSpacesService()!.namespaceToSpaceId(options.namespace), + ]; + // all users can see the "all spaces" ID, and we don't need to recheck authorization for any namespaces that we just checked earlier + const namespaces = uniq( + response.resolved_objects.flatMap((resolved) => resolved.saved_object.namespaces || []) + ).filter((x) => x !== ALL_SPACES_ID && !previouslyAuthorizedSpaceIds.includes(x)); + + const privilegeMap = await this.getNamespacesPrivilegeMap( + namespaces, + previouslyAuthorizedSpaceIds + ); + + return { + ...response, + resolved_objects: response.resolved_objects.map((resolved) => ({ + ...resolved, + saved_object: { + ...resolved.saved_object, + namespaces: + resolved.saved_object.namespaces && + this.redactAndSortNamespaces(resolved.saved_object.namespaces, privilegeMap), + }, + })), + }; + } + public async resolve( type: string, id: string, @@ -1030,6 +1103,8 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra response: T, previouslyAuthorizedNamespaces: Array ): Promise { + // WARNING: the bulkResolve function has a bespoke implementation of this; any changes here should be applied there too. + if (this.getSpacesService() === undefined) { return response; } diff --git a/x-pack/plugins/security/server/session_management/session_cookie.test.ts b/x-pack/plugins/security/server/session_management/session_cookie.test.ts index cb14fb03ec85d..40b5fe3821200 100644 --- a/x-pack/plugins/security/server/session_management/session_cookie.test.ts +++ b/x-pack/plugins/security/server/session_management/session_cookie.test.ts @@ -59,9 +59,8 @@ describe('Session cookie', () => { }); it('cookie validator properly handles cookies with different base path', () => { - const [ - [{ validate }], - ] = (sessionCookieOptions.createCookieSessionStorageFactory as jest.Mock).mock.calls; + const [[{ validate }]] = (sessionCookieOptions.createCookieSessionStorageFactory as jest.Mock) + .mock.calls; expect( validate(sessionCookieMock.createValue({ path: sessionCookieOptions.serverBasePath })) diff --git a/x-pack/plugins/security/server/session_management/session_cookie.ts b/x-pack/plugins/security/server/session_management/session_cookie.ts index 964d3d07a39a2..e747b15e78e89 100644 --- a/x-pack/plugins/security/server/session_management/session_cookie.ts +++ b/x-pack/plugins/security/server/session_management/session_cookie.ts @@ -89,9 +89,8 @@ export class SessionCookie { sameSite: config.sameSiteCookies, validate: (sessionValue: SessionCookieValue | SessionCookieValue[]) => { // ensure that this cookie was created with the current Kibana configuration - const invalidSessionValue = (Array.isArray(sessionValue) - ? sessionValue - : [sessionValue] + const invalidSessionValue = ( + Array.isArray(sessionValue) ? sessionValue : [sessionValue] ).find((sess) => sess.path !== undefined && sess.path !== serverBasePath); if (invalidSessionValue) { diff --git a/x-pack/plugins/security/server/session_management/session_index.ts b/x-pack/plugins/security/server/session_management/session_index.ts index f1a9296177d9c..fd993f0fb843a 100644 --- a/x-pack/plugins/security/server/session_management/session_index.ts +++ b/x-pack/plugins/security/server/session_management/session_index.ts @@ -151,13 +151,11 @@ export class SessionIndex { */ async get(sid: string) { try { - const { - body: response, - statusCode, - } = await this.options.elasticsearchClient.get( - { id: sid, index: this.indexName }, - { ignore: [404] } - ); + const { body: response, statusCode } = + await this.options.elasticsearchClient.get( + { id: sid, index: this.indexName }, + { ignore: [404] } + ); const docNotFound = response.found === false; const indexNotFound = statusCode === 404; diff --git a/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.test.ts b/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.test.ts index 20a524251bd4a..10261b98b8f0a 100644 --- a/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.test.ts +++ b/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.test.ts @@ -34,7 +34,7 @@ interface Opts { securityEnabled?: boolean; } -const spaces = (deepFreeze([ +const spaces = deepFreeze([ { id: 'default', name: 'Default Space', @@ -50,7 +50,7 @@ const spaces = (deepFreeze([ name: 'Sales Space', disabledFeatures: [], }, -]) as unknown) as Space[]; +]) as unknown as Space[]; const setup = ({ securityEnabled = false }: Opts = {}) => { const baseClient = spacesClientMock.create(); @@ -70,19 +70,19 @@ const setup = ({ securityEnabled = false }: Opts = {}) => { }); authorization.mode.useRbacForRequest.mockReturnValue(securityEnabled); - const legacyAuditLogger = ({ + const legacyAuditLogger = { spacesAuthorizationFailure: jest.fn(), spacesAuthorizationSuccess: jest.fn(), - } as unknown) as jest.Mocked; + } as unknown as jest.Mocked; const auditLogger = auditServiceMock.create().asScoped(httpServerMock.createKibanaRequest()); const request = httpServerMock.createKibanaRequest(); const forbiddenError = new Error('Mock ForbiddenError'); - const errors = ({ + const errors = { decorateForbiddenError: jest.fn().mockReturnValue(forbiddenError), // other errors exist but are not needed for these test cases - } as unknown) as jest.Mocked; + } as unknown as jest.Mocked; const wrapper = new SecureSpacesClientWrapper( baseClient, @@ -269,16 +269,10 @@ describe('SecureSpacesClientWrapper', () => { describe(`with purpose='${scenario.purpose}'`, () => { test(`throws Boom.forbidden when user isn't authorized for any spaces`, async () => { const username = 'some-user'; - const { - authorization, - wrapper, - baseClient, - request, - auditLogger, - legacyAuditLogger, - } = setup({ - securityEnabled: true, - }); + const { authorization, wrapper, baseClient, request, auditLogger, legacyAuditLogger } = + setup({ + securityEnabled: true, + }); const privileges = scenario.expectedPrivilege(authorization); @@ -315,16 +309,10 @@ describe('SecureSpacesClientWrapper', () => { test(`returns spaces that the user is authorized for`, async () => { const username = 'some-user'; - const { - authorization, - wrapper, - baseClient, - request, - auditLogger, - legacyAuditLogger, - } = setup({ - securityEnabled: true, - }); + const { authorization, wrapper, baseClient, request, auditLogger, legacyAuditLogger } = + setup({ + securityEnabled: true, + }); const privileges = scenario.expectedPrivilege(authorization); diff --git a/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.ts b/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.ts index f3d66ac0381eb..e2b57d01fe11c 100644 --- a/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.ts +++ b/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.ts @@ -356,9 +356,8 @@ export class SecureSpacesClientWrapper implements ISpacesClient { const ensureAuthorizedDependencies: EnsureAuthorizedDependencies = { actions: this.authorization.actions, errors: this.errors, - checkSavedObjectsPrivilegesAsCurrentUser: this.authorization.checkSavedObjectsPrivilegesWithRequest( - this.request - ), + checkSavedObjectsPrivilegesAsCurrentUser: + this.authorization.checkSavedObjectsPrivilegesWithRequest(this.request), }; return ensureAuthorized(ensureAuthorizedDependencies, types, actions, namespaces, options); } diff --git a/x-pack/plugins/security/server/usage_collector/security_usage_collector.ts b/x-pack/plugins/security/server/usage_collector/security_usage_collector.ts index 15177132e0fb1..66f414109d7bb 100644 --- a/x-pack/plugins/security/server/usage_collector/security_usage_collector.ts +++ b/x-pack/plugins/security/server/usage_collector/security_usage_collector.ts @@ -132,12 +132,8 @@ export function registerSecurityUsageCollector({ usageCollection, config, licens }, }, fetch: () => { - const { - allowRbac, - allowAccessAgreement, - allowAuditLogging, - allowLegacyAuditLogging, - } = license.getFeatures(); + const { allowRbac, allowAccessAgreement, allowAuditLogging, allowLegacyAuditLogging } = + license.getFeatures(); if (!allowRbac) { return { auditLoggingEnabled: false, diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.mock.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.mock.ts index 641918fdda709..248b3e4c8beb7 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.mock.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.mock.ts @@ -93,60 +93,61 @@ export const getAddPrepackagedThreatMatchRulesSchemaMock = (): AddPrepackagedRul ], }); -export const getAddPrepackagedThreatMatchRulesSchemaDecodedMock = (): AddPrepackagedRulesSchemaDecoded => ({ - author: [], - description: 'some description', - name: 'Query with a rule id', - query: 'user.name: root or user.name: admin', - severity: 'high', - severity_mapping: [], - type: 'threat_match', - risk_score: 55, - risk_score_mapping: [], - language: 'kuery', - references: [], - actions: [], - enabled: false, - false_positives: [], - from: 'now-6m', - interval: '5m', - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - to: 'now', - threat: [], - throttle: null, - version: 1, - exceptions_list: [], - rule_id: 'rule-1', - threat_query: '*:*', - threat_index: ['list-index'], - threat_mapping: [ - { - entries: [ - { - field: 'host.name', - value: 'host.name', - type: 'mapping', - }, - ], - }, - ], - threat_filters: [ - { - bool: { - must: [ +export const getAddPrepackagedThreatMatchRulesSchemaDecodedMock = + (): AddPrepackagedRulesSchemaDecoded => ({ + author: [], + description: 'some description', + name: 'Query with a rule id', + query: 'user.name: root or user.name: admin', + severity: 'high', + severity_mapping: [], + type: 'threat_match', + risk_score: 55, + risk_score_mapping: [], + language: 'kuery', + references: [], + actions: [], + enabled: false, + false_positives: [], + from: 'now-6m', + interval: '5m', + max_signals: DEFAULT_MAX_SIGNALS, + tags: [], + to: 'now', + threat: [], + throttle: null, + version: 1, + exceptions_list: [], + rule_id: 'rule-1', + threat_query: '*:*', + threat_index: ['list-index'], + threat_mapping: [ + { + entries: [ { - query_string: { - query: 'host.name: linux', - analyze_wildcard: true, - time_zone: 'Zulu', - }, + field: 'host.name', + value: 'host.name', + type: 'mapping', }, ], - filter: [], - should: [], - must_not: [], }, - }, - ], -}); + ], + threat_filters: [ + { + bool: { + must: [ + { + query_string: { + query: 'host.name: linux', + analyze_wildcard: true, + time_zone: 'Zulu', + }, + }, + ], + filter: [], + should: [], + must_not: [], + }, + }, + ], + }); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.test.ts index 7e34dd4a1efc0..9d0456c507772 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.test.ts @@ -630,9 +630,7 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect(((message.schema as unknown) as AddPrepackagedRulesSchemaDecoded).enabled).toEqual( - false - ); + expect((message.schema as unknown as AddPrepackagedRulesSchemaDecoded).enabled).toEqual(false); }); test('rule_id is required', () => { @@ -686,10 +684,8 @@ describe('add prepackaged rules schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const { - interval: expectedInterval, - ...expectedNoInterval - } = getAddPrepackagedRulesSchemaDecodedMock(); + const { interval: expectedInterval, ...expectedNoInterval } = + getAddPrepackagedRulesSchemaDecodedMock(); const expected: AddPrepackagedRulesSchemaDecoded = { ...expectedNoInterval, interval: '5m', @@ -709,10 +705,8 @@ describe('add prepackaged rules schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const { - max_signals: expectedMaxSignals, - ...expectedNoMaxSignals - } = getAddPrepackagedRulesSchemaDecodedMock(); + const { max_signals: expectedMaxSignals, ...expectedNoMaxSignals } = + getAddPrepackagedRulesSchemaDecodedMock(); const expected: AddPrepackagedRulesSchemaDecoded = { ...expectedNoMaxSignals, max_signals: 100, @@ -1210,10 +1204,8 @@ describe('add prepackaged rules schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const { - actions: expectedActions, - ...expectedNoActions - } = getAddPrepackagedRulesSchemaDecodedMock(); + const { actions: expectedActions, ...expectedNoActions } = + getAddPrepackagedRulesSchemaDecodedMock(); const expected: AddPrepackagedRulesSchemaDecoded = { ...expectedNoActions, actions: [], @@ -1314,10 +1306,8 @@ describe('add prepackaged rules schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const { - throttle: expectedThrottle, - ...expectedNoThrottle - } = getAddPrepackagedRulesSchemaDecodedMock(); + const { throttle: expectedThrottle, ...expectedNoThrottle } = + getAddPrepackagedRulesSchemaDecodedMock(); const expected: AddPrepackagedRulesSchemaDecoded = { ...expectedNoThrottle, throttle: null, diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.test.ts index 1a937473c4b11..1a8e038cf9e28 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.test.ts @@ -694,10 +694,8 @@ describe('import rules schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const { - max_signals: expectedMaxSignals, - ...expectedNoMaxSignals - } = getImportRulesSchemaDecodedMock(); + const { max_signals: expectedMaxSignals, ...expectedNoMaxSignals } = + getImportRulesSchemaDecodedMock(); const expected: ImportRulesSchemaDecoded = { ...expectedNoMaxSignals, max_signals: 100, diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.test.ts index 72334c81ce077..4000b8af4829d 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.test.ts @@ -1174,12 +1174,8 @@ describe('create rules schema', () => { test('threat_index, threat_query, and threat_mapping are required when type is "threat_match" and validation fails without them', () => { /* eslint-disable @typescript-eslint/naming-convention */ - const { - threat_index, - threat_query, - threat_mapping, - ...payload - } = getCreateThreatMatchRulesSchemaMock(); + const { threat_index, threat_query, threat_mapping, ...payload } = + getCreateThreatMatchRulesSchemaMock(); const decoded = createRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.test.ts index f8404ac677186..ddf8f665919ec 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.test.ts @@ -421,7 +421,7 @@ describe('rules_schema', () => { const payload: Omit & { type: string } = getRulesSchemaMock(); payload.type = 'invalid_data'; - const dependents = getDependents((payload as unknown) as TypeAndTimelineOnly); + const dependents = getDependents(payload as unknown as TypeAndTimelineOnly); const decoded = dependents.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); diff --git a/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_endpoint_hosts.ts b/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_endpoint_hosts.ts index 7e647b2a2ec5e..d92706d4e861a 100644 --- a/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_endpoint_hosts.ts +++ b/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_endpoint_hosts.ts @@ -212,10 +212,12 @@ export async function indexEndpointHostDocs({ } const fetchKibanaVersion = async (kbnClient: KbnClient) => { - const version = ((await kbnClient.request({ - path: '/api/status', - method: 'GET', - })) as AxiosResponse).data.version.number; + const version = ( + (await kbnClient.request({ + path: '/api/status', + method: 'GET', + })) as AxiosResponse + ).data.version.number; if (!version) { throw new EndpointDataLoadingError('failed to get kibana version via `/api/status` api'); diff --git a/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_fleet_agent.ts b/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_fleet_agent.ts index 901af259a5d2b..b792b34dd510f 100644 --- a/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_fleet_agent.ts +++ b/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_fleet_agent.ts @@ -87,12 +87,14 @@ export const indexFleetAgentForHost = async ( }; const fetchFleetAgent = async (kbnClient: KbnClient, agentId: string): Promise => { - return ((await kbnClient - .request({ - path: AGENT_API_ROUTES.INFO_PATTERN.replace('{agentId}', agentId), - method: 'GET', - }) - .catch(wrapErrorAndRejectPromise)) as AxiosResponse).data.item; + return ( + (await kbnClient + .request({ + path: AGENT_API_ROUTES.INFO_PATTERN.replace('{agentId}', agentId), + method: 'GET', + }) + .catch(wrapErrorAndRejectPromise)) as AxiosResponse + ).data.item; }; export interface DeleteIndexedFleetAgentsResponse { diff --git a/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_fleet_endpoint_policy.ts b/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_fleet_endpoint_policy.ts index 05e81e2645281..bc8c7a0525b24 100644 --- a/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_fleet_endpoint_policy.ts +++ b/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_fleet_endpoint_policy.ts @@ -126,15 +126,17 @@ export const deleteIndexedFleetEndpointPolicies = async ( }; if (indexData.integrationPolicies.length) { - response.integrationPolicies = ((await kbnClient - .request({ - path: PACKAGE_POLICY_API_ROUTES.DELETE_PATTERN, - method: 'POST', - body: { - packagePolicyIds: indexData.integrationPolicies.map((policy) => policy.id), - }, - }) - .catch(wrapErrorAndRejectPromise)) as AxiosResponse).data; + response.integrationPolicies = ( + (await kbnClient + .request({ + path: PACKAGE_POLICY_API_ROUTES.DELETE_PATTERN, + method: 'POST', + body: { + packagePolicyIds: indexData.integrationPolicies.map((policy) => policy.id), + }, + }) + .catch(wrapErrorAndRejectPromise)) as AxiosResponse + ).data; } if (indexData.agentPolicies.length) { @@ -142,15 +144,17 @@ export const deleteIndexedFleetEndpointPolicies = async ( for (const agentPolicy of indexData.agentPolicies) { response.agentPolicies.push( - ((await kbnClient - .request({ - path: AGENT_POLICY_API_ROUTES.DELETE_PATTERN, - method: 'POST', - body: { - agentPolicyId: agentPolicy.id, - }, - }) - .catch(wrapErrorAndRejectPromise)) as AxiosResponse).data + ( + (await kbnClient + .request({ + path: AGENT_POLICY_API_ROUTES.DELETE_PATTERN, + method: 'POST', + body: { + agentPolicyId: agentPolicy.id, + }, + }) + .catch(wrapErrorAndRejectPromise)) as AxiosResponse + ).data ); } } diff --git a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts index 8f985db732b61..a1b8ca98afc20 100644 --- a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts +++ b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts @@ -860,8 +860,7 @@ export class EndpointDocGenerator extends BaseDataGenerator { direction: 'outgoing', }, registry: { - path: - 'HKEY_USERS\\S-1-5-21-2460036010-3910878774-3458087990-1001\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run\\chrome', + path: 'HKEY_USERS\\S-1-5-21-2460036010-3910878774-3458087990-1001\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run\\chrome', value: processName, data: { strings: `C:/fake_behavior/${processName}`, @@ -1853,8 +1852,8 @@ export class EndpointDocGenerator extends BaseDataGenerator { status: this.commonInfo.Endpoint.policy.applied.status, version: policyVersion, name: this.commonInfo.Endpoint.policy.applied.name, - endpoint_policy_version: this.commonInfo.Endpoint.policy.applied - .endpoint_policy_version, + endpoint_policy_version: + this.commonInfo.Endpoint.policy.applied.endpoint_policy_version, }, }, }, diff --git a/x-pack/plugins/security_solution/common/endpoint/index_data.ts b/x-pack/plugins/security_solution/common/endpoint/index_data.ts index 37dcc1e831b94..2221b2a2d2c96 100644 --- a/x-pack/plugins/security_solution/common/endpoint/index_data.ts +++ b/x-pack/plugins/security_solution/common/endpoint/index_data.ts @@ -119,12 +119,12 @@ export async function indexHostsAndAlerts( const getEndpointPackageInfo = async ( kbnClient: KbnClient ): Promise => { - const endpointPackage = ((await kbnClient.request({ - path: `${EPM_API_ROUTES.LIST_PATTERN}?category=security`, - method: 'GET', - })) as AxiosResponse).data.response.find( - (epmPackage) => epmPackage.name === 'endpoint' - ); + const endpointPackage = ( + (await kbnClient.request({ + path: `${EPM_API_ROUTES.LIST_PATTERN}?category=security`, + method: 'GET', + })) as AxiosResponse + ).data.response.find((epmPackage) => epmPackage.name === 'endpoint'); if (!endpointPackage) { throw new Error('EPM Endpoint package was not found!'); diff --git a/x-pack/plugins/security_solution/common/endpoint/service/policy/get_policy_data_for_update.ts b/x-pack/plugins/security_solution/common/endpoint/service/policy/get_policy_data_for_update.ts index b929cde3dbb1c..0787525a38b66 100644 --- a/x-pack/plugins/security_solution/common/endpoint/service/policy/get_policy_data_for_update.ts +++ b/x-pack/plugins/security_solution/common/endpoint/service/policy/get_policy_data_for_update.ts @@ -21,10 +21,9 @@ export const getPolicyDataForUpdate = (policy: MaybeImmutable): NewP const endpointPolicy = policyDataForUpdate.inputs[0].config.policy.value; // trim custom malware notification string - [ - endpointPolicy.windows.popup.malware, - endpointPolicy.mac.popup.malware, - ].forEach((objWithMessage) => objWithMessage.message.trim()); + [endpointPolicy.windows.popup.malware, endpointPolicy.mac.popup.malware].forEach( + (objWithMessage) => objWithMessage.message.trim() + ); return policyDataForUpdate; }; diff --git a/x-pack/plugins/security_solution/common/endpoint/service/trusted_apps/validations.ts b/x-pack/plugins/security_solution/common/endpoint/service/trusted_apps/validations.ts index 37aeec8adea80..0fe3c0269bd15 100644 --- a/x-pack/plugins/security_solution/common/endpoint/service/trusted_apps/validations.ts +++ b/x-pack/plugins/security_solution/common/endpoint/service/trusted_apps/validations.ts @@ -58,7 +58,8 @@ export const isPathValid = ({ const doesPathMatchRegex = ({ os, value }: { os: OperatingSystem; value: string }): boolean => { if (os === OperatingSystem.WINDOWS) { - const filePathRegex = /^[a-z]:(?:|\\\\[^<>:"'/\\|?*]+\\[^<>:"'/\\|?*]+|%\w+%|)[\\](?:[^<>:"'/\\|?*]+[\\/])*([^<>:"'/\\|?*])+$/i; + const filePathRegex = + /^[a-z]:(?:|\\\\[^<>:"'/\\|?*]+\\[^<>:"'/\\|?*]+|%\w+%|)[\\](?:[^<>:"'/\\|?*]+[\\/])*([^<>:"'/\\|?*])+$/i; return filePathRegex.test(value); } return /^(\/|(\/[\w\-]+)+|\/[\w\-]+\.[\w]+|(\/[\w-]+)+\/[\w\-]+\.[\w]+)$/i.test(value); diff --git a/x-pack/plugins/security_solution/common/endpoint/types/index.ts b/x-pack/plugins/security_solution/common/endpoint/types/index.ts index 3a2eaf2763c4f..9b899d9c1b887 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types/index.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types/index.ts @@ -860,8 +860,7 @@ type KbnConfigSchemaInputObjectTypeOf

> = { [K in Exclude>]?: KbnConfigSchemaInputTypeOf< P[K] >; -} & - { [K in keyof KbnConfigSchemaNonOptionalProps

]: KbnConfigSchemaInputTypeOf }; +} & { [K in keyof KbnConfigSchemaNonOptionalProps

]: KbnConfigSchemaInputTypeOf }; /** * Takes the props of a schema.object type, and returns a version that excludes @@ -1094,7 +1093,8 @@ export interface HostPolicyResponseAppliedAction { message: string; } -export type HostPolicyResponseConfiguration = HostPolicyResponse['Endpoint']['policy']['applied']['response']['configurations']; +export type HostPolicyResponseConfiguration = + HostPolicyResponse['Endpoint']['policy']['applied']['response']['configurations']; interface HostPolicyResponseConfigurationStatus { status: HostPolicyResponseActionStatus; diff --git a/x-pack/plugins/security_solution/common/license/mocks.ts b/x-pack/plugins/security_solution/common/license/mocks.ts index f352932b44613..b691e1510822f 100644 --- a/x-pack/plugins/security_solution/common/license/mocks.ts +++ b/x-pack/plugins/security_solution/common/license/mocks.ts @@ -8,7 +8,7 @@ import { LicenseService } from './license'; export const createLicenseServiceMock = (): jest.Mocked => { - return ({ + return { start: jest.fn(), stop: jest.fn(), getLicenseInformation: jest.fn(), @@ -17,5 +17,5 @@ export const createLicenseServiceMock = (): jest.Mocked => { isGoldPlus: jest.fn().mockReturnValue(true), isPlatinumPlus: jest.fn().mockReturnValue(true), isEnterprise: jest.fn().mockReturnValue(true), - } as unknown) as jest.Mocked; + } as unknown as jest.Mocked; }; diff --git a/x-pack/plugins/security_solution/common/search_strategy/timeline/index.ts b/x-pack/plugins/security_solution/common/search_strategy/timeline/index.ts index 7064ef033fc5a..548560ac5cb8c 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/timeline/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/timeline/index.ts @@ -53,29 +53,27 @@ export interface TimelineRequestOptionsPaginated sort: Array>; } -export type TimelineStrategyResponseType< - T extends TimelineFactoryQueryTypes -> = T extends TimelineEventsQueries.all - ? TimelineEventsAllStrategyResponse - : T extends TimelineEventsQueries.details - ? TimelineEventsDetailsStrategyResponse - : T extends TimelineEventsQueries.kpi - ? TimelineKpiStrategyResponse - : T extends TimelineEventsQueries.lastEventTime - ? TimelineEventsLastEventTimeStrategyResponse - : never; - -export type TimelineStrategyRequestType< - T extends TimelineFactoryQueryTypes -> = T extends TimelineEventsQueries.all - ? TimelineEventsAllRequestOptions - : T extends TimelineEventsQueries.details - ? TimelineEventsDetailsRequestOptions - : T extends TimelineEventsQueries.kpi - ? TimelineRequestBasicOptions - : T extends TimelineEventsQueries.lastEventTime - ? TimelineEventsLastEventTimeRequestOptions - : never; +export type TimelineStrategyResponseType = + T extends TimelineEventsQueries.all + ? TimelineEventsAllStrategyResponse + : T extends TimelineEventsQueries.details + ? TimelineEventsDetailsStrategyResponse + : T extends TimelineEventsQueries.kpi + ? TimelineKpiStrategyResponse + : T extends TimelineEventsQueries.lastEventTime + ? TimelineEventsLastEventTimeStrategyResponse + : never; + +export type TimelineStrategyRequestType = + T extends TimelineEventsQueries.all + ? TimelineEventsAllRequestOptions + : T extends TimelineEventsQueries.details + ? TimelineEventsDetailsRequestOptions + : T extends TimelineEventsQueries.kpi + ? TimelineRequestBasicOptions + : T extends TimelineEventsQueries.lastEventTime + ? TimelineEventsLastEventTimeRequestOptions + : never; export interface ColumnHeaderInput { aggregatable?: Maybe; diff --git a/x-pack/plugins/security_solution/common/types/timeline/note/index.ts b/x-pack/plugins/security_solution/common/types/timeline/note/index.ts index 074e4132efdff..4bda81d75d92e 100644 --- a/x-pack/plugins/security_solution/common/types/timeline/note/index.ts +++ b/x-pack/plugins/security_solution/common/types/timeline/note/index.ts @@ -31,6 +31,12 @@ export const SavedNoteRuntimeType = runtimeTypes.intersection([ export interface SavedNote extends runtimeTypes.TypeOf {} +/** + * This type represents a note type stored in a saved object that does not include any fields that reference + * other saved objects. + */ +export type NoteWithoutExternalRefs = Omit; + /** * Note Saved object type with metadata */ diff --git a/x-pack/plugins/security_solution/common/types/timeline/pinned_event/index.ts b/x-pack/plugins/security_solution/common/types/timeline/pinned_event/index.ts index dbb19df7a6b05..df230615818ac 100644 --- a/x-pack/plugins/security_solution/common/types/timeline/pinned_event/index.ts +++ b/x-pack/plugins/security_solution/common/types/timeline/pinned_event/index.ts @@ -30,6 +30,12 @@ export const SavedPinnedEventRuntimeType = runtimeTypes.intersection([ export interface SavedPinnedEvent extends runtimeTypes.TypeOf {} +/** + * This type represents a pinned event type stored in a saved object that does not include any fields that reference + * other saved objects. + */ +export type PinnedEventWithoutExternalRefs = Omit; + /** * Note Saved object type with metadata */ diff --git a/x-pack/plugins/security_solution/common/utility_types.ts b/x-pack/plugins/security_solution/common/utility_types.ts index 498b18dccaca5..ccdbc254033fd 100644 --- a/x-pack/plugins/security_solution/common/utility_types.ts +++ b/x-pack/plugins/security_solution/common/utility_types.ts @@ -25,7 +25,7 @@ export const stringEnum = (enumObj: T, enumName = 'enum') => Object.values(enumObj).includes(u) ? runtimeTypes.success(u as T[keyof T]) : runtimeTypes.failure(u, c), - (a) => (a as unknown) as string + (a) => a as unknown as string ); /** diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/cti_enrichments.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/cti_enrichments.spec.ts index 8ce3de6e5d7ac..8d60dc33216c0 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/cti_enrichments.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/cti_enrichments.spec.ts @@ -79,8 +79,7 @@ describe('CTI Enrichment', () => { { line: 4, text: ' "threat": {' }, { line: 3, - text: - ' "enrichments": "{\\"indicator\\":{\\"first_seen\\":\\"2021-03-10T08:02:14.000Z\\",\\"file\\":{\\"size\\":80280,\\"pe\\":{},\\"type\\":\\"elf\\",\\"hash\\":{\\"sha256\\":\\"a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3\\",\\"tlsh\\":\\"6D7312E017B517CC1371A8353BED205E9128223972AE35302E97528DF957703BAB2DBE\\",\\"ssdeep\\":\\"1536:87vbq1lGAXSEYQjbChaAU2yU23M51DjZgSQAvcYkFtZTjzBht5:8D+CAXFYQChaAUk5ljnQssL\\",\\"md5\\":\\"9b6c3518a91d23ed77504b5416bfb5b3\\"}},\\"type\\":\\"file\\"},\\"matched\\":{\\"atomic\\":\\"a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3\\",\\"field\\":\\"myhash.mysha256\\",\\"id\\":\\"84cf452c1e0375c3d4412cb550bd1783358468a3b3b777da4829d72c7d6fb74f\\",\\"index\\":\\"filebeat-7.12.0-2021.03.10-000001\\",\\"type\\":\\"indicator_match_rule\\"}}"', + text: ' "enrichments": "{\\"indicator\\":{\\"first_seen\\":\\"2021-03-10T08:02:14.000Z\\",\\"file\\":{\\"size\\":80280,\\"pe\\":{},\\"type\\":\\"elf\\",\\"hash\\":{\\"sha256\\":\\"a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3\\",\\"tlsh\\":\\"6D7312E017B517CC1371A8353BED205E9128223972AE35302E97528DF957703BAB2DBE\\",\\"ssdeep\\":\\"1536:87vbq1lGAXSEYQjbChaAU2yU23M51DjZgSQAvcYkFtZTjzBht5:8D+CAXFYQChaAUk5ljnQssL\\",\\"md5\\":\\"9b6c3518a91d23ed77504b5416bfb5b3\\"}},\\"type\\":\\"file\\"},\\"matched\\":{\\"atomic\\":\\"a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3\\",\\"field\\":\\"myhash.mysha256\\",\\"id\\":\\"84cf452c1e0375c3d4412cb550bd1783358468a3b3b777da4829d72c7d6fb74f\\",\\"index\\":\\"filebeat-7.12.0-2021.03.10-000001\\",\\"type\\":\\"indicator_match_rule\\"}}"', }, { line: 2, text: ' }' }, ]; diff --git a/x-pack/plugins/security_solution/cypress/urls/state.ts b/x-pack/plugins/security_solution/cypress/urls/state.ts index 59aac0fd8999c..b76750184c36f 100644 --- a/x-pack/plugins/security_solution/cypress/urls/state.ts +++ b/x-pack/plugins/security_solution/cypress/urls/state.ts @@ -6,8 +6,7 @@ */ export const ABSOLUTE_DATE_RANGE = { - url: - '/app/security/network/flows/?timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-01T20:03:29.186Z%27,kind:absolute,to:%272019-08-01T20:33:29.186Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-01T20:03:29.186Z%27,kind:absolute,to:%272019-08-01T20:33:29.186Z%27)))', + url: '/app/security/network/flows/?timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-01T20:03:29.186Z%27,kind:absolute,to:%272019-08-01T20:33:29.186Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-01T20:03:29.186Z%27,kind:absolute,to:%272019-08-01T20:33:29.186Z%27)))', urlWithTimestamps: '/app/security/network/flows/?timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1564691609186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1564691609186)))', diff --git a/x-pack/plugins/security_solution/public/app/deep_links/index.test.ts b/x-pack/plugins/security_solution/public/app/deep_links/index.test.ts index 59af6737e495f..6dd086757776f 100644 --- a/x-pack/plugins/security_solution/public/app/deep_links/index.test.ts +++ b/x-pack/plugins/security_solution/public/app/deep_links/index.test.ts @@ -27,18 +27,18 @@ describe('public search functions', () => { it('returns case links for basic license with only read_cases capabilities', () => { const basicLicense = 'basic'; - const basicLinks = getDeepLinks(mockGlobalState.app.enableExperimental, basicLicense, ({ + const basicLinks = getDeepLinks(mockGlobalState.app.enableExperimental, basicLicense, { siem: { read_cases: true, crud_cases: false }, - } as unknown) as Capabilities); + } as unknown as Capabilities); expect(basicLinks.some((l) => l.id === SecurityPageName.case)).toBeTruthy(); }); it('returns case links with NO deepLinks for basic license with only read_cases capabilities', () => { const basicLicense = 'basic'; - const basicLinks = getDeepLinks(mockGlobalState.app.enableExperimental, basicLicense, ({ + const basicLinks = getDeepLinks(mockGlobalState.app.enableExperimental, basicLicense, { siem: { read_cases: true, crud_cases: false }, - } as unknown) as Capabilities); + } as unknown as Capabilities); expect( basicLinks.find((l) => l.id === SecurityPageName.case)?.deepLinks?.length === 0 @@ -47,9 +47,9 @@ describe('public search functions', () => { it('returns case links with deepLinks for basic license with crud_cases capabilities', () => { const basicLicense = 'basic'; - const basicLinks = getDeepLinks(mockGlobalState.app.enableExperimental, basicLicense, ({ + const basicLinks = getDeepLinks(mockGlobalState.app.enableExperimental, basicLicense, { siem: { read_cases: true, crud_cases: true }, - } as unknown) as Capabilities); + } as unknown as Capabilities); expect( (basicLinks.find((l) => l.id === SecurityPageName.case)?.deepLinks?.length ?? 0) > 0 @@ -58,9 +58,9 @@ describe('public search functions', () => { it('returns NO case links for basic license with NO read_cases capabilities', () => { const basicLicense = 'basic'; - const basicLinks = getDeepLinks(mockGlobalState.app.enableExperimental, basicLicense, ({ + const basicLinks = getDeepLinks(mockGlobalState.app.enableExperimental, basicLicense, { siem: { read_cases: false, crud_cases: false }, - } as unknown) as Capabilities); + } as unknown as Capabilities); expect(basicLinks.some((l) => l.id === SecurityPageName.case)).toBeFalsy(); }); diff --git a/x-pack/plugins/security_solution/public/app/home/template_wrapper/index.tsx b/x-pack/plugins/security_solution/public/app/home/template_wrapper/index.tsx index 99f67ba26e8f1..04f81d2cac3df 100644 --- a/x-pack/plugins/security_solution/public/app/home/template_wrapper/index.tsx +++ b/x-pack/plugins/security_solution/public/app/home/template_wrapper/index.tsx @@ -65,8 +65,8 @@ interface SecuritySolutionPageWrapperProps { onAppLeave: (handler: AppLeaveHandler) => void; } -export const SecuritySolutionTemplateWrapper: React.FC = React.memo( - ({ children, onAppLeave }) => { +export const SecuritySolutionTemplateWrapper: React.FC = + React.memo(({ children, onAppLeave }) => { const solutionNav = useSecuritySolutionNavigation(); const [isTimelineBottomBarVisible] = useShowTimeline(); const getTimelineShowStatus = useMemo(() => getTimelineShowStatusByIdSelector(), []); @@ -99,5 +99,4 @@ export const SecuritySolutionTemplateWrapper: React.FC ); - } -); + }); diff --git a/x-pack/plugins/security_solution/public/cases/components/callout/callout.tsx b/x-pack/plugins/security_solution/public/cases/components/callout/callout.tsx index 4cd7fad10fe70..f00fa84c6ff0a 100644 --- a/x-pack/plugins/security_solution/public/cases/components/callout/callout.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/callout/callout.tsx @@ -29,11 +29,10 @@ const CallOutComponent = ({ showCallOut, handleDismissCallout, }: CallOutProps) => { - const handleCallOut = useCallback(() => handleDismissCallout(id, type), [ - handleDismissCallout, - id, - type, - ]); + const handleCallOut = useCallback( + () => handleDismissCallout(id, type), + [handleDismissCallout, id, type] + ); return showCallOut && !isEmpty(messages) ? ( diff --git a/x-pack/plugins/security_solution/public/common/components/and_or_badge/rounded_badge.tsx b/x-pack/plugins/security_solution/public/common/components/and_or_badge/rounded_badge.tsx index 8589e6669f7ed..eac20b0673426 100644 --- a/x-pack/plugins/security_solution/public/common/components/and_or_badge/rounded_badge.tsx +++ b/x-pack/plugins/security_solution/public/common/components/and_or_badge/rounded_badge.tsx @@ -12,7 +12,7 @@ import styled from 'styled-components'; import * as i18n from './translations'; import { AndOr } from '.'; -const RoundBadge = (styled(EuiBadge)` +const RoundBadge = styled(EuiBadge)` align-items: center; border-radius: 100%; display: inline-flex; @@ -30,7 +30,7 @@ const RoundBadge = (styled(EuiBadge)` .euiBadge__text { text-overflow: clip; } -` as unknown) as typeof EuiBadge; +` as unknown as typeof EuiBadge; RoundBadge.displayName = 'RoundBadge'; diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/drag_drop_context_wrapper.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/drag_drop_context_wrapper.tsx index 1ab19c44e29b2..da7313e85e631 100644 --- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/drag_drop_context_wrapper.tsx +++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/drag_drop_context_wrapper.tsx @@ -105,14 +105,12 @@ export const DragDropContextWrapperComponent: React.FC = ({ browserFields const getDataProviders = useMemo(() => dragAndDropSelectors.getDataProvidersSelector(), []); const { timelines } = useKibana().services; const sensors = [timelines.getUseAddToTimelineSensor()]; - const { - dataProviders: activeTimelineDataProviders, - timelineType, - } = useDeepEqualSelector((state) => - pick( - ['dataProviders', 'timelineType'], - getTimeline(state, TimelineId.active) ?? timelineDefaults - ) + const { dataProviders: activeTimelineDataProviders, timelineType } = useDeepEqualSelector( + (state) => + pick( + ['dataProviders', 'timelineType'], + getTimeline(state, TimelineId.active) ?? timelineDefaults + ) ); const dataProviders = useDeepEqualSelector(getDataProviders); diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/endpoint_host_isolation_cases_context.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/endpoint_host_isolation_cases_context.tsx index 8ae9d98a31429..089707ee0eec1 100644 --- a/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/endpoint_host_isolation_cases_context.tsx +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/endpoint_host_isolation_cases_context.tsx @@ -12,9 +12,8 @@ import { CaseViewRefreshPropInterface } from '../../../../../../cases/common'; * React Context that can hold the `Ref` that is created an passed to `CaseViewProps['refreshRef`]`, enabling * child components to trigger a refresh of a case. */ -export const CaseDetailsRefreshContext = React.createContext | null>( - null -); +export const CaseDetailsRefreshContext = + React.createContext | null>(null); /** * Returns the closes CaseDetails Refresh interface if any. Used in conjuction with `CaseDetailsRefreshContext` component diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/route_capture.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/route_capture.tsx index ebd25eef87cb7..a5e0c90402df4 100644 --- a/x-pack/plugins/security_solution/public/common/components/endpoint/route_capture.tsx +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/route_capture.tsx @@ -9,6 +9,8 @@ import React, { memo, useEffect } from 'react'; import { useLocation } from 'react-router-dom'; import { useDispatch } from 'react-redux'; import { AppLocation } from '../../../../common/endpoint/types'; +import { timelineActions } from '../../../timelines/store/timeline'; +import { TimelineId } from '../../../../../timelines/common'; /** * This component should be used above all routes, but below the Provider. @@ -18,6 +20,10 @@ export const RouteCapture = memo(({ children }) => { const location: AppLocation = useLocation(); const dispatch = useDispatch(); + useEffect(() => { + dispatch(timelineActions.showTimeline({ id: TimelineId.active, show: false })); + }, [dispatch, location.pathname]); + useEffect(() => { dispatch({ type: 'userChangedUrl', payload: location }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_accordion_group.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_accordion_group.tsx index e46a8520108b1..c17203de44c76 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_accordion_group.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_accordion_group.tsx @@ -84,9 +84,13 @@ const EnrichmentAccordion: React.FC<{ enrichment: CtiEnrichment; index: number; }> = ({ enrichment, index }) => { - const { id = `threat-details-item`, field, provider, type, value } = getEnrichmentIdentifiers( - enrichment - ); + const { + id = `threat-details-item`, + field, + provider, + type, + value, + } = getEnrichmentIdentifiers(enrichment); const accordionId = `${id}${field}`; return ( = ({ ); }, [summaryTab, threatIntelTab, tableTab, jsonTab]); - const selectedTab = useMemo(() => tabs.find((tab) => tab.id === selectedTabId) ?? tabs[0], [ - tabs, - selectedTabId, - ]); + const selectedTab = useMemo( + () => tabs.find((tab) => tab.id === selectedTabId) ?? tabs[0], + [tabs, selectedTabId] + ); return ( = ({ [isLoadingIndexPattern, combinedQueries, start, end] ); - const fields = useMemo(() => [...columnsHeader.map((c) => c.id), ...(queryFields ?? [])], [ - columnsHeader, - queryFields, - ]); + const fields = useMemo( + () => [...columnsHeader.map((c) => c.id), ...(queryFields ?? [])], + [columnsHeader, queryFields] + ); const sortField = useMemo( () => @@ -233,21 +233,19 @@ const EventsViewerComponent: React.FC = ({ [sort] ); - const [ - loading, - { events, updatedAt, inspect, loadPage, pageInfo, refetch, totalCount = 0 }, - ] = useTimelineEvents({ - docValueFields, - fields, - filterQuery: combinedQueries!.filterQuery, - id, - indexNames, - limit: itemsPerPage, - sort: sortField, - startDate: start, - endDate: end, - skip: !canQueryTimeline || combinedQueries?.filterQuery === undefined, // When the filterQuery comes back as undefined, it means an error has been thrown and the request should be skipped - }); + const [loading, { events, updatedAt, inspect, loadPage, pageInfo, refetch, totalCount = 0 }] = + useTimelineEvents({ + docValueFields, + fields, + filterQuery: combinedQueries!.filterQuery, + id, + indexNames, + limit: itemsPerPage, + sort: sortField, + startDate: start, + endDate: end, + skip: !canQueryTimeline || combinedQueries?.filterQuery === undefined, // When the filterQuery comes back as undefined, it means an error has been thrown and the request should be skipped + }); const totalCountMinusDeleted = useMemo( () => (totalCount > 0 ? totalCount - deletedEventIds.length : 0), @@ -264,10 +262,10 @@ const EventsViewerComponent: React.FC = ({ [showTotalCount, totalCountMinusDeleted, unit] ); - const nonDeletedEvents = useMemo(() => events.filter((e) => !deletedEventIds.includes(e._id)), [ - deletedEventIds, - events, - ]); + const nonDeletedEvents = useMemo( + () => events.filter((e) => !deletedEventIds.includes(e._id)), + [deletedEventIds, events] + ); const HeaderSectionContent = useMemo( () => diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx index c62337b2426d3..9e1fd3a769eee 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx @@ -177,8 +177,7 @@ const StatefulEventsViewerComponent: React.FC = ({ {tGridEnabled ? ( timelinesUi.getTGrid<'embedded'>({ - id, - type: 'embedded', + additionalFilters, browserFields, bulkActions, columns, @@ -189,9 +188,12 @@ const StatefulEventsViewerComponent: React.FC = ({ end, entityType, filters: globalFilters, + filterStatus: currentFilter, globalFullScreen, + graphEventId, graphOverlay, hasAlertsCrud, + id, indexNames: selectedPatterns, indexPattern, isLive, @@ -199,19 +201,17 @@ const StatefulEventsViewerComponent: React.FC = ({ itemsPerPage, itemsPerPageOptions: itemsPerPageOptions!, kqlMode, - query, + leadingControlColumns, onRuleChange, + query, renderCellValue, rowRenderers, setQuery, - start, sort, - additionalFilters, - graphEventId, - filterStatus: currentFilter, - leadingControlColumns, - trailingControlColumns, + start, tGridEventRenderedViewEnabled, + trailingControlColumns, + type: 'embedded', unit, }) ) : ( diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.test.tsx index 9a6879dfe6cc9..ac70d5276beb9 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.test.tsx @@ -16,7 +16,7 @@ import { ExceptionBuilder } from '../../../../shared_imports'; import { useAsync } from '@kbn/securitysolution-hook-utils'; import { getExceptionListSchemaMock } from '../../../../../../lists/common/schemas/response/exception_list_schema.mock'; import { useFetchIndex } from '../../../containers/source'; -import { stubIndexPattern } from 'src/plugins/data/common/index_patterns/index_pattern.stub'; +import { stubIndexPattern } from 'src/plugins/data/common/stubs'; import { useAddOrUpdateException } from '../use_add_exception'; import { useFetchOrCreateRuleExceptionList } from '../use_fetch_or_create_rule_exception_list'; import { useSignalIndex } from '../../../../detections/containers/detection_engine/alerts/use_signal_index'; diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx index 9d4626fba313f..228b7a2491fde 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx @@ -138,12 +138,12 @@ export const AddExceptionModal = memo(function AddExceptionModal({ const [fetchOrCreateListError, setFetchOrCreateListError] = useState(null); const { addError, addSuccess, addWarning } = useAppToasts(); const { loading: isSignalIndexLoading, signalIndexName } = useSignalIndex(); - const memoSignalIndexName = useMemo(() => (signalIndexName !== null ? [signalIndexName] : []), [ - signalIndexName, - ]); - const [isSignalIndexPatternLoading, { indexPatterns: signalIndexPatterns }] = useFetchIndex( - memoSignalIndexName + const memoSignalIndexName = useMemo( + () => (signalIndexName !== null ? [signalIndexName] : []), + [signalIndexName] ); + const [isSignalIndexPatternLoading, { indexPatterns: signalIndexPatterns }] = + useFetchIndex(memoSignalIndexName); const memoMlJobIds = useMemo(() => maybeRule?.machine_learning_job_id ?? [], [maybeRule]); const { loading: mlJobLoading, jobs } = useGetInstalledJob(memoMlJobIds); diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx index cc0ee7e1c0ed6..375648e0abc8d 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx @@ -119,12 +119,12 @@ export const EditExceptionModal = memo(function EditExceptionModal({ >([]); const { addError, addSuccess } = useAppToasts(); const { loading: isSignalIndexLoading, signalIndexName } = useSignalIndex(); - const memoSignalIndexName = useMemo(() => (signalIndexName !== null ? [signalIndexName] : []), [ - signalIndexName, - ]); - const [isSignalIndexPatternLoading, { indexPatterns: signalIndexPatterns }] = useFetchIndex( - memoSignalIndexName + const memoSignalIndexName = useMemo( + () => (signalIndexName !== null ? [signalIndexName] : []), + [signalIndexName] ); + const [isSignalIndexPatternLoading, { indexPatterns: signalIndexPatterns }] = + useFetchIndex(memoSignalIndexName); const memoMlJobIds = useMemo(() => maybeRule?.machine_learning_job_id ?? [], [maybeRule]); const { loading: mlJobLoading, jobs } = useGetInstalledJob(memoMlJobIds); diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx index 209d7d8fa273b..a90ec21f992f8 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx @@ -39,7 +39,7 @@ import { import { getExceptionListItemSchemaMock } from '../../../../../lists/common/schemas/response/exception_list_item_schema.mock'; import { getEntryMatchMock } from '../../../../../lists/common/schemas/types/entry_match.mock'; import { getCommentsArrayMock } from '../../../../../lists/common/schemas/types/comment.mock'; -import { fields } from '../../../../../../../src/plugins/data/common/index_patterns/fields/fields.mocks'; +import { fields } from '../../../../../../../src/plugins/data/common/mocks'; import { ENTRIES, OLD_DATE_RELATIVE_TO_DATE_NOW } from '../../../../../lists/common/constants.mock'; import { CodeSignature } from '../../../../common/ecs/file'; import { IndexPatternBase } from '@kbn/es-query'; @@ -342,7 +342,7 @@ describe('Exception helpers', () => { describe('#getCodeSignatureValue', () => { test('it should return empty string if code_signature nested value are undefined', () => { // Using the unsafe casting because with our types this shouldn't be possible but there have been issues with old data having undefined values in these fields - const payload = ([{ trusted: undefined, subject_name: undefined }] as unknown) as Flattened< + const payload = [{ trusted: undefined, subject_name: undefined }] as unknown as Flattened< CodeSignature[] >; const result = getCodeSignatureValue(payload); diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/index.tsx index c22823a89693d..40151b93732c1 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/index.tsx @@ -73,17 +73,18 @@ const ExceptionsViewerComponent = ({ const { services } = useKibana(); const [, dispatchToaster] = useStateToaster(); const onDispatchToaster = useCallback( - ({ title, color, iconType }) => (): void => { - dispatchToaster({ - type: 'addToaster', - toast: { - id: uuid.v4(), - title, - color, - iconType, - }, - }); - }, + ({ title, color, iconType }) => + (): void => { + dispatchToaster({ + type: 'addToaster', + toast: { + id: uuid.v4(), + title, + color, + iconType, + }, + }); + }, [dispatchToaster] ); const [ diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/reducer.ts b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/reducer.ts index 1a2a2b1dcf452..f67ac003dd012 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/reducer.ts +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/reducer.ts @@ -61,85 +61,90 @@ export type Action = totalDetectionsItems: number | null; }; -export const allExceptionItemsReducer = () => (state: State, action: Action): State => { - switch (action.type) { - case 'setExceptions': { - const { exceptions, pagination } = action; +export const allExceptionItemsReducer = + () => + (state: State, action: Action): State => { + switch (action.type) { + case 'setExceptions': { + const { exceptions, pagination } = action; - return { - ...state, - pagination: { - ...state.pagination, - pageIndex: pagination.page - 1, - pageSize: pagination.perPage, - totalItemCount: pagination.total ?? 0, - }, - exceptions, - }; + return { + ...state, + pagination: { + ...state.pagination, + pageIndex: pagination.page - 1, + pageSize: pagination.perPage, + totalItemCount: pagination.total ?? 0, + }, + exceptions, + }; + } + case 'updateFilterOptions': { + const { filter, pagination, showEndpointListsOnly, showDetectionsListsOnly } = + action.filters; + return { + ...state, + filterOptions: { + ...state.filterOptions, + ...filter, + }, + pagination: { + ...state.pagination, + ...pagination, + }, + showEndpointListsOnly: showEndpointListsOnly ?? state.showEndpointListsOnly, + showDetectionsListsOnly: showDetectionsListsOnly ?? state.showDetectionsListsOnly, + }; + } + case 'setExceptionItemTotals': { + return { + ...state, + totalEndpointItems: + action.totalEndpointItems == null + ? state.totalEndpointItems + : action.totalEndpointItems, + totalDetectionsItems: + action.totalDetectionsItems == null + ? state.totalDetectionsItems + : action.totalDetectionsItems, + }; + } + case 'updateIsInitLoading': { + return { + ...state, + isInitLoading: action.loading, + }; + } + case 'updateLoadingItemIds': { + return { + ...state, + loadingItemIds: [...state.loadingItemIds, ...action.items], + }; + } + case 'updateExceptionToEdit': { + const { exception, lists } = action; + const exceptionListToEdit = lists.find((list) => { + return list !== null && exception.list_id === list.listId; + }); + return { + ...state, + exceptionToEdit: exception, + exceptionListTypeToEdit: exceptionListToEdit ? exceptionListToEdit.type : null, + }; + } + case 'updateModalOpen': { + return { + ...state, + currentModal: action.modalName, + }; + } + case 'updateExceptionListTypeToEdit': { + return { + ...state, + exceptionListTypeToEdit: action.exceptionListType, + }; + } + default: + return state; } - case 'updateFilterOptions': { - const { filter, pagination, showEndpointListsOnly, showDetectionsListsOnly } = action.filters; - return { - ...state, - filterOptions: { - ...state.filterOptions, - ...filter, - }, - pagination: { - ...state.pagination, - ...pagination, - }, - showEndpointListsOnly: showEndpointListsOnly ?? state.showEndpointListsOnly, - showDetectionsListsOnly: showDetectionsListsOnly ?? state.showDetectionsListsOnly, - }; - } - case 'setExceptionItemTotals': { - return { - ...state, - totalEndpointItems: - action.totalEndpointItems == null ? state.totalEndpointItems : action.totalEndpointItems, - totalDetectionsItems: - action.totalDetectionsItems == null - ? state.totalDetectionsItems - : action.totalDetectionsItems, - }; - } - case 'updateIsInitLoading': { - return { - ...state, - isInitLoading: action.loading, - }; - } - case 'updateLoadingItemIds': { - return { - ...state, - loadingItemIds: [...state.loadingItemIds, ...action.items], - }; - } - case 'updateExceptionToEdit': { - const { exception, lists } = action; - const exceptionListToEdit = lists.find((list) => { - return list !== null && exception.list_id === list.listId; - }); - return { - ...state, - exceptionToEdit: exception, - exceptionListTypeToEdit: exceptionListToEdit ? exceptionListToEdit.type : null, - }; - } - case 'updateModalOpen': { - return { - ...state, - currentModal: action.modalName, - }; - } - case 'updateExceptionListTypeToEdit': { - return { - ...state, - exceptionListTypeToEdit: action.exceptionListType, - }; - } - default: - return state; - } -}; + }; diff --git a/x-pack/plugins/security_solution/public/common/components/formatted_date/index.tsx b/x-pack/plugins/security_solution/public/common/components/formatted_date/index.tsx index be11098c0d0ea..f6df6e132ebee 100644 --- a/x-pack/plugins/security_solution/public/common/components/formatted_date/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/formatted_date/index.tsx @@ -96,21 +96,19 @@ export const FormattedDate = React.memo<{ fieldName: string; value?: string | number | null; className?: string; -}>( - ({ value, fieldName, className = '' }): JSX.Element => { - if (value == null) { - return getOrEmptyTagFromValue(value); - } - const maybeDate = getMaybeDate(value); - return maybeDate.isValid() ? ( - - - - ) : ( - getOrEmptyTagFromValue(value) - ); +}>(({ value, fieldName, className = '' }): JSX.Element => { + if (value == null) { + return getOrEmptyTagFromValue(value); } -); + const maybeDate = getMaybeDate(value); + return maybeDate.isValid() ? ( + + + + ) : ( + getOrEmptyTagFromValue(value) + ); +}); FormattedDate.displayName = 'FormattedDate'; diff --git a/x-pack/plugins/security_solution/public/common/components/header_page/index.tsx b/x-pack/plugins/security_solution/public/common/components/header_page/index.tsx index 94abbca1da908..71747d9c5084f 100644 --- a/x-pack/plugins/security_solution/public/common/components/header_page/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/header_page/index.tsx @@ -49,9 +49,9 @@ const LinkBack = styled.div.attrs({ `; LinkBack.displayName = 'LinkBack'; -const Badge = (styled(EuiBadge)` +const Badge = styled(EuiBadge)` letter-spacing: 0; -` as unknown) as typeof EuiBadge; +` as unknown as typeof EuiBadge; Badge.displayName = 'Badge'; const HeaderSection = styled(EuiPageHeaderSection)` diff --git a/x-pack/plugins/security_solution/public/common/components/header_page/title.tsx b/x-pack/plugins/security_solution/public/common/components/header_page/title.tsx index a99132d5c7f62..5e622c0cf6355 100644 --- a/x-pack/plugins/security_solution/public/common/components/header_page/title.tsx +++ b/x-pack/plugins/security_solution/public/common/components/header_page/title.tsx @@ -19,9 +19,9 @@ const StyledEuiBetaBadge = styled(EuiBetaBadge)` StyledEuiBetaBadge.displayName = 'StyledEuiBetaBadge'; -const Badge = (styled(EuiBadge)` +const Badge = styled(EuiBadge)` letter-spacing: 0; -` as unknown) as typeof EuiBadge; +` as unknown as typeof EuiBadge; Badge.displayName = 'Badge'; const Header = styled.h1` diff --git a/x-pack/plugins/security_solution/public/common/components/hover_actions/actions/show_top_n.tsx b/x-pack/plugins/security_solution/public/common/components/hover_actions/actions/show_top_n.tsx index 0d6e59483fbc4..e1546c5220e22 100644 --- a/x-pack/plugins/security_solution/public/common/components/hover_actions/actions/show_top_n.tsx +++ b/x-pack/plugins/security_solution/public/common/components/hover_actions/actions/show_top_n.tsx @@ -21,6 +21,7 @@ import { useSourcererScope } from '../../../containers/sourcerer'; import { TooltipWithKeyboardShortcut } from '../../accessibility'; import { getAdditionalScreenReaderOnlyContext } from '../utils'; import { SHOW_TOP_N_KEYBOARD_SHORTCUT } from '../keyboard_shortcut_constants'; +import { Filter } from '../../../../../../../../src/plugins/data/public'; const SHOW_TOP = (fieldName: string) => i18n.translate('xpack.securitySolution.hoverActions.showTopTooltip', { @@ -35,11 +36,12 @@ interface Props { Component?: typeof EuiButtonEmpty | typeof EuiButtonIcon | typeof EuiContextMenuItem; enablePopOver?: boolean; field: string; + globalFilters?: Filter[]; onClick: () => void; onFilterAdded?: () => void; ownFocus: boolean; - showTopN: boolean; showTooltip?: boolean; + showTopN: boolean; timelineId?: string | null; value?: string[] | string | null; } @@ -56,6 +58,7 @@ export const ShowTopNButton: React.FC = React.memo( showTopN, timelineId, value, + globalFilters, }) => { const activeScope: SourcererScopeName = timelineId === TimelineId.active @@ -128,9 +131,10 @@ export const ShowTopNButton: React.FC = React.memo( timelineId={timelineId ?? undefined} toggleTopN={onClick} value={value} + globalFilters={globalFilters} /> ), - [browserFields, field, indexPattern, onClick, onFilterAdded, timelineId, value] + [browserFields, field, indexPattern, onClick, onFilterAdded, timelineId, value, globalFilters] ); return showTopN ? ( diff --git a/x-pack/plugins/security_solution/public/common/components/hover_actions/use_hover_action_items.test.tsx b/x-pack/plugins/security_solution/public/common/components/hover_actions/use_hover_action_items.test.tsx index 345f79521aa99..b961d700e8520 100644 --- a/x-pack/plugins/security_solution/public/common/components/hover_actions/use_hover_action_items.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/hover_actions/use_hover_action_items.test.tsx @@ -17,7 +17,7 @@ jest.mock('../../containers/sourcerer', () => ({ })); describe('useHoverActionItems', () => { - const defaultProps: UseHoverActionItemsProps = ({ + const defaultProps: UseHoverActionItemsProps = { dataProvider: [{} as DataProvider], defaultFocusedButtonRef: null, field: 'signal.rule.name', @@ -31,7 +31,7 @@ describe('useHoverActionItems', () => { toggleColumn: jest.fn(), toggleTopN: jest.fn(), values: ['rule name'], - } as unknown) as UseHoverActionItemsProps; + } as unknown as UseHoverActionItemsProps; beforeEach(() => { (useDeepEqualSelector as jest.Mock).mockImplementation((cb) => { diff --git a/x-pack/plugins/security_solution/public/common/components/hover_actions/use_hover_action_items.tsx b/x-pack/plugins/security_solution/public/common/components/hover_actions/use_hover_action_items.tsx index 7d480c9e4b04f..61cef7930a3a5 100644 --- a/x-pack/plugins/security_solution/public/common/components/hover_actions/use_hover_action_items.tsx +++ b/x-pack/plugins/security_solution/public/common/components/hover_actions/use_hover_action_items.tsx @@ -85,9 +85,10 @@ export const useHoverActionItems = ({ getFilterOutValueButton, getOverflowButton, } = timelines.getHoverActions(); - const filterManagerBackup = useMemo(() => kibana.services.data.query.filterManager, [ - kibana.services.data.query.filterManager, - ]); + const filterManagerBackup = useMemo( + () => kibana.services.data.query.filterManager, + [kibana.services.data.query.filterManager] + ); const getManageTimeline = useMemo(() => timelineSelectors.getManageTimelineById(), []); const { filterManager: activeFilterManager } = useDeepEqualSelector((state) => getManageTimeline(state, timelineId ?? '') diff --git a/x-pack/plugins/security_solution/public/common/components/links/index.tsx b/x-pack/plugins/security_solution/public/common/components/links/index.tsx index 9668f3ea99041..7db6b0204b649 100644 --- a/x-pack/plugins/security_solution/public/common/components/links/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/links/index.tsx @@ -46,9 +46,8 @@ import { getUebaDetailsUrl } from '../link_to/redirect_to_ueba'; export const DEFAULT_NUMBER_OF_LINK = 5; -export const LinkButton: React.FC< - PropsForButton | PropsForAnchor -> = ({ children, ...props }) => {children}; +export const LinkButton: React.FC | PropsForAnchor> = + ({ children, ...props }) => {children}; export const LinkAnchor: React.FC = ({ children, ...props }) => ( {children} diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/timeline/processor.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/timeline/processor.tsx index 3d9c7f0a09cd7..1546966c37cd8 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/timeline/processor.tsx +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/timeline/processor.tsx @@ -18,11 +18,10 @@ export const TimelineMarkDownRendererComponent: React.FC< } > = ({ id, title, graphEventId }) => { const handleTimelineClick = useTimelineClick(); - const onClickTimeline = useCallback(() => handleTimelineClick(id ?? '', graphEventId), [ - id, - graphEventId, - handleTimelineClick, - ]); + const onClickTimeline = useCallback( + () => handleTimelineClick(id ?? '', graphEventId), + [id, graphEventId, handleTimelineClick] + ); return ( diff --git a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx index 68a1ff14f2f0b..f25e8311ff8fe 100644 --- a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx @@ -124,9 +124,8 @@ export const MatrixHistogramComponent: React.FC = [chartHeight, startDate, legendPosition, endDate, handleBrushEnd, yTickFormatter, showLegend] ); const [isInitialLoading, setIsInitialLoading] = useState(true); - const [selectedStackByOption, setSelectedStackByOption] = useState( - defaultStackByOption - ); + const [selectedStackByOption, setSelectedStackByOption] = + useState(defaultStackByOption); const setSelectedChartOptionCallback = useCallback( (event: React.ChangeEvent) => { setSelectedStackByOption( @@ -150,9 +149,8 @@ export const MatrixHistogramComponent: React.FC = skip, }; - const [loading, { data, inspect, totalCount, refetch }] = useMatrixHistogramCombined( - matrixHistogramRequest - ); + const [loading, { data, inspect, totalCount, refetch }] = + useMatrixHistogramCombined(matrixHistogramRequest); const titleWithStackByField = useMemo( () => (title != null && typeof title === 'function' ? title(selectedStackByOption) : title), @@ -169,10 +167,10 @@ export const MatrixHistogramComponent: React.FC = return subtitle; }, [isInitialLoading, subtitle, totalCount]); - const hideHistogram = useMemo(() => (totalCount <= 0 && hideHistogramIfEmpty ? true : false), [ - totalCount, - hideHistogramIfEmpty, - ]); + const hideHistogram = useMemo( + () => (totalCount <= 0 && hideHistogramIfEmpty ? true : false), + [totalCount, hideHistogramIfEmpty] + ); const barChartData = useMemo(() => getCustomChartData(data, mapping), [data, mapping]); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/common/components/ml/api/throw_if_not_ok.test.ts b/x-pack/plugins/security_solution/public/common/components/ml/api/throw_if_not_ok.test.ts index 2130a3d8e05db..f4805e8a2126a 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml/api/throw_if_not_ok.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/ml/api/throw_if_not_ok.test.ts @@ -199,12 +199,10 @@ describe('throw_if_not_ok', () => { id: 'siem-api-suspicious_login_activity_ecs', success: false, error: { - msg: - '[status_exception] This job would cause a mapping clash with existing field [multi_bucket_impact] - avoid the clash by assigning a dedicated results index', + msg: '[status_exception] This job would cause a mapping clash with existing field [multi_bucket_impact] - avoid the clash by assigning a dedicated results index', path: '/_ml/anomaly_detectors/siem-api-suspicious_login_activity_ecs', query: {}, - body: - '{"job_type":"anomaly_detector","description":"SIEM Auditbeat: Detect unusually high number of authentication attempts (beta)","groups":["siem"],"analysis_config":{"bucket_span":"15m","detectors":[{"detector_description":"high number of authentication attempts","function":"high_non_zero_count","partition_field_name":"host.name"}],"influencers":["host.name","user.name","source.ip"]},"analysis_limits":{"model_memory_limit":"256mb"},"data_description":{"time_field":"@timestamp","time_format":"epoch_ms"},"custom_settings":{"created_by":"ml-module-siem-auditbeat","custom_urls":[{"url_name":"IP Address Details","url_value":"siem#/ml-network/ip/$source.ip$?query=!n&timerange=(global:(linkTo:!(timeline),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')),timeline:(linkTo:!(global),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')))"}]}}', + body: '{"job_type":"anomaly_detector","description":"SIEM Auditbeat: Detect unusually high number of authentication attempts (beta)","groups":["siem"],"analysis_config":{"bucket_span":"15m","detectors":[{"detector_description":"high number of authentication attempts","function":"high_non_zero_count","partition_field_name":"host.name"}],"influencers":["host.name","user.name","source.ip"]},"analysis_limits":{"model_memory_limit":"256mb"},"data_description":{"time_field":"@timestamp","time_format":"epoch_ms"},"custom_settings":{"created_by":"ml-module-siem-auditbeat","custom_urls":[{"url_name":"IP Address Details","url_value":"siem#/ml-network/ip/$source.ip$?query=!n&timerange=(global:(linkTo:!(timeline),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')),timeline:(linkTo:!(global),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')))"}]}}', statusCode: 400, response: '{"error":{"root_cause":[{"type":"status_exception","reason":"This job would cause a mapping clash with existing field [multi_bucket_impact] - avoid the clash by assigning a dedicated results index"}],"type":"status_exception","reason":"This job would cause a mapping clash with existing field [multi_bucket_impact] - avoid the clash by assigning a dedicated results index","caused_by":{"type":"illegal_argument_exception","reason":"mapper [multi_bucket_impact] of different type, current_type [keyword], merged_type [double]"}},"status":400}', @@ -214,12 +212,10 @@ describe('throw_if_not_ok', () => { id: 'siem-api-rare_process_linux_ecs', success: false, error: { - msg: - '[status_exception] This job would cause a mapping clash with existing field [multi_bucket_impact] - avoid the clash by assigning a dedicated results index', + msg: '[status_exception] This job would cause a mapping clash with existing field [multi_bucket_impact] - avoid the clash by assigning a dedicated results index', path: '/_ml/anomaly_detectors/siem-api-rare_process_linux_ecs', query: {}, - body: - '{"job_type":"anomaly_detector","description":"SIEM Auditbeat: Detect unusually rare processes on Linux (beta)","groups":["siem"],"analysis_config":{"bucket_span":"15m","detectors":[{"detector_description":"rare process executions on Linux","function":"rare","by_field_name":"process.name","partition_field_name":"host.name"}],"influencers":["host.name","process.name","user.name"]},"analysis_limits":{"model_memory_limit":"256mb"},"data_description":{"time_field":"@timestamp","time_format":"epoch_ms"},"custom_settings":{"created_by":"ml-module-siem-auditbeat","custom_urls":[{"url_name":"Host Details by process name","url_value":"siem#/ml-hosts/$host.name$?query=(query:\'process.name%20:%20%22$process.name$%22\',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')),timeline:(linkTo:!(global),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')))"},{"url_name":"Host Details by user name","url_value":"siem#/ml-hosts/$host.name$?query=(query:\'user.name%20:%20%22$user.name$%22\',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')),timeline:(linkTo:!(global),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')))"},{"url_name":"Hosts Overview by process name","url_value":"siem#/ml-hosts?query=(query:\'process.name%20:%20%22$process.name$%22\',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')),timeline:(linkTo:!(global),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')))"},{"url_name":"Hosts Overview by user name","url_value":"siem#/ml-hosts?query=(query:\'user.name%20:%20%22$user.name$%22\',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')),timeline:(linkTo:!(global),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')))"}]}}', + body: '{"job_type":"anomaly_detector","description":"SIEM Auditbeat: Detect unusually rare processes on Linux (beta)","groups":["siem"],"analysis_config":{"bucket_span":"15m","detectors":[{"detector_description":"rare process executions on Linux","function":"rare","by_field_name":"process.name","partition_field_name":"host.name"}],"influencers":["host.name","process.name","user.name"]},"analysis_limits":{"model_memory_limit":"256mb"},"data_description":{"time_field":"@timestamp","time_format":"epoch_ms"},"custom_settings":{"created_by":"ml-module-siem-auditbeat","custom_urls":[{"url_name":"Host Details by process name","url_value":"siem#/ml-hosts/$host.name$?query=(query:\'process.name%20:%20%22$process.name$%22\',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')),timeline:(linkTo:!(global),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')))"},{"url_name":"Host Details by user name","url_value":"siem#/ml-hosts/$host.name$?query=(query:\'user.name%20:%20%22$user.name$%22\',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')),timeline:(linkTo:!(global),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')))"},{"url_name":"Hosts Overview by process name","url_value":"siem#/ml-hosts?query=(query:\'process.name%20:%20%22$process.name$%22\',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')),timeline:(linkTo:!(global),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')))"},{"url_name":"Hosts Overview by user name","url_value":"siem#/ml-hosts?query=(query:\'user.name%20:%20%22$user.name$%22\',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')),timeline:(linkTo:!(global),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')))"}]}}', statusCode: 400, response: '{"error":{"root_cause":[{"type":"status_exception","reason":"This job would cause a mapping clash with existing field [multi_bucket_impact] - avoid the clash by assigning a dedicated results index"}],"type":"status_exception","reason":"This job would cause a mapping clash with existing field [multi_bucket_impact] - avoid the clash by assigning a dedicated results index","caused_by":{"type":"illegal_argument_exception","reason":"mapper [multi_bucket_impact] of different type, current_type [keyword], merged_type [double]"}},"status":400}', @@ -232,12 +228,10 @@ describe('throw_if_not_ok', () => { success: false, started: false, error: { - msg: - "[resource_not_found_exception] No known job with id 'siem-api-rare_process_linux_ecs'", + msg: "[resource_not_found_exception] No known job with id 'siem-api-rare_process_linux_ecs'", path: '/_ml/datafeeds/datafeed-siem-api-rare_process_linux_ecs', query: {}, - body: - '{"job_id":"siem-api-rare_process_linux_ecs","indexes":["auditbeat-*"],"query":{"bool":{"filter":[{"terms":{"event.action":["process_started","executed"]}}]}}}', + body: '{"job_id":"siem-api-rare_process_linux_ecs","indexes":["auditbeat-*"],"query":{"bool":{"filter":[{"terms":{"event.action":["process_started","executed"]}}]}}}', statusCode: 404, response: '{"error":{"root_cause":[{"type":"resource_not_found_exception","reason":"No known job with id \'siem-api-rare_process_linux_ecs\'"}],"type":"resource_not_found_exception","reason":"No known job with id \'siem-api-rare_process_linux_ecs\'"},"status":404}', @@ -248,12 +242,10 @@ describe('throw_if_not_ok', () => { success: false, started: false, error: { - msg: - "[resource_not_found_exception] No known job with id 'siem-api-suspicious_login_activity_ecs'", + msg: "[resource_not_found_exception] No known job with id 'siem-api-suspicious_login_activity_ecs'", path: '/_ml/datafeeds/datafeed-siem-api-suspicious_login_activity_ecs', query: {}, - body: - '{"job_id":"siem-api-suspicious_login_activity_ecs","indexes":["auditbeat-*"],"query":{"bool":{"filter":{"term":{"event.category":"authentication"}}}}}', + body: '{"job_id":"siem-api-suspicious_login_activity_ecs","indexes":["auditbeat-*"],"query":{"bool":{"filter":{"term":{"event.category":"authentication"}}}}}', statusCode: 404, response: '{"error":{"root_cause":[{"type":"resource_not_found_exception","reason":"No known job with id \'siem-api-suspicious_login_activity_ecs\'"}],"type":"resource_not_found_exception","reason":"No known job with id \'siem-api-suspicious_login_activity_ecs\'"},"status":404}', @@ -289,12 +281,10 @@ describe('throw_if_not_ok', () => { id: 'siem-api-suspicious_login_activity_ecs', success: false, error: { - msg: - '[status_exception] This job would cause a mapping clash with existing field [multi_bucket_impact] - avoid the clash by assigning a dedicated results index', + msg: '[status_exception] This job would cause a mapping clash with existing field [multi_bucket_impact] - avoid the clash by assigning a dedicated results index', path: '/_ml/anomaly_detectors/siem-api-suspicious_login_activity_ecs', query: {}, - body: - '{"job_type":"anomaly_detector","description":"SIEM Auditbeat: Detect unusually high number of authentication attempts (beta)","groups":["siem"],"analysis_config":{"bucket_span":"15m","detectors":[{"detector_description":"high number of authentication attempts","function":"high_non_zero_count","partition_field_name":"host.name"}],"influencers":["host.name","user.name","source.ip"]},"analysis_limits":{"model_memory_limit":"256mb"},"data_description":{"time_field":"@timestamp","time_format":"epoch_ms"},"custom_settings":{"created_by":"ml-module-siem-auditbeat","custom_urls":[{"url_name":"IP Address Details","url_value":"siem#/ml-network/ip/$source.ip$?query=!n,queryLocation:network.details,type:details)&timerange=(global:(linkTo:!(timeline),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')),timeline:(linkTo:!(global),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')))"}]}}', + body: '{"job_type":"anomaly_detector","description":"SIEM Auditbeat: Detect unusually high number of authentication attempts (beta)","groups":["siem"],"analysis_config":{"bucket_span":"15m","detectors":[{"detector_description":"high number of authentication attempts","function":"high_non_zero_count","partition_field_name":"host.name"}],"influencers":["host.name","user.name","source.ip"]},"analysis_limits":{"model_memory_limit":"256mb"},"data_description":{"time_field":"@timestamp","time_format":"epoch_ms"},"custom_settings":{"created_by":"ml-module-siem-auditbeat","custom_urls":[{"url_name":"IP Address Details","url_value":"siem#/ml-network/ip/$source.ip$?query=!n,queryLocation:network.details,type:details)&timerange=(global:(linkTo:!(timeline),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')),timeline:(linkTo:!(global),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')))"}]}}', statusCode: 400, response: '{"error":{"root_cause":[{"type":"status_exception","reason":"This job would cause a mapping clash with existing field [multi_bucket_impact] - avoid the clash by assigning a dedicated results index"}],"type":"status_exception","reason":"This job would cause a mapping clash with existing field [multi_bucket_impact] - avoid the clash by assigning a dedicated results index","caused_by":{"type":"illegal_argument_exception","reason":"mapper [multi_bucket_impact] of different type, current_type [keyword], merged_type [double]"}},"status":400}', @@ -304,12 +294,10 @@ describe('throw_if_not_ok', () => { id: 'siem-api-rare_process_linux_ecs', success: false, error: { - msg: - '[status_exception] This job would cause a mapping clash with existing field [multi_bucket_impact] - avoid the clash by assigning a dedicated results index', + msg: '[status_exception] This job would cause a mapping clash with existing field [multi_bucket_impact] - avoid the clash by assigning a dedicated results index', path: '/_ml/anomaly_detectors/siem-api-rare_process_linux_ecs', query: {}, - body: - '{"job_type":"anomaly_detector","description":"SIEM Auditbeat: Detect unusually rare processes on Linux (beta)","groups":["siem"],"analysis_config":{"bucket_span":"15m","detectors":[{"detector_description":"rare process executions on Linux","function":"rare","by_field_name":"process.name","partition_field_name":"host.name"}],"influencers":["host.name","process.name","user.name"]},"analysis_limits":{"model_memory_limit":"256mb"},"data_description":{"time_field":"@timestamp","time_format":"epoch_ms"},"custom_settings":{"created_by":"ml-module-siem-auditbeat","custom_urls":[{"url_name":"Host Details by process name","url_value":"siem#/ml-hosts/$host.name$?query=(query:\'process.name%20:%20%22$process.name$%22\',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')),timeline:(linkTo:!(global),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')))"},{"url_name":"Host Details by user name","url_value":"siem#/ml-hosts/$host.name$?query=(query:\'user.name%20:%20%22$user.name$%22\',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')),timeline:(linkTo:!(global),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')))"},{"url_name":"Hosts Overview by process name","url_value":"siem#/ml-hosts?query=(query:\'process.name%20:%20%22$process.name$%22\',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')),timeline:(linkTo:!(global),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')))"},{"url_name":"Hosts Overview by user name","url_value":"siem#/ml-hosts?query=(query:\'user.name%20:%20%22$user.name$%22\',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')),timeline:(linkTo:!(global),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')))"}]}}', + body: '{"job_type":"anomaly_detector","description":"SIEM Auditbeat: Detect unusually rare processes on Linux (beta)","groups":["siem"],"analysis_config":{"bucket_span":"15m","detectors":[{"detector_description":"rare process executions on Linux","function":"rare","by_field_name":"process.name","partition_field_name":"host.name"}],"influencers":["host.name","process.name","user.name"]},"analysis_limits":{"model_memory_limit":"256mb"},"data_description":{"time_field":"@timestamp","time_format":"epoch_ms"},"custom_settings":{"created_by":"ml-module-siem-auditbeat","custom_urls":[{"url_name":"Host Details by process name","url_value":"siem#/ml-hosts/$host.name$?query=(query:\'process.name%20:%20%22$process.name$%22\',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')),timeline:(linkTo:!(global),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')))"},{"url_name":"Host Details by user name","url_value":"siem#/ml-hosts/$host.name$?query=(query:\'user.name%20:%20%22$user.name$%22\',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')),timeline:(linkTo:!(global),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')))"},{"url_name":"Hosts Overview by process name","url_value":"siem#/ml-hosts?query=(query:\'process.name%20:%20%22$process.name$%22\',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')),timeline:(linkTo:!(global),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')))"},{"url_name":"Hosts Overview by user name","url_value":"siem#/ml-hosts?query=(query:\'user.name%20:%20%22$user.name$%22\',language:kuery)&timerange=(global:(linkTo:!(timeline),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')),timeline:(linkTo:!(global),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')))"}]}}', statusCode: 400, response: '{"error":{"root_cause":[{"type":"status_exception","reason":"This job would cause a mapping clash with existing field [multi_bucket_impact] - avoid the clash by assigning a dedicated results index"}],"type":"status_exception","reason":"This job would cause a mapping clash with existing field [multi_bucket_impact] - avoid the clash by assigning a dedicated results index","caused_by":{"type":"illegal_argument_exception","reason":"mapper [multi_bucket_impact] of different type, current_type [keyword], merged_type [double]"}},"status":400}', @@ -362,12 +350,10 @@ describe('throw_if_not_ok', () => { success: false, started: false, error: { - msg: - "[resource_not_found_exception] No known job with id 'siem-api-rare_process_linux_ecs'", + msg: "[resource_not_found_exception] No known job with id 'siem-api-rare_process_linux_ecs'", path: '/_ml/datafeeds/datafeed-siem-api-rare_process_linux_ecs', query: {}, - body: - '{"job_id":"siem-api-rare_process_linux_ecs","indexes":["auditbeat-*"],"query":{"bool":{"filter":[{"terms":{"event.action":["process_started","executed"]}}]}}}', + body: '{"job_id":"siem-api-rare_process_linux_ecs","indexes":["auditbeat-*"],"query":{"bool":{"filter":[{"terms":{"event.action":["process_started","executed"]}}]}}}', statusCode: 404, response: '{"error":{"root_cause":[{"type":"resource_not_found_exception","reason":"No known job with id \'siem-api-rare_process_linux_ecs\'"}],"type":"resource_not_found_exception","reason":"No known job with id \'siem-api-rare_process_linux_ecs\'"},"status":404}', @@ -378,12 +364,10 @@ describe('throw_if_not_ok', () => { success: false, started: false, error: { - msg: - "[resource_not_found_exception] No known job with id 'siem-api-suspicious_login_activity_ecs'", + msg: "[resource_not_found_exception] No known job with id 'siem-api-suspicious_login_activity_ecs'", path: '/_ml/datafeeds/datafeed-siem-api-suspicious_login_activity_ecs', query: {}, - body: - '{"job_id":"siem-api-suspicious_login_activity_ecs","indexes":["auditbeat-*"],"query":{"bool":{"filter":{"term":{"event.category":"authentication"}}}}}', + body: '{"job_id":"siem-api-suspicious_login_activity_ecs","indexes":["auditbeat-*"],"query":{"bool":{"filter":{"term":{"event.category":"authentication"}}}}}', statusCode: 404, response: '{"error":{"root_cause":[{"type":"resource_not_found_exception","reason":"No known job with id \'siem-api-suspicious_login_activity_ecs\'"}],"type":"resource_not_found_exception","reason":"No known job with id \'siem-api-suspicious_login_activity_ecs\'"},"status":404}', diff --git a/x-pack/plugins/security_solution/public/common/components/ml/conditional_links/remove_kql_variables.ts b/x-pack/plugins/security_solution/public/common/components/ml/conditional_links/remove_kql_variables.ts index e9c2817bc139e..c46d425a3eda9 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml/conditional_links/remove_kql_variables.ts +++ b/x-pack/plugins/security_solution/public/common/components/ml/conditional_links/remove_kql_variables.ts @@ -11,7 +11,8 @@ import { decodeRison, isRisonObject, isRegularString } from './rison_helpers'; export const operators = ['and', 'or', 'not']; export const removeKqlVariablesUsingRegex = (expression: string) => { - const myRegexp = /(\s+)*(and|or|not){0,1}(\s+)*([\w\.\-\[\]]+)\s*:\s*"(\$[\w\.\-\(\)\[\]]+\$)"(\s+)*(and|or|not){0,1}(\s+)*/g; + const myRegexp = + /(\s+)*(and|or|not){0,1}(\s+)*([\w\.\-\[\]]+)\s*:\s*"(\$[\w\.\-\(\)\[\]]+\$)"(\s+)*(and|or|not){0,1}(\s+)*/g; return expression.replace(myRegexp, replacer); }; diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/api.mock.ts b/x-pack/plugins/security_solution/public/common/components/ml_popover/api.mock.ts index 28d0ae179508d..d1ee8b4f81f2f 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/api.mock.ts +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/api.mock.ts @@ -366,12 +366,10 @@ export const mockSetupMlJobAllError: SetupMlResponse = { id: 'linux_anomalous_network_url_activity_ecs', success: false, error: { - msg: - "[resource_already_exists_exception] The job cannot be created with the Id 'linux_anomalous_network_url_activity_ecs'. The Id is already used.", + msg: "[resource_already_exists_exception] The job cannot be created with the Id 'linux_anomalous_network_url_activity_ecs'. The Id is already used.", path: '/_ml/anomaly_detectors/linux_anomalous_network_url_activity_ecs', query: {}, - body: - '{"job_type":"anomaly_detector","groups":["siem","auditbeat","process"],"description":"SIEM Auditbeat: Looks for an unusual web URL request from a Linux instance. Curl and wget web request activity is very common but unusual web requests from a Linux server can sometimes be malware delivery or execution (beta)","analysis_config":{"bucket_span":"15m","detectors":[{"detector_description":"rare by \\"process.title\\"","function":"rare","by_field_name":"process.title"}],"influencers":["host.name","destination.ip","destination.port"]},"analysis_limits":{"model_memory_limit":"32mb"},"data_description":{"time_field":"@timestamp"},"custom_settings":{"created_by":"ml-module-siem-auditbeat","custom_urls":[{"url_name":"Host Details","url_value":"siem#/ml-hosts/$host.name$?timerange=(global:(linkTo:!(timeline),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')),timeline:(linkTo:!(global),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')))"}]},"results_index_name":"linux_anomalous_network_url_activity_ecs"}', + body: '{"job_type":"anomaly_detector","groups":["siem","auditbeat","process"],"description":"SIEM Auditbeat: Looks for an unusual web URL request from a Linux instance. Curl and wget web request activity is very common but unusual web requests from a Linux server can sometimes be malware delivery or execution (beta)","analysis_config":{"bucket_span":"15m","detectors":[{"detector_description":"rare by \\"process.title\\"","function":"rare","by_field_name":"process.title"}],"influencers":["host.name","destination.ip","destination.port"]},"analysis_limits":{"model_memory_limit":"32mb"},"data_description":{"time_field":"@timestamp"},"custom_settings":{"created_by":"ml-module-siem-auditbeat","custom_urls":[{"url_name":"Host Details","url_value":"siem#/ml-hosts/$host.name$?timerange=(global:(linkTo:!(timeline),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')),timeline:(linkTo:!(global),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')))"}]},"results_index_name":"linux_anomalous_network_url_activity_ecs"}', statusCode: 400, response: '{"error":{"root_cause":[{"type":"resource_already_exists_exception","reason":"The job cannot be created with the Id \'linux_anomalous_network_url_activity_ecs\'. The Id is already used."}],"type":"resource_already_exists_exception","reason":"The job cannot be created with the Id \'linux_anomalous_network_url_activity_ecs\'. The Id is already used."},"status":400}', @@ -381,12 +379,10 @@ export const mockSetupMlJobAllError: SetupMlResponse = { id: 'linux_anomalous_network_port_activity_ecs', success: false, error: { - msg: - "[resource_already_exists_exception] The job cannot be created with the Id 'linux_anomalous_network_port_activity_ecs'. The Id is already used.", + msg: "[resource_already_exists_exception] The job cannot be created with the Id 'linux_anomalous_network_port_activity_ecs'. The Id is already used.", path: '/_ml/anomaly_detectors/linux_anomalous_network_port_activity_ecs', query: {}, - body: - '{"job_type":"anomaly_detector","description":"SIEM Auditbeat: Looks for unusual destination port activity that could indicate command-and-control, persistence mechanism, or data exfiltration activity (beta)","groups":["siem","auditbeat","process"],"analysis_config":{"bucket_span":"15m","detectors":[{"detector_description":"rare by \\"destination.port\\"","function":"rare","by_field_name":"destination.port"}],"influencers":["host.name","process.name","user.name","destination.ip"]},"analysis_limits":{"model_memory_limit":"32mb"},"data_description":{"time_field":"@timestamp"},"custom_settings":{"created_by":"ml-module-siem-auditbeat","custom_urls":[{"url_name":"Host Details by process name","url_value":"siem#/ml-hosts/$host.name$?kqlQuery=(filterQuery:(expression:\'process.name%20:%20%22$process.name$%22\',kind:kuery),queryLocation:hosts.details,type:details)&timerange=(global:(linkTo:!(timeline),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')),timeline:(linkTo:!(global),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')))"},{"url_name":"Host Details by user name","url_value":"siem#/ml-hosts/$host.name$?kqlQuery=(filterQuery:(expression:\'user.name%20:%20%22$user.name$%22\',kind:kuery),queryLocation:hosts.details,type:details)&timerange=(global:(linkTo:!(timeline),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')),timeline:(linkTo:!(global),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')))"},{"url_name":"Hosts Overview by process name","url_value":"siem#/ml-hosts?kqlQuery=(filterQuery:(expression:\'process.name%20:%20%22$process.name$%22\',kind:kuery),queryLocation:hosts.page,type:page)&timerange=(global:(linkTo:!(timeline),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')),timeline:(linkTo:!(global),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')))"},{"url_name":"Hosts Overview by user name","url_value":"siem#/ml-hosts?kqlQuery=(filterQuery:(expression:\'user.name%20:%20%22$user.name$%22\',kind:kuery),queryLocation:hosts.page,type:page)&timerange=(global:(linkTo:!(timeline),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')),timeline:(linkTo:!(global),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')))"}]},"results_index_name":"linux_anomalous_network_port_activity_ecs"}', + body: '{"job_type":"anomaly_detector","description":"SIEM Auditbeat: Looks for unusual destination port activity that could indicate command-and-control, persistence mechanism, or data exfiltration activity (beta)","groups":["siem","auditbeat","process"],"analysis_config":{"bucket_span":"15m","detectors":[{"detector_description":"rare by \\"destination.port\\"","function":"rare","by_field_name":"destination.port"}],"influencers":["host.name","process.name","user.name","destination.ip"]},"analysis_limits":{"model_memory_limit":"32mb"},"data_description":{"time_field":"@timestamp"},"custom_settings":{"created_by":"ml-module-siem-auditbeat","custom_urls":[{"url_name":"Host Details by process name","url_value":"siem#/ml-hosts/$host.name$?kqlQuery=(filterQuery:(expression:\'process.name%20:%20%22$process.name$%22\',kind:kuery),queryLocation:hosts.details,type:details)&timerange=(global:(linkTo:!(timeline),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')),timeline:(linkTo:!(global),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')))"},{"url_name":"Host Details by user name","url_value":"siem#/ml-hosts/$host.name$?kqlQuery=(filterQuery:(expression:\'user.name%20:%20%22$user.name$%22\',kind:kuery),queryLocation:hosts.details,type:details)&timerange=(global:(linkTo:!(timeline),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')),timeline:(linkTo:!(global),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')))"},{"url_name":"Hosts Overview by process name","url_value":"siem#/ml-hosts?kqlQuery=(filterQuery:(expression:\'process.name%20:%20%22$process.name$%22\',kind:kuery),queryLocation:hosts.page,type:page)&timerange=(global:(linkTo:!(timeline),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')),timeline:(linkTo:!(global),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')))"},{"url_name":"Hosts Overview by user name","url_value":"siem#/ml-hosts?kqlQuery=(filterQuery:(expression:\'user.name%20:%20%22$user.name$%22\',kind:kuery),queryLocation:hosts.page,type:page)&timerange=(global:(linkTo:!(timeline),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')),timeline:(linkTo:!(global),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')))"}]},"results_index_name":"linux_anomalous_network_port_activity_ecs"}', statusCode: 400, response: '{"error":{"root_cause":[{"type":"resource_already_exists_exception","reason":"The job cannot be created with the Id \'linux_anomalous_network_port_activity_ecs\'. The Id is already used."}],"type":"resource_already_exists_exception","reason":"The job cannot be created with the Id \'linux_anomalous_network_port_activity_ecs\'. The Id is already used."},"status":400}', @@ -399,12 +395,10 @@ export const mockSetupMlJobAllError: SetupMlResponse = { success: false, started: false, error: { - msg: - '[status_exception] A datafeed [datafeed-linux_anomalous_network_activity_ecs] already exists for job [linux_anomalous_network_activity_ecs]', + msg: '[status_exception] A datafeed [datafeed-linux_anomalous_network_activity_ecs] already exists for job [linux_anomalous_network_activity_ecs]', path: '/_ml/datafeeds/datafeed-linux_anomalous_network_activity_ecs', query: {}, - body: - '{"job_id":"linux_anomalous_network_activity_ecs","indices":["auditbeat-*"],"query":{"bool":{"filter":[{"term":{"event.action":"connected-to"}},{"term":{"agent.type":"auditbeat"}}],"must_not":[{"bool":{"should":[{"term":{"destination.ip":"127.0.0.1"}},{"term":{"destination.ip":"127.0.0.53"}},{"term":{"destination.ip":"::1"}}],"minimum_should_match":1}}]}}}', + body: '{"job_id":"linux_anomalous_network_activity_ecs","indices":["auditbeat-*"],"query":{"bool":{"filter":[{"term":{"event.action":"connected-to"}},{"term":{"agent.type":"auditbeat"}}],"must_not":[{"bool":{"should":[{"term":{"destination.ip":"127.0.0.1"}},{"term":{"destination.ip":"127.0.0.53"}},{"term":{"destination.ip":"::1"}}],"minimum_should_match":1}}]}}}', statusCode: 409, response: '{"error":{"root_cause":[{"type":"status_exception","reason":"A datafeed [datafeed-linux_anomalous_network_activity_ecs] already exists for job [linux_anomalous_network_activity_ecs]"}],"type":"status_exception","reason":"A datafeed [datafeed-linux_anomalous_network_activity_ecs] already exists for job [linux_anomalous_network_activity_ecs]"},"status":409}', @@ -415,12 +409,10 @@ export const mockSetupMlJobAllError: SetupMlResponse = { success: false, started: false, error: { - msg: - '[status_exception] A datafeed [datafeed-linux_anomalous_network_port_activity_ecs] already exists for job [linux_anomalous_network_port_activity_ecs]', + msg: '[status_exception] A datafeed [datafeed-linux_anomalous_network_port_activity_ecs] already exists for job [linux_anomalous_network_port_activity_ecs]', path: '/_ml/datafeeds/datafeed-linux_anomalous_network_port_activity_ecs', query: {}, - body: - '{"job_id":"linux_anomalous_network_port_activity_ecs","indices":["auditbeat-*"],"query":{"bool":{"filter":[{"term":{"event.action":"connected-to"}},{"term":{"agent.type":"auditbeat"}}],"must_not":[{"bool":{"should":[{"term":{"destination.ip":"::1"}},{"term":{"destination.ip":"127.0.0.1"}},{"term":{"destination.ip":"::"}},{"term":{"user.name_map.uid":"jenkins"}}],"minimum_should_match":1}}]}}}', + body: '{"job_id":"linux_anomalous_network_port_activity_ecs","indices":["auditbeat-*"],"query":{"bool":{"filter":[{"term":{"event.action":"connected-to"}},{"term":{"agent.type":"auditbeat"}}],"must_not":[{"bool":{"should":[{"term":{"destination.ip":"::1"}},{"term":{"destination.ip":"127.0.0.1"}},{"term":{"destination.ip":"::"}},{"term":{"user.name_map.uid":"jenkins"}}],"minimum_should_match":1}}]}}}', statusCode: 409, response: '{"error":{"root_cause":[{"type":"status_exception","reason":"A datafeed [datafeed-linux_anomalous_network_port_activity_ecs] already exists for job [linux_anomalous_network_port_activity_ecs]"}],"type":"status_exception","reason":"A datafeed [datafeed-linux_anomalous_network_port_activity_ecs] already exists for job [linux_anomalous_network_port_activity_ecs]"},"status":409}', @@ -436,12 +428,10 @@ export const mockSetupMlJobSingleErrorSingleSuccess: SetupMlResponse = { id: 'linux_anomalous_network_activity_ecs', success: false, error: { - msg: - "[resource_already_exists_exception] The job cannot be created with the Id 'linux_anomalous_network_activity_ecs'. The Id is already used.", + msg: "[resource_already_exists_exception] The job cannot be created with the Id 'linux_anomalous_network_activity_ecs'. The Id is already used.", path: '/_ml/anomaly_detectors/linux_anomalous_network_activity_ecs', query: {}, - body: - '{"job_type":"anomaly_detector","description":"SIEM Auditbeat: Looks for unusual processes using the network which could indicate command-and-control, lateral movement, persistence, or data exfiltration activity (beta)","groups":["siem","auditbeat","network"],"analysis_config":{"bucket_span":"15m","detectors":[{"detector_description":"rare by \\"process.name\\"","function":"rare","by_field_name":"process.name"}],"influencers":["host.name","process.name","user.name","destination.ip"]},"analysis_limits":{"model_memory_limit":"64mb"},"data_description":{"time_field":"@timestamp"},"custom_settings":{"created_by":"ml-module-siem-auditbeat","custom_urls":[{"url_name":"Host Details by process name","url_value":"siem#/ml-hosts/$host.name$?kqlQuery=(filterQuery:(expression:\'process.name%20:%20%22$process.name$%22\',kind:kuery),queryLocation:hosts.details,type:details)&timerange=(global:(linkTo:!(timeline),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')),timeline:(linkTo:!(global),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')))"},{"url_name":"Host Details by user name","url_value":"siem#/ml-hosts/$host.name$?kqlQuery=(filterQuery:(expression:\'user.name%20:%20%22$user.name$%22\',kind:kuery),queryLocation:hosts.details,type:details)&timerange=(global:(linkTo:!(timeline),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')),timeline:(linkTo:!(global),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')))"},{"url_name":"Hosts Overview by process name","url_value":"siem#/ml-hosts?kqlQuery=(filterQuery:(expression:\'process.name%20:%20%22$process.name$%22\',kind:kuery),queryLocation:hosts.page,type:page)&timerange=(global:(linkTo:!(timeline),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')),timeline:(linkTo:!(global),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')))"},{"url_name":"Hosts Overview by user name","url_value":"siem#/ml-hosts?kqlQuery=(filterQuery:(expression:\'user.name%20:%20%22$user.name$%22\',kind:kuery),queryLocation:hosts.page,type:page)&timerange=(global:(linkTo:!(timeline),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')),timeline:(linkTo:!(global),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')))"}]},"results_index_name":"linux_anomalous_network_activity_ecs"}', + body: '{"job_type":"anomaly_detector","description":"SIEM Auditbeat: Looks for unusual processes using the network which could indicate command-and-control, lateral movement, persistence, or data exfiltration activity (beta)","groups":["siem","auditbeat","network"],"analysis_config":{"bucket_span":"15m","detectors":[{"detector_description":"rare by \\"process.name\\"","function":"rare","by_field_name":"process.name"}],"influencers":["host.name","process.name","user.name","destination.ip"]},"analysis_limits":{"model_memory_limit":"64mb"},"data_description":{"time_field":"@timestamp"},"custom_settings":{"created_by":"ml-module-siem-auditbeat","custom_urls":[{"url_name":"Host Details by process name","url_value":"siem#/ml-hosts/$host.name$?kqlQuery=(filterQuery:(expression:\'process.name%20:%20%22$process.name$%22\',kind:kuery),queryLocation:hosts.details,type:details)&timerange=(global:(linkTo:!(timeline),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')),timeline:(linkTo:!(global),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')))"},{"url_name":"Host Details by user name","url_value":"siem#/ml-hosts/$host.name$?kqlQuery=(filterQuery:(expression:\'user.name%20:%20%22$user.name$%22\',kind:kuery),queryLocation:hosts.details,type:details)&timerange=(global:(linkTo:!(timeline),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')),timeline:(linkTo:!(global),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')))"},{"url_name":"Hosts Overview by process name","url_value":"siem#/ml-hosts?kqlQuery=(filterQuery:(expression:\'process.name%20:%20%22$process.name$%22\',kind:kuery),queryLocation:hosts.page,type:page)&timerange=(global:(linkTo:!(timeline),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')),timeline:(linkTo:!(global),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')))"},{"url_name":"Hosts Overview by user name","url_value":"siem#/ml-hosts?kqlQuery=(filterQuery:(expression:\'user.name%20:%20%22$user.name$%22\',kind:kuery),queryLocation:hosts.page,type:page)&timerange=(global:(linkTo:!(timeline),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')),timeline:(linkTo:!(global),timerange:(from:\'$earliest$\',kind:absolute,to:\'$latest$\')))"}]},"results_index_name":"linux_anomalous_network_activity_ecs"}', statusCode: 400, response: '{"error":{"root_cause":[{"type":"resource_already_exists_exception","reason":"The job cannot be created with the Id \'linux_anomalous_network_activity_ecs\'. The Id is already used."}],"type":"resource_already_exists_exception","reason":"The job cannot be created with the Id \'linux_anomalous_network_activity_ecs\'. The Id is already used."},"status":400}', @@ -455,12 +445,10 @@ export const mockSetupMlJobSingleErrorSingleSuccess: SetupMlResponse = { success: false, started: false, error: { - msg: - '[status_exception] A datafeed [datafeed-linux_anomalous_network_activity_ecs] already exists for job [linux_anomalous_network_activity_ecs]', + msg: '[status_exception] A datafeed [datafeed-linux_anomalous_network_activity_ecs] already exists for job [linux_anomalous_network_activity_ecs]', path: '/_ml/datafeeds/datafeed-linux_anomalous_network_activity_ecs', query: {}, - body: - '{"job_id":"linux_anomalous_network_activity_ecs","indices":["auditbeat-*"],"query":{"bool":{"filter":[{"term":{"event.action":"connected-to"}},{"term":{"agent.type":"auditbeat"}}],"must_not":[{"bool":{"should":[{"term":{"destination.ip":"127.0.0.1"}},{"term":{"destination.ip":"127.0.0.53"}},{"term":{"destination.ip":"::1"}}],"minimum_should_match":1}}]}}}', + body: '{"job_id":"linux_anomalous_network_activity_ecs","indices":["auditbeat-*"],"query":{"bool":{"filter":[{"term":{"event.action":"connected-to"}},{"term":{"agent.type":"auditbeat"}}],"must_not":[{"bool":{"should":[{"term":{"destination.ip":"127.0.0.1"}},{"term":{"destination.ip":"127.0.0.53"}},{"term":{"destination.ip":"::1"}}],"minimum_should_match":1}}]}}}', statusCode: 409, response: '{"error":{"root_cause":[{"type":"status_exception","reason":"A datafeed [datafeed-linux_anomalous_network_activity_ecs] already exists for job [linux_anomalous_network_activity_ecs]"}],"type":"status_exception","reason":"A datafeed [datafeed-linux_anomalous_network_activity_ecs] already exists for job [linux_anomalous_network_activity_ecs]"},"status":409}', diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/ml_popover.tsx b/x-pack/plugins/security_solution/public/common/components/ml_popover/ml_popover.tsx index d7d62d82a95b0..6abca9cfe8853 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/ml_popover.tsx +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/ml_popover.tsx @@ -96,9 +96,12 @@ export const MlPopover = React.memo(() => { const [isPopoverOpen, setIsPopoverOpen] = useState(false); const [filterProperties, setFilterProperties] = useState(defaultFilterProps); - const { isMlAdmin, isLicensed, loading: isLoadingSecurityJobs, jobs } = useSecurityJobs( - refreshToggle - ); + const { + isMlAdmin, + isLicensed, + loading: isLoadingSecurityJobs, + jobs, + } = useSecurityJobs(refreshToggle); const [, dispatchToaster] = useStateToaster(); const docLinks = useKibana().services.docLinks; const handleJobStateChange = useCallback( diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts index 22916b90c084d..08f2ef2267f97 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts @@ -7,13 +7,15 @@ import '../../../mock/match_media'; import { encodeIpv6 } from '../../../lib/helpers'; -import { getBreadcrumbsForRoute, setBreadcrumbs } from '.'; +import { getBreadcrumbsForRoute, useSetBreadcrumbs } from '.'; import { HostsTableType } from '../../../../hosts/store/model'; import { RouteSpyState, SiemRouteType } from '../../../utils/route/types'; import { TabNavigationProps } from '../tab_navigation/types'; import { NetworkRouteType } from '../../../../network/pages/navigation/types'; import { TimelineTabs } from '../../../../../common/types/timeline'; import { AdministrationSubTab } from '../../../../management/types'; +import { renderHook } from '@testing-library/react-hooks'; +import { TestProviders } from '../../../mock'; const setBreadcrumbsMock = jest.fn(); const chromeMock = { @@ -160,8 +162,7 @@ describe('Navigation Breadcrumbs', () => { text: 'Security', }, { - href: - "securitySolution/hosts?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + href: "securitySolution/hosts?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", text: 'Hosts', }, { @@ -180,8 +181,7 @@ describe('Navigation Breadcrumbs', () => { { text: 'Security', href: 'securitySolution/overview' }, { text: 'Network', - href: - "securitySolution/network?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + href: "securitySolution/network?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, { text: 'Flows', @@ -199,8 +199,7 @@ describe('Navigation Breadcrumbs', () => { { text: 'Security', href: 'securitySolution/overview' }, { text: 'Timelines', - href: - "securitySolution/timelines?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + href: "securitySolution/timelines?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, ]); }); @@ -214,13 +213,11 @@ describe('Navigation Breadcrumbs', () => { { text: 'Security', href: 'securitySolution/overview' }, { text: 'Hosts', - href: - "securitySolution/hosts?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + href: "securitySolution/hosts?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, { text: 'siem-kibana', - href: - "securitySolution/hosts/siem-kibana?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + href: "securitySolution/hosts/siem-kibana?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, { text: 'Authentications', href: '' }, ]); @@ -235,8 +232,7 @@ describe('Navigation Breadcrumbs', () => { { text: 'Security', href: 'securitySolution/overview' }, { text: 'Network', - href: - "securitySolution/network?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + href: "securitySolution/network?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, { text: ipv4, @@ -255,8 +251,7 @@ describe('Navigation Breadcrumbs', () => { { text: 'Security', href: 'securitySolution/overview' }, { text: 'Network', - href: - "securitySolution/network?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + href: "securitySolution/network?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, { text: ipv6, @@ -303,8 +298,7 @@ describe('Navigation Breadcrumbs', () => { { text: 'Security', href: 'securitySolution/overview' }, { text: 'Rules', - href: - "securitySolution/rules?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + href: "securitySolution/rules?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, ]); }); @@ -318,8 +312,7 @@ describe('Navigation Breadcrumbs', () => { { text: 'Security', href: 'securitySolution/overview' }, { text: 'Rules', - href: - "securitySolution/rules?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + href: "securitySolution/rules?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, { text: 'Create', @@ -345,8 +338,7 @@ describe('Navigation Breadcrumbs', () => { { text: 'Security', href: 'securitySolution/overview' }, { text: 'Rules', - href: - "securitySolution/rules?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + href: "securitySolution/rules?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, { text: mockRuleName, @@ -372,8 +364,7 @@ describe('Navigation Breadcrumbs', () => { { text: 'Security', href: 'securitySolution/overview' }, { text: 'Rules', - href: - "securitySolution/rules?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + href: "securitySolution/rules?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, { text: 'ALERT_RULE_NAME', @@ -395,8 +386,7 @@ describe('Navigation Breadcrumbs', () => { { text: 'Security', href: 'securitySolution/overview' }, { text: 'Cases', - href: - "securitySolution/case?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + href: "securitySolution/case?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, ]); }); @@ -416,8 +406,7 @@ describe('Navigation Breadcrumbs', () => { { text: 'Security', href: 'securitySolution/overview' }, { text: 'Cases', - href: - "securitySolution/case?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + href: "securitySolution/case?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, { text: sampleCase.name, @@ -438,35 +427,13 @@ describe('Navigation Breadcrumbs', () => { }, ]); }); - - test('should set "timeline.isOpen" to false when timeline is open', () => { - const breadcrumbs = getBreadcrumbsForRoute( - { - ...getMockObject('timelines', '/', undefined), - timeline: { - activeTab: TimelineTabs.query, - id: 'TIMELINE_ID', - isOpen: true, - graphEventId: 'GRAPH_EVENT_ID', - }, - }, - getUrlForAppMock - ); - expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolution/overview' }, - { - text: 'Timelines', - href: - "securitySolution/timelines?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))&timeline=(activeTab:query,graphEventId:GRAPH_EVENT_ID,id:TIMELINE_ID,isOpen:!f)", - }, - ]); - }); }); describe('setBreadcrumbs()', () => { test('should call chrome breadcrumb service with correct breadcrumbs', () => { const navigateToUrlMock = jest.fn(); - setBreadcrumbs( + const { result } = renderHook(() => useSetBreadcrumbs(), { wrapper: TestProviders }); + result.current( getMockObject('hosts', '/', hostName), chromeMock, getUrlForAppMock, @@ -480,14 +447,12 @@ describe('Navigation Breadcrumbs', () => { }), expect.objectContaining({ text: 'Hosts', - href: - "securitySolution/hosts?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + href: "securitySolution/hosts?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", onClick: expect.any(Function), }), expect.objectContaining({ text: 'siem-kibana', - href: - "securitySolution/hosts/siem-kibana?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + href: "securitySolution/hosts/siem-kibana?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", onClick: expect.any(Function), }), { diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.ts b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.ts index aae97d90cb4b8..f4e3814738f92 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.ts +++ b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.ts @@ -7,6 +7,7 @@ import { getOr, omit } from 'lodash/fp'; +import { useDispatch } from 'react-redux'; import { ChromeBreadcrumb } from '../../../../../../../../src/core/public'; import { APP_NAME, APP_ID } from '../../../../../common/constants'; import { StartServices } from '../../../../types'; @@ -27,33 +28,40 @@ import { UebaRouteSpyState, } from '../../../utils/route/types'; import { getAppOverviewUrl } from '../../link_to'; - +import { timelineActions } from '../../../../../public/timelines/store/timeline'; +import { TimelineId } from '../../../../../common/types/timeline'; import { TabNavigationProps } from '../tab_navigation/types'; import { getSearch } from '../helpers'; import { GetUrlForApp, NavigateToUrl, SearchNavTab } from '../types'; -export const setBreadcrumbs = ( - spyState: RouteSpyState & TabNavigationProps, - chrome: StartServices['chrome'], - getUrlForApp: GetUrlForApp, - navigateToUrl: NavigateToUrl -) => { - const breadcrumbs = getBreadcrumbsForRoute(spyState, getUrlForApp); - if (breadcrumbs) { - chrome.setBreadcrumbs( - breadcrumbs.map((breadcrumb) => ({ - ...breadcrumb, - ...(breadcrumb.href && !breadcrumb.onClick - ? { - onClick: (ev) => { - ev.preventDefault(); - navigateToUrl(breadcrumb.href!); - }, - } - : {}), - })) - ); - } +export const useSetBreadcrumbs = () => { + const dispatch = useDispatch(); + return ( + spyState: RouteSpyState & TabNavigationProps, + chrome: StartServices['chrome'], + getUrlForApp: GetUrlForApp, + navigateToUrl: NavigateToUrl + ) => { + const breadcrumbs = getBreadcrumbsForRoute(spyState, getUrlForApp); + if (breadcrumbs) { + chrome.setBreadcrumbs( + breadcrumbs.map((breadcrumb) => ({ + ...breadcrumb, + ...(breadcrumb.href && !breadcrumb.onClick + ? { + onClick: (ev) => { + ev.preventDefault(); + + dispatch(timelineActions.showTimeline({ id: TimelineId.active, show: false })); + + navigateToUrl(breadcrumb.href!); + }, + } + : {}), + })) + ); + } + }; }; const isNetworkRoutes = (spyState: RouteSpyState): spyState is NetworkRouteSpyState => @@ -79,14 +87,10 @@ const isRulesRoutes = (spyState: RouteSpyState): spyState is AdministrationRoute // eslint-disable-next-line complexity export const getBreadcrumbsForRoute = ( - objectParam: RouteSpyState & TabNavigationProps, + object: RouteSpyState & TabNavigationProps, getUrlForApp: GetUrlForApp ): ChromeBreadcrumb[] | null => { - const spyState: RouteSpyState = omit('navTabs', objectParam); - - // Sets `timeline.isOpen` to false in the state to avoid reopening the timeline on breadcrumb click. https://github.com/elastic/kibana/issues/100322 - const object = { ...objectParam, timeline: { ...objectParam.timeline, isOpen: false } }; - + const spyState: RouteSpyState = omit('navTabs', object); const overviewPath = getUrlForApp(APP_ID, { deepLinkId: SecurityPageName.overview }); const siemRootBreadcrumb: ChromeBreadcrumb = { text: APP_NAME, diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/index.test.tsx index 5bb0805acc378..393a3d3e0aaba 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/index.test.tsx @@ -10,7 +10,6 @@ import React from 'react'; import { CONSTANTS } from '../url_state/constants'; import { TabNavigationComponent } from './'; -import { setBreadcrumbs } from './breadcrumbs'; import { navTabs } from '../../../app/home/home_navigations'; import { HostsTableType } from '../../../hosts/store/model'; import { RouteSpyState } from '../../utils/route/types'; @@ -28,8 +27,10 @@ jest.mock('react-router-dom', () => { }; }); +const mockSetBreadcrumbs = jest.fn(); + jest.mock('./breadcrumbs', () => ({ - setBreadcrumbs: jest.fn(), + useSetBreadcrumbs: () => mockSetBreadcrumbs, })); const mockGetUrlForApp = jest.fn(); const mockNavigateToUrl = jest.fn(); @@ -102,7 +103,7 @@ describe('SIEM Navigation', () => { }; const wrapper = mount(); test('it calls setBreadcrumbs with correct path on mount', () => { - expect(setBreadcrumbs).toHaveBeenNthCalledWith( + expect(mockSetBreadcrumbs).toHaveBeenNthCalledWith( 1, { detailName: undefined, @@ -158,7 +159,7 @@ describe('SIEM Navigation', () => { tabName: 'authentications', }); wrapper.update(); - expect(setBreadcrumbs).toHaveBeenNthCalledWith( + expect(mockSetBreadcrumbs).toHaveBeenNthCalledWith( 2, { detailName: undefined, diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/index.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/index.tsx index 71a5309937fff..f8b9251f4ff91 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/index.tsx @@ -14,7 +14,7 @@ import { useKibana } from '../../lib/kibana'; import { RouteSpyState } from '../../utils/route/types'; import { useRouteSpy } from '../../utils/route/use_route_spy'; import { makeMapStateToProps } from '../url_state/helpers'; -import { setBreadcrumbs } from './breadcrumbs'; +import { useSetBreadcrumbs } from './breadcrumbs'; import { TabNavigation } from './tab_navigation'; import { TabNavigationComponentProps, SecuritySolutionTabNavigationProps } from './types'; @@ -42,6 +42,8 @@ export const TabNavigationComponent: React.FC< application: { getUrlForApp, navigateToUrl }, } = useKibana().services; + const setBreadcrumbs = useSetBreadcrumbs(); + useEffect(() => { if (pathName || pageName) { setBreadcrumbs( @@ -79,6 +81,7 @@ export const TabNavigationComponent: React.FC< tabName, getUrlForApp, navigateToUrl, + setBreadcrumbs, ]); return ( @@ -114,16 +117,17 @@ export const SecuritySolutionTabNavigationRedux = compose< ) ); -export const SecuritySolutionTabNavigation: React.FC = React.memo( - (props) => { - const [routeProps] = useRouteSpy(); - const stateNavReduxProps: RouteSpyState & SecuritySolutionTabNavigationProps = { - ...routeProps, - ...props, - }; +export const SecuritySolutionTabNavigation: React.FC = + React.memo( + (props) => { + const [routeProps] = useRouteSpy(); + const stateNavReduxProps: RouteSpyState & SecuritySolutionTabNavigationProps = { + ...routeProps, + ...props, + }; - return ; - }, - (prevProps, nextProps) => deepEqual(prevProps.navTabs, nextProps.navTabs) -); + return ; + }, + (prevProps, nextProps) => deepEqual(prevProps.navTabs, nextProps.navTabs) + ); SecuritySolutionTabNavigation.displayName = 'SecuritySolutionTabNavigation'; 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 b488000ac8736..820d90087ce48 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 @@ -17,6 +17,7 @@ import { useDeepEqualSelector } from '../../../hooks/use_selector'; import { UrlInputsModel } from '../../../store/inputs/model'; import { useRouteSpy } from '../../../utils/route/use_route_spy'; import { useIsExperimentalFeatureEnabled } from '../../../hooks/use_experimental_features'; +import { TestProviders } from '../../../mock'; jest.mock('../../../lib/kibana/kibana_react'); jest.mock('../../../lib/kibana'); @@ -96,8 +97,9 @@ describe('useSecuritySolutionNavigation', () => { }); it('should create navigation config', async () => { - const { result } = renderHook<{}, KibanaPageTemplateProps['solutionNav']>(() => - useSecuritySolutionNavigation() + const { result } = renderHook<{}, KibanaPageTemplateProps['solutionNav']>( + () => useSecuritySolutionNavigation(), + { wrapper: TestProviders } ); expect(result.current).toMatchInlineSnapshot(` @@ -243,8 +245,9 @@ describe('useSecuritySolutionNavigation', () => { // TODO: Steph/ueba remove when no longer experimental it('should include ueba when feature flag is on', async () => { (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(true); - const { result } = renderHook<{}, KibanaPageTemplateProps['solutionNav']>(() => - useSecuritySolutionNavigation() + const { result } = renderHook<{}, KibanaPageTemplateProps['solutionNav']>( + () => useSecuritySolutionNavigation(), + { wrapper: TestProviders } ); // @ts-ignore possibly undefined, but if undefined we want this test to fail @@ -259,8 +262,9 @@ describe('useSecuritySolutionNavigation', () => { read: true, }); - const { result } = renderHook<{}, KibanaPageTemplateProps['solutionNav']>(() => - useSecuritySolutionNavigation() + const { result } = renderHook<{}, KibanaPageTemplateProps['solutionNav']>( + () => useSecuritySolutionNavigation(), + { wrapper: TestProviders } ); const caseNavItem = (result.current?.items || [])[3].items?.find( @@ -286,8 +290,9 @@ describe('useSecuritySolutionNavigation', () => { read: false, }); - const { result } = renderHook<{}, KibanaPageTemplateProps['solutionNav']>(() => - useSecuritySolutionNavigation() + const { result } = renderHook<{}, KibanaPageTemplateProps['solutionNav']>( + () => useSecuritySolutionNavigation(), + { wrapper: TestProviders } ); const caseNavItem = (result.current?.items || [])[3].items?.find( diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.tsx index 5165a903bbde1..1e824015db872 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.tsx @@ -8,7 +8,7 @@ import { useEffect } from 'react'; import { usePrimaryNavigation } from './use_primary_navigation'; import { useKibana } from '../../../lib/kibana'; -import { setBreadcrumbs } from '../breadcrumbs'; +import { useSetBreadcrumbs } from '../breadcrumbs'; import { makeMapStateToProps } from '../../url_state/helpers'; import { useRouteSpy } from '../../../utils/route/use_route_spy'; import { navTabs } from '../../../../app/home/home_navigations'; @@ -32,11 +32,14 @@ export const useSecuritySolutionNavigation = () => { const { detailName, flowTarget, pageName, pathName, search, state, tabName } = routeProps; const uebaEnabled = useIsExperimentalFeatureEnabled('uebaEnabled'); - let enabledNavTabs: GenericNavRecord = (navTabs as unknown) as GenericNavRecord; + let enabledNavTabs: GenericNavRecord = navTabs as unknown as GenericNavRecord; if (!uebaEnabled) { const { ueba, ...rest } = enabledNavTabs; enabledNavTabs = rest; } + + const setBreadcrumbs = useSetBreadcrumbs(); + useEffect(() => { if (pathName || pageName) { setBreadcrumbs( @@ -74,6 +77,7 @@ export const useSecuritySolutionNavigation = () => { getUrlForApp, navigateToUrl, enabledNavTabs, + setBreadcrumbs, ]); return usePrimaryNavigation({ diff --git a/x-pack/plugins/security_solution/public/common/components/news_feed/helpers.test.ts b/x-pack/plugins/security_solution/public/common/components/news_feed/helpers.test.ts index bea5576263479..5ccf9ba505b55 100644 --- a/x-pack/plugins/security_solution/public/common/components/news_feed/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/news_feed/helpers.test.ts @@ -279,8 +279,7 @@ describe('helpers', () => { { title: { en: 'Got SIEM Questions?', ja: translatedTitle }, description: { - en: - "There's an awesome community of Elastic SIEM users out there. Join the discussion about configuring, learning, and using the Elastic SIEM app, and detecting threats!", + en: "There's an awesome community of Elastic SIEM users out there. Join the discussion about configuring, learning, and using the Elastic SIEM app, and detecting threats!", ja: translatedDescription, }, link_text: null, @@ -291,8 +290,7 @@ describe('helpers', () => { languages: null, badge: { en: '7.6' }, image_url: { - en: - 'https://aws1.discourse-cdn.com/elastic/original/3X/f/8/f8c3d0b9971cfcd0be349d973aa5799f71d280cc.png?blade=securitysolutionfeed', + en: 'https://aws1.discourse-cdn.com/elastic/original/3X/f/8/f8c3d0b9971cfcd0be349d973aa5799f71d280cc.png?blade=securitysolutionfeed', ja: translatedImageUrl, }, publish_on: new Date('2020-01-01T00:00:00'), diff --git a/x-pack/plugins/security_solution/public/common/components/page/index.tsx b/x-pack/plugins/security_solution/public/common/components/page/index.tsx index 9cfd0771425ee..fca49bd4539ec 100644 --- a/x-pack/plugins/security_solution/public/common/components/page/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/page/index.tsx @@ -104,9 +104,9 @@ export const DescriptionListStyled = styled(EuiDescriptionList)` DescriptionListStyled.displayName = 'DescriptionListStyled'; -export const CountBadge = (styled(EuiBadge)` +export const CountBadge = styled(EuiBadge)` margin-left: 5px; -` as unknown) as typeof EuiBadge; +` as unknown as typeof EuiBadge; CountBadge.displayName = 'CountBadge'; @@ -116,9 +116,9 @@ export const Spacer = styled.span` Spacer.displayName = 'Spacer'; -export const Badge = (styled(EuiBadge)` +export const Badge = styled(EuiBadge)` vertical-align: top; -` as unknown) as typeof EuiBadge; +` as unknown as typeof EuiBadge; Badge.displayName = 'Badge'; diff --git a/x-pack/plugins/security_solution/public/common/components/paginated_table/index.mock.tsx b/x-pack/plugins/security_solution/public/common/components/paginated_table/index.mock.tsx index 7012918bb041a..070c8a8d53b43 100644 --- a/x-pack/plugins/security_solution/public/common/components/paginated_table/index.mock.tsx +++ b/x-pack/plugins/security_solution/public/common/components/paginated_table/index.mock.tsx @@ -83,17 +83,13 @@ export const getHostsColumns = (): [ }, ]; -export const sortedHosts: [ - Columns, - Columns, - Columns, - Columns -] = getHostsColumns().map((h) => ({ ...h, sortable: true })) as [ - Columns, - Columns, - Columns, - Columns -]; +export const sortedHosts: [Columns, Columns, Columns, Columns] = + getHostsColumns().map((h) => ({ ...h, sortable: true })) as [ + Columns, + Columns, + Columns, + Columns + ]; export const rowItems: ItemsPerRow[] = [ { diff --git a/x-pack/plugins/security_solution/public/common/components/search_bar/index.tsx b/x-pack/plugins/security_solution/public/common/components/search_bar/index.tsx index a1be95ac33d34..baaffdf239dc8 100644 --- a/x-pack/plugins/security_solution/public/common/components/search_bar/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/search_bar/index.tsx @@ -235,9 +235,10 @@ export const SearchBarComponent = memo( [storage] ); - const getAppStateFromStorage = useCallback(() => storage.get(APP_STATE_STORAGE_KEY) ?? [], [ - storage, - ]); + const getAppStateFromStorage = useCallback( + () => storage.get(APP_STATE_STORAGE_KEY) ?? [], + [storage] + ); useEffect(() => { let isSubscribed = true; @@ -350,82 +351,84 @@ interface UpdateReduxSearchBar extends OnTimeChangeProps { updateTime: boolean; } -export const dispatchUpdateSearch = (dispatch: Dispatch) => ({ - end, - filters, - id, - isQuickSelection, - query, - resetSavedQuery, - savedQuery, - start, - timelineId, - filterManager, - updateTime = false, -}: UpdateReduxSearchBar): void => { - if (updateTime) { - const fromDate = formatDate(start); - let toDate = formatDate(end, { roundUp: true }); - if (isQuickSelection) { - if (end === start) { +export const dispatchUpdateSearch = + (dispatch: Dispatch) => + ({ + end, + filters, + id, + isQuickSelection, + query, + resetSavedQuery, + savedQuery, + start, + timelineId, + filterManager, + updateTime = false, + }: UpdateReduxSearchBar): void => { + if (updateTime) { + const fromDate = formatDate(start); + let toDate = formatDate(end, { roundUp: true }); + if (isQuickSelection) { + if (end === start) { + dispatch( + inputsActions.setAbsoluteRangeDatePicker({ + id, + fromStr: start, + toStr: end, + from: fromDate, + to: toDate, + }) + ); + } else { + dispatch( + inputsActions.setRelativeRangeDatePicker({ + id, + fromStr: start, + toStr: end, + from: fromDate, + to: toDate, + }) + ); + } + } else { + toDate = formatDate(end); dispatch( inputsActions.setAbsoluteRangeDatePicker({ id, - fromStr: start, - toStr: end, - from: fromDate, - to: toDate, + from: formatDate(start), + to: formatDate(end), }) ); - } else { + } + if (timelineId != null) { dispatch( - inputsActions.setRelativeRangeDatePicker({ - id, - fromStr: start, - toStr: end, - from: fromDate, - to: toDate, + timelineActions.updateRange({ + id: timelineId, + start: fromDate, + end: toDate, }) ); } - } else { - toDate = formatDate(end); + } + if (query != null) { dispatch( - inputsActions.setAbsoluteRangeDatePicker({ + inputsActions.setFilterQuery({ id, - from: formatDate(start), - to: formatDate(end), + ...query, }) ); } - if (timelineId != null) { - dispatch( - timelineActions.updateRange({ - id: timelineId, - start: fromDate, - end: toDate, - }) - ); + if (filters != null) { + filterManager.setFilters(filters); + } + if (savedQuery != null || resetSavedQuery) { + dispatch(inputsActions.setSavedQuery({ id, savedQuery })); } - } - if (query != null) { - dispatch( - inputsActions.setFilterQuery({ - id, - ...query, - }) - ); - } - if (filters != null) { - filterManager.setFilters(filters); - } - if (savedQuery != null || resetSavedQuery) { - dispatch(inputsActions.setSavedQuery({ id, savedQuery })); - } - dispatch(hostsActions.setHostTablesActivePageToZero()); - dispatch(networkActions.setNetworkTablesActivePageToZero()); -}; + dispatch(hostsActions.setHostTablesActivePageToZero()); + dispatch(networkActions.setNetworkTablesActivePageToZero()); + }; const mapDispatchToProps = (dispatch: Dispatch) => ({ updateSearch: dispatchUpdateSearch(dispatch), diff --git a/x-pack/plugins/security_solution/public/common/components/sourcerer/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/sourcerer/index.test.tsx index 87a7ce805940f..352fc95447822 100644 --- a/x-pack/plugins/security_solution/public/common/components/sourcerer/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/sourcerer/index.test.tsx @@ -110,9 +110,11 @@ describe('Sourcerer component', () => { wrapper.find(`[data-test-subj="sourcerer-popover"]`).first().prop('isOpen') ).toBeTruthy(); await waitFor(() => { - ((wrapper.find(EuiComboBox).props() as unknown) as { - onChange: (a: EuiComboBoxOptionOption[]) => void; - }).onChange([mockOptions[0], mockOptions[1]]); + ( + wrapper.find(EuiComboBox).props() as unknown as { + onChange: (a: EuiComboBoxOptionOption[]) => void; + } + ).onChange([mockOptions[0], mockOptions[1]]); wrapper.update(); }); wrapper.find(`[data-test-subj="add-index"]`).first().simulate('click'); diff --git a/x-pack/plugins/security_solution/public/common/components/super_date_picker/index.tsx b/x-pack/plugins/security_solution/public/common/components/super_date_picker/index.tsx index 04e4203df1a99..dc2ba963972a1 100644 --- a/x-pack/plugins/security_solution/public/common/components/super_date_picker/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/super_date_picker/index.tsx @@ -235,67 +235,69 @@ export const formatDate = ( return momentDate != null && momentDate.isValid() ? momentDate.toISOString() : ''; }; -export const dispatchUpdateReduxTime = (dispatch: Dispatch) => ({ - end, - id, - isQuickSelection, - kql, - start, - timelineId, -}: UpdateReduxTime): ReturnUpdateReduxTime => { - const fromDate = formatDate(start); - let toDate = formatDate(end, { roundUp: true }); - if (isQuickSelection) { - if (end === start) { +export const dispatchUpdateReduxTime = + (dispatch: Dispatch) => + ({ + end, + id, + isQuickSelection, + kql, + start, + timelineId, + }: UpdateReduxTime): ReturnUpdateReduxTime => { + const fromDate = formatDate(start); + let toDate = formatDate(end, { roundUp: true }); + if (isQuickSelection) { + if (end === start) { + dispatch( + inputsActions.setAbsoluteRangeDatePicker({ + id, + fromStr: start, + toStr: end, + from: fromDate, + to: toDate, + }) + ); + } else { + dispatch( + inputsActions.setRelativeRangeDatePicker({ + id, + fromStr: start, + toStr: end, + from: fromDate, + to: toDate, + }) + ); + } + } else { + toDate = formatDate(end); dispatch( inputsActions.setAbsoluteRangeDatePicker({ id, - fromStr: start, - toStr: end, - from: fromDate, - to: toDate, + from: formatDate(start), + to: formatDate(end), }) ); - } else { + } + if (timelineId != null) { dispatch( - inputsActions.setRelativeRangeDatePicker({ - id, - fromStr: start, - toStr: end, - from: fromDate, - to: toDate, + timelineActions.updateRange({ + id: timelineId, + start: fromDate, + end: toDate, }) ); } - } else { - toDate = formatDate(end); - dispatch( - inputsActions.setAbsoluteRangeDatePicker({ - id, - from: formatDate(start), - to: formatDate(end), - }) - ); - } - if (timelineId != null) { - dispatch( - timelineActions.updateRange({ - id: timelineId, - start: fromDate, - end: toDate, - }) - ); - } - if (kql) { + if (kql) { + return { + kqlHaveBeenUpdated: kql.refetch(dispatch), + }; + } + return { - kqlHaveBeenUpdated: kql.refetch(dispatch), + kqlHaveBeenUpdated: false, }; - } - - return { - kqlHaveBeenUpdated: false, }; -}; export const makeMapStateToProps = () => { const getDurationSelector = durationSelector(); diff --git a/x-pack/plugins/security_solution/public/common/components/threat_match/entry_item.test.tsx b/x-pack/plugins/security_solution/public/common/components/threat_match/entry_item.test.tsx index e9f26cfabb2b7..4b153f0c75775 100644 --- a/x-pack/plugins/security_solution/public/common/components/threat_match/entry_item.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/threat_match/entry_item.test.tsx @@ -10,10 +10,7 @@ import React from 'react'; import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; import { EntryItem } from './entry_item'; -import { - fields, - getField, -} from '../../../../../../../src/plugins/data/common/index_patterns/fields/fields.mocks'; +import { fields, getField } from '../../../../../../../src/plugins/data/common/mocks'; import { IndexPattern } from 'src/plugins/data/public'; jest.mock('../../../common/lib/kibana'); @@ -81,9 +78,11 @@ describe('EntryItem', () => { /> ); - ((wrapper.find(EuiComboBox).at(0).props() as unknown) as { - onChange: (a: EuiComboBoxOptionOption[]) => void; - }).onChange([{ label: 'machine.os' }]); + ( + wrapper.find(EuiComboBox).at(0).props() as unknown as { + onChange: (a: EuiComboBoxOptionOption[]) => void; + } + ).onChange([{ label: 'machine.os' }]); expect(mockOnChange).toHaveBeenCalledWith( { @@ -126,9 +125,11 @@ describe('EntryItem', () => { /> ); - ((wrapper.find(EuiComboBox).at(1).props() as unknown) as { - onChange: (a: EuiComboBoxOptionOption[]) => void; - }).onChange([{ label: 'is not' }]); + ( + wrapper.find(EuiComboBox).at(1).props() as unknown as { + onChange: (a: EuiComboBoxOptionOption[]) => void; + } + ).onChange([{ label: 'is not' }]); expect(mockOnChange).toHaveBeenCalledWith( { id: '123', field: 'ip', type: 'mapping', value: '' }, diff --git a/x-pack/plugins/security_solution/public/common/components/threat_match/helpers.test.tsx b/x-pack/plugins/security_solution/public/common/components/threat_match/helpers.test.tsx index 24a0f94e05883..0e5f379a18767 100644 --- a/x-pack/plugins/security_solution/public/common/components/threat_match/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/threat_match/helpers.test.tsx @@ -5,10 +5,7 @@ * 2.0. */ -import { - fields, - getField, -} from '../../../../../../../src/plugins/data/common/index_patterns/fields/fields.mocks'; +import { fields, getField } from '../../../../../../../src/plugins/data/common/mocks'; import { Entry, EmptyEntry, ThreatMapEntries, FormattedEntry } from './types'; import { FieldSpec, IndexPattern } from '../../../../../../../src/plugins/data/common'; import moment from 'moment-timezone'; diff --git a/x-pack/plugins/security_solution/public/common/components/threat_match/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/threat_match/index.test.tsx index 058703ce369f7..872f78d91eebb 100644 --- a/x-pack/plugins/security_solution/public/common/components/threat_match/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/threat_match/index.test.tsx @@ -10,7 +10,7 @@ import { ThemeProvider } from 'styled-components'; import { mount } from 'enzyme'; import { waitFor } from '@testing-library/react'; -import { fields } from '../../../../../../../src/plugins/data/common/index_patterns/fields/fields.mocks'; +import { fields } from '../../../../../../../src/plugins/data/common/mocks'; import { useKibana } from '../../../common/lib/kibana'; diff --git a/x-pack/plugins/security_solution/public/common/components/threat_match/list_item.test.tsx b/x-pack/plugins/security_solution/public/common/components/threat_match/list_item.test.tsx index 39fc5dee82e8c..d6704b3a9cf17 100644 --- a/x-pack/plugins/security_solution/public/common/components/threat_match/list_item.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/threat_match/list_item.test.tsx @@ -10,7 +10,7 @@ import { ThemeProvider } from 'styled-components'; import { mount } from 'enzyme'; import { useKibana } from '../../../common/lib/kibana'; -import { fields } from '../../../../../../../src/plugins/data/common/index_patterns/fields/fields.mocks'; +import { fields } from '../../../../../../../src/plugins/data/common/mocks'; import { ListItemComponent } from './list_item'; import { ThreatMapEntries } from './types'; diff --git a/x-pack/plugins/security_solution/public/common/components/threat_match/reducer.ts b/x-pack/plugins/security_solution/public/common/components/threat_match/reducer.ts index b7e4838a255f2..37637b2fb2baa 100644 --- a/x-pack/plugins/security_solution/public/common/components/threat_match/reducer.ts +++ b/x-pack/plugins/security_solution/public/common/components/threat_match/reducer.ts @@ -27,27 +27,29 @@ export type Action = lastEntry: ThreatMapEntries; }; -export const reducer = () => (state: State, action: Action): State => { - switch (action.type) { - case 'setEntries': { - const isAndLogicIncluded = - action.entries.filter(({ entries }) => entries.length > 1).length > 0; +export const reducer = + () => + (state: State, action: Action): State => { + switch (action.type) { + case 'setEntries': { + const isAndLogicIncluded = + action.entries.filter(({ entries }) => entries.length > 1).length > 0; - const returnState = { - ...state, - andLogicIncluded: isAndLogicIncluded, - entries: action.entries, - }; - return returnState; + const returnState = { + ...state, + andLogicIncluded: isAndLogicIncluded, + entries: action.entries, + }; + return returnState; + } + case 'setDefault': { + return { + ...state, + ...action.initialState, + entries: [{ ...action.lastEntry, entries: [getDefaultEmptyEntry()] }], + }; + } + default: + return state; } - case 'setDefault': { - return { - ...state, - ...action.initialState, - entries: [{ ...action.lastEntry, entries: [getDefaultEmptyEntry()] }], - }; - } - default: - return state; - } -}; + }; diff --git a/x-pack/plugins/security_solution/public/common/components/toasters/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/toasters/index.test.tsx index 1d78570e18a59..e52139b0f5eff 100644 --- a/x-pack/plugins/security_solution/public/common/components/toasters/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/toasters/index.test.tsx @@ -31,8 +31,7 @@ const mockToast: AppToast = { iconType: 'alert', title: 'Test & Test', toastLifeTimeMs: 100, - text: - 'Error 1, Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', + text: 'Error 1, Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', }; describe('Toaster', () => { diff --git a/x-pack/plugins/security_solution/public/common/components/toasters/modal_all_errors.test.tsx b/x-pack/plugins/security_solution/public/common/components/toasters/modal_all_errors.test.tsx index 94cf94ed46da7..e68f3ee0a9bbf 100644 --- a/x-pack/plugins/security_solution/public/common/components/toasters/modal_all_errors.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/toasters/modal_all_errors.test.tsx @@ -58,7 +58,7 @@ describe('Modal all errors', () => { // This test exists to ensure that errors will work if it is a non-array which can happen in rare corner cases. test('it doesnt cause errors when errors is not an array which can be the rare case in corner cases', () => { const mockToastWithTwoError = cloneDeep(mockToast); - mockToastWithTwoError.errors = ('' as unknown) as string[]; + mockToastWithTwoError.errors = '' as unknown as string[]; const wrapper = shallow( ); diff --git a/x-pack/plugins/security_solution/public/common/components/top_n/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/top_n/index.test.tsx index 4f6834e84d83a..6962ed03e81d4 100644 --- a/x-pack/plugins/security_solution/public/common/components/top_n/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/top_n/index.test.tsx @@ -94,8 +94,7 @@ const state: State = { id: TimelineId.active, dataProviders: [ { - id: - 'draggable-badge-default-draggable-netflow-renderer-timeline-1-_qpBe3EBD7k-aQQL7v7--_qpBe3EBD7k-aQQL7v7--network_transport-tcp', + id: 'draggable-badge-default-draggable-netflow-renderer-timeline-1-_qpBe3EBD7k-aQQL7v7--_qpBe3EBD7k-aQQL7v7--network_transport-tcp', name: 'tcp', enabled: true, excluded: false, @@ -161,6 +160,60 @@ let testProps = { }; describe('StatefulTopN', () => { + describe('rendering globalFilter', () => { + let wrapper: ReactWrapper; + const globalFilters = [ + { + meta: { + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'signal.rule.id', + params: { + query: 'd62249f0-1632-11ec-b035-19607969bc20', + }, + }, + query: { + match_phrase: { + 'signal.rule.id': 'd62249f0-1632-11ec-b035-19607969bc20', + }, + }, + }, + ]; + beforeEach(() => { + wrapper = mount( + + + + ); + }); + + test(`provides filters from non Redux state when rendering in alerts table`, () => { + const props = wrapper.find('[data-test-subj="top-n"]').first().props() as Props; + + expect(props.filters).toEqual([ + { + meta: { + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'signal.rule.id', + params: { + query: 'd62249f0-1632-11ec-b035-19607969bc20', + }, + }, + query: { + match_phrase: { + 'signal.rule.id': 'd62249f0-1632-11ec-b035-19607969bc20', + }, + }, + }, + ]); + }); + }); + describe('rendering in a global NON-timeline context', () => { let wrapper: ReactWrapper; diff --git a/x-pack/plugins/security_solution/public/common/components/top_n/index.tsx b/x-pack/plugins/security_solution/public/common/components/top_n/index.tsx index 2286a53030784..1556f2d0f3d13 100644 --- a/x-pack/plugins/security_solution/public/common/components/top_n/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/top_n/index.tsx @@ -41,11 +41,11 @@ const makeMapStateToProps = () => { // The mapped Redux state provided to this component includes the global // filters that appear at the top of most views in the app, and all the // filters in the active timeline: - const mapStateToProps = (state: State) => { + const mapStateToProps = (state: State, ownProps: { globalFilters?: Filter[] }) => { const activeTimeline: TimelineModel = getTimeline(state, TimelineId.active) ?? timelineDefaults; const activeTimelineFilters = activeTimeline.filters ?? EMPTY_FILTERS; const activeTimelineInput: inputsModel.InputsRange = getInputsTimeline(state); - + const { globalFilters } = ownProps; return { activeTimelineEventType: activeTimeline.eventType, activeTimelineFilters: @@ -59,7 +59,7 @@ const makeMapStateToProps = () => { dataProviders: activeTimeline.activeTab === TimelineTabs.query ? activeTimeline.dataProviders : [], globalQuery: getGlobalQuerySelector(state), - globalFilters: getGlobalFiltersQuerySelector(state), + globalFilters: globalFilters ?? getGlobalFiltersQuerySelector(state), kqlMode: activeTimeline.kqlMode, }; }; @@ -82,6 +82,7 @@ export interface OwnProps { toggleTopN: () => void; onFilterAdded?: () => void; value?: string[] | string | null; + globalFilters?: Filter[]; } type PropsFromRedux = ConnectedProps; type Props = OwnProps & PropsFromRedux; diff --git a/x-pack/plugins/security_solution/public/common/components/top_n/top_n.tsx b/x-pack/plugins/security_solution/public/common/components/top_n/top_n.tsx index 2676343c24ac1..7335d6203697f 100644 --- a/x-pack/plugins/security_solution/public/common/components/top_n/top_n.tsx +++ b/x-pack/plugins/security_solution/public/common/components/top_n/top_n.tsx @@ -80,9 +80,10 @@ const TopNComponent: React.FC = ({ toggleTopN, }) => { const [view, setView] = useState(defaultView); - const onViewSelected = useCallback((value: string) => setView(value as TimelineEventsType), [ - setView, - ]); + const onViewSelected = useCallback( + (value: string) => setView(value as TimelineEventsType), + [setView] + ); const indicesSelector = useMemo(getIndicesSelector, []); const { all: allIndices, raw: rawIndices } = useSelector( (state) => indicesSelector(state), diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/helpers.ts b/x-pack/plugins/security_solution/public/common/components/url_state/helpers.ts index 035e1314f1557..a880601d620a9 100644 --- a/x-pack/plugins/security_solution/public/common/components/url_state/helpers.ts +++ b/x-pack/plugins/security_solution/public/common/components/url_state/helpers.ts @@ -33,7 +33,7 @@ export const isDetectionsPages = (pageName: string) => export const decodeRisonUrlState = (value: string | undefined): T | null => { try { - return value ? ((decode(value) as unknown) as T) : null; + return value ? (decode(value) as unknown as T) : null; } catch (error) { if (error instanceof Error && error.message.startsWith('rison decoder error')) { return null; @@ -54,27 +54,27 @@ export const getParamFromQueryString = (queryString: string, key: string) => { return Array.isArray(queryParam) ? queryParam[0] : queryParam; }; -export const replaceStateKeyInQueryString = (stateKey: string, urlState: T) => ( - queryString: string -): string => { - const previousQueryValues = parse(queryString, { sort: false }); - if (urlState == null || (typeof urlState === 'string' && urlState === '')) { - delete previousQueryValues[stateKey]; +export const replaceStateKeyInQueryString = + (stateKey: string, urlState: T) => + (queryString: string): string => { + const previousQueryValues = parse(queryString, { sort: false }); + if (urlState == null || (typeof urlState === 'string' && urlState === '')) { + delete previousQueryValues[stateKey]; - return stringify(url.encodeQuery(previousQueryValues), { sort: false, encode: false }); - } + return stringify(url.encodeQuery(previousQueryValues), { sort: false, encode: false }); + } - // ಠ_ಠ Code was copied from x-pack/legacy/plugins/infra/public/utils/url_state.tsx ಠ_ಠ - // Remove this if these utilities are promoted to kibana core - const newValue = - typeof urlState === 'undefined' - ? previousQueryValues - : { - ...previousQueryValues, - [stateKey]: encodeRisonUrlState(urlState), - }; - return stringify(url.encodeQuery(newValue), { sort: false, encode: false }); -}; + // ಠ_ಠ Code was copied from x-pack/legacy/plugins/infra/public/utils/url_state.tsx ಠ_ಠ + // Remove this if these utilities are promoted to kibana core + const newValue = + typeof urlState === 'undefined' + ? previousQueryValues + : { + ...previousQueryValues, + [stateKey]: encodeRisonUrlState(urlState), + }; + return stringify(url.encodeQuery(newValue), { sort: false, encode: false }); + }; export const replaceQueryStringInLocation = ( location: H.Location, diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/url_state/index.test.tsx index 18b99adca3a55..81b2111f00da9 100644 --- a/x-pack/plugins/security_solution/public/common/components/url_state/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/url_state/index.test.tsx @@ -23,6 +23,7 @@ import { import { UrlStateContainerPropTypes } from './types'; import { useUrlStateHooks } from './use_url_state'; import { waitFor } from '@testing-library/react'; +import { useLocation } from 'react-router-dom'; let mockProps: UrlStateContainerPropTypes; @@ -59,11 +60,12 @@ jest.mock('../../lib/kibana', () => ({ }, })); -jest.mock('react-redux', () => { - const original = jest.requireActual('react-redux'); +jest.mock('react-router-dom', () => { + const original = jest.requireActual('react-router-dom'); + return { ...original, - useDispatch: () => jest.fn(), + useLocation: jest.fn(), }; }); @@ -84,6 +86,11 @@ describe('UrlStateContainer', () => { pageName, detailName, }).relativeTimeSearch.undefinedQuery; + + (useLocation as jest.Mock).mockReturnValue({ + pathname: mockProps.pathName, + }); + mount( useUrlStateHooks(args)} />); expect(mockSetRelativeRangeDatePicker.mock.calls[1][0]).toEqual({ @@ -113,6 +120,11 @@ describe('UrlStateContainer', () => { (page, namespaceLower, namespaceUpper, examplePath, type, pageName, detailName) => { mockProps = getMockPropsObj({ page, examplePath, namespaceLower, pageName, detailName }) .absoluteTimeSearch.undefinedQuery; + + (useLocation as jest.Mock).mockReturnValue({ + pathname: mockProps.pathName, + }); + mount( useUrlStateHooks(args)} />); expect(mockSetAbsoluteRangeDatePicker.mock.calls[1][0]).toEqual({ @@ -138,6 +150,11 @@ describe('UrlStateContainer', () => { (page, namespaceLower, namespaceUpper, examplePath, type, pageName, detailName) => { mockProps = getMockPropsObj({ page, examplePath, namespaceLower, pageName, detailName }) .relativeTimeSearch.undefinedQuery; + + (useLocation as jest.Mock).mockReturnValue({ + pathname: mockProps.pathName, + }); + mount( useUrlStateHooks(args)} />); expect(mockSetFilterQuery.mock.calls[0][0]).toEqual({ @@ -162,6 +179,11 @@ describe('UrlStateContainer', () => { pageName, detailName, }).noSearch.definedQuery; + + (useLocation as jest.Mock).mockReturnValue({ + pathname: mockProps.pathName, + }); + mount( useUrlStateHooks(args)} />); expect( @@ -176,6 +198,24 @@ describe('UrlStateContainer', () => { ); }); }); + + it("it doesn't update URL state when pathName and browserPAth are out of sync", () => { + mockProps = getMockPropsObj({ + page: CONSTANTS.networkPage, + examplePath: '/network', + namespaceLower: 'network', + pageName: SecurityPageName.network, + detailName: undefined, + }).noSearch.undefinedQuery; + + (useLocation as jest.Mock).mockReturnValue({ + pathname: 'out of sync path', + }); + + mount( useUrlStateHooks(args)} />); + + expect(mockHistory.replace).not.toHaveBeenCalled(); + }); }); describe('After Initialization, keep Relative Date up to date for global only on alerts page', () => { @@ -189,6 +229,11 @@ describe('UrlStateContainer', () => { pageName, detailName, }).relativeTimeSearch.undefinedQuery; + + (useLocation as jest.Mock).mockReturnValue({ + pathname: mockProps.pathName, + }); + const wrapper = mount( useUrlStateHooks(args)} /> ); diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/index_mocked.test.tsx b/x-pack/plugins/security_solution/public/common/components/url_state/index_mocked.test.tsx index 3175656f12071..f1e2cd7fa5357 100644 --- a/x-pack/plugins/security_solution/public/common/components/url_state/index_mocked.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/url_state/index_mocked.test.tsx @@ -15,6 +15,7 @@ import { CONSTANTS } from './constants'; import { getFilterQuery, getMockPropsObj, mockHistory, testCases } from './test_dependencies'; import { UrlStateContainerPropTypes } from './types'; import { useUrlStateHooks } from './use_url_state'; +import { useLocation } from 'react-router-dom'; let mockProps: UrlStateContainerPropTypes; @@ -31,13 +32,9 @@ jest.mock('../../lib/kibana', () => ({ }), })); -jest.mock('react-redux', () => { - const original = jest.requireActual('react-redux'); - return { - ...original, - useDispatch: () => jest.fn(), - }; -}); +jest.mock('react-router-dom', () => ({ + useLocation: jest.fn(), +})); describe('UrlStateContainer - lodash.throttle mocked to test update url', () => { afterEach(() => { @@ -54,6 +51,11 @@ describe('UrlStateContainer - lodash.throttle mocked to test update url', () => pageName: SecurityPageName.network, detailName: undefined, }).noSearch.definedQuery; + + (useLocation as jest.Mock).mockReturnValue({ + pathname: mockProps.pathName, + }); + const wrapper = mount( useUrlStateHooks(args)} /> ); @@ -105,6 +107,11 @@ describe('UrlStateContainer - lodash.throttle mocked to test update url', () => pageName: SecurityPageName.network, detailName: undefined, }).noSearch.undefinedQuery; + + (useLocation as jest.Mock).mockReturnValue({ + pathname: mockProps.pathName, + }); + const wrapper = mount( useUrlStateHooks(args)} /> ); @@ -137,6 +144,10 @@ describe('UrlStateContainer - lodash.throttle mocked to test update url', () => detailName: undefined, }).noSearch.undefinedQuery; + (useLocation as jest.Mock).mockReturnValue({ + pathname: mockProps.pathName, + }); + const wrapper = mount( useUrlStateHooks(args)} /> ); @@ -170,6 +181,10 @@ describe('UrlStateContainer - lodash.throttle mocked to test update url', () => detailName: undefined, }).noSearch.undefinedQuery; + (useLocation as jest.Mock).mockReturnValue({ + pathname: mockProps.pathName, + }); + const wrapper = mount( useUrlStateHooks(args)} /> ); @@ -203,6 +218,11 @@ describe('UrlStateContainer - lodash.throttle mocked to test update url', () => (page, namespaceLower, namespaceUpper, examplePath, type, pageName, detailName) => { mockProps = getMockPropsObj({ page, examplePath, namespaceLower, pageName, detailName }) .noSearch.undefinedQuery; + + (useLocation as jest.Mock).mockReturnValue({ + pathname: mockProps.pathName, + }); + mount( useUrlStateHooks(args)} />); expect(mockHistory.replace.mock.calls[0][0]).toEqual({ @@ -239,6 +259,11 @@ describe('UrlStateContainer - lodash.throttle mocked to test update url', () => pageName: SecurityPageName.network, detailName: undefined, }).noSearch.definedQuery; + + (useLocation as jest.Mock).mockReturnValue({ + pathname: mockProps.pathName, + }); + const wrapper = mount( useUrlStateHooks(args)} /> ); @@ -249,7 +274,12 @@ describe('UrlStateContainer - lodash.throttle mocked to test update url', () => "?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))" ); + (useLocation as jest.Mock).mockReturnValue({ + pathname: updatedProps.pathName, + }); + wrapper.setProps({ hookProps: updatedProps }); + wrapper.update(); expect( diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/initialize_redux_by_url.tsx b/x-pack/plugins/security_solution/public/common/components/url_state/initialize_redux_by_url.tsx index 4df5e093ec07c..3169b348961c4 100644 --- a/x-pack/plugins/security_solution/public/common/components/url_state/initialize_redux_by_url.tsx +++ b/x-pack/plugins/security_solution/public/common/components/url_state/initialize_redux_by_url.tsx @@ -25,88 +25,89 @@ import { DispatchSetInitialStateFromUrl, SetInitialStateFromUrl } from './types' import { queryTimelineById } from '../../../timelines/components/open_timeline/helpers'; import { SourcererScopeName, SourcererScopePatterns } from '../../store/sourcerer/model'; -export const dispatchSetInitialStateFromUrl = ( - dispatch: Dispatch -): DispatchSetInitialStateFromUrl => ({ - filterManager, - indexPattern, - pageName, - savedQueries, - updateTimeline, - updateTimelineIsLoading, - urlStateToUpdate, -}: SetInitialStateFromUrl): (() => void) => () => { - urlStateToUpdate.forEach(({ urlKey, newUrlStateString }) => { - if (urlKey === CONSTANTS.timerange) { - updateTimerange(newUrlStateString, dispatch); - } - if (urlKey === CONSTANTS.sourcerer) { - const sourcererState = decodeRisonUrlState(newUrlStateString); - if (sourcererState != null) { - const activeScopes: SourcererScopeName[] = Object.keys(sourcererState).filter( - (key) => !(key === SourcererScopeName.default && isDetectionsPages(pageName)) - ) as SourcererScopeName[]; - activeScopes.forEach((scope) => - dispatch( - sourcererActions.setSelectedIndexPatterns({ - id: scope, - selectedPatterns: sourcererState[scope] ?? [], - }) - ) - ); +export const dispatchSetInitialStateFromUrl = + (dispatch: Dispatch): DispatchSetInitialStateFromUrl => + ({ + filterManager, + indexPattern, + pageName, + savedQueries, + updateTimeline, + updateTimelineIsLoading, + urlStateToUpdate, + }: SetInitialStateFromUrl): (() => void) => + () => { + urlStateToUpdate.forEach(({ urlKey, newUrlStateString }) => { + if (urlKey === CONSTANTS.timerange) { + updateTimerange(newUrlStateString, dispatch); } - } - - if (urlKey === CONSTANTS.appQuery && indexPattern != null) { - const appQuery = decodeRisonUrlState(newUrlStateString); - if (appQuery != null) { - dispatch( - inputsActions.setFilterQuery({ - id: 'global', - query: appQuery.query, - language: appQuery.language, - }) - ); + if (urlKey === CONSTANTS.sourcerer) { + const sourcererState = decodeRisonUrlState(newUrlStateString); + if (sourcererState != null) { + const activeScopes: SourcererScopeName[] = Object.keys(sourcererState).filter( + (key) => !(key === SourcererScopeName.default && isDetectionsPages(pageName)) + ) as SourcererScopeName[]; + activeScopes.forEach((scope) => + dispatch( + sourcererActions.setSelectedIndexPatterns({ + id: scope, + selectedPatterns: sourcererState[scope] ?? [], + }) + ) + ); + } } - } - - if (urlKey === CONSTANTS.filters) { - const filters = decodeRisonUrlState(newUrlStateString); - filterManager.setFilters(filters || []); - } - if (urlKey === CONSTANTS.savedQuery) { - const savedQueryId = decodeRisonUrlState(newUrlStateString); - if (savedQueryId != null && savedQueryId !== '') { - savedQueries.getSavedQuery(savedQueryId).then((savedQueryData) => { - filterManager.setFilters(savedQueryData.attributes.filters || []); + if (urlKey === CONSTANTS.appQuery && indexPattern != null) { + const appQuery = decodeRisonUrlState(newUrlStateString); + if (appQuery != null) { dispatch( inputsActions.setFilterQuery({ id: 'global', - ...savedQueryData.attributes.query, + query: appQuery.query, + language: appQuery.language, }) ); - dispatch(inputsActions.setSavedQuery({ id: 'global', savedQuery: savedQueryData })); - }); + } } - } - if (urlKey === CONSTANTS.timeline) { - const timeline = decodeRisonUrlState(newUrlStateString); - if (timeline != null && timeline.id !== '') { - queryTimelineById({ - activeTimelineTab: timeline.activeTab, - duplicate: false, - graphEventId: timeline.graphEventId, - timelineId: timeline.id, - openTimeline: timeline.isOpen, - updateIsLoading: updateTimelineIsLoading, - updateTimeline, - }); + if (urlKey === CONSTANTS.filters) { + const filters = decodeRisonUrlState(newUrlStateString); + filterManager.setFilters(filters || []); } - } - }); -}; + + if (urlKey === CONSTANTS.savedQuery) { + const savedQueryId = decodeRisonUrlState(newUrlStateString); + if (savedQueryId != null && savedQueryId !== '') { + savedQueries.getSavedQuery(savedQueryId).then((savedQueryData) => { + filterManager.setFilters(savedQueryData.attributes.filters || []); + dispatch( + inputsActions.setFilterQuery({ + id: 'global', + ...savedQueryData.attributes.query, + }) + ); + dispatch(inputsActions.setSavedQuery({ id: 'global', savedQuery: savedQueryData })); + }); + } + } + + if (urlKey === CONSTANTS.timeline) { + const timeline = decodeRisonUrlState(newUrlStateString); + if (timeline != null && timeline.id !== '') { + queryTimelineById({ + activeTimelineTab: timeline.activeTab, + duplicate: false, + graphEventId: timeline.graphEventId, + timelineId: timeline.id, + openTimeline: timeline.isOpen, + updateIsLoading: updateTimelineIsLoading, + updateTimeline, + }); + } + } + }); + }; const updateTimerange = (newUrlStateString: string, dispatch: Dispatch) => { const timerangeStateData = decodeRisonUrlState(newUrlStateString); diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/test_dependencies.ts b/x-pack/plugins/security_solution/public/common/components/url_state/test_dependencies.ts index cdccd7f2b0b1c..caa5eefc51400 100644 --- a/x-pack/plugins/security_solution/public/common/components/url_state/test_dependencies.ts +++ b/x-pack/plugins/security_solution/public/common/components/url_state/test_dependencies.ts @@ -28,13 +28,18 @@ export const getFilterQuery = (): Query => ({ language: 'kuery', }); -export const mockSetFilterQuery: jest.Mock = (inputsActions.setFilterQuery as unknown) as jest.Mock; -export const mockAddGlobalLinkTo: jest.Mock = (inputsActions.addGlobalLinkTo as unknown) as jest.Mock; -export const mockAddTimelineLinkTo: jest.Mock = (inputsActions.addTimelineLinkTo as unknown) as jest.Mock; -export const mockRemoveGlobalLinkTo: jest.Mock = (inputsActions.removeGlobalLinkTo as unknown) as jest.Mock; -export const mockRemoveTimelineLinkTo: jest.Mock = (inputsActions.removeTimelineLinkTo as unknown) as jest.Mock; -export const mockSetAbsoluteRangeDatePicker: jest.Mock = (inputsActions.setAbsoluteRangeDatePicker as unknown) as jest.Mock; -export const mockSetRelativeRangeDatePicker: jest.Mock = (inputsActions.setRelativeRangeDatePicker as unknown) as jest.Mock; +export const mockSetFilterQuery: jest.Mock = inputsActions.setFilterQuery as unknown as jest.Mock; +export const mockAddGlobalLinkTo: jest.Mock = inputsActions.addGlobalLinkTo as unknown as jest.Mock; +export const mockAddTimelineLinkTo: jest.Mock = + inputsActions.addTimelineLinkTo as unknown as jest.Mock; +export const mockRemoveGlobalLinkTo: jest.Mock = + inputsActions.removeGlobalLinkTo as unknown as jest.Mock; +export const mockRemoveTimelineLinkTo: jest.Mock = + inputsActions.removeTimelineLinkTo as unknown as jest.Mock; +export const mockSetAbsoluteRangeDatePicker: jest.Mock = + inputsActions.setAbsoluteRangeDatePicker as unknown as jest.Mock; +export const mockSetRelativeRangeDatePicker: jest.Mock = + inputsActions.setRelativeRangeDatePicker as unknown as jest.Mock; jest.mock('../../store/actions', () => ({ inputsActions: { @@ -123,8 +128,8 @@ export const defaultProps: UrlStateContainerPropTypes = { [CONSTANTS.sourcerer]: {}, }, setInitialStateFromUrl: dispatchSetInitialStateFromUrl(mockDispatch), - updateTimeline: (jest.fn() as unknown) as DispatchUpdateTimeline, - updateTimelineIsLoading: (jest.fn() as unknown) as ActionCreator<{ + updateTimeline: jest.fn() as unknown as DispatchUpdateTimeline, + updateTimelineIsLoading: jest.fn() as unknown as ActionCreator<{ id: string; isLoading: boolean; }>, @@ -164,13 +169,7 @@ interface GetMockPropsObj { detailName: string | undefined; } -export const getMockPropsObj = ({ - page, - examplePath, - namespaceLower, - pageName, - detailName, -}: GetMockPropsObj) => ({ +export const getMockPropsObj = ({ page, examplePath, pageName, detailName }: GetMockPropsObj) => ({ noSearch: { undefinedQuery: getMockProps( { diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/use_url_state.tsx b/x-pack/plugins/security_solution/public/common/components/url_state/use_url_state.tsx index 87e17ba7691cc..b3505196d2366 100644 --- a/x-pack/plugins/security_solution/public/common/components/url_state/use_url_state.tsx +++ b/x-pack/plugins/security_solution/public/common/components/url_state/use_url_state.tsx @@ -9,7 +9,7 @@ import { difference, isEmpty } from 'lodash/fp'; import { useEffect, useRef, useState } from 'react'; import deepEqual from 'fast-deep-equal'; -import { useDispatch } from 'react-redux'; +import { useLocation } from 'react-router-dom'; import { useKibana } from '../../lib/kibana'; import { CONSTANTS, UrlStateType } from './constants'; import { @@ -32,9 +32,6 @@ import { UrlState, } from './types'; import { TimelineUrl } from '../../../timelines/store/timeline/model'; -import { timelineActions } from '../../../timelines/store/timeline'; -import { TimelineId } from '../../../../../timelines/common'; - function usePrevious(value: PreviousLocationUrlState) { const ref = useRef(value); useEffect(() => { @@ -61,20 +58,20 @@ const updateTimelineAtinitialization = ( export const useUrlStateHooks = ({ detailName, indexPattern, - history, navTabs, pageName, - pathName, - search, setInitialStateFromUrl, updateTimeline, updateTimelineIsLoading, urlState, + search, + pathName, + history, }: UrlStateContainerPropTypes) => { const [isInitializing, setIsInitializing] = useState(true); const { filterManager, savedQueries } = useKibana().services.data.query; + const { pathname: browserPathName } = useLocation(); const prevProps = usePrevious({ pathName, pageName, urlState }); - const dispatch = useDispatch(); const handleInitialize = (type: UrlStateType, needUpdate?: boolean) => { let mySearch = search; @@ -175,6 +172,14 @@ export const useUrlStateHooks = ({ }; useEffect(() => { + // When browser location and store location are out of sync, skip the execution. + // It happens in three scenarios: + // * When changing urlState and quickly moving to a new location. + // * Redirects as "security/hosts" -> "security/hosts/allHosts" + // * It also happens once on every location change because browserPathName gets updated before pathName + // *Warning*: Removing this return would cause redirect loops that crashes the APP. + if (browserPathName !== pathName) return; + const type: UrlStateType = getUrlType(pageName); if (isInitializing && pageName != null && pageName !== '') { handleInitialize(type); @@ -226,10 +231,9 @@ export const useUrlStateHooks = ({ }); } else if (pathName !== prevProps.pathName) { handleInitialize(type, isDetectionsPages(pageName)); - dispatch(timelineActions.showTimeline({ id: TimelineId.active, show: false })); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isInitializing, history, pathName, pageName, prevProps, urlState, dispatch]); + }, [isInitializing, history, pathName, pageName, prevProps, urlState, browserPathName]); useEffect(() => { document.title = `${getTitle(pageName, detailName, navTabs)} - Kibana`; diff --git a/x-pack/plugins/security_solution/public/common/components/with_hover_actions/index.tsx b/x-pack/plugins/security_solution/public/common/components/with_hover_actions/index.tsx index c867862e690bd..05dc523700690 100644 --- a/x-pack/plugins/security_solution/public/common/components/with_hover_actions/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/with_hover_actions/index.tsx @@ -19,11 +19,11 @@ import styled from 'styled-components'; const HOVER_INTENT_DELAY = 100; // ms // eslint-disable-next-line @typescript-eslint/no-explicit-any -const WithHoverActionsPopover = (styled(EuiPopover as any)` +const WithHoverActionsPopover = styled(EuiPopover as any)` .euiPopover__anchor { width: 100%; } -` as unknown) as typeof EuiPopover; +` as unknown as typeof EuiPopover; interface Props { /** diff --git a/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/index.ts b/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/index.ts index 4f558412576b4..c1d45f605c821 100644 --- a/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/index.ts +++ b/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/index.ts @@ -51,25 +51,21 @@ export const useTimelineLastEventTime = ({ const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); const [loading, setLoading] = useState(false); - const [ - TimelineLastEventTimeRequest, - setTimelineLastEventTimeRequest, - ] = useState({ - defaultIndex: indexNames, - docValueFields, - factoryQueryType: TimelineEventsQueries.lastEventTime, - indexKey, - details, - }); + const [TimelineLastEventTimeRequest, setTimelineLastEventTimeRequest] = + useState({ + defaultIndex: indexNames, + docValueFields, + factoryQueryType: TimelineEventsQueries.lastEventTime, + indexKey, + details, + }); - const [ - timelineLastEventTimeResponse, - setTimelineLastEventTimeResponse, - ] = useState({ - lastSeen: null, - refetch: refetch.current, - errorMessage: undefined, - }); + const [timelineLastEventTimeResponse, setTimelineLastEventTimeResponse] = + useState({ + lastSeen: null, + refetch: refetch.current, + errorMessage: undefined, + }); const { addError, addWarning } = useAppToasts(); const timelineLastEventTimeSearch = useCallback( diff --git a/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts b/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts index 52d8fc150ac20..31c59d7800783 100644 --- a/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts +++ b/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts @@ -89,21 +89,19 @@ export const useMatrixHistogram = ({ }, }); - const [ - matrixHistogramRequest, - setMatrixHistogramRequest, - ] = useState({ - defaultIndex: initialIndexName, - factoryQueryType: initialFactoryQueryType, - filterQuery: createFilter(filterQuery), - histogramType: initialHistogramType ?? histogramType, - timerange: initialTimerange, - stackByField, - threshold, - ...(isPtrIncluded != null ? { isPtrIncluded } : {}), - ...(!isEmpty(docValueFields) ? { docValueFields } : {}), - ...(includeMissingData != null ? { includeMissingData } : {}), - }); + const [matrixHistogramRequest, setMatrixHistogramRequest] = + useState({ + defaultIndex: initialIndexName, + factoryQueryType: initialFactoryQueryType, + filterQuery: createFilter(filterQuery), + histogramType: initialHistogramType ?? histogramType, + timerange: initialTimerange, + stackByField, + threshold, + ...(isPtrIncluded != null ? { isPtrIncluded } : {}), + ...(!isEmpty(docValueFields) ? { docValueFields } : {}), + ...(includeMissingData != null ? { includeMissingData } : {}), + }); const { addError, addWarning } = useAppToasts(); const [matrixHistogramResponse, setMatrixHistogramResponse] = useState({ @@ -257,19 +255,20 @@ export const useMatrixHistogramCombined = ( includeMissingData: true, }); - const skipMissingData = useMemo(() => !matrixHistogramQueryProps.stackByField.endsWith('.ip'), [ - matrixHistogramQueryProps.stackByField, - ]); + const skipMissingData = useMemo( + () => !matrixHistogramQueryProps.stackByField.endsWith('.ip'), + [matrixHistogramQueryProps.stackByField] + ); const [missingDataLoading, missingDataResponse] = useMatrixHistogram({ ...matrixHistogramQueryProps, includeMissingData: false, skip: skipMissingData || matrixHistogramQueryProps.filterQuery === undefined, }); - const combinedLoading = useMemo(() => mainLoading || missingDataLoading, [ - mainLoading, - missingDataLoading, - ]); + const combinedLoading = useMemo( + () => mainLoading || missingDataLoading, + [mainLoading, missingDataLoading] + ); const combinedResponse = useMemo(() => { if (skipMissingData) return mainResponse; diff --git a/x-pack/plugins/security_solution/public/common/containers/source/index.tsx b/x-pack/plugins/security_solution/public/common/containers/source/index.tsx index f619e6565b06b..e8558d51c61f6 100644 --- a/x-pack/plugins/security_solution/public/common/containers/source/index.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/source/index.tsx @@ -78,7 +78,7 @@ export const getBrowserFields = memoizeOne( if (accumulator[field.category].fields == null) { accumulator[field.category].fields = {}; } - accumulator[field.category].fields[field.name] = (field as unknown) as BrowserField; + accumulator[field.category].fields[field.name] = field as unknown as BrowserField; return accumulator; }, {}); }, diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.test.ts b/x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.test.ts index 6c5caa25a1f96..fffc982d8aa58 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.test.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.test.ts @@ -97,14 +97,14 @@ describe('useAppToasts', () => { }); it('works normally with a bsearch type error', async () => { - const error = ({ + const error = { message: 'some message', attributes: {}, // empty object and should not show up in the output err: { statusCode: 400, innerMessages: { somethingElse: 'message' }, }, - } as unknown) as IEsError; + } as unknown as IEsError; const { result } = renderHook(() => useAppToasts()); result.current.addError(error, { title: 'title' }); @@ -113,14 +113,14 @@ describe('useAppToasts', () => { }); it('parses a bsearch correctly in the stack and name', async () => { - const error = ({ + const error = { message: 'some message', attributes: {}, // empty object and should not show up in the output err: { statusCode: 400, innerMessages: { somethingElse: 'message' }, }, - } as unknown) as IEsError; + } as unknown as IEsError; const { result } = renderHook(() => useAppToasts()); result.current.addError(error, { title: 'title' }); const errorObj = addErrorMock.mock.calls[0][0]; @@ -177,27 +177,27 @@ describe('useAppToasts', () => { }); it('works normally with a bsearch type error', async () => { - const error = ({ + const error = { message: 'some message', attributes: {}, // empty object and should not show up in the output err: { statusCode: 400, innerMessages: { somethingElse: 'message' }, }, - } as unknown) as IEsError; + } as unknown as IEsError; const result = errorToErrorStackAdapter(error); expect(result).toEqual(Error('some message (400)')); }); it('parses a bsearch correctly in the stack and name', async () => { - const error = ({ + const error = { message: 'some message', attributes: {}, // empty object and should not show up in the output err: { statusCode: 400, innerMessages: { somethingElse: 'message' }, }, - } as unknown) as IEsError; + } as unknown as IEsError; const result = errorToErrorStackAdapter(error); const parsedStack = JSON.parse(result.stack ?? ''); expect(parsedStack).toEqual({ diff --git a/x-pack/plugins/security_solution/public/common/lib/cell_actions/default_cell_actions.tsx b/x-pack/plugins/security_solution/public/common/lib/cell_actions/default_cell_actions.tsx index ae62c214d7b7a..149a0c62b8b6a 100644 --- a/x-pack/plugins/security_solution/public/common/lib/cell_actions/default_cell_actions.tsx +++ b/x-pack/plugins/security_solution/public/common/lib/cell_actions/default_cell_actions.tsx @@ -6,6 +6,7 @@ */ import React, { useCallback, useState, useMemo } from 'react'; +import { Filter } from '../../../../../../../src/plugins/data/public'; import type { BrowserFields, @@ -37,193 +38,185 @@ const useKibanaServices = () => { /** the default actions shown in `EuiDataGrid` cells */ export const defaultCellActions: TGridCellAction[] = [ - ({ data, pageSize }: { data: TimelineNonEcsData[][]; pageSize: number }) => ({ - rowIndex, - columnId, - Component, - }) => { - const { timelines, filterManager } = useKibanaServices(); - - const pageRowIndex = getPageRowIndex(rowIndex, pageSize); - if (pageRowIndex >= data.length) { - return null; - } - - const value = getMappedNonEcsValue({ - data: data[pageRowIndex], - fieldName: columnId, - }); - - return ( - <> - {timelines.getHoverActions().getFilterForValueButton({ - Component, - field: columnId, - filterManager, - onFilterAdded, - ownFocus: false, - showTooltip: false, - value, - })} - - ); - }, - ({ data, pageSize }: { data: TimelineNonEcsData[][]; pageSize: number }) => ({ - rowIndex, - columnId, - Component, - }) => { - const { timelines, filterManager } = useKibanaServices(); - - const pageRowIndex = getPageRowIndex(rowIndex, pageSize); - if (pageRowIndex >= data.length) { - return null; - } - - const value = getMappedNonEcsValue({ - data: data[pageRowIndex], - fieldName: columnId, - }); - - return ( - <> - {timelines.getHoverActions().getFilterOutValueButton({ - Component, - field: columnId, - filterManager, - onFilterAdded, - ownFocus: false, - showTooltip: false, - value, - })} - - ); - }, - ({ data, pageSize }: { data: TimelineNonEcsData[][]; pageSize: number }) => ({ - rowIndex, - columnId, - Component, - }) => { - const { timelines } = useKibanaServices(); - - const pageRowIndex = getPageRowIndex(rowIndex, pageSize); - if (pageRowIndex >= data.length) { - return null; - } - - const value = getMappedNonEcsValue({ - data: data[pageRowIndex], - fieldName: columnId, - }); - - return ( - <> - {timelines.getHoverActions().getCopyButton({ - Component, - field: columnId, - isHoverAction: false, - ownFocus: false, - showTooltip: false, - value, - })} - - ); - }, - ({ data, pageSize }: { data: TimelineNonEcsData[][]; pageSize: number }) => ({ - rowIndex, - columnId, - Component, - }) => { - const { timelines } = useKibanaServices(); - - const pageRowIndex = getPageRowIndex(rowIndex, pageSize); - if (pageRowIndex >= data.length) { - return null; - } - - const value = getMappedNonEcsValue({ - data: data[pageRowIndex], - fieldName: columnId, - }); - - const dataProvider: DataProvider[] = useMemo( - () => - value?.map((x) => ({ - and: [], - enabled: true, - id: `${escapeDataProviderId(columnId)}-row-${rowIndex}-col-${columnId}-val-${x}`, - name: x, - excluded: false, - kqlQuery: '', - queryMatch: { + ({ data, pageSize }: { data: TimelineNonEcsData[][]; pageSize: number }) => + ({ rowIndex, columnId, Component }) => { + const { timelines, filterManager } = useKibanaServices(); + + const pageRowIndex = getPageRowIndex(rowIndex, pageSize); + if (pageRowIndex >= data.length) { + return null; + } + + const value = getMappedNonEcsValue({ + data: data[pageRowIndex], + fieldName: columnId, + }); + + return ( + <> + {timelines.getHoverActions().getFilterForValueButton({ + Component, field: columnId, - value: x, - operator: IS_OPERATOR, - }, - })) ?? [], - [columnId, rowIndex, value] - ); - - return ( - <> - {timelines.getHoverActions().getAddToTimelineButton({ - Component, - dataProvider, - field: columnId, - ownFocus: false, - showTooltip: false, - })} - - ); - }, + filterManager, + onFilterAdded, + ownFocus: false, + showTooltip: false, + value, + })} + + ); + }, + ({ data, pageSize }: { data: TimelineNonEcsData[][]; pageSize: number }) => + ({ rowIndex, columnId, Component }) => { + const { timelines, filterManager } = useKibanaServices(); + + const pageRowIndex = getPageRowIndex(rowIndex, pageSize); + if (pageRowIndex >= data.length) { + return null; + } + + const value = getMappedNonEcsValue({ + data: data[pageRowIndex], + fieldName: columnId, + }); + + return ( + <> + {timelines.getHoverActions().getFilterOutValueButton({ + Component, + field: columnId, + filterManager, + onFilterAdded, + ownFocus: false, + showTooltip: false, + value, + })} + + ); + }, + ({ data, pageSize }: { data: TimelineNonEcsData[][]; pageSize: number }) => + ({ rowIndex, columnId, Component }) => { + const { timelines } = useKibanaServices(); + + const pageRowIndex = getPageRowIndex(rowIndex, pageSize); + if (pageRowIndex >= data.length) { + return null; + } + + const value = getMappedNonEcsValue({ + data: data[pageRowIndex], + fieldName: columnId, + }); + + return ( + <> + {timelines.getHoverActions().getCopyButton({ + Component, + field: columnId, + isHoverAction: false, + ownFocus: false, + showTooltip: false, + value, + })} + + ); + }, + ({ data, pageSize }: { data: TimelineNonEcsData[][]; pageSize: number }) => + ({ rowIndex, columnId, Component }) => { + const { timelines } = useKibanaServices(); + + const pageRowIndex = getPageRowIndex(rowIndex, pageSize); + if (pageRowIndex >= data.length) { + return null; + } + + const value = getMappedNonEcsValue({ + data: data[pageRowIndex], + fieldName: columnId, + }); + + const dataProvider: DataProvider[] = useMemo( + () => + value?.map((x) => ({ + and: [], + enabled: true, + id: `${escapeDataProviderId(columnId)}-row-${rowIndex}-col-${columnId}-val-${x}`, + name: x, + excluded: false, + kqlQuery: '', + queryMatch: { + field: columnId, + value: x, + operator: IS_OPERATOR, + }, + })) ?? [], + [columnId, rowIndex, value] + ); + + return ( + <> + {timelines.getHoverActions().getAddToTimelineButton({ + Component, + dataProvider, + field: columnId, + ownFocus: false, + showTooltip: false, + })} + + ); + }, ({ - browserFields, - data, - timelineId, - pageSize, - }: { - browserFields: BrowserFields; - data: TimelineNonEcsData[][]; - timelineId: string; - pageSize: number; - }) => ({ rowIndex, columnId, Component }) => { - const [showTopN, setShowTopN] = useState(false); - const onClick = useCallback(() => setShowTopN(!showTopN), [showTopN]); - - const pageRowIndex = getPageRowIndex(rowIndex, pageSize); - if (pageRowIndex >= data.length) { - return null; - } - - const value = getMappedNonEcsValue({ - data: data[pageRowIndex], - fieldName: columnId, - }); - - const showButton = useMemo( - () => - allowTopN({ - browserField: getAllFieldsByName(browserFields)[columnId], - fieldName: columnId, - hideTopN: false, - }), - [columnId] - ); - - return showButton ? ( - - ) : null; - }, + browserFields, + data, + globalFilters, + timelineId, + pageSize, + }: { + browserFields: BrowserFields; + data: TimelineNonEcsData[][]; + globalFilters?: Filter[]; + timelineId: string; + pageSize: number; + }) => + ({ rowIndex, columnId, Component }) => { + const [showTopN, setShowTopN] = useState(false); + const onClick = useCallback(() => setShowTopN(!showTopN), [showTopN]); + + const pageRowIndex = getPageRowIndex(rowIndex, pageSize); + if (pageRowIndex >= data.length) { + return null; + } + + const value = getMappedNonEcsValue({ + data: data[pageRowIndex], + fieldName: columnId, + }); + + const showButton = useMemo( + () => + allowTopN({ + browserField: getAllFieldsByName(browserFields)[columnId], + fieldName: columnId, + hideTopN: false, + }), + [columnId] + ); + + return showButton ? ( + + ) : null; + }, ]; diff --git a/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts b/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts index 62d8628dc1592..df821c7ac5f6d 100644 --- a/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts +++ b/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts @@ -68,17 +68,19 @@ const mockUiSettings: Record = { }, }; -export const createUseUiSettingMock = () => (key: string, defaultValue?: unknown): unknown => { - const result = mockUiSettings[key]; +export const createUseUiSettingMock = + () => + (key: string, defaultValue?: unknown): unknown => { + const result = mockUiSettings[key]; - if (typeof result != null) return result; + if (typeof result != null) return result; - if (defaultValue != null) { - return defaultValue; - } + if (defaultValue != null) { + return defaultValue; + } - throw new TypeError(`Unexpected config key: ${key}`); -}; + throw new TypeError(`Unexpected config key: ${key}`); + }; export const createUseUiSetting$Mock = () => { const useUiSettingMock = createUseUiSettingMock(); @@ -99,7 +101,7 @@ export const createStartServicesMock = (): StartServices => { const locator = urlService.locators.create(new MlLocatorDefinition()); const fleet = fleetMock.createStartMock(); - return ({ + return { ...core, cases: { getAllCases: jest.fn(), @@ -147,7 +149,7 @@ export const createStartServicesMock = (): StartServices => { ml: { locator, }, - } as unknown) as StartServices; + } as unknown as StartServices; }; export const createWithKibanaMock = () => { diff --git a/x-pack/plugins/security_solution/public/common/mock/endpoint/app_root_provider.tsx b/x-pack/plugins/security_solution/public/common/mock/endpoint/app_root_provider.tsx index 023802a902675..86df9ac486fe8 100644 --- a/x-pack/plugins/security_solution/public/common/mock/endpoint/app_root_provider.tsx +++ b/x-pack/plugins/security_solution/public/common/mock/endpoint/app_root_provider.tsx @@ -36,12 +36,10 @@ export const AppRootProvider = memo<{ children, }) => { const isDarkMode = useObservable(uiSettings.get$('theme:darkMode')); - const services = useMemo(() => ({ http, notifications, application, data }), [ - application, - data, - http, - notifications, - ]); + const services = useMemo( + () => ({ http, notifications, application, data }), + [application, data, http, notifications] + ); return ( diff --git a/x-pack/plugins/security_solution/public/common/mock/endpoint/dependencies_start_mock.ts b/x-pack/plugins/security_solution/public/common/mock/endpoint/dependencies_start_mock.ts index 31d2573c81c77..947a26d48322b 100644 --- a/x-pack/plugins/security_solution/public/common/mock/endpoint/dependencies_start_mock.ts +++ b/x-pack/plugins/security_solution/public/common/mock/endpoint/dependencies_start_mock.ts @@ -42,7 +42,7 @@ export interface DepsStartMock { * Returns a mock of our app's depsStart (plugin start dependencies) */ export const depsStartMock: () => DepsStartMock = () => { - const dataMock: DataMock = (dataPluginMock.createStartContract() as unknown) as DataMock; + const dataMock: DataMock = dataPluginMock.createStartContract() as unknown as DataMock; dataMock.indexPatterns.getFieldsForWildcard = jest.fn(); dataMock.query.filterManager.setFilters = jest.fn(); dataMock.query.filterManager.getUpdates$ = jest.fn(() => { diff --git a/x-pack/plugins/security_solution/public/common/mock/endpoint/http_handler_mock_factory.ts b/x-pack/plugins/security_solution/public/common/mock/endpoint/http_handler_mock_factory.ts index 12a7de96ba0d1..6185be0663a9a 100644 --- a/x-pack/plugins/security_solution/public/common/mock/endpoint/http_handler_mock_factory.ts +++ b/x-pack/plugins/security_solution/public/common/mock/endpoint/http_handler_mock_factory.ts @@ -32,22 +32,21 @@ export type ResponseProvidersInterface< I extends Record = Record > = I; -type SingleResponseProvider< - F extends ResponseProviderCallback = ResponseProviderCallback -> = jest.MockedFunction & { - /** - * Delay responding to the HTTP call until this promise is resolved. Use it to introduce - * elongated delays in order to test intermediate UI states. - * - * @example - * apiMocks.responseProvider.someProvider.mockDelay - * // Delay this response by 1/2 second - * .mockImplementation( - * () => new Promise(r => setTimeout(r, 500)) - * ) - */ - mockDelay: jest.MockedFunction<() => Promise>; -}; +type SingleResponseProvider = + jest.MockedFunction & { + /** + * Delay responding to the HTTP call until this promise is resolved. Use it to introduce + * elongated delays in order to test intermediate UI states. + * + * @example + * apiMocks.responseProvider.someProvider.mockDelay + * // Delay this response by 1/2 second + * .mockImplementation( + * () => new Promise(r => setTimeout(r, 500)) + * ) + */ + mockDelay: jest.MockedFunction<() => Promise>; + }; /** * The interface for a `core.http` set of mocked API responses. @@ -67,11 +66,9 @@ interface MockedApi; - } - >; + responseProvider: Readonly<{ + [K in keyof R]: SingleResponseProvider; + }>; } type HttpMethods = keyof Pick< diff --git a/x-pack/plugins/security_solution/public/common/mock/news.ts b/x-pack/plugins/security_solution/public/common/mock/news.ts index 936a79beec312..50f50531c72a4 100644 --- a/x-pack/plugins/security_solution/public/common/mock/news.ts +++ b/x-pack/plugins/security_solution/public/common/mock/news.ts @@ -13,16 +13,14 @@ export const rawNewsApiResponse: RawNewsApiResponse = { { title: { en: 'Got SIEM Questions?' }, description: { - en: - "There's an awesome community of Elastic SIEM users out there. Join the discussion about configuring, learning, and using the Elastic SIEM app, and detecting threats!", + en: "There's an awesome community of Elastic SIEM users out there. Join the discussion about configuring, learning, and using the Elastic SIEM app, and detecting threats!", }, link_text: null, link_url: { en: 'https://discuss.elastic.co/c/security?blade=securitysolutionfeed' }, languages: null, badge: { en: '7.6' }, image_url: { - en: - 'https://aws1.discourse-cdn.com/elastic/original/3X/f/8/f8c3d0b9971cfcd0be349d973aa5799f71d280cc.png?blade=securitysolutionfeed', + en: 'https://aws1.discourse-cdn.com/elastic/original/3X/f/8/f8c3d0b9971cfcd0be349d973aa5799f71d280cc.png?blade=securitysolutionfeed', }, publish_on: new Date('2020-01-01T00:00:00'), expire_on: new Date('2020-12-31T00:00:00'), @@ -31,19 +29,16 @@ export const rawNewsApiResponse: RawNewsApiResponse = { { title: { en: 'Elastic Security 7.5.0 released' }, description: { - en: - 'Elastic Security combines the threat hunting and analytics of Elastic SIEM with the prevention and response provided by Elastic Endpoint Security.', + en: 'Elastic Security combines the threat hunting and analytics of Elastic SIEM with the prevention and response provided by Elastic Endpoint Security.', }, link_text: null, link_url: { - en: - 'https://www.elastic.co/blog/elastic-security-7-5-0-released?blade=securitysolutionfeed', + en: 'https://www.elastic.co/blog/elastic-security-7-5-0-released?blade=securitysolutionfeed', }, languages: null, badge: { en: '7.5' }, image_url: { - en: - 'https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1caa35177420c61b/5d0d0394d8ff351753cbf2c5/illustrated-screenshot-hero-siem.png?blade=securitysolutionfeed', + en: 'https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1caa35177420c61b/5d0d0394d8ff351753cbf2c5/illustrated-screenshot-hero-siem.png?blade=securitysolutionfeed', }, publish_on: new Date('2019-12-02T00:00:00'), expire_on: new Date('2020-12-31T00:00:00'), @@ -52,19 +47,16 @@ export const rawNewsApiResponse: RawNewsApiResponse = { { title: { en: 'Elastic Endpoint Security Overview Webinar' }, description: { - en: - 'At Elastic, we’re bringing endpoint protection and SIEM together into the same experience to streamline how you secure your organization.', + en: 'At Elastic, we’re bringing endpoint protection and SIEM together into the same experience to streamline how you secure your organization.', }, link_text: null, link_url: { - en: - 'https://www.elastic.co/webinars/elastic-endpoint-security-overview-security-starts-at-the-endpoint?blade=securitysolutionfeed', + en: 'https://www.elastic.co/webinars/elastic-endpoint-security-overview-security-starts-at-the-endpoint?blade=securitysolutionfeed', }, languages: null, badge: { en: '7.5' }, image_url: { - en: - 'https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd0eb8689eafe398a/5d970ecc1970e80e85277925/illustration-endpoint-hero.png?blade=securitysolutionfeed', + en: 'https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd0eb8689eafe398a/5d970ecc1970e80e85277925/illustration-endpoint-hero.png?blade=securitysolutionfeed', }, publish_on: new Date('2019-11-17T00:00:00'), expire_on: new Date('2020-12-31T00:00:00'), @@ -73,19 +65,16 @@ export const rawNewsApiResponse: RawNewsApiResponse = { { title: { en: 'Trying Elastic SIEM at Home?' }, description: { - en: - 'For small businesses and homes, having access to effective security analytics can come at a high cost of either time or money. Well, until now!', + en: 'For small businesses and homes, having access to effective security analytics can come at a high cost of either time or money. Well, until now!', }, link_text: null, link_url: { - en: - 'https://www.elastic.co/blog/elastic-siem-for-small-business-and-home-1-getting-started?blade=securitysolutionfeed', + en: 'https://www.elastic.co/blog/elastic-siem-for-small-business-and-home-1-getting-started?blade=securitysolutionfeed', }, languages: null, badge: { en: '7.5' }, image_url: { - en: - 'https://images.contentstack.io/v3/assets/bltefdd0b53724fa2ce/blt024c26b7636cb24f/5daf4e293a326d6df6c0e025/home-siem-blog-1-map.jpg?blade=securitysolutionfeed', + en: 'https://images.contentstack.io/v3/assets/bltefdd0b53724fa2ce/blt024c26b7636cb24f/5daf4e293a326d6df6c0e025/home-siem-blog-1-map.jpg?blade=securitysolutionfeed', }, publish_on: new Date('2019-10-24T00:00:00'), expire_on: new Date('2020-12-31T00:00:00'), @@ -94,19 +83,16 @@ export const rawNewsApiResponse: RawNewsApiResponse = { { title: { en: 'Introducing Elastic Endpoint Security' }, description: { - en: - 'Elastic is excited to announce the introduction of Elastic Endpoint Security, based on Elastic’s acquisition of Endgame, a pioneer and industry-recognized leader in endpoint threat prevention, detection, and response.', + en: 'Elastic is excited to announce the introduction of Elastic Endpoint Security, based on Elastic’s acquisition of Endgame, a pioneer and industry-recognized leader in endpoint threat prevention, detection, and response.', }, link_text: null, link_url: { - en: - 'https://www.elastic.co/blog/introducing-elastic-endpoint-security?blade=securitysolutionfeed', + en: 'https://www.elastic.co/blog/introducing-elastic-endpoint-security?blade=securitysolutionfeed', }, languages: null, badge: { en: '7.5' }, image_url: { - en: - 'https://images.contentstack.io/v3/assets/bltefdd0b53724fa2ce/blt1f87637fb7870298/5d9fe27bf8ca980f8717f6f8/screenshot-resolver-trickbot-enrichments-showing-defender-shutdown-endgame-2-optimized.png?blade=securitysolutionfeed', + en: 'https://images.contentstack.io/v3/assets/bltefdd0b53724fa2ce/blt1f87637fb7870298/5d9fe27bf8ca980f8717f6f8/screenshot-resolver-trickbot-enrichments-showing-defender-shutdown-endgame-2-optimized.png?blade=securitysolutionfeed', }, publish_on: new Date('2019-10-15T00:00:00'), expire_on: new Date('2020-03-01T00:00:00'), @@ -115,19 +101,16 @@ export const rawNewsApiResponse: RawNewsApiResponse = { { title: { en: 'What is Elastic Common Schema (ECS)?' }, description: { - en: - 'Elastic SIEM is powered by Elastic Common Schema. With ECS, analytics content such as dashboards, rules, and machine learning jobs can be applied more broadly, searches can be crafted more narrowly, and field names are easier to remember.', + en: 'Elastic SIEM is powered by Elastic Common Schema. With ECS, analytics content such as dashboards, rules, and machine learning jobs can be applied more broadly, searches can be crafted more narrowly, and field names are easier to remember.', }, link_text: null, link_url: { - en: - 'https://www.elastic.co/blog/introducing-the-elastic-common-schema?blade=securitysolutionfeed', + en: 'https://www.elastic.co/blog/introducing-the-elastic-common-schema?blade=securitysolutionfeed', }, languages: null, badge: { en: '7.0' }, image_url: { - en: - 'https://images.contentstack.io/v3/assets/bltefdd0b53724fa2ce/blt71256f06dc672546/5c98d595975fd58f4d12646d/ecs-intro-dashboard-1360.jpg?blade=securitysolutionfeed', + en: 'https://images.contentstack.io/v3/assets/bltefdd0b53724fa2ce/blt71256f06dc672546/5c98d595975fd58f4d12646d/ecs-intro-dashboard-1360.jpg?blade=securitysolutionfeed', }, publish_on: new Date('2019-02-13T00:00:00'), expire_on: new Date('2020-12-31T00:00:00'), 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 0c227ac639569..975487bb2b384 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 @@ -76,7 +76,7 @@ const TestProvidersWithPrivilegesComponent: React.FC = ({ ({ eui: euiDarkVars, darkMode: true })}> {children} @@ -94,7 +94,7 @@ export const useFormFieldMock = (options?: Partial>): FieldHook return { path: 'path', type: 'type', - value: ('mockedValue' as unknown) as T, + value: 'mockedValue' as unknown as T, isPristine: false, isDirty: false, isModified: false, diff --git a/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts b/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts index b601e531d4991..80899324fe92f 100644 --- a/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts +++ b/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts @@ -1631,8 +1631,7 @@ export const mockTimelineResults: OpenTimelineResult[] = [ noteIds: ['noteId4'], notes: [ { - note: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Magna ac placerat vestibulum lectus. Morbi tincidunt ornare massa eget egestas purus. Quis varius quam quisque id diam. Nulla pellentesque dignissim enim sit amet.', + note: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Magna ac placerat vestibulum lectus. Morbi tincidunt ornare massa eget egestas purus. Quis varius quam quisque id diam. Nulla pellentesque dignissim enim sit amet.', savedObjectId: 'noteId1', updated: 1553700738 * 1000, updatedBy: 'alice', @@ -1644,106 +1643,91 @@ export const mockTimelineResults: OpenTimelineResult[] = [ updatedBy: 'alice', }, { - note: - 'Cras tincidunt lobortis feugiat vivamus at augue eget arcu dictum. Morbi quis commodo odio aenean sed. Sit amet aliquam id diam. Enim nec dui nunc mattis enim ut tellus elementum.', + note: 'Cras tincidunt lobortis feugiat vivamus at augue eget arcu dictum. Morbi quis commodo odio aenean sed. Sit amet aliquam id diam. Enim nec dui nunc mattis enim ut tellus elementum.', savedObjectId: 'noteId3', updated: 1553700740 * 1000, updatedBy: 'bob', }, { - note: - 'Lobortis elementum nibh tellus molestie nunc non blandit massa enim. Vulputate ut pharetra sit amet aliquam id diam.', + note: 'Lobortis elementum nibh tellus molestie nunc non blandit massa enim. Vulputate ut pharetra sit amet aliquam id diam.', savedObjectId: 'noteId4', updated: 1553700741 * 1000, updatedBy: 'alice', }, { - note: - 'Lobortis elementum nibh tellus molestie nunc non blandit massa enim. Vulputate ut pharetra sit amet aliquam id diam.', + note: 'Lobortis elementum nibh tellus molestie nunc non blandit massa enim. Vulputate ut pharetra sit amet aliquam id diam.', savedObjectId: 'noteId5', updated: 1553700742 * 1000, updatedBy: 'alice', }, { - note: - 'Lobortis elementum nibh tellus molestie nunc non blandit massa enim. Vulputate ut pharetra sit amet aliquam id diam.', + note: 'Lobortis elementum nibh tellus molestie nunc non blandit massa enim. Vulputate ut pharetra sit amet aliquam id diam.', savedObjectId: 'noteId6', updated: 1553700743 * 1000, updatedBy: 'alice', }, { - note: - 'Lobortis elementum nibh tellus molestie nunc non blandit massa enim. Vulputate ut pharetra sit amet aliquam id diam.', + note: 'Lobortis elementum nibh tellus molestie nunc non blandit massa enim. Vulputate ut pharetra sit amet aliquam id diam.', savedObjectId: 'noteId7', updated: 1553700744 * 1000, updatedBy: 'alice', }, { - note: - 'Lobortis elementum nibh tellus molestie nunc non blandit massa enim. Vulputate ut pharetra sit amet aliquam id diam.', + note: 'Lobortis elementum nibh tellus molestie nunc non blandit massa enim. Vulputate ut pharetra sit amet aliquam id diam.', savedObjectId: 'noteId8', updated: 1553700745 * 1000, updatedBy: 'alice', }, { - note: - 'Lobortis elementum nibh tellus molestie nunc non blandit massa enim. Vulputate ut pharetra sit amet aliquam id diam.', + note: 'Lobortis elementum nibh tellus molestie nunc non blandit massa enim. Vulputate ut pharetra sit amet aliquam id diam.', savedObjectId: 'noteId9', updated: 1553700746 * 1000, updatedBy: 'alice', }, { - note: - 'Lobortis elementum nibh tellus molestie nunc non blandit massa enim. Vulputate ut pharetra sit amet aliquam id diam.', + note: 'Lobortis elementum nibh tellus molestie nunc non blandit massa enim. Vulputate ut pharetra sit amet aliquam id diam.', savedObjectId: 'noteId10', updated: 1553700747 * 1000, updatedBy: 'alice', }, { - note: - 'Lobortis elementum nibh tellus molestie nunc non blandit massa enim. Vulputate ut pharetra sit amet aliquam id diam.', + note: 'Lobortis elementum nibh tellus molestie nunc non blandit massa enim. Vulputate ut pharetra sit amet aliquam id diam.', savedObjectId: 'noteId11', updated: 1553700748 * 1000, updatedBy: 'alice', }, { - note: - 'Lobortis elementum nibh tellus molestie nunc non blandit massa enim. Vulputate ut pharetra sit amet aliquam id diam.', + note: 'Lobortis elementum nibh tellus molestie nunc non blandit massa enim. Vulputate ut pharetra sit amet aliquam id diam.', savedObjectId: 'noteId12', updated: 1553700749 * 1000, updatedBy: 'alice', }, { - note: - 'Lobortis elementum nibh tellus molestie nunc non blandit massa enim. Vulputate ut pharetra sit amet aliquam id diam.', + note: 'Lobortis elementum nibh tellus molestie nunc non blandit massa enim. Vulputate ut pharetra sit amet aliquam id diam.', savedObjectId: 'noteId13', updated: 1553700750 * 1000, updatedBy: 'alice', }, { - note: - 'Lobortis elementum nibh tellus molestie nunc non blandit massa enim. Vulputate ut pharetra sit amet aliquam id diam.', + note: 'Lobortis elementum nibh tellus molestie nunc non blandit massa enim. Vulputate ut pharetra sit amet aliquam id diam.', savedObjectId: 'noteId14', updated: 1553700751 * 1000, updatedBy: 'alice', }, { - note: - 'Lobortis elementum nibh tellus molestie nunc non blandit massa enim. Vulputate ut pharetra sit amet aliquam id diam.', + note: 'Lobortis elementum nibh tellus molestie nunc non blandit massa enim. Vulputate ut pharetra sit amet aliquam id diam.', savedObjectId: 'noteId15', updated: 1553700752 * 1000, updatedBy: 'alice', }, { - note: - 'Lobortis elementum nibh tellus molestie nunc non blandit massa enim. Vulputate ut pharetra sit amet aliquam id diam.', + note: 'Lobortis elementum nibh tellus molestie nunc non blandit massa enim. Vulputate ut pharetra sit amet aliquam id diam.', savedObjectId: 'noteId16', updated: 1553700753 * 1000, updatedBy: 'alice', }, { - note: - 'Lobortis elementum nibh tellus molestie nunc non blandit massa enim. Vulputate ut pharetra sit amet aliquam id diam.', + note: 'Lobortis elementum nibh tellus molestie nunc non blandit massa enim. Vulputate ut pharetra sit amet aliquam id diam.', savedObjectId: 'noteId17', updated: 1553700753 * 1000, updatedBy: 'alice', @@ -1836,8 +1820,7 @@ export const mockTimelineResults: OpenTimelineResult[] = [ updatedBy: 'olivia', }, { - note: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', + note: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', savedObjectId: 'noteId6', updated: 1543604192 * 1000, updatedBy: 'olivia', @@ -1874,8 +1857,7 @@ export const mockTimelineResults: OpenTimelineResult[] = [ noteIds: [], notes: [ { - note: - 'Lobortis elementum nibh tellus molestie nunc non blandit massa enim. Vulputate ut pharetra sit amet aliquam id diam.', + note: 'Lobortis elementum nibh tellus molestie nunc non blandit massa enim. Vulputate ut pharetra sit amet aliquam id diam.', savedObjectId: 'noteId7', updated: 1541677314 * 1000, updatedBy: 'charlotte', @@ -2103,8 +2085,7 @@ export const defaultTimelineProps: CreateTimelineProps = { and: [], enabled: true, excluded: false, - id: - 'send-alert-to-timeline-action-default-draggable-event-details-value-formatted-field-value-timeline-1-alert-id-1', + id: 'send-alert-to-timeline-action-default-draggable-event-details-value-formatted-field-value-timeline-1-alert-id-1', kqlQuery: '', name: '1', queryMatch: { field: '_id', operator: ':', value: '1' }, diff --git a/x-pack/plugins/security_solution/public/common/store/app/actions.ts b/x-pack/plugins/security_solution/public/common/store/app/actions.ts index a262b053d706c..7e2ebed62ae16 100644 --- a/x-pack/plugins/security_solution/public/common/store/app/actions.ts +++ b/x-pack/plugins/security_solution/public/common/store/app/actions.ts @@ -15,9 +15,8 @@ export const updateNote = actionCreator<{ note: Note }>('UPDATE_NOTE'); export const addNotes = actionCreator<{ notes: Note[] }>('ADD_NOTE'); -export const addError = actionCreator<{ id: string; title: string; message: string[] }>( - 'ADD_ERRORS' -); +export const addError = + actionCreator<{ id: string; title: string; message: string[] }>('ADD_ERRORS'); export const removeError = actionCreator<{ id: string }>('REMOVE_ERRORS'); diff --git a/x-pack/plugins/security_solution/public/common/store/inputs/actions.ts b/x-pack/plugins/security_solution/public/common/store/inputs/actions.ts index 5843f094edb38..1e38b76909ca4 100644 --- a/x-pack/plugins/security_solution/public/common/store/inputs/actions.ts +++ b/x-pack/plugins/security_solution/public/common/store/inputs/actions.ts @@ -67,9 +67,8 @@ export const setInspectionParameter = actionCreator<{ export const deleteAllQuery = actionCreator<{ id: InputsModelId }>('DELETE_ALL_QUERY'); -export const toggleTimelineLinkTo = actionCreator<{ linkToId: InputsModelId }>( - 'TOGGLE_TIMELINE_LINK_TO' -); +export const toggleTimelineLinkTo = + actionCreator<{ linkToId: InputsModelId }>('TOGGLE_TIMELINE_LINK_TO'); export const removeTimelineLinkTo = actionCreator('REMOVE_TIMELINE_LINK_TO'); export const addTimelineLinkTo = actionCreator<{ linkToId: InputsModelId }>('ADD_TIMELINE_LINK_TO'); diff --git a/x-pack/plugins/security_solution/public/common/store/sourcerer/actions.ts b/x-pack/plugins/security_solution/public/common/store/sourcerer/actions.ts index 7533b052add39..5162776d3ddf5 100644 --- a/x-pack/plugins/security_solution/public/common/store/sourcerer/actions.ts +++ b/x-pack/plugins/security_solution/public/common/store/sourcerer/actions.ts @@ -22,9 +22,8 @@ export const setIndexPatternsList = actionCreator<{ configIndexPatterns: string[]; }>('SET_INDEX_PATTERNS_LIST'); -export const setSignalIndexName = actionCreator<{ signalIndexName: string }>( - 'SET_SIGNAL_INDEX_NAME' -); +export const setSignalIndexName = + actionCreator<{ signalIndexName: string }>('SET_SIGNAL_INDEX_NAME'); export const setSourcererScopeLoading = actionCreator<{ id: SourcererScopeName; loading: boolean }>( 'SET_SOURCERER_SCOPE_LOADING' diff --git a/x-pack/plugins/security_solution/public/common/store/sourcerer/selectors.test.ts b/x-pack/plugins/security_solution/public/common/store/sourcerer/selectors.test.ts index dd608138ef9f0..1edda4f997cdf 100644 --- a/x-pack/plugins/security_solution/public/common/store/sourcerer/selectors.test.ts +++ b/x-pack/plugins/security_solution/public/common/store/sourcerer/selectors.test.ts @@ -14,27 +14,28 @@ describe('Sourcerer selectors', () => { describe('getSourcererScopeSelector', () => { it('Should exclude elastic cloud alias when selected patterns include "logs-*" as an alias', () => { const mapStateToProps = getSourcererScopeSelector(); - expect( - mapStateToProps(mockGlobalState, SourcererScopeName.default).selectedPatterns - ).toEqual([ - 'apm-*-transaction*', - 'auditbeat-*', - 'endgame-*', - 'filebeat-*', - 'logs-*', - 'packetbeat-*', - 'traces-apm*', - 'winlogbeat-*', - '-*elastic-cloud-logs-*', - ]); + expect(mapStateToProps(mockGlobalState, SourcererScopeName.default).selectedPatterns).toEqual( + [ + 'apm-*-transaction*', + 'auditbeat-*', + 'endgame-*', + 'filebeat-*', + 'logs-*', + 'packetbeat-*', + 'traces-apm*', + 'winlogbeat-*', + '-*elastic-cloud-logs-*', + ] + ); }); it('Should NOT exclude elastic cloud alias when selected patterns does NOT include "logs-*" as an alias', () => { const mapStateToProps = getSourcererScopeSelector(); const myMockGlobalState = cloneDeep(mockGlobalState); - myMockGlobalState.sourcerer.sourcererScopes.default.selectedPatterns = myMockGlobalState.sourcerer.sourcererScopes.default.selectedPatterns.filter( - (index) => !index.includes('logs-*') - ); + myMockGlobalState.sourcerer.sourcererScopes.default.selectedPatterns = + myMockGlobalState.sourcerer.sourcererScopes.default.selectedPatterns.filter( + (index) => !index.includes('logs-*') + ); expect( mapStateToProps(myMockGlobalState, SourcererScopeName.default).selectedPatterns ).toEqual([ diff --git a/x-pack/plugins/security_solution/public/common/store/types.ts b/x-pack/plugins/security_solution/public/common/store/types.ts index 6943b4cf73117..842af9af1d286 100644 --- a/x-pack/plugins/security_solution/public/common/store/types.ts +++ b/x-pack/plugins/security_solution/public/common/store/types.ts @@ -148,8 +148,6 @@ export type CreateStructuredSelector = < SelectorMap extends { [key: string]: (...args: never[]) => unknown } >( selectorMap: SelectorMap -) => ( - state: SelectorMap[keyof SelectorMap] extends (state: infer S) => unknown ? S : never -) => { +) => (state: SelectorMap[keyof SelectorMap] extends (state: infer S) => unknown ? S : never) => { [Key in keyof SelectorMap]: ReturnType; }; diff --git a/x-pack/plugins/security_solution/public/common/utils/default_date_settings.test.ts b/x-pack/plugins/security_solution/public/common/utils/default_date_settings.test.ts index 2c5f9d2dda471..2963594791304 100644 --- a/x-pack/plugins/security_solution/public/common/utils/default_date_settings.test.ts +++ b/x-pack/plugins/security_solution/public/common/utils/default_date_settings.test.ts @@ -498,17 +498,21 @@ describe('getIntervalSettings', () => { beforeEach(() => { // Disable momentJS deprecation warning and it looks like it is not typed either so // we have to disable the type as well and cannot extend it easily. - ((moment as unknown) as { - suppressDeprecationWarnings: boolean; - }).suppressDeprecationWarnings = true; + ( + moment as unknown as { + suppressDeprecationWarnings: boolean; + } + ).suppressDeprecationWarnings = true; }); afterEach(() => { // Re-enable momentJS deprecation warning and it looks like it is not typed either so // we have to disable the type as well and cannot extend it easily. - ((moment as unknown) as { - suppressDeprecationWarnings: boolean; - }).suppressDeprecationWarnings = false; + ( + moment as unknown as { + suppressDeprecationWarnings: boolean; + } + ).suppressDeprecationWarnings = false; }); test('should return the first value if it is ok', () => { const value = parseDateWithDefault( diff --git a/x-pack/plugins/security_solution/public/common/utils/validators/index.ts b/x-pack/plugins/security_solution/public/common/utils/validators/index.ts index 178ae3b0f716e..6d85e1fdd981e 100644 --- a/x-pack/plugins/security_solution/public/common/utils/validators/index.ts +++ b/x-pack/plugins/security_solution/public/common/utils/validators/index.ts @@ -9,7 +9,8 @@ import { isEmpty } from 'lodash/fp'; export * from './is_endpoint_host_isolated'; -const urlExpression = /(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})/gi; +const urlExpression = + /(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})/gi; export const isUrlInvalid = (url: string | null | undefined) => { if (!isEmpty(url) && url != null && url.match(urlExpression) == null) { diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx index 6bd902658c8e4..29324d186784e 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx @@ -38,9 +38,8 @@ export const AlertsCountPanel = memo( // create a unique, but stable (across re-renders) query id const uniqueQueryId = useMemo(() => `${DETECTIONS_ALERTS_COUNT_ID}-${uuid.v4()}`, []); - const [selectedStackByOption, setSelectedStackByOption] = useState( - DEFAULT_STACK_BY_FIELD - ); + const [selectedStackByOption, setSelectedStackByOption] = + useState(DEFAULT_STACK_BY_FIELD); // TODO: Once we are past experimental phase this code should be removed // const fetchMethod = useIsExperimentalFeatureEnabled('ruleRegistryEnabled') diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx index 2182ed7da0c4f..b6bdab405acfc 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx @@ -247,10 +247,10 @@ export const AlertsHistogramPanel = memo( } }, [showLinkToAlerts, goToDetectionEngine, formatUrl]); - const titleText = useMemo(() => (onlyField == null ? title : i18n.TOP(onlyField)), [ - onlyField, - title, - ]); + const titleText = useMemo( + () => (onlyField == null ? title : i18n.TOP(onlyField)), + [onlyField, title] + ); return ( diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx index e7a8ba91cff8f..261ac8cfee1a6 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx @@ -372,8 +372,7 @@ describe('alert actions', () => { and: [], enabled: true, excluded: false, - id: - 'send-alert-to-timeline-action-default-draggable-event-details-value-formatted-field-value-timeline-1-alert-id-my-group-id', + id: 'send-alert-to-timeline-action-default-draggable-event-details-value-formatted-field-value-timeline-1-alert-id-my-group-id', kqlQuery: '', name: '1', queryMatch: { field: 'signal.group.id', operator: ':', value: 'my-group-id' }, diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/helpers.test.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_table/helpers.test.ts index c296b75a0a253..44d4c62c384a4 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/helpers.test.ts @@ -88,13 +88,13 @@ describe('helpers', () => { }); test('it should trace an error if the value is not a string', () => { - const mockConsole: Console = ({ trace: jest.fn() } as unknown) as Console; + const mockConsole: Console = { trace: jest.fn() } as unknown as Console; const value = getStringArray( 'a', [ { field: 'a', - values: (5 as unknown) as string[], + values: 5 as unknown as string[], isObjectArray: false, originalValue: 'zed', }, @@ -102,9 +102,7 @@ describe('helpers', () => { mockConsole ); expect(value).toEqual([]); - expect( - mockConsole.trace - ).toHaveBeenCalledWith( + expect(mockConsole.trace).toHaveBeenCalledWith( 'Data type that is not a string or string array detected:', 5, 'when trying to access field:', @@ -115,13 +113,13 @@ describe('helpers', () => { }); test('it should trace an error if the value is an array of mixed values', () => { - const mockConsole: Console = ({ trace: jest.fn() } as unknown) as Console; + const mockConsole: Console = { trace: jest.fn() } as unknown as Console; const value = getStringArray( 'a', [ { field: 'a', - values: (['hi', 5] as unknown) as string[], + values: ['hi', 5] as unknown as string[], isObjectArray: false, originalValue: 'zed', }, @@ -129,9 +127,7 @@ describe('helpers', () => { mockConsole ); expect(value).toEqual([]); - expect( - mockConsole.trace - ).toHaveBeenCalledWith( + expect(mockConsole.trace).toHaveBeenCalledWith( 'Data type that is not a string or string array detected:', ['hi', 5], 'when trying to access field:', @@ -175,7 +171,7 @@ describe('helpers', () => { const dupTimelineDetails = [...mockTimelineDetails]; dupTimelineDetails[0] = { ...dupTimelineDetails[0], - values: ('apache' as unknown) as string[], + values: 'apache' as unknown as string[], }; // very unsafe cast for this test case const replacement = replaceTemplateFieldFromQuery( 'host.name: *', @@ -227,7 +223,7 @@ describe('helpers', () => { const dupTimelineDetails = [...mockTimelineDetails]; dupTimelineDetails[0] = { ...dupTimelineDetails[0], - values: ('apache' as unknown) as string[], + values: 'apache' as unknown as string[], }; // very unsafe cast for this test case const replacement = replaceTemplateFieldFromQuery( 'host.name: *', @@ -352,7 +348,7 @@ describe('helpers', () => { const dupTimelineDetails = [...mockTimelineDetails]; dupTimelineDetails[0] = { ...dupTimelineDetails[0], - values: ('apache' as unknown) as string[], + values: 'apache' as unknown as string[], }; // very unsafe cast for this test case const mockDataProvider: DataProvider = mockDataProviders[0]; mockDataProvider.queryMatch.field = 'host.name'; @@ -477,7 +473,7 @@ describe('helpers', () => { const dupTimelineDetails = [...mockTimelineDetails]; dupTimelineDetails[0] = { ...dupTimelineDetails[0], - values: ('apache' as unknown) as string[], + values: 'apache' as unknown as string[], }; // very unsafe cast for this test case const mockDataProvider: DataProvider = mockDataProviders[0]; mockDataProvider.queryMatch.field = 'host.name'; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx index 3a815468932f0..fc8dd4b024fd9 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx @@ -129,11 +129,8 @@ const AlertContextMenuComponent: React.FC getManageTimeline(state, TimelineId.active ?? '') ); - const filterManager = useMemo(() => activeFilterManager ?? filterManagerBackup, [ - activeFilterManager, - filterManagerBackup, - ]); + const filterManager = useMemo( + () => activeFilterManager ?? filterManagerBackup, + [activeFilterManager, filterManagerBackup] + ); const updateTimelineIsLoading = useCallback( (payload) => dispatch(timelineActions.updateIsLoading(payload)), diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.test.tsx index e13f95e9d72ef..62e787952aa73 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.test.tsx @@ -142,11 +142,11 @@ describe('helpers', () => { filterManager: mockFilterManager, query: mockQueryBarWithFilters.query, savedId: mockQueryBarWithFilters.saved_id, - indexPatterns: ({ + indexPatterns: { fields: [{ name: 'event.category', type: 'test type' }], title: 'test title', getFormatterForField: () => ({ convert: (val: unknown) => val }), - } as unknown) as IndexPattern, + } as unknown as IndexPattern, }); const wrapper = shallow(result[0].description as React.ReactElement); const filterLabelComponent = wrapper.find(esFilters.FilterLabel).at(0); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.tsx index 17162a2206fc3..305e0fcd46ef8 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.tsx @@ -50,11 +50,11 @@ const NoteDescriptionContainer = styled(EuiFlexItem)` export const isNotEmptyArray = (values: string[]) => !isEmpty(values.join('')); -const EuiBadgeWrap = (styled(EuiBadge)` +const EuiBadgeWrap = styled(EuiBadge)` .euiBadge__text { white-space: pre-wrap !important; } -` as unknown) as typeof EuiBadge; +` as unknown as typeof EuiBadge; const Query = styled.div` white-space: pre-wrap; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.tsx index 90568e28793a3..281ef8c0f62ac 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.tsx @@ -49,9 +49,8 @@ const PrePackagedRulesPromptComponent: React.FC = ( [navigateTo] ); - const [ - { isSignalIndexExists, isAuthenticated, hasEncryptionKey, canUserCRUD, hasIndexWrite }, - ] = useUserData(); + const [{ isSignalIndexExists, isAuthenticated, hasEncryptionKey, canUserCRUD, hasIndexWrite }] = + useUserData(); const { getLoadPrebuiltRulesAndTemplatesButton } = usePrePackagedRules({ canUserCRUD, diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.test.tsx index 12923609db266..460f984202327 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.test.tsx @@ -48,7 +48,7 @@ jest.mock('../../../../timelines/containers/all', () => { describe('QueryBarDefineRule', () => { beforeEach(() => { - ((useGetAllTimeline as unknown) as jest.Mock).mockReturnValue({ + (useGetAllTimeline as unknown as jest.Mock).mockReturnValue({ fetchAllTimeline: jest.fn(), timelines: getAllTimeline('', mockOpenTimelineQueryResults.timeline ?? []), loading: false, diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/custom_histogram.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/custom_histogram.tsx index 5977467b45c9c..de3ef136afe5c 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/custom_histogram.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/custom_histogram.tsx @@ -48,10 +48,10 @@ export const PreviewCustomQueryHistogram = ({ } }, [setQuery, inspect, isLoading, isInitializing, refetch]); - const barConfig = useMemo((): ChartSeriesConfigs => getHistogramConfig(to, from, true), [ - from, - to, - ]); + const barConfig = useMemo( + (): ChartSeriesConfigs => getHistogramConfig(to, from, true), + [from, to] + ); const subtitle = useMemo( (): string => diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/reducer.ts b/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/reducer.ts index afddca63afcc6..0c5f288cbf410 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/reducer.ts +++ b/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/reducer.ts @@ -67,99 +67,101 @@ export type Action = type: 'setToFrom'; }; -export const queryPreviewReducer = () => (state: State, action: Action): State => { - switch (action.type) { - case 'setQueryInfo': { - if (action.queryBar != null) { - const { queryString, language, filters, queryFilter } = getInfoFromQueryBar( - action.queryBar, - action.index, - action.ruleType - ); +export const queryPreviewReducer = + () => + (state: State, action: Action): State => { + switch (action.type) { + case 'setQueryInfo': { + if (action.queryBar != null) { + const { queryString, language, filters, queryFilter } = getInfoFromQueryBar( + action.queryBar, + action.index, + action.ruleType + ); + + return { + ...state, + queryString, + language, + filters, + queryFilter, + showHistogram: false, + }; + } return { ...state, - queryString, - language, - filters, - queryFilter, showHistogram: false, }; } + case 'setTimeframeSelect': { + return { + ...state, + timeframe: action.timeframe, + showHistogram: false, + warnings: [], + }; + } + case 'setResetRuleTypeChange': { + const showNonEqlHist = + action.ruleType === 'query' || + action.ruleType === 'saved_query' || + (action.ruleType === 'threshold' && !state.thresholdFieldExists); - return { - ...state, - showHistogram: false, - }; - } - case 'setTimeframeSelect': { - return { - ...state, - timeframe: action.timeframe, - showHistogram: false, - warnings: [], - }; - } - case 'setResetRuleTypeChange': { - const showNonEqlHist = - action.ruleType === 'query' || - action.ruleType === 'saved_query' || - (action.ruleType === 'threshold' && !state.thresholdFieldExists); - - return { - ...state, - showHistogram: false, - timeframe: 'h', - timeframeOptions: getTimeframeOptions(action.ruleType), - showNonEqlHistogram: showNonEqlHist, - warnings: [], - }; - } - case 'setWarnings': { - return { - ...state, - warnings: action.warnings, - }; - } - case 'setShowHistogram': { - return { - ...state, - showHistogram: action.show, - }; - } - case 'setThresholdQueryVals': { - const thresholdField = - action.threshold != null && - action.threshold.field != null && - action.threshold.field.length > 0 && - action.threshold.field.every((field) => field.trim() !== ''); - const showNonEqlHist = - action.ruleType === 'query' || - action.ruleType === 'saved_query' || - (action.ruleType === 'threshold' && !thresholdField); + return { + ...state, + showHistogram: false, + timeframe: 'h', + timeframeOptions: getTimeframeOptions(action.ruleType), + showNonEqlHistogram: showNonEqlHist, + warnings: [], + }; + } + case 'setWarnings': { + return { + ...state, + warnings: action.warnings, + }; + } + case 'setShowHistogram': { + return { + ...state, + showHistogram: action.show, + }; + } + case 'setThresholdQueryVals': { + const thresholdField = + action.threshold != null && + action.threshold.field != null && + action.threshold.field.length > 0 && + action.threshold.field.every((field) => field.trim() !== ''); + const showNonEqlHist = + action.ruleType === 'query' || + action.ruleType === 'saved_query' || + (action.ruleType === 'threshold' && !thresholdField); - return { - ...state, - thresholdFieldExists: thresholdField, - showNonEqlHistogram: showNonEqlHist, - showHistogram: false, - warnings: [], - }; - } - case 'setToFrom': { - return { - ...state, - fromTime: formatDate('now'), - toTime: formatDate(`now-1${state.timeframe}`), - }; - } - case 'setNoiseWarning': { - return { - ...state, - warnings: [...state.warnings, i18n.QUERY_PREVIEW_NOISE_WARNING], - }; + return { + ...state, + thresholdFieldExists: thresholdField, + showNonEqlHistogram: showNonEqlHist, + showHistogram: false, + warnings: [], + }; + } + case 'setToFrom': { + return { + ...state, + fromTime: formatDate('now'), + toTime: formatDate(`now-1${state.timeframe}`), + }; + } + case 'setNoiseWarning': { + return { + ...state, + warnings: [...state.warnings, i18n.QUERY_PREVIEW_NOISE_WARNING], + }; + } + default: + return state; } - default: - return state; - } -}; + }; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.test.tsx index 51c2cad069d7d..1793b31197f7d 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.test.tsx @@ -10,7 +10,7 @@ import { mount, shallow } from 'enzyme'; import { ThemeProvider } from 'styled-components'; import { act } from '@testing-library/react'; -import { stubIndexPattern } from 'src/plugins/data/common/index_patterns/index_pattern.stub'; +import { stubIndexPattern } from 'src/plugins/data/common/stubs'; import { StepAboutRule } from '.'; import { useFetchIndex } from '../../../../common/containers/source'; import { mockAboutStepRule } from '../../../pages/detection_engine/rules/all/__mocks__/mock'; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/schema.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/schema.tsx index a697d922eda97..58202929c49a3 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/schema.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/schema.tsx @@ -37,31 +37,33 @@ export const validateSingleAction = async ( return [...actionParamsErrors, ...mustacheErrors]; }; -export const validateRuleActionsField = (actionTypeRegistry: ActionTypeRegistryContract) => async ( - ...data: Parameters -): Promise | void | undefined> => { - const [{ value, path }] = data as [{ value: AlertAction[]; path: string }]; +export const validateRuleActionsField = + (actionTypeRegistry: ActionTypeRegistryContract) => + async ( + ...data: Parameters + ): Promise | void | undefined> => { + const [{ value, path }] = data as [{ value: AlertAction[]; path: string }]; - const errors = []; - for (const actionItem of value) { - const errorsArray = await validateSingleAction(actionItem, actionTypeRegistry); + const errors = []; + for (const actionItem of value) { + const errorsArray = await validateSingleAction(actionItem, actionTypeRegistry); - if (errorsArray.length) { - const actionTypeName = getActionTypeName(actionItem.actionTypeId); - const errorsListItems = errorsArray.map((error) => `* ${error}\n`); + if (errorsArray.length) { + const actionTypeName = getActionTypeName(actionItem.actionTypeId); + const errorsListItems = errorsArray.map((error) => `* ${error}\n`); - errors.push(`\n**${actionTypeName}:**\n${errorsListItems.join('')}`); + errors.push(`\n**${actionTypeName}:**\n${errorsListItems.join('')}`); + } } - } - if (errors.length) { - return { - code: 'ERR_FIELD_FORMAT', - path, - message: `${errors.join('\n')}`, - }; - } -}; + if (errors.length) { + return { + code: 'ERR_FIELD_FORMAT', + path, + message: `${errors.join('\n')}`, + }; + } + }; export const getSchema = ({ actionTypeRegistry, diff --git a/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.tsx b/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.tsx index 425d049388764..200b21bbecc4b 100644 --- a/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.tsx @@ -86,9 +86,10 @@ export const TakeActionDropdownComponent = React.memo( [detailsData] ); - const alertIds = useMemo(() => (isEmpty(actionsData.eventId) ? null : [actionsData.eventId]), [ - actionsData.eventId, - ]); + const alertIds = useMemo( + () => (isEmpty(actionsData.eventId) ? null : [actionsData.eventId]), + [actionsData.eventId] + ); const isEvent = actionsData.eventKind === 'event'; const togglePopoverHandler = useCallback(() => { 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 67863f05c7d83..3d95dca81165e 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 @@ -70,7 +70,7 @@ describe('useUserInfo', () => { {children} diff --git a/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/form.test.tsx b/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/form.test.tsx index 0b127a034609f..f77f6b1ea23fb 100644 --- a/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/form.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/form.test.tsx @@ -16,10 +16,10 @@ import { useImportList } from '@kbn/securitysolution-list-hooks'; jest.mock('@kbn/securitysolution-list-hooks'); const mockUseImportList = useImportList as jest.Mock; -const mockFile = ({ +const mockFile = { name: 'foo.csv', type: 'text/csv', -} as unknown) as File; +} as unknown as File; const mockSelectFile:

(container: ReactWrapper

, file: File) => Promise = async ( container, @@ -28,7 +28,7 @@ const mockSelectFile:

(container: ReactWrapper

, file: File) => Promise { if (fileChange) { - fileChange(({ item: () => file } as unknown) as FormEvent); + fileChange({ item: () => file } as unknown as FormEvent); } }); }; @@ -86,10 +86,10 @@ describe('ValueListsForm', () => { }); it('disables upload and displays an error if file has invalid extension', async () => { - const badMockFile = ({ + const badMockFile = { name: 'foo.pdf', type: 'application/pdf', - } as unknown) as File; + } as unknown as File; const container = mount( diff --git a/x-pack/plugins/security_solution/public/detections/configurations/examples/observablity_alerts/render_cell_value.tsx b/x-pack/plugins/security_solution/public/detections/configurations/examples/observablity_alerts/render_cell_value.tsx index 684680ea2e852..12e0a5486b3a2 100644 --- a/x-pack/plugins/security_solution/public/detections/configurations/examples/observablity_alerts/render_cell_value.tsx +++ b/x-pack/plugins/security_solution/public/detections/configurations/examples/observablity_alerts/render_cell_value.tsx @@ -26,61 +26,60 @@ const reason = * accepts `EuiDataGridCellValueElementProps`, plus `data` * from the TGrid */ -export const RenderCellValue: React.FC< - EuiDataGridCellValueElementProps & CellValueElementProps -> = ({ - columnId, - data, - eventId, - header, - isDetails, - isDraggable, - isExpandable, - isExpanded, - linkValues, - rowIndex, - setCellProps, - timelineId, -}) => { - const value = - getMappedNonEcsValue({ - data, - fieldName: columnId, - })?.reduce((x) => x[0]) ?? ''; +export const RenderCellValue: React.FC = + ({ + columnId, + data, + eventId, + header, + isDetails, + isDraggable, + isExpandable, + isExpanded, + linkValues, + rowIndex, + setCellProps, + timelineId, + }) => { + const value = + getMappedNonEcsValue({ + data, + fieldName: columnId, + })?.reduce((x) => x[0]) ?? ''; - switch (columnId) { - case ALERT_STATUS: - return ( - - ); - case ALERT_DURATION: - return {moment().fromNow(true)}; - case 'signal.rule.severity': - return ; - case 'signal.reason': - return ( - - {reason} - - ); - default: - // NOTE: we're using `DefaultCellRenderer` in this example configuration as a fallback, but - // using `DefaultCellRenderer` here is entirely optional - return ( - - ); - } -}; + switch (columnId) { + case ALERT_STATUS: + return ( + + ); + case ALERT_DURATION: + return {moment().fromNow(true)}; + case 'signal.rule.severity': + return ; + case 'signal.reason': + return ( + + {reason} + + ); + default: + // NOTE: we're using `DefaultCellRenderer` in this example configuration as a fallback, but + // using `DefaultCellRenderer` here is entirely optional + return ( + + ); + } + }; diff --git a/x-pack/plugins/security_solution/public/detections/configurations/examples/security_solution_rac/render_cell_value.tsx b/x-pack/plugins/security_solution/public/detections/configurations/examples/security_solution_rac/render_cell_value.tsx index 879712c85327e..6475ef5bef970 100644 --- a/x-pack/plugins/security_solution/public/detections/configurations/examples/security_solution_rac/render_cell_value.tsx +++ b/x-pack/plugins/security_solution/public/detections/configurations/examples/security_solution_rac/render_cell_value.tsx @@ -23,58 +23,57 @@ const reason = * accepts `EuiDataGridCellValueElementProps`, plus `data` * from the TGrid */ -export const RenderCellValue: React.FC< - EuiDataGridCellValueElementProps & CellValueElementProps -> = ({ - columnId, - data, - eventId, - header, - isDetails, - isExpandable, - isExpanded, - linkValues, - rowIndex, - setCellProps, - timelineId, -}) => { - const value = - getMappedNonEcsValue({ - data, - fieldName: columnId, - })?.reduce((x) => x[0]) ?? ''; - const draggableId = `${timelineId}-${eventId}-${columnId}-${value}`; +export const RenderCellValue: React.FC = + ({ + columnId, + data, + eventId, + header, + isDetails, + isExpandable, + isExpanded, + linkValues, + rowIndex, + setCellProps, + timelineId, + }) => { + const value = + getMappedNonEcsValue({ + data, + fieldName: columnId, + })?.reduce((x) => x[0]) ?? ''; + const draggableId = `${timelineId}-${eventId}-${columnId}-${value}`; - switch (columnId) { - case 'signal.rule.severity': - return ( - - - - ); - case 'signal.reason': - return {reason}; - default: - return ( - - ); - } -}; + switch (columnId) { + case 'signal.rule.severity': + return ( + + + + ); + case 'signal.reason': + return {reason}; + default: + return ( + + ); + } + }; diff --git a/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/render_cell_value.tsx b/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/render_cell_value.tsx index 46fb853a7aa29..d9cdc4e0d0912 100644 --- a/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/render_cell_value.tsx +++ b/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/render_cell_value.tsx @@ -16,40 +16,39 @@ import { DefaultCellRenderer } from '../../../timelines/components/timeline/cell * accepts `EuiDataGridCellValueElementProps`, plus `data` * from the TGrid */ -export const RenderCellValue: React.FC< - EuiDataGridCellValueElementProps & CellValueElementProps -> = ({ - columnId, - data, - eventId, - isDraggable, - header, - isDetails, - isExpandable, - isExpanded, - linkValues, - rowIndex, - setCellProps, - timelineId, - ecsData, - rowRenderers, - browserFields, -}) => ( - -); +export const RenderCellValue: React.FC = + ({ + columnId, + data, + eventId, + isDraggable, + header, + isDetails, + isExpandable, + isExpanded, + linkValues, + rowIndex, + setCellProps, + timelineId, + ecsData, + rowRenderers, + browserFields, + }) => ( + + ); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.test.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.test.ts index b944cb640b719..fa850ce6b36ea 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.test.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.test.ts @@ -42,8 +42,7 @@ describe('Detections Alerts API', () => { test('check parameter url, body', async () => { await fetchQueryAlerts({ query: mockAlertsQuery, signal: abortCtrl.signal }); expect(fetchMock).toHaveBeenCalledWith('/api/detection_engine/signals/search', { - body: - '{"aggs":{"alertsByGrouping":{"terms":{"field":"signal.rule.risk_score","missing":"All others","order":{"_count":"desc"},"size":10},"aggs":{"alerts":{"date_histogram":{"field":"@timestamp","fixed_interval":"81000000ms","min_doc_count":0,"extended_bounds":{"min":1579644343954,"max":1582236343955}}}}}},"query":{"bool":{"filter":[{"bool":{"must":[],"filter":[{"match_all":{}}],"should":[],"must_not":[]}},{"range":{"@timestamp":{"gte":1579644343954,"lte":1582236343955}}}]}}}', + body: '{"aggs":{"alertsByGrouping":{"terms":{"field":"signal.rule.risk_score","missing":"All others","order":{"_count":"desc"},"size":10},"aggs":{"alerts":{"date_histogram":{"field":"@timestamp","fixed_interval":"81000000ms","min_doc_count":0,"extended_bounds":{"min":1579644343954,"max":1582236343955}}}}}},"query":{"bool":{"filter":[{"bool":{"must":[],"filter":[{"match_all":{}}],"should":[],"must_not":[]}},{"range":{"@timestamp":{"gte":1579644343954,"lte":1582236343955}}}]}}}', method: 'POST', signal: abortCtrl.signal, }); @@ -71,8 +70,7 @@ describe('Detections Alerts API', () => { status: 'closed', }); expect(fetchMock).toHaveBeenCalledWith('/api/detection_engine/signals/status', { - body: - '{"conflicts":"proceed","status":"closed","bool":{"filter":{"terms":{"_id":["b4ee5c32e3a321057edcc953ca17228c6fdfe5ba43fdbbdaffa8cefa11605cc5"]}}}}', + body: '{"conflicts":"proceed","status":"closed","bool":{"filter":{"terms":{"_id":["b4ee5c32e3a321057edcc953ca17228c6fdfe5ba43fdbbdaffa8cefa11605cc5"]}}}}', method: 'POST', signal: abortCtrl.signal, }); @@ -85,8 +83,7 @@ describe('Detections Alerts API', () => { status: 'open', }); expect(fetchMock).toHaveBeenCalledWith('/api/detection_engine/signals/status', { - body: - '{"conflicts":"proceed","status":"open","bool":{"filter":{"terms":{"_id":["b4ee5c32e3a321057edcc953ca17228c6fdfe5ba43fdbbdaffa8cefa11605cc5"]}}}}', + body: '{"conflicts":"proceed","status":"open","bool":{"filter":{"terms":{"_id":["b4ee5c32e3a321057edcc953ca17228c6fdfe5ba43fdbbdaffa8cefa11605cc5"]}}}}', method: 'POST', signal: abortCtrl.signal, }); @@ -183,8 +180,7 @@ describe('Detections Alerts API', () => { caseIds: ['88c04a90-b19c-11eb-b838-bf3c7840b969'], }); expect(postMock).toHaveBeenCalledWith('/api/endpoint/isolate', { - body: - '{"endpoint_ids":["fd8a122b-4c54-4c05-b295-e5f8381fc59d"],"comment":"commento","case_ids":["88c04a90-b19c-11eb-b838-bf3c7840b969"]}', + body: '{"endpoint_ids":["fd8a122b-4c54-4c05-b295-e5f8381fc59d"],"comment":"commento","case_ids":["88c04a90-b19c-11eb-b838-bf3c7840b969"]}', }); }); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.test.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.test.ts index 8fd2b5f437bcd..d72731af493c1 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.test.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.test.ts @@ -48,8 +48,7 @@ describe('Detections Rules API', () => { const payload = getCreateRulesSchemaMock(); await createRule({ rule: payload, signal: abortCtrl.signal }); expect(fetchMock).toHaveBeenCalledWith('/api/detection_engine/rules', { - body: - '{"description":"Detecting root and admin users","name":"Query with a rule id","query":"user.name: root or user.name: admin","severity":"high","type":"query","risk_score":55,"language":"kuery","rule_id":"rule-1"}', + body: '{"description":"Detecting root and admin users","name":"Query with a rule id","query":"user.name: root or user.name: admin","severity":"high","type":"query","risk_score":55,"language":"kuery","rule_id":"rule-1"}', method: 'POST', signal: abortCtrl.signal, }); @@ -66,8 +65,7 @@ describe('Detections Rules API', () => { const payload = getUpdateRulesSchemaMock(); await updateRule({ rule: payload, signal: abortCtrl.signal }); expect(fetchMock).toHaveBeenCalledWith('/api/detection_engine/rules', { - body: - '{"description":"Detecting root and admin users","name":"Query with a rule id","query":"user.name: root or user.name: admin","severity":"high","type":"query","risk_score":55,"language":"kuery","id":"04128c15-0d1b-4716-a4c5-46997ac7f3bd"}', + body: '{"description":"Detecting root and admin users","name":"Query with a rule id","query":"user.name: root or user.name: admin","severity":"high","type":"query","risk_score":55,"language":"kuery","id":"04128c15-0d1b-4716-a4c5-46997ac7f3bd"}', method: 'PUT', signal: abortCtrl.signal, }); @@ -425,8 +423,7 @@ describe('Detections Rules API', () => { test('check parameter url, body when duplicating rules', async () => { await duplicateRules({ rules: rulesMock.data }); expect(fetchMock).toHaveBeenCalledWith('/api/detection_engine/rules/_bulk_create', { - body: - '[{"actions":[],"author":[],"description":"Elastic Endpoint detected Credential Dumping. Click the Elastic Endpoint icon in the event.module column or the link in the rule.reference column in the External Alerts tab of the SIEM Detections page for additional information.","enabled":false,"false_positives":[],"from":"now-660s","index":["endgame-*"],"interval":"10m","language":"kuery","output_index":".siem-signals-default","max_signals":100,"risk_score":73,"risk_score_mapping":[],"name":"Credential Dumping - Detected - Elastic Endpoint [Duplicate]","query":"event.kind:alert and event.module:endgame and event.action:cred_theft_event and endgame.metadata.type:detection","filters":[],"references":[],"severity":"high","severity_mapping":[],"tags":["Elastic","Endpoint"],"to":"now","type":"query","threat":[],"throttle":null,"version":1},{"actions":[],"author":[],"description":"Elastic Endpoint detected an Adversary Behavior. Click the Elastic Endpoint icon in the event.module column or the link in the rule.reference column in the External Alerts tab of the SIEM Detections page for additional information.","enabled":false,"false_positives":[],"from":"now-660s","index":["endgame-*"],"interval":"10m","language":"kuery","output_index":".siem-signals-default","max_signals":100,"risk_score":47,"risk_score_mapping":[],"name":"Adversary Behavior - Detected - Elastic Endpoint [Duplicate]","query":"event.kind:alert and event.module:endgame and event.action:rules_engine_event","filters":[],"references":[],"severity":"medium","severity_mapping":[],"tags":["Elastic","Endpoint"],"to":"now","type":"query","threat":[],"throttle":null,"version":1}]', + body: '[{"actions":[],"author":[],"description":"Elastic Endpoint detected Credential Dumping. Click the Elastic Endpoint icon in the event.module column or the link in the rule.reference column in the External Alerts tab of the SIEM Detections page for additional information.","enabled":false,"false_positives":[],"from":"now-660s","index":["endgame-*"],"interval":"10m","language":"kuery","output_index":".siem-signals-default","max_signals":100,"risk_score":73,"risk_score_mapping":[],"name":"Credential Dumping - Detected - Elastic Endpoint [Duplicate]","query":"event.kind:alert and event.module:endgame and event.action:cred_theft_event and endgame.metadata.type:detection","filters":[],"references":[],"severity":"high","severity_mapping":[],"tags":["Elastic","Endpoint"],"to":"now","type":"query","threat":[],"throttle":null,"version":1},{"actions":[],"author":[],"description":"Elastic Endpoint detected an Adversary Behavior. Click the Elastic Endpoint icon in the event.module column or the link in the rule.reference column in the External Alerts tab of the SIEM Detections page for additional information.","enabled":false,"false_positives":[],"from":"now-660s","index":["endgame-*"],"interval":"10m","language":"kuery","output_index":".siem-signals-default","max_signals":100,"risk_score":47,"risk_score_mapping":[],"name":"Adversary Behavior - Detected - Elastic Endpoint [Duplicate]","query":"event.kind:alert and event.module:endgame and event.action:rules_engine_event","filters":[],"references":[],"severity":"medium","severity_mapping":[],"tags":["Elastic","Endpoint"],"to":"now","type":"query","threat":[],"throttle":null,"version":1}]', method: 'POST', }); }); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/rules_table_reducer.test.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/rules_table_reducer.test.ts index 2a983117db524..cba9611071976 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/rules_table_reducer.test.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/rules_table_reducer.test.ts @@ -97,21 +97,16 @@ describe('allRulesReducer', () => { describe('#setRules', () => { it('should update rules and reset loading/selected rule ids', () => { - const { - selectedRuleIds, - loadingRuleIds, - loadingRulesAction, - pagination, - rules, - } = rulesTableReducer(initialState, { - type: 'setRules', - rules: [mockRule('someRuleId')], - pagination: { - page: 1, - perPage: 20, - total: 0, - }, - }); + const { selectedRuleIds, loadingRuleIds, loadingRulesAction, pagination, rules } = + rulesTableReducer(initialState, { + type: 'setRules', + rules: [mockRule('someRuleId')], + pagination: { + page: 1, + perPage: 20, + total: 0, + }, + }); expect(rules).toEqual([mockRule('someRuleId')]); expect(selectedRuleIds).toEqual([]); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_dissasociate_exception_list.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_dissasociate_exception_list.tsx index 107e66a69768e..cf2500cd4036e 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_dissasociate_exception_list.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_dissasociate_exception_list.tsx @@ -43,31 +43,31 @@ export const useDissasociateExceptionList = ({ let isSubscribed = true; const abortCtrl = new AbortController(); - const dissasociateListFromRule = (id: string) => async ( - exceptionLists: List[] - ): Promise => { - try { - if (isSubscribed) { - setLoading(true); + const dissasociateListFromRule = + (id: string) => + async (exceptionLists: List[]): Promise => { + try { + if (isSubscribed) { + setLoading(true); - await patchRule({ - ruleProperties: { - rule_id: id, - exceptions_list: exceptionLists, - }, - signal: abortCtrl.signal, - }); + await patchRule({ + ruleProperties: { + rule_id: id, + exceptions_list: exceptionLists, + }, + signal: abortCtrl.signal, + }); - onSuccess(); - setLoading(false); + onSuccess(); + setLoading(false); + } + } catch (err) { + if (isSubscribed) { + setLoading(false); + onError(err); + } } - } catch (err) { - if (isSubscribed) { - setLoading(false); - onError(err); - } - } - }; + }; dissasociateList.current = dissasociateListFromRule(ruleRuleId); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx index 063dc849027a7..848bdd7f8ef71 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx @@ -132,10 +132,8 @@ const DetectionEnginePageComponent: React.FC = ({ hasIndexRead, }, ] = useUserData(); - const { - loading: listsConfigLoading, - needsConfiguration: needsListsConfiguration, - } = useListsConfig(); + const { loading: listsConfigLoading, needsConfiguration: needsListsConfiguration } = + useListsConfig(); const { formatUrl } = useFormatUrl(SecurityPageName.rules); const [showBuildingBlockAlerts, setShowBuildingBlockAlerts] = useState(false); const [showOnlyThreatIndicatorAlerts, setShowOnlyThreatIndicatorAlerts] = useState(false); @@ -180,10 +178,9 @@ const DetectionEnginePageComponent: React.FC = ({ const timelineId = TimelineId.detectionsPage; clearEventsLoading!({ id: timelineId }); clearEventsDeleted!({ id: timelineId }); - clearSelected!({ id: timelineId }); setFilterGroup(newFilterGroup); }, - [clearEventsLoading, clearEventsDeleted, clearSelected, setFilterGroup] + [clearEventsLoading, clearEventsDeleted, setFilterGroup] ); const alertsHistogramDefaultFilters = useMemo( 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 23bf634cb1081..5c2d5f5d62b5c 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 @@ -76,21 +76,16 @@ export const ExceptionListsTable = React.memo(() => { exceptionReferenceModalInitialState ); const [filters, setFilters] = useState(undefined); - const [ - loadingExceptions, - exceptions, - pagination, - setPagination, - refreshExceptions, - ] = useExceptionLists({ - errorMessage: i18n.ERROR_EXCEPTION_LISTS, - filterOptions: filters, - http, - namespaceTypes: ['single', 'agnostic'], - notifications, - showTrustedApps: false, - showEventFilters: false, - }); + const [loadingExceptions, exceptions, pagination, setPagination, refreshExceptions] = + useExceptionLists({ + errorMessage: i18n.ERROR_EXCEPTION_LISTS, + filterOptions: filters, + http, + namespaceTypes: ['single', 'agnostic'], + notifications, + showTrustedApps: false, + showEventFilters: false, + }); const [loadingTableInfo, exceptionListsWithRuleRefs, exceptionsListsRef] = useAllExceptionLists({ exceptionLists: exceptions ?? [], }); @@ -121,49 +116,42 @@ export const ExceptionListsTable = React.memo(() => { ); const handleDelete = useCallback( - ({ - id, - listId, - namespaceType, - }: { - id: string; - listId: string; - namespaceType: NamespaceType; - }) => async () => { - try { - setDeletingListIds((ids) => [...ids, id]); - if (refreshExceptions != null) { - refreshExceptions(); - } - - if (exceptionsListsRef[id] != null && exceptionsListsRef[id].rules.length === 0) { - await deleteExceptionList({ - id, - namespaceType, - onError: handleDeleteError, - onSuccess: handleDeleteSuccess(listId), - }); - + ({ id, listId, namespaceType }: { id: string; listId: string; namespaceType: NamespaceType }) => + async () => { + try { + setDeletingListIds((ids) => [...ids, id]); if (refreshExceptions != null) { refreshExceptions(); } - } else { - setReferenceModalState({ - contentText: i18n.referenceErrorMessage(exceptionsListsRef[id].rules.length), - rulesReferences: exceptionsListsRef[id].rules.map(({ name }) => name), - isLoading: true, - listId: id, - listNamespaceType: namespaceType, - }); - setShowReferenceErrorModal(true); + + if (exceptionsListsRef[id] != null && exceptionsListsRef[id].rules.length === 0) { + await deleteExceptionList({ + id, + namespaceType, + onError: handleDeleteError, + onSuccess: handleDeleteSuccess(listId), + }); + + if (refreshExceptions != null) { + refreshExceptions(); + } + } else { + setReferenceModalState({ + contentText: i18n.referenceErrorMessage(exceptionsListsRef[id].rules.length), + rulesReferences: exceptionsListsRef[id].rules.map(({ name }) => name), + isLoading: true, + listId: id, + listNamespaceType: namespaceType, + }); + setShowReferenceErrorModal(true); + } + // route to patch rules with associated exception list + } catch (error) { + handleDeleteError(error); + } finally { + setDeletingListIds((ids) => ids.filter((_id) => _id !== id)); } - // route to patch rules with associated exception list - } catch (error) { - handleDeleteError(error); - } finally { - setDeletingListIds((ids) => ids.filter((_id) => _id !== id)); - } - }, + }, [ deleteExceptionList, exceptionsListsRef, @@ -174,9 +162,10 @@ export const ExceptionListsTable = React.memo(() => { ); const handleExportSuccess = useCallback( - (listId: string) => (blob: Blob): void => { - setExportDownload({ name: listId, blob }); - }, + (listId: string) => + (blob: Blob): void => { + setExportDownload({ name: listId, blob }); + }, [] ); @@ -188,24 +177,17 @@ export const ExceptionListsTable = React.memo(() => { ); const handleExport = useCallback( - ({ - id, - listId, - namespaceType, - }: { - id: string; - listId: string; - namespaceType: NamespaceType; - }) => async () => { - setExportingListIds((ids) => [...ids, id]); - await exportExceptionList({ - id, - listId, - namespaceType, - onError: handleExportError, - onSuccess: handleExportSuccess(listId), - }); - }, + ({ id, listId, namespaceType }: { id: string; listId: string; namespaceType: NamespaceType }) => + async () => { + setExportingListIds((ids) => [...ids, id]); + await exportExceptionList({ + id, + listId, + namespaceType, + onError: handleExportError, + onSuccess: handleExportSuccess(listId), + }); + }, [exportExceptionList, handleExportError, handleExportSuccess] ); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx index 22281fa2c8687..f32321a0a03dc 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx @@ -182,15 +182,13 @@ export const RulesTables = React.memo( rulesNotUpdated ); - const hasActionsPrivileges = useMemo(() => (isBoolean(actions.show) ? actions.show : true), [ - actions, - ]); + const hasActionsPrivileges = useMemo( + () => (isBoolean(actions.show) ? actions.show : true), + [actions] + ); - const [ - isDeleteConfirmationVisible, - showDeleteConfirmation, - hideDeleteConfirmation, - ] = useBoolState(); + const [isDeleteConfirmationVisible, showDeleteConfirmation, hideDeleteConfirmation] = + useBoolState(); const [confirmDeletion, handleDeletionConfirm, handleDeletionCancel] = useAsyncConfirmation({ onInit: showDeleteConfirmation, @@ -300,10 +298,10 @@ export const RulesTables = React.memo( reFetchRules, ]); - const monitoringColumns = useMemo(() => getMonitoringColumns(navigateToApp, formatUrl), [ - navigateToApp, - formatUrl, - ]); + const monitoringColumns = useMemo( + () => getMonitoringColumns(navigateToApp, formatUrl), + [navigateToApp, formatUrl] + ); useEffect(() => { setRefreshRulesData(reFetchRules); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts index 44a85cb2028c0..91db957a1aefd 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts @@ -380,9 +380,9 @@ export const formatRule = ( actionsData: ActionsStepRule, rule?: Rule | null ): T => - (deepmerge.all([ + deepmerge.all([ formatDefineStepData(defineStepData), formatAboutStepData(aboutStepData, rule?.exceptions_list), formatScheduleStepData(scheduleData), formatActionsStepData(actionsData), - ]) as unknown) as T; + ]) as unknown as T; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.tsx index 1f2bda768d19c..a2f4385aeeb86 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.tsx @@ -97,10 +97,8 @@ const CreateRulePageComponent: React.FC = () => { canUserCRUD, }, ] = useUserData(); - const { - loading: listsConfigLoading, - needsConfiguration: needsListsConfiguration, - } = useListsConfig(); + const { loading: listsConfigLoading, needsConfiguration: needsListsConfiguration } = + useListsConfig(); const { navigateToApp } = useKibana().services.application; const loading = userInfoLoading || listsConfigLoading; const [, dispatchToaster] = useStateToaster(); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx index 6276d934fed41..70d7faa47b9ee 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx @@ -178,7 +178,8 @@ const RuleDetailsPageComponent: React.FC = ({ (getTimeline(state, TimelineId.detectionsRulesDetailsPage) ?? timelineDefaults).graphEventId ); const updatedAt = useShallowEqualSelector( - (state) => (getTimeline(state, TimelineId.detectionsPage) ?? timelineDefaults).updated + (state) => + (getTimeline(state, TimelineId.detectionsRulesDetailsPage) ?? timelineDefaults).updated ); const isAlertsLoading = useShallowEqualSelector( (state) => (getTimeline(state, TimelineId.detectionsPage) ?? timelineDefaults).isLoading @@ -205,10 +206,8 @@ const RuleDetailsPageComponent: React.FC = ({ signalIndexName, }, ] = useUserData(); - const { - loading: listsConfigLoading, - needsConfiguration: needsListsConfiguration, - } = useListsConfig(); + const { loading: listsConfigLoading, needsConfiguration: needsListsConfiguration } = + useListsConfig(); const loading = userInfoLoading || listsConfigLoading; const { detailName: ruleId } = useParams<{ detailName: string }>(); const { @@ -352,7 +351,7 @@ const RuleDetailsPageComponent: React.FC = ({ // Callback for when open/closed filter changes const onFilterGroupChangedCallback = useCallback( (newFilterGroup: Status) => { - const timelineId = TimelineId.detectionsPage; + const timelineId = TimelineId.detectionsRulesDetailsPage; clearEventsLoading!({ id: timelineId }); clearEventsDeleted!({ id: timelineId }); clearSelected!({ id: timelineId }); @@ -392,7 +391,6 @@ const RuleDetailsPageComponent: React.FC = ({ const alertsTableDefaultFilters = useMemo( () => [ ...buildAlertsRuleIdFilter(ruleId), - ...filters, ...(ruleRegistryEnabled ? [ // TODO: Once we are past experimental phase this code should be removed @@ -401,13 +399,13 @@ const RuleDetailsPageComponent: React.FC = ({ : [...buildShowBuildingBlockFilter(showBuildingBlockAlerts)]), ...buildThreatMatchFilter(showOnlyThreatIndicatorAlerts), ], - [ruleId, filters, ruleRegistryEnabled, showBuildingBlockAlerts, showOnlyThreatIndicatorAlerts] + [ruleId, ruleRegistryEnabled, showBuildingBlockAlerts, showOnlyThreatIndicatorAlerts] ); - const alertMergedFilters = useMemo(() => [...alertDefaultFilters, ...filters], [ - alertDefaultFilters, - filters, - ]); + const alertMergedFilters = useMemo( + () => [...alertDefaultFilters, ...filters], + [alertDefaultFilters, filters] + ); const tabs = useMemo( () => ( diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.tsx index caec85f537d2b..cea97aed28cc1 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.tsx @@ -71,10 +71,8 @@ const EditRulePageComponent: FC = () => { canUserCRUD, }, ] = useUserData(); - const { - loading: listsConfigLoading, - needsConfiguration: needsListsConfiguration, - } = useListsConfig(); + const { loading: listsConfigLoading, needsConfiguration: needsListsConfiguration } = + useListsConfig(); const { navigateToApp } = useKibana().services.application; const { detailName: ruleId } = useParams<{ detailName: string | undefined }>(); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx index f20ace09ed2b6..0f109214c6bf3 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx @@ -378,31 +378,29 @@ export const getActionMessageRuleParams = (ruleType: Type): string[] => { return ruleParamsKeys; }; -export const getActionMessageParams = memoizeOne( - (ruleType: Type | undefined): ActionVariables => { - if (!ruleType) { - return { state: [], params: [] }; - } - const actionMessageRuleParams = getActionMessageRuleParams(ruleType); - // Prefixes are being added automatically by the ActionTypeForm - return { - state: [{ name: 'signals_count', description: 'state.signals_count' }], - params: [], - context: [ - { - name: 'results_link', - description: 'context.results_link', - useWithTripleBracesInTemplates: true, - }, - { name: 'alerts', description: 'context.alerts' }, - ...actionMessageRuleParams.map((param) => { - const extendedParam = `rule.${param}`; - return { name: extendedParam, description: `context.${extendedParam}` }; - }), - ], - }; +export const getActionMessageParams = memoizeOne((ruleType: Type | undefined): ActionVariables => { + if (!ruleType) { + return { state: [], params: [] }; } -); + const actionMessageRuleParams = getActionMessageRuleParams(ruleType); + // Prefixes are being added automatically by the ActionTypeForm + return { + state: [{ name: 'signals_count', description: 'state.signals_count' }], + params: [], + context: [ + { + name: 'results_link', + description: 'context.results_link', + useWithTripleBracesInTemplates: true, + }, + { name: 'alerts', description: 'context.alerts' }, + ...actionMessageRuleParams.map((param) => { + const extendedParam = `rule.${param}`; + return { name: extendedParam, description: `context.${extendedParam}` }; + }), + ], + }; +}); // typed as null not undefined as the initial state for this value is null. export const userHasPermissions = (canUserCRUD: boolean | null): boolean => diff --git a/x-pack/plugins/security_solution/public/hosts/containers/authentications/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/authentications/index.tsx index f60e0b461d055..f446380e54937 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/authentications/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/authentications/index.tsx @@ -78,10 +78,8 @@ export const useAuthentications = ({ const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); const [loading, setLoading] = useState(false); - const [ - authenticationsRequest, - setAuthenticationsRequest, - ] = useState(null); + const [authenticationsRequest, setAuthenticationsRequest] = + useState(null); const { getTransformChangesIfTheyExist } = useTransforms(); const { addError, addWarning } = useAppToasts(); diff --git a/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx index a3703ab64beda..59b08a1b7c07d 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx @@ -50,16 +50,14 @@ export const useFirstLastSeenHost = ({ const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); const [loading, setLoading] = useState(false); - const [ - firstLastSeenHostRequest, - setFirstLastSeenHostRequest, - ] = useState({ - defaultIndex: indexNames, - docValueFields: docValueFields ?? [], - factoryQueryType: HostsQueries.firstOrLastSeen, - hostName, - order, - }); + const [firstLastSeenHostRequest, setFirstLastSeenHostRequest] = + useState({ + defaultIndex: indexNames, + docValueFields: docValueFields ?? [], + factoryQueryType: HostsQueries.firstOrLastSeen, + hostName, + order, + }); const [firstLastSeenHostResponse, setFirstLastSeenHostResponse] = useState( { diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/index.tsx index 77196ae15646b..c15c68d246f14 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/index.tsx @@ -56,28 +56,24 @@ export const useHostsKpiAuthentications = ({ const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); const [loading, setLoading] = useState(false); - const [ - hostsKpiAuthenticationsRequest, - setHostsKpiAuthenticationsRequest, - ] = useState(null); + const [hostsKpiAuthenticationsRequest, setHostsKpiAuthenticationsRequest] = + useState(null); const { getTransformChangesIfTheyExist } = useTransforms(); - const [ - hostsKpiAuthenticationsResponse, - setHostsKpiAuthenticationsResponse, - ] = useState({ - authenticationsSuccess: 0, - authenticationsSuccessHistogram: [], - authenticationsFailure: 0, - authenticationsFailureHistogram: [], - id: ID, - inspect: { - dsl: [], - response: [], - }, - isInspected: false, - refetch: refetch.current, - }); + const [hostsKpiAuthenticationsResponse, setHostsKpiAuthenticationsResponse] = + useState({ + authenticationsSuccess: 0, + authenticationsSuccessHistogram: [], + authenticationsFailure: 0, + authenticationsFailureHistogram: [], + id: ID, + inspect: { + dsl: [], + response: [], + }, + isInspected: false, + refetch: refetch.current, + }); const { addError, addWarning } = useAppToasts(); const hostsKpiAuthenticationsSearch = useCallback( diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/index.tsx index 3a478962559c2..fdce4dfe79591 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/index.tsx @@ -55,10 +55,8 @@ export const useHostsKpiHosts = ({ const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); const [loading, setLoading] = useState(false); - const [ - hostsKpiHostsRequest, - setHostsKpiHostsRequest, - ] = useState(null); + const [hostsKpiHostsRequest, setHostsKpiHostsRequest] = + useState(null); const { getTransformChangesIfTheyExist } = useTransforms(); const [hostsKpiHostsResponse, setHostsKpiHostsResponse] = useState({ diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/index.tsx index d7f5469cf3117..5b9eeb2710ff3 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/index.tsx @@ -58,10 +58,8 @@ export const useHostsKpiUniqueIps = ({ const [loading, setLoading] = useState(false); const { getTransformChangesIfTheyExist } = useTransforms(); - const [ - hostsKpiUniqueIpsRequest, - setHostsKpiUniqueIpsRequest, - ] = useState(null); + const [hostsKpiUniqueIpsRequest, setHostsKpiUniqueIpsRequest] = + useState(null); const [hostsKpiUniqueIpsResponse, setHostsKpiUniqueIpsResponse] = useState( { diff --git a/x-pack/plugins/security_solution/public/hosts/containers/uncommon_processes/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/uncommon_processes/index.tsx index e94873dee5632..9548027520bd1 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/uncommon_processes/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/uncommon_processes/index.tsx @@ -78,10 +78,8 @@ export const useUncommonProcesses = ({ const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); const [loading, setLoading] = useState(false); - const [ - uncommonProcessesRequest, - setUncommonProcessesRequest, - ] = useState(null); + const [uncommonProcessesRequest, setUncommonProcessesRequest] = + useState(null); const { addError, addWarning } = useAppToasts(); const wrappedLoadMore = useCallback( diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.test.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.test.tsx index 5be29a94b5330..9ec2f69cfef86 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.test.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.test.tsx @@ -86,7 +86,7 @@ describe('body', () => { isInitializing={false} detailName={'host-1'} setQuery={jest.fn()} - setAbsoluteRangeDatePicker={(jest.fn() as unknown) as SetAbsoluteRangeDatePicker} + setAbsoluteRangeDatePicker={jest.fn() as unknown as SetAbsoluteRangeDatePicker} hostDetailsPagePath={hostDetailsPagePath} indexNames={[]} indexPattern={mockIndexPattern} diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx index c07df41225d40..627f5e5aef507 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx @@ -74,9 +74,10 @@ const HostDetailsComponent: React.FC = ({ detailName, hostDeta const capabilities = useMlCapabilities(); const kibana = useKibana(); - const hostDetailsPageFilters: Filter[] = useMemo(() => getHostDetailsPageFilters(detailName), [ - detailName, - ]); + const hostDetailsPageFilters: Filter[] = useMemo( + () => getHostDetailsPageFilters(detailName), + [detailName] + ); const getFilters = () => [...hostDetailsPageFilters, ...filters]; const narrowDateRange = useCallback( diff --git a/x-pack/plugins/security_solution/public/hosts/pages/navigation/hosts_query_tab_body.tsx b/x-pack/plugins/security_solution/public/hosts/pages/navigation/hosts_query_tab_body.tsx index 624bccb11fd23..cc43cfed4619d 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/navigation/hosts_query_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/navigation/hosts_query_tab_body.tsx @@ -25,10 +25,8 @@ export const HostsQueryTabBody = ({ startDate, type, }: HostsComponentsQueryProps) => { - const [ - loading, - { hosts, totalCount, pageInfo, loadPage, id, inspect, isInspected, refetch }, - ] = useAllHost({ docValueFields, endDate, filterQuery, indexNames, skip, startDate, type }); + const [loading, { hosts, totalCount, pageInfo, loadPage, id, inspect, isInspected, refetch }] = + useAllHost({ docValueFields, endDate, filterQuery, indexNames, skip, startDate, type }); return ( = Exclude extends never ? T1 : never; @@ -38,7 +39,7 @@ type Exact = T extends Shape ? ExactKeys : never; */ const querystringStringify = ( params: Exact -): string => querystring.stringify((params as unknown) as querystring.ParsedUrlQueryInput); +): string => querystring.stringify(params as unknown as querystring.ParsedUrlQueryInput); /** Make `selected_endpoint` required */ type EndpointDetailsUrlProps = Omit & @@ -160,6 +161,25 @@ const normalizeTrustedAppsPageLocation = ( } }; +const normalizePolicyDetailsArtifactsListPageLocation = ( + location?: Partial +): Partial => { + if (location) { + return { + ...(!isDefaultOrMissing(location.page_index, MANAGEMENT_DEFAULT_PAGE) + ? { page_index: location.page_index } + : {}), + ...(!isDefaultOrMissing(location.page_size, MANAGEMENT_DEFAULT_PAGE_SIZE) + ? { page_size: location.page_size } + : {}), + ...(!isDefaultOrMissing(location.show, undefined) ? { show: location.show } : {}), + ...(!isDefaultOrMissing(location.filter, '') ? { filter: location.filter } : ''), + }; + } else { + return {}; + } +}; + const normalizeEventFiltersPageLocation = ( location?: Partial ): Partial => { @@ -257,6 +277,34 @@ export const getTrustedAppsListPath = (location?: Partial { + const showParamValue = extractFirstParamValue( + query, + 'show' + ) as PolicyDetailsArtifactsPageLocation['show']; + + return { + ...extractListPaginationParams(query), + show: showParamValue && 'list' === showParamValue ? showParamValue : undefined, + }; +}; + +export const getPolicyDetailsArtifactsListPath = ( + policyId: string, + location?: Partial +): string => { + const path = generatePath(MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_APPS_PATH, { + tabName: AdministrationSubTab.policies, + policyId, + }); + + return `${path}${appendSearch( + querystring.stringify(normalizePolicyDetailsArtifactsListPageLocation(location)) + )}`; +}; + export const extractEventFiltetrsPageLocation = ( query: querystring.ParsedUrlQuery ): EventFiltersPageLocation => { diff --git a/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/hooks/use_normalized_artifact.ts b/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/hooks/use_normalized_artifact.ts index 78d7bd2d2f804..7b3fb07bf10ac 100644 --- a/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/hooks/use_normalized_artifact.ts +++ b/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/hooks/use_normalized_artifact.ts @@ -36,7 +36,7 @@ export const useNormalizedArtifact = (item: AnyArtifact): ArtifactInfo => { updated_at, updated_by, description, - entries: (entries as unknown) as ArtifactInfo['entries'], + entries: entries as unknown as ArtifactInfo['entries'], os: isTrustedApp(item) ? item.os : getOsFromExceptionItem(item), effectScope: isTrustedApp(item) ? item.effectScope : getEffectScopeFromExceptionItem(item), }; diff --git a/x-pack/plugins/security_solution/public/management/components/paginated_content/paginated_content.tsx b/x-pack/plugins/security_solution/public/management/components/paginated_content/paginated_content.tsx index ee0bf02f730f4..fa123e023bd9f 100644 --- a/x-pack/plugins/security_solution/public/management/components/paginated_content/paginated_content.tsx +++ b/x-pack/plugins/security_solution/public/management/components/paginated_content/paginated_content.tsx @@ -200,7 +200,7 @@ export const PaginatedContent = memo( let key: Key; if (itemId) { - key = (item[itemId] as unknown) as Key; + key = item[itemId] as unknown as Key; } else { if (itemKeys.has(item)) { key = itemKeys.get(item)!; diff --git a/x-pack/plugins/security_solution/public/management/components/search_exceptions/search_exceptions.test.tsx b/x-pack/plugins/security_solution/public/management/components/search_exceptions/search_exceptions.test.tsx index 5c909e062ceb9..ddee8e13f069d 100644 --- a/x-pack/plugins/security_solution/public/management/components/search_exceptions/search_exceptions.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/search_exceptions/search_exceptions.test.tsx @@ -32,8 +32,10 @@ describe('Search exceptions', () => { it('should have a default value', () => { const expectedDefaultValue = 'this is a default value'; const element = mount(getElement(expectedDefaultValue)); - const defaultValue = element.find('[data-test-subj="searchField"]').first().props() - .defaultValue; + const defaultValue = element + .find('[data-test-subj="searchField"]') + .first() + .props().defaultValue; expect(defaultValue).toBe(expectedDefaultValue); }); diff --git a/x-pack/plugins/security_solution/public/management/components/search_exceptions/search_exceptions.tsx b/x-pack/plugins/security_solution/public/management/components/search_exceptions/search_exceptions.tsx index a737b53e2d9b9..a77a2a41038d7 100644 --- a/x-pack/plugins/security_solution/public/management/components/search_exceptions/search_exceptions.tsx +++ b/x-pack/plugins/security_solution/public/management/components/search_exceptions/search_exceptions.tsx @@ -58,12 +58,10 @@ export const SearchExceptions = memo( (ev: React.ChangeEvent) => setQuery(ev.target.value), [setQuery] ); - const handleOnSearch = useCallback(() => onSearch(query, includedPolicies, excludedPolicies), [ - onSearch, - query, - includedPolicies, - excludedPolicies, - ]); + const handleOnSearch = useCallback( + () => onSearch(query, includedPolicies, excludedPolicies), + [onSearch, query, includedPolicies, excludedPolicies] + ); const handleOnSearchQuery = useCallback( (value) => { 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 9c557f83012bf..cf3f53b5b2ea9 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 @@ -89,8 +89,8 @@ export const endpointMetadataHttpMocks = httpHandlerMockFactory HostPolicyResponse; }>; -export const endpointPolicyResponseHttpMock = httpHandlerMockFactory( - [ +export const endpointPolicyResponseHttpMock = + httpHandlerMockFactory([ { id: 'policyResponse', path: BASE_POLICY_RESPONSE_ROUTE, @@ -99,14 +99,13 @@ export const endpointPolicyResponseHttpMock = httpHandlerMockFactory ActivityLog; }>; -export const endpointActivityLogHttpMock = httpHandlerMockFactory( - [ +export const endpointActivityLogHttpMock = + httpHandlerMockFactory([ { id: 'activityLogResponse', path: ENDPOINT_ACTION_LOG_ROUTE, @@ -148,14 +147,13 @@ export const endpointActivityLogHttpMock = httpHandlerMockFactory GetPackagesResponse; }>; -export const fleetGetPackageListHttpMock = httpHandlerMockFactory( - [ +export const fleetGetPackageListHttpMock = + httpHandlerMockFactory([ { id: 'packageList', method: 'get', @@ -168,14 +166,13 @@ export const fleetGetPackageListHttpMock = httpHandlerMockFactory GetAgentPoliciesResponse; }>; -export const fleetGetAgentPolicyListHttpMock = httpHandlerMockFactory( - [ +export const fleetGetAgentPolicyListHttpMock = + httpHandlerMockFactory([ { id: 'agentPolicy', path: AGENT_POLICY_API_ROUTES.LIST_PATTERN, @@ -201,15 +198,14 @@ export const fleetGetAgentPolicyListHttpMock = httpHandlerMockFactory CheckPermissionsResponse; }>; -export const fleetGetCheckPermissionsHttpMock = httpHandlerMockFactory( - [ +export const fleetGetCheckPermissionsHttpMock = + httpHandlerMockFactory([ { id: 'checkPermissions', path: appRoutesService.getCheckPermissionsPath(), @@ -221,8 +217,7 @@ export const fleetGetCheckPermissionsHttpMock = httpHandlerMockFactory + const updatedLogDataItems = ( + [...new Set([...lastLoadedLogData.data, ...activityLog.data])] as ActivityLog['data'] + ).sort((a, b) => new Date(b.item.data['@timestamp']) > new Date(a.item.data['@timestamp']) ? 1 : -1 ); diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts index e830bcfde8a06..a6fed8d6528cf 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts @@ -70,11 +70,8 @@ export const policyItemsLoading = (state: Immutable) => state.pol export const selectedPolicyId = (state: Immutable) => state.selectedPolicyId; export const endpointPackageInfo = (state: Immutable) => state.endpointPackageInfo; -export const getIsEndpointPackageInfoUninitialized: ( - state: Immutable -) => boolean = createSelector(endpointPackageInfo, (packageInfo) => - isUninitialisedResourceState(packageInfo) -); +export const getIsEndpointPackageInfoUninitialized: (state: Immutable) => boolean = + createSelector(endpointPackageInfo, (packageInfo) => isUninitialisedResourceState(packageInfo)); export const isAutoRefreshEnabled = (state: Immutable) => state.isAutoRefreshEnabled; @@ -146,8 +143,9 @@ export const policyResponseFailedOrWarningActionCount: ( Object.entries(applied.response.configurations).map(([key, val]) => { let count = 0; for (const action of val.concerned_actions) { - const actionStatus = applied.actions.find((policyActions) => policyActions.name === action) - ?.status; + const actionStatus = applied.actions.find( + (policyActions) => policyActions.name === action + )?.status; if ( actionStatus === HostPolicyResponseActionStatus.failure || actionStatus === HostPolicyResponseActionStatus.warning @@ -252,11 +250,10 @@ export const hasSelectedEndpoint: (state: Immutable) => boolean = ); /** What policy details panel view to show */ -export const showView: ( - state: EndpointState -) => EndpointIndexUIQueryParams['show'] = createSelector(uiQueryParams, (searchParams) => { - return searchParams.show ?? 'details'; -}); +export const showView: (state: EndpointState) => EndpointIndexUIQueryParams['show'] = + createSelector(uiQueryParams, (searchParams) => { + return searchParams.show ?? 'details'; + }); /** * Returns the selected endpoint's elastic agent Id @@ -321,7 +318,7 @@ export const searchBarQuery: (state: Immutable) => Query = create ({ admin_query: adminQuery }) => { const decodedQuery: Query = { query: '', language: 'kuery' }; if (adminQuery) { - const urlDecodedQuery = (decode(adminQuery) as unknown) as Query; + const urlDecodedQuery = decode(adminQuery) as unknown as Query; if (urlDecodedQuery && typeof urlDecodedQuery.query === 'string') { decodedQuery.query = urlDecodedQuery.query; } @@ -343,17 +340,15 @@ export const getCurrentIsolationRequestState = ( return state.isolationRequestState; }; -export const getIsIsolationRequestPending: ( - state: Immutable -) => boolean = createSelector(getCurrentIsolationRequestState, (isolateHost) => - isLoadingResourceState(isolateHost) -); +export const getIsIsolationRequestPending: (state: Immutable) => boolean = + createSelector(getCurrentIsolationRequestState, (isolateHost) => + isLoadingResourceState(isolateHost) + ); -export const getWasIsolationRequestSuccessful: ( - state: Immutable -) => boolean = createSelector(getCurrentIsolationRequestState, (isolateHost) => - isLoadedResourceState(isolateHost) -); +export const getWasIsolationRequestSuccessful: (state: Immutable) => boolean = + createSelector(getCurrentIsolationRequestState, (isolateHost) => + isLoadedResourceState(isolateHost) + ); export const getIsolationRequestError: ( state: Immutable @@ -363,11 +358,10 @@ export const getIsolationRequestError: ( } }); -export const getIsOnEndpointDetailsActivityLog: ( - state: Immutable -) => boolean = createSelector(uiQueryParams, (searchParams) => { - return searchParams.show === EndpointDetailsTabsTypes.activityLog; -}); +export const getIsOnEndpointDetailsActivityLog: (state: Immutable) => boolean = + createSelector(uiQueryParams, (searchParams) => { + return searchParams.show === EndpointDetailsTabsTypes.activityLog; + }); export const getActivityLogDataPaging = ( state: Immutable @@ -386,23 +380,14 @@ export const getLastLoadedActivityLogData: ( return getLastLoadedResourceState(activityLog)?.data; }); -export const getActivityLogUninitialized: ( - state: Immutable -) => boolean = createSelector(getActivityLogData, (activityLog) => - isUninitialisedResourceState(activityLog) -); +export const getActivityLogUninitialized: (state: Immutable) => boolean = + createSelector(getActivityLogData, (activityLog) => isUninitialisedResourceState(activityLog)); -export const getActivityLogRequestLoading: ( - state: Immutable -) => boolean = createSelector(getActivityLogData, (activityLog) => - isLoadingResourceState(activityLog) -); +export const getActivityLogRequestLoading: (state: Immutable) => boolean = + createSelector(getActivityLogData, (activityLog) => isLoadingResourceState(activityLog)); -export const getActivityLogRequestLoaded: ( - state: Immutable -) => boolean = createSelector(getActivityLogData, (activityLog) => - isLoadedResourceState(activityLog) -); +export const getActivityLogRequestLoaded: (state: Immutable) => boolean = + createSelector(getActivityLogData, (activityLog) => isLoadedResourceState(activityLog)); export const getActivityLogIterableData: ( state: Immutable @@ -411,13 +396,12 @@ export const getActivityLogIterableData: ( return isLoadedResourceState(activityLog) ? activityLog.data.data : emptyArray; }); -export const getActivityLogError: ( - state: Immutable -) => ServerApiError | undefined = createSelector(getActivityLogData, (activityLog) => { - if (isFailedResourceState(activityLog)) { - return activityLog.error; - } -}); +export const getActivityLogError: (state: Immutable) => ServerApiError | undefined = + createSelector(getActivityLogData, (activityLog) => { + if (isFailedResourceState(activityLog)) { + return activityLog.error; + } + }); // returns a true if either lgo is uninitialised // or if it has failed an api call after having fetched a non empty log list earlier diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/endpoint_agent_status.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/endpoint_agent_status.test.tsx index be7fef156815b..164b69b3f8bb6 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/endpoint_agent_status.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/endpoint_agent_status.test.tsx @@ -61,7 +61,8 @@ describe('When using the EndpointAgentStatus component', () => { describe.skip('and host is isolated or pending isolation', () => { beforeEach(async () => { // Ensure pending action api sets pending action for the test endpoint metadata - const pendingActionsResponseProvider = httpMocks.responseProvider.pendingActions.getMockImplementation(); + const pendingActionsResponseProvider = + httpMocks.responseProvider.pendingActions.getMockImplementation(); httpMocks.responseProvider.pendingActions.mockImplementation((...args) => { const response = pendingActionsResponseProvider!(...args); response.data.some((pendingAction) => { diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/search_bar.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/search_bar.test.tsx index 624a3c265c4c0..1cb21c7da1703 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/search_bar.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/search_bar.test.tsx @@ -92,7 +92,7 @@ describe('when rendering the endpoint list `AdminSearchBar`', () => { ])( 'should update the url and exclude the `admin_query` param when %s was entered', async (_, value) => { - await render(); + await render({ admin_query: "(language:kuery,query:'foo')" }); await submitQuery(value); expect(getQueryParamsFromStore().admin_query).toBe(undefined); diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/search_bar.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/search_bar.tsx index e5ae0bc8aa4d5..18d22e0cd1b15 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/search_bar.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/search_bar.tsx @@ -24,7 +24,7 @@ const AdminQueryBar = styled.div` export const AdminSearchBar = memo(() => { const history = useHistory(); - const queryParams = useEndpointSelector(selectors.uiQueryParams); + const { admin_query: _, ...queryParams } = useEndpointSelector(selectors.uiQueryParams); const searchBarIndexPatterns = useEndpointSelector(selectors.patterns); const searchBarQuery = useEndpointSelector(selectors.searchBarQuery); const clonedIndexPatterns = useMemo( @@ -40,7 +40,7 @@ export const AdminSearchBar = memo(() => { // ensure we reset the page back to the first one, so that user id not (possibly) being left on an invalid page page_index: '0', ...(params.query?.query.trim() - ? { admin_query: encode((params.query as unknown) as RisonValue) } + ? { admin_query: encode(params.query as unknown as RisonValue) } : {}), }) ); 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 index 30ab082559c7b..05887d82cacad 100644 --- 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 @@ -44,14 +44,8 @@ const StickyFlexItem = styled(EuiFlexItem)` export const DateRangePicker = memo(() => { const dispatch = useDispatch(); - const { - page, - pageSize, - startDate, - endDate, - autoRefreshOptions, - recentlyUsedDateRanges, - } = useEndpointSelector(getActivityLogDataPaging); + const { page, pageSize, startDate, endDate, autoRefreshOptions, recentlyUsedDateRanges } = + useEndpointSelector(getActivityLogDataPaging); const activityLogLoading = useEndpointSelector(getActivityLogRequestLoading); 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 f0b6b5fbc8962..aa8df6ecf5613 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 @@ -53,9 +53,13 @@ export const EndpointActivityLog = memo( const activityLogSize = activityLogData.length; const activityLogError = useEndpointSelector(getActivityLogError); const dispatch = useDispatch<(action: EndpointAction) => void>(); - const { page, pageSize, startDate, endDate, disabled: isPagingDisabled } = useEndpointSelector( - getActivityLogDataPaging - ); + const { + page, + pageSize, + startDate, + endDate, + disabled: isPagingDisabled, + } = useEndpointSelector(getActivityLogDataPaging); const hasActiveDateRange = useMemo(() => !!startDate || !!endDate, [startDate, endDate]); const showEmptyState = useMemo( diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/index.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/index.tsx index 84b0bdad4d6eb..5b490cbb92ac5 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/index.tsx @@ -16,10 +16,8 @@ import { EndpointDetails } from './endpoint_details'; export const EndpointDetailsFlyout = memo(() => { const history = useHistory(); const queryParams = useEndpointSelector(uiQueryParams); - const { - selected_endpoint: selectedEndpoint, - ...queryParamsWithoutSelectedEndpoint - } = queryParams; + const { selected_endpoint: selectedEndpoint, ...queryParamsWithoutSelectedEndpoint } = + queryParams; const handleFlyoutClose = useCallback(() => { const { show: _show, ...urlSearchParams } = queryParamsWithoutSelectedEndpoint; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/host_constants.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/host_constants.ts index 45cf7d725443d..8e0e4fd969c22 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/host_constants.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/host_constants.ts @@ -8,11 +8,9 @@ import { i18n } from '@kbn/i18n'; import { HostStatus, HostPolicyResponseActionStatus } from '../../../../../common/endpoint/types'; -export const HOST_STATUS_TO_BADGE_COLOR = Object.freeze< - { - [key in HostStatus]: string; - } ->({ +export const HOST_STATUS_TO_BADGE_COLOR = Object.freeze<{ + [key in HostStatus]: string; +}>({ [HostStatus.HEALTHY]: 'secondary', [HostStatus.UNHEALTHY]: 'warning', [HostStatus.UPDATING]: 'primary', @@ -21,27 +19,27 @@ export const HOST_STATUS_TO_BADGE_COLOR = Object.freeze< [HostStatus.UNENROLLED]: 'default', }); -export const POLICY_STATUS_TO_HEALTH_COLOR = Object.freeze< - { [key in keyof typeof HostPolicyResponseActionStatus]: string } ->({ +export const POLICY_STATUS_TO_HEALTH_COLOR = Object.freeze<{ + [key in keyof typeof HostPolicyResponseActionStatus]: string; +}>({ success: 'secondary', warning: 'warning', failure: 'danger', unsupported: 'default', }); -export const POLICY_STATUS_TO_BADGE_COLOR = Object.freeze< - { [key in keyof typeof HostPolicyResponseActionStatus]: string } ->({ +export const POLICY_STATUS_TO_BADGE_COLOR = Object.freeze<{ + [key in keyof typeof HostPolicyResponseActionStatus]: string; +}>({ success: 'secondary', warning: 'warning', failure: 'danger', unsupported: 'default', }); -export const POLICY_STATUS_TO_TEXT = Object.freeze< - { [key in keyof typeof HostPolicyResponseActionStatus]: string } ->({ +export const POLICY_STATUS_TO_TEXT = Object.freeze<{ + [key in keyof typeof HostPolicyResponseActionStatus]: string; +}>({ success: i18n.translate('xpack.securitySolution.policyStatusText.success', { defaultMessage: 'Success', }), diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx index 8af1498c24318..4f211a7073a50 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx @@ -221,9 +221,8 @@ export const EndpointList = () => { const PAD_LEFT: React.CSSProperties = useMemo(() => ({ paddingLeft: '6px' }), []); - const handleDeployEndpointsClick = useNavigateToAppEventHandler( - 'fleet', - { + const handleDeployEndpointsClick = + useNavigateToAppEventHandler('fleet', { path: `/policies/${selectedPolicyId}?openEnrollmentFlyout=true`, state: { onDoneNavigateTo: [ @@ -231,8 +230,7 @@ export const EndpointList = () => { { path: getEndpointListPath({ name: 'endpointList' }) }, ], }, - } - ); + }); const selectionOptions = useMemo(() => { return policyItems.map((item) => { diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/action.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/action.ts index c4432faef455f..4325c4d90951a 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/action.ts +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/action.ts @@ -18,9 +18,10 @@ export type EventFiltersListPageDataChanged = Action<'eventFiltersListPageDataCh payload: EventFiltersListPageState['listPage']['data']; }; -export type EventFiltersListPageDataExistsChanged = Action<'eventFiltersListPageDataExistsChanged'> & { - payload: EventFiltersListPageState['listPage']['dataExist']; -}; +export type EventFiltersListPageDataExistsChanged = + Action<'eventFiltersListPageDataExistsChanged'> & { + payload: EventFiltersListPageState['listPage']['dataExist']; + }; export type EventFilterForDeletion = Action<'eventFilterForDeletion'> & { payload: ExceptionListItemSchema; diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/middleware.ts index ad9e3d32a3f4c..60920a7420d16 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/middleware.ts @@ -349,6 +349,5 @@ export const createEventFiltersPageMiddleware = ( }; }; -export const eventFiltersPageMiddlewareFactory: ImmutableMiddlewareFactory = ( - coreStart -) => createEventFiltersPageMiddleware(new EventFiltersHttpService(coreStart.http)); +export const eventFiltersPageMiddlewareFactory: ImmutableMiddlewareFactory = + (coreStart) => createEventFiltersPageMiddleware(new EventFiltersHttpService(coreStart.http)); diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/reducer.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/reducer.ts index c90d4af769e06..353484492eb73 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/reducer.ts +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/reducer.ts @@ -66,18 +66,16 @@ const handleEventFiltersListPageDataChanges: CaseReducer = ( - state, - action -) => { - return { - ...state, - listPage: { - ...state.listPage, - dataExist: action.payload, - }, +const handleEventFiltersListPageDataExistChanges: CaseReducer = + (state, action) => { + return { + ...state, + listPage: { + ...state.listPage, + dataExist: action.payload, + }, + }; }; -}; const eventFiltersInitForm: CaseReducer = (state, action) => { return { diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/selector.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/selector.ts index 2fa196a053f78..8293c1a0452eb 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/selector.ts +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/selector.ts @@ -53,11 +53,10 @@ export const getListApiSuccessResponse: EventFiltersSelector< return getLastLoadedResourceState(listPageData)?.data.content; }); -export const getListItems: EventFiltersSelector< - Immutable -> = createSelector(getListApiSuccessResponse, (apiResponseData) => { - return apiResponseData?.data || []; -}); +export const getListItems: EventFiltersSelector> = + createSelector(getListApiSuccessResponse, (apiResponseData) => { + return apiResponseData?.data || []; + }); export const getTotalCountListItems: EventFiltersSelector> = createSelector( getListApiSuccessResponse, @@ -71,12 +70,10 @@ export const getTotalCountListItems: EventFiltersSelector> = c * of content is being loaded, this selector will then attempt to use the previousState to return * the query used. */ -export const getCurrentListItemsQuery: EventFiltersSelector = createSelector( - getCurrentListPageDataState, - (pageDataState) => { +export const getCurrentListItemsQuery: EventFiltersSelector = + createSelector(getCurrentListPageDataState, (pageDataState) => { return getLastLoadedResourceState(pageDataState)?.data.query ?? {}; - } -); + }); export const getListPagination: EventFiltersSelector = createSelector( getListApiSuccessResponse, @@ -91,15 +88,13 @@ export const getListPagination: EventFiltersSelector = createSelecto } ); -export const getListFetchError: EventFiltersSelector< - Immutable | undefined -> = createSelector(getCurrentListPageDataState, (listPageDataState) => { - return (isFailedResourceState(listPageDataState) && listPageDataState.error) || undefined; -}); +export const getListFetchError: EventFiltersSelector | undefined> = + createSelector(getCurrentListPageDataState, (listPageDataState) => { + return (isFailedResourceState(listPageDataState) && listPageDataState.error) || undefined; + }); -export const getListPageDataExistsState: EventFiltersSelector< - StoreState['listPage']['dataExist'] -> = ({ listPage: { dataExist } }) => dataExist; +export const getListPageDataExistsState: EventFiltersSelector = + ({ listPage: { dataExist } }) => dataExist; export const getListIsLoading: EventFiltersSelector = createSelector( getCurrentListPageDataState, @@ -201,9 +196,8 @@ export const showDeleteModal: EventFiltersSelector = createSelector( } ); -export const getItemToDelete: EventFiltersSelector< - StoreState['listPage']['deletion']['item'] -> = createSelector(getDeletionState, ({ item }) => item); +export const getItemToDelete: EventFiltersSelector = + createSelector(getDeletionState, ({ item }) => item); export const isDeletionInProgress: EventFiltersSelector = createSelector( getDeletionState, 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 fb2251072f03d..d170c21e6dea9 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 @@ -210,8 +210,8 @@ export const esResponseData = () => ({ /** * Mock `core.http` methods used by Event Filters List page */ -export const eventFiltersListQueryHttpMock = httpHandlerMockFactory( - [ +export const eventFiltersListQueryHttpMock = + httpHandlerMockFactory([ { id: 'eventFiltersCreateList', method: 'post', @@ -228,5 +228,4 @@ export const eventFiltersListQueryHttpMock = httpHandlerMockFactory { expect(notifications.toasts.addSuccess).not.toBeCalled(); expect(notifications.toasts.addDanger).toBeCalledWith( getCreationErrorMessage( - (store.getState()!.management!.eventFilters!.form! - .submissionResourceState as FailedResourceState).error + ( + store.getState()!.management!.eventFilters!.form! + .submissionResourceState as FailedResourceState + ).error ) ); }); @@ -188,8 +190,10 @@ describe('EventFiltersNotification', () => { expect(notifications.toasts.addSuccess).not.toBeCalled(); expect(notifications.toasts.addDanger).toBeCalledWith( getUpdateErrorMessage( - (store.getState()!.management!.eventFilters!.form! - .submissionResourceState as FailedResourceState).error + ( + store.getState()!.management!.eventFilters!.form! + .submissionResourceState as FailedResourceState + ).error ) ); }); @@ -216,8 +220,10 @@ describe('EventFiltersNotification', () => { expect(notifications.toasts.addSuccess).not.toBeCalled(); expect(notifications.toasts.addWarning).toBeCalledWith( getGetErrorMessage( - (store.getState()!.management!.eventFilters!.form! - .submissionResourceState as FailedResourceState).error + ( + store.getState()!.management!.eventFilters!.form! + .submissionResourceState as FailedResourceState + ).error ) ); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/models/policy_details_config.ts b/x-pack/plugins/security_solution/public/management/pages/policy/models/policy_details_config.ts index e99c63b5e1aa7..be552252e6fcf 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/models/policy_details_config.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/models/policy_details_config.ts @@ -11,29 +11,26 @@ import { UIPolicyConfig } from '../../../../../common/endpoint/types'; /** * Returns value from `configuration` */ -export const getIn = (a: UIPolicyConfig) => (key: Key) => < - SubKey extends keyof UIPolicyConfig[Key] ->( - subKey: SubKey -) => ( - leafKey: LeafKey -): UIPolicyConfig[Key][SubKey][LeafKey] => { - return a[key][subKey][leafKey]; -}; +export const getIn = + (a: UIPolicyConfig) => + (key: Key) => + (subKey: SubKey) => + ( + leafKey: LeafKey + ): UIPolicyConfig[Key][SubKey][LeafKey] => { + return a[key][subKey][leafKey]; + }; /** * Returns cloned `configuration` with `value` set by the `keyPath`. */ -export const setIn = (a: UIPolicyConfig) => (key: Key) => < - SubKey extends keyof UIPolicyConfig[Key] ->( - subKey: SubKey -) => (leafKey: LeafKey) => < - V extends UIPolicyConfig[Key][SubKey][LeafKey] ->( - v: V -): UIPolicyConfig => { - const c = cloneDeep(a); - c[key][subKey][leafKey] = v; - return c; -}; +export const setIn = + (a: UIPolicyConfig) => + (key: Key) => + (subKey: SubKey) => + (leafKey: LeafKey) => + (v: V): UIPolicyConfig => { + const c = cloneDeep(a); + c[key][subKey][leafKey] = v; + return c; + }; diff --git a/x-pack/plugins/uptime/public/components/overview/filter_group/index.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/action/index.ts similarity index 53% rename from x-pack/plugins/uptime/public/components/overview/filter_group/index.ts rename to x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/action/index.ts index befe65560dfd6..ab84bb4f253ea 100644 --- a/x-pack/plugins/uptime/public/components/overview/filter_group/index.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/action/index.ts @@ -5,5 +5,7 @@ * 2.0. */ -export { FilterGroupComponent } from './filter_group'; -export { FilterGroup } from './filter_group_container'; +import { PolicySettingsAction } from './policy_settings_action'; +import { PolicyTrustedAppsAction } from './policy_trusted_apps_action'; + +export type PolicyDetailsAction = PolicySettingsAction | PolicyTrustedAppsAction; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/action.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/action/policy_settings_action.ts similarity index 83% rename from x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/action.ts rename to x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/action/policy_settings_action.ts index 6bd39e9c24f96..eec0ab1c6445c 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/action.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/action/policy_settings_action.ts @@ -5,11 +5,11 @@ * 2.0. */ -import { ILicense } from '../../../../../../../licensing/common/types'; -import { GetAgentStatusResponse } from '../../../../../../../fleet/common/types/rest_spec'; -import { PolicyData, UIPolicyConfig } from '../../../../../../common/endpoint/types'; -import { ServerApiError } from '../../../../../common/types'; -import { PolicyDetailsState } from '../../types'; +import { ILicense } from '../../../../../../../../licensing/common/types'; +import { GetAgentStatusResponse } from '../../../../../../../../fleet/common/types/rest_spec'; +import { PolicyData, UIPolicyConfig } from '../../../../../../../common/endpoint/types'; +import { ServerApiError } from '../../../../../../common/types'; +import { PolicyDetailsState } from '../../../types'; export interface ServerReturnedPolicyDetailsData { type: 'serverReturnedPolicyDetailsData'; @@ -69,7 +69,7 @@ export interface LicenseChanged { payload: ILicense; } -export type PolicyDetailsAction = +export type PolicySettingsAction = | ServerReturnedPolicyDetailsData | UserClickedPolicyDetailsSaveButton | ServerReturnedPolicyDetailsAgentSummaryData diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/action/policy_trusted_apps_action.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/action/policy_trusted_apps_action.ts new file mode 100644 index 0000000000000..46e0f8293cc33 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/action/policy_trusted_apps_action.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +// TODO: defined trusted apps actions (code below only here to silence TS) +export type PolicyTrustedAppsAction = + | { + type: 'a'; + } + | { type: 'b' }; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/index.test.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/index.test.ts index 4ef7adf4d9425..479da6b873480 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/index.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/index.test.ts @@ -251,9 +251,9 @@ describe('policy details: ', () => { expect(http.put).toHaveBeenCalledTimes(2); - const lastPutCallPayload = ((http.put.mock.calls[ - http.put.mock.calls.length - 1 - ] as unknown) as [string, HttpFetchOptions])[1]; + const lastPutCallPayload = ( + http.put.mock.calls[http.put.mock.calls.length - 1] as unknown as [string, HttpFetchOptions] + )[1]; // license is below platinum in this test, paid features are off expect(JSON.parse(lastPutCallPayload.body as string)).toEqual({ diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/index.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/index.ts index bc83c7438eba8..bc9e42ddf7f52 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/index.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/index.ts @@ -11,8 +11,7 @@ import { AppAction } from '../../../../../common/store/actions'; import { Immutable } from '../../../../../../common/endpoint/types'; export { policyDetailsMiddlewareFactory } from './middleware'; -export { PolicyDetailsAction } from './action'; -export { policyDetailsReducer } from './reducer'; +export { policyDetailsReducer, initialPolicyDetailsState } from './reducer'; export interface EndpointPolicyDetailsStatePluginState { policyDetails: Immutable; @@ -21,3 +20,4 @@ export interface EndpointPolicyDetailsStatePluginState { export interface EndpointPolicyDetailsStatePluginReducer { policyDetails: ImmutableReducer; } +export { PolicyDetailsAction } from './action'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware.ts deleted file mode 100644 index 250d9c82b6489..0000000000000 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware.ts +++ /dev/null @@ -1,146 +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 { IHttpFetchError } from 'kibana/public'; -import { - DefaultPolicyNotificationMessage, - DefaultPolicyRuleNotificationMessage, -} from '../../../../../../common/endpoint/models/policy_config'; -import { PolicyDetailsState, UpdatePolicyResponse } from '../../types'; -import { - policyIdFromParams, - isOnPolicyDetailsPage, - policyDetails, - policyDetailsForUpdate, - needsToRefresh, -} from './selectors'; -import { - sendGetPackagePolicy, - sendGetFleetAgentStatusForPolicy, - sendPutPackagePolicy, -} from '../services/ingest'; -import { NewPolicyData, PolicyData } from '../../../../../../common/endpoint/types'; -import { ImmutableMiddlewareFactory } from '../../../../../common/store'; -import { getPolicyDataForUpdate } from '../../../../../../common/endpoint/service/policy/get_policy_data_for_update'; - -export const policyDetailsMiddlewareFactory: ImmutableMiddlewareFactory = ( - coreStart -) => { - const http = coreStart.http; - return ({ getState, dispatch }) => (next) => async (action) => { - next(action); - const state = getState(); - - if (action.type === 'userChangedUrl' && needsToRefresh(state) && isOnPolicyDetailsPage(state)) { - const id = policyIdFromParams(state); - let policyItem: PolicyData; - - try { - policyItem = (await sendGetPackagePolicy(http, id)).item; - // sets default user notification message if policy config message is empty - if (policyItem.inputs[0].config.policy.value.windows.popup.malware.message === '') { - policyItem.inputs[0].config.policy.value.windows.popup.malware.message = DefaultPolicyNotificationMessage; - policyItem.inputs[0].config.policy.value.mac.popup.malware.message = DefaultPolicyNotificationMessage; - policyItem.inputs[0].config.policy.value.linux.popup.malware.message = DefaultPolicyNotificationMessage; - } - if (policyItem.inputs[0].config.policy.value.windows.popup.ransomware.message === '') { - policyItem.inputs[0].config.policy.value.windows.popup.ransomware.message = DefaultPolicyNotificationMessage; - } - if ( - policyItem.inputs[0].config.policy.value.windows.popup.memory_protection.message === '' - ) { - policyItem.inputs[0].config.policy.value.windows.popup.memory_protection.message = DefaultPolicyRuleNotificationMessage; - } - if ( - policyItem.inputs[0].config.policy.value.windows.popup.behavior_protection.message === '' - ) { - policyItem.inputs[0].config.policy.value.windows.popup.behavior_protection.message = DefaultPolicyRuleNotificationMessage; - } - if (policyItem.inputs[0].config.policy.value.mac.popup.behavior_protection.message === '') { - policyItem.inputs[0].config.policy.value.mac.popup.behavior_protection.message = DefaultPolicyRuleNotificationMessage; - } - if ( - policyItem.inputs[0].config.policy.value.linux.popup.behavior_protection.message === '' - ) { - policyItem.inputs[0].config.policy.value.linux.popup.behavior_protection.message = DefaultPolicyRuleNotificationMessage; - } - } catch (error) { - dispatch({ - type: 'serverFailedToReturnPolicyDetailsData', - payload: error.body || error, - }); - return; - } - - dispatch({ - type: 'serverReturnedPolicyDetailsData', - payload: { - policyItem, - }, - }); - - // Agent summary is secondary data, so its ok for it to come after the details - // page is populated with the main content - if (policyItem.policy_id) { - const { results } = await sendGetFleetAgentStatusForPolicy(http, policyItem.policy_id); - dispatch({ - type: 'serverReturnedPolicyDetailsAgentSummaryData', - payload: { - agentStatusSummary: results, - }, - }); - } - } else if (action.type === 'userClickedPolicyDetailsSaveButton') { - const { id } = policyDetails(state) as PolicyData; - const updatedPolicyItem = policyDetailsForUpdate(state) as NewPolicyData; - - let apiResponse: UpdatePolicyResponse; - try { - apiResponse = await sendPutPackagePolicy(http, id, updatedPolicyItem).catch( - (error: IHttpFetchError) => { - if (!error.response || error.response.status !== 409) { - return Promise.reject(error); - } - // Handle 409 error (version conflict) here, by using the latest document - // for the package policy and adding the updated policy to it, ensuring that - // any recent updates to `manifest_artifacts` are retained. - return sendGetPackagePolicy(http, id).then((packagePolicy) => { - const latestUpdatedPolicyItem = packagePolicy.item; - latestUpdatedPolicyItem.inputs[0].config.policy = - updatedPolicyItem.inputs[0].config.policy; - - return sendPutPackagePolicy( - http, - id, - getPolicyDataForUpdate(latestUpdatedPolicyItem) - ); - }); - } - ); - } catch (error) { - dispatch({ - type: 'serverReturnedPolicyDetailsUpdateFailure', - payload: { - success: false, - error: error.body || error, - }, - }); - return; - } - - dispatch({ - type: 'serverReturnedUpdatedPolicyDetailsData', - payload: { - policyItem: apiResponse.item, - updateStatus: { - success: true, - }, - }, - }); - } - }; -}; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware/index.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware/index.ts new file mode 100644 index 0000000000000..6b7e4e7d541c8 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware/index.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ImmutableMiddlewareFactory } from '../../../../../../common/store'; +import { PolicyDetailsState } from '../../../types'; +import { policyTrustedAppsMiddlewareRunner } from './policy_trusted_apps_middleware'; +import { policySettingsMiddlewareRunner } from './policy_settings_middleware'; + +export const policyDetailsMiddlewareFactory: ImmutableMiddlewareFactory = ( + coreStart +) => { + return (store) => (next) => async (action) => { + next(action); + + policySettingsMiddlewareRunner(coreStart, store, action); + policyTrustedAppsMiddlewareRunner(coreStart, store, action); + }; +}; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware/policy_settings_middleware.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware/policy_settings_middleware.ts new file mode 100644 index 0000000000000..73b244944e502 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware/policy_settings_middleware.ts @@ -0,0 +1,144 @@ +/* + * 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 { IHttpFetchError } from 'kibana/public'; +import { + DefaultPolicyNotificationMessage, + DefaultPolicyRuleNotificationMessage, +} from '../../../../../../../common/endpoint/models/policy_config'; +import { MiddlewareRunner, UpdatePolicyResponse } from '../../../types'; +import { + policyIdFromParams, + isOnPolicyDetailsPage, + policyDetails, + policyDetailsForUpdate, + needsToRefresh, +} from '../selectors/policy_settings_selectors'; +import { + sendGetPackagePolicy, + sendGetFleetAgentStatusForPolicy, + sendPutPackagePolicy, +} from '../../services/ingest'; +import { NewPolicyData, PolicyData } from '../../../../../../../common/endpoint/types'; +import { getPolicyDataForUpdate } from '../../../../../../../common/endpoint/service/policy'; + +export const policySettingsMiddlewareRunner: MiddlewareRunner = async ( + coreStart, + { dispatch, getState }, + action +) => { + const http = coreStart.http; + const state = getState(); + + if (action.type === 'userChangedUrl' && needsToRefresh(state) && isOnPolicyDetailsPage(state)) { + const id = policyIdFromParams(state); + let policyItem: PolicyData; + + try { + policyItem = (await sendGetPackagePolicy(http, id)).item; + // sets default user notification message if policy config message is empty + if (policyItem.inputs[0].config.policy.value.windows.popup.malware.message === '') { + policyItem.inputs[0].config.policy.value.windows.popup.malware.message = + DefaultPolicyNotificationMessage; + policyItem.inputs[0].config.policy.value.mac.popup.malware.message = + DefaultPolicyNotificationMessage; + policyItem.inputs[0].config.policy.value.linux.popup.malware.message = + DefaultPolicyNotificationMessage; + } + if (policyItem.inputs[0].config.policy.value.windows.popup.ransomware.message === '') { + policyItem.inputs[0].config.policy.value.windows.popup.ransomware.message = + DefaultPolicyNotificationMessage; + } + if (policyItem.inputs[0].config.policy.value.windows.popup.memory_protection.message === '') { + policyItem.inputs[0].config.policy.value.windows.popup.memory_protection.message = + DefaultPolicyRuleNotificationMessage; + } + if ( + policyItem.inputs[0].config.policy.value.windows.popup.behavior_protection.message === '' + ) { + policyItem.inputs[0].config.policy.value.windows.popup.behavior_protection.message = + DefaultPolicyRuleNotificationMessage; + } + if (policyItem.inputs[0].config.policy.value.mac.popup.behavior_protection.message === '') { + policyItem.inputs[0].config.policy.value.mac.popup.behavior_protection.message = + DefaultPolicyRuleNotificationMessage; + } + if (policyItem.inputs[0].config.policy.value.linux.popup.behavior_protection.message === '') { + policyItem.inputs[0].config.policy.value.linux.popup.behavior_protection.message = + DefaultPolicyRuleNotificationMessage; + } + } catch (error) { + dispatch({ + type: 'serverFailedToReturnPolicyDetailsData', + payload: error.body || error, + }); + return; + } + + dispatch({ + type: 'serverReturnedPolicyDetailsData', + payload: { + policyItem, + }, + }); + + // Agent summary is secondary data, so its ok for it to come after the details + // page is populated with the main content + if (policyItem.policy_id) { + const { results } = await sendGetFleetAgentStatusForPolicy(http, policyItem.policy_id); + dispatch({ + type: 'serverReturnedPolicyDetailsAgentSummaryData', + payload: { + agentStatusSummary: results, + }, + }); + } + } else if (action.type === 'userClickedPolicyDetailsSaveButton') { + const { id } = policyDetails(state) as PolicyData; + const updatedPolicyItem = policyDetailsForUpdate(state) as NewPolicyData; + + let apiResponse: UpdatePolicyResponse; + try { + apiResponse = await sendPutPackagePolicy(http, id, updatedPolicyItem).catch( + (error: IHttpFetchError) => { + if (!error.response || error.response.status !== 409) { + return Promise.reject(error); + } + // Handle 409 error (version conflict) here, by using the latest document + // for the package policy and adding the updated policy to it, ensuring that + // any recent updates to `manifest_artifacts` are retained. + return sendGetPackagePolicy(http, id).then((packagePolicy) => { + const latestUpdatedPolicyItem = packagePolicy.item; + latestUpdatedPolicyItem.inputs[0].config.policy = + updatedPolicyItem.inputs[0].config.policy; + + return sendPutPackagePolicy(http, id, getPolicyDataForUpdate(latestUpdatedPolicyItem)); + }); + } + ); + } catch (error) { + dispatch({ + type: 'serverReturnedPolicyDetailsUpdateFailure', + payload: { + success: false, + error: error.body || error, + }, + }); + return; + } + + dispatch({ + type: 'serverReturnedUpdatedPolicyDetailsData', + payload: { + policyItem: apiResponse.item, + updateStatus: { + success: true, + }, + }, + }); + } +}; diff --git a/x-pack/plugins/uptime/public/state/api/index_pattern.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware/policy_trusted_apps_middleware.ts similarity index 54% rename from x-pack/plugins/uptime/public/state/api/index_pattern.ts rename to x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware/policy_trusted_apps_middleware.ts index d41c955997bf4..171bbd881302e 100644 --- a/x-pack/plugins/uptime/public/state/api/index_pattern.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware/policy_trusted_apps_middleware.ts @@ -5,9 +5,12 @@ * 2.0. */ -import { API_URLS } from '../../../common/constants'; -import { apiService } from './utils'; +import { MiddlewareRunner } from '../../../types'; -export const fetchIndexPattern = async () => { - return await apiService.get(API_URLS.INDEX_PATTERN); +export const policyTrustedAppsMiddlewareRunner: MiddlewareRunner = async ( + coreStart, + store, + action +) => { + // FIXME: implement middlware for trusted apps }; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer/index.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer/index.ts new file mode 100644 index 0000000000000..a577c1ca85ef0 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer/index.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ImmutableReducer } from '../../../../../../common/store'; +import { PolicyDetailsState } from '../../../types'; +import { AppAction } from '../../../../../../common/store/actions'; +import { policySettingsReducer } from './policy_settings_reducer'; +import { initialPolicyDetailsState } from './initial_policy_details_state'; +import { policyTrustedAppsReducer } from './trusted_apps_reducer'; + +export * from './initial_policy_details_state'; + +export const policyDetailsReducer: ImmutableReducer = ( + state = initialPolicyDetailsState(), + action +) => { + return [policySettingsReducer, policyTrustedAppsReducer].reduce( + (updatedState, runReducer) => runReducer(updatedState, action), + state + ); +}; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer/initial_policy_details_state.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer/initial_policy_details_state.ts new file mode 100644 index 0000000000000..723f8fe31bd2a --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer/initial_policy_details_state.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Immutable } from '../../../../../../../common/endpoint/types'; +import { PolicyDetailsState } from '../../../types'; +import { + MANAGEMENT_DEFAULT_PAGE, + MANAGEMENT_DEFAULT_PAGE_SIZE, +} from '../../../../../common/constants'; +import { createUninitialisedResourceState } from '../../../../../state'; + +/** + * Return a fresh copy of initial state, since we mutate state in the reducer. + */ +export const initialPolicyDetailsState: () => Immutable = () => ({ + policyItem: undefined, + isLoading: false, + agentStatusSummary: { + error: 0, + events: 0, + offline: 0, + online: 0, + total: 0, + other: 0, + }, + artifacts: { + location: { + page_index: MANAGEMENT_DEFAULT_PAGE, + page_size: MANAGEMENT_DEFAULT_PAGE_SIZE, + show: undefined, + filter: '', + }, + availableList: createUninitialisedResourceState(), + }, +}); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer/policy_settings_reducer.ts similarity index 81% rename from x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer.ts rename to x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer/policy_settings_reducer.ts index 512059e9c3aab..9997e547e8148 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer/policy_settings_reducer.ts @@ -5,16 +5,20 @@ * 2.0. */ -import { fullPolicy, isOnPolicyDetailsPage, license } from './selectors'; +// eslint-disable-next-line import/no-nodejs-modules +import { parse } from 'querystring'; +import { fullPolicy, isOnPolicyDetailsPage, license } from '../selectors/policy_settings_selectors'; import { Immutable, PolicyConfig, - UIPolicyConfig, PolicyData, -} from '../../../../../../common/endpoint/types'; -import { ImmutableReducer } from '../../../../../common/store'; -import { AppAction } from '../../../../../common/store/actions'; -import { PolicyDetailsState } from '../../types'; + UIPolicyConfig, +} from '../../../../../../../common/endpoint/types'; +import { ImmutableReducer } from '../../../../../../common/store'; +import { AppAction } from '../../../../../../common/store/actions'; +import { PolicyDetailsState } from '../../../types'; +import { extractPolicyDetailsArtifactsListPageLocation } from '../../../../../common/routing'; +import { initialPolicyDetailsState } from './initial_policy_details_state'; const updatePolicyConfigInPolicyData = ( policyData: Immutable, @@ -33,23 +37,7 @@ const updatePolicyConfigInPolicyData = ( })), }); -/** - * Return a fresh copy of initial state, since we mutate state in the reducer. - */ -export const initialPolicyDetailsState: () => Immutable = () => ({ - policyItem: undefined, - isLoading: false, - agentStatusSummary: { - error: 0, - events: 0, - offline: 0, - online: 0, - total: 0, - other: 0, - }, -}); - -export const policyDetailsReducer: ImmutableReducer = ( +export const policySettingsReducer: ImmutableReducer = ( state = initialPolicyDetailsState(), action ) => { @@ -106,6 +94,12 @@ export const policyDetailsReducer: ImmutableReducer = { ...state, location: action.payload, + artifacts: { + ...state.artifacts, + location: extractPolicyDetailsArtifactsListPageLocation( + parse(action.payload.search.slice(1)) + ), + }, }; const isCurrentlyOnDetailsPage = isOnPolicyDetailsPage(newState); const wasPreviouslyOnDetailsPage = isOnPolicyDetailsPage(state); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer/trusted_apps_reducer.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer/trusted_apps_reducer.ts new file mode 100644 index 0000000000000..7f2f9e437ca06 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer/trusted_apps_reducer.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ImmutableReducer } from '../../../../../../common/store'; +import { PolicyDetailsState } from '../../../types'; +import { AppAction } from '../../../../../../common/store/actions'; +import { initialPolicyDetailsState } from './initial_policy_details_state'; + +export const policyTrustedAppsReducer: ImmutableReducer = ( + state = initialPolicyDetailsState(), + action +) => { + // FIXME: implement trusted apps reducer + return state; +}; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors/index.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors/index.ts new file mode 100644 index 0000000000000..af5be5c39480e --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './policy_settings_selectors'; +export * from './trusted_apps_selectors'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors/policy_settings_selectors.ts similarity index 90% rename from x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors.ts rename to x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors/policy_settings_selectors.ts index da4409fc4409e..23ab0fd73c9e1 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors/policy_settings_selectors.ts @@ -7,23 +7,23 @@ import { createSelector } from 'reselect'; import { matchPath } from 'react-router-dom'; -import { ILicense } from '../../../../../../../licensing/common/types'; -import { unsetPolicyFeaturesAccordingToLicenseLevel } from '../../../../../../common/license/policy_config'; -import { PolicyDetailsState } from '../../types'; +import { ILicense } from '../../../../../../../../licensing/common/types'; +import { unsetPolicyFeaturesAccordingToLicenseLevel } from '../../../../../../../common/license/policy_config'; +import { PolicyDetailsArtifactsPageLocation, PolicyDetailsState } from '../../../types'; import { Immutable, NewPolicyData, PolicyConfig, PolicyData, UIPolicyConfig, -} from '../../../../../../common/endpoint/types'; -import { policyFactory as policyConfigFactory } from '../../../../../../common/endpoint/models/policy_config'; +} from '../../../../../../../common/endpoint/types'; +import { policyFactory as policyConfigFactory } from '../../../../../../../common/endpoint/models/policy_config'; import { MANAGEMENT_ROUTING_POLICY_DETAILS_FORM_PATH, MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_APPS_PATH, -} from '../../../../common/constants'; -import { ManagementRoutePolicyDetailsParams } from '../../../../types'; -import { getPolicyDataForUpdate } from '../../../../../../common/endpoint/service/policy/get_policy_data_for_update'; +} from '../../../../../common/constants'; +import { ManagementRoutePolicyDetailsParams } from '../../../../../types'; +import { getPolicyDataForUpdate } from '../../../../../../../common/endpoint/service/policy/get_policy_data_for_update'; /** Returns the policy details */ export const policyDetails = (state: Immutable) => state.policyItem; @@ -80,6 +80,13 @@ export const needsToRefresh = (state: Immutable): boolean => return !state.policyItem && !state.apiError; }; +/** + * Returns current artifacts location + */ +export const getCurrentArtifactsLocation = ( + state: Immutable +): Immutable => state.artifacts.location; + /** Returns a boolean of whether the user is on the policy form page or not */ export const isOnPolicyFormPage = (state: Immutable) => { return ( @@ -138,9 +145,8 @@ export const fullPolicy: (s: Immutable) => PolicyConfig = cr } ); -const fullWindowsPolicySettings: ( - s: PolicyDetailsState -) => PolicyConfig['windows'] = createSelector(fullPolicy, (policy) => policy?.windows); +const fullWindowsPolicySettings: (s: PolicyDetailsState) => PolicyConfig['windows'] = + createSelector(fullPolicy, (policy) => policy?.windows); const fullMacPolicySettings: (s: PolicyDetailsState) => PolicyConfig['mac'] = createSelector( fullPolicy, diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors/trusted_apps_selectors.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors/trusted_apps_selectors.ts new file mode 100644 index 0000000000000..f7a568b5ade0e --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors/trusted_apps_selectors.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const isOnTrustedAppsView = () => { + return true; +}; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/types.ts b/x-pack/plugins/security_solution/public/management/pages/policy/types.ts index 6d767df73cd1c..9000fb469afd3 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/types.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/types.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { CoreStart } from 'kibana/public'; import { ILicense } from '../../../../../licensing/common/types'; import { AppLocation, @@ -12,6 +13,7 @@ import { ProtectionFields, PolicyData, UIPolicyConfig, + MaybeImmutable, } from '../../../../common/endpoint/types'; import { ServerApiError } from '../../../common/types'; import { @@ -21,6 +23,19 @@ import { GetPackagesResponse, UpdatePackagePolicyResponse, } from '../../../../../fleet/common'; +import { AsyncResourceState } from '../../state'; +import { TrustedAppsListData } from '../trusted_apps/state'; +import { ImmutableMiddlewareAPI } from '../../../common/store'; +import { AppAction } from '../../../common/store/actions'; + +/** + * Function that runs Policy Details middleware + */ +export type MiddlewareRunner = ( + coreStart: CoreStart, + store: ImmutableMiddlewareAPI, + action: MaybeImmutable +) => Promise; /** * Policy list store state @@ -61,6 +76,8 @@ export interface PolicyDetailsState { isLoading: boolean; /** current location of the application */ location?: Immutable; + /** artifacts namespace inside policy details page */ + artifacts: PolicyArtifactsState; /** A summary of stats for the agents associated with a given Fleet Agent Policy */ agentStatusSummary?: Omit; /** Status of an update to the policy */ @@ -72,12 +89,29 @@ export interface PolicyDetailsState { license?: ILicense; } +/** + * Policy artifacts store state + */ +export interface PolicyArtifactsState { + /** artifacts location params */ + location: PolicyDetailsArtifactsPageLocation; + /** A list of artifacts can be linked to the policy */ + availableList: AsyncResourceState; +} + export enum OS { windows = 'windows', mac = 'mac', linux = 'linux', } +export interface PolicyDetailsArtifactsPageLocation { + page_index: number; + page_size: number; + show?: 'list'; + filter: string; +} + /** * Returns the keys of an object whose values meet a criteria. * Ex) interface largeNestedObject = { diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/components/antivirus_registration_form/index.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/components/antivirus_registration_form/index.tsx index 51edf1a200c53..45aad6c3d1432 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/components/antivirus_registration_form/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/components/antivirus_registration_form/index.tsx @@ -13,7 +13,7 @@ import { EuiSpacer, EuiSwitch, EuiText } from '@elastic/eui'; import { OperatingSystem } from '../../../../../../../common/endpoint/types'; import { isAntivirusRegistrationEnabled } from '../../../store/policy_details/selectors'; import { usePolicyDetailsSelector } from '../../policy_hooks'; -import { ConfigForm } from '../../components/config_form'; +import { ConfigForm } from '../config_form'; const TRANSLATIONS: Readonly<{ [K in 'title' | 'description' | 'label']: string }> = { title: i18n.translate( diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/components/events_form/index.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/components/events_form/index.tsx index 9bdc5aa493935..1a78bb2fb0215 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/components/events_form/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/components/events_form/index.tsx @@ -24,9 +24,8 @@ interface OperatingSystemToOsMap { [OperatingSystem.MAC]: OS.mac; } -export type ProtectionField< - T extends OperatingSystem -> = keyof UIPolicyConfig[OperatingSystemToOsMap[T]]['events']; +export type ProtectionField = + keyof UIPolicyConfig[OperatingSystemToOsMap[T]]['events']; export type EventFormSelection = { [K in ProtectionField]: boolean }; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/exception_items_summary.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/exception_items_summary.test.tsx index f4ee33446d504..1d9edbe66fc78 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/exception_items_summary.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/exception_items_summary.test.tsx @@ -24,19 +24,18 @@ const getStatValue = (el: reactTestingLibrary.RenderResult, stat: string) => { }; describe('Fleet event filters card', () => { - const renderComponent: ( - stats: GetExceptionSummaryResponse - ) => reactTestingLibrary.RenderResult = (stats) => { - const Wrapper: React.FC = ({ children }) => ( - - {children} - - ); - const component = reactTestingLibrary.render(, { - wrapper: Wrapper, - }); - return component; - }; + const renderComponent: (stats: GetExceptionSummaryResponse) => reactTestingLibrary.RenderResult = + (stats) => { + const Wrapper: React.FC = ({ children }) => ( + + {children} + + ); + const component = reactTestingLibrary.render(, { + wrapper: Wrapper, + }); + return component; + }; it('should renders correctly', () => { const summary: GetExceptionSummaryResponse = { windows: 3, diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_edit_extension.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_edit_extension.tsx index 281cde48e05da..fe321e6a321c2 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_edit_extension.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_edit_extension.tsx @@ -84,7 +84,7 @@ const WrappedPolicyDetailsForm = memo<{ // ensure we don't override it. updatedPolicy: { // Casting is needed due to the use of `Immutable<>` in our store data - inputs: (updatedPolicy.inputs as unknown) as NewPackagePolicy['inputs'], + inputs: updatedPolicy.inputs as unknown as NewPackagePolicy['inputs'], }, }); } diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_advanced.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_advanced.tsx index 38d228d2652d4..8e0d8c544563a 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_advanced.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_advanced.tsx @@ -149,7 +149,7 @@ const PolicyAdvanced = React.memo( if (policyDetailsConfig) { const newPayload = cloneDeep(policyDetailsConfig); setValue( - (newPayload as unknown) as Record, + newPayload as unknown as Record, event.target.value, configPath ); @@ -164,7 +164,7 @@ const PolicyAdvanced = React.memo( const value = policyDetailsConfig && - getValue((policyDetailsConfig as unknown) as Record, configPath); + getValue(policyDetailsConfig as unknown as Record, configPath); return ( <> diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/action.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/action.ts index a3f804ed6cd77..f9965da4a2256 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/action.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/action.ts @@ -22,7 +22,8 @@ export type TrustedAppsListResourceStateChanged = ResourceStateChanged< TrustedAppsListData >; -export type TrustedAppDeletionSubmissionResourceStateChanged = ResourceStateChanged<'trustedAppDeletionSubmissionResourceStateChanged'>; +export type TrustedAppDeletionSubmissionResourceStateChanged = + ResourceStateChanged<'trustedAppDeletionSubmissionResourceStateChanged'>; export type TrustedAppDeletionDialogStarted = Action<'trustedAppDeletionDialogStarted'> & { payload: { @@ -45,16 +46,18 @@ export type TrustedAppCreationDialogStarted = Action<'trustedAppCreationDialogSt }; }; -export type TrustedAppCreationDialogFormStateUpdated = Action<'trustedAppCreationDialogFormStateUpdated'> & { - payload: { - entry: NewTrustedApp; - isValid: boolean; +export type TrustedAppCreationDialogFormStateUpdated = + Action<'trustedAppCreationDialogFormStateUpdated'> & { + payload: { + entry: NewTrustedApp; + isValid: boolean; + }; }; -}; -export type TrustedAppCreationEditItemStateChanged = Action<'trustedAppCreationEditItemStateChanged'> & { - payload: AsyncResourceState; -}; +export type TrustedAppCreationEditItemStateChanged = + Action<'trustedAppCreationEditItemStateChanged'> & { + payload: AsyncResourceState; + }; export type TrustedAppCreationDialogConfirmed = Action<'trustedAppCreationDialogConfirmed'>; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.ts index cf7cff30d6d6d..aa34550d75849 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/middleware.ts @@ -479,6 +479,5 @@ export const createTrustedAppsPageMiddleware = ( }; }; -export const trustedAppsPageMiddlewareFactory: ImmutableMiddlewareFactory = ( - coreStart -) => createTrustedAppsPageMiddleware(new TrustedAppsHttpService(coreStart.http)); +export const trustedAppsPageMiddlewareFactory: ImmutableMiddlewareFactory = + (coreStart) => createTrustedAppsPageMiddleware(new TrustedAppsHttpService(coreStart.http)); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.ts index d0de5dc80ee79..46722d8b9522c 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/reducer.ts @@ -68,15 +68,13 @@ const trustedAppsListResourceStateChanged: CaseReducer = ( - state, - action -) => { - return { - ...state, - deletionDialog: { ...state.deletionDialog, submissionResourceState: action.payload.newState }, +const trustedAppDeletionSubmissionResourceStateChanged: CaseReducer = + (state, action) => { + return { + ...state, + deletionDialog: { ...state.deletionDialog, submissionResourceState: action.payload.newState }, + }; }; -}; const trustedAppDeletionDialogStarted: CaseReducer = ( state, @@ -95,15 +93,13 @@ const trustedAppDeletionDialogClosed: CaseReducer = ( - state, - action -) => { - return { - ...state, - creationDialog: { ...state.creationDialog, submissionResourceState: action.payload.newState }, +const trustedAppCreationSubmissionResourceStateChanged: CaseReducer = + (state, action) => { + return { + ...state, + creationDialog: { ...state.creationDialog, submissionResourceState: action.payload.newState }, + }; }; -}; const trustedAppCreationDialogStarted: CaseReducer = ( state, @@ -118,15 +114,13 @@ const trustedAppCreationDialogStarted: CaseReducer = ( - state, - action -) => { - return { - ...state, - creationDialog: { ...state.creationDialog, formState: { ...action.payload } }, +const trustedAppCreationDialogFormStateUpdated: CaseReducer = + (state, action) => { + return { + ...state, + creationDialog: { ...state.creationDialog, formState: { ...action.payload } }, + }; }; -}; const handleUpdateToEditItemState: CaseReducer = ( state, diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.ts index 1565dc1310b18..b8c2018cd8787 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/store/selectors.ts @@ -192,11 +192,10 @@ export const entriesExistState: ( state: Immutable ) => Immutable = (state) => state.entriesExist; -export const checkingIfEntriesExist: ( - state: Immutable -) => boolean = createSelector(entriesExistState, (doEntriesExists) => { - return !isLoadedResourceState(doEntriesExists); -}); +export const checkingIfEntriesExist: (state: Immutable) => boolean = + createSelector(entriesExistState, (doEntriesExists) => { + return !isLoadedResourceState(doEntriesExists); + }); export const entriesExist: (state: Immutable) => boolean = createSelector( entriesExistState, @@ -205,13 +204,12 @@ export const entriesExist: (state: Immutable) => boole } ); -export const prevEntriesExist: ( - state: Immutable -) => boolean = createSelector(entriesExistState, (doEntriesExists) => { - return ( - isLoadingResourceState(doEntriesExists) && !!getLastLoadedResourceState(doEntriesExists)?.data - ); -}); +export const prevEntriesExist: (state: Immutable) => boolean = + createSelector(entriesExistState, (doEntriesExists) => { + return ( + isLoadingResourceState(doEntriesExists) && !!getLastLoadedResourceState(doEntriesExists)?.data + ); + }); export const trustedAppsListPageActive: (state: Immutable) => boolean = ( state @@ -221,9 +219,8 @@ export const policiesState = ( state: Immutable ): Immutable => state.policies; -export const loadingPolicies: ( - state: Immutable -) => boolean = createSelector(policiesState, (policies) => isLoadingResourceState(policies)); +export const loadingPolicies: (state: Immutable) => boolean = + createSelector(policiesState, (policies) => isLoadingResourceState(policies)); export const listOfPolicies: ( state: Immutable @@ -250,11 +247,10 @@ export const isEdit: (state: Immutable) => boolean = c } ); -export const editItemId: ( - state: Immutable -) => string | undefined = createSelector(getCurrentLocation, ({ id }) => { - return id; -}); +export const editItemId: (state: Immutable) => string | undefined = + createSelector(getCurrentLocation, ({ id }) => { + return id; + }); export const editItemState: ( state: Immutable @@ -262,11 +258,10 @@ export const editItemState: ( return state.creationDialog.editItem; }; -export const isFetchingEditTrustedAppItem: ( - state: Immutable -) => boolean = createSelector(editItemState, (editTrustedAppState) => { - return editTrustedAppState ? isLoadingResourceState(editTrustedAppState) : false; -}); +export const isFetchingEditTrustedAppItem: (state: Immutable) => boolean = + createSelector(editItemState, (editTrustedAppState) => { + return editTrustedAppState ? isLoadingResourceState(editTrustedAppState) : false; + }); export const editTrustedAppFetchError: ( state: Immutable diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/create_trusted_app_form.test.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/create_trusted_app_form.test.tsx index e4486d235b3a7..2d412a8b09676 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/create_trusted_app_form.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/create_trusted_app_form.test.tsx @@ -184,9 +184,9 @@ describe('When using the Trusted App Form', () => { it('should show an initial condition entry with labels', () => { const defaultCondition = getCondition(); - const labels = Array.from( - defaultCondition.querySelectorAll('.euiFormRow__labelWrapper') - ).map((label) => (label.textContent || '').trim()); + const labels = Array.from(defaultCondition.querySelectorAll('.euiFormRow__labelWrapper')).map( + (label) => (label.textContent || '').trim() + ); expect(labels).toEqual(['Field', 'Operator', 'Value', '']); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/create_trusted_app_form.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/create_trusted_app_form.tsx index b8c0fe52f67a1..f9b83fd69a75e 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/create_trusted_app_form.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/create_trusted_app_form.tsx @@ -64,11 +64,9 @@ interface ValidationResult { isValid: boolean; /** Individual form field validations */ - result: Partial< - { - [key in keyof NewTrustedApp]: FieldValidationState; - } - >; + result: Partial<{ + [key in keyof NewTrustedApp]: FieldValidationState; + }>; } const addResultToValidation = ( @@ -222,11 +220,9 @@ export const CreateTrustedAppForm = memo( ); const [wasVisited, setWasVisited] = useState< - Partial< - { - [key in keyof NewTrustedApp]: boolean; - } - > + Partial<{ + [key in keyof NewTrustedApp]: boolean; + }> >({}); const getTestId = useTestIdGenerator(dataTestSubj); @@ -358,14 +354,15 @@ export const CreateTrustedAppForm = memo( [notifyOfChange, trustedApp] ); - const handleConditionBuilderOnVisited: LogicalConditionBuilderProps['onVisited'] = useCallback(() => { - setWasVisited((prevState) => { - return { - ...prevState, - entries: true, - }; - }); - }, []); + const handleConditionBuilderOnVisited: LogicalConditionBuilderProps['onVisited'] = + useCallback(() => { + setWasVisited((prevState) => { + return { + ...prevState, + entries: true, + }; + }); + }, []); const handlePolicySelectChange: EffectedPolicySelectProps['onChange'] = useCallback( (selection) => { diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/translations.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/translations.ts index 6ffcf5614a697..d6361c68b0154 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/translations.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/translations.ts @@ -60,9 +60,9 @@ export const OPERATOR_TITLES: { [K in OperatorFieldIds]: string } = { }), }; -export const ENTRY_PROPERTY_TITLES: Readonly< - { [K in keyof Omit]: string } -> = { +export const ENTRY_PROPERTY_TITLES: Readonly<{ + [K in keyof Omit]: string; +}> = { field: i18n.translate('xpack.securitySolution.trustedapps.trustedapp.entry.field', { defaultMessage: 'Field', }), diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_notifications.test.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_notifications.test.tsx index 917ee8415763c..a642e0c7bd79d 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_notifications.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_notifications.test.tsx @@ -91,8 +91,7 @@ describe('TrustedAppsNotifications', () => { expect(notifications.toasts.addSuccess).not.toBeCalled(); expect(notifications.toasts.addDanger).toBeCalledWith({ - text: - 'Unable to remove "trusted app 3" from the Trusted Applications list. Reason: Not Found', + text: 'Unable to remove "trusted app 3" from the Trusted Applications list. Reason: Not Found', title: 'Removal failure', }); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx index 30e170575e2f4..c696c4705912e 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx @@ -96,7 +96,7 @@ describe('When on the Trusted Apps Page', () => { const currentGetHandler = http.get.getMockImplementation(); http.get.mockImplementation(async (...args) => { - const path = (args[0] as unknown) as string; + const path = args[0] as unknown as string; // @ts-ignore const httpOptions = args[1] as HttpFetchOptions; @@ -237,7 +237,7 @@ describe('When on the Trusted Apps Page', () => { expect(coreStart.http.put).toHaveBeenCalledTimes(1); - const lastCallToPut = (coreStart.http.put.mock.calls[0] as unknown) as [ + const lastCallToPut = coreStart.http.put.mock.calls[0] as unknown as [ string, HttpFetchOptions ]; @@ -319,9 +319,11 @@ describe('When on the Trusted Apps Page', () => { expect(coreStart.http.get).toHaveBeenCalledWith(TRUSTED_APP_GET_URI); expect( - (renderResult.getByTestId( - 'addTrustedAppFlyout-createForm-nameTextField' - ) as HTMLInputElement).value + ( + renderResult.getByTestId( + 'addTrustedAppFlyout-createForm-nameTextField' + ) as HTMLInputElement + ).value ).toEqual('one app for edit'); }); @@ -678,26 +680,24 @@ describe('When on the Trusted Apps Page', () => { }); describe('and there are no trusted apps', () => { - const releaseExistsResponse: jest.MockedFunction< - () => Promise - > = jest.fn(async () => { - return { - data: [], - total: 0, - page: 1, - per_page: 1, - }; - }); - const releaseListResponse: jest.MockedFunction< - () => Promise - > = jest.fn(async () => { - return { - data: [], - total: 0, - page: 1, - per_page: 20, - }; - }); + const releaseExistsResponse: jest.MockedFunction<() => Promise> = + jest.fn(async () => { + return { + data: [], + total: 0, + page: 1, + per_page: 1, + }; + }); + const releaseListResponse: jest.MockedFunction<() => Promise> = + jest.fn(async () => { + return { + data: [], + total: 0, + page: 1, + per_page: 20, + }; + }); beforeEach(() => { const priorMockImplementation = coreStart.http.get.getMockImplementation(); diff --git a/x-pack/plugins/security_solution/public/management/store/middleware.ts b/x-pack/plugins/security_solution/public/management/store/middleware.ts index dae2357d5bb10..d011a9dcb91a7 100644 --- a/x-pack/plugins/security_solution/public/management/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/store/middleware.ts @@ -24,8 +24,10 @@ import { eventFiltersPageMiddlewareFactory } from '../pages/event_filters/store/ type ManagementSubStateKey = keyof State[typeof MANAGEMENT_STORE_GLOBAL_NAMESPACE]; -const createSubStateSelector = (namespace: K) => (state: State) => - state[MANAGEMENT_STORE_GLOBAL_NAMESPACE][namespace]; +const createSubStateSelector = + (namespace: K) => + (state: State) => + state[MANAGEMENT_STORE_GLOBAL_NAMESPACE][namespace]; export const managementMiddlewareFactory: SecuritySubPluginMiddlewareFactory = ( coreStart, diff --git a/x-pack/plugins/security_solution/public/management/store/reducer.ts b/x-pack/plugins/security_solution/public/management/store/reducer.ts index 25c7c87c6f5c9..662d2b4322bcb 100644 --- a/x-pack/plugins/security_solution/public/management/store/reducer.ts +++ b/x-pack/plugins/security_solution/public/management/store/reducer.ts @@ -9,7 +9,7 @@ import { combineReducers } from 'redux'; import { policyDetailsReducer, initialPolicyDetailsState, -} from '../pages/policy/store/policy_details/reducer'; +} from '../pages/policy/store/policy_details'; import { MANAGEMENT_STORE_ENDPOINTS_NAMESPACE, MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE, diff --git a/x-pack/plugins/security_solution/public/network/components/details/index.test.tsx b/x-pack/plugins/security_solution/public/network/components/details/index.test.tsx index ceb89db7b0f53..fb24289c52642 100644 --- a/x-pack/plugins/security_solution/public/network/components/details/index.test.tsx +++ b/x-pack/plugins/security_solution/public/network/components/details/index.test.tsx @@ -47,10 +47,10 @@ describe('IP Overview Component', () => { ip: '10.10.10.10', isInDetailsSidePanel: false, isLoadingAnomaliesData: false, - narrowDateRange: (jest.fn() as unknown) as NarrowDateRange, + narrowDateRange: jest.fn() as unknown as NarrowDateRange, startDate: '2019-06-15T06:00:00.000Z', type: networkModel.NetworkType.details, - updateFlowTargetAction: (jest.fn() as unknown) as ActionCreator<{ + updateFlowTargetAction: jest.fn() as unknown as ActionCreator<{ flowTarget: FlowTarget; }>, }; diff --git a/x-pack/plugins/security_solution/public/network/components/details/mock.ts b/x-pack/plugins/security_solution/public/network/components/details/mock.ts index a1b2011a4abbe..ebe3cb1d997df 100644 --- a/x-pack/plugins/security_solution/public/network/components/details/mock.ts +++ b/x-pack/plugins/security_solution/public/network/components/details/mock.ts @@ -7,56 +7,55 @@ import { NetworkDetailsStrategyResponse } from '../../../../common/search_strategy'; -export const mockData: Readonly< - Record -> = { - complete: { - source: { - firstSeen: '2019-02-07T17:19:41.636Z', - lastSeen: '2019-02-07T17:19:41.636Z', - autonomousSystem: { organization: { name: 'Test Org' }, number: 12345 }, - geo: { - continent_name: ['North America'], - city_name: ['New York'], - country_iso_code: ['US'], - country_name: undefined, - location: { - lat: [40.7214], - lon: [-74.0052], +export const mockData: Readonly> = + { + complete: { + source: { + firstSeen: '2019-02-07T17:19:41.636Z', + lastSeen: '2019-02-07T17:19:41.636Z', + autonomousSystem: { organization: { name: 'Test Org' }, number: 12345 }, + geo: { + continent_name: ['North America'], + city_name: ['New York'], + country_iso_code: ['US'], + country_name: undefined, + location: { + lat: [40.7214], + lon: [-74.0052], + }, + region_iso_code: ['US-NY'], + region_name: ['New York'], }, - region_iso_code: ['US-NY'], - region_name: ['New York'], }, - }, - destination: { - firstSeen: '2019-02-07T17:19:41.648Z', - lastSeen: '2019-02-07T17:19:41.648Z', - autonomousSystem: { organization: { name: 'Test Org' }, number: 12345 }, - geo: { - continent_name: ['North America'], - city_name: ['New York'], - country_iso_code: ['US'], - country_name: undefined, - location: { - lat: [40.7214], - lon: [-74.0052], + destination: { + firstSeen: '2019-02-07T17:19:41.648Z', + lastSeen: '2019-02-07T17:19:41.648Z', + autonomousSystem: { organization: { name: 'Test Org' }, number: 12345 }, + geo: { + continent_name: ['North America'], + city_name: ['New York'], + country_iso_code: ['US'], + country_name: undefined, + location: { + lat: [40.7214], + lon: [-74.0052], + }, + region_iso_code: ['US-NY'], + region_name: ['New York'], }, - region_iso_code: ['US-NY'], - region_name: ['New York'], }, - }, - host: { - os: { - kernel: ['4.14.50-v7+'], - name: ['Raspbian GNU/Linux'], - family: [''], - version: ['9 (stretch)'], - platform: ['raspbian'], + host: { + os: { + kernel: ['4.14.50-v7+'], + name: ['Raspbian GNU/Linux'], + family: [''], + version: ['9 (stretch)'], + platform: ['raspbian'], + }, + name: ['raspberrypi'], + id: ['b19a781f683541a7a25ee345133aa399'], + ip: ['10.10.10.10'], + architecture: ['armv7l'], }, - name: ['raspberrypi'], - id: ['b19a781f683541a7a25ee345133aa399'], - ip: ['10.10.10.10'], - architecture: ['armv7l'], }, - }, -}; + }; diff --git a/x-pack/plugins/security_solution/public/network/components/embeddables/map_tool_tip/line_tool_tip_content.tsx b/x-pack/plugins/security_solution/public/network/components/embeddables/map_tool_tip/line_tool_tip_content.tsx index e9763119f4e47..134fd0b90cb78 100644 --- a/x-pack/plugins/security_solution/public/network/components/embeddables/map_tool_tip/line_tool_tip_content.tsx +++ b/x-pack/plugins/security_solution/public/network/components/embeddables/map_tool_tip/line_tool_tip_content.tsx @@ -20,10 +20,10 @@ import * as i18n from '../translations'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { ITooltipProperty } from '../../../../../../maps/public/classes/tooltips/tooltip_property'; -const FlowBadge = (styled(EuiBadge)` +const FlowBadge = styled(EuiBadge)` height: 45px; min-width: 85px; -` as unknown) as typeof EuiBadge; +` as unknown as typeof EuiBadge; const EuiFlexGroupStyled = styled(EuiFlexGroup)` margin: 0 auto; diff --git a/x-pack/plugins/security_solution/public/network/components/users_table/index.tsx b/x-pack/plugins/security_solution/public/network/components/users_table/index.tsx index 30469d052b4b8..66c36208fd98a 100644 --- a/x-pack/plugins/security_solution/public/network/components/users_table/index.tsx +++ b/x-pack/plugins/security_solution/public/network/components/users_table/index.tsx @@ -118,11 +118,11 @@ const UsersTableComponent: React.FC = ({ [dispatch, sort, type] ); - // eslint-disable-next-line react-hooks/exhaustive-deps - const columns = useMemo(() => getUsersColumns(flowTarget, usersTableId), [ - flowTarget, - usersTableId, - ]); + const columns = useMemo( + () => getUsersColumns(flowTarget, usersTableId), + // eslint-disable-next-line react-hooks/exhaustive-deps + [flowTarget, usersTableId] + ); return ( (null); + const [networkDetailsRequest, setNetworkDetailsRequest] = + useState(null); const [networkDetailsResponse, setNetworkDetailsResponse] = useState({ networkDetails: {}, diff --git a/x-pack/plugins/security_solution/public/network/containers/kpi_network/dns/index.tsx b/x-pack/plugins/security_solution/public/network/containers/kpi_network/dns/index.tsx index 375b77d11d70c..63fb751572b0b 100644 --- a/x-pack/plugins/security_solution/public/network/containers/kpi_network/dns/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/kpi_network/dns/index.tsx @@ -60,10 +60,8 @@ export const useNetworkKpiDns = ({ const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); const [loading, setLoading] = useState(false); - const [ - networkKpiDnsRequest, - setNetworkKpiDnsRequest, - ] = useState(null); + const [networkKpiDnsRequest, setNetworkKpiDnsRequest] = + useState(null); const { getTransformChangesIfTheyExist } = useTransforms(); const [networkKpiDnsResponse, setNetworkKpiDnsResponse] = useState({ diff --git a/x-pack/plugins/security_solution/public/network/containers/kpi_network/network_events/index.tsx b/x-pack/plugins/security_solution/public/network/containers/kpi_network/network_events/index.tsx index 6b1f92a8dba19..4ecf455a31724 100644 --- a/x-pack/plugins/security_solution/public/network/containers/kpi_network/network_events/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/kpi_network/network_events/index.tsx @@ -60,25 +60,21 @@ export const useNetworkKpiNetworkEvents = ({ const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); const [loading, setLoading] = useState(false); - const [ - networkKpiNetworkEventsRequest, - setNetworkKpiNetworkEventsRequest, - ] = useState(null); + const [networkKpiNetworkEventsRequest, setNetworkKpiNetworkEventsRequest] = + useState(null); const { getTransformChangesIfTheyExist } = useTransforms(); - const [ - networkKpiNetworkEventsResponse, - setNetworkKpiNetworkEventsResponse, - ] = useState({ - networkEvents: 0, - id: ID, - inspect: { - dsl: [], - response: [], - }, - isInspected: false, - refetch: refetch.current, - }); + const [networkKpiNetworkEventsResponse, setNetworkKpiNetworkEventsResponse] = + useState({ + networkEvents: 0, + id: ID, + inspect: { + dsl: [], + response: [], + }, + isInspected: false, + refetch: refetch.current, + }); const { addError, addWarning } = useAppToasts(); const networkKpiNetworkEventsSearch = useCallback( diff --git a/x-pack/plugins/security_solution/public/network/containers/kpi_network/tls_handshakes/index.tsx b/x-pack/plugins/security_solution/public/network/containers/kpi_network/tls_handshakes/index.tsx index 84f108dad79f5..2dbf909334b15 100644 --- a/x-pack/plugins/security_solution/public/network/containers/kpi_network/tls_handshakes/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/kpi_network/tls_handshakes/index.tsx @@ -60,25 +60,21 @@ export const useNetworkKpiTlsHandshakes = ({ const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); const [loading, setLoading] = useState(false); - const [ - networkKpiTlsHandshakesRequest, - setNetworkKpiTlsHandshakesRequest, - ] = useState(null); + const [networkKpiTlsHandshakesRequest, setNetworkKpiTlsHandshakesRequest] = + useState(null); const { getTransformChangesIfTheyExist } = useTransforms(); - const [ - networkKpiTlsHandshakesResponse, - setNetworkKpiTlsHandshakesResponse, - ] = useState({ - tlsHandshakes: 0, - id: ID, - inspect: { - dsl: [], - response: [], - }, - isInspected: false, - refetch: refetch.current, - }); + const [networkKpiTlsHandshakesResponse, setNetworkKpiTlsHandshakesResponse] = + useState({ + tlsHandshakes: 0, + id: ID, + inspect: { + dsl: [], + response: [], + }, + isInspected: false, + refetch: refetch.current, + }); const { addError, addWarning } = useAppToasts(); const networkKpiTlsHandshakesSearch = useCallback( diff --git a/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_flows/index.tsx b/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_flows/index.tsx index 2699d63144be1..612aac175fd9a 100644 --- a/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_flows/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_flows/index.tsx @@ -59,24 +59,20 @@ export const useNetworkKpiUniqueFlows = ({ const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); const [loading, setLoading] = useState(false); - const [ - networkKpiUniqueFlowsRequest, - setNetworkKpiUniqueFlowsRequest, - ] = useState(null); + const [networkKpiUniqueFlowsRequest, setNetworkKpiUniqueFlowsRequest] = + useState(null); - const [ - networkKpiUniqueFlowsResponse, - setNetworkKpiUniqueFlowsResponse, - ] = useState({ - uniqueFlowId: 0, - id: ID, - inspect: { - dsl: [], - response: [], - }, - isInspected: false, - refetch: refetch.current, - }); + const [networkKpiUniqueFlowsResponse, setNetworkKpiUniqueFlowsResponse] = + useState({ + uniqueFlowId: 0, + id: ID, + inspect: { + dsl: [], + response: [], + }, + isInspected: false, + refetch: refetch.current, + }); const { addError, addWarning } = useAppToasts(); const networkKpiUniqueFlowsSearch = useCallback( diff --git a/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_private_ips/index.tsx b/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_private_ips/index.tsx index b7c532e5867f0..42a8e30a8f906 100644 --- a/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_private_ips/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_private_ips/index.tsx @@ -64,28 +64,24 @@ export const useNetworkKpiUniquePrivateIps = ({ const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); const [loading, setLoading] = useState(false); - const [ - networkKpiUniquePrivateIpsRequest, - setNetworkKpiUniquePrivateIpsRequest, - ] = useState(null); + const [networkKpiUniquePrivateIpsRequest, setNetworkKpiUniquePrivateIpsRequest] = + useState(null); const { getTransformChangesIfTheyExist } = useTransforms(); - const [ - networkKpiUniquePrivateIpsResponse, - setNetworkKpiUniquePrivateIpsResponse, - ] = useState({ - uniqueDestinationPrivateIps: 0, - uniqueDestinationPrivateIpsHistogram: null, - uniqueSourcePrivateIps: 0, - uniqueSourcePrivateIpsHistogram: null, - id: ID, - inspect: { - dsl: [], - response: [], - }, - isInspected: false, - refetch: refetch.current, - }); + const [networkKpiUniquePrivateIpsResponse, setNetworkKpiUniquePrivateIpsResponse] = + useState({ + uniqueDestinationPrivateIps: 0, + uniqueDestinationPrivateIpsHistogram: null, + uniqueSourcePrivateIps: 0, + uniqueSourcePrivateIpsHistogram: null, + id: ID, + inspect: { + dsl: [], + response: [], + }, + isInspected: false, + refetch: refetch.current, + }); const { addError, addWarning } = useAppToasts(); const networkKpiUniquePrivateIpsSearch = useCallback( diff --git a/x-pack/plugins/security_solution/public/network/containers/network_top_countries/index.tsx b/x-pack/plugins/security_solution/public/network/containers/network_top_countries/index.tsx index 053dca60a740e..f64ee85ab7cf0 100644 --- a/x-pack/plugins/security_solution/public/network/containers/network_top_countries/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/network_top_countries/index.tsx @@ -78,10 +78,8 @@ export const useNetworkTopCountries = ({ const queryId = useMemo(() => `${ID}-${flowTarget}`, [flowTarget]); const { getTransformChangesIfTheyExist } = useTransforms(); - const [ - networkTopCountriesRequest, - setHostRequest, - ] = useState(null); + const [networkTopCountriesRequest, setHostRequest] = + useState(null); const wrappedLoadMore = useCallback( (newActivePage: number) => { @@ -100,26 +98,24 @@ export const useNetworkTopCountries = ({ ); const { addError, addWarning } = useAppToasts(); - const [ - networkTopCountriesResponse, - setNetworkTopCountriesResponse, - ] = useState({ - networkTopCountries: [], - id: queryId, - inspect: { - dsl: [], - response: [], - }, - isInspected: false, - loadPage: wrappedLoadMore, - pageInfo: { - activePage: 0, - fakeTotalCount: 0, - showMorePagesIndicator: false, - }, - refetch: refetch.current, - totalCount: -1, - }); + const [networkTopCountriesResponse, setNetworkTopCountriesResponse] = + useState({ + networkTopCountries: [], + id: queryId, + inspect: { + dsl: [], + response: [], + }, + isInspected: false, + loadPage: wrappedLoadMore, + pageInfo: { + activePage: 0, + fakeTotalCount: 0, + showMorePagesIndicator: false, + }, + refetch: refetch.current, + totalCount: -1, + }); const networkTopCountriesSearch = useCallback( (request: NetworkTopCountriesRequestOptions | null) => { diff --git a/x-pack/plugins/security_solution/public/network/containers/network_top_n_flow/index.tsx b/x-pack/plugins/security_solution/public/network/containers/network_top_n_flow/index.tsx index 8e9f64d19fe39..0b4c164782f3d 100644 --- a/x-pack/plugins/security_solution/public/network/containers/network_top_n_flow/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/network_top_n_flow/index.tsx @@ -77,10 +77,8 @@ export const useNetworkTopNFlow = ({ const [loading, setLoading] = useState(false); const { getTransformChangesIfTheyExist } = useTransforms(); - const [ - networkTopNFlowRequest, - setTopNFlowRequest, - ] = useState(null); + const [networkTopNFlowRequest, setTopNFlowRequest] = + useState(null); const wrappedLoadMore = useCallback( (newActivePage: number) => { diff --git a/x-pack/plugins/security_solution/public/network/pages/details/index.tsx b/x-pack/plugins/security_solution/public/network/pages/details/index.tsx index 10588b449473a..a3f953fc24fe2 100644 --- a/x-pack/plugins/security_solution/public/network/pages/details/index.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/details/index.tsx @@ -118,16 +118,16 @@ const NetworkDetailsComponent: React.FC = () => { skip: isInitializing, }); - const headerDraggableArguments = useMemo(() => ({ field: `${flowTarget}.ip`, value: ip }), [ - flowTarget, - ip, - ]); + const headerDraggableArguments = useMemo( + () => ({ field: `${flowTarget}.ip`, value: ip }), + [flowTarget, ip] + ); // When the filterQuery comes back as undefined, it means an error has been thrown and the request should be skipped - const shouldSkip = useMemo(() => isInitializing || filterQuery === undefined, [ - isInitializing, - filterQuery, - ]); + const shouldSkip = useMemo( + () => isInitializing || filterQuery === undefined, + [isInitializing, filterQuery] + ); return (

@@ -273,7 +273,7 @@ const NetworkDetailsComponent: React.FC = () => { { - const [ - loading, - { id, inspect, isInspected, tls, totalCount, pageInfo, loadPage, refetch }, - ] = useNetworkTls({ - endDate, - filterQuery, - flowTarget, - indexNames, - ip, - skip, - startDate, - type, - }); + const [loading, { id, inspect, isInspected, tls, totalCount, pageInfo, loadPage, refetch }] = + useNetworkTls({ + endDate, + filterQuery, + flowTarget, + indexNames, + ip, + skip, + startDate, + type, + }); return ( { const capabilities = useMlCapabilities(); const capabilitiesFetched = capabilities.capabilitiesFetched; - const userHasMlUserPermissions = useMemo(() => hasMlUserPermissions(capabilities), [ - capabilities, - ]); + const userHasMlUserPermissions = useMemo( + () => hasMlUserPermissions(capabilities), + [capabilities] + ); const networkRoutePath = useMemo( () => getNetworkRoutePath(capabilitiesFetched, userHasMlUserPermissions), [capabilitiesFetched, userHasMlUserPermissions] diff --git a/x-pack/plugins/security_solution/public/network/pages/navigation/tls_query_tab_body.tsx b/x-pack/plugins/security_solution/public/network/pages/navigation/tls_query_tab_body.tsx index fd20a322be682..58c6f755b9175 100644 --- a/x-pack/plugins/security_solution/public/network/pages/navigation/tls_query_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/navigation/tls_query_tab_body.tsx @@ -25,19 +25,17 @@ const TlsQueryTabBodyComponent: React.FC = ({ startDate, type, }) => { - const [ - loading, - { id, inspect, isInspected, tls, totalCount, pageInfo, loadPage, refetch }, - ] = useNetworkTls({ - endDate, - filterQuery, - flowTarget, - indexNames, - ip, - skip, - startDate, - type, - }); + const [loading, { id, inspect, isInspected, tls, totalCount, pageInfo, loadPage, refetch }] = + useNetworkTls({ + endDate, + filterQuery, + flowTarget, + indexNames, + ip, + skip, + startDate, + type, + }); return ( = ({ filterBy }) => { const dispatch = useDispatch(); - const updateIsLoading = useCallback((payload) => dispatch(dispatchUpdateIsLoading(payload)), [ - dispatch, - ]); + const updateIsLoading = useCallback( + (payload) => dispatch(dispatchUpdateIsLoading(payload)), + [dispatch] + ); const updateTimeline = useMemo(() => dispatchUpdateTimeline(dispatch), [dispatch]); const { formatUrl } = useFormatUrl(SecurityPageName.timelines); diff --git a/x-pack/plugins/security_solution/public/overview/components/sidebar/index.tsx b/x-pack/plugins/security_solution/public/overview/components/sidebar/index.tsx index 811078bd2e45f..bd464cb5a3712 100644 --- a/x-pack/plugins/security_solution/public/overview/components/sidebar/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/sidebar/index.tsx @@ -12,9 +12,8 @@ import { FilterMode as RecentTimelinesFilterMode } from '../recent_timelines/typ import { Sidebar } from './sidebar'; export const StatefulSidebar = React.memo(() => { - const [recentTimelinesFilterBy, setRecentTimelinesFilterBy] = useState( - 'favorites' - ); + const [recentTimelinesFilterBy, setRecentTimelinesFilterBy] = + useState('favorites'); return ( { beforeEach(() => { casesMock = casesPluginMock.createStartContract(); casesMock.getRecentCases.mockImplementation(() => <>{'test'}); - useKibanaMock.mockReturnValue(({ + useKibanaMock.mockReturnValue({ services: { cases: casesMock, application: { @@ -33,7 +33,7 @@ describe('Sidebar', () => { getUrlForApp: jest.fn(() => ''), }, }, - } as unknown) as ReturnType); + } as unknown as ReturnType); }); it('does not render the recently created cases section when the user does not have read permissions', async () => { diff --git a/x-pack/plugins/security_solution/public/overview/containers/overview_network/index.tsx b/x-pack/plugins/security_solution/public/overview/containers/overview_network/index.tsx index 2b3adc36ae746..dd98a0ff03632 100644 --- a/x-pack/plugins/security_solution/public/overview/containers/overview_network/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/containers/overview_network/index.tsx @@ -55,10 +55,8 @@ export const useNetworkOverview = ({ const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); const [loading, setLoading] = useState(false); - const [ - overviewNetworkRequest, - setNetworkRequest, - ] = useState(null); + const [overviewNetworkRequest, setNetworkRequest] = + useState(null); const [overviewNetworkResponse, setNetworkOverviewResponse] = useState({ overviewNetwork: {}, diff --git a/x-pack/plugins/security_solution/public/plugin.tsx b/x-pack/plugins/security_solution/public/plugin.tsx index 93fa70ddd9bfb..f8a2de61f5d6f 100644 --- a/x-pack/plugins/security_solution/public/plugin.tsx +++ b/x-pack/plugins/security_solution/public/plugin.tsx @@ -334,22 +334,19 @@ export class Plugin implements IPlugin { if (!this._store) { const defaultIndicesName = coreStart.uiSettings.get(DEFAULT_INDEX_KEY); - const [ - { createStore, createInitialState }, - kibanaIndexPatterns, - configIndexPatterns, - ] = await Promise.all([ - this.lazyApplicationDependencies(), - startPlugins.data.indexPatterns.getIdsWithTitle(), - startPlugins.data.search - .search( - { indices: defaultIndicesName, onlyCheckIfIndicesExist: true }, - { - strategy: 'indexFields', - } - ) - .toPromise(), - ]); + const [{ createStore, createInitialState }, kibanaIndexPatterns, configIndexPatterns] = + await Promise.all([ + this.lazyApplicationDependencies(), + startPlugins.data.indexPatterns.getIdsWithTitle(), + startPlugins.data.search + .search( + { indices: defaultIndicesName, onlyCheckIfIndicesExist: true }, + { + strategy: 'indexFields', + } + ) + .toPromise(), + ]); let signal: { name: string | null } = { name: null }; try { @@ -389,11 +386,11 @@ export class Plugin implements IPlugin; + ) as unknown as Reducer; this._store = createStore( createInitialState( diff --git a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/emptify_mock.ts b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/emptify_mock.ts index e5110015da904..b0d3ad22c9dc8 100644 --- a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/emptify_mock.ts +++ b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/emptify_mock.ts @@ -66,9 +66,7 @@ export function emptifyMock( : dataAccessLayer.relatedEvents(...args); }, - async eventsWithEntityIDAndCategory( - ...args - ): Promise<{ + async eventsWithEntityIDAndCategory(...args): Promise<{ events: SafeResolverEvent[]; nextEvent: string | null; }> { diff --git a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_with_related_events_and_cursor_on_origin.ts b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_with_related_events_and_cursor_on_origin.ts index 722303b390ce8..82c4caac22f5b 100644 --- a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_with_related_events_and_cursor_on_origin.ts +++ b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_with_related_events_and_cursor_on_origin.ts @@ -57,15 +57,12 @@ export function noAncestorsTwoChildrenWithRelatedEventsOnOriginWithOneAfterCurso databaseDocumentID: '_id', entityIDs: { origin: 'origin', firstChild: 'firstChild', secondChild: 'secondChild' }, }; - const { - tree, - relatedEvents, - nodeDataResponse, - } = mockTreeWithNoAncestorsAndTwoChildrenAndRelatedEventsOnOrigin({ - originID: metadata.entityIDs.origin, - firstChildID: metadata.entityIDs.firstChild, - secondChildID: metadata.entityIDs.secondChild, - }); + const { tree, relatedEvents, nodeDataResponse } = + mockTreeWithNoAncestorsAndTwoChildrenAndRelatedEventsOnOrigin({ + originID: metadata.entityIDs.origin, + firstChildID: metadata.entityIDs.firstChild, + secondChildID: metadata.entityIDs.secondChild, + }); return { metadata, diff --git a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_with_related_events_on_origin.ts b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_with_related_events_on_origin.ts index 0d4cd9c3b4265..b2ed63d793505 100644 --- a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_with_related_events_on_origin.ts +++ b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_with_related_events_on_origin.ts @@ -48,15 +48,12 @@ export function noAncestorsTwoChildrenWithRelatedEventsOnOrigin(): { databaseDocumentID: '_id', entityIDs: { origin: 'origin', firstChild: 'firstChild', secondChild: 'secondChild' }, }; - const { - tree, - relatedEvents, - nodeDataResponse, - } = mockTreeWithNoAncestorsAndTwoChildrenAndRelatedEventsOnOrigin({ - originID: metadata.entityIDs.origin, - firstChildID: metadata.entityIDs.firstChild, - secondChildID: metadata.entityIDs.secondChild, - }); + const { tree, relatedEvents, nodeDataResponse } = + mockTreeWithNoAncestorsAndTwoChildrenAndRelatedEventsOnOrigin({ + originID: metadata.entityIDs.origin, + firstChildID: metadata.entityIDs.firstChild, + secondChildID: metadata.entityIDs.secondChild, + }); return { metadata, diff --git a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/pausify_mock.ts b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/pausify_mock.ts index f91abf35338d2..83af8feafcc7f 100644 --- a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/pausify_mock.ts +++ b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/pausify_mock.ts @@ -154,9 +154,7 @@ export function pausifyMock({ /** * Fetch related events for an entity ID */ - async eventsWithEntityIDAndCategory( - ...args - ): Promise<{ + async eventsWithEntityIDAndCategory(...args): Promise<{ events: SafeResolverEvent[]; nextEvent: string | null; }> { diff --git a/x-pack/plugins/security_solution/public/resolver/mocks/resolver_tree.ts b/x-pack/plugins/security_solution/public/resolver/mocks/resolver_tree.ts index 1ffbc3d060e48..0dc08f7adbfbc 100644 --- a/x-pack/plugins/security_solution/public/resolver/mocks/resolver_tree.ts +++ b/x-pack/plugins/security_solution/public/resolver/mocks/resolver_tree.ts @@ -16,11 +16,7 @@ import * as eventModel from '../../../common/endpoint/models/event'; import * as nodeModel from '../../../common/endpoint/models/node'; import { mockResolverNode } from './resolver_node'; -export function mockTreeWithOneNodeAndTwoPagesOfRelatedEvents({ - originID, -}: { - originID: string; -}): { +export function mockTreeWithOneNodeAndTwoPagesOfRelatedEvents({ originID }: { originID: string }): { nodes: ResolverNode[]; events: SafeResolverEvent[]; } { @@ -186,8 +182,7 @@ export function mockTreeWithNoAncestorsAnd2Children({ const secondChildNode: ResolverNode = mockResolverNode({ id: secondChildID, - name: - 'really_really_really_really_really_really_really_really_really_really_really_really_really_really_long_node_name', + name: 'really_really_really_really_really_really_really_really_really_really_really_really_really_really_long_node_name', parentID: originID, timestamp: 1600863932318, }); diff --git a/x-pack/plugins/security_solution/public/resolver/models/location_search.ts b/x-pack/plugins/security_solution/public/resolver/models/location_search.ts index dcfd18be0f395..d25b4e312d2b8 100644 --- a/x-pack/plugins/security_solution/public/resolver/models/location_search.ts +++ b/x-pack/plugins/security_solution/public/resolver/models/location_search.ts @@ -12,39 +12,38 @@ import * as schema from './schema'; * Validates an `unknown` value, narrowing it to `PanelViewAndParameters`. * Use this to validate that the value decoded from the URL is a valid `PanelViewAndParameters` object. */ -export const isPanelViewAndParameters: ( - value: unknown -) => value is PanelViewAndParameters = schema.oneOf([ - schema.object({ - panelView: schema.literal('nodes' as const), - }), - schema.object({ - panelView: schema.literal('nodeDetail' as const), - panelParameters: schema.object({ - nodeID: schema.string(), +export const isPanelViewAndParameters: (value: unknown) => value is PanelViewAndParameters = + schema.oneOf([ + schema.object({ + panelView: schema.literal('nodes' as const), }), - }), - schema.object({ - panelView: schema.literal('nodeEvents' as const), - panelParameters: schema.object({ - nodeID: schema.string(), + schema.object({ + panelView: schema.literal('nodeDetail' as const), + panelParameters: schema.object({ + nodeID: schema.string(), + }), }), - }), - schema.object({ - panelView: schema.literal('nodeEventsInCategory' as const), - panelParameters: schema.object({ - nodeID: schema.string(), - eventCategory: schema.string(), + schema.object({ + panelView: schema.literal('nodeEvents' as const), + panelParameters: schema.object({ + nodeID: schema.string(), + }), }), - }), - schema.object({ - panelView: schema.literal('eventDetail' as const), - panelParameters: schema.object({ - nodeID: schema.string(), - eventCategory: schema.string(), - eventID: schema.oneOf([schema.string(), schema.literal(undefined), schema.number()]), - eventTimestamp: schema.string(), - winlogRecordID: schema.string(), + schema.object({ + panelView: schema.literal('nodeEventsInCategory' as const), + panelParameters: schema.object({ + nodeID: schema.string(), + eventCategory: schema.string(), + }), }), - }), -]); + schema.object({ + panelView: schema.literal('eventDetail' as const), + panelParameters: schema.object({ + nodeID: schema.string(), + eventCategory: schema.string(), + eventID: schema.oneOf([schema.string(), schema.literal(undefined), schema.number()]), + eventTimestamp: schema.string(), + winlogRecordID: schema.string(), + }), + }), + ]); diff --git a/x-pack/plugins/security_solution/public/resolver/models/matrix3.test.ts b/x-pack/plugins/security_solution/public/resolver/models/matrix3.test.ts index 84bd0bf512442..6fc58641e316f 100644 --- a/x-pack/plugins/security_solution/public/resolver/models/matrix3.test.ts +++ b/x-pack/plugins/security_solution/public/resolver/models/matrix3.test.ts @@ -9,15 +9,7 @@ import { multiply } from './matrix3'; describe('matrix3', () => { it('can multiply two matrix3s', () => { expect(multiply([1, 2, 3, 4, 5, 6, 7, 8, 9], [10, 11, 12, 13, 14, 15, 16, 17, 18])).toEqual([ - 84, - 90, - 96, - 201, - 216, - 231, - 318, - 342, - 366, + 84, 90, 96, 201, 216, 231, 318, 342, 366, ]); }); }); diff --git a/x-pack/plugins/security_solution/public/resolver/models/node_data.test.ts b/x-pack/plugins/security_solution/public/resolver/models/node_data.test.ts index 4c9dcd65e0bd1..d59d87655e719 100644 --- a/x-pack/plugins/security_solution/public/resolver/models/node_data.test.ts +++ b/x-pack/plugins/security_solution/public/resolver/models/node_data.test.ts @@ -141,9 +141,7 @@ describe('node data model', () => { requestedNodes: new Set(['1']), numberOfRequestedEvents: 0, }) - ).toEqual( - new Map([['2', { events: node2Events, status: 'error' }]]) - ); + ).toEqual(new Map([['2', { events: node2Events, status: 'error' }]])); }); it('attempts to remove entries from the map even if they do not exist', () => { diff --git a/x-pack/plugins/security_solution/public/resolver/models/schema.ts b/x-pack/plugins/security_solution/public/resolver/models/schema.ts index 21d34964de95d..684afb442d334 100644 --- a/x-pack/plugins/security_solution/public/resolver/models/schema.ts +++ b/x-pack/plugins/security_solution/public/resolver/models/schema.ts @@ -76,10 +76,9 @@ type KeysWithOptionalValues = { */ type OptionalKeyWhenValueAcceptsUndefined = { [K in Exclude>]: T[K]; -} & - { - [K in KeysWithOptionalValues]?: Exclude; - }; +} & { + [K in KeysWithOptionalValues]?: Exclude; +}; /** * Validate that `value` is an object with string keys. The value at each key is tested against its own validator. @@ -99,11 +98,9 @@ export function object< >(validatorDictionary: ValidatorDictionary) { return function ( value: unknown - ): value is /** If a key can point to `undefined`, then instead make the key optional and exclude `undefined` from the value type. */ OptionalKeyWhenValueAcceptsUndefined< - { - [K in keyof ValidatorDictionary]: TypeOf; - } - > { + ): value is /** If a key can point to `undefined`, then instead make the key optional and exclude `undefined` from the value type. */ OptionalKeyWhenValueAcceptsUndefined<{ + [K in keyof ValidatorDictionary]: TypeOf; + }> { // This only validates non-null objects if (typeof value !== 'object' || value === null) { return false; diff --git a/x-pack/plugins/security_solution/public/resolver/store/camera/selectors.ts b/x-pack/plugins/security_solution/public/resolver/store/camera/selectors.ts index f1023bc643210..7694d416f9c38 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/camera/selectors.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/camera/selectors.ts @@ -275,32 +275,31 @@ export const scale: (state: CameraState) => (time: number) => Vector2 = createSe /** * The 2D clipping planes used for the orthographic projection. See https://en.wikipedia.org/wiki/Orthographic_projection */ -export const clippingPlanes: ( - state: CameraState -) => (time: number) => ClippingPlanes = createSelector( - (state) => state.rasterSize, - scale, - (rasterSize, scaleAtTime) => - /** - * memoizing this for object reference equality. - */ - defaultMemoize((time: number) => { - const [scaleX, scaleY] = scaleAtTime(time); - const renderWidth = rasterSize[0]; - const renderHeight = rasterSize[1]; - const clippingPlaneRight = renderWidth / 2 / scaleX; - const clippingPlaneTop = renderHeight / 2 / scaleY; - - return { - renderWidth, - renderHeight, - clippingPlaneRight, - clippingPlaneTop, - clippingPlaneLeft: -clippingPlaneRight, - clippingPlaneBottom: -clippingPlaneTop, - }; - }) -); +export const clippingPlanes: (state: CameraState) => (time: number) => ClippingPlanes = + createSelector( + (state) => state.rasterSize, + scale, + (rasterSize, scaleAtTime) => + /** + * memoizing this for object reference equality. + */ + defaultMemoize((time: number) => { + const [scaleX, scaleY] = scaleAtTime(time); + const renderWidth = rasterSize[0]; + const renderHeight = rasterSize[1]; + const clippingPlaneRight = renderWidth / 2 / scaleX; + const clippingPlaneTop = renderHeight / 2 / scaleY; + + return { + renderWidth, + renderHeight, + clippingPlaneRight, + clippingPlaneTop, + clippingPlaneLeft: -clippingPlaneRight, + clippingPlaneBottom: -clippingPlaneTop, + }; + }) + ); /** * Whether or not the camera is animating, at a given time. @@ -361,12 +360,8 @@ export const translation: (state: CameraState) => (time: number) => Vector2 = cr * A matrix that when applied to a Vector2 converts it from screen coordinates to world coordinates. * See https://en.wikipedia.org/wiki/Orthographic_projection */ -export const inverseProjectionMatrix: ( - state: CameraState -) => (time: number) => Matrix3 = createSelector( - clippingPlanes, - translation, - (clippingPlanesAtTime, translationAtTime) => { +export const inverseProjectionMatrix: (state: CameraState) => (time: number) => Matrix3 = + createSelector(clippingPlanes, translation, (clippingPlanesAtTime, translationAtTime) => { /** * Memoizing this for object reference equality (and reduced memory churn.) */ @@ -418,8 +413,7 @@ export const inverseProjectionMatrix: ( multiply(scaleToClippingPlaneDimensions, multiply(invertY, screenToNDC)) ); }); - } -); + }); /** * The viewable area in the Resolver map, in world coordinates. diff --git a/x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts b/x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts index 76b29cadd2713..1f302e82d70ec 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts @@ -126,14 +126,13 @@ const nodeData = (state: DataState): Map | undefined => { /** * Returns a function that can be called to retrieve the node data for a specific node ID. */ -export const nodeDataForID: ( - state: DataState -) => (id: string) => NodeData | undefined = createSelector(nodeData, (nodeInfo) => { - return (id: string) => { - const info = nodeInfo?.get(id); - return info; - }; -}); +export const nodeDataForID: (state: DataState) => (id: string) => NodeData | undefined = + createSelector(nodeData, (nodeInfo) => { + return (id: string) => { + const info = nodeInfo?.get(id); + return info; + }; + }); /** * Returns a function that can be called to retrieve the state of the node, running, loading, or terminated. @@ -171,30 +170,30 @@ export const graphableNodes = createSelector(resolverTreeResponse, function (tre } }); -const tree = createSelector(graphableNodes, originID, function indexedProcessTree( - // eslint-disable-next-line @typescript-eslint/no-shadow +const tree = createSelector( graphableNodes, - currentOriginID -) { - return indexedProcessTreeModel.factory(graphableNodes, currentOriginID); -}); + originID, + function indexedProcessTree( + // eslint-disable-next-line @typescript-eslint/no-shadow + graphableNodes, + currentOriginID + ) { + return indexedProcessTreeModel.factory(graphableNodes, currentOriginID); + } +); /** * This returns a map of nodeIDs to the associated stats provided by the datasource. */ -export const nodeStats: ( - state: DataState -) => (nodeID: string) => EventStats | undefined = createSelector( - resolverTreeResponse, - (resolverTree?: NewResolverTree) => { +export const nodeStats: (state: DataState) => (nodeID: string) => EventStats | undefined = + createSelector(resolverTreeResponse, (resolverTree?: NewResolverTree) => { if (resolverTree) { const map = resolverTreeModel.nodeStats(resolverTree); return (nodeID: string) => map.get(nodeID); } else { return () => undefined; } - } -); + }); /** * The total number of events related to a node. @@ -408,14 +407,10 @@ export const layout: (state: DataState) => IsometricTaxiLayout = createSelector( * Legacy functions take process events instead of nodeID, use this to get * process events for them. */ -export const graphNodeForID: ( - state: DataState -) => (nodeID: string) => ResolverNode | null = createSelector( - tree, - (indexedProcessTree) => (nodeID: string) => { +export const graphNodeForID: (state: DataState) => (nodeID: string) => ResolverNode | null = + createSelector(tree, (indexedProcessTree) => (nodeID: string) => { return indexedProcessTreeModel.treeNode(indexedProcessTree, nodeID); - } -); + }); /** * Takes a nodeID (aka entity_id) and returns the associated aria level as a number or null if the node ID isn't in the tree. @@ -423,10 +418,11 @@ export const graphNodeForID: ( export const ariaLevel: (state: DataState) => (nodeID: string) => number | null = createSelector( layout, graphNodeForID, - ({ ariaLevels }, graphNodeGetter) => (nodeID: string) => { - const node = graphNodeGetter(nodeID); - return node ? ariaLevels.get(node) ?? null : null; - } + ({ ariaLevels }, graphNodeGetter) => + (nodeID: string) => { + const node = graphNodeGetter(nodeID); + return node ? ariaLevels.get(node) ?? null : null; + } ); /** @@ -434,12 +430,8 @@ export const ariaLevel: (state: DataState) => (nodeID: string) => number | null * For root nodes, other root nodes are treated as siblings. * This is used to calculate the `aria-flowto` attribute. */ -export const ariaFlowtoCandidate: ( - state: DataState -) => (nodeID: string) => string | null = createSelector( - tree, - graphNodeForID, - (indexedProcessTree, nodeGetter) => { +export const ariaFlowtoCandidate: (state: DataState) => (nodeID: string) => string | null = + createSelector(tree, graphNodeForID, (indexedProcessTree, nodeGetter) => { // A map of preceding sibling IDs to following sibling IDs or `null`, if there is no following sibling const memo: Map = new Map(); @@ -498,8 +490,7 @@ export const ariaFlowtoCandidate: ( return memoizedGetter(nodeID); }; - } -); + }); const spatiallyIndexedLayout: (state: DataState) => rbush = createSelector( layout, @@ -548,9 +539,7 @@ const spatiallyIndexedLayout: (state: DataState) => rbush = creat /** * Returns nodes and edge lines that could be visible in the `query`. */ -export const nodesAndEdgelines: ( - state: DataState -) => ( +export const nodesAndEdgelines: (state: DataState) => ( /** * An axis aligned bounding box (in world corrdinates) to search in. Any entities that might collide with this box will be returned. */ @@ -627,21 +616,20 @@ export function treeRequestParametersToAbort(state: DataState): TreeFetcherParam /** * The sum of all related event categories for a process. */ -export const statsTotalForNode: ( - state: DataState -) => (event: ResolverNode) => number | null = createSelector(nodeStats, (getNodeStats) => { - return (node: ResolverNode) => { - const nodeID = nodeModel.nodeID(node); - if (nodeID === undefined) { - return null; - } - const stats = getNodeStats(nodeID); - if (!stats) { - return null; - } - return stats.total; - }; -}); +export const statsTotalForNode: (state: DataState) => (event: ResolverNode) => number | null = + createSelector(nodeStats, (getNodeStats) => { + return (node: ResolverNode) => { + const nodeID = nodeModel.nodeID(node); + if (nodeID === undefined) { + return null; + } + const stats = getNodeStats(nodeID); + if (!stats) { + return null; + } + return stats.total; + }; + }); /** * Total count of events related to `node`. diff --git a/x-pack/plugins/security_solution/public/resolver/store/selectors.ts b/x-pack/plugins/security_solution/public/resolver/store/selectors.ts index 8e99168c02cbe..8dcca877b31f6 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/selectors.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/selectors.ts @@ -101,12 +101,8 @@ export const resolverComponentInstanceID = composeSelectors( /** * This returns a map of nodeIDs to the associated stats provided by the datasource. */ -export const nodeStats: ( - state: ResolverState -) => (nodeID: string) => EventStats | undefined = composeSelectors( - dataStateSelector, - dataSelectors.nodeStats -); +export const nodeStats: (state: ResolverState) => (nodeID: string) => EventStats | undefined = + composeSelectors(dataStateSelector, dataSelectors.nodeStats); /** * This returns the "aggregate total" for related events, tallied as the sum @@ -233,25 +229,25 @@ export const statsTotalForNode = composeSelectors( * The bounding box represents what the camera can see. The camera position is a function of time because it can be * animated. So in order to get the currently visible entities, we need to pass in time. */ -export const visibleNodesAndEdgeLines = createSelector(nodesAndEdgelines, boundingBox, function ( - /* eslint-disable @typescript-eslint/no-shadow */ +export const visibleNodesAndEdgeLines = createSelector( nodesAndEdgelines, - boundingBox - /* eslint-enable @typescript-eslint/no-shadow */ -) { - // `boundingBox` and `nodesAndEdgelines` are each memoized. - return (time: number) => nodesAndEdgelines(boundingBox(time)); -}); + boundingBox, + function ( + /* eslint-disable @typescript-eslint/no-shadow */ + nodesAndEdgelines, + boundingBox + /* eslint-enable @typescript-eslint/no-shadow */ + ) { + // `boundingBox` and `nodesAndEdgelines` are each memoized. + return (time: number) => nodesAndEdgelines(boundingBox(time)); + } +); /** * Takes a nodeID (aka entity_id) and returns the associated aria level as a number or null if the node ID isn't in the tree. */ -export const ariaLevel: ( - state: ResolverState -) => (nodeID: string) => number | null = composeSelectors( - dataStateSelector, - dataSelectors.ariaLevel -); +export const ariaLevel: (state: ResolverState) => (nodeID: string) => number | null = + composeSelectors(dataStateSelector, dataSelectors.ariaLevel); /** * the node ID of the node representing the databaseDocumentID @@ -390,30 +386,29 @@ export const graphNodeForID = composeSelectors(dataStateSelector, dataSelectors. /** * Returns a Set of node IDs representing the visible nodes in the view that we do no have node data for already. */ -export const newIDsToRequest: ( - state: ResolverState -) => (time: number) => Set = createSelector( - composeSelectors(dataStateSelector, (dataState: DataState) => dataState.nodeData), - visibleNodesAndEdgeLines, - function (nodeData, visibleNodesAndEdgeLinesAtTime) { - return defaultMemoize((time: number) => { - const { processNodePositions: nodesInView } = visibleNodesAndEdgeLinesAtTime(time); - - const nodes: Set = new Set(); - // loop through the nodes in view and see if any of them are new aka we don't have node data for them already - for (const node of nodesInView.keys()) { - const id = nodeModel.nodeID(node); - // if the node has a valid ID field, and we either don't have any node data currently, or - // the map doesn't have info for this particular node, then add it to the set so it'll be requested - // by the middleware - if (id !== undefined && (!nodeData || !nodeData.has(id))) { - nodes.add(id); +export const newIDsToRequest: (state: ResolverState) => (time: number) => Set = + createSelector( + composeSelectors(dataStateSelector, (dataState: DataState) => dataState.nodeData), + visibleNodesAndEdgeLines, + function (nodeData, visibleNodesAndEdgeLinesAtTime) { + return defaultMemoize((time: number) => { + const { processNodePositions: nodesInView } = visibleNodesAndEdgeLinesAtTime(time); + + const nodes: Set = new Set(); + // loop through the nodes in view and see if any of them are new aka we don't have node data for them already + for (const node of nodesInView.keys()) { + const id = nodeModel.nodeID(node); + // if the node has a valid ID field, and we either don't have any node data currently, or + // the map doesn't have info for this particular node, then add it to the set so it'll be requested + // by the middleware + if (id !== undefined && (!nodeData || !nodeData.has(id))) { + nodes.add(id); + } } - } - return nodes; - }); - } -); + return nodes; + }); + } + ); /** * Returns the schema for the current resolver tree. Currently, only used in the graph controls panel. diff --git a/x-pack/plugins/security_solution/public/resolver/store/ui/selectors.ts b/x-pack/plugins/security_solution/public/resolver/store/ui/selectors.ts index d3b0708fea5b7..792d8a0fe056a 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/ui/selectors.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/ui/selectors.ts @@ -79,9 +79,7 @@ export const relativeHref: ( * Returns a map of ecs category name to urls for use in panel navigation. * @deprecated use `useLinkProps` */ -export const relatedEventsRelativeHrefs: ( - state: ResolverUIState -) => ( +export const relatedEventsRelativeHrefs: (state: ResolverUIState) => ( categories: Record | undefined, nodeID: string // eslint-disable-next-line @typescript-eslint/no-shadow diff --git a/x-pack/plugins/security_solution/public/resolver/view/clickthrough.test.tsx b/x-pack/plugins/security_solution/public/resolver/view/clickthrough.test.tsx index 78b84ac4cc108..57ce1b1991fdd 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/clickthrough.test.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/clickthrough.test.tsx @@ -27,10 +27,8 @@ const resolverComponentInstanceID = 'resolverComponentInstanceID'; describe("Resolver, when rendered with the `indices` prop set to `[]` and the `databaseDocumentID` prop set to `_id`, and when the document is found in an index called 'awesome_index'", () => { beforeEach(async () => { // create a mock data access layer - const { - metadata: dataAccessLayerMetadata, - dataAccessLayer, - } = noAncestorsTwoChildenInIndexCalledAwesomeIndex(); + const { metadata: dataAccessLayerMetadata, dataAccessLayer } = + noAncestorsTwoChildenInIndexCalledAwesomeIndex(); // save a reference to the entity IDs exposed by the mock data layer entityIDs = dataAccessLayerMetadata.entityIDs; @@ -421,10 +419,8 @@ describe('Resolver, when using a generated tree with 20 generations, 4 children describe('Resolver, when analyzing a tree that has 2 related registry and 1 related event of all other categories for the origin node', () => { beforeEach(async () => { // create a mock data access layer with related events - const { - metadata: dataAccessLayerMetadata, - dataAccessLayer, - } = noAncestorsTwoChildrenWithRelatedEventsOnOrigin(); + const { metadata: dataAccessLayerMetadata, dataAccessLayer } = + noAncestorsTwoChildrenWithRelatedEventsOnOrigin(); // save a reference to the entity IDs exposed by the mock data layer entityIDs = dataAccessLayerMetadata.entityIDs; diff --git a/x-pack/plugins/security_solution/public/resolver/view/index.tsx b/x-pack/plugins/security_solution/public/resolver/view/index.tsx index 449ca8deb564f..3c3e0e3e74319 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/index.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/index.tsx @@ -21,9 +21,10 @@ import { ResolverWithoutProviders } from './resolver_without_providers'; */ export const Resolver = React.memo((props: ResolverProps) => { const context = useKibana(); - const dataAccessLayer: DataAccessLayer = useMemo(() => dataAccessLayerFactory(context), [ - context, - ]); + const dataAccessLayer: DataAccessLayer = useMemo( + () => dataAccessLayerFactory(context), + [context] + ); const store = useMemo(() => resolverStoreFactory(dataAccessLayer), [dataAccessLayer]); diff --git a/x-pack/plugins/security_solution/public/resolver/view/panel.test.tsx b/x-pack/plugins/security_solution/public/resolver/view/panel.test.tsx index 332f806b59ec7..3b2d222ac3812 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panel.test.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panel.test.tsx @@ -49,10 +49,8 @@ describe(`Resolver: when analyzing a tree with no ancestors and two children and beforeEach(() => { // create a mock data access layer - const { - metadata: dataAccessLayerMetadata, - dataAccessLayer, - } = noAncestorsTwoChildrenWithRelatedEventsOnOrigin(); + const { metadata: dataAccessLayerMetadata, dataAccessLayer } = + noAncestorsTwoChildrenWithRelatedEventsOnOrigin(); entityIDs = dataAccessLayerMetadata.entityIDs; diff --git a/x-pack/plugins/security_solution/public/resolver/view/process_event_dot.tsx b/x-pack/plugins/security_solution/public/resolver/view/process_event_dot.tsx index 4c14106b70397..86b908c5afd9f 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/process_event_dot.tsx @@ -174,9 +174,10 @@ const UnstyledProcessEventDot = React.memo( // define a standard way of giving HTML IDs to nodes based on their entity_id/nodeID. // this is used to link nodes via aria attributes - const nodeHTMLID = useCallback((id: string) => htmlIdGenerator(htmlIDPrefix)(`${id}:node`), [ - htmlIDPrefix, - ]); + const nodeHTMLID = useCallback( + (id: string) => htmlIdGenerator(htmlIDPrefix)(`${id}:node`), + [htmlIDPrefix] + ); const ariaLevel: number | null = useSelector((state: ResolverState) => selectors.ariaLevel(state)(nodeID) diff --git a/x-pack/plugins/security_solution/public/resolver/view/resolver_without_providers.tsx b/x-pack/plugins/security_solution/public/resolver/view/resolver_without_providers.tsx index d9e40aac36d20..e0fd79597dd1f 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/resolver_without_providers.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/resolver_without_providers.tsx @@ -69,11 +69,8 @@ export const ResolverWithoutProviders = React.memo( // use this for the entire render in order to keep things in sync const timeAtRender = timestamp(); - const { - processNodePositions, - connectingEdgeLineSegments, - } = useSelector((state: ResolverState) => - selectors.visibleNodesAndEdgeLines(state)(timeAtRender) + const { processNodePositions, connectingEdgeLineSegments } = useSelector( + (state: ResolverState) => selectors.visibleNodesAndEdgeLines(state)(timeAtRender) ); const { projectionMatrix, ref: cameraRef, onMouseDown } = useCamera(); diff --git a/x-pack/plugins/security_solution/public/resolver/view/use_camera.test.tsx b/x-pack/plugins/security_solution/public/resolver/view/use_camera.test.tsx index 76c70e7f4f0d6..16d25a519fe8c 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/use_camera.test.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/use_camera.test.tsx @@ -201,15 +201,7 @@ describe('useCamera on an unpainted element', () => { }); it('should zoom in', () => { expect(map(() => projectionMatrix)).toYieldEqualTo([ - 1.0292841801261479, - 0, - 400, - 0, - -1.0292841801261479, - 300, - 0, - 0, - 0, + 1.0292841801261479, 0, 400, 0, -1.0292841801261479, 300, 0, 0, 0, ]); }); }); diff --git a/x-pack/plugins/security_solution/public/resolver/view/use_link_props.ts b/x-pack/plugins/security_solution/public/resolver/view/use_link_props.ts index a04c8283bccdd..b0544c8b21dde 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/use_link_props.ts +++ b/x-pack/plugins/security_solution/public/resolver/view/use_link_props.ts @@ -20,9 +20,10 @@ type EventHandlerCallback = MouseEventHandler selectors.relativeHref(state)(panelViewAndParameters) ); diff --git a/x-pack/plugins/security_solution/public/resolver/view/use_sync_selected_node.test.tsx b/x-pack/plugins/security_solution/public/resolver/view/use_sync_selected_node.test.tsx index dcafcecd854a8..7ff17e2a86b57 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/use_sync_selected_node.test.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/use_sync_selected_node.test.tsx @@ -26,10 +26,8 @@ describe(`Resolver: when analyzing a tree with 0 ancestors, 2 children, 2 relate }; beforeEach(() => { - const { - metadata: dataAccessLayerMetadata, - dataAccessLayer, - } = noAncestorsTwoChildrenWithRelatedEventsOnOrigin(); + const { metadata: dataAccessLayerMetadata, dataAccessLayer } = + noAncestorsTwoChildrenWithRelatedEventsOnOrigin(); entityIDs = dataAccessLayerMetadata.entityIDs; diff --git a/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/index.test.tsx index 4a8eb8dbec195..645487e6a584d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/index.test.tsx @@ -170,7 +170,7 @@ describe('StatefulEditDataProvider', () => { }); test('it renders the current value when the type of value is an array', () => { - const reallyAnArray = ([value] as unknown) as string; + const reallyAnArray = [value] as unknown as string; const wrapper = mount( diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/add_timeline_button/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/add_timeline_button/index.test.tsx index 2035b1b4be7b1..b94898d52b8ba 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/add_timeline_button/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/add_timeline_button/index.test.tsx @@ -175,7 +175,7 @@ describe('AddTimelineButton', () => { }, }); - ((useGetAllTimeline as unknown) as jest.Mock).mockReturnValue({ + (useGetAllTimeline as unknown as jest.Mock).mockReturnValue({ fetchAllTimeline: jest.fn(), timelines: getAllTimeline('', mockOpenTimelineQueryResults.timeline ?? []), loading: false, diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx index e3a1152428d62..9f1730c367a81 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx @@ -119,9 +119,10 @@ const FlyoutHeaderPanelComponent: React.FC = ({ timeline isEmpty(dataProviders) && isEmpty(kqlQueryTimeline) && timelineType === 'template' ? ' ' : kqlQueryTimeline; - const kqlQueryTest = useMemo(() => ({ query: kqlQueryExpression, language: 'kuery' }), [ - kqlQueryExpression, - ]); + const kqlQueryTest = useMemo( + () => ({ query: kqlQueryExpression, language: 'kuery' }), + [kqlQueryExpression] + ); const combinedQueries = useMemo( () => @@ -377,9 +378,10 @@ const FlyoutHeaderComponent: React.FC = ({ timelineId }) => { isEmpty(dataProviders) && isEmpty(kqlQueryTimeline) && timelineType === 'template' ? ' ' : kqlQueryTimeline; - const kqlQuery = useMemo(() => ({ query: kqlQueryExpression, language: 'kuery' }), [ - kqlQueryExpression, - ]); + const kqlQuery = useMemo( + () => ({ query: kqlQueryExpression, language: 'kuery' }), + [kqlQueryExpression] + ); const combinedQueries = useMemo( () => diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/index.tsx index ad1d126e3c853..aa501965193dd 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/index.tsx @@ -28,9 +28,12 @@ type VoidFunc = () => void; const FlyoutComponent: React.FC = ({ timelineId, onAppLeave }) => { const dispatch = useDispatch(); const getTimelineShowStatus = useMemo(() => getTimelineShowStatusByIdSelector(), []); - const { activeTab, show, status: timelineStatus, updated } = useDeepEqualSelector((state) => - getTimelineShowStatus(state, timelineId) - ); + const { + activeTab, + show, + status: timelineStatus, + updated, + } = useDeepEqualSelector((state) => getTimelineShowStatus(state, timelineId)); const [focusOwnership, setFocusOwnership] = useState(true); const [triggerOnBlur, setTriggerOnBlur] = useState(true); diff --git a/x-pack/plugins/security_solution/public/timelines/components/notes/add_note/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/notes/add_note/index.tsx index 2cefb6a9f6344..21f3efb78462d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/notes/add_note/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/notes/add_note/index.tsx @@ -54,9 +54,10 @@ export const AddNote = React.memo<{ }>(({ associateNote, newNote, onCancelAddNote, updateNewNote, autoFocusDisabled = false }) => { const dispatch = useDispatch(); - const updateNote = useCallback((note: Note) => dispatch(appActions.updateNote({ note })), [ - dispatch, - ]); + const updateNote = useCallback( + (note: Note) => dispatch(appActions.updateNote({ note })), + [dispatch] + ); const handleClick = useCallback( () => diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/edit_timeline_batch_actions.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/edit_timeline_batch_actions.tsx index 4a40a6ac75a9c..c1372868e07df 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/edit_timeline_batch_actions.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/edit_timeline_batch_actions.tsx @@ -57,13 +57,15 @@ export const useEditTimelineBatchActions = ({ const selectedIds = useMemo(() => getExportedIds(selectedItems ?? []), [selectedItems]); - const handleEnableExportTimelineDownloader = useCallback(() => enableExportTimelineDownloader(), [ - enableExportTimelineDownloader, - ]); + const handleEnableExportTimelineDownloader = useCallback( + () => enableExportTimelineDownloader(), + [enableExportTimelineDownloader] + ); - const handleOnOpenDeleteTimelineModal = useCallback(() => onOpenDeleteTimelineModal(), [ - onOpenDeleteTimelineModal, - ]); + const handleOnOpenDeleteTimelineModal = useCallback( + () => onOpenDeleteTimelineModal(), + [onOpenDeleteTimelineModal] + ); const getBatchItemsPopoverContent = useCallback( (closePopover: () => void) => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts index 37bdfd38bf8bc..69b63e83186e3 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts @@ -958,7 +958,7 @@ describe('helpers', () => { beforeAll(async () => { (getTimeline as jest.Mock).mockResolvedValue(selectedTimeline); - await queryTimelineById<{}>((args as unknown) as QueryTimelineById<{}>); + await queryTimelineById<{}>(args as unknown as QueryTimelineById<{}>); }); afterAll(() => { @@ -1012,7 +1012,7 @@ describe('helpers', () => { beforeAll(async () => { (getTimeline as jest.Mock).mockResolvedValue(selectedTimeline); - await queryTimelineById<{}>((args as unknown) as QueryTimelineById<{}>); + await queryTimelineById<{}>(args as unknown as QueryTimelineById<{}>); }); afterAll(() => { @@ -1080,7 +1080,7 @@ describe('helpers', () => { beforeAll(async () => { (getTimeline as jest.Mock).mockResolvedValue(template); - await queryTimelineById<{}>((args as unknown) as QueryTimelineById<{}>); + await queryTimelineById<{}>(args as unknown as QueryTimelineById<{}>); }); afterAll(() => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts index 0dda12d612777..c72aa5878478d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts @@ -377,86 +377,89 @@ export const queryTimelineById = ({ }); }; -export const dispatchUpdateTimeline = (dispatch: Dispatch): DispatchUpdateTimeline => ({ - duplicate, - id, - forceNotes = false, - from, - notes, - timeline, - to, - ruleNote, -}: UpdateTimeline): (() => void) => () => { - if (!isEmpty(timeline.indexNames)) { - dispatch( - sourcererActions.initTimelineIndexPatterns({ - id: SourcererScopeName.timeline, - selectedPatterns: timeline.indexNames, - eventType: timeline.eventType, - }) - ); - } - if ( - timeline.status === TimelineStatus.immutable && - timeline.timelineType === TimelineType.template - ) { - dispatch( - dispatchSetRelativeRangeDatePicker({ - id: 'timeline', - fromStr: 'now-24h', - toStr: 'now', - from: DEFAULT_FROM_MOMENT.toISOString(), - to: DEFAULT_TO_MOMENT.toISOString(), - }) - ); - } else { - dispatch(dispatchSetTimelineRangeDatePicker({ from, to })); - } - dispatch(dispatchAddTimeline({ id, timeline, savedTimeline: duplicate })); - if ( - timeline.kqlQuery != null && - timeline.kqlQuery.filterQuery != null && - timeline.kqlQuery.filterQuery.kuery != null && - timeline.kqlQuery.filterQuery.kuery.expression !== '' - ) { - dispatch( - dispatchApplyKqlFilterQuery({ - id, - filterQuery: { - kuery: { - kind: timeline.kqlQuery.filterQuery.kuery.kind ?? 'kuery', - expression: timeline.kqlQuery.filterQuery.kuery.expression || '', +export const dispatchUpdateTimeline = + (dispatch: Dispatch): DispatchUpdateTimeline => + ({ + duplicate, + id, + forceNotes = false, + from, + notes, + timeline, + to, + ruleNote, + }: UpdateTimeline): (() => void) => + () => { + if (!isEmpty(timeline.indexNames)) { + dispatch( + sourcererActions.initTimelineIndexPatterns({ + id: SourcererScopeName.timeline, + selectedPatterns: timeline.indexNames, + eventType: timeline.eventType, + }) + ); + } + if ( + timeline.status === TimelineStatus.immutable && + timeline.timelineType === TimelineType.template + ) { + dispatch( + dispatchSetRelativeRangeDatePicker({ + id: 'timeline', + fromStr: 'now-24h', + toStr: 'now', + from: DEFAULT_FROM_MOMENT.toISOString(), + to: DEFAULT_TO_MOMENT.toISOString(), + }) + ); + } else { + dispatch(dispatchSetTimelineRangeDatePicker({ from, to })); + } + dispatch(dispatchAddTimeline({ id, timeline, savedTimeline: duplicate })); + if ( + timeline.kqlQuery != null && + timeline.kqlQuery.filterQuery != null && + timeline.kqlQuery.filterQuery.kuery != null && + timeline.kqlQuery.filterQuery.kuery.expression !== '' + ) { + dispatch( + dispatchApplyKqlFilterQuery({ + id, + filterQuery: { + kuery: { + kind: timeline.kqlQuery.filterQuery.kuery.kind ?? 'kuery', + expression: timeline.kqlQuery.filterQuery.kuery.expression || '', + }, + serializedQuery: timeline.kqlQuery.filterQuery.serializedQuery || '', }, - serializedQuery: timeline.kqlQuery.filterQuery.serializedQuery || '', - }, - }) - ); - } + }) + ); + } - if (duplicate && ruleNote != null && !isEmpty(ruleNote)) { - const newNote = createNote({ newNote: ruleNote }); - dispatch(dispatchUpdateNote({ note: newNote })); - dispatch(dispatchAddGlobalTimelineNote({ noteId: newNote.id, id })); - } + if (duplicate && ruleNote != null && !isEmpty(ruleNote)) { + const newNote = createNote({ newNote: ruleNote }); + dispatch(dispatchUpdateNote({ note: newNote })); + dispatch(dispatchAddGlobalTimelineNote({ noteId: newNote.id, id })); + } - if (!duplicate || forceNotes) { - dispatch( - dispatchAddNotes({ - notes: - notes != null - ? notes.map((note: NoteResult) => ({ - created: note.created != null ? new Date(note.created) : new Date(), - id: note.noteId, - lastEdit: note.updated != null ? new Date(note.updated) : new Date(), - note: note.note || '', - user: note.updatedBy || 'unknown', - saveObjectId: note.noteId, - version: note.version, - eventId: note.eventId ?? null, - timelineId: note.timelineId ?? null, - })) - : [], - }) - ); - } -}; + if (!duplicate || forceNotes) { + dispatch( + dispatchAddNotes({ + notes: + notes != null + ? notes.map((note: NoteResult) => ({ + created: note.created != null ? new Date(note.created) : new Date(), + id: note.noteId, + lastEdit: note.updated != null ? new Date(note.updated) : new Date(), + note: note.note || '', + user: note.updatedBy || 'unknown', + saveObjectId: note.noteId, + version: note.version, + eventId: note.eventId ?? null, + timelineId: note.timelineId ?? null, + })) + : [], + }) + ); + } + }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.test.tsx index c0b451a875522..92d602bb89e8f 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.test.tsx @@ -95,14 +95,14 @@ describe('StatefulOpenTimeline', () => { }); mockHistory = []; (useHistory as jest.Mock).mockReturnValue(mockHistory); - ((useGetAllTimeline as unknown) as jest.Mock).mockReturnValue({ + (useGetAllTimeline as unknown as jest.Mock).mockReturnValue({ fetchAllTimeline: jest.fn(), timelines: getAllTimeline('', mockOpenTimelineQueryResults.timeline ?? []), loading: false, totalCount: mockOpenTimelineQueryResults.totalCount, refetch: jest.fn(), }); - ((useTimelineStatus as unknown) as jest.Mock).mockReturnValue({ + (useTimelineStatus as unknown as jest.Mock).mockReturnValue({ timelineStatus: null, templateTimelineType: null, templateTimelineFilter:
, diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx index 21e85fd3c5a4f..25dcdb887d336 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx @@ -115,9 +115,10 @@ export const StatefulOpenTimelineComponent = React.memo( const existingIndexNames = useDeepEqualSelector(existingIndexNamesSelector); const updateTimeline = useMemo(() => dispatchUpdateTimeline(dispatch), [dispatch]); - const updateIsLoading = useCallback((payload) => dispatch(dispatchUpdateIsLoading(payload)), [ - dispatch, - ]); + const updateIsLoading = useCallback( + (payload) => dispatch(dispatchUpdateIsLoading(payload)), + [dispatch] + ); const { customTemplateTimelineCount, @@ -134,15 +135,12 @@ export const StatefulOpenTimelineComponent = React.memo( defaultTimelineCount, templateTimelineCount, }); - const { - timelineStatus, - templateTimelineFilter, - installPrepackagedTimelines, - } = useTimelineStatus({ - timelineType, - customTemplateTimelineCount, - elasticTemplateTimelineCount, - }); + const { timelineStatus, templateTimelineFilter, installPrepackagedTimelines } = + useTimelineStatus({ + timelineType, + customTemplateTimelineCount, + elasticTemplateTimelineCount, + }); const refetch = useCallback(() => { fetchAllTimeline({ pageInfo: { diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/index.test.tsx index 12da999c21fc8..3eab32a6ed76b 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/index.test.tsx @@ -38,23 +38,21 @@ jest.mock('../use_timeline_status', () => ({ // mock for EuiSelectable's virtualization jest.mock( 'react-virtualized-auto-sizer', - () => ({ - children, - }: { - children: (dimensions: { width: number; height: number }) => ReactElement; - }) => children({ width: 100, height: 500 }) + () => + ({ children }: { children: (dimensions: { width: number; height: number }) => ReactElement }) => + children({ width: 100, height: 500 }) ); describe('OpenTimelineModal', () => { const mockInstallPrepackagedTimelines = jest.fn(); beforeEach(() => { - ((useGetAllTimeline as unknown) as jest.Mock).mockReturnValue({ + (useGetAllTimeline as unknown as jest.Mock).mockReturnValue({ fetchAllTimeline: jest.fn(), timelines: getAllTimeline('', mockOpenTimelineQueryResults.timeline ?? []), loading: false, totalCount: mockOpenTimelineQueryResults.totalCount, }); - ((useTimelineStatus as unknown) as jest.Mock).mockReturnValue({ + (useTimelineStatus as unknown as jest.Mock).mockReturnValue({ timelineStatus: null, templateTimelineType: null, templateTimelineFilter:
, diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_timeline_status.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_timeline_status.tsx index baa9a591f9fbf..060212d0972de 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_timeline_status.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_timeline_status.tsx @@ -36,14 +36,15 @@ export const useTimelineStatus = ({ installPrepackagedTimelines: () => void; } => { const [selectedTab, setSelectedTab] = useState(null); - const isTemplateFilterEnabled = useMemo(() => timelineType === TimelineType.template, [ - timelineType, - ]); + const isTemplateFilterEnabled = useMemo( + () => timelineType === TimelineType.template, + [timelineType] + ); - const templateTimelineType = useMemo(() => (!isTemplateFilterEnabled ? null : selectedTab), [ - selectedTab, - isTemplateFilterEnabled, - ]); + const templateTimelineType = useMemo( + () => (!isTemplateFilterEnabled ? null : selectedTab), + [selectedTab, isTemplateFilterEnabled] + ); const timelineStatus = useMemo( () => diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/footer.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/footer.tsx index a9d83bd85ba7b..32c3f5a885346 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/footer.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/footer.tsx @@ -89,11 +89,8 @@ export const EventDetailsFooter = React.memo( refetch: expandedEvent?.refetch, timelineId, }); - const { - closeAddEventFilterModal, - isAddEventFilterModalOpen, - onAddEventFilterClick, - } = useEventFilterModal(); + const { closeAddEventFilterModal, isAddEventFilterModalOpen, onAddEventFilterClick } = + useEventFilterModal(); const { alertsEcsData } = useFetchEcsAlertsData({ alertIds: eventIds, diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx index fee4646e88186..ba58e8a084067 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx @@ -92,9 +92,8 @@ const EventDetailsPanelComponent: React.FC = ({ 'isolateHost' ); - const [isIsolateActionSuccessBannerVisible, setIsIsolateActionSuccessBannerVisible] = useState( - false - ); + const [isIsolateActionSuccessBannerVisible, setIsIsolateActionSuccessBannerVisible] = + useState(false); const showAlertDetails = useCallback(() => { setIsHostIsolationPanel(false); @@ -115,9 +114,10 @@ const EventDetailsPanelComponent: React.FC = ({ [detailsData] ); - const alertId = useMemo(() => getFieldValue({ category: '_id', field: '_id' }, detailsData), [ - detailsData, - ]); + const alertId = useMemo( + () => getFieldValue({ category: '_id', field: '_id' }, detailsData), + [detailsData] + ); const hostName = useMemo( () => getFieldValue({ category: 'host', field: 'host.name' }, detailsData), diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/index.test.tsx index 511887dc93cdf..7e530da542bf8 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/index.test.tsx @@ -117,7 +117,8 @@ describe('Details Panel Component', () => { }); test('it should not render the DetailsPanel if an expanded detail with a panelView, but not params have been set', () => { - state.timeline.timelineById.test.expandedDetail = dataLessExpandedDetail as TimelineExpandedDetail; // Casting as the dataless doesn't meet the actual type requirements + state.timeline.timelineById.test.expandedDetail = + dataLessExpandedDetail as TimelineExpandedDetail; // Casting as the dataless doesn't meet the actual type requirements const wrapper = mount( @@ -224,7 +225,8 @@ describe('Details Panel Component', () => { describe('DetailsPanel:HostDetails: rendering', () => { beforeEach(() => { - state.timeline.timelineById.test.expandedDetail = hostExpandedDetail as TimelineExpandedDetail; + state.timeline.timelineById.test.expandedDetail = + hostExpandedDetail as TimelineExpandedDetail; store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage); }); @@ -241,7 +243,8 @@ describe('Details Panel Component', () => { describe('DetailsPanel:NetworkDetails: rendering', () => { beforeEach(() => { - state.timeline.timelineById.test.expandedDetail = networkExpandedDetail as TimelineExpandedDetail; + state.timeline.timelineById.test.expandedDetail = + networkExpandedDetail as TimelineExpandedDetail; store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx index a99bf730a7fee..8c71a3b38dcd3 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx @@ -160,10 +160,10 @@ const StatefulEventComponent: React.FC = ({ [notesById, noteIds] ); - const hasRowRenderers: boolean = useMemo(() => getRowRenderer(event.ecs, rowRenderers) != null, [ - event.ecs, - rowRenderers, - ]); + const hasRowRenderers: boolean = useMemo( + () => getRowRenderer(event.ecs, rowRenderers) != null, + [event.ecs, rowRenderers] + ); const onToggleShowNotes = useCallback(() => { const eventId = event._id; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_row_renderer/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_row_renderer/index.tsx index cf1f4a26c709d..7124a3de968ba 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_row_renderer/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_row_renderer/index.tsx @@ -61,10 +61,10 @@ export const StatefulRowRenderer = ({ rowindexAttribute: ARIA_ROWINDEX_ATTRIBUTE, }); - const rowRenderer = useMemo(() => getRowRenderer(event.ecs, rowRenderers), [ - event.ecs, - rowRenderers, - ]); + const rowRenderer = useMemo( + () => getRowRenderer(event.ecs, rowRenderers), + [event.ecs, rowRenderers] + ); const content = useMemo( () => diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/use_stateful_event_focus/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/use_stateful_event_focus/index.tsx index 4e8fd7dc48968..3064b29b65640 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/use_stateful_event_focus/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/use_stateful_event_focus/index.tsx @@ -90,12 +90,10 @@ export const useStatefulEventFocus = ({ ] ); - const memoizedReturn = useMemo(() => ({ focusOwnership, onFocus, onOutsideClick, onKeyDown }), [ - focusOwnership, - onFocus, - onKeyDown, - onOutsideClick, - ]); + const memoizedReturn = useMemo( + () => ({ focusOwnership, onFocus, onOutsideClick, onKeyDown }), + [focusOwnership, onFocus, onKeyDown, onOutsideClick] + ); return memoizedReturn; }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx index 1bfefbd1197a0..9509ae0eb7838 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx @@ -97,8 +97,9 @@ jest.mock('../../graph_overlay'); jest.mock( 'react-visibility-sensor', - () => ({ children }: { children: (args: { isVisible: boolean }) => React.ReactNode }) => - children({ isVisible: true }) + () => + ({ children }: { children: (args: { isVisible: boolean }) => React.ReactNode }) => + children({ isVisible: true }) ); jest.mock('../../../../common/lib/helpers/scheduler', () => ({ @@ -121,7 +122,7 @@ describe('Body', () => { const props: StatefulBodyProps = { activePage: 0, browserFields: mockBrowserFields, - clearSelected: (jest.fn() as unknown) as StatefulBodyProps['clearSelected'], + clearSelected: jest.fn() as unknown as StatefulBodyProps['clearSelected'], columnHeaders: defaultHeaders, data: mockTimelineData, eventIdToNoteIds: {}, @@ -134,7 +135,7 @@ describe('Body', () => { renderCellValue: DefaultCellRenderer, rowRenderers: defaultRowRenderers, selectedEventIds: {}, - setSelected: (jest.fn() as unknown) as StatefulBodyProps['setSelected'], + setSelected: jest.fn() as unknown as StatefulBodyProps['setSelected'], sort: mockSort, showCheckboxes: false, tabType: TimelineTabs.query, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/agent_statuses.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/agent_statuses.tsx index edec2d0d823fa..44aeadce6e3b6 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/agent_statuses.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/agent_statuses.tsx @@ -27,12 +27,8 @@ export const AgentStatuses = React.memo( isDraggable: boolean; value: string; }) => { - const { - isIsolated, - agentStatus, - pendingIsolation, - pendingUnisolation, - } = useHostIsolationStatus({ agentId: value }); + const { isIsolated, agentStatus, pendingIsolation, pendingUnisolation } = + useHostIsolationStatus({ agentId: value }); const isolationFieldName = 'host.isolation'; return ( diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_signature.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_signature.tsx index 2a5b57d77498f..18e2d0844779b 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_signature.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_signature.tsx @@ -31,9 +31,9 @@ const SignatureFlexItem = styled(EuiFlexItem)` SignatureFlexItem.displayName = 'SignatureFlexItem'; -const Badge = (styled(EuiBadge)` +const Badge = styled(EuiBadge)` vertical-align: top; -` as unknown) as typeof EuiBadge; +` as unknown as typeof EuiBadge; Badge.displayName = 'Badge'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_signature.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_signature.tsx index 412fd9d04fe7c..41f35e7c50e30 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_signature.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_signature.tsx @@ -22,9 +22,9 @@ import { IS_OPERATOR, QueryOperator } from '../../../data_providers/data_provide import * as i18n from './translations'; -const Badge = (styled(EuiBadge)` +const Badge = styled(EuiBadge)` vertical-align: top; -` as unknown) as typeof EuiBadge; +` as unknown as typeof EuiBadge; Badge.displayName = 'Badge'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/add_data_provider_popover.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/add_data_provider_popover.tsx index 77b776e3a801d..84f286b435a48 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/add_data_provider_popover.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/add_data_provider_popover.tsx @@ -44,13 +44,15 @@ const AddDataProviderPopoverComponent: React.FC = ( pick(['dataProviders', 'timelineType'], getTimeline(state, timelineId)) ); - const handleOpenPopover = useCallback(() => setIsAddFilterPopoverOpen(true), [ - setIsAddFilterPopoverOpen, - ]); + const handleOpenPopover = useCallback( + () => setIsAddFilterPopoverOpen(true), + [setIsAddFilterPopoverOpen] + ); - const handleClosePopover = useCallback(() => setIsAddFilterPopoverOpen(false), [ - setIsAddFilterPopoverOpen, - ]); + const handleClosePopover = useCallback( + () => setIsAddFilterPopoverOpen(false), + [setIsAddFilterPopoverOpen] + ); const handleDataProviderEdited = useCallback( ({ andProviderId, excluded, field, id, operator, providerId, value, type }) => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/empty.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/empty.tsx index f729827fb00b7..d5c5332b25053 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/empty.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/empty.tsx @@ -26,12 +26,12 @@ const Text = styled(EuiText)` Text.displayName = 'Text'; -const BadgeHighlighted = (styled(EuiBadge)` +const BadgeHighlighted = styled(EuiBadge)` height: 20px; margin: 0 5px 0 5px; maxwidth: 85px; minwidth: 85px; -` as unknown) as typeof EuiBadge; +` as unknown as typeof EuiBadge; BadgeHighlighted.displayName = 'BadgeHighlighted'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider_badge.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider_badge.tsx index 1227e73cd7115..6ee6d45804d54 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider_badge.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider_badge.tsx @@ -159,9 +159,10 @@ export const ProviderBadge = React.memo( [isEnabled, isExcluded] ); - const formattedValue = useMemo(() => (isString(val) && val === '' ? getEmptyString() : val), [ - val, - ]); + const formattedValue = useMemo( + () => (isString(val) && val === '' ? getEmptyString() : val), + [val] + ); const prefix = useMemo(() => (isExcluded ? {i18n.NOT} : null), [isExcluded]); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider_item_actions.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider_item_actions.tsx index 6c452e0615c1b..8c758eb6ef569 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider_item_actions.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider_item_actions.tsx @@ -54,7 +54,7 @@ interface OwnProps { type: DataProviderType; } -const MyEuiPopover = styled((EuiPopover as unknown) as FunctionComponent)< +const MyEuiPopover = styled(EuiPopover as unknown as FunctionComponent)< EuiPopoverProps & { id?: string; } diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/providers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/providers.tsx index 5b982e4e831f7..3db938f5efe07 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/providers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/providers.tsx @@ -377,10 +377,10 @@ interface DataProvidersGroup extends Props { const DataProvidersGroup = React.memo( ({ browserFields, timelineId, group, groupIndex, isLastGroup }) => { - const droppableId = useMemo(() => getTimelineProviderDroppableId({ groupIndex, timelineId }), [ - groupIndex, - timelineId, - ]); + const droppableId = useMemo( + () => getTimelineProviderDroppableId({ groupIndex, timelineId }), + [groupIndex, timelineId] + ); const GroupDataProviders = useMemo( () => diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/index.tsx index b67b9348f51aa..737c6b99cea75 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/index.tsx @@ -205,23 +205,21 @@ export const EqlTabContentComponent: React.FC = ({ ); }, [dispatch, timelineId]); - const [ - isQueryLoading, - { events, inspect, totalCount, pageInfo, loadPage, updatedAt, refetch }, - ] = useTimelineEvents({ - docValueFields, - endDate: end, - eqlOptions: restEqlOption, - id: timelineId, - indexNames: selectedPatterns, - fields: getTimelineQueryFields(), - language: 'eql', - limit: itemsPerPage, - filterQuery: eqlQuery ?? '', - startDate: start, - skip: !canQueryTimeline(), - timerangeKind, - }); + const [isQueryLoading, { events, inspect, totalCount, pageInfo, loadPage, updatedAt, refetch }] = + useTimelineEvents({ + docValueFields, + endDate: end, + eqlOptions: restEqlOption, + id: timelineId, + indexNames: selectedPatterns, + fields: getTimelineQueryFields(), + language: 'eql', + limit: itemsPerPage, + filterQuery: eqlQuery ?? '', + startDate: start, + skip: !canQueryTimeline(), + timerangeKind, + }); const handleOnPanelClosed = useCallback(() => { onEventClosed({ tabType: TimelineTabs.eql, timelineId }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/index.tsx index f68538703951a..80a682293dbba 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/index.tsx @@ -90,7 +90,7 @@ const LoadingPanelContainer = styled.div` LoadingPanelContainer.displayName = 'LoadingPanelContainer'; -const PopoverRowItems = styled((EuiPopover as unknown) as FC)< +const PopoverRowItems = styled(EuiPopover as unknown as FC)< EuiPopoverProps & { className?: string; id?: string; @@ -132,9 +132,10 @@ export const EventsCountComponent = ({ serverSideEventCount: number; footerText: string | React.ReactNode; }) => { - const totalCount = useMemo(() => (serverSideEventCount > 0 ? serverSideEventCount : 0), [ - serverSideEventCount, - ]); + const totalCount = useMemo( + () => (serverSideEventCount > 0 ? serverSideEventCount : 0), + [serverSideEventCount] + ); return (
setIsPopoverOpen(!isPopoverOpen), [ - isPopoverOpen, - setIsPopoverOpen, - ]); + const onButtonClick = useCallback( + () => setIsPopoverOpen(!isPopoverOpen), + [isPopoverOpen, setIsPopoverOpen] + ); const closePopover = useCallback(() => setIsPopoverOpen(false), [setIsPopoverOpen]); @@ -316,10 +317,10 @@ export const FooterComponent = ({ [closePopover, itemsPerPage, itemsPerPageOptions, onChangeItemsPerPage] ); - const totalPages = useMemo(() => Math.ceil(totalCount / itemsPerPage), [ - itemsPerPage, - totalCount, - ]); + const totalPages = useMemo( + () => Math.ceil(totalCount / itemsPerPage), + [itemsPerPage, totalCount] + ); useEffect(() => { if (paginationLoading && !isLoading) { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/header/title_and_description.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/title_and_description.tsx index 8db68706576cb..e989685130a38 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/header/title_and_description.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/title_and_description.tsx @@ -137,9 +137,10 @@ export const TimelineTitleAndDescription = React.memo i18n.UNSAVED_TIMELINE_WARNING(timelineType), [ - timelineType, - ]); + const calloutMessage = useMemo( + () => i18n.UNSAVED_TIMELINE_WARNING(timelineType), + [timelineType] + ); const descriptionLabel = useMemo(() => `${i18n.TIMELINE_DESCRIPTION} (${i18n.OPTIONAL})`, []); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.tsx index b5e3d853bc81c..2051f95b75ae8 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.tsx @@ -115,9 +115,11 @@ export const PinnedTabContentComponent: React.FC = ({ showExpandedDetails, sort, }) => { - const { browserFields, docValueFields, loading: loadingSourcerer } = useSourcererScope( - SourcererScopeName.timeline - ); + const { + browserFields, + docValueFields, + loading: loadingSourcerer, + } = useSourcererScope(SourcererScopeName.timeline); const { setTimelineFullScreen, timelineFullScreen } = useTimelineFullScreen(); const existingIndexNamesSelector = useMemo( @@ -181,22 +183,20 @@ export const PinnedTabContentComponent: React.FC = ({ [sort] ); - const [ - isQueryLoading, - { events, totalCount, pageInfo, loadPage, updatedAt, refetch }, - ] = useTimelineEvents({ - docValueFields, - endDate: '', - id: `pinned-${timelineId}`, - indexNames: existingIndexNames, - fields: timelineQueryFields, - limit: itemsPerPage, - filterQuery, - skip: filterQuery === '', - startDate: '', - sort: timelineQuerySortField, - timerangeKind: undefined, - }); + const [isQueryLoading, { events, totalCount, pageInfo, loadPage, updatedAt, refetch }] = + useTimelineEvents({ + docValueFields, + endDate: '', + id: `pinned-${timelineId}`, + indexNames: existingIndexNames, + fields: timelineQueryFields, + limit: itemsPerPage, + filterQuery, + skip: filterQuery === '', + startDate: '', + sort: timelineQuerySortField, + timerangeKind: undefined, + }); const handleOnPanelClosed = useCallback(() => { onEventClosed({ tabType: TimelineTabs.pinned, timelineId }); @@ -284,14 +284,8 @@ const makeMapStateToProps = () => { const getTimeline = timelineSelectors.getTimelineByIdSelector(); const mapStateToProps = (state: State, { timelineId }: OwnProps) => { const timeline: TimelineModel = getTimeline(state, timelineId) ?? timelineDefaults; - const { - columns, - expandedDetail, - itemsPerPage, - itemsPerPageOptions, - pinnedEventIds, - sort, - } = timeline; + const { columns, expandedDetail, itemsPerPage, itemsPerPageOptions, pinnedEventIds, sort } = + timeline; return { columns, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.tsx index 5f5cab00fcbbe..12525c9e4db75 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.tsx @@ -22,9 +22,9 @@ import * as i18n from './translations'; import { useCreateTimelineButton } from './use_create_timeline'; import { timelineDefaults } from '../../../store/timeline/defaults'; -const NotesCountBadge = (styled(EuiBadge)` +const NotesCountBadge = styled(EuiBadge)` margin-left: 5px; -` as unknown) as typeof EuiBadge; +` as unknown as typeof EuiBadge; NotesCountBadge.displayName = 'NotesCountBadge'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx index 4ae9e79fac420..b65e432126283 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx @@ -67,9 +67,11 @@ export const EqlQueryBarTimeline = memo(({ timelineId }: { timelineId: string }) const getOptionsSelected = useMemo(() => getEqlOptions(), []); const optionsSelected = useDeepEqualSelector((state) => getOptionsSelected(state, timelineId)); - const { loading: indexPatternsLoading, indexPattern, selectedPatterns } = useSourcererScope( - SourcererScopeName.timeline - ); + const { + loading: indexPatternsLoading, + indexPattern, + selectedPatterns, + } = useSourcererScope(SourcererScopeName.timeline); const initialState = { ...defaultValues, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx index c150f1a44f196..d2cf158818f75 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx @@ -200,18 +200,19 @@ export const QueryTabContentComponent: React.FC = ({ getManageTimeline(state, timelineId ?? '') ); - const filterManager = useMemo(() => activeFilterManager ?? new FilterManager(uiSettings), [ - activeFilterManager, - uiSettings, - ]); + const filterManager = useMemo( + () => activeFilterManager ?? new FilterManager(uiSettings), + [activeFilterManager, uiSettings] + ); const esQueryConfig = useMemo(() => esQuery.getEsQueryConfig(uiSettings), [uiSettings]); const kqlQuery: { query: string; language: KueryFilterQueryKind; - } = useMemo(() => ({ query: kqlQueryExpression.trim(), language: 'kuery' }), [ - kqlQueryExpression, - ]); + } = useMemo( + () => ({ query: kqlQueryExpression.trim(), language: 'kuery' }), + [kqlQueryExpression] + ); const combinedQueries = combineQueries({ config: esQueryConfig, @@ -271,23 +272,21 @@ export const QueryTabContentComponent: React.FC = ({ ); }, [activeFilterManager, dispatch, filterManager, timelineId, uiSettings]); - const [ - isQueryLoading, - { events, inspect, totalCount, pageInfo, loadPage, updatedAt, refetch }, - ] = useTimelineEvents({ - docValueFields, - endDate: end, - id: timelineId, - indexNames: selectedPatterns, - fields: getTimelineQueryFields(), - language: kqlQuery.language, - limit: itemsPerPage, - filterQuery: combinedQueries?.filterQuery, - startDate: start, - skip: !canQueryTimeline, - sort: timelineQuerySortField, - timerangeKind, - }); + const [isQueryLoading, { events, inspect, totalCount, pageInfo, loadPage, updatedAt, refetch }] = + useTimelineEvents({ + docValueFields, + endDate: end, + id: timelineId, + indexNames: selectedPatterns, + fields: getTimelineQueryFields(), + language: kqlQuery.language, + limit: itemsPerPage, + filterQuery: combinedQueries?.filterQuery, + startDate: start, + skip: !canQueryTimeline, + sort: timelineQuerySortField, + timerangeKind, + }); const handleOnPanelClosed = useCallback(() => { onEventClosed({ tabType: TimelineTabs.query, timelineId }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/pick_events.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/pick_events.tsx index 3a845575852d1..5682bdb91ff58 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/pick_events.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/pick_events.tsx @@ -264,9 +264,10 @@ const PickEventTypeComponents: React.FC = ({ [onChangeCombo, indexesPatternOptions, renderOption, selectedOptions] ); - const filterOptions = useMemo(() => getEventTypeOptions(filterEventType !== 'custom'), [ - filterEventType, - ]); + const filterOptions = useMemo( + () => getEventTypeOptions(filterEventType !== 'custom'), + [filterEventType] + ); const filter = useMemo( () => ( diff --git a/x-pack/plugins/security_solution/public/timelines/containers/details/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/details/index.tsx index 98d385a1fdff0..e59eaeed4f2a6 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/details/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/containers/details/index.tsx @@ -48,15 +48,12 @@ export const useTimelineEventsDetails = ({ const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); const [loading, setLoading] = useState(false); - const [ - timelineDetailsRequest, - setTimelineDetailsRequest, - ] = useState(null); + const [timelineDetailsRequest, setTimelineDetailsRequest] = + useState(null); const { addError, addWarning } = useAppToasts(); - const [timelineDetailsResponse, setTimelineDetailsResponse] = useState( - null - ); + const [timelineDetailsResponse, setTimelineDetailsResponse] = + useState(null); const timelineDetailsSearch = useCallback( (request: TimelineEventsDetailsRequestOptions | null) => { diff --git a/x-pack/plugins/security_solution/public/timelines/containers/kpis/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/kpis/index.tsx index be93a13ab1c6a..f32b4df2c4684 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/kpis/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/containers/kpis/index.tsx @@ -47,10 +47,8 @@ export const useTimelineKpis = ({ const [timelineKpiRequest, setTimelineKpiRequest] = useState( null ); - const [ - timelineKpiResponse, - setTimelineKpiResponse, - ] = useState(null); + const [timelineKpiResponse, setTimelineKpiResponse] = + useState(null); const { addError, addWarning } = useAppToasts(); const timelineKpiSearch = useCallback( diff --git a/x-pack/plugins/security_solution/public/timelines/containers/local_storage/index.test.ts b/x-pack/plugins/security_solution/public/timelines/containers/local_storage/index.test.ts index 8fbb330d51231..938655803be80 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/local_storage/index.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/containers/local_storage/index.test.ts @@ -294,7 +294,7 @@ describe('SiemLocalStorage', () => { }; timelineStorage.addTimeline( TimelineId.hostsPageEvents, - (invalidColumnsMockTimelineModel as unknown) as TimelineModel + invalidColumnsMockTimelineModel as unknown as TimelineModel ); timelineStorage.addTimeline(TimelineId.hostsPageExternalAlerts, mockTimelineModel); const timelines = getTimelinesInStorageByIds(storage, [ diff --git a/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.test.tsx b/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.test.tsx index 3165af8ee3a5e..597ff25246d94 100644 --- a/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.test.tsx @@ -46,7 +46,7 @@ describe('TimelinesPageComponent', () => { describe('If the user is authorized', () => { beforeAll(() => { - ((useKibana as unknown) as jest.Mock).mockReturnValue({ + (useKibana as unknown as jest.Mock).mockReturnValue({ services: { application: { capabilities: { @@ -61,7 +61,7 @@ describe('TimelinesPageComponent', () => { }); afterAll(() => { - ((useKibana as unknown) as jest.Mock).mockReset(); + (useKibana as unknown as jest.Mock).mockReset(); }); test('should not show the import timeline modal by default', () => { @@ -92,7 +92,7 @@ describe('TimelinesPageComponent', () => { describe('If the user is not authorized', () => { beforeAll(() => { - ((useKibana as unknown) as jest.Mock).mockReturnValue({ + (useKibana as unknown as jest.Mock).mockReturnValue({ services: { application: { capabilities: { @@ -107,7 +107,7 @@ describe('TimelinesPageComponent', () => { }); afterAll(() => { - ((useKibana as unknown) as jest.Mock).mockReset(); + (useKibana as unknown as jest.Mock).mockReset(); }); test('should not show the import timeline modal by default', () => { expect( diff --git a/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.tsx b/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.tsx index 5d2e45b638d59..728153f47abd7 100644 --- a/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.tsx +++ b/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.tsx @@ -38,8 +38,8 @@ export const TimelinesPageComponent: React.FC = () => { }, [setImportDataModalToggle]); const { indicesExist } = useSourcererScope(); - const capabilitiesCanUserCRUD: boolean = !!useKibana().services.application.capabilities.siem - .crud; + const capabilitiesCanUserCRUD: boolean = + !!useKibana().services.application.capabilities.siem.crud; return ( <> diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts index d0d5fdacad312..3750bc22ddc69 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts @@ -51,9 +51,8 @@ export const addHistory = actionCreator<{ id: string; historyId: string }>('ADD_ export const addNote = actionCreator<{ id: string; noteId: string }>('ADD_NOTE'); -export const addNoteToEvent = actionCreator<{ id: string; noteId: string; eventId: string }>( - 'ADD_NOTE_TO_EVENT' -); +export const addNoteToEvent = + actionCreator<{ id: string; noteId: string; eventId: string }>('ADD_NOTE_TO_EVENT'); export const showTimeline = actionCreator<{ id: string; show: boolean }>('SHOW_TIMELINE'); @@ -67,9 +66,8 @@ export const createTimeline = actionCreator('CREATE_TIMELI export const pinEvent = actionCreator<{ id: string; eventId: string }>('PIN_EVENT'); -export const setTimelineUpdatedAt = actionCreator<{ id: string; updated: number }>( - 'SET_TIMELINE_UPDATED_AT' -); +export const setTimelineUpdatedAt = + actionCreator<{ id: string; updated: number }>('SET_TIMELINE_UPDATED_AT'); export const removeProvider = actionCreator<{ id: string; @@ -146,9 +144,8 @@ export const applyKqlFilterQuery = actionCreator<{ filterQuery: SerializedFilterQuery; }>('APPLY_KQL_FILTER_QUERY'); -export const updateIsFavorite = actionCreator<{ id: string; isFavorite: boolean }>( - 'UPDATE_IS_FAVORITE' -); +export const updateIsFavorite = + actionCreator<{ id: string; isFavorite: boolean }>('UPDATE_IS_FAVORITE'); export const updateIsLive = actionCreator<{ id: string; isLive: boolean }>('UPDATE_IS_LIVE'); @@ -158,17 +155,14 @@ export const updateTitleAndDescription = actionCreator<{ title: string; }>('UPDATE_TITLE_AND_DESCRIPTION'); -export const updatePageIndex = actionCreator<{ id: string; activePage: number }>( - 'UPDATE_PAGE_INDEX' -); +export const updatePageIndex = + actionCreator<{ id: string; activePage: number }>('UPDATE_PAGE_INDEX'); -export const updateProviders = actionCreator<{ id: string; providers: DataProvider[] }>( - 'UPDATE_PROVIDERS' -); +export const updateProviders = + actionCreator<{ id: string; providers: DataProvider[] }>('UPDATE_PROVIDERS'); -export const updateRange = actionCreator<{ id: string; start: string; end: string }>( - 'UPDATE_RANGE' -); +export const updateRange = + actionCreator<{ id: string; start: string; end: string }>('UPDATE_RANGE'); export const updateAutoSaveMsg = actionCreator<{ timelineId: string | null; @@ -187,9 +181,8 @@ export const setFilters = actionCreator<{ filters: Filter[]; }>('SET_TIMELINE_FILTERS'); -export const updateEventType = actionCreator<{ id: string; eventType: TimelineEventsType }>( - 'UPDATE_EVENT_TYPE' -); +export const updateEventType = + actionCreator<{ id: string; eventType: TimelineEventsType }>('UPDATE_EVENT_TYPE'); export const setExcludedRowRendererIds = actionCreator<{ id: string; diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/defaults.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/defaults.ts index 0ba3f91173d0a..4691872bfb927 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/defaults.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/defaults.ts @@ -14,64 +14,64 @@ import { SubsetTimelineModel, TimelineModel } from './model'; // normalizeTimeRange uses getTimeRangeSettings which cannot be used outside Kibana context if the uiSettings is not false const { from: start, to: end } = normalizeTimeRange({ from: '', to: '' }, false); -export const timelineDefaults: SubsetTimelineModel & - Pick = { - activeTab: TimelineTabs.query, - prevActiveTab: TimelineTabs.query, - columns: defaultHeaders, - documentType: '', - defaultColumns: defaultHeaders, - dataProviders: [], - dateRange: { start, end }, - deletedEventIds: [], - description: '', - eqlOptions: { - eventCategoryField: 'event.category', - tiebreakerField: '', - timestampField: '@timestamp', - query: '', - size: 100, - }, - eventType: 'all', - eventIdToNoteIds: {}, - excludedRowRendererIds: [], - expandedDetail: {}, - highlightedDropAndProviderId: '', - historyIds: [], - filters: [], - indexNames: [], - isFavorite: false, - isLive: false, - isSelectAllChecked: false, - isLoading: false, - isSaving: false, - itemsPerPage: 25, - itemsPerPageOptions: [10, 25, 50, 100], - kqlMode: 'filter', - kqlQuery: { - filterQuery: null, - }, - loadingEventIds: [], - queryFields: [], - title: '', - timelineType: TimelineType.default, - templateTimelineId: null, - templateTimelineVersion: null, - noteIds: [], - pinnedEventIds: {}, - pinnedEventsSaveObject: {}, - savedObjectId: null, - selectAll: false, - selectedEventIds: {}, - show: false, - showCheckboxes: false, - sort: [ - { - columnId: '@timestamp', - columnType: 'number', - sortDirection: 'desc', +export const timelineDefaults: SubsetTimelineModel & Pick = + { + activeTab: TimelineTabs.query, + prevActiveTab: TimelineTabs.query, + columns: defaultHeaders, + documentType: '', + defaultColumns: defaultHeaders, + dataProviders: [], + dateRange: { start, end }, + deletedEventIds: [], + description: '', + eqlOptions: { + eventCategoryField: 'event.category', + tiebreakerField: '', + timestampField: '@timestamp', + query: '', + size: 100, }, - ], - status: TimelineStatus.draft, - version: null, -}; + eventType: 'all', + eventIdToNoteIds: {}, + excludedRowRendererIds: [], + expandedDetail: {}, + highlightedDropAndProviderId: '', + historyIds: [], + filters: [], + indexNames: [], + isFavorite: false, + isLive: false, + isSelectAllChecked: false, + isLoading: false, + isSaving: false, + itemsPerPage: 25, + itemsPerPageOptions: [10, 25, 50, 100], + kqlMode: 'filter', + kqlQuery: { + filterQuery: null, + }, + loadingEventIds: [], + queryFields: [], + title: '', + timelineType: TimelineType.default, + templateTimelineId: null, + templateTimelineVersion: null, + noteIds: [], + pinnedEventIds: {}, + pinnedEventsSaveObject: {}, + savedObjectId: null, + selectAll: false, + selectedEventIds: {}, + show: false, + showCheckboxes: false, + sort: [ + { + columnId: '@timestamp', + columnType: 'number', + sortDirection: 'desc', + }, + ], + status: TimelineStatus.draft, + version: null, + }; diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.test.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.test.ts index 686c8220f677b..1b1434e3e8a3e 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.test.ts @@ -75,8 +75,7 @@ describe('Epic Timeline', () => { }, and: [ { - id: - 'plain-column-renderer-data-provider-hosts-page-event_module-CQg7I24BHe9nqdOi_LYL-event_module-endgame', + id: 'plain-column-renderer-data-provider-hosts-page-event_module-CQg7I24BHe9nqdOi_LYL-event_module-endgame', name: 'event.module: endgame', enabled: true, excluded: false, @@ -217,8 +216,7 @@ describe('Epic Timeline', () => { { enabled: true, excluded: false, - id: - 'plain-column-renderer-data-provider-hosts-page-event_module-CQg7I24BHe9nqdOi_LYL-event_module-endgame', + id: 'plain-column-renderer-data-provider-hosts-page-event_module-CQg7I24BHe9nqdOi_LYL-event_module-endgame', kqlQuery: '', name: 'event.module: endgame', queryMatch: { diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.ts index c199ed0bfde8b..c1f107a004da3 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.ts @@ -120,210 +120,208 @@ const timelineActionsType = [ const isItAtimelineAction = (timelineId: string | undefined) => timelineId && timelineId.toLowerCase().startsWith('timeline'); -export const createTimelineEpic = (): Epic< - Action, - Action, - State, - TimelineEpicDependencies -> => ( - action$, - state$, - { - selectAllTimelineQuery, - selectNotesByIdSelector, - timelineByIdSelector, - timelineTimeRangeSelector, - kibana$, - } -) => { - const timeline$ = state$.pipe(map(timelineByIdSelector), filter(isNotNull)); +export const createTimelineEpic = + (): Epic> => + ( + action$, + state$, + { + selectAllTimelineQuery, + selectNotesByIdSelector, + timelineByIdSelector, + timelineTimeRangeSelector, + kibana$, + } + ) => { + const timeline$ = state$.pipe(map(timelineByIdSelector), filter(isNotNull)); - const allTimelineQuery$ = state$.pipe( - map((state) => { - const getQuery = selectAllTimelineQuery(); - return getQuery(state, ALL_TIMELINE_QUERY_ID); - }), - filter(isNotNull) - ); + const allTimelineQuery$ = state$.pipe( + map((state) => { + const getQuery = selectAllTimelineQuery(); + return getQuery(state, ALL_TIMELINE_QUERY_ID); + }), + filter(isNotNull) + ); - const notes$ = state$.pipe(map(selectNotesByIdSelector), filter(isNotNull)); + const notes$ = state$.pipe(map(selectNotesByIdSelector), filter(isNotNull)); - const timelineTimeRange$ = state$.pipe(map(timelineTimeRangeSelector), filter(isNotNull)); + const timelineTimeRange$ = state$.pipe(map(timelineTimeRangeSelector), filter(isNotNull)); - return merge( - action$.pipe( - withLatestFrom(timeline$), - filter(([action, timeline]) => { - const timelineId: string = get('payload.id', action); - const timelineObj: TimelineModel = timeline[timelineId]; - if (action.type === addError.type) { - return true; - } - if ( - isItAtimelineAction(timelineId) && - timelineObj != null && - timelineObj.status != null && - TimelineStatus.immutable === timelineObj.status - ) { - return false; - } else if (action.type === createTimeline.type && isItAtimelineAction(timelineId)) { - myEpicTimelineId.setTimelineVersion(null); - myEpicTimelineId.setTimelineId(null); - myEpicTimelineId.setTemplateTimelineId(null); - myEpicTimelineId.setTemplateTimelineVersion(null); - } else if (action.type === addTimeline.type && isItAtimelineAction(timelineId)) { - const addNewTimeline: TimelineModel = get('payload.timeline', action); - myEpicTimelineId.setTimelineId(addNewTimeline.savedObjectId); - myEpicTimelineId.setTimelineVersion(addNewTimeline.version); - myEpicTimelineId.setTemplateTimelineId(addNewTimeline.templateTimelineId); - myEpicTimelineId.setTemplateTimelineVersion(addNewTimeline.templateTimelineVersion); - return getOr(false, 'payload.savedTimeline', action); - } else if ( - timelineActionsType.includes(action.type) && - !timelineObj.isLoading && - isItAtimelineAction(timelineId) - ) { - return true; - } - }), - debounceTime(500), - mergeMap(([action]) => { - dispatcherTimelinePersistQueue.next({ action }); - return empty(); - }) - ), - dispatcherTimelinePersistQueue.pipe( - delay(500), - withLatestFrom(timeline$, notes$, timelineTimeRange$), - concatMap(([objAction, timeline, notes, timelineTimeRange]) => { - const action: ActionTimeline = get('action', objAction); - const timelineId = myEpicTimelineId.getTimelineId(); - const version = myEpicTimelineId.getTimelineVersion(); - const templateTimelineId = myEpicTimelineId.getTemplateTimelineId(); - const templateTimelineVersion = myEpicTimelineId.getTemplateTimelineVersion(); + return merge( + action$.pipe( + withLatestFrom(timeline$), + filter(([action, timeline]) => { + const timelineId: string = get('payload.id', action); + const timelineObj: TimelineModel = timeline[timelineId]; + if (action.type === addError.type) { + return true; + } + if ( + isItAtimelineAction(timelineId) && + timelineObj != null && + timelineObj.status != null && + TimelineStatus.immutable === timelineObj.status + ) { + return false; + } else if (action.type === createTimeline.type && isItAtimelineAction(timelineId)) { + myEpicTimelineId.setTimelineVersion(null); + myEpicTimelineId.setTimelineId(null); + myEpicTimelineId.setTemplateTimelineId(null); + myEpicTimelineId.setTemplateTimelineVersion(null); + } else if (action.type === addTimeline.type && isItAtimelineAction(timelineId)) { + const addNewTimeline: TimelineModel = get('payload.timeline', action); + myEpicTimelineId.setTimelineId(addNewTimeline.savedObjectId); + myEpicTimelineId.setTimelineVersion(addNewTimeline.version); + myEpicTimelineId.setTemplateTimelineId(addNewTimeline.templateTimelineId); + myEpicTimelineId.setTemplateTimelineVersion(addNewTimeline.templateTimelineVersion); + return getOr(false, 'payload.savedTimeline', action); + } else if ( + timelineActionsType.includes(action.type) && + !timelineObj.isLoading && + isItAtimelineAction(timelineId) + ) { + return true; + } + }), + debounceTime(500), + mergeMap(([action]) => { + dispatcherTimelinePersistQueue.next({ action }); + return empty(); + }) + ), + dispatcherTimelinePersistQueue.pipe( + delay(500), + withLatestFrom(timeline$, notes$, timelineTimeRange$), + concatMap(([objAction, timeline, notes, timelineTimeRange]) => { + const action: ActionTimeline = get('action', objAction); + const timelineId = myEpicTimelineId.getTimelineId(); + const version = myEpicTimelineId.getTimelineVersion(); + const templateTimelineId = myEpicTimelineId.getTemplateTimelineId(); + const templateTimelineVersion = myEpicTimelineId.getTemplateTimelineVersion(); - if (timelineNoteActionsType.includes(action.type)) { - return epicPersistNote( - action, - timeline, - notes, - action$, - timeline$, - notes$, - allTimelineQuery$ - ); - } else if (timelinePinnedEventActionsType.includes(action.type)) { - return epicPersistPinnedEvent(action, timeline, action$, timeline$, allTimelineQuery$); - } else if (timelineFavoriteActionsType.includes(action.type)) { - return epicPersistTimelineFavorite( - action, - timeline, - action$, - timeline$, - allTimelineQuery$ - ); - } else if (timelineActionsType.includes(action.type)) { - return from( - persistTimeline({ - timelineId, - version, - timeline: { - ...convertTimelineAsInput(timeline[action.payload.id], timelineTimeRange), - templateTimelineId, - templateTimelineVersion, - }, - }) - ).pipe( - withLatestFrom(timeline$, allTimelineQuery$, kibana$), - mergeMap(([result, recentTimeline, allTimelineQuery, kibana]) => { - const error = result as TimelineErrorResponse; - if (error.status_code != null && error.status_code === 405) { - kibana.notifications!.toasts.addDanger({ - title: i18n.UPDATE_TIMELINE_ERROR_TITLE, - text: error.message ?? i18n.UPDATE_TIMELINE_ERROR_TEXT, - }); - return [ - endTimelineSaving({ - id: action.payload.id, - }), - ]; - } + if (timelineNoteActionsType.includes(action.type)) { + return epicPersistNote( + action, + timeline, + notes, + action$, + timeline$, + notes$, + allTimelineQuery$ + ); + } else if (timelinePinnedEventActionsType.includes(action.type)) { + return epicPersistPinnedEvent(action, timeline, action$, timeline$, allTimelineQuery$); + } else if (timelineFavoriteActionsType.includes(action.type)) { + return epicPersistTimelineFavorite( + action, + timeline, + action$, + timeline$, + allTimelineQuery$ + ); + } else if (timelineActionsType.includes(action.type)) { + return from( + persistTimeline({ + timelineId, + version, + timeline: { + ...convertTimelineAsInput(timeline[action.payload.id], timelineTimeRange), + templateTimelineId, + templateTimelineVersion, + }, + }) + ).pipe( + withLatestFrom(timeline$, allTimelineQuery$, kibana$), + mergeMap(([result, recentTimeline, allTimelineQuery, kibana]) => { + const error = result as TimelineErrorResponse; + if (error.status_code != null && error.status_code === 405) { + kibana.notifications!.toasts.addDanger({ + title: i18n.UPDATE_TIMELINE_ERROR_TITLE, + text: error.message ?? i18n.UPDATE_TIMELINE_ERROR_TEXT, + }); + return [ + endTimelineSaving({ + id: action.payload.id, + }), + ]; + } + + const savedTimeline = recentTimeline[action.payload.id]; + const response: ResponseTimeline = get('data.persistTimeline', result); + if (response == null) { + return [ + endTimelineSaving({ + id: action.payload.id, + }), + ]; + } + const callOutMsg = response.code === 403 ? [showCallOutUnauthorizedMsg()] : []; + + if (allTimelineQuery.refetch != null) { + (allTimelineQuery.refetch as inputsModel.Refetch)(); + } - const savedTimeline = recentTimeline[action.payload.id]; - const response: ResponseTimeline = get('data.persistTimeline', result); - if (response == null) { return [ + response.code === 409 + ? updateAutoSaveMsg({ + timelineId: action.payload.id, + newTimelineModel: omitTypenameInTimeline(savedTimeline, response.timeline), + }) + : updateTimeline({ + id: action.payload.id, + timeline: { + ...savedTimeline, + updated: response.timeline.updated ?? undefined, + savedObjectId: response.timeline.savedObjectId, + version: response.timeline.version, + status: response.timeline.status ?? TimelineStatus.active, + timelineType: response.timeline.timelineType ?? TimelineType.default, + templateTimelineId: response.timeline.templateTimelineId ?? null, + templateTimelineVersion: + response.timeline.templateTimelineVersion ?? null, + isSaving: false, + }, + }), + ...callOutMsg, endTimelineSaving({ id: action.payload.id, }), ]; - } - const callOutMsg = response.code === 403 ? [showCallOutUnauthorizedMsg()] : []; - - if (allTimelineQuery.refetch != null) { - (allTimelineQuery.refetch as inputsModel.Refetch)(); - } - - return [ - response.code === 409 - ? updateAutoSaveMsg({ - timelineId: action.payload.id, - newTimelineModel: omitTypenameInTimeline(savedTimeline, response.timeline), - }) - : updateTimeline({ - id: action.payload.id, - timeline: { - ...savedTimeline, - updated: response.timeline.updated ?? undefined, - savedObjectId: response.timeline.savedObjectId, - version: response.timeline.version, - status: response.timeline.status ?? TimelineStatus.active, - timelineType: response.timeline.timelineType ?? TimelineType.default, - templateTimelineId: response.timeline.templateTimelineId ?? null, - templateTimelineVersion: response.timeline.templateTimelineVersion ?? null, - isSaving: false, - }, - }), - ...callOutMsg, - endTimelineSaving({ - id: action.payload.id, - }), - ]; - }), - startWith(startTimelineSaving({ id: action.payload.id })), - takeUntil( - action$.pipe( - withLatestFrom(timeline$), - filter(([checkAction, updatedTimeline]) => { - if ( - checkAction.type === endTimelineSaving.type && - updatedTimeline[get('payload.id', checkAction)].savedObjectId != null - ) { - myEpicTimelineId.setTimelineId( - updatedTimeline[get('payload.id', checkAction)].savedObjectId - ); - myEpicTimelineId.setTimelineVersion( - updatedTimeline[get('payload.id', checkAction)].version - ); - myEpicTimelineId.setTemplateTimelineId( - updatedTimeline[get('payload.id', checkAction)].templateTimelineId - ); - myEpicTimelineId.setTemplateTimelineVersion( - updatedTimeline[get('payload.id', checkAction)].templateTimelineVersion - ); - return true; - } - return false; - }) + }), + startWith(startTimelineSaving({ id: action.payload.id })), + takeUntil( + action$.pipe( + withLatestFrom(timeline$), + filter(([checkAction, updatedTimeline]) => { + if ( + checkAction.type === endTimelineSaving.type && + updatedTimeline[get('payload.id', checkAction)].savedObjectId != null + ) { + myEpicTimelineId.setTimelineId( + updatedTimeline[get('payload.id', checkAction)].savedObjectId + ); + myEpicTimelineId.setTimelineVersion( + updatedTimeline[get('payload.id', checkAction)].version + ); + myEpicTimelineId.setTemplateTimelineId( + updatedTimeline[get('payload.id', checkAction)].templateTimelineId + ); + myEpicTimelineId.setTemplateTimelineVersion( + updatedTimeline[get('payload.id', checkAction)].templateTimelineVersion + ); + return true; + } + return false; + }) + ) ) - ) - ); - } - return empty(); - }) - ) - ); -}; + ); + } + return empty(); + }) + ) + ); + }; const timelineInput: TimelineInput = { columns: null, diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_favorite.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_favorite.ts index ae314f54715cd..af067278d997a 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_favorite.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_favorite.ts @@ -102,12 +102,14 @@ export const epicPersistTimelineFavorite = ( ) ); -export const createTimelineFavoriteEpic = (): Epic => (action$) => { - return action$.pipe( - filter((action) => timelineFavoriteActionsType.includes(action.type)), - mergeMap((action) => { - dispatcherTimelinePersistQueue.next({ action }); - return empty(); - }) - ); -}; +export const createTimelineFavoriteEpic = + (): Epic => + (action$) => { + return action$.pipe( + filter((action) => timelineFavoriteActionsType.includes(action.type)), + mergeMap((action) => { + dispatcherTimelinePersistQueue.next({ action }); + return empty(); + }) + ); + }; diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.ts index 22b679504caa3..9a889e9ec1af8 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.ts @@ -39,25 +39,22 @@ export const isPageTimeline = (timelineId: string | undefined): boolean => // Is not a flyout timeline !(timelineId && timelineId.toLowerCase().startsWith('timeline')); -export const createTimelineLocalStorageEpic = (): Epic< - Action, - Action, - State, - TimelineEpicDependencies -> => (action$, state$, { timelineByIdSelector, storage }) => { - const timeline$ = state$.pipe(map(timelineByIdSelector), filter(isNotNull)); - return action$.pipe( - delay(500), - withLatestFrom(timeline$), - filter(([action]) => isPageTimeline(get('payload.id', action))), - tap(([action, timelineById]) => { - if (timelineActionTypes.includes(action.type)) { - if (storage) { - const timelineId: TimelineIdLiteral = get('payload.id', action); - addTimelineInStorage(storage, timelineId, timelineById[timelineId]); +export const createTimelineLocalStorageEpic = + (): Epic> => + (action$, state$, { timelineByIdSelector, storage }) => { + const timeline$ = state$.pipe(map(timelineByIdSelector), filter(isNotNull)); + return action$.pipe( + delay(500), + withLatestFrom(timeline$), + filter(([action]) => isPageTimeline(get('payload.id', action))), + tap(([action, timelineById]) => { + if (timelineActionTypes.includes(action.type)) { + if (storage) { + const timelineId: TimelineIdLiteral = get('payload.id', action); + addTimelineInStorage(storage, timelineId, timelineById[timelineId]); + } } - } - }), - ignoreElements() - ); -}; + }), + ignoreElements() + ); + }; diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_note.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_note.ts index 974bd9d998c0a..f7b747dc0dba1 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_note.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_note.ts @@ -120,14 +120,16 @@ export const epicPersistNote = ( ) ); -export const createTimelineNoteEpic = (): Epic => (action$) => - action$.pipe( - filter((action) => timelineNoteActionsType.includes(action.type)), - switchMap((action) => { - dispatcherTimelinePersistQueue.next({ action }); - return empty(); - }) - ); +export const createTimelineNoteEpic = + (): Epic => + (action$) => + action$.pipe( + filter((action) => timelineNoteActionsType.includes(action.type)), + switchMap((action) => { + dispatcherTimelinePersistQueue.next({ action }); + return empty(); + }) + ); const getNote = (noteId: string | undefined | null, notes: NotesById): string => { if (noteId != null) { diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_pinned_event.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_pinned_event.ts index c6c348615af27..7e9431a594a0e 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_pinned_event.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_pinned_event.ts @@ -124,11 +124,13 @@ export const epicPersistPinnedEvent = ( ) ); -export const createTimelinePinnedEventEpic = (): Epic => (action$) => - action$.pipe( - filter((action) => timelinePinnedEventActionsType.includes(action.type)), - mergeMap((action) => { - dispatcherTimelinePersistQueue.next({ action }); - return empty(); - }) - ); +export const createTimelinePinnedEventEpic = + (): Epic => + (action$) => + action$.pipe( + filter((action) => timelinePinnedEventActionsType.includes(action.type)), + mergeMap((action) => { + dispatcherTimelinePersistQueue.next({ action }); + return empty(); + }) + ); diff --git a/x-pack/plugins/security_solution/public/transforms/containers/use_create_transforms.ts b/x-pack/plugins/security_solution/public/transforms/containers/use_create_transforms.ts index ab38c4cca1860..789a35939cba8 100644 --- a/x-pack/plugins/security_solution/public/transforms/containers/use_create_transforms.ts +++ b/x-pack/plugins/security_solution/public/transforms/containers/use_create_transforms.ts @@ -27,7 +27,7 @@ export const useCreateTransforms = (): ReturnTransform => { const [, dispatchToaster] = useStateToaster(); const [transformSettings] = useUiSetting$( DEFAULT_TRANSFORMS, - (JSON.stringify(defaultTransformsSetting) as unknown) as TransformConfigSchema // TODO: The types are not 100% correct within uiSettings$, so I have to cast here. Once that is fixed, this cast can be removed + JSON.stringify(defaultTransformsSetting) as unknown as TransformConfigSchema // TODO: The types are not 100% correct within uiSettings$, so I have to cast here. Once that is fixed, this cast can be removed ); const [transforms, setTransforms] = useState>({ createTransforms: noop, diff --git a/x-pack/plugins/security_solution/public/transforms/containers/use_transforms.ts b/x-pack/plugins/security_solution/public/transforms/containers/use_transforms.ts index acb1ac6d1e77f..9a98fb0815a3e 100644 --- a/x-pack/plugins/security_solution/public/transforms/containers/use_transforms.ts +++ b/x-pack/plugins/security_solution/public/transforms/containers/use_transforms.ts @@ -43,7 +43,7 @@ export interface ReturnTransform { export const useTransforms = (): ReturnTransform => { const [transformSettings] = useUiSetting$( DEFAULT_TRANSFORMS, - (JSON.stringify(defaultTransformsSetting) as unknown) as TransformConfigSchema // TODO: The types are not 100% correct within uiSettings$, so I have to cast here. Once that is fixed, this cast can be removed + JSON.stringify(defaultTransformsSetting) as unknown as TransformConfigSchema // TODO: The types are not 100% correct within uiSettings$, so I have to cast here. Once that is fixed, this cast can be removed ); // TODO: Once we are past experimental phase this code should be removed const metricsEntitiesEnabled = useIsExperimentalFeatureEnabled('metricsEntitiesEnabled'); diff --git a/x-pack/plugins/security_solution/public/transforms/utils/adjust_timerange.test.ts b/x-pack/plugins/security_solution/public/transforms/utils/adjust_timerange.test.ts index 8dc4210494ea3..daf3ab5365bef 100644 --- a/x-pack/plugins/security_solution/public/transforms/utils/adjust_timerange.test.ts +++ b/x-pack/plugins/security_solution/public/transforms/utils/adjust_timerange.test.ts @@ -14,16 +14,20 @@ type ReturnTypeAdjustTimeRange = ReturnType; describe('adjust_timerange', () => { beforeEach(() => { // Adds extra switch to suppress deprecation warnings that moment does not expose in TypeScript - (moment as typeof moment & { - suppressDeprecationWarnings: boolean; - }).suppressDeprecationWarnings = true; + ( + moment as typeof moment & { + suppressDeprecationWarnings: boolean; + } + ).suppressDeprecationWarnings = true; }); afterEach(() => { // Adds extra switch to suppress deprecation warnings that moment does not expose in TypeScript - (moment as typeof moment & { - suppressDeprecationWarnings: boolean; - }).suppressDeprecationWarnings = false; + ( + moment as typeof moment & { + suppressDeprecationWarnings: boolean; + } + ).suppressDeprecationWarnings = false; }); test('it will adjust the time range from by rounding down by an hour within "from"', () => { diff --git a/x-pack/plugins/security_solution/public/transforms/utils/get_transform_changes_if_they_exist.test.ts b/x-pack/plugins/security_solution/public/transforms/utils/get_transform_changes_if_they_exist.test.ts index e5d290500499e..7a4e11526d83e 100644 --- a/x-pack/plugins/security_solution/public/transforms/utils/get_transform_changes_if_they_exist.test.ts +++ b/x-pack/plugins/security_solution/public/transforms/utils/get_transform_changes_if_they_exist.test.ts @@ -15,16 +15,20 @@ type ReturnTypeGetTransformChangesIfTheyExist = ReturnType { beforeEach(() => { // Adds extra switch to suppress deprecation warnings that moment does not expose in TypeScript - (moment as typeof moment & { - suppressDeprecationWarnings: boolean; - }).suppressDeprecationWarnings = true; + ( + moment as typeof moment & { + suppressDeprecationWarnings: boolean; + } + ).suppressDeprecationWarnings = true; }); afterEach(() => { // Adds extra switch to suppress deprecation warnings that moment does not expose in TypeScript - (moment as typeof moment & { - suppressDeprecationWarnings: boolean; - }).suppressDeprecationWarnings = false; + ( + moment as typeof moment & { + suppressDeprecationWarnings: boolean; + } + ).suppressDeprecationWarnings = false; }); describe('transformSettings enabled conditional logic', () => { diff --git a/x-pack/plugins/security_solution/public/ueba/pages/details/index.tsx b/x-pack/plugins/security_solution/public/ueba/pages/details/index.tsx index 5a297099f3834..b7a994ffa7ca8 100644 --- a/x-pack/plugins/security_solution/public/ueba/pages/details/index.tsx +++ b/x-pack/plugins/security_solution/public/ueba/pages/details/index.tsx @@ -63,9 +63,10 @@ const UebaDetailsComponent: React.FC = ({ detailName, uebaDeta const { globalFullScreen } = useGlobalFullScreen(); const kibana = useKibana(); - const uebaDetailsPageFilters: Filter[] = useMemo(() => getUebaDetailsPageFilters(detailName), [ - detailName, - ]); + const uebaDetailsPageFilters: Filter[] = useMemo( + () => getUebaDetailsPageFilters(detailName), + [detailName] + ); const getFilters = () => [...uebaDetailsPageFilters, ...filters]; const { docValueFields, indicesExist, indexPattern, selectedPatterns } = useSourcererScope( diff --git a/x-pack/plugins/security_solution/public/ueba/pages/navigation/host_rules_query_tab_body.tsx b/x-pack/plugins/security_solution/public/ueba/pages/navigation/host_rules_query_tab_body.tsx index bce19a9da7ab9..66e5ad257caea 100644 --- a/x-pack/plugins/security_solution/public/ueba/pages/navigation/host_rules_query_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/ueba/pages/navigation/host_rules_query_tab_body.tsx @@ -27,19 +27,17 @@ export const HostRulesQueryTabBody = ({ startDate, type, }: HostQueryProps) => { - const [ - loading, - { data, totalCount, pageInfo, loadPage, id, inspect, isInspected, refetch }, - ] = useHostRules({ - docValueFields, - endDate, - filterQuery, - hostName, - indexNames, - skip, - startDate, - type, - }); + const [loading, { data, totalCount, pageInfo, loadPage, id, inspect, isInspected, refetch }] = + useHostRules({ + docValueFields, + endDate, + filterQuery, + hostName, + indexNames, + skip, + startDate, + type, + }); return ( { - const [ - loading, - { data, totalCount, pageInfo, loadPage, id, inspect, isInspected, refetch }, - ] = useRiskScore({ docValueFields, endDate, filterQuery, indexNames, skip, startDate, type }); + const [loading, { data, totalCount, pageInfo, loadPage, id, inspect, isInspected, refetch }] = + useRiskScore({ docValueFields, endDate, filterQuery, indexNames, skip, startDate, type }); return ( { await ensureCreateEndpointEventFiltersList(kbn); await bluebird.map( - Array.from({ length: (flags.count as unknown) as number }), + Array.from({ length: flags.count as unknown as number }), () => kbn .request({ diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/migrate_artifacts_to_fleet.ts b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/migrate_artifacts_to_fleet.ts index 07edfce24affd..53c4d5bafe39c 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/migrate_artifacts_to_fleet.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/migrate_artifacts_to_fleet.ts @@ -39,14 +39,12 @@ export const migrateArtifactsToFleet = async ( try { while (hasMore) { // Retrieve list of artifact records - const { - saved_objects: artifactList, - total, - } = await soClient.find({ - type: ArtifactConstants.SAVED_OBJECT_TYPE, - page: 1, - perPage: 10, - }); + const { saved_objects: artifactList, total } = + await soClient.find({ + type: ArtifactConstants.SAVED_OBJECT_TYPE, + page: 1, + perPage: 10, + }); if (totalArtifactsMigrated === -1) { totalArtifactsMigrated = total; diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts b/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts index 176318546b209..a1b93b5bd59f7 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts @@ -113,12 +113,10 @@ describe('Policy-Changing license watcher', () => { // mock a Policy with a higher-tiered feature enabled packagePolicySvcMock.list.mockResolvedValueOnce({ items: [ - MockPPWithEndpointPolicy( - (pc: PolicyConfig): PolicyConfig => { - pc.windows.popup.malware.message = CustomMessage; - return pc; - } - ), + MockPPWithEndpointPolicy((pc: PolicyConfig): PolicyConfig => { + pc.windows.popup.malware.message = CustomMessage; + return pc; + }), ], total: 1, page: 1, diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.ts b/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.ts index 63c5c85bd1e93..8303291b5bcd4 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.ts @@ -52,14 +52,14 @@ export class PolicyWatcher { * client to do */ private makeInternalSOClient(soStart: SavedObjectsServiceStart): SavedObjectsClientContract { - const fakeRequest = ({ + const fakeRequest = { headers: {}, getBasePath: () => '', path: '/', route: { settings: {} }, url: { href: {} }, raw: { req: { url: '/' } }, - } as unknown) as KibanaRequest; + } as unknown as KibanaRequest; return soStart.getScopedClient(fakeRequest, { excludedWrappers: ['security'] }); } diff --git a/x-pack/plugins/security_solution/server/endpoint/mocks.ts b/x-pack/plugins/security_solution/server/endpoint/mocks.ts index d442e4c8a7e5a..0b4060a7e024f 100644 --- a/x-pack/plugins/security_solution/server/endpoint/mocks.ts +++ b/x-pack/plugins/security_solution/server/endpoint/mocks.ts @@ -59,57 +59,58 @@ export const createMockEndpointAppContext = ( export const createMockEndpointAppContextService = ( mockManifestManager?: ManifestManager ): jest.Mocked => { - return ({ + return { start: jest.fn(), stop: jest.fn(), getExperimentalFeatures: jest.fn(), getAgentService: jest.fn(), getAgentPolicyService: jest.fn(), getManifestManager: jest.fn().mockReturnValue(mockManifestManager ?? jest.fn()), - } as unknown) as jest.Mocked; + } as unknown as jest.Mocked; }; /** * Creates a mocked input contract for the `EndpointAppContextService#start()` method */ -export const createMockEndpointAppContextServiceStartContract = (): jest.Mocked => { - const factory = new AppClientFactory(); - const config = createMockConfig(); - const casesClientMock = createCasesClientMock(); - const savedObjectsStart = savedObjectsServiceMock.createStartContract(); - const agentService = createMockAgentService(); - const agentPolicyService = createMockAgentPolicyService(); - const endpointMetadataService = new EndpointMetadataService( - savedObjectsStart, - agentService, - agentPolicyService - ); +export const createMockEndpointAppContextServiceStartContract = + (): jest.Mocked => { + const factory = new AppClientFactory(); + const config = createMockConfig(); + const casesClientMock = createCasesClientMock(); + const savedObjectsStart = savedObjectsServiceMock.createStartContract(); + const agentService = createMockAgentService(); + const agentPolicyService = createMockAgentPolicyService(); + const endpointMetadataService = new EndpointMetadataService( + savedObjectsStart, + agentService, + agentPolicyService + ); - factory.setup({ getSpaceId: () => 'mockSpace', config }); + factory.setup({ getSpaceId: () => 'mockSpace', config }); - return { - agentService, - agentPolicyService, - endpointMetadataService, - packageService: createMockPackageService(), - logger: loggingSystemMock.create().get('mock_endpoint_app_context'), - manifestManager: getManifestManagerMock(), - appClientFactory: factory, - security: securityMock.createStart(), - alerting: alertsMock.createStart(), - config, - licenseService: new LicenseService(), - registerIngestCallback: jest.fn< - ReturnType, - Parameters - >(), - exceptionListsClient: listMock.getExceptionListClient(), - packagePolicyService: createPackagePolicyServiceMock(), - cases: { - getCasesClientWithRequest: jest.fn(async () => casesClientMock), - }, + return { + agentService, + agentPolicyService, + endpointMetadataService, + packageService: createMockPackageService(), + logger: loggingSystemMock.create().get('mock_endpoint_app_context'), + manifestManager: getManifestManagerMock(), + appClientFactory: factory, + security: securityMock.createStart(), + alerting: alertsMock.createStart(), + config, + licenseService: new LicenseService(), + registerIngestCallback: jest.fn< + ReturnType, + Parameters + >(), + exceptionListsClient: listMock.getExceptionListClient(), + packagePolicyService: createPackagePolicyServiceMock(), + cases: { + getCasesClientWithRequest: jest.fn(async () => casesClientMock), + }, + }; }; -}; /** * Create mock PackageService @@ -147,7 +148,8 @@ export const createMockMetadataRequestContext = (): jest.Mocked, + requestHandlerContext: + xpackMocks.createRequestHandlerContext() as unknown as jest.Mocked, }; }; @@ -155,7 +157,8 @@ export function createRouteHandlerContext( dataClient: jest.Mocked, savedObjectsClient: jest.Mocked ) { - const context = (xpackMocks.createRequestHandlerContext() as unknown) as jest.Mocked; + const context = + xpackMocks.createRequestHandlerContext() as unknown as jest.Mocked; context.core.elasticsearch.client = dataClient; context.core.savedObjects.client = savedObjectsClient; return context; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.test.ts index 2bfa5ae1aa3c4..0e25bb3561788 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.test.ts @@ -198,7 +198,7 @@ describe('Host Isolation', () => { RequestHandler ] = routerMock.post.mock.calls.find(([{ path }]) => path.startsWith(routePrefix))!; await routeHandler(ctx, mockRequest, mockResponse); - return (ctx as unknown) as jest.Mocked; + return ctx as unknown as jest.Mocked; }; }); @@ -239,8 +239,9 @@ describe('Host Isolation', () => { body: { endpoint_ids: ['ABC-XYZ-000'] }, searchResponse: metadataResponse, }); - const actionDoc: EndpointAction = (ctx.core.elasticsearch.client.asCurrentUser - .index as jest.Mock).mock.calls[0][0].body; + const actionDoc: EndpointAction = ( + ctx.core.elasticsearch.client.asCurrentUser.index as jest.Mock + ).mock.calls[0][0].body; expect(actionDoc.agents).toContain(AgentID); }); it('records the user who performed the action to the action record', async () => { @@ -249,8 +250,9 @@ describe('Host Isolation', () => { body: { endpoint_ids: ['XYZ'] }, mockUser: testU, }); - const actionDoc: EndpointAction = (ctx.core.elasticsearch.client.asCurrentUser - .index as jest.Mock).mock.calls[0][0].body; + const actionDoc: EndpointAction = ( + ctx.core.elasticsearch.client.asCurrentUser.index as jest.Mock + ).mock.calls[0][0].body; expect(actionDoc.user_id).toEqual(testU.username); }); it('records the comment in the action payload', async () => { @@ -258,16 +260,18 @@ describe('Host Isolation', () => { const ctx = await callRoute(ISOLATE_HOST_ROUTE, { body: { endpoint_ids: ['XYZ'], comment: CommentText }, }); - const actionDoc: EndpointAction = (ctx.core.elasticsearch.client.asCurrentUser - .index as jest.Mock).mock.calls[0][0].body; + const actionDoc: EndpointAction = ( + ctx.core.elasticsearch.client.asCurrentUser.index as jest.Mock + ).mock.calls[0][0].body; expect(actionDoc.data.comment).toEqual(CommentText); }); it('creates an action and returns its ID', async () => { const ctx = await callRoute(ISOLATE_HOST_ROUTE, { body: { endpoint_ids: ['XYZ'], comment: 'XYZ' }, }); - const actionDoc: EndpointAction = (ctx.core.elasticsearch.client.asCurrentUser - .index as jest.Mock).mock.calls[0][0].body; + const actionDoc: EndpointAction = ( + ctx.core.elasticsearch.client.asCurrentUser.index as jest.Mock + ).mock.calls[0][0].body; const actionID = actionDoc.action_id; expect(mockResponse.ok).toBeCalled(); expect((mockResponse.ok.mock.calls[0][0]?.body as HostIsolationResponse).action).toEqual( @@ -278,8 +282,9 @@ describe('Host Isolation', () => { const ctx = await callRoute(ISOLATE_HOST_ROUTE, { body: { endpoint_ids: ['XYZ'] }, }); - const actionDoc: EndpointAction = (ctx.core.elasticsearch.client.asCurrentUser - .index as jest.Mock).mock.calls[0][0].body; + const actionDoc: EndpointAction = ( + ctx.core.elasticsearch.client.asCurrentUser.index as jest.Mock + ).mock.calls[0][0].body; expect(actionDoc.timeout).toEqual(300); }); @@ -295,8 +300,9 @@ describe('Host Isolation', () => { body: { endpoint_ids: ['XYZ'] }, searchResponse: doc, }); - const actionDoc: EndpointAction = (ctx.core.elasticsearch.client.asCurrentUser - .index as jest.Mock).mock.calls[0][0].body; + const actionDoc: EndpointAction = ( + ctx.core.elasticsearch.client.asCurrentUser.index as jest.Mock + ).mock.calls[0][0].body; expect(actionDoc.agents).toContain(AgentID); }); @@ -304,16 +310,18 @@ describe('Host Isolation', () => { const ctx = await callRoute(ISOLATE_HOST_ROUTE, { body: { endpoint_ids: ['XYZ'] }, }); - const actionDoc: EndpointAction = (ctx.core.elasticsearch.client.asCurrentUser - .index as jest.Mock).mock.calls[0][0].body; + const actionDoc: EndpointAction = ( + ctx.core.elasticsearch.client.asCurrentUser.index as jest.Mock + ).mock.calls[0][0].body; expect(actionDoc.data.command).toEqual('isolate'); }); it('sends the unisolate command payload from the unisolate route', async () => { const ctx = await callRoute(UNISOLATE_HOST_ROUTE, { body: { endpoint_ids: ['XYZ'] }, }); - const actionDoc: EndpointAction = (ctx.core.elasticsearch.client.asCurrentUser - .index as jest.Mock).mock.calls[0][0].body; + const actionDoc: EndpointAction = ( + ctx.core.elasticsearch.client.asCurrentUser.index as jest.Mock + ).mock.calls[0][0].body; expect(actionDoc.data.command).toEqual('unisolate'); }); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts index 9e5af2ed2d2bc..bb1917ba12323 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts @@ -83,16 +83,16 @@ describe('test endpoint route', () => { mockResponse = httpServerMock.createResponseFactory(); startContract = createMockEndpointAppContextServiceStartContract(); - (startContract.packagePolicyService as jest.Mocked).list.mockImplementation( - () => { - return Promise.resolve({ - items: [], - total: 0, - page: 1, - perPage: 1000, - }); - } - ); + ( + startContract.packagePolicyService as jest.Mocked + ).list.mockImplementation(() => { + return Promise.resolve({ + items: [], + total: 0, + page: 1, + perPage: 1000, + }); + }); }); describe('with new transform package', () => { @@ -319,9 +319,9 @@ describe('test endpoint route', () => { ); mockAgentService.getAgentStatusById = jest.fn().mockReturnValue('error'); - mockAgentService.getAgent = jest.fn().mockReturnValue(({ + mockAgentService.getAgent = jest.fn().mockReturnValue({ active: true, - } as unknown) as Agent); + } as unknown as Agent); [routeConfig, routeHandler] = routerMock.get.mock.calls.find(([{ path }]) => path.startsWith(`${HOST_METADATA_LIST_ROUTE}`) @@ -456,9 +456,9 @@ describe('test endpoint route', () => { (mockScopedClient.asCurrentUser.search as jest.Mock).mockImplementationOnce(() => Promise.resolve({ body: response }) ); - mockAgentService.getAgent = jest.fn().mockReturnValue(({ + mockAgentService.getAgent = jest.fn().mockReturnValue({ active: false, - } as unknown) as Agent); + } as unknown as Agent); [routeConfig, routeHandler] = routerMock.get.mock.calls.find(([{ path }]) => path.startsWith(`${HOST_METADATA_LIST_ROUTE}`) diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/agent_status.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/agent_status.test.ts index ffe194ed19789..32893f3bfbc34 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/agent_status.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/agent_status.test.ts @@ -49,7 +49,7 @@ describe('test filtering endpoint hosts by agent status', () => { mockAgentService.listAgents .mockImplementationOnce(() => Promise.resolve({ - agents: [({ id: 'id1' } as unknown) as Agent, ({ id: 'id2' } as unknown) as Agent], + agents: [{ id: 'id1' } as unknown as Agent, { id: 'id2' } as unknown as Agent], total: 2, page: 1, perPage: 2, @@ -82,7 +82,7 @@ describe('test filtering endpoint hosts by agent status', () => { mockAgentService.listAgents .mockImplementationOnce(() => Promise.resolve({ - agents: [({ id: 'A' } as unknown) as Agent, ({ id: 'B' } as unknown) as Agent], + agents: [{ id: 'A' } as unknown as Agent, { id: 'B' } as unknown as Agent], total: 2, page: 1, perPage: 2, diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/query_strategies.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/query_strategies.ts index 2d7bff4a53f3f..ae9d0780de337 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/query_strategies.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/query_strategies.ts @@ -38,7 +38,7 @@ export const queryResponseToHostListResult = ( return { resultLength: - ((response.hits?.total as unknown) as { value: number; relation: string }).value || 0, + (response.hits?.total as unknown as { value: number; relation: string }).value || 0, resultList: list, }; }; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/test_support.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/test_support.ts index 7dae6fe5c69e4..307fbb7cbf7a4 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/test_support.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/test_support.ts @@ -11,7 +11,7 @@ import { HostMetadata } from '../../../../../common/endpoint/types'; export function createV2SearchResponse( hostMetadata?: HostMetadata ): estypes.SearchResponse { - return ({ + return { took: 15, timed_out: false, _shards: { @@ -40,5 +40,5 @@ export function createV2SearchResponse( ] : [], }, - } as unknown) as estypes.SearchResponse; + } as unknown as estypes.SearchResponse; } diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/unenroll.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/unenroll.test.ts index 8efbc1940ea7d..a9b39fb16d38b 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/unenroll.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/unenroll.test.ts @@ -36,10 +36,10 @@ describe('test find all unenrolled Agent id', () => { mockPackagePolicyService.list .mockResolvedValueOnce({ items: [ - ({ + { id: '1', policy_id: 'abc123', - } as unknown) as PackagePolicy, + } as unknown as PackagePolicy, ], total: 1, perPage: 10, @@ -55,9 +55,9 @@ describe('test find all unenrolled Agent id', () => { .mockImplementationOnce(() => Promise.resolve({ agents: [ - ({ + { id: 'id1', - } as unknown) as Agent, + } as unknown as Agent, ], total: 2, page: 1, @@ -67,9 +67,9 @@ describe('test find all unenrolled Agent id', () => { .mockImplementationOnce(() => Promise.resolve({ agents: [ - ({ + { id: 'id2', - } as unknown) as Agent, + } as unknown as Agent, ], total: 2, page: 1, diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/policy/handlers.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/policy/handlers.test.ts index 2b5fe11a8f687..90cda7ceb05d4 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/policy/handlers.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/policy/handlers.test.ts @@ -129,7 +129,7 @@ describe('test policy response handler', () => { agentListResult = { agents: [ - ({ + { local_metadata: { elastic: { agent: { @@ -137,8 +137,8 @@ describe('test policy response handler', () => { }, }, }, - } as unknown) as Agent, - ({ + } as unknown as Agent, + { local_metadata: { elastic: { agent: { @@ -146,8 +146,8 @@ describe('test policy response handler', () => { }, }, }, - } as unknown) as Agent, - ({ + } as unknown as Agent, + { local_metadata: { elastic: { agent: { @@ -155,7 +155,7 @@ describe('test policy response handler', () => { }, }, }, - } as unknown) as Agent, + } as unknown as Agent, ], total: 2, page: 1, @@ -240,7 +240,7 @@ describe('test policy response handler', () => { function createSearchResponse( hostPolicyResponse?: HostPolicyResponse ): estypes.SearchResponse { - return ({ + return { took: 15, timed_out: false, _shards: { @@ -267,5 +267,5 @@ function createSearchResponse( ] : [], }, - } as unknown) as estypes.SearchResponse; + } as unknown as estypes.SearchResponse; } diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.test.ts index 156bcd0de2cc9..81f1d45471594 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/handlers.test.ts @@ -95,7 +95,8 @@ const TRUSTED_APP: TrustedApp = { ], }; -const packagePolicyClient = createPackagePolicyServiceMock() as jest.Mocked; +const packagePolicyClient = + createPackagePolicyServiceMock() as jest.Mocked; describe('handlers', () => { beforeEach(() => { @@ -115,29 +116,28 @@ describe('handlers', () => { // Ensure that `logFactory.get()` always returns the same instance for the same given prefix const instances = new Map>(); const logFactoryGetMock = context.logFactory.get.getMockImplementation(); - context.logFactory.get.mockImplementation( - (prefix): Logger => { - if (!instances.has(prefix)) { - instances.set(prefix, logFactoryGetMock!(prefix)!); - } - return instances.get(prefix)!; + context.logFactory.get.mockImplementation((prefix): Logger => { + if (!instances.has(prefix)) { + instances.set(prefix, logFactoryGetMock!(prefix)!); } - ); + return instances.get(prefix)!; + }); return context; }; let appContextMock: ReturnType = createAppContextMock(); - let exceptionsListClient: jest.Mocked = listMock.getExceptionListClient() as jest.Mocked; + let exceptionsListClient: jest.Mocked = + listMock.getExceptionListClient() as jest.Mocked; const createHandlerContextMock = () => - (({ + ({ ...xpackMocks.createRequestHandlerContext(), lists: { getListClient: jest.fn(), getExceptionListClient: jest.fn().mockReturnValue(exceptionsListClient), }, - } as unknown) as jest.Mocked); + } as unknown as jest.Mocked); const assertResponse = ( response: jest.Mocked, diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/service.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/service.test.ts index 3323080851801..fd7baf80983a4 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/service.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/service.test.ts @@ -38,8 +38,10 @@ import { ENDPOINT_TRUSTED_APPS_LIST_ID } from '@kbn/securitysolution-list-consta import { getPackagePoliciesResponse, getTrustedAppByPolicy } from './mocks'; const exceptionsListClient = listMock.getExceptionListClient() as jest.Mocked; -const packagePolicyClient = createPackagePolicyServiceMock() as jest.Mocked; -const savedObjectClient = savedObjectsClientMock.create() as jest.Mocked; +const packagePolicyClient = + createPackagePolicyServiceMock() as jest.Mocked; +const savedObjectClient = + savedObjectsClientMock.create() as jest.Mocked; const EXCEPTION_LIST_ITEM: ExceptionListItemSchema = { _version: 'abc123', diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/test_utils.ts b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/test_utils.ts index 2aee9f22887a9..b2def0f612d15 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/test_utils.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/test_utils.ts @@ -7,27 +7,26 @@ import { ExceptionListClient } from '../../../../../lists/server'; -export const updateExceptionListItemImplementationMock: ExceptionListClient['updateExceptionListItem'] = async ( - listItem -) => { - return { - _version: listItem._version || 'abc123', - id: listItem.id || '123', - comments: [], - created_at: '11/11/2011T11:11:11.111', - created_by: 'admin', - description: listItem.description || '', - entries: listItem.entries || [], - item_id: listItem.itemId || '', - list_id: 'endpoint_trusted_apps', - meta: undefined, - name: listItem.name || '', - namespace_type: listItem.namespaceType || '', - os_types: listItem.osTypes || '', - tags: listItem.tags || [], - type: 'simple', - tie_breaker_id: '123', - updated_at: '11/11/2011T11:11:11.111', - updated_by: 'admin', +export const updateExceptionListItemImplementationMock: ExceptionListClient['updateExceptionListItem'] = + async (listItem) => { + return { + _version: listItem._version || 'abc123', + id: listItem.id || '123', + comments: [], + created_at: '11/11/2011T11:11:11.111', + created_by: 'admin', + description: listItem.description || '', + entries: listItem.entries || [], + item_id: listItem.itemId || '', + list_id: 'endpoint_trusted_apps', + meta: undefined, + name: listItem.name || '', + namespace_type: listItem.namespaceType || '', + os_types: listItem.osTypes || '', + tags: listItem.tags || [], + type: 'simple', + tie_breaker_id: '123', + updated_at: '11/11/2011T11:11:11.111', + updated_by: 'admin', + }; }; -}; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_client.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_client.ts index d7b05ffa5592e..85db51087b609 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_client.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_client.ts @@ -32,10 +32,7 @@ export class ManifestClient { ) { this.savedObjectsClient = savedObjectsClient; - const [validated, errors] = validate( - (schemaVersion as unknown) as object, - manifestSchemaVersion - ); + const [validated, errors] = validate(schemaVersion as unknown as object, manifestSchemaVersion); if (errors != null || validated === null) { throw new Error(`Invalid manifest version: ${schemaVersion}`); diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts index 340137a530e6d..89a704ecf1b1d 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts @@ -183,11 +183,11 @@ describe('ManifestManager', () => { } }); - (manifestManagerContext.artifactClient as jest.Mocked).getArtifact.mockImplementation( - async (id) => { - return ARTIFACTS_BY_ID[id]; - } - ); + ( + manifestManagerContext.artifactClient as jest.Mocked + ).getArtifact.mockImplementation(async (id) => { + return ARTIFACTS_BY_ID[id]; + }); const manifest = await manifestManager.getLastComputedManifest(); @@ -244,12 +244,12 @@ describe('ManifestManager', () => { } }); - (manifestManagerContext.artifactClient as jest.Mocked).getArtifact.mockImplementation( - async (id) => { - // report the MACOS Exceptions artifact as not found - return id === ARTIFACT_ID_EXCEPTIONS_MACOS ? undefined : ARTIFACTS_BY_ID[id]; - } - ); + ( + manifestManagerContext.artifactClient as jest.Mocked + ).getArtifact.mockImplementation(async (id) => { + // report the MACOS Exceptions artifact as not found + return id === ARTIFACT_ID_EXCEPTIONS_MACOS ? undefined : ARTIFACTS_BY_ID[id]; + }); const manifest = await manifestManager.getLastComputedManifest(); diff --git a/x-pack/plugins/security_solution/server/endpoint/utils/create_internal_readonly_so_client.ts b/x-pack/plugins/security_solution/server/endpoint/utils/create_internal_readonly_so_client.ts index b925146ff3b6c..17b5bf16f7b96 100644 --- a/x-pack/plugins/security_solution/server/endpoint/utils/create_internal_readonly_so_client.ts +++ b/x-pack/plugins/security_solution/server/endpoint/utils/create_internal_readonly_so_client.ts @@ -32,14 +32,14 @@ export class InternalReadonlySoClientMethodNotAllowedError extends EndpointError export const createInternalReadonlySoClient = ( savedObjectsServiceStart: SavedObjectsServiceStart ): SavedObjectsClientContract => { - const fakeRequest = ({ + const fakeRequest = { headers: {}, getBasePath: () => '', path: '/', route: { settings: {} }, url: { href: {} }, raw: { req: { url: '/' } }, - } as unknown) as KibanaRequest; + } as unknown as KibanaRequest; const internalSoClient = savedObjectsServiceStart.getScopedClient(fakeRequest, { excludedWrappers: ['security'], diff --git a/x-pack/plugins/security_solution/server/index.ts b/x-pack/plugins/security_solution/server/index.ts index 7e3da726f6ebe..b72a21c0da643 100644 --- a/x-pack/plugins/security_solution/server/index.ts +++ b/x-pack/plugins/security_solution/server/index.ts @@ -20,7 +20,8 @@ export const config: PluginConfigDescriptor = { enableExperimental: true, }, schema: configSchema, - deprecations: ({ renameFromRoot }) => [ + deprecations: ({ deprecate, renameFromRoot }) => [ + deprecate('enabled', '8.0.0'), renameFromRoot('xpack.siem.enabled', 'xpack.securitySolution.enabled'), renameFromRoot( 'xpack.siem.maxRuleImportExportSize', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts index 3c069ec048688..b47a9fc3a5d60 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts @@ -48,7 +48,7 @@ const createRequestContextMock = ( clients: ReturnType = createMockClients() ): SecuritySolutionRequestHandlerContextMock => { const coreContext = coreMock.createRequestHandlerContext(); - return ({ + return { alerting: { getRulesClient: jest.fn(() => clients.rulesClient) }, core: { ...coreContext, @@ -64,7 +64,7 @@ const createRequestContextMock = ( getExecutionLogClient: jest.fn(() => clients.ruleExecutionLogClient), getSpaceId: jest.fn(() => 'default'), }, - } as unknown) as SecuritySolutionRequestHandlerContextMock; + } as unknown as SecuritySolutionRequestHandlerContextMock; }; const createTools = () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts index 97d976d337564..5d0f0c246f6b0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -448,12 +448,13 @@ export const getMockPrivilegesResult = () => ({ application: {}, }); -export const getEmptySavedObjectsResponse = (): SavedObjectsFindResponse => ({ - page: 1, - per_page: 1, - total: 0, - saved_objects: [], -}); +export const getEmptySavedObjectsResponse = + (): SavedObjectsFindResponse => ({ + page: 1, + per_page: 1, + total: 0, + saved_objects: [], + }); export const getRuleExecutionStatuses = (): Array< SavedObjectsFindResult diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts index 866c70626d2bc..48efcc4495aff 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts @@ -86,12 +86,12 @@ describe.each([ beforeEach(() => { server = serverMock.create(); ({ clients, context } = requestContextMock.createTools()); - securitySetup = ({ + securitySetup = { authc: { getCurrentUser: jest.fn().mockReturnValue(mockGetCurrentUser), }, authz: {}, - } as unknown) as SecurityPluginSetup; + } as unknown as SecurityPluginSetup; mockExceptionsClient = listMock.getExceptionListClient(); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.test.ts index 1b171f693d80b..78572863f7472 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.test.ts @@ -70,12 +70,12 @@ describe.each([ server = serverMock.create(); ({ clients, context } = requestContextMock.createTools()); - securitySetup = ({ + securitySetup = { authc: { getCurrentUser: jest.fn().mockReturnValue(mockGetCurrentUser), }, authz: {}, - } as unknown) as SecurityPluginSetup; + } as unknown as SecurityPluginSetup; clients.rulesClient.find.mockResolvedValue(getEmptyFindResult()); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/read_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/read_rules_route.ts index 5672648190653..6b643bde22b17 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/read_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/read_rules_route.ts @@ -68,8 +68,10 @@ export const readRulesRoute = ( const [currentStatus] = ruleStatuses; if (currentStatus != null && rule.executionStatus.status === 'error') { currentStatus.attributes.lastFailureMessage = `Reason: ${rule.executionStatus.error?.reason} Message: ${rule.executionStatus.error?.message}`; - currentStatus.attributes.lastFailureAt = rule.executionStatus.lastExecutionDate.toISOString(); - currentStatus.attributes.statusDate = rule.executionStatus.lastExecutionDate.toISOString(); + currentStatus.attributes.lastFailureAt = + rule.executionStatus.lastExecutionDate.toISOString(); + currentStatus.attributes.statusDate = + rule.executionStatus.lastExecutionDate.toISOString(); currentStatus.attributes.status = RuleExecutionStatus.failed; } const transformed = transform(rule, currentStatus, isRuleRegistryEnabled); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.test.ts index f07a63b690a2d..8001b7d6bf4d4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.test.ts @@ -294,7 +294,7 @@ describe.each([ }); test('returns 500 if the data is not of type siem alert', () => { - const unsafeCast = ({ data: [{ random: 1 }] } as unknown) as PartialAlert; + const unsafeCast = { data: [{ random: 1 }] } as unknown as PartialAlert; const output = transform(unsafeCast, undefined, isRuleRegistryEnabled); expect(output).toBeNull(); }); @@ -419,7 +419,7 @@ describe.each([ }); test('returns 500 if the data is not of type siem alert', () => { - const unsafeCast = ({ name: 'something else' } as unknown) as PartialAlert; + const unsafeCast = { name: 'something else' } as unknown as PartialAlert; const output = transformOrBulkError( 'rule-1', unsafeCast, @@ -506,7 +506,7 @@ describe.each([ }); test('returns 1 error and success of false if the data is not of type siem alert', () => { - const unsafeCast = ({ name: 'something else' } as unknown) as PartialAlert; + const unsafeCast = { name: 'something else' } as unknown as PartialAlert; const output = transformOrImportError( 'rule-1', unsafeCast, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/create_signals_migration_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/create_signals_migration_route.test.ts index f10907f7c0c02..b02d93ef63d1d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/create_signals_migration_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/create_signals_migration_route.test.ts @@ -43,11 +43,11 @@ describe('creating signals migrations route', () => { (getIndexVersionsByIndex as jest.Mock).mockResolvedValue({ 'my-signals-index': -1 }); (getSignalVersionsByIndex as jest.Mock).mockResolvedValue({ 'my-signals-index': [] }); - const securityMock = ({ + const securityMock = { authc: { getCurrentUser: jest.fn().mockReturnValue({ user: { username: 'my-username' } }), }, - } as unknown) as SetupPlugins['security']; + } as unknown as SetupPlugins['security']; createSignalsMigrationRoute(server.router, securityMock); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/finalize_signals_migration_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/finalize_signals_migration_route.test.ts index 5f7749741bee3..9a53831507e81 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/finalize_signals_migration_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/finalize_signals_migration_route.test.ts @@ -20,11 +20,11 @@ describe('finalizing signals migrations', () => { beforeEach(() => { server = serverMock.create(); - const securityMock = ({ + const securityMock = { authc: { getCurrentUser: jest.fn().mockReturnValue({ user: { username: 'my-username' } }), }, - } as unknown) as SetupPlugins['security']; + } as unknown as SetupPlugins['security']; finalizeSignalsMigrationRoute(server.router, securityMock); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.ts index 775115ded7c4f..b03050da59f49 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.ts @@ -200,16 +200,15 @@ interface Schema { validate: (input: any) => { value: any; error?: Error }; } -export const buildRouteValidation = (schema: Schema): RouteValidationFunction => ( - payload: T, - { ok, badRequest } -) => { - const { value, error } = schema.validate(payload); - if (error) { - return badRequest(error.message); - } - return ok(value); -}; +export const buildRouteValidation = + (schema: Schema): RouteValidationFunction => + (payload: T, { ok, badRequest }) => { + const { value, error } = schema.validate(payload); + if (error) { + return badRequest(error.message); + } + return ok(value); + }; const statusToErrorMessage = (statusCode: number) => { switch (statusCode) { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/migrations.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/migrations.ts index 3004304445ff7..2853ca92e2a58 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/migrations.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/migrations.ts @@ -96,25 +96,17 @@ export const ruleActionsSavedObjectMigration = { }, ] as RuleAlertAction[]; } else if (action.action_type_id === '.jira') { - const { - title, - comments, - description, - issueType, - priority, - labels, - parent, - summary, - } = action.params.subActionParams as { - title: string; - description: string; - issueType: string; - priority?: string; - labels?: string[]; - parent?: string; - comments?: unknown[]; - summary?: string; - }; + const { title, comments, description, issueType, priority, labels, parent, summary } = + action.params.subActionParams as { + title: string; + description: string; + issueType: string; + priority?: string; + labels?: string[]; + parent?: string; + comments?: unknown[]; + summary?: string; + }; return [ ...acc, { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_registry_adapter/rule_registry_log_client/rule_registry_log_client.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_registry_adapter/rule_registry_log_client/rule_registry_log_client.ts index a5515f8db8552..3cd6171b5bbeb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_registry_adapter/rule_registry_log_client/rule_registry_log_client.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_registry_adapter/rule_registry_log_client/rule_registry_log_client.ts @@ -56,9 +56,7 @@ interface FindExecutionLogArgs { } interface IRuleRegistryLogClient { - find: ( - args: FindExecutionLogArgs - ) => Promise<{ + find: (args: FindExecutionLogArgs) => Promise<{ [ruleId: string]: IRuleStatusSOAttributes[] | undefined; }>; create: (event: RuleExecutionEvent) => Promise; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/eql.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/eql.ts index 76389d7376fc8..096d2a5330047 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/eql.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/eql.ts @@ -8,7 +8,7 @@ import { EqlSearchStrategyResponse } from '../../../../../../../../src/plugins/data/common'; import { EqlSearchResponse } from '../../../../../common/detection_engine/types'; -export const sequenceResponse = ({ +export const sequenceResponse = { rawResponse: { body: { is_partial: false, @@ -788,4 +788,4 @@ export const sequenceResponse = ({ meta: {}, hits: {}, }, -} as unknown) as EqlSearchStrategyResponse>; +} as unknown as EqlSearchStrategyResponse>; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule_type.ts index 1b867507905a7..bcab8a0af5ffb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule_type.ts @@ -33,12 +33,12 @@ export const createRuleTypeMocks = ( const mockedConfig$ = of({} as ConfigType); - const loggerMock = ({ + const loggerMock = { debug: jest.fn(), info: jest.fn(), warn: jest.fn(), error: jest.fn(), - } as unknown) as Logger; + } as unknown as Logger; const alerting = { registerType: ({ executor }) => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_factory.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_factory.ts index 2d3a4a5afa5c2..df15d4b2c0112 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_factory.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_factory.ts @@ -39,296 +39,327 @@ import { RuleExecutionStatus } from '../../../../common/detection_engine/schemas import { scheduleThrottledNotificationActions } from '../notifications/schedule_throttle_notification_actions'; /* eslint-disable complexity */ -export const createSecurityRuleTypeFactory: CreateSecurityRuleTypeFactory = ({ - lists, - logger, - mergeStrategy, - ignoreFields, - ruleDataClient, - ruleDataService, -}) => (type) => { - const persistenceRuleType = createPersistenceRuleTypeFactory({ ruleDataClient, logger }); - return persistenceRuleType({ - ...type, - async executor(options) { - const { - alertId, - params, - previousStartedAt, - startedAt, - services, - spaceId, - state, - updatedBy: updatedByUser, - } = options; - let runState = state; - const { from, maxSignals, meta, ruleId, timestampOverride, to } = params; - const { alertWithPersistence, savedObjectsClient, scopedClusterClient } = services; - const searchAfterSize = Math.min(maxSignals, DEFAULT_SEARCH_AFTER_PAGE_SIZE); - - const esClient = scopedClusterClient.asCurrentUser; - - const ruleStatusClient = new RuleExecutionLogClient({ savedObjectsClient, ruleDataService }); - const ruleSO = await savedObjectsClient.get('alert', alertId); - - const { - actions, - name, - schedule: { interval }, - } = ruleSO.attributes; - const refresh = actions.length ? 'wait_for' : false; - - const buildRuleMessage = buildRuleMessageFactory({ - id: alertId, - ruleId, - name, - index: ruleDataClient.indexName, - }); - - logger.debug(buildRuleMessage('[+] Starting Signal Rule execution')); - logger.debug(buildRuleMessage(`interval: ${interval}`)); - - let wroteWarningStatus = false; - await ruleStatusClient.logStatusChange({ - spaceId, - ruleId: alertId, - newStatus: RuleExecutionStatus['going to run'], - }); - - let result = createResultObject(state); - - // check if rule has permissions to access given index pattern - // move this collection of lines into a function in utils - // so that we can use it in create rules route, bulk, etc. - try { - if (!isMachineLearningParams(params)) { - const index = params.index; - const hasTimestampOverride = !!timestampOverride; - - const inputIndices = params.index ?? []; - - const [privileges, timestampFieldCaps] = await Promise.all([ - checkPrivilegesFromEsClient(esClient, inputIndices), - esClient.fieldCaps({ - index: index ?? ['*'], - fields: hasTimestampOverride ? [TIMESTAMP, timestampOverride as string] : [TIMESTAMP], - include_unmapped: true, - }), - ]); - - fold, void>( - async (error: Error) => logger.error(buildRuleMessage(error.message)), - async (status: Promise) => (wroteWarningStatus = await status) - )( - flow( - () => - tryCatch( - () => - hasReadIndexPrivileges({ - spaceId, - ruleId: alertId, - privileges, - logger, - buildRuleMessage, - ruleStatusClient, - }), - toError - ), - chain((wroteStatus: unknown) => - tryCatch( - () => - hasTimestampFields({ - spaceId, - ruleId: alertId, - wroteStatus: wroteStatus as boolean, - timestampField: hasTimestampOverride - ? (timestampOverride as string) - : '@timestamp', - ruleName: name, - timestampFieldCapsResponse: timestampFieldCaps, - inputIndices, - ruleStatusClient, - logger, - buildRuleMessage, - }), - toError - ) - ) - )() as Either> - ); - } - } catch (exc) { - logger.error(buildRuleMessage(`Check privileges failed to execute ${exc}`)); - } - let hasError = false; - const { tuples, remainingGap } = getRuleRangeTuples({ - logger, - previousStartedAt, - from: from as string, - to: to as string, - interval, - maxSignals: DEFAULT_MAX_SIGNALS, - buildRuleMessage, - }); - if (remainingGap.asMilliseconds() > 0) { - const gapString = remainingGap.humanize(); - const gapMessage = buildRuleMessage( - `${gapString} (${remainingGap.asMilliseconds()}ms) were not queried between this rule execution and the last execution, so signals may have been missed.`, - 'Consider increasing your look behind time or adding more Kibana instances.' - ); - logger.warn(gapMessage); - hasError = true; - await ruleStatusClient.logStatusChange({ - spaceId, - ruleId: alertId, - newStatus: RuleExecutionStatus.failed, - message: gapMessage, - metrics: { gap: gapString }, - }); - } - - try { - const { listClient, exceptionsClient } = getListClient({ - esClient: services.scopedClusterClient.asCurrentUser, - updatedByUser, +export const createSecurityRuleTypeFactory: CreateSecurityRuleTypeFactory = + ({ lists, logger, mergeStrategy, ignoreFields, ruleDataClient, ruleDataService }) => + (type) => { + const persistenceRuleType = createPersistenceRuleTypeFactory({ ruleDataClient, logger }); + return persistenceRuleType({ + ...type, + async executor(options) { + const { + alertId, + params, + previousStartedAt, + startedAt, + services, spaceId, - lists, - savedObjectClient: options.services.savedObjectsClient, + state, + updatedBy: updatedByUser, + } = options; + let runState = state; + const { from, maxSignals, meta, ruleId, timestampOverride, to } = params; + const { alertWithPersistence, savedObjectsClient, scopedClusterClient } = services; + const searchAfterSize = Math.min(maxSignals, DEFAULT_SEARCH_AFTER_PAGE_SIZE); + + const esClient = scopedClusterClient.asCurrentUser; + + const ruleStatusClient = new RuleExecutionLogClient({ + savedObjectsClient, + ruleDataService, }); - - const exceptionItems = await getExceptions({ - client: exceptionsClient, - lists: (params.exceptionsList as ListArray) ?? [], + const ruleSO = await savedObjectsClient.get('alert', alertId); + + const { + actions, + name, + schedule: { interval }, + } = ruleSO.attributes; + const refresh = actions.length ? 'wait_for' : false; + + const buildRuleMessage = buildRuleMessageFactory({ + id: alertId, + ruleId, + name, + index: ruleDataClient.indexName, }); - const bulkCreate = bulkCreateFactory( - logger, - alertWithPersistence, - buildRuleMessage, - refresh - ); + logger.debug(buildRuleMessage('[+] Starting Signal Rule execution')); + logger.debug(buildRuleMessage(`interval: ${interval}`)); - const wrapHits = wrapHitsFactory({ - logger, - ignoreFields, - mergeStrategy, - ruleSO, + let wroteWarningStatus = false; + await ruleStatusClient.logStatusChange({ spaceId, + ruleId: alertId, + newStatus: RuleExecutionStatus['going to run'], }); - const wrapSequences = wrapSequencesFactory({ + let result = createResultObject(state); + + // check if rule has permissions to access given index pattern + // move this collection of lines into a function in utils + // so that we can use it in create rules route, bulk, etc. + try { + if (!isMachineLearningParams(params)) { + const index = params.index; + const hasTimestampOverride = !!timestampOverride; + + const inputIndices = params.index ?? []; + + const [privileges, timestampFieldCaps] = await Promise.all([ + checkPrivilegesFromEsClient(esClient, inputIndices), + esClient.fieldCaps({ + index: index ?? ['*'], + fields: hasTimestampOverride + ? [TIMESTAMP, timestampOverride as string] + : [TIMESTAMP], + include_unmapped: true, + }), + ]); + + fold, void>( + async (error: Error) => logger.error(buildRuleMessage(error.message)), + async (status: Promise) => (wroteWarningStatus = await status) + )( + flow( + () => + tryCatch( + () => + hasReadIndexPrivileges({ + spaceId, + ruleId: alertId, + privileges, + logger, + buildRuleMessage, + ruleStatusClient, + }), + toError + ), + chain((wroteStatus: unknown) => + tryCatch( + () => + hasTimestampFields({ + spaceId, + ruleId: alertId, + wroteStatus: wroteStatus as boolean, + timestampField: hasTimestampOverride + ? (timestampOverride as string) + : '@timestamp', + ruleName: name, + timestampFieldCapsResponse: timestampFieldCaps, + inputIndices, + ruleStatusClient, + logger, + buildRuleMessage, + }), + toError + ) + ) + )() as Either> + ); + } + } catch (exc) { + logger.error(buildRuleMessage(`Check privileges failed to execute ${exc}`)); + } + let hasError = false; + const { tuples, remainingGap } = getRuleRangeTuples({ logger, - ignoreFields, - mergeStrategy, - ruleSO, - spaceId, + previousStartedAt, + from: from as string, + to: to as string, + interval, + maxSignals: DEFAULT_MAX_SIGNALS, + buildRuleMessage, }); + if (remainingGap.asMilliseconds() > 0) { + const gapString = remainingGap.humanize(); + const gapMessage = buildRuleMessage( + `${gapString} (${remainingGap.asMilliseconds()}ms) were not queried between this rule execution and the last execution, so signals may have been missed.`, + 'Consider increasing your look behind time or adding more Kibana instances.' + ); + logger.warn(gapMessage); + hasError = true; + await ruleStatusClient.logStatusChange({ + spaceId, + ruleId: alertId, + newStatus: RuleExecutionStatus.failed, + message: gapMessage, + metrics: { gap: gapString }, + }); + } - for (const tuple of tuples) { - const runResult = await type.executor({ - ...options, - services, - state: runState, - runOpts: { - buildRuleMessage, - bulkCreate, - exceptionItems, - listClient, - rule: ruleSO, - searchAfterSize, - tuple, - wrapHits, - wrapSequences, - }, + try { + const { listClient, exceptionsClient } = getListClient({ + esClient: services.scopedClusterClient.asCurrentUser, + updatedByUser, + spaceId, + lists, + savedObjectClient: options.services.savedObjectsClient, }); - const createdSignals = result.createdSignals.concat(runResult.createdSignals); - const warningMessages = result.warningMessages.concat(runResult.warningMessages); - result = { - bulkCreateTimes: result.bulkCreateTimes.concat(runResult.bulkCreateTimes), - createdSignals, - createdSignalsCount: createdSignals.length, - errors: result.errors.concat(runResult.errors), - lastLookbackDate: runResult.lastLookbackDate, - searchAfterTimes: result.searchAfterTimes.concat(runResult.searchAfterTimes), - state: runState, - success: result.success && runResult.success, - warning: warningMessages.length > 0, - warningMessages, - }; - runState = runResult.state; - } + const exceptionItems = await getExceptions({ + client: exceptionsClient, + lists: (params.exceptionsList as ListArray) ?? [], + }); - if (result.warningMessages.length) { - const warningMessage = buildRuleMessage(result.warningMessages.join()); - await ruleStatusClient.logStatusChange({ + const bulkCreate = bulkCreateFactory( + logger, + alertWithPersistence, + buildRuleMessage, + refresh + ); + + const wrapHits = wrapHitsFactory({ + logger, + ignoreFields, + mergeStrategy, + ruleSO, + spaceId, + }); + + const wrapSequences = wrapSequencesFactory({ + logger, + ignoreFields, + mergeStrategy, + ruleSO, spaceId, - ruleId: alertId, - newStatus: RuleExecutionStatus['partial failure'], - message: warningMessage, }); - } - if (result.success) { - const createdSignalsCount = result.createdSignals.length; - - if (actions.length) { - const notificationRuleParams: NotificationRuleTypeParams = ({ - ...params, - name: name as string, - id: ruleSO.id as string, - } as unknown) as NotificationRuleTypeParams; - - const fromInMs = parseScheduleDates(`now-${interval}`)?.format('x'); - const toInMs = parseScheduleDates('now')?.format('x'); - const resultsLink = getNotificationResultsLink({ - from: fromInMs, - to: toInMs, - id: ruleSO.id, - kibanaSiemAppUrl: (meta as { kibana_siem_app_url?: string } | undefined) - ?.kibana_siem_app_url, + for (const tuple of tuples) { + const runResult = await type.executor({ + ...options, + services, + state: runState, + runOpts: { + buildRuleMessage, + bulkCreate, + exceptionItems, + listClient, + rule: ruleSO, + searchAfterSize, + tuple, + wrapHits, + wrapSequences, + }, }); - logger.info(buildRuleMessage(`Found ${createdSignalsCount} signals for notification.`)); + const createdSignals = result.createdSignals.concat(runResult.createdSignals); + const warningMessages = result.warningMessages.concat(runResult.warningMessages); + result = { + bulkCreateTimes: result.bulkCreateTimes.concat(runResult.bulkCreateTimes), + createdSignals, + createdSignalsCount: createdSignals.length, + errors: result.errors.concat(runResult.errors), + lastLookbackDate: runResult.lastLookbackDate, + searchAfterTimes: result.searchAfterTimes.concat(runResult.searchAfterTimes), + state: runState, + success: result.success && runResult.success, + warning: warningMessages.length > 0, + warningMessages, + }; + runState = runResult.state; + } + + if (result.warningMessages.length) { + const warningMessage = buildRuleMessage(result.warningMessages.join()); + await ruleStatusClient.logStatusChange({ + spaceId, + ruleId: alertId, + newStatus: RuleExecutionStatus['partial failure'], + message: warningMessage, + }); + } - if (ruleSO.attributes.throttle != null) { - await scheduleThrottledNotificationActions({ - alertInstance: services.alertInstanceFactory(alertId), - throttle: ruleSO.attributes.throttle, - startedAt, + if (result.success) { + const createdSignalsCount = result.createdSignals.length; + + if (actions.length) { + const notificationRuleParams: NotificationRuleTypeParams = { + ...params, + name: name as string, + id: ruleSO.id as string, + } as unknown as NotificationRuleTypeParams; + + const fromInMs = parseScheduleDates(`now-${interval}`)?.format('x'); + const toInMs = parseScheduleDates('now')?.format('x'); + const resultsLink = getNotificationResultsLink({ + from: fromInMs, + to: toInMs, id: ruleSO.id, kibanaSiemAppUrl: (meta as { kibana_siem_app_url?: string } | undefined) ?.kibana_siem_app_url, - outputIndex: ruleDataClient.indexName, - ruleId, - esClient: services.scopedClusterClient.asCurrentUser, - notificationRuleParams, - }); - } else if (createdSignalsCount) { - const alertInstance = services.alertInstanceFactory(alertId); - scheduleNotificationActions({ - alertInstance, - signalsCount: createdSignalsCount, - signals: result.createdSignals, - resultsLink, - ruleParams: notificationRuleParams, }); + + logger.info( + buildRuleMessage(`Found ${createdSignalsCount} signals for notification.`) + ); + + if (ruleSO.attributes.throttle != null) { + await scheduleThrottledNotificationActions({ + alertInstance: services.alertInstanceFactory(alertId), + throttle: ruleSO.attributes.throttle, + startedAt, + id: ruleSO.id, + kibanaSiemAppUrl: (meta as { kibana_siem_app_url?: string } | undefined) + ?.kibana_siem_app_url, + outputIndex: ruleDataClient.indexName, + ruleId, + esClient: services.scopedClusterClient.asCurrentUser, + notificationRuleParams, + }); + } else if (createdSignalsCount) { + const alertInstance = services.alertInstanceFactory(alertId); + scheduleNotificationActions({ + alertInstance, + signalsCount: createdSignalsCount, + signals: result.createdSignals, + resultsLink, + ruleParams: notificationRuleParams, + }); + } } - } - logger.debug(buildRuleMessage('[+] Signal Rule execution completed.')); - logger.debug( - buildRuleMessage( - `[+] Finished indexing ${createdSignalsCount} signals into ${ruleDataClient.indexName}` - ) - ); + logger.debug(buildRuleMessage('[+] Signal Rule execution completed.')); + logger.debug( + buildRuleMessage( + `[+] Finished indexing ${createdSignalsCount} signals into ${ruleDataClient.indexName}` + ) + ); + + if (!hasError && !wroteWarningStatus && !result.warning) { + await ruleStatusClient.logStatusChange({ + spaceId, + ruleId: alertId, + newStatus: RuleExecutionStatus.succeeded, + message: 'succeeded', + metrics: { + bulkCreateTimeDurations: result.bulkCreateTimes, + searchAfterTimeDurations: result.searchAfterTimes, + lastLookBackDate: result.lastLookbackDate?.toISOString(), + }, + }); + } - if (!hasError && !wroteWarningStatus && !result.warning) { + // adding this log line so we can get some information from cloud + logger.info( + buildRuleMessage( + `[+] Finished indexing ${createdSignalsCount} ${ + !isEmpty(tuples) + ? `signals searched between date ranges ${JSON.stringify(tuples, null, 2)}` + : '' + }` + ) + ); + } else { + const errorMessage = buildRuleMessage( + 'Bulk Indexing of signals failed:', + result.errors.join() + ); + logger.error(errorMessage); await ruleStatusClient.logStatusChange({ spaceId, ruleId: alertId, - newStatus: RuleExecutionStatus.succeeded, - message: 'succeeded', + newStatus: RuleExecutionStatus.failed, + message: errorMessage, metrics: { bulkCreateTimeDurations: result.bulkCreateTimes, searchAfterTimeDurations: result.searchAfterTimes, @@ -336,28 +367,19 @@ export const createSecurityRuleTypeFactory: CreateSecurityRuleTypeFactory = ({ }, }); } - - // adding this log line so we can get some information from cloud - logger.info( - buildRuleMessage( - `[+] Finished indexing ${createdSignalsCount} ${ - !isEmpty(tuples) - ? `signals searched between date ranges ${JSON.stringify(tuples, null, 2)}` - : '' - }` - ) - ); - } else { - const errorMessage = buildRuleMessage( - 'Bulk Indexing of signals failed:', - result.errors.join() + } catch (error) { + const errorMessage = error.message ?? '(no error message given)'; + const message = buildRuleMessage( + 'An error occurred during rule execution:', + `message: "${errorMessage}"` ); - logger.error(errorMessage); + + logger.error(message); await ruleStatusClient.logStatusChange({ spaceId, ruleId: alertId, newStatus: RuleExecutionStatus.failed, - message: errorMessage, + message, metrics: { bulkCreateTimeDurations: result.bulkCreateTimes, searchAfterTimeDurations: result.searchAfterTimes, @@ -365,28 +387,8 @@ export const createSecurityRuleTypeFactory: CreateSecurityRuleTypeFactory = ({ }, }); } - } catch (error) { - const errorMessage = error.message ?? '(no error message given)'; - const message = buildRuleMessage( - 'An error occurred during rule execution:', - `message: "${errorMessage}"` - ); - - logger.error(message); - await ruleStatusClient.logStatusChange({ - spaceId, - ruleId: alertId, - newStatus: RuleExecutionStatus.failed, - message, - metrics: { - bulkCreateTimeDurations: result.bulkCreateTimes, - searchAfterTimeDurations: result.searchAfterTimes, - lastLookBackDate: result.lastLookbackDate?.toISOString(), - }, - }); - } - return result.state; - }, - }); -}; + return result.state; + }, + }); + }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/build_rule_message_factory.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/build_rule_message_factory.ts index 09d2cb559fe69..0d7586eb23386 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/build_rule_message_factory.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/build_rule_message_factory.ts @@ -13,16 +13,13 @@ export interface BuildRuleMessageFactoryParams { index: string; } -export const buildRuleMessageFactory = ({ - id, - ruleId, - index, - name, -}: BuildRuleMessageFactoryParams): BuildRuleMessage => (...messages) => - [ - ...messages, - `name: "${name}"`, - `id: "${id}"`, - `rule id: "${ruleId ?? '(unknown rule id)'}"`, - `signals index alias: "${index}"`, - ].join(' '); +export const buildRuleMessageFactory = + ({ id, ruleId, index, name }: BuildRuleMessageFactoryParams): BuildRuleMessage => + (...messages) => + [ + ...messages, + `name: "${name}"`, + `id: "${id}"`, + `rule id: "${ruleId ?? '(unknown rule id)'}"`, + `signals index alias: "${index}"`, + ].join(' '); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/bulk_create_factory.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/bulk_create_factory.ts index cd7a5150ad384..af0a8a27f2b25 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/bulk_create_factory.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/bulk_create_factory.ts @@ -26,86 +26,88 @@ export interface GenericBulkCreateResponse { errors: string[]; } -export const bulkCreateFactory = ( - logger: Logger, - alertWithPersistence: PersistenceAlertService, - buildRuleMessage: BuildRuleMessage, - refreshForBulkCreate: RefreshTypes -) => async (wrappedDocs: Array>): Promise> => { - if (wrappedDocs.length === 0) { - return { - errors: [], - success: true, - bulkCreateDuration: '0', - createdItemsCount: 0, - createdItems: [], - }; - } - - const start = performance.now(); - - const response = await alertWithPersistence( - wrappedDocs.map((doc) => ({ - id: doc._id, - fields: doc.fields ?? doc._source ?? {}, - })), - refreshForBulkCreate - ); - - const end = performance.now(); - - logger.debug( - buildRuleMessage( - `individual bulk process time took: ${makeFloatString(end - start)} milliseconds` - ) - ); - logger.debug( - buildRuleMessage(`took property says bulk took: ${response.body.took} milliseconds`) - ); - - const createdItems = wrappedDocs - .map((doc, index) => { - const responseIndex = response.body.items[index].index; +export const bulkCreateFactory = + ( + logger: Logger, + alertWithPersistence: PersistenceAlertService, + buildRuleMessage: BuildRuleMessage, + refreshForBulkCreate: RefreshTypes + ) => + async (wrappedDocs: Array>): Promise> => { + if (wrappedDocs.length === 0) { return { - _id: responseIndex?._id ?? '', - _index: responseIndex?._index ?? '', - [ALERT_INSTANCE_ID]: responseIndex?._id ?? '', - ...doc._source, + errors: [], + success: true, + bulkCreateDuration: '0', + createdItemsCount: 0, + createdItems: [], }; - }) - .filter((_, index) => response.body.items[index].index?.status === 201); - const createdItemsCount = createdItems.length; + } - const duplicateSignalsCount = countBy(response.body.items, 'create.status')['409']; - const errorCountByMessage = errorAggregator(response.body, [409]); + const start = performance.now(); - logger.debug(buildRuleMessage(`bulk created ${createdItemsCount} signals`)); + const response = await alertWithPersistence( + wrappedDocs.map((doc) => ({ + id: doc._id, + fields: doc.fields ?? doc._source ?? {}, + })), + refreshForBulkCreate + ); - if (duplicateSignalsCount > 0) { - logger.debug(buildRuleMessage(`ignored ${duplicateSignalsCount} duplicate signals`)); - } + const end = performance.now(); - if (!isEmpty(errorCountByMessage)) { - logger.error( + logger.debug( buildRuleMessage( - `[-] bulkResponse had errors with responses of: ${JSON.stringify(errorCountByMessage)}` + `individual bulk process time took: ${makeFloatString(end - start)} milliseconds` ) ); + logger.debug( + buildRuleMessage(`took property says bulk took: ${response.body.took} milliseconds`) + ); + + const createdItems = wrappedDocs + .map((doc, index) => { + const responseIndex = response.body.items[index].index; + return { + _id: responseIndex?._id ?? '', + _index: responseIndex?._index ?? '', + [ALERT_INSTANCE_ID]: responseIndex?._id ?? '', + ...doc._source, + }; + }) + .filter((_, index) => response.body.items[index].index?.status === 201); + const createdItemsCount = createdItems.length; - return { - errors: Object.keys(errorCountByMessage), - success: false, - bulkCreateDuration: makeFloatString(end - start), - createdItemsCount: createdItems.length, - createdItems, - }; - } else { - return { - errors: [], - success: true, - bulkCreateDuration: makeFloatString(end - start), - createdItemsCount: createdItems.length, - createdItems, - }; - } -}; + const duplicateSignalsCount = countBy(response.body.items, 'create.status')['409']; + const errorCountByMessage = errorAggregator(response.body, [409]); + + logger.debug(buildRuleMessage(`bulk created ${createdItemsCount} signals`)); + + if (duplicateSignalsCount > 0) { + logger.debug(buildRuleMessage(`ignored ${duplicateSignalsCount} duplicate signals`)); + } + + if (!isEmpty(errorCountByMessage)) { + logger.error( + buildRuleMessage( + `[-] bulkResponse had errors with responses of: ${JSON.stringify(errorCountByMessage)}` + ) + ); + + return { + errors: Object.keys(errorCountByMessage), + success: false, + bulkCreateDuration: makeFloatString(end - start), + createdItemsCount: createdItems.length, + createdItems, + }; + } else { + return { + errors: [], + success: true, + bulkCreateDuration: makeFloatString(end - start), + createdItemsCount: createdItems.length, + createdItems, + }; + } + }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.test.ts index 453c4d8b785d3..70b17ab96ab00 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.test.ts @@ -362,13 +362,13 @@ describe('buildAlert', () => { test('it will remove a "signal" numeric clash', () => { const sampleDoc = sampleDocNoSortIdWithTimestamp('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'); - const doc = ({ + const doc = { ...sampleDoc, _source: { ...sampleDoc._source, signal: 127, }, - } as unknown) as SignalDoc; + } as unknown as SignalDoc; const output = removeClashes(doc); const timestamp = output._source[TIMESTAMP]; expect(output).toEqual({ @@ -382,13 +382,13 @@ describe('buildAlert', () => { test('it will remove a "signal" object clash', () => { const sampleDoc = sampleDocNoSortIdWithTimestamp('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'); - const doc = ({ + const doc = { ...sampleDoc, _source: { ...sampleDoc._source, signal: { child_1: { child_2: 'Test nesting' } }, }, - } as unknown) as SignalDoc; + } as unknown as SignalDoc; const output = removeClashes(doc); const timestamp = output._source[TIMESTAMP]; expect(output).toEqual({ @@ -402,13 +402,13 @@ describe('buildAlert', () => { test('it will not remove a "signal" if that is signal is one of our signals', () => { const sampleDoc = sampleDocNoSortIdWithTimestamp('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'); - const doc = ({ + const doc = { ...sampleDoc, _source: { ...sampleDoc._source, signal: { rule: { id: '123' } }, }, - } as unknown) as SignalDoc; + } as unknown as SignalDoc; const output = removeClashes(doc); const timestamp = output._source[TIMESTAMP]; const expected = { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.ts index d39d6fa2eb805..6bb14df48eac0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.ts @@ -123,7 +123,7 @@ export const buildAlert = ( const { id, output_index: outputIndex, ...mappedRule } = rule; mappedRule.uuid = id; - return ({ + return { [TIMESTAMP]: new Date().toISOString(), [ALERT_RULE_CONSUMER]: SERVER_APP_ID, [SPACE_IDS]: spaceId != null ? [spaceId] : [], @@ -133,7 +133,7 @@ export const buildAlert = ( [ALERT_DEPTH]: depth, [ALERT_REASON]: reason, ...flattenWithPrefix(ALERT_RULE_NAMESPACE, mappedRule as RulesSchema), - } as unknown) as RACAlert; + } as unknown as RACAlert; }; /** diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert_group_from_sequence.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert_group_from_sequence.test.ts index 09367c97e0187..6daafbfae40f2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert_group_from_sequence.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert_group_from_sequence.test.ts @@ -23,12 +23,12 @@ import { getQueryRuleParams } from '../../../schemas/rule_schemas.mock'; const SPACE_ID = 'space'; -const loggerMock = ({ +const loggerMock = { debug: jest.fn(), info: jest.fn(), warn: jest.fn(), error: jest.fn(), -} as unknown) as Logger; +} as unknown as Logger; describe('buildAlert', () => { beforeEach(() => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/wrap_hits_factory.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/wrap_hits_factory.ts index 1b169a2996b7e..69c1821a35edd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/wrap_hits_factory.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/wrap_hits_factory.ts @@ -13,44 +13,46 @@ import { SearchAfterAndBulkCreateParams, SimpleHit, WrapHits } from '../../signa import { generateId } from '../../signals/utils'; import { buildBulkBody } from './utils/build_bulk_body'; -export const wrapHitsFactory = ({ - logger, - ignoreFields, - mergeStrategy, - ruleSO, - spaceId, -}: { - logger: Logger; - ruleSO: SearchAfterAndBulkCreateParams['ruleSO']; - mergeStrategy: ConfigType['alertMergeStrategy']; - ignoreFields: ConfigType['alertIgnoreFields']; - spaceId: string | null | undefined; -}): WrapHits => (events, buildReasonMessage) => { - try { - const wrappedDocs = events.map((event) => { - return { - _index: '', - _id: generateId( - event._index, - event._id, - String(event._version), - ruleSO.attributes.params.ruleId ?? '' - ), - _source: buildBulkBody( - spaceId, - ruleSO, - event as SimpleHit, - mergeStrategy, - ignoreFields, - true, - buildReasonMessage - ), - }; - }); +export const wrapHitsFactory = + ({ + logger, + ignoreFields, + mergeStrategy, + ruleSO, + spaceId, + }: { + logger: Logger; + ruleSO: SearchAfterAndBulkCreateParams['ruleSO']; + mergeStrategy: ConfigType['alertMergeStrategy']; + ignoreFields: ConfigType['alertIgnoreFields']; + spaceId: string | null | undefined; + }): WrapHits => + (events, buildReasonMessage) => { + try { + const wrappedDocs = events.map((event) => { + return { + _index: '', + _id: generateId( + event._index, + event._id, + String(event._version), + ruleSO.attributes.params.ruleId ?? '' + ), + _source: buildBulkBody( + spaceId, + ruleSO, + event as SimpleHit, + mergeStrategy, + ignoreFields, + true, + buildReasonMessage + ), + }; + }); - return filterDuplicateSignals(ruleSO.id, wrappedDocs, true); - } catch (error) { - logger.error(error); - return []; - } -}; + return filterDuplicateSignals(ruleSO.id, wrappedDocs, true); + } catch (error) { + logger.error(error); + return []; + } + }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/wrap_sequences_factory.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/wrap_sequences_factory.ts index edfb220d8b6ba..9315f096552d8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/wrap_sequences_factory.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/wrap_sequences_factory.ts @@ -12,30 +12,32 @@ import { buildAlertGroupFromSequence } from './utils/build_alert_group_from_sequ import { ConfigType } from '../../../../config'; import { WrappedRACAlert } from '../types'; -export const wrapSequencesFactory = ({ - logger, - ruleSO, - ignoreFields, - mergeStrategy, - spaceId, -}: { - logger: Logger; - ruleSO: SearchAfterAndBulkCreateParams['ruleSO']; - ignoreFields: ConfigType['alertIgnoreFields']; - mergeStrategy: ConfigType['alertMergeStrategy']; - spaceId: string | null | undefined; -}): WrapSequences => (sequences, buildReasonMessage) => - sequences.reduce( - (acc: WrappedRACAlert[], sequence) => [ - ...acc, - ...buildAlertGroupFromSequence( - logger, - sequence, - ruleSO, - mergeStrategy, - spaceId, - buildReasonMessage - ), - ], - [] - ); +export const wrapSequencesFactory = + ({ + logger, + ruleSO, + ignoreFields, + mergeStrategy, + spaceId, + }: { + logger: Logger; + ruleSO: SearchAfterAndBulkCreateParams['ruleSO']; + ignoreFields: ConfigType['alertIgnoreFields']; + mergeStrategy: ConfigType['alertMergeStrategy']; + spaceId: string | null | undefined; + }): WrapSequences => + (sequences, buildReasonMessage) => + sequences.reduce( + (acc: WrappedRACAlert[], sequence) => [ + ...acc, + ...buildAlertGroupFromSequence( + logger, + sequence, + ruleSO, + mergeStrategy, + spaceId, + buildReasonMessage + ), + ], + [] + ); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml/create_ml_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml/create_ml_alert_type.ts index 7d891d430c63c..ec2f5dd104646 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml/create_ml_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml/create_ml_alert_type.ts @@ -14,15 +14,8 @@ import { createSecurityRuleTypeFactory } from '../create_security_rule_type_fact import { CreateRuleOptions } from '../types'; export const createMlAlertType = (createOptions: CreateRuleOptions) => { - const { - lists, - logger, - mergeStrategy, - ignoreFields, - ml, - ruleDataClient, - ruleDataService, - } = createOptions; + const { lists, logger, mergeStrategy, ignoreFields, ml, ruleDataClient, ruleDataService } = + createOptions; const createSecurityRuleType = createSecurityRuleTypeFactory({ lists, logger, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_install.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_install.test.ts index 4ef84fd1619ea..c5d4d6d5d2d48 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_install.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_install.test.ts @@ -21,7 +21,8 @@ describe.each([ }); test('should return empty array if the two rule ids match', () => { - const ruleFromFileSystem = getAddPrepackagedRulesSchemaDecodedMock() as AddPrepackagedRulesSchemaDecoded; + const ruleFromFileSystem = + getAddPrepackagedRulesSchemaDecodedMock() as AddPrepackagedRulesSchemaDecoded; ruleFromFileSystem.rule_id = 'rule-1'; const installedRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); @@ -31,7 +32,8 @@ describe.each([ }); test('should return the rule to install if the id of the two rules do not match', () => { - const ruleFromFileSystem = getAddPrepackagedRulesSchemaDecodedMock() as AddPrepackagedRulesSchemaDecoded; + const ruleFromFileSystem = + getAddPrepackagedRulesSchemaDecodedMock() as AddPrepackagedRulesSchemaDecoded; ruleFromFileSystem.rule_id = 'rule-1'; const installedRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); @@ -41,10 +43,12 @@ describe.each([ }); test('should return two rules to install if both the ids of the two rules do not match', () => { - const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaDecodedMock() as AddPrepackagedRulesSchemaDecoded; + const ruleFromFileSystem1 = + getAddPrepackagedRulesSchemaDecodedMock() as AddPrepackagedRulesSchemaDecoded; ruleFromFileSystem1.rule_id = 'rule-1'; - const ruleFromFileSystem2 = getAddPrepackagedRulesSchemaDecodedMock() as AddPrepackagedRulesSchemaDecoded; + const ruleFromFileSystem2 = + getAddPrepackagedRulesSchemaDecodedMock() as AddPrepackagedRulesSchemaDecoded; ruleFromFileSystem2.rule_id = 'rule-2'; const installedRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); @@ -54,13 +58,16 @@ describe.each([ }); test('should return two rules of three to install if both the ids of the two rules do not match but the third does', () => { - const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaDecodedMock() as AddPrepackagedRulesSchemaDecoded; + const ruleFromFileSystem1 = + getAddPrepackagedRulesSchemaDecodedMock() as AddPrepackagedRulesSchemaDecoded; ruleFromFileSystem1.rule_id = 'rule-1'; - const ruleFromFileSystem2 = getAddPrepackagedRulesSchemaDecodedMock() as AddPrepackagedRulesSchemaDecoded; + const ruleFromFileSystem2 = + getAddPrepackagedRulesSchemaDecodedMock() as AddPrepackagedRulesSchemaDecoded; ruleFromFileSystem2.rule_id = 'rule-2'; - const ruleFromFileSystem3 = getAddPrepackagedRulesSchemaDecodedMock() as AddPrepackagedRulesSchemaDecoded; + const ruleFromFileSystem3 = + getAddPrepackagedRulesSchemaDecodedMock() as AddPrepackagedRulesSchemaDecoded; ruleFromFileSystem3.rule_id = 'rule-3'; const installedRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.test.ts index ee39120a8b6d0..b5580de152fcc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.test.ts @@ -22,7 +22,7 @@ describe.each([ ...rulesOptionsMock, enabled: false, }; - ((rulesOptionsMock.rulesClient as unknown) as RulesClientMock).update.mockResolvedValue( + (rulesOptionsMock.rulesClient as unknown as RulesClientMock).update.mockResolvedValue( getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()) ); await patchRules(ruleOptions); @@ -42,7 +42,7 @@ describe.each([ if (ruleOptions.rule != null) { ruleOptions.rule.enabled = false; } - ((rulesOptionsMock.rulesClient as unknown) as RulesClientMock).update.mockResolvedValue( + (rulesOptionsMock.rulesClient as unknown as RulesClientMock).update.mockResolvedValue( getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()) ); await patchRules(ruleOptions); @@ -62,7 +62,7 @@ describe.each([ if (ruleOptions.rule != null) { ruleOptions.rule.enabled = false; } - ((rulesOptionsMock.rulesClient as unknown) as RulesClientMock).update.mockResolvedValue( + (rulesOptionsMock.rulesClient as unknown as RulesClientMock).update.mockResolvedValue( getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()) ); await patchRules(ruleOptions); @@ -88,7 +88,7 @@ describe.each([ if (ruleOptions.rule != null) { ruleOptions.rule.enabled = false; } - ((rulesOptionsMock.rulesClient as unknown) as RulesClientMock).update.mockResolvedValue( + (rulesOptionsMock.rulesClient as unknown as RulesClientMock).update.mockResolvedValue( getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()) ); await patchRules(ruleOptions); @@ -120,7 +120,7 @@ describe.each([ }, ], }; - ((rulesOptionsMock.rulesClient as unknown) as RulesClientMock).update.mockResolvedValue( + (rulesOptionsMock.rulesClient as unknown as RulesClientMock).update.mockResolvedValue( getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()) ); await patchRules(ruleOptions); @@ -157,7 +157,7 @@ describe.each([ }, ]; } - ((ruleOptions.rulesClient as unknown) as RulesClientMock).update.mockResolvedValue( + (ruleOptions.rulesClient as unknown as RulesClientMock).update.mockResolvedValue( getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()) ); await patchRules(ruleOptions); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts index c27caaed5449e..5417417caad6b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts @@ -204,7 +204,7 @@ export const isAlertType = ( isRuleRegistryEnabled: boolean, partialAlert: PartialAlert ): partialAlert is RuleAlertType => { - const ruleTypeValues = (Object.values(ruleTypeMappings) as unknown) as string[]; + const ruleTypeValues = Object.values(ruleTypeMappings) as unknown as string[]; return isRuleRegistryEnabled ? ruleTypeValues.includes(partialAlert.alertTypeId as string) : partialAlert.alertTypeId === SIGNALS_ID; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.test.ts index 1ad5cd7ae934c..74301f3665ff8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.test.ts @@ -18,10 +18,10 @@ describe.each([ it('should call rulesClient.disable if the rule was enabled and enabled is false', async () => { const rulesOptionsMock = getUpdateRulesOptionsMock(isRuleRegistryEnabled); rulesOptionsMock.ruleUpdate.enabled = false; - ((rulesOptionsMock.rulesClient as unknown) as RulesClientMock).get.mockResolvedValue( + (rulesOptionsMock.rulesClient as unknown as RulesClientMock).get.mockResolvedValue( getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()) ); - ((rulesOptionsMock.rulesClient as unknown) as RulesClientMock).update.mockResolvedValue( + (rulesOptionsMock.rulesClient as unknown as RulesClientMock).update.mockResolvedValue( getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()) ); @@ -38,11 +38,11 @@ describe.each([ const rulesOptionsMock = getUpdateRulesOptionsMock(isRuleRegistryEnabled); rulesOptionsMock.ruleUpdate.enabled = true; - ((rulesOptionsMock.rulesClient as unknown) as RulesClientMock).get.mockResolvedValue({ + (rulesOptionsMock.rulesClient as unknown as RulesClientMock).get.mockResolvedValue({ ...getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()), enabled: false, }); - ((rulesOptionsMock.rulesClient as unknown) as RulesClientMock).update.mockResolvedValue( + (rulesOptionsMock.rulesClient as unknown as RulesClientMock).update.mockResolvedValue( getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()) ); @@ -59,11 +59,11 @@ describe.each([ const rulesOptionsMock = getUpdateMlRulesOptionsMock(isRuleRegistryEnabled); rulesOptionsMock.ruleUpdate.enabled = true; - ((rulesOptionsMock.rulesClient as unknown) as RulesClientMock).update.mockResolvedValue( + (rulesOptionsMock.rulesClient as unknown as RulesClientMock).update.mockResolvedValue( getAlertMock(isRuleRegistryEnabled, getMlRuleParams()) ); - ((rulesOptionsMock.rulesClient as unknown) as RulesClientMock).get.mockResolvedValue( + (rulesOptionsMock.rulesClient as unknown as RulesClientMock).get.mockResolvedValue( getAlertMock(isRuleRegistryEnabled, getMlRuleParams()) ); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.test.ts index 95e8552c4b14b..574b16207786f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.test.ts @@ -295,22 +295,22 @@ describe('utils', () => { test('returns "NOTIFICATION_THROTTLE_NO_ACTIONS" if actions is an empty array and we do not have a throttle', () => { expect( - transformFromAlertThrottle(({ + transformFromAlertThrottle({ muteAll: false, notifyWhen: 'onActiveAlert', actions: [], - } as unknown) as SanitizedAlert) + } as unknown as SanitizedAlert) ).toEqual(NOTIFICATION_THROTTLE_NO_ACTIONS); }); test('returns "NOTIFICATION_THROTTLE_NO_ACTIONS" if actions is an empty array and we have a throttle', () => { expect( - transformFromAlertThrottle(({ + transformFromAlertThrottle({ muteAll: false, notifyWhen: 'onThrottleInterval', actions: [], throttle: '1d', - } as unknown) as SanitizedAlert) + } as unknown as SanitizedAlert) ).toEqual(NOTIFICATION_THROTTLE_NO_ACTIONS); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.test.ts index 5f392bed75f76..00286e9f0d3c9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.test.ts @@ -399,13 +399,13 @@ describe('buildBulkBody', () => { const sampleDoc = sampleDocNoSortId(); const buildReasonMessage = jest.fn().mockReturnValue('reasonable reason'); delete sampleDoc._source.source; - const doc = ({ + const doc = { ...sampleDoc, _source: { ...sampleDoc._source, signal: 123, }, - } as unknown) as SignalSourceHit; + } as unknown as SignalSourceHit; const { '@timestamp': timestamp, ...fakeSignalSourceHit } = buildBulkBody( ruleSO, doc, @@ -463,13 +463,13 @@ describe('buildBulkBody', () => { const sampleDoc = sampleDocNoSortId(); const buildReasonMessage = jest.fn().mockReturnValue('reasonable reason'); delete sampleDoc._source.source; - const doc = ({ + const doc = { ...sampleDoc, _source: { ...sampleDoc._source, signal: { child_1: { child_2: 'nested data' } }, }, - } as unknown) as SignalSourceHit; + } as unknown as SignalSourceHit; const { '@timestamp': timestamp, ...fakeSignalSourceHit } = buildBulkBody( ruleSO, doc, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.ts index eb7e8a9fbb6d7..f62e6cebf719b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.ts @@ -53,7 +53,11 @@ export const buildBulkBody = ( // Filter out any kibana.* fields from the generated signal - kibana.* fields are aliases // in siem-signals so we can't write to them, but for signals-on-signals they'll be returned // in the fields API response and merged into the mergedDoc source - const { threshold_result: thresholdResult, kibana, ...filteredSource } = mergedDoc._source || { + const { + threshold_result: thresholdResult, + kibana, + ...filteredSource + } = mergedDoc._source || { threshold_result: null, }; const signalHit: SignalHit = { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_signal.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_signal.test.ts index 90b9cce9e057d..e69bbd64faada 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_signal.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_signal.test.ts @@ -338,39 +338,39 @@ describe('buildSignal', () => { test('it will remove a "signal" numeric clash', () => { const sampleDoc = sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'); - const doc = ({ + const doc = { ...sampleDoc, _source: { ...sampleDoc._source, signal: 127, }, - } as unknown) as BaseSignalHit; + } as unknown as BaseSignalHit; const output = removeClashes(doc); expect(output).toEqual(sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71')); }); test('it will remove a "signal" object clash', () => { const sampleDoc = sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'); - const doc = ({ + const doc = { ...sampleDoc, _source: { ...sampleDoc._source, signal: { child_1: { child_2: 'Test nesting' } }, }, - } as unknown) as BaseSignalHit; + } as unknown as BaseSignalHit; const output = removeClashes(doc); expect(output).toEqual(sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71')); }); test('it will not remove a "signal" if that is signal is one of our signals', () => { const sampleDoc = sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'); - const doc = ({ + const doc = { ...sampleDoc, _source: { ...sampleDoc._source, signal: { rule: { id: '123' } }, }, - } as unknown) as BaseSignalHit; + } as unknown as BaseSignalHit; const output = removeClashes(doc); const expected = { ...sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'), diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_factory.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_factory.ts index 80d1ebf800191..29790bb08b8f8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_factory.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_factory.ts @@ -22,80 +22,82 @@ export interface GenericBulkCreateResponse { errors: string[]; } -export const bulkCreateFactory = ( - logger: Logger, - esClient: ElasticsearchClient, - buildRuleMessage: BuildRuleMessage, - refreshForBulkCreate: RefreshTypes -) => async (wrappedDocs: Array>): Promise> => { - if (wrappedDocs.length === 0) { - return { - errors: [], - success: true, - bulkCreateDuration: '0', - createdItemsCount: 0, - createdItems: [], - }; - } +export const bulkCreateFactory = + ( + logger: Logger, + esClient: ElasticsearchClient, + buildRuleMessage: BuildRuleMessage, + refreshForBulkCreate: RefreshTypes + ) => + async (wrappedDocs: Array>): Promise> => { + if (wrappedDocs.length === 0) { + return { + errors: [], + success: true, + bulkCreateDuration: '0', + createdItemsCount: 0, + createdItems: [], + }; + } - const bulkBody = wrappedDocs.flatMap((wrappedDoc) => [ - { - create: { - _index: wrappedDoc._index, - _id: wrappedDoc._id, + const bulkBody = wrappedDocs.flatMap((wrappedDoc) => [ + { + create: { + _index: wrappedDoc._index, + _id: wrappedDoc._id, + }, }, - }, - wrappedDoc._source, - ]); - const start = performance.now(); + wrappedDoc._source, + ]); + const start = performance.now(); - const { body: response } = await esClient.bulk({ - refresh: refreshForBulkCreate, - body: bulkBody, - }); + const { body: response } = await esClient.bulk({ + refresh: refreshForBulkCreate, + body: bulkBody, + }); - const end = performance.now(); - logger.debug( - buildRuleMessage( - `individual bulk process time took: ${makeFloatString(end - start)} milliseconds` - ) - ); - logger.debug(buildRuleMessage(`took property says bulk took: ${response.took} milliseconds`)); - const createdItems = wrappedDocs - .map((doc, index) => ({ - _id: response.items[index].create?._id ?? '', - _index: response.items[index].create?._index ?? '', - ...doc._source, - })) - .filter((_, index) => get(response.items[index], 'create.status') === 201); - const createdItemsCount = createdItems.length; - const duplicateSignalsCount = countBy(response.items, 'create.status')['409']; - const errorCountByMessage = errorAggregator(response, [409]); - - logger.debug(buildRuleMessage(`bulk created ${createdItemsCount} signals`)); - if (duplicateSignalsCount > 0) { - logger.debug(buildRuleMessage(`ignored ${duplicateSignalsCount} duplicate signals`)); - } - if (!isEmpty(errorCountByMessage)) { - logger.error( + const end = performance.now(); + logger.debug( buildRuleMessage( - `[-] bulkResponse had errors with responses of: ${JSON.stringify(errorCountByMessage)}` + `individual bulk process time took: ${makeFloatString(end - start)} milliseconds` ) ); - return { - errors: Object.keys(errorCountByMessage), - success: false, - bulkCreateDuration: makeFloatString(end - start), - createdItemsCount, - createdItems, - }; - } else { - return { - errors: [], - success: true, - bulkCreateDuration: makeFloatString(end - start), - createdItemsCount, - createdItems, - }; - } -}; + logger.debug(buildRuleMessage(`took property says bulk took: ${response.took} milliseconds`)); + const createdItems = wrappedDocs + .map((doc, index) => ({ + _id: response.items[index].create?._id ?? '', + _index: response.items[index].create?._index ?? '', + ...doc._source, + })) + .filter((_, index) => get(response.items[index], 'create.status') === 201); + const createdItemsCount = createdItems.length; + const duplicateSignalsCount = countBy(response.items, 'create.status')['409']; + const errorCountByMessage = errorAggregator(response, [409]); + + logger.debug(buildRuleMessage(`bulk created ${createdItemsCount} signals`)); + if (duplicateSignalsCount > 0) { + logger.debug(buildRuleMessage(`ignored ${duplicateSignalsCount} duplicate signals`)); + } + if (!isEmpty(errorCountByMessage)) { + logger.error( + buildRuleMessage( + `[-] bulkResponse had errors with responses of: ${JSON.stringify(errorCountByMessage)}` + ) + ); + return { + errors: Object.keys(errorCountByMessage), + success: false, + bulkCreateDuration: makeFloatString(end - start), + createdItemsCount, + createdItems, + }; + } else { + return { + errors: [], + success: true, + bulkCreateDuration: makeFloatString(end - start), + createdItemsCount, + createdItems, + }; + } + }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_ml_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_ml_signals.ts index be6f4cb8feae5..c2ac2a031ca9b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_ml_signals.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_ml_signals.ts @@ -54,8 +54,8 @@ export const transformAnomalyFieldsToEcs = (anomaly: Anomaly): EcsAnomaly => { } const omitDottedFields = omit(errantFields.map((field) => field.name)); - const setNestedFields = errantFields.map((field) => (_anomaly: Anomaly) => - set(_anomaly, field.name, field.value) + const setNestedFields = errantFields.map( + (field) => (_anomaly: Anomaly) => set(_anomaly, field.name, field.value) ); const setTimestamp = (_anomaly: Anomaly) => set(_anomaly, '@timestamp', new Date(timestamp).toISOString()); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.ts index 20c4cb16dadc8..e5776899e4942 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.ts @@ -86,7 +86,7 @@ export const mlExecutor = async ({ ml, // Using fake KibanaRequest as it is needed to satisfy the ML Services API, but can be empty as it is // currently unused by the mlAnomalySearch function. - request: ({} as unknown) as KibanaRequest, + request: {} as unknown as KibanaRequest, savedObjectsClient: services.savedObjectsClient, jobIds: ruleParams.machineLearningJobId, anomalyThreshold: ruleParams.anomalyThreshold, @@ -107,28 +107,25 @@ export const mlExecutor = async ({ if (anomalyCount) { logger.info(buildRuleMessage(`Found ${anomalyCount} signals from ML anomalies.`)); } - const { - success, - errors, - bulkCreateDuration, - createdItemsCount, - createdItems, - } = await bulkCreateMlSignals({ - someResult: filteredAnomalyResults, - ruleSO: rule, - services, - logger, - id: rule.id, - signalsIndex: ruleParams.outputIndex, - buildRuleMessage, - bulkCreate, - wrapHits, - }); + const { success, errors, bulkCreateDuration, createdItemsCount, createdItems } = + await bulkCreateMlSignals({ + someResult: filteredAnomalyResults, + ruleSO: rule, + services, + logger, + id: rule.id, + signalsIndex: ruleParams.outputIndex, + buildRuleMessage, + bulkCreate, + wrapHits, + }); // The legacy ES client does not define failures when it can be present on the structure, hence why I have the & { failures: [] } const shardFailures = - (filteredAnomalyResults._shards as typeof filteredAnomalyResults._shards & { - failures: []; - }).failures ?? []; + ( + filteredAnomalyResults._shards as typeof filteredAnomalyResults._shards & { + failures: []; + } + ).failures ?? []; const searchErrors = createErrorsFromShard({ errors: shardFailures, }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.ts index 524bc6a6c524c..02cad1e8e508c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.ts @@ -147,26 +147,21 @@ export const thresholdExecutor = async ({ buildRuleMessage, }); - const { - success, - bulkCreateDuration, - createdItemsCount, - createdItems, - errors, - } = await bulkCreateThresholdSignals({ - someResult: thresholdResults, - ruleSO: rule, - filter: esFilter, - services, - logger, - inputIndexPattern: inputIndex, - signalsIndex: ruleParams.outputIndex, - startedAt, - from: tuple.from.toDate(), - signalHistory, - bulkCreate, - wrapHits, - }); + const { success, bulkCreateDuration, createdItemsCount, createdItems, errors } = + await bulkCreateThresholdSignals({ + someResult: thresholdResults, + ruleSO: rule, + filter: esFilter, + services, + logger, + inputIndexPattern: inputIndex, + signalsIndex: ruleParams.outputIndex, + startedAt, + from: tuple.from.toDate(), + signalHistory, + bulkCreate, + wrapHits, + }); result = mergeReturns([ result, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filters/create_field_and_set_tuples.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filters/create_field_and_set_tuples.test.ts index b46237cc93bc8..89820bf504c37 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filters/create_field_and_set_tuples.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filters/create_field_and_set_tuples.test.ts @@ -284,16 +284,14 @@ describe('filterEventsAgainstList', () => { }, }, ]; - const [ - { matchedSet: matchedSet1 }, - { matchedSet: matchedSet2 }, - ] = await createFieldAndSetTuples({ - listClient, - logger: mockLogger, - events, - exceptionItem, - buildRuleMessage, - }); + const [{ matchedSet: matchedSet1 }, { matchedSet: matchedSet2 }] = + await createFieldAndSetTuples({ + listClient, + logger: mockLogger, + events, + exceptionItem, + buildRuleMessage, + }); expect([...matchedSet1]).toEqual([JSON.stringify(['1.1.1.1']), JSON.stringify(['2.2.2.2'])]); expect([...matchedSet2]).toEqual([JSON.stringify(['3.3.3.3']), JSON.stringify(['5.5.5.5'])]); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filters/filter_events_against_list.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filters/filter_events_against_list.test.ts index 5b2f3426cd8aa..9e118eaada4b5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filters/filter_events_against_list.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filters/filter_events_against_list.test.ts @@ -59,7 +59,7 @@ describe('filterEventsAgainstList', () => { buildRuleMessage, }); expect(res.hits.hits.length).toEqual(4); - expect(((mockLogger.debug as unknown) as jest.Mock).mock.calls[0][0]).toContain( + expect((mockLogger.debug as unknown as jest.Mock).mock.calls[0][0]).toContain( 'no exception items of type list found - returning original search result' ); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_messages.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_messages.ts index fe9528c7a2da4..5f30220e71402 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_messages.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_messages.ts @@ -13,16 +13,13 @@ export interface BuildRuleMessageFactoryParams { index: string; } -export const buildRuleMessageFactory = ({ - id, - ruleId, - index, - name, -}: BuildRuleMessageFactoryParams): BuildRuleMessage => (...messages) => - [ - ...messages, - `name: "${name}"`, - `id: "${id}"`, - `rule id: "${ruleId ?? '(unknown rule id)'}"`, - `signals index: "${index}"`, - ].join(' '); +export const buildRuleMessageFactory = + ({ id, ruleId, index, name }: BuildRuleMessageFactoryParams): BuildRuleMessage => + (...messages) => + [ + ...messages, + `name: "${name}"`, + `id: "${id}"`, + `rule id: "${ruleId ?? '(unknown rule id)'}"`, + `signals index: "${index}"`, + ].join(' '); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_exceptions_list.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_exceptions_list.test.ts index 75180c6eaee48..e38d1aa774094 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_exceptions_list.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_exceptions_list.test.ts @@ -37,7 +37,7 @@ describe('extract_exceptions_list', () => { test('logs expect error message if the exceptionsList is undefined', () => { extractExceptionsList({ logger, - exceptionsList: (undefined as unknown) as RuleParams['exceptionsList'], + exceptionsList: undefined as unknown as RuleParams['exceptionsList'], }); expect(logger.error).toBeCalledWith( 'Exception list is null when it never should be. This indicates potentially that saved object migrations did not run correctly. Returning empty saved object reference' diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_exceptions_list.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_exceptions_list.test.ts index 72373eebf4fba..f0ff1b6072479 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_exceptions_list.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_exceptions_list.test.ts @@ -48,7 +48,7 @@ describe('inject_exceptions_list', () => { test('logs expect error message if the exceptionsList is undefined', () => { injectExceptionsReferences({ logger, - exceptionsList: (undefined as unknown) as RuleParams['exceptionsList'], + exceptionsList: undefined as unknown as RuleParams['exceptionsList'], savedObjectReferences: mockSavedObjectReferences(), }); expect(logger.error).toBeCalledWith( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts index 55a184a1c0bcc..1f46654e855b2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts @@ -994,29 +994,25 @@ describe('searchAfterAndBulkCreate', () => { sampleDocSearchResultsNoSortIdNoHits() ) ); - const { - success, - createdSignalsCount, - lastLookBackDate, - errors, - } = await searchAfterAndBulkCreate({ - ruleSO, - tuple, - listClient, - exceptionsList: [], - services: mockService, - logger: mockLogger, - eventsTelemetry: undefined, - id: sampleRuleGuid, - inputIndexPattern, - signalsIndex: DEFAULT_SIGNALS_INDEX, - pageSize: 1, - filter: undefined, - buildReasonMessage, - buildRuleMessage, - bulkCreate, - wrapHits, - }); + const { success, createdSignalsCount, lastLookBackDate, errors } = + await searchAfterAndBulkCreate({ + ruleSO, + tuple, + listClient, + exceptionsList: [], + services: mockService, + logger: mockLogger, + eventsTelemetry: undefined, + id: sampleRuleGuid, + inputIndexPattern, + signalsIndex: DEFAULT_SIGNALS_INDEX, + pageSize: 1, + filter: undefined, + buildReasonMessage, + buildRuleMessage, + bulkCreate, + wrapHits, + }); expect(success).toEqual(false); expect(errors).toEqual(['error on creation']); expect(mockService.scopedClusterClient.asCurrentUser.search).toHaveBeenCalledTimes(5); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.ts index 7b30f70701e8c..2b1d27fc2fcd0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.ts @@ -69,11 +69,10 @@ export const singleSearchAfter = async ({ }); const start = performance.now(); - const { - body: nextSearchAfterResult, - } = await services.scopedClusterClient.asCurrentUser.search( - searchAfterQuery as estypes.SearchRequest - ); + const { body: nextSearchAfterResult } = + await services.scopedClusterClient.asCurrentUser.search( + searchAfterQuery as estypes.SearchRequest + ); const end = performance.now(); const searchErrors = createErrorsFromShard({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/get_threshold_bucket_filters.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/get_threshold_bucket_filters.ts index c4569ef9818d9..5cafff24c544b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/get_threshold_bucket_filters.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/get_threshold_bucket_filters.ts @@ -54,10 +54,10 @@ export const getThresholdBucketFilters = async ({ ); return [ - ({ + { bool: { must_not: filters, }, - } as unknown) as Filter, + } as unknown as Filter, ]; }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts index 40c64bf19f0a1..c3e95d6d196ca 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts @@ -683,14 +683,14 @@ describe('utils', () => { describe('#getExceptions', () => { test('it successfully returns array of exception list items', async () => { listMock.getExceptionListClient = () => - (({ + ({ findExceptionListsItem: jest.fn().mockResolvedValue({ data: [getExceptionListItemSchemaMock()], page: 1, per_page: 10000, total: 1, }), - } as unknown) as ExceptionListClient); + } as unknown as ExceptionListClient); const client = listMock.getExceptionListClient(); const exceptions = await getExceptions({ client, @@ -712,9 +712,9 @@ describe('utils', () => { test('it throws if "getExceptionListClient" fails', async () => { const err = new Error('error fetching list'); listMock.getExceptionListClient = () => - (({ + ({ getExceptionList: jest.fn().mockRejectedValue(err), - } as unknown) as ExceptionListClient); + } as unknown as ExceptionListClient); await expect(() => getExceptions({ @@ -727,9 +727,9 @@ describe('utils', () => { test('it throws if "findExceptionListsItem" fails', async () => { const err = new Error('error fetching list'); listMock.getExceptionListClient = () => - (({ + ({ findExceptionListsItem: jest.fn().mockRejectedValue(err), - } as unknown) as ExceptionListClient); + } as unknown as ExceptionListClient); await expect(() => getExceptions({ @@ -741,9 +741,9 @@ describe('utils', () => { test('it returns empty array if "findExceptionListsItem" returns null', async () => { listMock.getExceptionListClient = () => - (({ + ({ findExceptionListsItem: jest.fn().mockResolvedValue(null), - } as unknown) as ExceptionListClient); + } as unknown as ExceptionListClient); const exceptions = await getExceptions({ client: listMock.getExceptionListClient(), diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/wrap_hits_factory.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/wrap_hits_factory.ts index 27220d80ebd6e..6f040465389fc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/wrap_hits_factory.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/wrap_hits_factory.ts @@ -11,29 +11,31 @@ import { buildBulkBody } from './build_bulk_body'; import { filterDuplicateSignals } from './filter_duplicate_signals'; import type { ConfigType } from '../../../config'; -export const wrapHitsFactory = ({ - ruleSO, - signalsIndex, - mergeStrategy, - ignoreFields, -}: { - ruleSO: SearchAfterAndBulkCreateParams['ruleSO']; - signalsIndex: string; - mergeStrategy: ConfigType['alertMergeStrategy']; - ignoreFields: ConfigType['alertIgnoreFields']; -}): WrapHits => (events, buildReasonMessage) => { - const wrappedDocs: WrappedSignalHit[] = events.flatMap((doc) => [ - { - _index: signalsIndex, - _id: generateId( - doc._index, - doc._id, - String(doc._version), - ruleSO.attributes.params.ruleId ?? '' - ), - _source: buildBulkBody(ruleSO, doc, mergeStrategy, ignoreFields, buildReasonMessage), - }, - ]); +export const wrapHitsFactory = + ({ + ruleSO, + signalsIndex, + mergeStrategy, + ignoreFields, + }: { + ruleSO: SearchAfterAndBulkCreateParams['ruleSO']; + signalsIndex: string; + mergeStrategy: ConfigType['alertMergeStrategy']; + ignoreFields: ConfigType['alertIgnoreFields']; + }): WrapHits => + (events, buildReasonMessage) => { + const wrappedDocs: WrappedSignalHit[] = events.flatMap((doc) => [ + { + _index: signalsIndex, + _id: generateId( + doc._index, + doc._id, + String(doc._version), + ruleSO.attributes.params.ruleId ?? '' + ), + _source: buildBulkBody(ruleSO, doc, mergeStrategy, ignoreFields, buildReasonMessage), + }, + ]); - return filterDuplicateSignals(ruleSO.id, wrappedDocs, false); -}; + return filterDuplicateSignals(ruleSO.id, wrappedDocs, false); + }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/wrap_sequences_factory.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/wrap_sequences_factory.ts index d4a4c55db1d23..f62992f550787 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/wrap_sequences_factory.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/wrap_sequences_factory.ts @@ -9,28 +9,30 @@ import { SearchAfterAndBulkCreateParams, WrappedSignalHit, WrapSequences } from import { buildSignalGroupFromSequence } from './build_bulk_body'; import { ConfigType } from '../../../config'; -export const wrapSequencesFactory = ({ - ruleSO, - signalsIndex, - mergeStrategy, - ignoreFields, -}: { - ruleSO: SearchAfterAndBulkCreateParams['ruleSO']; - signalsIndex: string; - mergeStrategy: ConfigType['alertMergeStrategy']; - ignoreFields: ConfigType['alertIgnoreFields']; -}): WrapSequences => (sequences, buildReasonMessage) => - sequences.reduce( - (acc: WrappedSignalHit[], sequence) => [ - ...acc, - ...buildSignalGroupFromSequence( - sequence, - ruleSO, - signalsIndex, - mergeStrategy, - ignoreFields, - buildReasonMessage - ), - ], - [] - ); +export const wrapSequencesFactory = + ({ + ruleSO, + signalsIndex, + mergeStrategy, + ignoreFields, + }: { + ruleSO: SearchAfterAndBulkCreateParams['ruleSO']; + signalsIndex: string; + mergeStrategy: ConfigType['alertMergeStrategy']; + ignoreFields: ConfigType['alertIgnoreFields']; + }): WrapSequences => + (sequences, buildReasonMessage) => + sequences.reduce( + (acc: WrappedSignalHit[], sequence) => [ + ...acc, + ...buildSignalGroupFromSequence( + sequence, + ruleSO, + signalsIndex, + mergeStrategy, + ignoreFields, + buildReasonMessage + ), + ], + [] + ); diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/constants.ts b/x-pack/plugins/security_solution/server/lib/telemetry/constants.ts index 1c03e52c67ae7..771e3e059c336 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/constants.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/constants.ts @@ -11,6 +11,8 @@ export const TELEMETRY_CHANNEL_LISTS = 'security-lists'; export const TELEMETRY_CHANNEL_ENDPOINT_META = 'endpoint-metadata'; +export const LIST_TRUSTED_APPLICATION = 'trusted_application'; + export const LIST_ENDPOINT_EXCEPTION = 'endpoint_exception'; export const LIST_ENDPOINT_EVENT_FILTER = 'endpoint_event_filter'; diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/filters.test.ts b/x-pack/plugins/security_solution/server/lib/telemetry/filters.test.ts new file mode 100644 index 0000000000000..4844a10d99f90 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/telemetry/filters.test.ts @@ -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 { copyAllowlistedFields } from './filters'; + +describe('Security Telemetry filters', () => { + describe('allowlistEventFields', () => { + const allowlist = { + a: true, + b: true, + c: { + d: true, + }, + }; + + it('filters top level', () => { + const event = { + a: 'a', + a1: 'a1', + b: 'b', + b1: 'b1', + }; + expect(copyAllowlistedFields(allowlist, event)).toStrictEqual({ + a: 'a', + b: 'b', + }); + }); + + it('filters nested', () => { + const event = { + a: { + a1: 'a1', + }, + a1: 'a1', + b: { + b1: 'b1', + }, + b1: 'b1', + c: { + d: 'd', + e: 'e', + f: 'f', + }, + }; + expect(copyAllowlistedFields(allowlist, event)).toStrictEqual({ + a: { + a1: 'a1', + }, + b: { + b1: 'b1', + }, + c: { + d: 'd', + }, + }); + }); + + it('filters arrays of objects', () => { + const event = { + a: [ + { + a1: 'a1', + }, + ], + b: { + b1: 'b1', + }, + c: [ + { + d: 'd1', + e: 'e1', + f: 'f1', + }, + { + d: 'd2', + e: 'e2', + f: 'f2', + }, + { + d: 'd3', + e: 'e3', + f: 'f3', + }, + ], + }; + expect(copyAllowlistedFields(allowlist, event)).toStrictEqual({ + a: [ + { + a1: 'a1', + }, + ], + b: { + b1: 'b1', + }, + c: [ + { + d: 'd1', + }, + { + d: 'd2', + }, + { + d: 'd3', + }, + ], + }); + }); + + it("doesn't create empty objects", () => { + const event = { + a: 'a', + b: 'b', + c: { + e: 'e', + }, + }; + expect(copyAllowlistedFields(allowlist, event)).toStrictEqual({ + a: 'a', + b: 'b', + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/filters.ts b/x-pack/plugins/security_solution/server/lib/telemetry/filters.ts index 18c3baccf9aa0..61172fac511f7 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/filters.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/filters.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { TelemetryEvent } from './types'; + export interface AllowlistFields { [key: string]: boolean | AllowlistFields; } @@ -124,3 +126,48 @@ export const allowlistEventFields: AllowlistFields = { }, ...allowlistBaseEventFields, }; + +export const exceptionListEventFields: AllowlistFields = { + created_at: true, + description: true, + effectScope: true, + entries: true, + id: true, + name: true, + os: true, + os_types: true, +}; + +/** + * Filters out information not required for downstream analysis + * + * @param allowlist + * @param event + * @returns + */ +export function copyAllowlistedFields( + allowlist: AllowlistFields, + event: TelemetryEvent +): TelemetryEvent { + return Object.entries(allowlist).reduce((newEvent, [allowKey, allowValue]) => { + const eventValue = event[allowKey]; + if (eventValue !== null && eventValue !== undefined) { + if (allowValue === true) { + return { ...newEvent, [allowKey]: eventValue }; + } else if (typeof allowValue === 'object' && Array.isArray(eventValue)) { + const subValues = eventValue.filter((v) => typeof v === 'object'); + return { + ...newEvent, + [allowKey]: subValues.map((v) => copyAllowlistedFields(allowValue, v as TelemetryEvent)), + }; + } else if (typeof allowValue === 'object' && typeof eventValue === 'object') { + const values = copyAllowlistedFields(allowValue, eventValue as TelemetryEvent); + return { + ...newEvent, + ...(Object.keys(values).length > 0 ? { [allowKey]: values } : {}), + }; + } + } + return newEvent; + }, {}); +} diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/helpers.test.ts b/x-pack/plugins/security_solution/server/lib/telemetry/helpers.test.ts index a4d11b71f2a8e..647219e8c5585 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/helpers.test.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/helpers.test.ts @@ -7,17 +7,19 @@ import moment from 'moment'; import { createMockPackagePolicy } from './mocks'; -import { TrustedApp } from '../../../common/endpoint/types'; -import { LIST_ENDPOINT_EXCEPTION, LIST_ENDPOINT_EVENT_FILTER } from './constants'; +import { + LIST_ENDPOINT_EXCEPTION, + LIST_ENDPOINT_EVENT_FILTER, + LIST_TRUSTED_APPLICATION, +} from './constants'; import { getPreviousDiagTaskTimestamp, getPreviousEpMetaTaskTimestamp, batchTelemetryRecords, isPackagePolicyList, - templateTrustedApps, - templateEndpointExceptions, + templateExceptionList, } from './helpers'; -import { EndpointExceptionListItem } from './types'; +import { ExceptionListItem } from './types'; describe('test diagnostic telemetry scheduled task timing helper', () => { test('test -5 mins is returned when there is no previous task run', async () => { @@ -133,8 +135,8 @@ describe('test package policy type guard', () => { describe('list telemetry schema', () => { test('trusted apps document is correctly formed', () => { - const data = [{ id: 'test_1' }] as TrustedApp[]; - const templatedItems = templateTrustedApps(data); + const data = [{ id: 'test_1' }] as ExceptionListItem[]; + const templatedItems = templateExceptionList(data, LIST_TRUSTED_APPLICATION); expect(templatedItems[0]?.trusted_application.length).toEqual(1); expect(templatedItems[0]?.endpoint_exception.length).toEqual(0); @@ -142,8 +144,8 @@ describe('list telemetry schema', () => { }); test('trusted apps document is correctly formed with multiple entries', () => { - const data = [{ id: 'test_2' }, { id: 'test_2' }] as TrustedApp[]; - const templatedItems = templateTrustedApps(data); + const data = [{ id: 'test_2' }, { id: 'test_2' }] as ExceptionListItem[]; + const templatedItems = templateExceptionList(data, LIST_TRUSTED_APPLICATION); expect(templatedItems[0]?.trusted_application.length).toEqual(1); expect(templatedItems[1]?.trusted_application.length).toEqual(1); @@ -152,8 +154,8 @@ describe('list telemetry schema', () => { }); test('endpoint exception document is correctly formed', () => { - const data = [{ id: 'test_3' }] as EndpointExceptionListItem[]; - const templatedItems = templateEndpointExceptions(data, LIST_ENDPOINT_EXCEPTION); + const data = [{ id: 'test_3' }] as ExceptionListItem[]; + const templatedItems = templateExceptionList(data, LIST_ENDPOINT_EXCEPTION); expect(templatedItems[0]?.trusted_application.length).toEqual(0); expect(templatedItems[0]?.endpoint_exception.length).toEqual(1); @@ -161,12 +163,8 @@ describe('list telemetry schema', () => { }); test('endpoint exception document is correctly formed with multiple entries', () => { - const data = [ - { id: 'test_4' }, - { id: 'test_4' }, - { id: 'test_4' }, - ] as EndpointExceptionListItem[]; - const templatedItems = templateEndpointExceptions(data, LIST_ENDPOINT_EXCEPTION); + const data = [{ id: 'test_4' }, { id: 'test_4' }, { id: 'test_4' }] as ExceptionListItem[]; + const templatedItems = templateExceptionList(data, LIST_ENDPOINT_EXCEPTION); expect(templatedItems[0]?.trusted_application.length).toEqual(0); expect(templatedItems[0]?.endpoint_exception.length).toEqual(1); @@ -176,8 +174,8 @@ describe('list telemetry schema', () => { }); test('endpoint event filters document is correctly formed', () => { - const data = [{ id: 'test_5' }] as EndpointExceptionListItem[]; - const templatedItems = templateEndpointExceptions(data, LIST_ENDPOINT_EVENT_FILTER); + const data = [{ id: 'test_5' }] as ExceptionListItem[]; + const templatedItems = templateExceptionList(data, LIST_ENDPOINT_EVENT_FILTER); expect(templatedItems[0]?.trusted_application.length).toEqual(0); expect(templatedItems[0]?.endpoint_exception.length).toEqual(0); @@ -185,8 +183,8 @@ describe('list telemetry schema', () => { }); test('endpoint event filters document is correctly formed with multiple entries', () => { - const data = [{ id: 'test_6' }, { id: 'test_6' }] as EndpointExceptionListItem[]; - const templatedItems = templateEndpointExceptions(data, LIST_ENDPOINT_EVENT_FILTER); + const data = [{ id: 'test_6' }, { id: 'test_6' }] as ExceptionListItem[]; + const templatedItems = templateExceptionList(data, LIST_ENDPOINT_EVENT_FILTER); expect(templatedItems[0]?.trusted_application.length).toEqual(0); expect(templatedItems[0]?.endpoint_exception.length).toEqual(0); diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts b/x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts index bb2cc4f42ca90..a9eaef3ce6edc 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts @@ -7,10 +7,15 @@ import moment from 'moment'; import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; -import { TrustedApp } from '../../../common/endpoint/types'; import { PackagePolicy } from '../../../../fleet/common/types/models/package_policy'; -import { EndpointExceptionListItem, ListTemplate } from './types'; -import { LIST_ENDPOINT_EXCEPTION, LIST_ENDPOINT_EVENT_FILTER } from './constants'; +import { copyAllowlistedFields, exceptionListEventFields } from './filters'; +import { ExceptionListItem, ListTemplate, TelemetryEvent } from './types'; +import { + LIST_ENDPOINT_EXCEPTION, + LIST_ENDPOINT_EVENT_FILTER, + LIST_TRUSTED_APPLICATION, +} from './constants'; +import { TrustedApp } from '../../../common/endpoint/types'; /** * Determines the when the last run was in order to execute to. @@ -89,43 +94,41 @@ export function isPackagePolicyList( } /** - * Maps Exception list item to parsable object + * Maps trusted application to shared telemetry object * * @param exceptionListItem * @returns collection of endpoint exceptions */ -export const exceptionListItemToEndpointEntry = (exceptionListItem: ExceptionListItemSchema) => { +export const trustedApplicationToTelemetryEntry = (trustedApplication: TrustedApp) => { + return { + id: trustedApplication.id, + version: trustedApplication.version || '', + name: trustedApplication.name, + description: trustedApplication.description, + created_at: trustedApplication.created_at, + updated_at: trustedApplication.updated_at, + entries: trustedApplication.entries, + os: trustedApplication.os, + } as ExceptionListItem; +}; + +/** + * Maps endpoint lists to shared telemetry object + * + * @param exceptionListItem + * @returns collection of endpoint exceptions + */ +export const exceptionListItemToTelemetryEntry = (exceptionListItem: ExceptionListItemSchema) => { return { id: exceptionListItem.id, version: exceptionListItem._version || '', name: exceptionListItem.name, description: exceptionListItem.description, created_at: exceptionListItem.created_at, - created_by: exceptionListItem.created_by, updated_at: exceptionListItem.updated_at, - updated_by: exceptionListItem.updated_by, entries: exceptionListItem.entries, os_types: exceptionListItem.os_types, - } as EndpointExceptionListItem; -}; - -/** - * Constructs the lists telemetry schema from a collection of Trusted Apps - * - * @param listData - * @returns lists telemetry schema - */ -export const templateTrustedApps = (listData: TrustedApp[]) => { - return listData.map((item) => { - const template: ListTemplate = { - trusted_application: [], - endpoint_exception: [], - endpoint_event_filter: [], - }; - - template.trusted_application.push(item); - return template; - }); + } as ExceptionListItem; }; /** @@ -135,10 +138,7 @@ export const templateTrustedApps = (listData: TrustedApp[]) => { * @param listType * @returns lists telemetry schema */ -export const templateEndpointExceptions = ( - listData: EndpointExceptionListItem[], - listType: string -) => { +export const templateExceptionList = (listData: ExceptionListItem[], listType: string) => { return listData.map((item) => { const template: ListTemplate = { trusted_application: [], @@ -146,13 +146,24 @@ export const templateEndpointExceptions = ( endpoint_event_filter: [], }; + // cast exception list type to a TelemetryEvent for allowlist filtering + const filteredListItem = copyAllowlistedFields( + exceptionListEventFields, + item as unknown as TelemetryEvent + ); + + if (listType === LIST_TRUSTED_APPLICATION) { + template.trusted_application.push(filteredListItem); + return template; + } + if (listType === LIST_ENDPOINT_EXCEPTION) { - template.endpoint_exception.push(item); + template.endpoint_exception.push(filteredListItem); return template; } if (listType === LIST_ENDPOINT_EVENT_FILTER) { - template.endpoint_event_filter.push(item); + template.endpoint_event_filter.push(filteredListItem); return template; } diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/mocks.ts b/x-pack/plugins/security_solution/server/lib/telemetry/mocks.ts index 31903d5dafe93..20a71657b2ffe 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/mocks.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/mocks.ts @@ -17,7 +17,7 @@ import { PackagePolicy } from '../../../../fleet/common/types/models/package_pol export const createMockTelemetryEventsSender = ( enableTelemtry: boolean ): jest.Mocked => { - return ({ + return { setup: jest.fn(), start: jest.fn(), stop: jest.fn(), @@ -27,11 +27,11 @@ export const createMockTelemetryEventsSender = ( isTelemetryOptedIn: jest.fn().mockReturnValue(enableTelemtry ?? jest.fn()), sendIfDue: jest.fn(), sendEvents: jest.fn(), - } as unknown) as jest.Mocked; + } as unknown as jest.Mocked; }; export const createMockTelemetryReceiver = (): jest.Mocked => { - return ({ + return { start: jest.fn(), fetchClusterInfo: jest.fn(), fetchLicenseInfo: jest.fn(), @@ -40,14 +40,14 @@ export const createMockTelemetryReceiver = (): jest.Mocked => fetchEndpointMetrics: jest.fn(), fetchEndpointPolicyResponses: jest.fn(), fetchTrustedApplications: jest.fn(), - } as unknown) as jest.Mocked; + } as unknown as jest.Mocked; }; /** * Creates a mocked package policy */ export const createMockPackagePolicy = (): jest.Mocked => { - return ({ + return { id: jest.fn(), inputs: jest.fn(), version: jest.fn(), @@ -56,7 +56,7 @@ export const createMockPackagePolicy = (): jest.Mocked => { updated_by: jest.fn(), created_at: jest.fn(), created_by: jest.fn(), - } as unknown) as jest.Mocked; + } as unknown as jest.Mocked; }; /** diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/receiver.ts b/x-pack/plugins/security_solution/server/lib/telemetry/receiver.ts index dbdf8d6fe61ad..8b715b8e8d585 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/receiver.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/receiver.ts @@ -17,7 +17,7 @@ import { AgentService, AgentPolicyServiceInterface } from '../../../../fleet/ser import { ExceptionListClient } from '../../../../lists/server'; import { EndpointAppContextService } from '../../endpoint/endpoint_app_context_services'; import { TELEMETRY_MAX_BUFFER_SIZE } from './constants'; -import { exceptionListItemToEndpointEntry } from './helpers'; +import { exceptionListItemToTelemetryEntry, trustedApplicationToTelemetryEntry } from './helpers'; import { TelemetryEvent, ESLicense, ESClusterInfo, GetEndpointListResponse } from './types'; export class TelemetryReceiver { @@ -42,7 +42,8 @@ export class TelemetryReceiver { this.agentPolicyService = endpointContextService?.getAgentPolicyService(); this.esClient = core?.elasticsearch.client.asInternalUser; this.exceptionListClient = exceptionListClient; - this.soClient = (core?.savedObjects.createInternalRepository() as unknown) as SavedObjectsClientContract; + this.soClient = + core?.savedObjects.createInternalRepository() as unknown as SavedObjectsClientContract; } public async fetchFleetAgents() { @@ -201,7 +202,16 @@ export class TelemetryReceiver { throw Error('exception list client is unavailable: cannot retrieve trusted applications'); } - return getTrustedAppsList(this.exceptionListClient, { page: 1, per_page: 10_000 }); + const results = await getTrustedAppsList(this.exceptionListClient, { + page: 1, + per_page: 10_000, + }); + return { + data: results?.data.map(trustedApplicationToTelemetryEntry), + total: results?.total ?? 0, + page: results?.page ?? 1, + per_page: results?.per_page ?? this.max_records, + }; } public async fetchEndpointList(listId: string): Promise { @@ -223,7 +233,7 @@ export class TelemetryReceiver { }); return { - data: results?.data.map(exceptionListItemToEndpointEntry) ?? [], + data: results?.data.map(exceptionListItemToTelemetryEntry) ?? [], total: results?.total ?? 0, page: results?.page ?? 1, per_page: results?.per_page ?? this.max_records, diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/sender.test.ts b/x-pack/plugins/security_solution/server/lib/telemetry/sender.test.ts index d04d0ab49afe9..21e6b2cf6d9c4 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/sender.test.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/sender.test.ts @@ -6,7 +6,7 @@ */ /* eslint-disable dot-notation */ -import { TelemetryEventsSender, copyAllowlistedFields } from './sender'; +import { TelemetryEventsSender } from './sender'; import { loggingSystemMock } from 'src/core/server/mocks'; import { usageCountersServiceMock } from 'src/plugins/usage_collection/server/usage_counters/usage_counters_service.mock'; import { URL } from 'url'; @@ -220,123 +220,6 @@ describe('TelemetryEventsSender', () => { }); }); -describe('allowlistEventFields', () => { - const allowlist = { - a: true, - b: true, - c: { - d: true, - }, - }; - - it('filters top level', () => { - const event = { - a: 'a', - a1: 'a1', - b: 'b', - b1: 'b1', - }; - expect(copyAllowlistedFields(allowlist, event)).toStrictEqual({ - a: 'a', - b: 'b', - }); - }); - - it('filters nested', () => { - const event = { - a: { - a1: 'a1', - }, - a1: 'a1', - b: { - b1: 'b1', - }, - b1: 'b1', - c: { - d: 'd', - e: 'e', - f: 'f', - }, - }; - expect(copyAllowlistedFields(allowlist, event)).toStrictEqual({ - a: { - a1: 'a1', - }, - b: { - b1: 'b1', - }, - c: { - d: 'd', - }, - }); - }); - - it('filters arrays of objects', () => { - const event = { - a: [ - { - a1: 'a1', - }, - ], - b: { - b1: 'b1', - }, - c: [ - { - d: 'd1', - e: 'e1', - f: 'f1', - }, - { - d: 'd2', - e: 'e2', - f: 'f2', - }, - { - d: 'd3', - e: 'e3', - f: 'f3', - }, - ], - }; - expect(copyAllowlistedFields(allowlist, event)).toStrictEqual({ - a: [ - { - a1: 'a1', - }, - ], - b: { - b1: 'b1', - }, - c: [ - { - d: 'd1', - }, - { - d: 'd2', - }, - { - d: 'd3', - }, - ], - }); - }); - - it("doesn't create empty objects", () => { - const event = { - a: 'a', - b: 'b', - c: { - e: 'e', - }, - }; - expect(copyAllowlistedFields(allowlist, event)).toStrictEqual({ - a: 'a', - b: 'b', - }); - }); -}); - describe('getV3UrlFromV2', () => { let logger: ReturnType; diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts b/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts index 2e615a2681174..0037aaa28fee3 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts @@ -17,7 +17,7 @@ import { TaskManagerStartContract, } from '../../../../task_manager/server'; import { TelemetryReceiver } from './receiver'; -import { AllowlistFields, allowlistEventFields } from './filters'; +import { allowlistEventFields, copyAllowlistedFields } from './filters'; import { DiagnosticTask, EndpointTask, ExceptionListsTask } from './tasks'; import { createUsageCounterLabel } from './helpers'; import { TelemetryEvent } from './types'; @@ -194,8 +194,8 @@ export class TelemetryEventsSender { /** * This function sends events to the elastic telemetry channel. Caution is required - * because it does no allowlist filtering. The caller is responsible for making sure - * that there is no sensitive material or PII in the records that are sent upstream. + * because it does no allowlist filtering at send time. The function call site is + * responsible for ensuring sure no sensitive material is in telemetry events. * * @param channel the elastic telemetry channel * @param toSend telemetry events @@ -294,30 +294,3 @@ export class TelemetryEventsSender { } } } - -export function copyAllowlistedFields( - allowlist: AllowlistFields, - event: TelemetryEvent -): TelemetryEvent { - return Object.entries(allowlist).reduce((newEvent, [allowKey, allowValue]) => { - const eventValue = event[allowKey]; - if (eventValue !== null && eventValue !== undefined) { - if (allowValue === true) { - return { ...newEvent, [allowKey]: eventValue }; - } else if (typeof allowValue === 'object' && Array.isArray(eventValue)) { - const subValues = eventValue.filter((v) => typeof v === 'object'); - return { - ...newEvent, - [allowKey]: subValues.map((v) => copyAllowlistedFields(allowValue, v as TelemetryEvent)), - }; - } else if (typeof allowValue === 'object' && typeof eventValue === 'object') { - const values = copyAllowlistedFields(allowValue, eventValue as TelemetryEvent); - return { - ...newEvent, - ...(Object.keys(values).length > 0 ? { [allowKey]: values } : {}), - }; - } - } - return newEvent; - }, {}); -} diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/endpoint.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/endpoint.ts index 67ca21b28a698..2cebbc0af69c1 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/endpoint.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/endpoint.ts @@ -167,7 +167,7 @@ export class TelemetryEndpointTask { return 0; } - const { body: endpointMetricsResponse } = (endpointData.endpointMetrics as unknown) as { + const { body: endpointMetricsResponse } = endpointData.endpointMetrics as unknown as { body: EndpointMetricsAggregation; }; @@ -250,7 +250,7 @@ export class TelemetryEndpointTask { * the last 24h or no failures/warnings in the policy applied. * */ - const { body: failedPolicyResponses } = (endpointData.epPolicyResponse as unknown) as { + const { body: failedPolicyResponses } = endpointData.epPolicyResponse as unknown as { body: EndpointPolicyResponseAggregation; }; diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/security_lists.ts b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/security_lists.ts index b54858e1f5f42..fe2039419b02d 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/tasks/security_lists.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/tasks/security_lists.ts @@ -19,9 +19,10 @@ import { import { LIST_ENDPOINT_EXCEPTION, LIST_ENDPOINT_EVENT_FILTER, + LIST_TRUSTED_APPLICATION, TELEMETRY_CHANNEL_LISTS, } from '../constants'; -import { batchTelemetryRecords, templateEndpointExceptions, templateTrustedApps } from '../helpers'; +import { batchTelemetryRecords, templateExceptionList } from '../helpers'; import { TelemetryEventsSender } from '../sender'; import { TelemetryReceiver } from '../receiver'; @@ -110,7 +111,7 @@ export class TelemetryExceptionListsTask { // Lists Telemetry: Trusted Applications const trustedApps = await this.receiver.fetchTrustedApplications(); - const trustedAppsJson = templateTrustedApps(trustedApps.data); + const trustedAppsJson = templateExceptionList(trustedApps.data, LIST_TRUSTED_APPLICATION); this.logger.debug(`Trusted Apps: ${trustedAppsJson}`); batchTelemetryRecords(trustedAppsJson, MAX_TELEMETRY_BATCH).forEach((batch) => @@ -120,7 +121,7 @@ export class TelemetryExceptionListsTask { // Lists Telemetry: Endpoint Exceptions const epExceptions = await this.receiver.fetchEndpointList(ENDPOINT_LIST_ID); - const epExceptionsJson = templateEndpointExceptions(epExceptions.data, LIST_ENDPOINT_EXCEPTION); + const epExceptionsJson = templateExceptionList(epExceptions.data, LIST_ENDPOINT_EXCEPTION); this.logger.debug(`EP Exceptions: ${epExceptionsJson}`); batchTelemetryRecords(epExceptionsJson, MAX_TELEMETRY_BATCH).forEach((batch) => @@ -130,7 +131,7 @@ export class TelemetryExceptionListsTask { // Lists Telemetry: Endpoint Event Filters const epFilters = await this.receiver.fetchEndpointList(ENDPOINT_EVENT_FILTERS_LIST_ID); - const epFiltersJson = templateEndpointExceptions(epFilters.data, LIST_ENDPOINT_EVENT_FILTER); + const epFiltersJson = templateExceptionList(epFilters.data, LIST_ENDPOINT_EVENT_FILTER); this.logger.debug(`EP Event Filters: ${epFiltersJson}`); batchTelemetryRecords(epFiltersJson, MAX_TELEMETRY_BATCH).forEach((batch) => diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/types.ts b/x-pack/plugins/security_solution/server/lib/telemetry/types.ts index b78017314a982..abcad26ed000c 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/types.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/types.ts @@ -6,7 +6,6 @@ */ import { schema, TypeOf } from '@kbn/config-schema'; -import { TrustedApp } from '../../../common/endpoint/types'; type BaseSearchTypes = string | number | boolean | object; export type SearchTypes = BaseSearchTypes | BaseSearchTypes[] | undefined; @@ -211,26 +210,25 @@ export interface GetEndpointListResponse { per_page: number; page: number; total: number; - data: EndpointExceptionListItem[]; + data: ExceptionListItem[]; } // Telemetry List types -export interface EndpointExceptionListItem { +export interface ExceptionListItem { id: string; version: string; name: string; description: string; created_at: string; - created_by: string; updated_at: string; - updated_by: string; entries: object; + os: string; os_types: object; } export interface ListTemplate { - trusted_application: TrustedApp[]; - endpoint_exception: EndpointExceptionListItem[]; - endpoint_event_filter: EndpointExceptionListItem[]; + trusted_application: TelemetryEvent[]; + endpoint_exception: TelemetryEvent[]; + endpoint_event_filter: TelemetryEvent[]; } diff --git a/x-pack/plugins/security_solution/server/lib/timeline/constants.ts b/x-pack/plugins/security_solution/server/lib/timeline/constants.ts index 9e761a1f5c318..e38096bc2e82c 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/constants.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/constants.ts @@ -15,3 +15,8 @@ export const SAVED_QUERY_ID_REF_NAME = 'savedQueryId'; * https://github.com/elastic/kibana/blob/master/src/plugins/data/public/query/saved_query/saved_query_service.ts#L54 */ export const SAVED_QUERY_TYPE = 'query'; + +/** + * The reference name for the timeline ID field within the notes and pinned events saved object definition + */ +export const TIMELINE_ID_REF_NAME = 'timelineId'; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.test.ts index 1b230ad0446ce..81f62e0c76542 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.test.ts @@ -41,12 +41,12 @@ describe('clean draft timelines', () => { server = serverMock.create(); context = requestContextMock.createTools().context; - securitySetup = ({ + securitySetup = { authc: { getCurrentUser: jest.fn().mockReturnValue(mockGetCurrentUser), }, authz: {}, - } as unknown) as SecurityPluginSetup; + } as unknown as SecurityPluginSetup; mockGetTimeline = jest.fn(); mockGetDraftTimeline = jest.fn(); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/get_draft_timelines/index.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/get_draft_timelines/index.test.ts index 7e3e2a23222c3..ec9b4e08a673a 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/get_draft_timelines/index.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/get_draft_timelines/index.test.ts @@ -40,12 +40,12 @@ describe('get draft timelines', () => { server = serverMock.create(); context = requestContextMock.createTools().context; - securitySetup = ({ + securitySetup = { authc: { getCurrentUser: jest.fn().mockReturnValue(mockGetCurrentUser), }, authz: {}, - } as unknown) as SecurityPluginSetup; + } as unknown as SecurityPluginSetup; mockGetTimeline = jest.fn(); mockGetDraftTimeline = jest.fn(); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/persist_note.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/persist_note.ts index 32fd87f39620b..ad94f06f2d34f 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/persist_note.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/persist_note.ts @@ -42,18 +42,16 @@ export const persistNoteRoute = ( const frameworkRequest = await buildFrameworkRequest(context, security, request); const { note } = request.body; const noteId = request.body?.noteId ?? null; - const version = request.body?.version ?? null; - const res = await persistNote( - frameworkRequest, + const res = await persistNote({ + request: frameworkRequest, noteId, - version, - { + note: { ...note, timelineId: note.timelineId || null, }, - true - ); + overrideOwner: true, + }); return response.ok({ body: { data: { persistNote: res } }, diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/helpers.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/helpers.test.ts index c384c1df80ad1..4e174f23d0746 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/helpers.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/helpers.test.ts @@ -40,12 +40,12 @@ describe.each([ const mockFileName = 'prepackaged_timelines.ndjson'; beforeEach(async () => { - securitySetup = ({ + securitySetup = { authc: { getCurrentUser: jest.fn().mockReturnValue(mockGetCurrentUser), }, authz: {}, - } as unknown) as SecurityPluginSetup; + } as unknown as SecurityPluginSetup; clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled)); @@ -166,8 +166,7 @@ describe.each([ value: '3c322ed995865f642c1a269d54cbd177bd4b0e6efcf15a589f4f8582efbe7509', operator: ':', }, - id: - 'send-signal-to-timeline-action-default-draggable-event-details-value-formatted-field-value-timeline-1-signal-id-3c322ed995865f642c1a269d54cbd177bd4b0e6efcf15a589f4f8582efbe7509', + id: 'send-signal-to-timeline-action-default-draggable-event-details-value-formatted-field-value-timeline-1-signal-id-3c322ed995865f642c1a269d54cbd177bd4b0e6efcf15a589f4f8582efbe7509', enabled: true, }, ], diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/index.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/index.test.ts index 427086f3c4b6c..345987a82873f 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/index.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/index.test.ts @@ -49,12 +49,12 @@ describe('installPrepackagedTimelines', () => { server = serverMock.create(); context = requestContextMock.createTools().context; - securitySetup = ({ + securitySetup = { authc: { getCurrentUser: jest.fn().mockReturnValue(mockGetCurrentUser), }, authz: {}, - } as unknown) as SecurityPluginSetup; + } as unknown as SecurityPluginSetup; installPrepackedTimelinesRoute(server.router, createMockConfig(), securitySetup); }); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/helpers.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/helpers.test.ts index f5e5b7dfb8ae9..05f3b5373a8de 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/helpers.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/helpers.test.ts @@ -71,12 +71,12 @@ describe('createTimelines', () => { let frameworkRequest: FrameworkRequest; beforeAll(async () => { - securitySetup = ({ + securitySetup = { authc: { getCurrentUser: jest.fn(), }, authz: {}, - } as unknown) as SecurityPluginSetup; + } as unknown as SecurityPluginSetup; const { context } = requestContextMock.createTools(); const mockRequest = getCreateTimelinesRequest(createTimelineWithoutTimelineId); @@ -119,7 +119,7 @@ describe('createTimelines', () => { }); test('persistNotes', () => { - expect((persistNotes as jest.Mock).mock.calls[0][4]).toEqual([ + expect((persistNotes as jest.Mock).mock.calls[0][3]).toEqual([ { created: 1603885051655, createdBy: 'elastic', diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/helpers.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/helpers.ts index e202230bf5cce..b393c753853f5 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/helpers.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/helpers.ts @@ -54,7 +54,6 @@ export const createTimelines = async ({ isImmutable ); const newTimelineSavedObjectId = responseTimeline.timeline.savedObjectId; - const newTimelineVersion = responseTimeline.timeline.version; let myPromises: unknown[] = []; if (pinnedEventIds != null && !isEmpty(pinnedEventIds)) { @@ -73,7 +72,6 @@ export const createTimelines = async ({ persistNotes( frameworkRequest, timelineSavedObjectId ?? newTimelineSavedObjectId, - newTimelineVersion, existingNoteIds, notes, overrideNotesOwner diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/index.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/index.test.ts index 995d8549fff94..de11037db7038 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/index.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/index.test.ts @@ -51,12 +51,12 @@ describe('create timelines', () => { server = serverMock.create(); context = requestContextMock.createTools().context; - securitySetup = ({ + securitySetup = { authc: { getCurrentUser: jest.fn().mockReturnValue(mockGetCurrentUser), }, authz: {}, - } as unknown) as SecurityPluginSetup; + } as unknown as SecurityPluginSetup; mockGetTimeline = jest.fn(); mockGetTemplateTimeline = jest.fn(); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/index.ts index 432a441e61e07..d94039e3f9856 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/index.ts @@ -49,13 +49,8 @@ export const createTimelinesRoute = ( const frameworkRequest = await buildFrameworkRequest(context, security, request); const { timelineId, timeline, version } = request.body; - const { - templateTimelineId, - templateTimelineVersion, - timelineType, - title, - status, - } = timeline; + const { templateTimelineId, templateTimelineVersion, timelineType, title, status } = + timeline; const compareTimelinesStatus = new CompareTimelinesStatus({ status, title, diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/export_timelines/index.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/export_timelines/index.test.ts index da6d0059d5738..832310b57a76c 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/export_timelines/index.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/export_timelines/index.test.ts @@ -53,17 +53,17 @@ describe('export timelines', () => { beforeEach(() => { server = serverMock.create(); ({ clients, context } = requestContextMock.createTools()); - securitySetup = ({ + securitySetup = { authc: { getCurrentUser: jest.fn().mockReturnValue(mockGetCurrentUser), }, authz: {}, - } as unknown) as SecurityPluginSetup; + } as unknown as SecurityPluginSetup; clients.savedObjectsClient.bulkGet.mockResolvedValue(mockTimelinesSavedObjects()); - ((convertSavedObjectToSavedTimeline as unknown) as jest.Mock).mockReturnValue(mockTimelines()); - ((convertSavedObjectToSavedNote as unknown) as jest.Mock).mockReturnValue(mockNotes()); - ((convertSavedObjectToSavedPinnedEvent as unknown) as jest.Mock).mockReturnValue( + (convertSavedObjectToSavedTimeline as unknown as jest.Mock).mockReturnValue(mockTimelines()); + (convertSavedObjectToSavedNote as unknown as jest.Mock).mockReturnValue(mockNotes()); + (convertSavedObjectToSavedPinnedEvent as unknown as jest.Mock).mockReturnValue( mockPinnedEvents() ); exportTimelinesRoute(server.router, createMockConfig(), securitySetup); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timeline/index.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timeline/index.test.ts index 13a3a3909095a..edc0afd08c4ef 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timeline/index.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timeline/index.test.ts @@ -37,12 +37,12 @@ describe('get timeline', () => { server = serverMock.create(); context = requestContextMock.createTools().context; - securitySetup = ({ + securitySetup = { authc: { getCurrentUser: jest.fn().mockReturnValue(mockGetCurrentUser), }, authz: {}, - } as unknown) as SecurityPluginSetup; + } as unknown as SecurityPluginSetup; getTimelineRoute(server.router, createMockConfig(), securitySetup); }); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timelines/index.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timelines/index.test.ts index a29902934172f..54c9345d4ac7f 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timelines/index.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timelines/index.test.ts @@ -35,12 +35,12 @@ describe('get all timelines', () => { server = serverMock.create(); context = requestContextMock.createTools().context; - securitySetup = ({ + securitySetup = { authc: { getCurrentUser: jest.fn().mockReturnValue(mockGetCurrentUser), }, authz: {}, - } as unknown) as SecurityPluginSetup; + } as unknown as SecurityPluginSetup; getTimelinesRoute(server.router, createMockConfig(), securitySetup); }); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/create_timelines_stream_from_ndjson.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/create_timelines_stream_from_ndjson.ts index d016fe8a24ff2..9ddeee365a0a2 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/create_timelines_stream_from_ndjson.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/create_timelines_stream_from_ndjson.ts @@ -26,11 +26,10 @@ type ErrorFactory = (message: string) => Error; export const createPlainError = (message: string) => new Error(message); -export const decodeOrThrow = ( - runtimeType: rt.Type, - createError: ErrorFactory = createPlainError -) => (inputValue: I) => - pipe(runtimeType.decode(inputValue), fold(throwErrors(createError), identity)); +export const decodeOrThrow = + (runtimeType: rt.Type, createError: ErrorFactory = createPlainError) => + (inputValue: I) => + pipe(runtimeType.decode(inputValue), fold(throwErrors(createError), identity)); export const validateTimelines = (): Transform => createMapStream((obj: ImportTimelineResponse) => diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.test.ts index 2f51b23d73676..b4e2bc0222dd5 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.test.ts @@ -59,12 +59,12 @@ describe('import timelines', () => { server = serverMock.create(); context = requestContextMock.createTools().context; - securitySetup = ({ + securitySetup = { authc: { getCurrentUser: jest.fn().mockReturnValue(mockGetCurrentUser), }, authz: {}, - } as unknown) as SecurityPluginSetup; + } as unknown as SecurityPluginSetup; mockGetTimeline = jest.fn(); mockGetTemplateTimeline = jest.fn(); @@ -88,9 +88,11 @@ describe('import timelines', () => { jest.doMock('./get_timelines_from_stream', () => { return { - getTupleDuplicateErrorsAndUniqueTimeline: mockGetTupleDuplicateErrorsAndUniqueTimeline.mockReturnValue( - [mockDuplicateIdErrors, mockUniqueParsedObjects] - ), + getTupleDuplicateErrorsAndUniqueTimeline: + mockGetTupleDuplicateErrorsAndUniqueTimeline.mockReturnValue([ + mockDuplicateIdErrors, + mockUniqueParsedObjects, + ]), }; }); }); @@ -257,25 +259,13 @@ describe('import timelines', () => { test('should provide no noteSavedObjectId when Creating notes for a timeline', async () => { const mockRequest = await getImportTimelinesRequest(); await server.inject(mockRequest, context); - expect(mockPersistNote.mock.calls[0][1]).toBeNull(); - }); - - test('should provide new timeline version when Creating notes for a timeline', async () => { - const mockRequest = await getImportTimelinesRequest(); - await server.inject(mockRequest, context); - expect(mockPersistNote.mock.calls[0][1]).toBeNull(); - }); - - test('should provide note content when Creating notes for a timeline', async () => { - const mockRequest = await getImportTimelinesRequest(); - await server.inject(mockRequest, context); - expect(mockPersistNote.mock.calls[0][2]).toEqual(mockCreatedTimeline.version); + expect(mockPersistNote.mock.calls[0][0].noteId).toBeNull(); }); test('should provide new notes with original author info when Creating notes for a timeline', async () => { const mockRequest = await getImportTimelinesRequest(); await server.inject(mockRequest, context); - expect(mockPersistNote.mock.calls[0][3]).toEqual({ + expect(mockPersistNote.mock.calls[0][0].note).toEqual({ eventId: undefined, note: 'original note', created: '1584830796960', @@ -284,7 +274,7 @@ describe('import timelines', () => { updatedBy: 'original author A', timelineId: mockCreatedTimeline.savedObjectId, }); - expect(mockPersistNote.mock.calls[1][3]).toEqual({ + expect(mockPersistNote.mock.calls[1][0].note).toEqual({ eventId: mockUniqueParsedObjects[0].eventNotes[0].eventId, note: 'original event note', created: '1584830796960', @@ -293,7 +283,7 @@ describe('import timelines', () => { updatedBy: 'original author B', timelineId: mockCreatedTimeline.savedObjectId, }); - expect(mockPersistNote.mock.calls[2][3]).toEqual({ + expect(mockPersistNote.mock.calls[2][0].note).toEqual({ eventId: mockUniqueParsedObjects[0].eventNotes[1].eventId, note: 'event note2', created: '1584830796960', @@ -310,7 +300,7 @@ describe('import timelines', () => { const mockRequest = await getImportTimelinesRequest(); await server.inject(mockRequest, context); - expect(mockPersistNote.mock.calls[0][3]).toEqual({ + expect(mockPersistNote.mock.calls[0][0].note).toEqual({ created: mockUniqueParsedObjects[0].globalNotes[0].created, createdBy: mockUniqueParsedObjects[0].globalNotes[0].createdBy, updated: mockUniqueParsedObjects[0].globalNotes[0].updated, @@ -319,7 +309,7 @@ describe('import timelines', () => { note: mockUniqueParsedObjects[0].globalNotes[0].note, timelineId: mockCreatedTimeline.savedObjectId, }); - expect(mockPersistNote.mock.calls[1][3]).toEqual({ + expect(mockPersistNote.mock.calls[1][0].note).toEqual({ created: mockUniqueParsedObjects[0].eventNotes[0].created, createdBy: mockUniqueParsedObjects[0].eventNotes[0].createdBy, updated: mockUniqueParsedObjects[0].eventNotes[0].updated, @@ -328,7 +318,7 @@ describe('import timelines', () => { note: mockUniqueParsedObjects[0].eventNotes[0].note, timelineId: mockCreatedTimeline.savedObjectId, }); - expect(mockPersistNote.mock.calls[2][3]).toEqual({ + expect(mockPersistNote.mock.calls[2][0].note).toEqual({ created: mockUniqueParsedObjects[0].eventNotes[1].created, createdBy: mockUniqueParsedObjects[0].eventNotes[1].createdBy, updated: mockUniqueParsedObjects[0].eventNotes[1].updated, @@ -517,12 +507,12 @@ describe('import timeline templates', () => { server = serverMock.create(); context = requestContextMock.createTools().context; - securitySetup = ({ + securitySetup = { authc: { getCurrentUser: jest.fn().mockReturnValue(mockGetCurrentUser), }, authz: {}, - } as unknown) as SecurityPluginSetup; + } as unknown as SecurityPluginSetup; mockGetTimeline = jest.fn(); mockGetTemplateTimeline = jest.fn(); @@ -548,9 +538,11 @@ describe('import timeline templates', () => { jest.doMock('./get_timelines_from_stream', () => { return { - getTupleDuplicateErrorsAndUniqueTimeline: mockGetTupleDuplicateErrorsAndUniqueTimeline.mockReturnValue( - [mockDuplicateIdErrors, mockUniqueParsedTemplateTimelineObjects] - ), + getTupleDuplicateErrorsAndUniqueTimeline: + mockGetTupleDuplicateErrorsAndUniqueTimeline.mockReturnValue([ + mockDuplicateIdErrors, + mockUniqueParsedTemplateTimelineObjects, + ]), }; }); @@ -640,19 +632,13 @@ describe('import timeline templates', () => { test('should provide no noteSavedObjectId when Creating notes for a timeline', async () => { const mockRequest = await getImportTimelinesRequest(); await server.inject(mockRequest, context); - expect(mockPersistNote.mock.calls[0][1]).toBeNull(); - }); - - test('should provide new timeline version when Creating notes for a timeline', async () => { - const mockRequest = await getImportTimelinesRequest(); - await server.inject(mockRequest, context); - expect(mockPersistNote.mock.calls[0][2]).toEqual(mockCreatedTemplateTimeline.version); + expect(mockPersistNote.mock.calls[0][0].noteId).toBeNull(); }); test('should exclude event notes when creating notes', async () => { const mockRequest = await getImportTimelinesRequest(); await server.inject(mockRequest, context); - expect(mockPersistNote.mock.calls[0][3]).toEqual({ + expect(mockPersistNote.mock.calls[0][0].note).toEqual({ eventId: undefined, note: mockUniqueParsedTemplateTimelineObjects[0].globalNotes[0].note, created: mockUniqueParsedTemplateTimelineObjects[0].globalNotes[0].created, @@ -792,19 +778,13 @@ describe('import timeline templates', () => { test('should provide noteSavedObjectId when Creating notes for a timeline', async () => { const mockRequest = await getImportTimelinesRequest(); await server.inject(mockRequest, context); - expect(mockPersistNote.mock.calls[0][1]).toBeNull(); - }); - - test('should provide new timeline version when Creating notes for a timeline', async () => { - const mockRequest = await getImportTimelinesRequest(); - await server.inject(mockRequest, context); - expect(mockPersistNote.mock.calls[0][2]).toEqual(mockCreatedTemplateTimeline.version); + expect(mockPersistNote.mock.calls[0][0].noteId).toBeNull(); }); test('should exclude event notes when creating notes', async () => { const mockRequest = await getImportTimelinesRequest(); await server.inject(mockRequest, context); - expect(mockPersistNote.mock.calls[0][3]).toEqual({ + expect(mockPersistNote.mock.calls[0][0].note).toEqual({ eventId: undefined, note: mockUniqueParsedTemplateTimelineObjects[0].globalNotes[0].note, created: mockUniqueParsedTemplateTimelineObjects[0].globalNotes[0].created, diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.ts index 65ffd10c5168b..c06d99adf04d0 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.ts @@ -64,7 +64,7 @@ export const importTimelinesRoute = ( const frameworkRequest = await buildFrameworkRequest(context, security, request); const res = await importTimelines( - (file as unknown) as Readable, + file as unknown as Readable, config.maxTimelineImportExportSize, frameworkRequest, isImmutable === 'true' diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/patch_timelines/index.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/patch_timelines/index.test.ts index e0cd1a166dd43..09316098cea23 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/patch_timelines/index.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/patch_timelines/index.test.ts @@ -48,12 +48,12 @@ describe('update timelines', () => { server = serverMock.create(); context = requestContextMock.createTools().context; - securitySetup = ({ + securitySetup = { authc: { getCurrentUser: jest.fn().mockReturnValue(mockGetCurrentUser), }, authz: {}, - } as unknown) as SecurityPluginSetup; + } as unknown as SecurityPluginSetup; mockGetTimeline = jest.fn(); mockGetTemplateTimeline = jest.fn(); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/patch_timelines/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/patch_timelines/index.ts index e3ad9bc7cb048..9f892f4ee7edb 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/patch_timelines/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/patch_timelines/index.ts @@ -42,13 +42,8 @@ export const patchTimelinesRoute = ( try { const frameworkRequest = await buildFrameworkRequest(context, security, request); const { timelineId, timeline, version } = request.body; - const { - templateTimelineId, - templateTimelineVersion, - timelineType, - title, - status, - } = timeline; + const { templateTimelineId, templateTimelineVersion, timelineType, title, status } = + timeline; const compareTimelinesStatus = new CompareTimelinesStatus({ status, diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/persist_favorite/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/persist_favorite/index.ts index 0de64171e0fb8..3d3e9e7cef2ab 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/persist_favorite/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/persist_favorite/index.ts @@ -41,12 +41,8 @@ export const persistFavoriteRoute = ( try { const frameworkRequest = await buildFrameworkRequest(context, security, request); - const { - timelineId, - templateTimelineId, - templateTimelineVersion, - timelineType, - } = request.body; + const { timelineId, templateTimelineId, templateTimelineVersion, timelineType } = + request.body; const timeline = await persistFavorite( frameworkRequest, diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/field_migrator.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/field_migrator.ts new file mode 100644 index 0000000000000..608c104440e78 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/field_migrator.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { TIMELINE_ID_REF_NAME } from '../../constants'; +import { timelineSavedObjectType } from '../../saved_object_mappings'; +import { FieldMigrator } from '../../utils/migrator'; + +/** + * A migrator to handle moving specific fields that reference the timeline saved object to the references field within a note saved + * object. + */ +export const noteFieldsMigrator = new FieldMigrator([ + { path: 'timelineId', type: timelineSavedObjectType, name: TIMELINE_ID_REF_NAME }, +]); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/index.ts index 34914517da683..81941853c57a3 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/index.ts @@ -28,13 +28,17 @@ export interface Notes { search: string | null, sort: SortNote | null ) => Promise; - persistNote: ( - request: FrameworkRequest, - noteId: string | null, - version: string | null, - note: SavedNote, - overrideOwner: boolean - ) => Promise; + persistNote: ({ + request, + noteId, + note, + overrideOwner, + }: { + request: FrameworkRequest; + noteId: string | null; + note: SavedNote; + overrideOwner: boolean; + }) => Promise; convertSavedObjectToSavedNote: ( savedObject: unknown, timelineVersion?: string | undefined | null diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/persist_notes.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/persist_notes.ts index 58b4e33444d94..612c9083cb343 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/persist_notes.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/persist_notes.ts @@ -13,7 +13,6 @@ import { NoteResult } from '../../../../../common/types/timeline/note'; export const persistNotes = async ( frameworkRequest: FrameworkRequest, timelineSavedObjectId: string, - timelineVersion?: string | null, existingNoteIds?: string[], newNotes?: NoteResult[], overrideOwner: boolean = true @@ -26,13 +25,12 @@ export const persistNotes = async ( timelineSavedObjectId, overrideOwner ); - return persistNote( - frameworkRequest, - overrideOwner ? existingNoteIds?.find((nId) => nId === note.noteId) ?? null : null, - timelineVersion ?? null, - newNote, - overrideOwner - ); + return persistNote({ + request: frameworkRequest, + noteId: overrideOwner ? existingNoteIds?.find((nId) => nId === note.noteId) ?? null : null, + note: newNote, + overrideOwner, + }); }) ?? [] ); }; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/saved_object.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/saved_object.ts index 91caaa8cc8a8b..fcfbb2345c4e4 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/saved_object.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/saved_object.ts @@ -25,11 +25,13 @@ import { NoteResult, ResponseNotes, ResponseNote, + NoteWithoutExternalRefs, } from '../../../../../common/types/timeline/note'; import { FrameworkRequest } from '../../../framework'; import { noteSavedObjectType } from '../../saved_object_mappings/notes'; -import { convertSavedObjectToSavedTimeline, pickSavedTimeline } from '../timelines'; +import { createTimeline } from '../timelines'; import { timelineSavedObjectType } from '../../saved_object_mappings'; +import { noteFieldsMigrator } from './field_migrator'; export const deleteNote = async (request: FrameworkRequest, noteIds: string[]) => { const savedObjectsClient = request.context.core.savedObjects.client; @@ -42,8 +44,7 @@ export const deleteNote = async (request: FrameworkRequest, noteIds: string[]) = export const deleteNoteByTimelineId = async (request: FrameworkRequest, timelineId: string) => { const options: SavedObjectsFindOptions = { type: noteSavedObjectType, - search: timelineId, - searchFields: ['timelineId'], + hasReference: { type: timelineSavedObjectType, id: timelineId }, }; const notesToBeDeleted = await getAllSavedNote(request, options); const savedObjectsClient = request.context.core.savedObjects.client; @@ -81,8 +82,7 @@ export const getNotesByTimelineId = async ( ): Promise => { const options: SavedObjectsFindOptions = { type: noteSavedObjectType, - search: timelineId, - searchFields: ['timelineId'], + hasReference: { type: timelineSavedObjectType, id: timelineId }, }; const notesByTimelineId = await getAllSavedNote(request, options); return notesByTimelineId.notes; @@ -106,62 +106,29 @@ export const getAllNotes = async ( return getAllSavedNote(request, options); }; -export const persistNote = async ( - request: FrameworkRequest, - noteId: string | null, - version: string | null, - note: SavedNote, - overrideOwner: boolean = true -): Promise => { +export const persistNote = async ({ + request, + noteId, + note, + overrideOwner = true, +}: { + request: FrameworkRequest; + noteId: string | null; + note: SavedNote; + overrideOwner?: boolean; +}): Promise => { try { - const savedObjectsClient = request.context.core.savedObjects.client; - if (noteId == null) { - const timelineVersionSavedObject = - note.timelineId == null - ? await (async () => { - const timelineResult = convertSavedObjectToSavedTimeline( - await savedObjectsClient.create( - timelineSavedObjectType, - pickSavedTimeline(null, {}, request.user) - ) - ); - note.timelineId = timelineResult.savedObjectId; - return timelineResult.version; - })() - : null; - - // Create new note - return { - code: 200, - message: 'success', - note: convertSavedObjectToSavedNote( - await savedObjectsClient.create( - noteSavedObjectType, - overrideOwner ? pickSavedNote(noteId, note, request.user) : note - ), - timelineVersionSavedObject != null ? timelineVersionSavedObject : undefined - ), - }; + return await createNote({ + request, + noteId, + note, + overrideOwner, + }); } // Update existing note - - const existingNote = await getSavedNote(request, noteId); - return { - code: 200, - message: 'success', - note: convertSavedObjectToSavedNote( - await savedObjectsClient.update( - noteSavedObjectType, - noteId, - overrideOwner ? pickSavedNote(noteId, note, request.user) : note, - { - version: existingNote.version || undefined, - } - ) - ), - }; + return await updateNote({ request, noteId, note, overrideOwner }); } catch (err) { if (getOr(null, 'output.statusCode', err) === 403) { const noteToReturn: NoteResult = { @@ -181,22 +148,138 @@ export const persistNote = async ( } }; +const createNote = async ({ + request, + noteId, + note, + overrideOwner = true, +}: { + request: FrameworkRequest; + noteId: string | null; + note: SavedNote; + overrideOwner?: boolean; +}) => { + const savedObjectsClient = request.context.core.savedObjects.client; + const userInfo = request.user; + + const shallowCopyOfNote = { ...note }; + let timelineVersion: string | undefined; + + if (note.timelineId == null) { + const { timeline: timelineResult } = await createTimeline({ + timelineId: null, + timeline: {}, + savedObjectsClient, + userInfo, + }); + + shallowCopyOfNote.timelineId = timelineResult.savedObjectId; + timelineVersion = timelineResult.version; + } + + const noteWithCreator = overrideOwner + ? pickSavedNote(noteId, shallowCopyOfNote, userInfo) + : shallowCopyOfNote; + + const { transformedFields: migratedAttributes, references } = + noteFieldsMigrator.extractFieldsToReferences({ + data: noteWithCreator, + }); + + const createdNote = await savedObjectsClient.create( + noteSavedObjectType, + migratedAttributes, + { + references, + } + ); + + const repopulatedSavedObject = noteFieldsMigrator.populateFieldsFromReferences(createdNote); + + const convertedNote = convertSavedObjectToSavedNote(repopulatedSavedObject, timelineVersion); + + // Create new note + return { + code: 200, + message: 'success', + note: convertedNote, + }; +}; + +const updateNote = async ({ + request, + noteId, + note, + overrideOwner = true, +}: { + request: FrameworkRequest; + noteId: string; + note: SavedNote; + overrideOwner?: boolean; +}) => { + const savedObjectsClient = request.context.core.savedObjects.client; + const userInfo = request.user; + + const existingNote = await savedObjectsClient.get( + noteSavedObjectType, + noteId + ); + + const noteWithCreator = overrideOwner ? pickSavedNote(noteId, note, userInfo) : note; + + const { transformedFields: migratedPatchAttributes, references } = + noteFieldsMigrator.extractFieldsToReferences({ + data: noteWithCreator, + existingReferences: existingNote.references, + }); + + const updatedNote = await savedObjectsClient.update( + noteSavedObjectType, + noteId, + migratedPatchAttributes, + { + version: existingNote.version || undefined, + references, + } + ); + + const populatedNote = noteFieldsMigrator.populateFieldsFromReferencesForPatch({ + dataBeforeRequest: note, + dataReturnedFromRequest: updatedNote, + }); + + const convertedNote = convertSavedObjectToSavedNote(populatedNote); + + return { + code: 200, + message: 'success', + note: convertedNote, + }; +}; + const getSavedNote = async (request: FrameworkRequest, NoteId: string) => { const savedObjectsClient = request.context.core.savedObjects.client; - const savedObject = await savedObjectsClient.get(noteSavedObjectType, NoteId); + const savedObject = await savedObjectsClient.get( + noteSavedObjectType, + NoteId + ); + + const populatedNote = noteFieldsMigrator.populateFieldsFromReferences(savedObject); - return convertSavedObjectToSavedNote(savedObject); + return convertSavedObjectToSavedNote(populatedNote); }; const getAllSavedNote = async (request: FrameworkRequest, options: SavedObjectsFindOptions) => { const savedObjectsClient = request.context.core.savedObjects.client; - const savedObjects = await savedObjectsClient.find(options); + const savedObjects = await savedObjectsClient.find(options); return { totalCount: savedObjects.total, - notes: savedObjects.saved_objects.map((savedObject) => - convertSavedObjectToSavedNote(savedObject) - ), + notes: savedObjects.saved_objects.map((savedObject) => { + const populatedNote = noteFieldsMigrator.populateFieldsFromReferences(savedObject); + + return convertSavedObjectToSavedNote(populatedNote); + }), }; }; @@ -233,11 +316,9 @@ const pickSavedNote = ( if (noteId == null) { savedNote.created = new Date().valueOf(); savedNote.createdBy = userInfo?.username ?? UNAUTHENTICATED_USER; - savedNote.updated = new Date().valueOf(); - savedNote.updatedBy = userInfo?.username ?? UNAUTHENTICATED_USER; - } else if (noteId != null) { - savedNote.updated = new Date().valueOf(); - savedNote.updatedBy = userInfo?.username ?? UNAUTHENTICATED_USER; } + + savedNote.updated = new Date().valueOf(); + savedNote.updatedBy = userInfo?.username ?? UNAUTHENTICATED_USER; return savedNote; }; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/pinned_events/field_migrator.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/pinned_events/field_migrator.ts new file mode 100644 index 0000000000000..5939676c2a924 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/pinned_events/field_migrator.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { TIMELINE_ID_REF_NAME } from '../../constants'; +import { timelineSavedObjectType } from '../../saved_object_mappings'; +import { FieldMigrator } from '../../utils/migrator'; + +/** + * A migrator to handle moving specific fields that reference the timeline saved object to the references field within a note saved + * object. + */ +export const pinnedEventFieldsMigrator = new FieldMigrator([ + { path: 'timelineId', type: timelineSavedObjectType, name: TIMELINE_ID_REF_NAME }, +]); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/pinned_events/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/pinned_events/index.ts index b3d262b13cbf3..260531e1106bf 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/pinned_events/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/pinned_events/index.ts @@ -19,13 +19,13 @@ import { PinnedEventSavedObjectRuntimeType, SavedPinnedEvent, PinnedEvent as PinnedEventResponse, + PinnedEventWithoutExternalRefs, } from '../../../../../common/types/timeline/pinned_event'; -import { PageInfoNote, SortNote } from '../../../../../common/types/timeline/note'; import { FrameworkRequest } from '../../../framework'; -import { pickSavedTimeline } from '../../saved_object/timelines'; -import { convertSavedObjectToSavedTimeline } from '../timelines'; +import { createTimeline } from '../../saved_object/timelines'; import { pinnedEventSavedObjectType } from '../../saved_object_mappings/pinned_events'; +import { pinnedEventFieldsMigrator } from './field_migrator'; import { timelineSavedObjectType } from '../../saved_object_mappings'; export interface PinnedEvent { @@ -46,13 +46,6 @@ export interface PinnedEvent { timelineId: string ) => Promise; - getAllPinnedEvents: ( - request: FrameworkRequest, - pageInfo: PageInfoNote | null, - search: string | null, - sort: SortNote | null - ) => Promise; - persistPinnedEventOnTimeline: ( request: FrameworkRequest, pinnedEventId: string | null, // pinned event saved object id @@ -117,26 +110,7 @@ export const getAllPinnedEventsByTimelineId = async ( ): Promise => { const options: SavedObjectsFindOptions = { type: pinnedEventSavedObjectType, - search: timelineId, - searchFields: ['timelineId'], - }; - return getAllSavedPinnedEvents(request, options); -}; - -export const getAllPinnedEvents = async ( - request: FrameworkRequest, - pageInfo: PageInfoNote | null, - search: string | null, - sort: SortNote | null -): Promise => { - const options: SavedObjectsFindOptions = { - type: pinnedEventSavedObjectType, - perPage: pageInfo != null ? pageInfo.pageSize : undefined, - page: pageInfo != null ? pageInfo.pageIndex : undefined, - search: search != null ? search : undefined, - searchFields: ['timelineId', 'eventId'], - sortField: sort != null ? sort.sortField : undefined, - sortOrder: sort != null ? sort.sortOrder : undefined, + hasReference: { type: timelineSavedObjectType, id: timelineId }, }; return getAllSavedPinnedEvents(request, options); }; @@ -147,51 +121,35 @@ export const persistPinnedEventOnTimeline = async ( eventId: string, timelineId: string | null ): Promise => { - const savedObjectsClient = request.context.core.savedObjects.client; - try { - if (pinnedEventId == null) { - const timelineVersionSavedObject = - timelineId == null - ? await (async () => { - const timelineResult = convertSavedObjectToSavedTimeline( - await savedObjectsClient.create( - timelineSavedObjectType, - pickSavedTimeline(null, {}, request.user || null) - ) - ); - timelineId = timelineResult.savedObjectId; // eslint-disable-line no-param-reassign - return timelineResult.version; - })() - : null; - - if (timelineId != null) { - const allPinnedEventId = await getAllPinnedEventsByTimelineId(request, timelineId); - const isPinnedAlreadyExisting = allPinnedEventId.filter( - (pinnedEvent) => pinnedEvent.eventId === eventId - ); - - if (isPinnedAlreadyExisting.length === 0) { - const savedPinnedEvent: SavedPinnedEvent = { - eventId, - timelineId, - }; - // create Pinned Event on Timeline - return convertSavedObjectToSavedPinnedEvent( - await savedObjectsClient.create( - pinnedEventSavedObjectType, - pickSavedPinnedEvent(pinnedEventId, savedPinnedEvent, request.user || null) - ), - timelineVersionSavedObject != null ? timelineVersionSavedObject : undefined - ); - } - return isPinnedAlreadyExisting[0]; - } - throw new Error('You can NOT pinned event without a timelineID'); + if (pinnedEventId != null) { + // Delete Pinned Event on Timeline + await deletePinnedEventOnTimeline(request, [pinnedEventId]); + return null; } - // Delete Pinned Event on Timeline - await deletePinnedEventOnTimeline(request, [pinnedEventId]); - return null; + + const { timelineId: validatedTimelineId, timelineVersion } = await getValidTimelineIdAndVersion( + request, + timelineId + ); + + const pinnedEvents = await getPinnedEventsInTimelineWithEventId( + request, + validatedTimelineId, + eventId + ); + + // we already had this event pinned so let's just return the one we already had + if (pinnedEvents.length > 0) { + return pinnedEvents[0]; + } + + return await createPinnedEvent({ + request, + eventId, + timelineId: validatedTimelineId, + timelineVersion, + }); } catch (err) { if (getOr(null, 'output.statusCode', err) === 404) { /* @@ -215,11 +173,91 @@ export const persistPinnedEventOnTimeline = async ( } }; +const getValidTimelineIdAndVersion = async ( + request: FrameworkRequest, + timelineId: string | null +): Promise<{ timelineId: string; timelineVersion?: string }> => { + if (timelineId != null) { + return { + timelineId, + }; + } + + const savedObjectsClient = request.context.core.savedObjects.client; + + // create timeline because it didn't exist + const { timeline: timelineResult } = await createTimeline({ + timelineId: null, + timeline: {}, + savedObjectsClient, + userInfo: request.user, + }); + + return { + timelineId: timelineResult.savedObjectId, + timelineVersion: timelineResult.version, + }; +}; + +const getPinnedEventsInTimelineWithEventId = async ( + request: FrameworkRequest, + timelineId: string, + eventId: string +): Promise => { + const allPinnedEventId = await getAllPinnedEventsByTimelineId(request, timelineId); + const pinnedEvents = allPinnedEventId.filter((pinnedEvent) => pinnedEvent.eventId === eventId); + + return pinnedEvents; +}; + +const createPinnedEvent = async ({ + request, + eventId, + timelineId, + timelineVersion, +}: { + request: FrameworkRequest; + eventId: string; + timelineId: string; + timelineVersion?: string; +}) => { + const savedObjectsClient = request.context.core.savedObjects.client; + + const savedPinnedEvent: SavedPinnedEvent = { + eventId, + timelineId, + }; + + const pinnedEventWithCreator = pickSavedPinnedEvent(null, savedPinnedEvent, request.user); + + const { transformedFields: migratedAttributes, references } = + pinnedEventFieldsMigrator.extractFieldsToReferences({ + data: pinnedEventWithCreator, + }); + + const createdPinnedEvent = await savedObjectsClient.create( + pinnedEventSavedObjectType, + migratedAttributes, + { references } + ); + + const repopulatedSavedObject = + pinnedEventFieldsMigrator.populateFieldsFromReferences(createdPinnedEvent); + + // create Pinned Event on Timeline + return convertSavedObjectToSavedPinnedEvent(repopulatedSavedObject, timelineVersion); +}; + const getSavedPinnedEvent = async (request: FrameworkRequest, pinnedEventId: string) => { const savedObjectsClient = request.context.core.savedObjects.client; - const savedObject = await savedObjectsClient.get(pinnedEventSavedObjectType, pinnedEventId); + const savedObject = await savedObjectsClient.get( + pinnedEventSavedObjectType, + pinnedEventId + ); + + const populatedPinnedEvent = pinnedEventFieldsMigrator.populateFieldsFromReferences(savedObject); - return convertSavedObjectToSavedPinnedEvent(savedObject); + return convertSavedObjectToSavedPinnedEvent(populatedPinnedEvent); }; const getAllSavedPinnedEvents = async ( @@ -227,11 +265,14 @@ const getAllSavedPinnedEvents = async ( options: SavedObjectsFindOptions ) => { const savedObjectsClient = request.context.core.savedObjects.client; - const savedObjects = await savedObjectsClient.find(options); + const savedObjects = await savedObjectsClient.find(options); - return savedObjects.saved_objects.map((savedObject) => - convertSavedObjectToSavedPinnedEvent(savedObject) - ); + return savedObjects.saved_objects.map((savedObject) => { + const populatedPinnedEvent = + pinnedEventFieldsMigrator.populateFieldsFromReferences(savedObject); + + return convertSavedObjectToSavedPinnedEvent(populatedPinnedEvent); + }); }; export const savePinnedEvents = ( @@ -284,11 +325,10 @@ export const pickSavedPinnedEvent = ( if (pinnedEventId == null) { savedPinnedEvent.created = dateNow; savedPinnedEvent.createdBy = userInfo?.username ?? UNAUTHENTICATED_USER; - savedPinnedEvent.updated = dateNow; - savedPinnedEvent.updatedBy = userInfo?.username ?? UNAUTHENTICATED_USER; - } else if (pinnedEventId != null) { - savedPinnedEvent.updated = dateNow; - savedPinnedEvent.updatedBy = userInfo?.username ?? UNAUTHENTICATED_USER; } + + savedPinnedEvent.updated = dateNow; + savedPinnedEvent.updatedBy = userInfo?.username ?? UNAUTHENTICATED_USER; + return savedPinnedEvent; }; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.test.ts index 1136753bc8316..f7e4de69097f1 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.test.ts @@ -49,7 +49,7 @@ describe('saved_object', () => { beforeEach(() => { mockFindSavedObject = jest.fn().mockResolvedValue({ saved_objects: [], total: 0 }); - mockRequest = ({ + mockRequest = { user: { username: 'username', }, @@ -62,7 +62,7 @@ describe('saved_object', () => { }, }, }, - } as unknown) as FrameworkRequest; + } as unknown as FrameworkRequest; }); afterEach(() => { @@ -117,7 +117,7 @@ describe('saved_object', () => { pageSize: 10, pageIndex: 1, }; - let result = (null as unknown) as AllTimelinesResponse; + let result = null as unknown as AllTimelinesResponse; beforeEach(async () => { (convertSavedObjectToSavedTimeline as jest.Mock).mockReturnValue(mockGetTimelineValue); mockFindSavedObject = jest @@ -127,7 +127,7 @@ describe('saved_object', () => { .mockResolvedValueOnce({ saved_objects: [mockSavedObject], total: 1 }) .mockResolvedValueOnce({ saved_objects: [mockSavedObject], total: 1 }) .mockResolvedValue({ saved_objects: [], total: 0 }); - mockRequest = ({ + mockRequest = { user: { username: 'username', }, @@ -140,7 +140,7 @@ describe('saved_object', () => { }, }, }, - } as unknown) as FrameworkRequest; + } as unknown as FrameworkRequest; result = await getAllTimeline(mockRequest, false, pageInfo, null, null, null, null); }); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.ts index d35a023e8df5a..b3ed5675cea31 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.ts @@ -426,7 +426,7 @@ export const persistTimeline = async ( } }; -const createTimeline = async ({ +export const createTimeline = async ({ timelineId, timeline, savedObjectsClient, @@ -437,12 +437,10 @@ const createTimeline = async ({ savedObjectsClient: SavedObjectsClientContract; userInfo: AuthenticatedUser | null; }) => { - const { - transformedFields: migratedAttributes, - references, - } = timelineFieldsMigrator.extractFieldsToReferences({ - data: pickSavedTimeline(timelineId, timeline, userInfo), - }); + const { transformedFields: migratedAttributes, references } = + timelineFieldsMigrator.extractFieldsToReferences({ + data: pickSavedTimeline(timelineId, timeline, userInfo), + }); const createdTimeline = await savedObjectsClient.create( timelineSavedObjectType, @@ -452,9 +450,8 @@ const createTimeline = async ({ } ); - const repopulatedSavedObject = timelineFieldsMigrator.populateFieldsFromReferences( - createdTimeline - ); + const repopulatedSavedObject = + timelineFieldsMigrator.populateFieldsFromReferences(createdTimeline); // Create new timeline const newTimeline = convertSavedObjectToSavedTimeline(repopulatedSavedObject); @@ -485,13 +482,11 @@ const updateTimeline = async ({ timelineId ); - const { - transformedFields: migratedPatchAttributes, - references, - } = timelineFieldsMigrator.extractFieldsToReferences({ - data: pickSavedTimeline(timelineId, timeline, userInfo), - existingReferences: rawTimelineSavedObject.references, - }); + const { transformedFields: migratedPatchAttributes, references } = + timelineFieldsMigrator.extractFieldsToReferences({ + data: pickSavedTimeline(timelineId, timeline, userInfo), + existingReferences: rawTimelineSavedObject.references, + }); // Update Timeline await savedObjectsClient.update(timelineSavedObjectType, timelineId, migratedPatchAttributes, { @@ -517,13 +512,11 @@ const updatePartialSavedTimeline = async ( timelineId ); - const { - transformedFields, - references, - } = timelineFieldsMigrator.extractFieldsToReferences({ - data: timeline, - existingReferences: currentSavedTimeline.references, - }); + const { transformedFields, references } = + timelineFieldsMigrator.extractFieldsToReferences({ + data: timeline, + existingReferences: currentSavedTimeline.references, + }); const timelineUpdateAttributes = pickSavedTimeline( null, @@ -541,12 +534,11 @@ const updatePartialSavedTimeline = async ( { references } ); - const populatedTimeline = timelineFieldsMigrator.populateFieldsFromReferencesForPatch( - { + const populatedTimeline = + timelineFieldsMigrator.populateFieldsFromReferencesForPatch({ dataBeforeRequest: timelineUpdateAttributes, dataReturnedFromRequest: updatedTimeline, - } - ); + }); return populatedTimeline; }; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/pick_saved_timeline.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/pick_saved_timeline.test.ts index 3a37a49d03dcd..5aa7dd9c7ad46 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/pick_saved_timeline.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/pick_saved_timeline.test.ts @@ -76,13 +76,13 @@ describe('pickSavedTimeline', () => { }); beforeAll(() => { - Date = (jest.fn(() => ({ + Date = jest.fn(() => ({ valueOf: jest.fn().mockReturnValue(mockDateNow), - })) as unknown) as DateConstructor; + })) as unknown as DateConstructor; }); afterAll(() => { - ((Date as unknown) as jest.Mock).mockRestore(); + (Date as unknown as jest.Mock).mockRestore(); }); describe('Set create / update time correctly ', () => { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/migrations/notes.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/migrations/notes.ts new file mode 100644 index 0000000000000..76773b7fcd518 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/migrations/notes.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SavedObjectMigrationMap } from 'kibana/server'; +import { migrateTimelineIdToReferences } from './utils'; + +export const notesMigrations: SavedObjectMigrationMap = { + '7.16.0': migrateTimelineIdToReferences, +}; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/migrations/pinned_events.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/migrations/pinned_events.ts new file mode 100644 index 0000000000000..4d21190d9381c --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/migrations/pinned_events.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SavedObjectMigrationMap } from 'kibana/server'; +import { migrateTimelineIdToReferences } from './utils'; + +export const pinnedEventsMigrations: SavedObjectMigrationMap = { + '7.16.0': migrateTimelineIdToReferences, +}; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/migrations/timelines.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/migrations/timelines.test.ts index b74b1b37ad151..c585e6ee2e8fb 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/migrations/timelines.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/migrations/timelines.test.ts @@ -27,7 +27,7 @@ describe('timeline migrations', () => { const migratedDoc = migrateSavedQueryIdToReferences({ id: '1', type: 'awesome', - attributes: ({ awesome: 'yes', savedQueryId: '123' } as unknown) as SavedQueryId, + attributes: { awesome: 'yes', savedQueryId: '123' } as unknown as SavedQueryId, }); expect(migratedDoc.attributes).toEqual({ awesome: 'yes' }); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/migrations/timelines.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/migrations/timelines.ts index 7c26df4a475e9..45733d7737b62 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/migrations/timelines.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/migrations/timelines.ts @@ -11,7 +11,7 @@ import { SavedObjectUnsanitizedDoc, } from 'kibana/server'; import { SAVED_QUERY_ID_REF_NAME, SAVED_QUERY_TYPE } from '../../constants'; -import { createReference } from './utils'; +import { createMigratedDoc, createReference } from './utils'; export interface SavedQueryId { savedQueryId?: string | null; @@ -29,13 +29,12 @@ export const migrateSavedQueryIdToReferences = ( SAVED_QUERY_TYPE ); - return { - ...doc, - attributes: { - ...restAttributes, - }, - references: [...docReferences, ...savedQueryIdReferences], - }; + return createMigratedDoc({ + doc, + attributes: restAttributes, + docReferences, + migratedReferences: savedQueryIdReferences, + }); }; export const timelinesMigrations: SavedObjectMigrationMap = { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/migrations/types.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/migrations/types.ts new file mode 100644 index 0000000000000..7c62310a99aa6 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/migrations/types.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export interface TimelineId { + timelineId?: string | null; +} diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/migrations/utils.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/migrations/utils.test.ts index cdf4124dc9c45..329f09e85f3a7 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/migrations/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/migrations/utils.test.ts @@ -5,9 +5,39 @@ * 2.0. */ -import { createReference } from './utils'; +import { timelineSavedObjectType } from '../timelines'; +import { TIMELINE_ID_REF_NAME } from '../../constants'; +import { TimelineId } from './types'; +import { createMigratedDoc, createReference, migrateTimelineIdToReferences } from './utils'; describe('migration utils', () => { + describe('migrateTimelineIdToReferences', () => { + it('removes the timelineId from the migrated document', () => { + const migratedDoc = migrateTimelineIdToReferences({ + id: '1', + type: 'awesome', + attributes: { timelineId: '123' }, + }); + + expect(migratedDoc.attributes).toEqual({}); + expect(migratedDoc.references).toEqual([ + { id: '123', name: TIMELINE_ID_REF_NAME, type: timelineSavedObjectType }, + ]); + }); + + it('preserves additional fields when migrating timeline id', () => { + const migratedDoc = migrateTimelineIdToReferences({ + id: '1', + type: 'awesome', + attributes: { awesome: 'yes', timelineId: '123' } as unknown as TimelineId, + }); + + expect(migratedDoc.attributes).toEqual({ awesome: 'yes' }); + expect(migratedDoc.references).toEqual([ + { id: '123', name: TIMELINE_ID_REF_NAME, type: timelineSavedObjectType }, + ]); + }); + }); describe('createReference', () => { it('returns an array with a reference when the id is defined', () => { expect(createReference('awesome', 'name', 'type')).toEqual([ @@ -23,4 +53,52 @@ describe('migration utils', () => { expect(createReference(null, 'name', 'type')).toHaveLength(0); }); }); + + describe('createMigratedDoc', () => { + it('overwrites the attributes of the original doc', () => { + const doc = { + id: '1', + attributes: { + hello: '1', + }, + type: 'yes', + }; + + expect( + createMigratedDoc({ doc, attributes: {}, docReferences: [], migratedReferences: [] }) + ).toEqual({ + id: '1', + attributes: {}, + type: 'yes', + references: [], + }); + }); + + it('combines the references', () => { + const doc = { + id: '1', + attributes: { + hello: '1', + }, + type: 'yes', + }; + + expect( + createMigratedDoc({ + doc, + attributes: {}, + docReferences: [{ id: '1', name: 'name', type: 'type' }], + migratedReferences: [{ id: '5', name: 'name5', type: 'type5' }], + }) + ).toEqual({ + id: '1', + attributes: {}, + type: 'yes', + references: [ + { id: '1', name: 'name', type: 'type' }, + { id: '5', name: 'name5', type: 'type5' }, + ], + }); + }); + }); }); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/migrations/utils.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/migrations/utils.ts index 8efa6bb0ec93d..7bd7bc148c263 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/migrations/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/migrations/utils.ts @@ -5,7 +5,14 @@ * 2.0. */ -import { SavedObjectReference } from 'kibana/server'; +import { + SavedObjectReference, + SavedObjectSanitizedDoc, + SavedObjectUnsanitizedDoc, +} from 'kibana/server'; +import { timelineSavedObjectType } from '../timelines'; +import { TIMELINE_ID_REF_NAME } from '../../constants'; +import { TimelineId } from './types'; export function createReference( id: string | null | undefined, @@ -14,3 +21,41 @@ export function createReference( ): SavedObjectReference[] { return id != null ? [{ id, name, type }] : []; } + +export const migrateTimelineIdToReferences = ( + doc: SavedObjectUnsanitizedDoc +): SavedObjectSanitizedDoc => { + const { timelineId, ...restAttributes } = doc.attributes; + + const { references: docReferences = [] } = doc; + const timelineIdReferences = createReference( + timelineId, + TIMELINE_ID_REF_NAME, + timelineSavedObjectType + ); + + return createMigratedDoc({ + doc, + attributes: restAttributes, + docReferences, + migratedReferences: timelineIdReferences, + }); +}; + +export const createMigratedDoc = ({ + doc, + attributes, + docReferences, + migratedReferences, +}: { + doc: SavedObjectUnsanitizedDoc; + attributes: object; + docReferences: SavedObjectReference[]; + migratedReferences: SavedObjectReference[]; +}): SavedObjectSanitizedDoc => ({ + ...doc, + attributes: { + ...attributes, + }, + references: [...docReferences, ...migratedReferences], +}); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/notes.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/notes.ts index 5815747d3e720..eda2478e7809d 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/notes.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/notes.ts @@ -6,14 +6,12 @@ */ import { SavedObjectsType } from '../../../../../../../src/core/server'; +import { notesMigrations } from './migrations/notes'; export const noteSavedObjectType = 'siem-ui-timeline-note'; export const noteSavedObjectMappings: SavedObjectsType['mappings'] = { properties: { - timelineId: { - type: 'keyword', - }, eventId: { type: 'keyword', }, @@ -40,4 +38,5 @@ export const noteType: SavedObjectsType = { hidden: false, namespaceType: 'single', mappings: noteSavedObjectMappings, + migrations: notesMigrations, }; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/pinned_events.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/pinned_events.ts index fbbffe35a58c0..2f8e72ad763f9 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/pinned_events.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/pinned_events.ts @@ -6,14 +6,12 @@ */ import { SavedObjectsType } from '../../../../../../../src/core/server'; +import { pinnedEventsMigrations } from './migrations/pinned_events'; export const pinnedEventSavedObjectType = 'siem-ui-timeline-pinned-event'; export const pinnedEventSavedObjectMappings: SavedObjectsType['mappings'] = { properties: { - timelineId: { - type: 'keyword', - }, eventId: { type: 'keyword', }, @@ -37,4 +35,5 @@ export const pinnedEventType: SavedObjectsType = { hidden: false, namespaceType: 'single', mappings: pinnedEventSavedObjectMappings, + migrations: pinnedEventsMigrations, }; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/timelines.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/timelines.ts index 8300f72a162ed..e1e3a454087f9 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/timelines.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object_mappings/timelines.ts @@ -6,7 +6,7 @@ */ import { SavedObjectsType } from '../../../../../../../src/core/server'; -import { timelinesMigrations } from './migrations'; +import { timelinesMigrations } from './migrations/timelines'; export const timelineSavedObjectType = 'siem-ui-timeline'; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/utils/compare_timelines_status.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/utils/compare_timelines_status.test.ts index 7fb49a3923a25..d59c312d0b2a1 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/utils/compare_timelines_status.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/utils/compare_timelines_status.test.ts @@ -53,8 +53,9 @@ describe('CompareTimelinesStatus', () => { }; }); - const CompareTimelinesStatus = jest.requireActual('./compare_timelines_status') - .CompareTimelinesStatus; + const CompareTimelinesStatus = jest.requireActual( + './compare_timelines_status' + ).CompareTimelinesStatus; timelineObj = new CompareTimelinesStatus({ timelineInput: { @@ -127,8 +128,9 @@ describe('CompareTimelinesStatus', () => { }; }); - const CompareTimelinesStatus = jest.requireActual('./compare_timelines_status') - .CompareTimelinesStatus; + const CompareTimelinesStatus = jest.requireActual( + './compare_timelines_status' + ).CompareTimelinesStatus; timelineObj = new CompareTimelinesStatus({ timelineInput: { @@ -192,8 +194,9 @@ describe('CompareTimelinesStatus', () => { ), })); - const CompareTimelinesStatus = jest.requireActual('./compare_timelines_status') - .CompareTimelinesStatus; + const CompareTimelinesStatus = jest.requireActual( + './compare_timelines_status' + ).CompareTimelinesStatus; timelineObj = new CompareTimelinesStatus({ timelineInput: { @@ -276,8 +279,9 @@ describe('CompareTimelinesStatus', () => { getTimelineTemplateOrNull: mockGetTemplateTimeline, })); - const CompareTimelinesStatus = jest.requireActual('./compare_timelines_status') - .CompareTimelinesStatus; + const CompareTimelinesStatus = jest.requireActual( + './compare_timelines_status' + ).CompareTimelinesStatus; timelineObj = new CompareTimelinesStatus({ timelineInput: { @@ -369,8 +373,9 @@ describe('CompareTimelinesStatus', () => { }; }); - const CompareTimelinesStatus = jest.requireActual('./compare_timelines_status') - .CompareTimelinesStatus; + const CompareTimelinesStatus = jest.requireActual( + './compare_timelines_status' + ).CompareTimelinesStatus; timelineObj = new CompareTimelinesStatus({ timelineInput: { @@ -448,8 +453,9 @@ describe('CompareTimelinesStatus', () => { }; }); - const CompareTimelinesStatus = jest.requireActual('./compare_timelines_status') - .CompareTimelinesStatus; + const CompareTimelinesStatus = jest.requireActual( + './compare_timelines_status' + ).CompareTimelinesStatus; timelineObj = new CompareTimelinesStatus({ timelineInput: { @@ -537,8 +543,9 @@ describe('CompareTimelinesStatus', () => { }; }); - const CompareTimelinesStatus = jest.requireActual('./compare_timelines_status') - .CompareTimelinesStatus; + const CompareTimelinesStatus = jest.requireActual( + './compare_timelines_status' + ).CompareTimelinesStatus; timelineObj = new CompareTimelinesStatus({ timelineInput: { @@ -612,8 +619,9 @@ describe('CompareTimelinesStatus', () => { }; }); - const CompareTimelinesStatus = jest.requireActual('./compare_timelines_status') - .CompareTimelinesStatus; + const CompareTimelinesStatus = jest.requireActual( + './compare_timelines_status' + ).CompareTimelinesStatus; timelineObj = new CompareTimelinesStatus({ timelineInput: { @@ -679,8 +687,9 @@ describe('CompareTimelinesStatus', () => { getTimelineTemplateOrNull: mockGetTemplateTimeline, })); - const CompareTimelinesStatus = jest.requireActual('./compare_timelines_status') - .CompareTimelinesStatus; + const CompareTimelinesStatus = jest.requireActual( + './compare_timelines_status' + ).CompareTimelinesStatus; timelineObj = new CompareTimelinesStatus({ timelineInput: { @@ -743,8 +752,9 @@ describe('CompareTimelinesStatus', () => { ), })); - const CompareTimelinesStatus = jest.requireActual('./compare_timelines_status') - .CompareTimelinesStatus; + const CompareTimelinesStatus = jest.requireActual( + './compare_timelines_status' + ).CompareTimelinesStatus; timelineObj = new CompareTimelinesStatus({ timelineInput: { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/utils/compare_timelines_status.ts b/x-pack/plugins/security_solution/server/lib/timeline/utils/compare_timelines_status.ts index 00a31aa04d1bf..385a70e877520 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/utils/compare_timelines_status.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/utils/compare_timelines_status.ts @@ -212,8 +212,8 @@ export class CompareTimelinesStatus { private get isTemplateVersionConflict() { const templateTimelineVersion = this.templateTimelineObject?.getVersion; - const existingTemplateTimelineVersion = this.templateTimelineObject?.data - ?.templateTimelineVersion; + const existingTemplateTimelineVersion = + this.templateTimelineObject?.data?.templateTimelineVersion; if ( templateTimelineVersion != null && this.templateTimelineObject.isExists && diff --git a/x-pack/plugins/security_solution/server/mocks.ts b/x-pack/plugins/security_solution/server/mocks.ts index e9f6693818a99..cff68527af4b9 100644 --- a/x-pack/plugins/security_solution/server/mocks.ts +++ b/x-pack/plugins/security_solution/server/mocks.ts @@ -9,9 +9,9 @@ import { AppClient } from './types'; type AppClientMock = jest.Mocked; const createAppClientMock = (): AppClientMock => - (({ + ({ getSignalsIndex: jest.fn(), - } as unknown) as AppClientMock); + } as unknown as AppClientMock); export const siemMock = { createClient: createAppClientMock, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/event_enrichment/query.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/event_enrichment/query.ts index 4760e6a227cd3..786f9a20f77e5 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/event_enrichment/query.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/event_enrichment/query.ts @@ -11,42 +11,37 @@ import { createQueryFilterClauses } from '../../../../../utils/build_query'; import { SecuritySolutionFactory } from '../../types'; import { buildIndicatorShouldClauses } from './helpers'; -export const buildEventEnrichmentQuery: SecuritySolutionFactory['buildDsl'] = ({ - defaultIndex, - docValueFields, - eventFields, - filterQuery, - timerange: { from, to }, -}) => { - const filter = [ - ...createQueryFilterClauses(filterQuery), - { term: { 'event.type': 'indicator' } }, - { - range: { - '@timestamp': { - gte: from, - lte: to, - format: 'strict_date_optional_time', +export const buildEventEnrichmentQuery: SecuritySolutionFactory['buildDsl'] = + ({ defaultIndex, docValueFields, eventFields, filterQuery, timerange: { from, to } }) => { + const filter = [ + ...createQueryFilterClauses(filterQuery), + { term: { 'event.type': 'indicator' } }, + { + range: { + '@timestamp': { + gte: from, + lte: to, + format: 'strict_date_optional_time', + }, }, }, - }, - ]; + ]; - return { - allowNoIndices: true, - ignoreUnavailable: true, - index: defaultIndex, - body: { - _source: false, - ...(!isEmpty(docValueFields) && { docvalue_fields: docValueFields }), - fields: ['*'], - query: { - bool: { - should: buildIndicatorShouldClauses(eventFields), - filter, - minimum_should_match: 1, + return { + allowNoIndices: true, + ignoreUnavailable: true, + index: defaultIndex, + body: { + _source: false, + ...(!isEmpty(docValueFields) && { docvalue_fields: docValueFields }), + fields: ['*'], + query: { + bool: { + should: buildIndicatorShouldClauses(eventFields), + filter, + minimum_should_match: 1, + }, }, }, - }, + }; }; -}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/event_enrichment/response.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/event_enrichment/response.ts index 29a842d84558c..800e033a781e9 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/event_enrichment/response.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/event_enrichment/response.ts @@ -11,21 +11,18 @@ import { SecuritySolutionFactory } from '../../types'; import { buildIndicatorEnrichments, getTotalCount } from './helpers'; import { buildEventEnrichmentQuery } from './query'; -export const parseEventEnrichmentResponse: SecuritySolutionFactory['parse'] = async ( - options, - response, - deps -) => { - const inspect = { - dsl: [inspectStringifyObject(buildEventEnrichmentQuery(options))], - }; - const totalCount = getTotalCount(response.rawResponse.hits.total); - const enrichments = buildIndicatorEnrichments(response.rawResponse.hits.hits); +export const parseEventEnrichmentResponse: SecuritySolutionFactory['parse'] = + async (options, response, deps) => { + const inspect = { + dsl: [inspectStringifyObject(buildEventEnrichmentQuery(options))], + }; + const totalCount = getTotalCount(response.rawResponse.hits.total); + const enrichments = buildIndicatorEnrichments(response.rawResponse.hits.hits); - return { - ...response, - enrichments, - inspect, - totalCount, + return { + ...response, + enrichments, + inspect, + totalCount, + }; }; -}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/__mocks__/index.ts index cc97a5f0cacef..e0473c9501b0c 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/__mocks__/index.ts @@ -441,10 +441,10 @@ export const mockOptions: HostDetailsRequestOptions = { from: '2020-09-02T15:17:13.678Z', to: '2020-09-03T15:17:13.678Z', }, - sort: ({ + sort: { direction: Direction.desc, field: 'success', - } as unknown) as SortField, + } as unknown as SortField, params: {}, hostName: 'bastion00.siem.estc.dev', } as HostDetailsRequestOptions; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/index.ts index 90c01a2346eab..88c8bed1dbceb 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/kpi/authentications/index.ts @@ -22,92 +22,94 @@ import { } from './helpers'; import { buildHostsKpiAuthenticationsQueryEntities } from './query.hosts_kpi_authentications_entities.dsl'; -export const hostsKpiAuthentications: SecuritySolutionFactory = { - buildDsl: (options: HostsKpiAuthenticationsRequestOptions) => - buildHostsKpiAuthenticationsQuery(options), - parse: async ( - options: HostsKpiAuthenticationsRequestOptions, - response: IEsSearchResponse - ): Promise => { - const inspect = { - dsl: [inspectStringifyObject(buildHostsKpiAuthenticationsQuery(options))], - }; +export const hostsKpiAuthentications: SecuritySolutionFactory = + { + buildDsl: (options: HostsKpiAuthenticationsRequestOptions) => + buildHostsKpiAuthenticationsQuery(options), + parse: async ( + options: HostsKpiAuthenticationsRequestOptions, + response: IEsSearchResponse + ): Promise => { + const inspect = { + dsl: [inspectStringifyObject(buildHostsKpiAuthenticationsQuery(options))], + }; - const authenticationsSuccessHistogram = getOr( - null, - 'aggregations.authentication_success_histogram.buckets', - response.rawResponse - ); - const authenticationsFailureHistogram = getOr( - null, - 'aggregations.authentication_failure_histogram.buckets', - response.rawResponse - ); - - return { - ...response, - inspect, - authenticationsSuccess: getOr( + const authenticationsSuccessHistogram = getOr( null, - 'aggregations.authentication_success.doc_count', + 'aggregations.authentication_success_histogram.buckets', response.rawResponse - ), - authenticationsSuccessHistogram: formatAuthenticationsHistogramData( - authenticationsSuccessHistogram - ), - authenticationsFailure: getOr( + ); + const authenticationsFailureHistogram = getOr( null, - 'aggregations.authentication_failure.doc_count', + 'aggregations.authentication_failure_histogram.buckets', response.rawResponse - ), - authenticationsFailureHistogram: formatAuthenticationsHistogramData( - authenticationsFailureHistogram - ), - }; - }, -}; + ); -export const hostsKpiAuthenticationsEntities: SecuritySolutionFactory = { - buildDsl: (options: HostsKpiAuthenticationsRequestOptions) => - buildHostsKpiAuthenticationsQueryEntities(options), - parse: async ( - options: HostsKpiAuthenticationsRequestOptions, - response: IEsSearchResponse - ): Promise => { - const inspect = { - dsl: [inspectStringifyObject(buildHostsKpiAuthenticationsQueryEntities(options))], - }; + return { + ...response, + inspect, + authenticationsSuccess: getOr( + null, + 'aggregations.authentication_success.doc_count', + response.rawResponse + ), + authenticationsSuccessHistogram: formatAuthenticationsHistogramData( + authenticationsSuccessHistogram + ), + authenticationsFailure: getOr( + null, + 'aggregations.authentication_failure.doc_count', + response.rawResponse + ), + authenticationsFailureHistogram: formatAuthenticationsHistogramData( + authenticationsFailureHistogram + ), + }; + }, + }; - const authenticationsSuccessHistogram = getOr( - null, - 'aggregations.authentication_success_histogram.buckets', - response.rawResponse - ); - const authenticationsFailureHistogram = getOr( - null, - 'aggregations.authentication_failure_histogram.buckets', - response.rawResponse - ); +export const hostsKpiAuthenticationsEntities: SecuritySolutionFactory = + { + buildDsl: (options: HostsKpiAuthenticationsRequestOptions) => + buildHostsKpiAuthenticationsQueryEntities(options), + parse: async ( + options: HostsKpiAuthenticationsRequestOptions, + response: IEsSearchResponse + ): Promise => { + const inspect = { + dsl: [inspectStringifyObject(buildHostsKpiAuthenticationsQueryEntities(options))], + }; - return { - ...response, - inspect, - authenticationsSuccess: getOr( + const authenticationsSuccessHistogram = getOr( null, - 'aggregations.authentication_success.value', + 'aggregations.authentication_success_histogram.buckets', response.rawResponse - ), - authenticationsSuccessHistogram: formatAuthenticationsHistogramDataEntities( - authenticationsSuccessHistogram - ), - authenticationsFailure: getOr( + ); + const authenticationsFailureHistogram = getOr( null, - 'aggregations.authentication_failure.value', + 'aggregations.authentication_failure_histogram.buckets', response.rawResponse - ), - authenticationsFailureHistogram: formatAuthenticationsHistogramDataEntities( - authenticationsFailureHistogram - ), - }; - }, -}; + ); + + return { + ...response, + inspect, + authenticationsSuccess: getOr( + null, + 'aggregations.authentication_success.value', + response.rawResponse + ), + authenticationsSuccessHistogram: formatAuthenticationsHistogramDataEntities( + authenticationsSuccessHistogram + ), + authenticationsFailure: getOr( + null, + 'aggregations.authentication_failure.value', + response.rawResponse + ), + authenticationsFailureHistogram: formatAuthenticationsHistogramDataEntities( + authenticationsFailureHistogram + ), + }; + }, + }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/http/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/http/__mocks__/index.ts index b34027338e2ba..9d52eb638eac6 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/http/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/http/__mocks__/index.ts @@ -171,8 +171,7 @@ export const mockSearchStrategyResponse: IEsSearchResponse = { }, }, { - key: - '/.kibana-task-manager-xavier-m/_update_by_query?ignore_unavailable=true&refresh=true&max_docs=10&conflicts=proceed', + key: '/.kibana-task-manager-xavier-m/_update_by_query?ignore_unavailable=true&refresh=true&max_docs=10&conflicts=proceed', doc_count: 28715, methods: { doc_count_error_upper_bound: 0, @@ -208,8 +207,7 @@ export const mockSearchStrategyResponse: IEsSearchResponse = { }, }, { - key: - '/.kibana-task-manager-andrewg-local-testing-7-9-ff/_update_by_query?ignore_unavailable=true&refresh=true&max_docs=10&conflicts=proceed', + key: '/.kibana-task-manager-andrewg-local-testing-7-9-ff/_update_by_query?ignore_unavailable=true&refresh=true&max_docs=10&conflicts=proceed', doc_count: 28161, methods: { doc_count_error_upper_bound: 0, @@ -505,15 +503,13 @@ export const formattedSearchStrategyResponse = { }, { node: { - _id: - '/.kibana-task-manager-xavier-m/_update_by_query?ignore_unavailable=true&refresh=true&max_docs=10&conflicts=proceed', + _id: '/.kibana-task-manager-xavier-m/_update_by_query?ignore_unavailable=true&refresh=true&max_docs=10&conflicts=proceed', domains: ['es.siem.estc.dev:9200'], methods: ['POST'], statuses: ['200'], lastHost: 'bastion00.siem.estc.dev', lastSourceIp: '24.168.52.229', - path: - '/.kibana-task-manager-xavier-m/_update_by_query?ignore_unavailable=true&refresh=true&max_docs=10&conflicts=proceed', + path: '/.kibana-task-manager-xavier-m/_update_by_query?ignore_unavailable=true&refresh=true&max_docs=10&conflicts=proceed', requestCount: 28715, }, cursor: { @@ -524,15 +520,13 @@ export const formattedSearchStrategyResponse = { }, { node: { - _id: - '/.kibana-task-manager-andrewg-local-testing-7-9-ff/_update_by_query?ignore_unavailable=true&refresh=true&max_docs=10&conflicts=proceed', + _id: '/.kibana-task-manager-andrewg-local-testing-7-9-ff/_update_by_query?ignore_unavailable=true&refresh=true&max_docs=10&conflicts=proceed', domains: ['es.siem.estc.dev:9200'], methods: ['POST'], statuses: ['200'], lastHost: 'bastion00.siem.estc.dev', lastSourceIp: '67.173.227.94', - path: - '/.kibana-task-manager-andrewg-local-testing-7-9-ff/_update_by_query?ignore_unavailable=true&refresh=true&max_docs=10&conflicts=proceed', + path: '/.kibana-task-manager-andrewg-local-testing-7-9-ff/_update_by_query?ignore_unavailable=true&refresh=true&max_docs=10&conflicts=proceed', requestCount: 28161, }, cursor: { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/network_events/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/network_events/index.ts index 5f4db9591dbf6..ca29aac1bb6d5 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/network_events/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/network_events/index.ts @@ -35,22 +35,23 @@ export const networkKpiNetworkEvents: SecuritySolutionFactory = { - buildDsl: (options: NetworkKpiNetworkEventsRequestOptions) => - buildNetworkEventsQueryEntities(options), - parse: async ( - options: NetworkKpiNetworkEventsRequestOptions, - response: IEsSearchResponse - ): Promise => { - const inspect = { - dsl: [inspectStringifyObject(buildNetworkEventsQueryEntities(options))], - }; +export const networkKpiNetworkEventsEntities: SecuritySolutionFactory = + { + buildDsl: (options: NetworkKpiNetworkEventsRequestOptions) => + buildNetworkEventsQueryEntities(options), + parse: async ( + options: NetworkKpiNetworkEventsRequestOptions, + response: IEsSearchResponse + ): Promise => { + const inspect = { + dsl: [inspectStringifyObject(buildNetworkEventsQueryEntities(options))], + }; - return { - ...response, - inspect, - // @ts-expect-error code doesn't handle TotalHits - networkEvents: response.rawResponse.aggregations?.events?.value ?? null, - }; - }, -}; + return { + ...response, + inspect, + // @ts-expect-error code doesn't handle TotalHits + networkEvents: response.rawResponse.aggregations?.events?.value ?? null, + }; + }, + }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/tls_handshakes/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/tls_handshakes/index.ts index 016abdb10f935..f911eaa0c406b 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/tls_handshakes/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/tls_handshakes/index.ts @@ -35,22 +35,23 @@ export const networkKpiTlsHandshakes: SecuritySolutionFactory = { - buildDsl: (options: NetworkKpiTlsHandshakesRequestOptions) => - buildTlsHandshakeQueryEntities(options), - parse: async ( - options: NetworkKpiTlsHandshakesRequestOptions, - response: IEsSearchResponse - ): Promise => { - const inspect = { - dsl: [inspectStringifyObject(buildTlsHandshakeQueryEntities(options))], - }; +export const networkKpiTlsHandshakesEntities: SecuritySolutionFactory = + { + buildDsl: (options: NetworkKpiTlsHandshakesRequestOptions) => + buildTlsHandshakeQueryEntities(options), + parse: async ( + options: NetworkKpiTlsHandshakesRequestOptions, + response: IEsSearchResponse + ): Promise => { + const inspect = { + dsl: [inspectStringifyObject(buildTlsHandshakeQueryEntities(options))], + }; - return { - ...response, - inspect, - // @ts-expect-error code doesn't handle TotalHits - tlsHandshakes: response.rawResponse.aggregations?.tls?.value ?? null, - }; - }, -}; + return { + ...response, + inspect, + // @ts-expect-error code doesn't handle TotalHits + tlsHandshakes: response.rawResponse.aggregations?.tls?.value ?? null, + }; + }, + }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/unique_private_ips/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/unique_private_ips/index.ts index 6eaa9ac8f2214..ffe53c95efaf2 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/unique_private_ips/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/kpi/unique_private_ips/index.ts @@ -19,90 +19,92 @@ import { formatHistogramData } from '../common'; import { buildUniquePrivateIpsQuery } from './query.network_kpi_unique_private_ips.dsl'; import { buildUniquePrivateIpsQueryEntities } from './query.network_kpi_unique_private_ips_entities.dsl'; -export const networkKpiUniquePrivateIps: SecuritySolutionFactory = { - // @ts-expect-error auto_date_histogram.buckets is incompatible - buildDsl: (options: NetworkKpiUniquePrivateIpsRequestOptions) => - buildUniquePrivateIpsQuery(options), - parse: async ( - options: NetworkKpiUniquePrivateIpsRequestOptions, - response: IEsSearchResponse - ): Promise => { - const inspect = { - dsl: [inspectStringifyObject(buildUniquePrivateIpsQuery(options))], - }; +export const networkKpiUniquePrivateIps: SecuritySolutionFactory = + { + // @ts-expect-error auto_date_histogram.buckets is incompatible + buildDsl: (options: NetworkKpiUniquePrivateIpsRequestOptions) => + buildUniquePrivateIpsQuery(options), + parse: async ( + options: NetworkKpiUniquePrivateIpsRequestOptions, + response: IEsSearchResponse + ): Promise => { + const inspect = { + dsl: [inspectStringifyObject(buildUniquePrivateIpsQuery(options))], + }; - const uniqueSourcePrivateIpsHistogram = getOr( - null, - 'aggregations.source.histogram.buckets', - response.rawResponse - ); - const uniqueDestinationPrivateIpsHistogram = getOr( - null, - 'aggregations.destination.histogram.buckets', - response.rawResponse - ); - - return { - ...response, - inspect, - uniqueSourcePrivateIps: getOr( + const uniqueSourcePrivateIpsHistogram = getOr( null, - 'aggregations.source.unique_private_ips.value', + 'aggregations.source.histogram.buckets', response.rawResponse - ), - uniqueDestinationPrivateIps: getOr( + ); + const uniqueDestinationPrivateIpsHistogram = getOr( null, - 'aggregations.destination.unique_private_ips.value', + 'aggregations.destination.histogram.buckets', response.rawResponse - ), - uniqueSourcePrivateIpsHistogram: formatHistogramData(uniqueSourcePrivateIpsHistogram), - uniqueDestinationPrivateIpsHistogram: formatHistogramData( - uniqueDestinationPrivateIpsHistogram - ), - }; - }, -}; + ); -export const networkKpiUniquePrivateIpsEntities: SecuritySolutionFactory = { - // @ts-expect-error auto_date_histogram.buckets is incompatible - buildDsl: (options: NetworkKpiUniquePrivateIpsRequestOptions) => - buildUniquePrivateIpsQueryEntities(options), - parse: async ( - options: NetworkKpiUniquePrivateIpsRequestOptions, - response: IEsSearchResponse - ): Promise => { - const inspect = { - dsl: [inspectStringifyObject(buildUniquePrivateIpsQueryEntities(options))], - }; + return { + ...response, + inspect, + uniqueSourcePrivateIps: getOr( + null, + 'aggregations.source.unique_private_ips.value', + response.rawResponse + ), + uniqueDestinationPrivateIps: getOr( + null, + 'aggregations.destination.unique_private_ips.value', + response.rawResponse + ), + uniqueSourcePrivateIpsHistogram: formatHistogramData(uniqueSourcePrivateIpsHistogram), + uniqueDestinationPrivateIpsHistogram: formatHistogramData( + uniqueDestinationPrivateIpsHistogram + ), + }; + }, + }; - const uniqueSourcePrivateIpsHistogram = getOr( - null, - 'aggregations.source.histogram.buckets', - response.rawResponse - ); - const uniqueDestinationPrivateIpsHistogram = getOr( - null, - 'aggregations.destination.histogram.buckets', - response.rawResponse - ); +export const networkKpiUniquePrivateIpsEntities: SecuritySolutionFactory = + { + // @ts-expect-error auto_date_histogram.buckets is incompatible + buildDsl: (options: NetworkKpiUniquePrivateIpsRequestOptions) => + buildUniquePrivateIpsQueryEntities(options), + parse: async ( + options: NetworkKpiUniquePrivateIpsRequestOptions, + response: IEsSearchResponse + ): Promise => { + const inspect = { + dsl: [inspectStringifyObject(buildUniquePrivateIpsQueryEntities(options))], + }; - return { - ...response, - inspect, - uniqueSourcePrivateIps: getOr( + const uniqueSourcePrivateIpsHistogram = getOr( null, - 'aggregations.source.unique_private_ips.value', + 'aggregations.source.histogram.buckets', response.rawResponse - ), - uniqueDestinationPrivateIps: getOr( + ); + const uniqueDestinationPrivateIpsHistogram = getOr( null, - 'aggregations.destination.unique_private_ips.value', + 'aggregations.destination.histogram.buckets', response.rawResponse - ), - uniqueSourcePrivateIpsHistogram: formatHistogramData(uniqueSourcePrivateIpsHistogram), - uniqueDestinationPrivateIpsHistogram: formatHistogramData( - uniqueDestinationPrivateIpsHistogram - ), - }; - }, -}; + ); + + return { + ...response, + inspect, + uniqueSourcePrivateIps: getOr( + null, + 'aggregations.source.unique_private_ips.value', + response.rawResponse + ), + uniqueDestinationPrivateIps: getOr( + null, + 'aggregations.destination.unique_private_ips.value', + response.rawResponse + ), + uniqueSourcePrivateIpsHistogram: formatHistogramData(uniqueSourcePrivateIpsHistogram), + uniqueDestinationPrivateIpsHistogram: formatHistogramData( + uniqueDestinationPrivateIpsHistogram + ), + }; + }, + }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/tls/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/tls/__mocks__/index.ts index 8616a2ef14856..41cf691e00f99 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/tls/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/tls/__mocks__/index.ts @@ -81,7 +81,7 @@ export const formattedSearchStrategyResponse = { issuers: { terms: { field: 'tls.server.issuer' } }, subjects: { terms: { field: 'tls.server.subject' } }, not_after: { terms: { field: 'tls.server.not_after' } }, - ja3: { terms: { field: 'tls.server.ja3s' } }, + ja3: { terms: { field: 'tls.client.ja3s' } }, }, }, }, @@ -136,7 +136,7 @@ export const expectedDsl = { issuers: { terms: { field: 'tls.server.issuer' } }, subjects: { terms: { field: 'tls.server.subject' } }, not_after: { terms: { field: 'tls.server.not_after' } }, - ja3: { terms: { field: 'tls.server.ja3s' } }, + ja3: { terms: { field: 'tls.client.ja3s' } }, }, }, }, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/tls/query.tls_network.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/tls/query.tls_network.dsl.ts index be60b33ae2d22..7f3f649ed965a 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/tls/query.tls_network.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/tls/query.tls_network.dsl.ts @@ -47,7 +47,7 @@ const getAggs = (querySize: number, sort: SortField) => ({ }, ja3: { terms: { - field: 'tls.server.ja3s', + field: 'tls.client.ja3s', }, }, }, diff --git a/x-pack/plugins/security_solution/server/usage/collector.ts b/x-pack/plugins/security_solution/server/usage/collector.ts index bb09a2a063d33..5402dd4c375a8 100644 --- a/x-pack/plugins/security_solution/server/usage/collector.ts +++ b/x-pack/plugins/security_solution/server/usage/collector.ts @@ -398,7 +398,7 @@ export const registerCollector: RegisterCollector = ({ isReady: () => true, fetch: async ({ esClient }: CollectorFetchContext): Promise => { const internalSavedObjectsClient = await getInternalSavedObjectsClient(core); - const soClient = (internalSavedObjectsClient as unknown) as SavedObjectsClientContract; + const soClient = internalSavedObjectsClient as unknown as SavedObjectsClientContract; return { detectionMetrics: diff --git a/x-pack/plugins/security_solution/server/usage/detections/detections.test.ts b/x-pack/plugins/security_solution/server/usage/detections/detections.test.ts index ab0267d8b2787..86c77f8febaf6 100644 --- a/x-pack/plugins/security_solution/server/usage/detections/detections.test.ts +++ b/x-pack/plugins/security_solution/server/usage/detections/detections.test.ts @@ -313,10 +313,10 @@ describe('Detections Usage and Metrics', () => { }); it('returns an empty array if there is no data', async () => { - mlMock.anomalyDetectorsProvider.mockReturnValue(({ + mlMock.anomalyDetectorsProvider.mockReturnValue({ jobs: null, jobStats: null, - } as unknown) as ReturnType); + } as unknown as ReturnType); const result = await fetchDetectionsMetrics('', '', esClientMock, savedObjectsClient, mlMock); expect(result).toEqual( @@ -329,9 +329,9 @@ describe('Detections Usage and Metrics', () => { it('returns an ml job telemetry object from anomaly detectors provider', async () => { const mockJobSummary = jest.fn().mockResolvedValue(getMockJobSummaryResponse()); const mockListModules = jest.fn().mockResolvedValue(getMockListModulesResponse()); - mlMock.modulesProvider.mockReturnValue(({ + mlMock.modulesProvider.mockReturnValue({ listModules: mockListModules, - } as unknown) as ReturnType); + } as unknown as ReturnType); mlMock.jobServiceProvider.mockReturnValue({ jobsSummary: mockJobSummary, }); @@ -341,11 +341,11 @@ describe('Detections Usage and Metrics', () => { .fn() .mockResolvedValue(getMockMlDatafeedStatsResponse()); - mlMock.anomalyDetectorsProvider.mockReturnValue(({ + mlMock.anomalyDetectorsProvider.mockReturnValue({ jobs: mockJobsResponse, jobStats: mockJobStatsResponse, datafeedStats: mockDatafeedStatsResponse, - } as unknown) as ReturnType); + } as unknown as ReturnType); const result = await fetchDetectionsMetrics('', '', esClientMock, savedObjectsClient, mlMock); diff --git a/x-pack/plugins/security_solution/server/utils/build_validation/route_validation.ts b/x-pack/plugins/security_solution/server/utils/build_validation/route_validation.ts index 5dfa74c14bf72..72f6c114c1c39 100644 --- a/x-pack/plugins/security_solution/server/utils/build_validation/route_validation.ts +++ b/x-pack/plugins/security_solution/server/utils/build_validation/route_validation.ts @@ -26,34 +26,30 @@ type RequestValidationResult = error: RouteValidationError; }; -export const buildRouteValidation = >( - schema: T -): RouteValidationFunction => ( - inputValue: unknown, - validationResult: RouteValidationResultFactory -) => - pipe( - schema.decode(inputValue), - (decoded) => exactCheck(inputValue, decoded), - fold>( - (errors: rt.Errors) => validationResult.badRequest(formatErrors(errors).join()), - (validatedInput: A) => validationResult.ok(validatedInput) - ) - ); +export const buildRouteValidation = + >(schema: T): RouteValidationFunction => + (inputValue: unknown, validationResult: RouteValidationResultFactory) => + pipe( + schema.decode(inputValue), + (decoded) => exactCheck(inputValue, decoded), + fold>( + (errors: rt.Errors) => validationResult.badRequest(formatErrors(errors).join()), + (validatedInput: A) => validationResult.ok(validatedInput) + ) + ); -export const buildRouteValidationWithExcess = < - T extends rt.InterfaceType | GenericIntersectionC | rt.PartialType, - A = rt.TypeOf ->( - schema: T -): RouteValidationFunction => ( - inputValue: unknown, - validationResult: RouteValidationResultFactory -) => - pipe( - excess(schema).decode(inputValue), - fold>( - (errors: rt.Errors) => validationResult.badRequest(formatErrors(errors).join()), - (validatedInput: A) => validationResult.ok(validatedInput) - ) - ); +export const buildRouteValidationWithExcess = + < + T extends rt.InterfaceType | GenericIntersectionC | rt.PartialType, + A = rt.TypeOf + >( + schema: T + ): RouteValidationFunction => + (inputValue: unknown, validationResult: RouteValidationResultFactory) => + pipe( + excess(schema).decode(inputValue), + fold>( + (errors: rt.Errors) => validationResult.badRequest(formatErrors(errors).join()), + (validatedInput: A) => validationResult.ok(validatedInput) + ) + ); diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/setup_environment.tsx b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/setup_environment.tsx index 3f066875e880c..66ba21b136816 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/setup_environment.tsx +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/setup_environment.tsx @@ -70,8 +70,9 @@ export const setupEnvironment = () => { this.terminate = () => {}; }; -export const WithAppDependencies = (Comp: any) => (props: any) => ( - - - -); +export const WithAppDependencies = (Comp: any) => (props: any) => + ( + + + + ); diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/policy_edit.test.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/policy_edit.test.ts index 0c6fa8d28ad8d..7d5d605b216bc 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/policy_edit.test.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/policy_edit.test.ts @@ -186,15 +186,8 @@ describe('', () => { const latestRequest = server.requests[server.requests.length - 1]; - const { - name, - isManagedPolicy, - schedule, - repository, - retention, - config, - snapshotName, - } = POLICY_EDIT; + const { name, isManagedPolicy, schedule, repository, retention, config, snapshotName } = + POLICY_EDIT; const expected = { name, diff --git a/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_logistics.tsx b/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_logistics.tsx index 06c65a1713692..34a36961d4281 100644 --- a/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_logistics.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_logistics.tsx @@ -55,9 +55,8 @@ export const PolicyStepLogistics: React.FunctionComponent = ({ const { i18n, history } = useServices(); const { docLinks } = useCore(); - const [showRepositoryNotFoundWarning, setShowRepositoryNotFoundWarning] = useState( - false - ); + const [showRepositoryNotFoundWarning, setShowRepositoryNotFoundWarning] = + useState(false); // State for touched inputs const [touched, setTouched] = useState({ diff --git a/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_settings/fields/indices_and_data_streams_field/helpers.tsx b/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_settings/fields/indices_and_data_streams_field/helpers.tsx index 99eb8edfb711d..4a883f779c16f 100644 --- a/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_settings/fields/indices_and_data_streams_field/helpers.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_settings/fields/indices_and_data_streams_field/helpers.tsx @@ -22,23 +22,19 @@ export const mapSelectionToIndicesOptions = ({ indices: string[]; }): EuiSelectableOption[] => { return orderDataStreamsAndIndices({ - dataStreams: dataStreams.map( - (dataStream): EuiSelectableOption => { - return { - label: dataStream, - append: , - checked: allSelected || selection.includes(dataStream) ? 'on' : undefined, - }; - } - ), - indices: indices.map( - (index): EuiSelectableOption => { - return { - label: index, - checked: allSelected || selection.includes(index) ? 'on' : undefined, - }; - } - ), + dataStreams: dataStreams.map((dataStream): EuiSelectableOption => { + return { + label: dataStream, + append: , + checked: allSelected || selection.includes(dataStream) ? 'on' : undefined, + }; + }), + indices: indices.map((index): EuiSelectableOption => { + return { + label: index, + checked: allSelected || selection.includes(index) ? 'on' : undefined, + }; + }), }); }; diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/policy_edit/policy_edit.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/policy_edit/policy_edit.tsx index 4ab0f15cc5523..cbf63ddaac014 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/policy_edit/policy_edit.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/policy_edit/policy_edit.tsx @@ -60,9 +60,11 @@ export const PolicyEdit: React.FunctionComponent { diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/restore_snapshot/restore_snapshot.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/restore_snapshot/restore_snapshot.tsx index 0f950ef3234ba..be889323122f0 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/restore_snapshot/restore_snapshot.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/restore_snapshot/restore_snapshot.tsx @@ -40,10 +40,11 @@ export const RestoreSnapshot: React.FunctionComponent({}); // Load snapshot - const { error: snapshotError, isLoading: loadingSnapshot, data: snapshotData } = useLoadSnapshot( - repositoryName, - snapshotId - ); + const { + error: snapshotError, + isLoading: loadingSnapshot, + data: snapshotData, + } = useLoadSnapshot(repositoryName, snapshotId); // Update repository state when data is loaded useEffect(() => { diff --git a/x-pack/plugins/snapshot_restore/public/application/services/validation/validate_restore.ts b/x-pack/plugins/snapshot_restore/public/application/services/validation/validate_restore.ts index d8cbdf824ffc9..a671e30b8f9f3 100644 --- a/x-pack/plugins/snapshot_restore/public/application/services/validation/validate_restore.ts +++ b/x-pack/plugins/snapshot_restore/public/application/services/validation/validate_restore.ts @@ -20,13 +20,8 @@ const isStringEmpty = (str: string | null): boolean => { export const validateRestore = (restoreSettings: RestoreSettings): RestoreValidation => { const i18n = textService.i18n; - const { - indices, - renamePattern, - renameReplacement, - indexSettings, - ignoreIndexSettings, - } = restoreSettings; + const { indices, renamePattern, renameReplacement, indexSettings, ignoreIndexSettings } = + restoreSettings; const validation: RestoreValidation = { isValid: true, diff --git a/x-pack/plugins/snapshot_restore/public/locator.ts b/x-pack/plugins/snapshot_restore/public/locator.ts index ba57446a03887..fac7d389d8e96 100644 --- a/x-pack/plugins/snapshot_restore/public/locator.ts +++ b/x-pack/plugins/snapshot_restore/public/locator.ts @@ -22,7 +22,8 @@ export interface SnapshotRestoreLocatorDefinitionDependencies { } export class SnapshotRestoreLocatorDefinition - implements LocatorDefinition { + implements LocatorDefinition +{ constructor(protected readonly deps: SnapshotRestoreLocatorDefinitionDependencies) {} public readonly id = SNAPSHOT_RESTORE_LOCATOR_ID; diff --git a/x-pack/plugins/snapshot_restore/server/index.ts b/x-pack/plugins/snapshot_restore/server/index.ts index d85d03923df1f..e10bffd6073d2 100644 --- a/x-pack/plugins/snapshot_restore/server/index.ts +++ b/x-pack/plugins/snapshot_restore/server/index.ts @@ -12,6 +12,7 @@ import { configSchema, SnapshotRestoreConfig } from './config'; export const plugin = (ctx: PluginInitializerContext) => new SnapshotRestoreServerPlugin(ctx); export const config: PluginConfigDescriptor = { + deprecations: ({ deprecate }) => [deprecate('enabled', '8.0.0')], schema: configSchema, exposeToBrowser: { slm_ui: true, diff --git a/x-pack/plugins/snapshot_restore/server/routes/api/policy.ts b/x-pack/plugins/snapshot_restore/server/routes/api/policy.ts index 52e4e7011f0d4..a93540c1ba90d 100644 --- a/x-pack/plugins/snapshot_restore/server/routes/api/policy.ts +++ b/x-pack/plugins/snapshot_restore/server/routes/api/policy.ts @@ -103,7 +103,7 @@ export function registerPolicyRoutes({ const response = await clusterClient.asCurrentUser.slm.putLifecycle({ policy_id: name, // TODO: bring {@link SlmPolicyEs['policy']} in line with {@link PutSnapshotLifecycleRequest['body']} - body: (serializePolicy(policy) as unknown) as estypes.SlmPutLifecycleRequest['body'], + body: serializePolicy(policy) as unknown as estypes.SlmPutLifecycleRequest['body'], }); return res.ok({ body: response.body }); @@ -133,7 +133,7 @@ export function registerPolicyRoutes({ const response = await clusterClient.asCurrentUser.slm.putLifecycle({ policy_id: name, // TODO: bring {@link SlmPolicyEs['policy']} in line with {@link PutSnapshotLifecycleRequest['body']} - body: (serializePolicy(policy) as unknown) as estypes.SlmPutLifecycleRequest['body'], + body: serializePolicy(policy) as unknown as estypes.SlmPutLifecycleRequest['body'], }); return res.ok({ body: response.body }); diff --git a/x-pack/plugins/snapshot_restore/server/routes/api/repositories.ts b/x-pack/plugins/snapshot_restore/server/routes/api/repositories.ts index a0799f4de8c92..6048ec3b52656 100644 --- a/x-pack/plugins/snapshot_restore/server/routes/api/repositories.ts +++ b/x-pack/plugins/snapshot_restore/server/routes/api/repositories.ts @@ -46,11 +46,10 @@ export function registerRepositoriesRoutes({ let managedRepository: ManagedRepository; try { - const { - body: repositoriesByName, - } = await clusterClient.asCurrentUser.snapshot.getRepository({ - repository: '_all', - }); + const { body: repositoriesByName } = + await clusterClient.asCurrentUser.snapshot.getRepository({ + repository: '_all', + }); repositoryNames = Object.keys(repositoriesByName); repositories = repositoryNames.map((name) => { const { type = '', settings = {} } = repositoriesByName[name]; diff --git a/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.ts b/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.ts index fb68ca5c13dbe..6838ae2700f3a 100644 --- a/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.ts +++ b/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.ts @@ -40,11 +40,10 @@ export function registerSnapshotsRoutes({ let repositories: string[] = []; try { - const { - body: repositoriesByName, - } = await clusterClient.asCurrentUser.snapshot.getRepository({ - repository: '_all', - }); + const { body: repositoriesByName } = + await clusterClient.asCurrentUser.snapshot.getRepository({ + repository: '_all', + }); repositories = Object.keys(repositoriesByName); if (repositories.length === 0) { diff --git a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_status_summary_indicator.tsx b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_status_summary_indicator.tsx index 8d9c2f17bdec6..74fa3622f88c7 100644 --- a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_status_summary_indicator.tsx +++ b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_status_summary_indicator.tsx @@ -41,12 +41,8 @@ const renderIcon = (props: Props) => { return ; } - const { - successful, - hasUnresolvableErrors, - hasMissingReferences, - hasConflicts, - } = summarizedCopyResult; + const { successful, hasUnresolvableErrors, hasMissingReferences, hasConflicts } = + summarizedCopyResult; if (successful) { return ( diff --git a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_footer.tsx b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_footer.tsx index 998b202a8d6b3..0cdff60555cef 100644 --- a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_footer.tsx +++ b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_footer.tsx @@ -38,13 +38,8 @@ const isResolvableError = ({ error: { type } }: FailedImport) => const isUnresolvableError = (failure: FailedImport) => !isResolvableError(failure); export const CopyToSpaceFlyoutFooter = (props: Props) => { - const { - copyInProgress, - conflictResolutionInProgress, - initialCopyFinished, - copyResult, - retries, - } = props; + const { copyInProgress, conflictResolutionInProgress, initialCopyFinished, copyResult, retries } = + props; let summarizedResults = { successCount: 0, diff --git a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_internal.test.tsx b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_internal.test.tsx index 2bad5757613e0..988564d0140cc 100644 --- a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_internal.test.tsx +++ b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_internal.test.tsx @@ -257,13 +257,8 @@ describe.skip('CopyToSpaceFlyout', () => { }); it('allows the form to be filled out', async () => { - const { - wrapper, - onClose, - mockSpacesManager, - mockToastNotifications, - savedObjectToCopy, - } = await setup(); + const { wrapper, onClose, mockSpacesManager, mockToastNotifications, savedObjectToCopy } = + await setup(); mockSpacesManager.copySavedObjects.mockResolvedValue({ 'space-1': { @@ -319,13 +314,8 @@ describe.skip('CopyToSpaceFlyout', () => { }); it('allows conflicts to be resolved', async () => { - const { - wrapper, - onClose, - mockSpacesManager, - mockToastNotifications, - savedObjectToCopy, - } = await setup(); + const { wrapper, onClose, mockSpacesManager, mockToastNotifications, savedObjectToCopy } = + await setup(); mockSpacesManager.copySavedObjects.mockResolvedValue({ 'space-1': { @@ -464,13 +454,8 @@ describe.skip('CopyToSpaceFlyout', () => { }); it('displays a warning when missing references are encountered', async () => { - const { - wrapper, - onClose, - mockSpacesManager, - mockToastNotifications, - savedObjectToCopy, - } = await setup(); + const { wrapper, onClose, mockSpacesManager, mockToastNotifications, savedObjectToCopy } = + await setup(); mockSpacesManager.copySavedObjects.mockResolvedValue({ 'space-1': { diff --git a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/resolve_all_conflicts.test.tsx b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/resolve_all_conflicts.test.tsx index a6d887ede432e..96fe78e237a80 100644 --- a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/resolve_all_conflicts.test.tsx +++ b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/resolve_all_conflicts.test.tsx @@ -17,7 +17,7 @@ import type { ResolveAllConflictsProps } from './resolve_all_conflicts'; import { ResolveAllConflicts } from './resolve_all_conflicts'; describe('ResolveAllConflicts', () => { - const summarizedCopyResult = ({ + const summarizedCopyResult = { objects: [ // these objects have minimal attributes to exercise test scenarios; these are not fully realistic results { type: 'type-1', id: 'id-1', conflict: undefined }, // not a conflict @@ -51,7 +51,7 @@ describe('ResolveAllConflicts', () => { }, }, ], - } as unknown) as SummarizedCopyToSpaceResult; + } as unknown as SummarizedCopyToSpaceResult; const retries: ImportRetry[] = [ { type: 'type-1', id: 'id-1', overwrite: false }, { type: 'type-5', id: 'id-5', overwrite: true, destinationId: 'dest-5b' }, diff --git a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/space_result.tsx b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/space_result.tsx index 932b8bc9254f6..ebfe0be779917 100644 --- a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/space_result.tsx +++ b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/space_result.tsx @@ -74,13 +74,8 @@ export const SpaceResultProcessing = (props: Pick) => { }; export const SpaceResult = (props: Props) => { - const { - space, - summarizedCopyResult, - retries, - onRetriesChange, - conflictResolutionInProgress, - } = props; + const { space, summarizedCopyResult, retries, onRetriesChange, conflictResolutionInProgress } = + props; const { objects } = summarizedCopyResult; const spaceHasPendingOverwrites = retries.some((r) => r.overwrite); const [destinationMap, setDestinationMap] = useState(getInitialDestinationMap(objects)); diff --git a/x-pack/plugins/spaces/public/management/edit_space/delete_spaces_button.test.tsx b/x-pack/plugins/spaces/public/management/edit_space/delete_spaces_button.test.tsx index fbfcfbe12b737..900c032c8fbfa 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/delete_spaces_button.test.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/delete_spaces_button.test.tsx @@ -29,7 +29,7 @@ describe('DeleteSpacesButton', () => { const wrapper = shallowWithIntl( diff --git a/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.test.tsx b/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.test.tsx index ba8fe84385401..465cb76c9e5bd 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.test.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.test.tsx @@ -64,7 +64,7 @@ describe('ManageSpacePage', () => { const wrapper = mountWithIntl( { const wrapper = mountWithIntl( { const wrapper = mountWithIntl( { const wrapper = mountWithIntl( Promise.reject(error)} notifications={notifications} history={history} @@ -276,7 +276,7 @@ describe('ManageSpacePage', () => { const wrapper = mountWithIntl( { const wrapper = mountWithIntl( { describe('#setup', () => { it('registers the spaces management page under the kibana section', () => { - const mockKibanaSection = ({ + const mockKibanaSection = { registerApp: jest.fn(), - } as unknown) as ManagementSection; + } as unknown as ManagementSection; const managementMockSetup = managementPluginMock.createSetupContract(); managementMockSetup.sections.section.kibana = mockKibanaSection; const deps = { @@ -57,9 +57,9 @@ describe('ManagementService', () => { describe('#stop', () => { it('disables the spaces management page', () => { const mockSpacesManagementPage = { disable: jest.fn() }; - const mockKibanaSection = ({ + const mockKibanaSection = { registerApp: jest.fn().mockReturnValue(mockSpacesManagementPage), - } as unknown) as ManagementSection; + } as unknown as ManagementSection; const managementMockSetup = managementPluginMock.createSetupContract(); managementMockSetup.sections.section.kibana = mockKibanaSection; diff --git a/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_page.test.tsx b/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_page.test.tsx index 74301bd169de4..bb1ea2914fc43 100644 --- a/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_page.test.tsx +++ b/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_page.test.tsx @@ -65,7 +65,7 @@ describe('SpacesGridPage', () => { expect( shallowWithIntl( { const wrapper = mountWithIntl( { const wrapper = mountWithIntl( { const wrapper = mountWithIntl( Promise.reject(error)} notifications={notifications} getUrlForApp={getUrlForApp} diff --git a/x-pack/plugins/spaces/public/management/spaces_management_app.tsx b/x-pack/plugins/spaces/public/management/spaces_management_app.tsx index a7a7591b94562..5d44a1cd78635 100644 --- a/x-pack/plugins/spaces/public/management/spaces_management_app.tsx +++ b/x-pack/plugins/spaces/public/management/spaces_management_app.tsx @@ -40,15 +40,8 @@ export const spacesManagementApp = Object.freeze({ title, async mount({ element, setBreadcrumbs, history }) { - const [ - [coreStart, { features }], - { SpacesGridPage }, - { ManageSpacePage }, - ] = await Promise.all([ - getStartServices(), - import('./spaces_grid'), - import('./edit_space'), - ]); + const [[coreStart, { features }], { SpacesGridPage }, { ManageSpacePage }] = + await Promise.all([getStartServices(), import('./spaces_grid'), import('./edit_space')]); const spacesBreadcrumbs = [ { diff --git a/x-pack/plugins/spaces/public/nav_control/nav_control_popover.test.tsx b/x-pack/plugins/spaces/public/nav_control/nav_control_popover.test.tsx index 241f8c72ef9ff..d1282331027c2 100644 --- a/x-pack/plugins/spaces/public/nav_control/nav_control_popover.test.tsx +++ b/x-pack/plugins/spaces/public/nav_control/nav_control_popover.test.tsx @@ -24,7 +24,7 @@ describe('NavControlPopover', () => { const wrapper = shallow( { const wrapper = mountWithIntl( { - const { - spaces, - shareOptions, - onChange, - enableCreateNewSpaceLink, - enableSpaceAgnosticBehavior, - } = props; + const { spaces, shareOptions, onChange, enableCreateNewSpaceLink, enableSpaceAgnosticBehavior } = + props; const { services } = useSpaces(); const { application, docLinks } = services; const { selectedSpaceIds, initiallySelectedSpaceIds } = shareOptions; diff --git a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_to_space_flyout_internal.test.tsx b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_to_space_flyout_internal.test.tsx index 8b435865c760f..6daea2413599a 100644 --- a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_to_space_flyout_internal.test.tsx +++ b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_to_space_flyout_internal.test.tsx @@ -320,13 +320,8 @@ describe('ShareToSpaceFlyout', () => { }); it('allows the form to be filled out to add a space', async () => { - const { - wrapper, - onClose, - mockSpacesManager, - mockToastNotifications, - savedObjectToShare, - } = await setup(); + const { wrapper, onClose, mockSpacesManager, mockToastNotifications, savedObjectToShare } = + await setup(); expect(wrapper.find(ShareToSpaceForm)).toHaveLength(1); expect(wrapper.find(EuiLoadingSpinner)).toHaveLength(0); @@ -348,13 +343,8 @@ describe('ShareToSpaceFlyout', () => { }); it('allows the form to be filled out to remove a space', async () => { - const { - wrapper, - onClose, - mockSpacesManager, - mockToastNotifications, - savedObjectToShare, - } = await setup(); + const { wrapper, onClose, mockSpacesManager, mockToastNotifications, savedObjectToShare } = + await setup(); expect(wrapper.find(ShareToSpaceForm)).toHaveLength(1); expect(wrapper.find(EuiLoadingSpinner)).toHaveLength(0); @@ -376,13 +366,8 @@ describe('ShareToSpaceFlyout', () => { }); it('allows the form to be filled out to add and remove a space', async () => { - const { - wrapper, - onClose, - mockSpacesManager, - mockToastNotifications, - savedObjectToShare, - } = await setup(); + const { wrapper, onClose, mockSpacesManager, mockToastNotifications, savedObjectToShare } = + await setup(); expect(wrapper.find(ShareToSpaceForm)).toHaveLength(1); expect(wrapper.find(EuiLoadingSpinner)).toHaveLength(0); diff --git a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_to_space_flyout_internal.tsx b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_to_space_flyout_internal.tsx index 076825e7c70aa..111968671d6bb 100644 --- a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_to_space_flyout_internal.tsx +++ b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_to_space_flyout_internal.tsx @@ -274,12 +274,8 @@ export const ShareToSpaceFlyoutInternal = (props: ShareToSpaceFlyoutProps) => { : aliasTargets.filter(({ targetSpace }) => spacesToAddSet.has(targetSpace)); return { isSelectionChanged, spacesToAdd, spacesToRemove, aliasesToDisable }; }; - const { - isSelectionChanged, - spacesToAdd, - spacesToRemove, - aliasesToDisable, - } = getSelectionChanges(); + const { isSelectionChanged, spacesToAdd, spacesToRemove, aliasesToDisable } = + getSelectionChanges(); const [showAliasesToDisable, setShowAliasesToDisable] = useState(false); const [shareInProgress, setShareInProgress] = useState(false); diff --git a/x-pack/plugins/spaces/public/space_list/space_list_internal.tsx b/x-pack/plugins/spaces/public/space_list/space_list_internal.tsx index bfe4486fafa76..fbc09ff023714 100644 --- a/x-pack/plugins/spaces/public/space_list/space_list_internal.tsx +++ b/x-pack/plugins/spaces/public/space_list/space_list_internal.tsx @@ -60,8 +60,9 @@ export const SpaceListInternal = ({ } const isSharedToAllSpaces = namespaces.includes(ALL_SPACES_ID); - const unauthorizedSpacesCount = namespaces.filter((namespace) => namespace === UNKNOWN_SPACE) - .length; + const unauthorizedSpacesCount = namespaces.filter( + (namespace) => namespace === UNKNOWN_SPACE + ).length; let displayedSpaces: SpaceTarget[]; let button: ReactNode = null; diff --git a/x-pack/plugins/spaces/public/spaces_context/context.tsx b/x-pack/plugins/spaces/public/spaces_context/context.tsx index 64df17bed5768..639960c1724a5 100644 --- a/x-pack/plugins/spaces/public/spaces_context/context.tsx +++ b/x-pack/plugins/spaces/public/spaces_context/context.tsx @@ -20,7 +20,7 @@ const context = createContext export const useSpaces = < Services extends Partial >(): SpacesReactContextValue => - useContext((context as unknown) as React.Context>); + useContext(context as unknown as React.Context>); export const createSpacesReactContext = >( services: Services, @@ -38,6 +38,6 @@ export const createSpacesReactContext = >( return { value, Provider, - Consumer: (context.Consumer as unknown) as React.Consumer>, + Consumer: context.Consumer as unknown as React.Consumer>, }; }; diff --git a/x-pack/plugins/spaces/public/spaces_context/wrapper_internal.tsx b/x-pack/plugins/spaces/public/spaces_context/wrapper_internal.tsx index 83ad69b1017d5..f0a263958c5eb 100644 --- a/x-pack/plugins/spaces/public/spaces_context/wrapper_internal.tsx +++ b/x-pack/plugins/spaces/public/spaces_context/wrapper_internal.tsx @@ -54,10 +54,10 @@ export const SpacesContextWrapperInternal = ( const { spacesManager, getStartServices, feature, children } = props; const [context, setContext] = useState | undefined>(); - const spacesDataPromise = useMemo(() => getSpacesData(spacesManager, feature), [ - spacesManager, - feature, - ]); + const spacesDataPromise = useMemo( + () => getSpacesData(spacesManager, feature), + [spacesManager, feature] + ); useEffect(() => { getStartServices().then(([coreStart]) => { diff --git a/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.mock.ts b/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.mock.ts index 3f3cc3f4d7801..18de3b0a567c4 100644 --- a/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.mock.ts +++ b/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.mock.ts @@ -12,8 +12,8 @@ import type { Space } from '../../common'; import type { SpacesManager } from './spaces_manager'; function createSpacesManagerMock() { - return ({ - onActiveSpaceChange$: (of(undefined) as unknown) as Observable, + return { + onActiveSpaceChange$: of(undefined) as unknown as Observable, getSpaces: jest.fn().mockResolvedValue([]), getSpace: jest.fn().mockResolvedValue(undefined), getActiveSpace: jest.fn().mockResolvedValue(undefined), @@ -27,7 +27,7 @@ function createSpacesManagerMock() { resolveCopySavedObjectsErrors: jest.fn().mockResolvedValue(undefined), getShareSavedObjectPermissions: jest.fn().mockResolvedValue(undefined), redirectToSpaceSelector: jest.fn().mockResolvedValue(undefined), - } as unknown) as jest.Mocked; + } as unknown as jest.Mocked; } export const spacesManagerMock = { diff --git a/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.test.ts b/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.test.ts index 13be8398da480..45c4d41112d7d 100644 --- a/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.test.ts +++ b/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.test.ts @@ -15,7 +15,7 @@ import type { PluginsStart } from '../plugin'; import { spacesServiceMock } from '../spaces_service/spaces_service.mock'; import { setupCapabilitiesSwitcher } from './capabilities_switcher'; -const features = ([ +const features = [ { id: 'feature_1', name: 'Feature 1', @@ -79,7 +79,7 @@ const features = ([ }, }, }, -] as unknown) as KibanaFeature[]; +] as unknown as KibanaFeature[]; const buildCapabilities = () => Object.freeze({ @@ -134,7 +134,7 @@ const setup = (space: Space) => { const logger = loggingSystemMock.createLogger(); const switcher = setupCapabilitiesSwitcher( - (coreSetup as unknown) as CoreSetup, + coreSetup as unknown as CoreSetup, () => spacesService, logger ); diff --git a/x-pack/plugins/spaces/server/config.test.ts b/x-pack/plugins/spaces/server/config.test.ts index a6f8c37b293ef..dfaaff832696e 100644 --- a/x-pack/plugins/spaces/server/config.test.ts +++ b/x-pack/plugins/spaces/server/config.test.ts @@ -19,7 +19,9 @@ const applyConfigDeprecations = (settings: Record = {}) => { deprecation, path: '', })), - () => ({ message }) => deprecationMessages.push(message) + () => + ({ message }) => + deprecationMessages.push(message) ); return { messages: deprecationMessages, diff --git a/x-pack/plugins/spaces/server/default_space/default_space_service.test.ts b/x-pack/plugins/spaces/server/default_space/default_space_service.test.ts index 5da11e3e27176..c082a75cc894b 100644 --- a/x-pack/plugins/spaces/server/default_space/default_space_service.test.ts +++ b/x-pack/plugins/spaces/server/default_space/default_space_service.test.ts @@ -40,7 +40,7 @@ interface SetupOpts { const setup = ({ elasticsearchStatus, savedObjectsStatus, license }: SetupOpts) => { const core = coreMock.createSetup(); const { status } = core; - status.core$ = (new Rx.BehaviorSubject({ + status.core$ = new Rx.BehaviorSubject({ elasticsearch: { level: elasticsearchStatus, summary: '', @@ -49,7 +49,7 @@ const setup = ({ elasticsearchStatus, savedObjectsStatus, license }: SetupOpts) level: savedObjectsStatus, summary: '', }, - }) as unknown) as Rx.Observable; + }) as unknown as Rx.Observable; const { savedObjects } = coreMock.createStart(); const repository = savedObjects.createInternalRepository() as jest.Mocked; @@ -73,7 +73,7 @@ const setup = ({ elasticsearchStatus, savedObjectsStatus, license }: SetupOpts) }); return { - coreStatus: (status as unknown) as { core$: Rx.BehaviorSubject }, + coreStatus: status as unknown as { core$: Rx.BehaviorSubject }, serviceStatus$, logger, license$, @@ -127,7 +127,7 @@ test(`does not initialize if savedObjects is unavailable`, async () => { test(`does not initialize if the license is unavailable`, async () => { const license = licensingMock.createLicense({ - license: ({ type: ' ', status: ' ' } as unknown) as ILicense, + license: { type: ' ', status: ' ' } as unknown as ILicense, }) as Writable; license.isAvailable = false; diff --git a/x-pack/plugins/spaces/server/lib/copy_to_spaces/copy_to_spaces.test.ts b/x-pack/plugins/spaces/server/lib/copy_to_spaces/copy_to_spaces.test.ts index 74ada21399f6e..24e6323cc9500 100644 --- a/x-pack/plugins/spaces/server/lib/copy_to_spaces/copy_to_spaces.test.ts +++ b/x-pack/plugins/spaces/server/lib/copy_to_spaces/copy_to_spaces.test.ts @@ -143,7 +143,7 @@ describe('copySavedObjectsToSpaces', () => { const response: SavedObjectsImportResponse = { success: true, successCount: expectedObjects.length, - successResults: [('Some success(es) occurred!' as unknown) as SavedObjectsImportSuccess], + successResults: ['Some success(es) occurred!' as unknown as SavedObjectsImportSuccess], warnings: [], }; diff --git a/x-pack/plugins/spaces/server/lib/copy_to_spaces/resolve_copy_conflicts.test.ts b/x-pack/plugins/spaces/server/lib/copy_to_spaces/resolve_copy_conflicts.test.ts index 581c6a9bbfe4b..374892d018db9 100644 --- a/x-pack/plugins/spaces/server/lib/copy_to_spaces/resolve_copy_conflicts.test.ts +++ b/x-pack/plugins/spaces/server/lib/copy_to_spaces/resolve_copy_conflicts.test.ts @@ -112,7 +112,7 @@ describe('resolveCopySavedObjectsToSpacesConflicts', () => { const response: SavedObjectsImportResponse = { success: true, successCount: filteredObjects.length, - successResults: [('Some success(es) occurred!' as unknown) as SavedObjectsImportSuccess], + successResults: ['Some success(es) occurred!' as unknown as SavedObjectsImportSuccess], warnings: [], }; @@ -138,10 +138,8 @@ describe('resolveCopySavedObjectsToSpacesConflicts', () => { const request = httpServerMock.createKibanaRequest(); - const resolveCopySavedObjectsToSpacesConflicts = resolveCopySavedObjectsToSpacesConflictsFactory( - savedObjects, - request - ); + const resolveCopySavedObjectsToSpacesConflicts = + resolveCopySavedObjectsToSpacesConflictsFactory(savedObjects, request); const namespace = 'sourceSpace'; const objects = [{ type: 'dashboard', id: 'my-dashboard' }]; @@ -214,7 +212,7 @@ describe('resolveCopySavedObjectsToSpacesConflicts', () => { return Promise.resolve({ success: true, successCount: filteredObjects.length, - successResults: [('Some success(es) occurred!' as unknown) as SavedObjectsImportSuccess], + successResults: ['Some success(es) occurred!' as unknown as SavedObjectsImportSuccess], warnings: [], }); }, @@ -222,10 +220,8 @@ describe('resolveCopySavedObjectsToSpacesConflicts', () => { const request = httpServerMock.createKibanaRequest(); - const resolveCopySavedObjectsToSpacesConflicts = resolveCopySavedObjectsToSpacesConflictsFactory( - savedObjects, - request - ); + const resolveCopySavedObjectsToSpacesConflicts = + resolveCopySavedObjectsToSpacesConflictsFactory(savedObjects, request); const result = await resolveCopySavedObjectsToSpacesConflicts('sourceSpace', { includeReferences: true, @@ -286,10 +282,8 @@ describe('resolveCopySavedObjectsToSpacesConflicts', () => { const request = httpServerMock.createKibanaRequest(); - const resolveCopySavedObjectsToSpacesConflicts = resolveCopySavedObjectsToSpacesConflictsFactory( - savedObjects, - request - ); + const resolveCopySavedObjectsToSpacesConflicts = + resolveCopySavedObjectsToSpacesConflictsFactory(savedObjects, request); await expect( resolveCopySavedObjectsToSpacesConflicts('sourceSpace', { diff --git a/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts b/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts index 50e9a8356b1e0..7cc7f67938d2e 100644 --- a/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts +++ b/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts @@ -61,12 +61,13 @@ describe.skip('onPostAuthInterceptor', () => { const { http, elasticsearch } = await root.setup(); // Mock esNodesCompatibility$ to prevent `root.start()` from blocking on ES version check - elasticsearch.esNodesCompatibility$ = elasticsearchServiceMock.createInternalSetup().esNodesCompatibility$; + elasticsearch.esNodesCompatibility$ = + elasticsearchServiceMock.createInternalSetup().esNodesCompatibility$; const loggingMock = loggingSystemMock.create().asLoggerFactory().get('xpack', 'spaces'); const featuresPlugin = featuresPluginMock.createSetup(); - featuresPlugin.getKibanaFeatures.mockReturnValue(([ + featuresPlugin.getKibanaFeatures.mockReturnValue([ { id: 'feature-1', name: 'feature 1', @@ -87,7 +88,7 @@ describe.skip('onPostAuthInterceptor', () => { name: 'feature 4', app: ['kibana'], }, - ] as unknown) as KibanaFeature[]); + ] as unknown as KibanaFeature[]); const mockRepository = jest.fn().mockImplementation(() => { return { @@ -142,11 +143,11 @@ describe.skip('onPostAuthInterceptor', () => { // interceptor to parse out the space id and rewrite the request's URL. Rather than duplicating that logic, // we are including the already tested interceptor here in the test chain. initSpacesOnRequestInterceptor({ - http: (http as unknown) as CoreSetup['http'], + http: http as unknown as CoreSetup['http'], }); initSpacesOnPostAuthRequestInterceptor({ - http: (http as unknown) as CoreSetup['http'], + http: http as unknown as CoreSetup['http'], log: loggingMock, features: featuresPlugin, getSpacesService: () => spacesServiceStart, diff --git a/x-pack/plugins/spaces/server/lib/request_interceptors/on_request_interceptor.test.ts b/x-pack/plugins/spaces/server/lib/request_interceptors/on_request_interceptor.test.ts index 3bc3b7833c454..abc960f7eab62 100644 --- a/x-pack/plugins/spaces/server/lib/request_interceptors/on_request_interceptor.test.ts +++ b/x-pack/plugins/spaces/server/lib/request_interceptors/on_request_interceptor.test.ts @@ -82,10 +82,11 @@ describe.skip('onRequestInterceptor', () => { await root.preboot(); const { http, elasticsearch } = await root.setup(); // Mock esNodesCompatibility$ to prevent `root.start()` from blocking on ES version check - elasticsearch.esNodesCompatibility$ = elasticsearchServiceMock.createInternalSetup().esNodesCompatibility$; + elasticsearch.esNodesCompatibility$ = + elasticsearchServiceMock.createInternalSetup().esNodesCompatibility$; initSpacesOnRequestInterceptor({ - http: (http as unknown) as CoreSetup['http'], + http: http as unknown as CoreSetup['http'], }); const router = http.createRouter('/'); diff --git a/x-pack/plugins/spaces/server/plugin.ts b/x-pack/plugins/spaces/server/plugin.ts index a7400fb71c1db..9455321c4eaa3 100644 --- a/x-pack/plugins/spaces/server/plugin.ts +++ b/x-pack/plugins/spaces/server/plugin.ts @@ -90,7 +90,8 @@ export interface SpacesPluginStart { } export class SpacesPlugin - implements Plugin { + implements Plugin +{ private readonly config$: Observable; private readonly kibanaIndexConfig$: Observable<{ kibana: { index: string } }>; diff --git a/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_mock_so_repository.ts b/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_mock_so_repository.ts index acc13be6802c2..d48233a75c59c 100644 --- a/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_mock_so_repository.ts +++ b/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_mock_so_repository.ts @@ -9,7 +9,7 @@ import type { ISavedObjectsRepository } from 'src/core/server'; import { SavedObjectsErrorHelpers } from 'src/core/server'; export const createMockSavedObjectsRepository = (spaces: any[] = []) => { - const mockSavedObjectsClientContract = ({ + const mockSavedObjectsClientContract = { get: jest.fn((type, id) => { const result = spaces.filter((s) => s.id === id); if (!result.length) { @@ -40,7 +40,7 @@ export const createMockSavedObjectsRepository = (spaces: any[] = []) => { return {}; }), deleteByNamespace: jest.fn(), - } as unknown) as jest.Mocked; + } as unknown as jest.Mocked; return mockSavedObjectsClientContract; }; diff --git a/x-pack/plugins/spaces/server/routes/api/__fixtures__/route_contexts.ts b/x-pack/plugins/spaces/server/routes/api/__fixtures__/route_contexts.ts index 591ae71b1faf2..1d2cb6f37cfbe 100644 --- a/x-pack/plugins/spaces/server/routes/api/__fixtures__/route_contexts.ts +++ b/x-pack/plugins/spaces/server/routes/api/__fixtures__/route_contexts.ts @@ -7,7 +7,7 @@ import type { RequestHandlerContext } from 'src/core/server'; -export const mockRouteContext = ({ +export const mockRouteContext = { licensing: { license: { check: jest.fn().mockReturnValue({ @@ -15,9 +15,9 @@ export const mockRouteContext = ({ }), }, }, -} as unknown) as RequestHandlerContext; +} as unknown as RequestHandlerContext; -export const mockRouteContextWithInvalidLicense = ({ +export const mockRouteContextWithInvalidLicense = { licensing: { license: { check: jest.fn().mockReturnValue({ @@ -26,4 +26,4 @@ export const mockRouteContextWithInvalidLicense = ({ }), }, }, -} as unknown) as RequestHandlerContext; +} as unknown as RequestHandlerContext; diff --git a/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.test.ts b/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.test.ts index 29cad687bf71f..f200017b620c4 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.test.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.test.ts @@ -47,11 +47,8 @@ describe('copy to space', () => { const log = loggingSystemMock.create().get('spaces'); const coreStart = coreMock.createStart(); - const { - savedObjects, - savedObjectsExporter, - savedObjectsImporter, - } = createMockSavedObjectsService(spaces); + const { savedObjects, savedObjectsExporter, savedObjectsImporter } = + createMockSavedObjectsService(spaces); coreStart.savedObjects = savedObjects; savedObjectsExporter.exportByObjects.mockImplementation(createExportSavedObjectsToStreamMock()); savedObjectsImporter.import.mockImplementation(createImportSavedObjectsFromStreamMock()); @@ -89,10 +86,8 @@ describe('copy to space', () => { usageStatsServicePromise, }); - const [ - [ctsRouteDefinition, ctsRouteHandler], - [resolveRouteDefinition, resolveRouteHandler], - ] = router.post.mock.calls; + const [[ctsRouteDefinition, ctsRouteHandler], [resolveRouteDefinition, resolveRouteHandler]] = + router.post.mock.calls; return { coreStart, @@ -412,15 +407,13 @@ describe('copy to space', () => { expect(status).toEqual(200); expect(savedObjectsImporter.resolveImportErrors).toHaveBeenCalledTimes(2); - const [ - resolveImportErrorsFirstCallOptions, - ] = savedObjectsImporter.resolveImportErrors.mock.calls[0]; + const [resolveImportErrorsFirstCallOptions] = + savedObjectsImporter.resolveImportErrors.mock.calls[0]; expect(resolveImportErrorsFirstCallOptions).toMatchObject({ namespace: 'a-space' }); - const [ - resolveImportErrorsSecondCallOptions, - ] = savedObjectsImporter.resolveImportErrors.mock.calls[1]; + const [resolveImportErrorsSecondCallOptions] = + savedObjectsImporter.resolveImportErrors.mock.calls[1]; expect(resolveImportErrorsSecondCallOptions).toMatchObject({ namespace: 'b-space' }); }); diff --git a/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.ts b/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.ts index 90f8971af006b..6815d19883d26 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.ts @@ -163,10 +163,8 @@ export function initCopyToSpacesApi(deps: ExternalRouteDeps) { usageStatsClient.incrementResolveCopySavedObjectsErrors({ headers, createNewCopies }) ); - const resolveCopySavedObjectsToSpacesConflicts = resolveCopySavedObjectsToSpacesConflictsFactory( - startServices.savedObjects, - request - ); + const resolveCopySavedObjectsToSpacesConflicts = + resolveCopySavedObjectsToSpacesConflictsFactory(startServices.savedObjects, request); const sourceSpaceId = getSpacesService().getSpaceId(request); const resolveConflictsResponse = await resolveCopySavedObjectsToSpacesConflicts( sourceSpaceId, diff --git a/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts b/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts index 85c6ce74763b2..f52432786872e 100644 --- a/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts +++ b/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts @@ -49,12 +49,12 @@ const ERROR_NAMESPACE_SPECIFIED = 'Spaces currently determines the namespaces'; const spacesClient = spacesClientMock.create(); spacesService.createSpacesClient.mockReturnValue(spacesClient); const typeRegistry = savedObjectsTypeRegistryMock.create(); - typeRegistry.getAllTypes.mockReturnValue(([ + typeRegistry.getAllTypes.mockReturnValue([ // for test purposes we only need the names of the object types { name: 'foo' }, { name: 'bar' }, { name: 'space' }, - ] as unknown) as SavedObjectsType[]); + ] as unknown as SavedObjectsType[]); const client = new SpacesSavedObjectsClient({ request, @@ -93,6 +93,32 @@ const ERROR_NAMESPACE_SPECIFIED = 'Spaces currently determines the namespaces'; }); }); + describe('#bulkResolve', () => { + test(`throws error if options.namespace is specified`, async () => { + const { client } = createSpacesSavedObjectsClient(); + + await expect(client.bulkResolve([], { namespace: 'bar' })).rejects.toThrow( + ERROR_NAMESPACE_SPECIFIED + ); + }); + + test(`supplements options with the current namespace`, async () => { + const { client, baseClient } = createSpacesSavedObjectsClient(); + const expectedReturnValue = { resolved_objects: [] }; + baseClient.bulkResolve.mockReturnValue(Promise.resolve(expectedReturnValue)); + + const options = Object.freeze({ foo: 'bar' }); + // @ts-expect-error + const actualReturnValue = await client.bulkResolve([], options); + + expect(actualReturnValue).toBe(expectedReturnValue); + expect(baseClient.bulkResolve).toHaveBeenCalledWith([], { + foo: 'bar', + namespace: currentSpace.expectedNamespace, + }); + }); + }); + describe('#resolve', () => { test(`throws error if options.namespace is specified`, async () => { const { client } = createSpacesSavedObjectsClient(); @@ -161,14 +187,14 @@ const ERROR_NAMESPACE_SPECIFIED = 'Spaces currently determines the namespaces'; typeRegistry.isShareable.mockImplementation((type) => type === 'bar'); // 'baz' is neither agnostic nor shareable, so it is isolated (namespaceType: 'single' or namespaceType: 'multiple-isolated') baseClient.bulkGet.mockResolvedValue({ - saved_objects: ([ + saved_objects: [ { type: 'foo', id: '1', key: 'val' }, { type: 'bar', id: '2', key: 'val' }, { type: 'baz', id: '3', key: 'val' }, // this should be replaced with a 400 error { type: 'foo', id: '4', key: 'val' }, { type: 'bar', id: '5', key: 'val' }, { type: 'baz', id: '6', key: 'val' }, // this should not be replaced with a 400 error because the user did not search for it in '*' all spaces - ] as unknown) as SavedObject[], + ] as unknown as SavedObject[], }); const objects = [ diff --git a/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.ts b/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.ts index 6cfd784042317..5149f8695284c 100644 --- a/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.ts +++ b/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.ts @@ -13,6 +13,7 @@ import type { SavedObjectsBaseOptions, SavedObjectsBulkCreateObject, SavedObjectsBulkGetObject, + SavedObjectsBulkResolveObject, SavedObjectsBulkUpdateObject, SavedObjectsCheckConflictsObject, SavedObjectsClientContract, @@ -92,14 +93,7 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract { this.errors = baseClient.errors; } - /** - * Check what conflicts will result when creating a given array of saved objects. This includes "unresolvable conflicts", which are - * multi-namespace objects that exist in a different namespace; such conflicts cannot be resolved/overwritten. - * - * @param objects - * @param options - */ - public async checkConflicts( + async checkConflicts( objects: SavedObjectsCheckConflictsObject[] = [], options: SavedObjectsBaseOptions = {} ) { @@ -111,18 +105,7 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract { }); } - /** - * Persists an object - * - * @param {string} type - * @param {object} attributes - * @param {object} [options={}] - * @property {string} [options.id] - force id on creation, not recommended - * @property {boolean} [options.overwrite=false] - * @property {string} [options.namespace] - * @returns {promise} - { id, type, version, attributes } - */ - public async create( + async create( type: string, attributes: T = {} as T, options: SavedObjectsCreateOptions = {} @@ -135,16 +118,7 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract { }); } - /** - * Creates multiple documents at once - * - * @param {array} objects - [{ type, id, attributes }] - * @param {object} [options={}] - * @property {boolean} [options.overwrite=false] - overwrites existing documents - * @property {string} [options.namespace] - * @returns {promise} - { saved_objects: [{ id, type, version, attributes, error: { message } }]} - */ - public async bulkCreate( + async bulkCreate( objects: Array>, options: SavedObjectsBaseOptions = {} ) { @@ -156,16 +130,7 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract { }); } - /** - * Deletes an object - * - * @param {string} type - * @param {string} id - * @param {object} [options={}] - * @property {string} [options.namespace] - * @returns {promise} - */ - public async delete(type: string, id: string, options: SavedObjectsBaseOptions = {}) { + async delete(type: string, id: string, options: SavedObjectsBaseOptions = {}) { throwErrorIfNamespaceSpecified(options); return await this.client.delete(type, id, { @@ -174,23 +139,7 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract { }); } - /** - * @param {object} [options={}] - * @property {(string|Array)} [options.type] - * @property {string} [options.search] - * @property {string} [options.defaultSearchOperator] - * @property {Array} [options.searchFields] - see Elasticsearch Simple Query String - * Query field argument for more information - * @property {integer} [options.page=1] - * @property {integer} [options.perPage=20] - * @property {string} [options.sortField] - * @property {string} [options.sortOrder] - * @property {Array} [options.fields] - * @property {string} [options.namespaces] - * @property {object} [options.hasReference] - { type, id } - * @returns {promise} - { saved_objects: [{ id, type, version, attributes }], total, per_page, page } - */ - public async find(options: SavedObjectsFindOptions) { + async find(options: SavedObjectsFindOptions) { let namespaces: string[]; try { namespaces = await this.getSearchableSpaces(options.namespaces); @@ -215,21 +164,7 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract { }); } - /** - * Returns an array of objects by id - * - * @param {array} objects - an array ids, or an array of objects containing id and optionally type - * @param {object} [options={}] - * @property {string} [options.namespace] - * @returns {promise} - { saved_objects: [{ id, type, version, attributes }] } - * @example - * - * bulkGet([ - * { id: 'one', type: 'config' }, - * { id: 'foo', type: 'index-pattern' } - * ]) - */ - public async bulkGet( + async bulkGet( objects: SavedObjectsBulkGetObject[] = [], options: SavedObjectsBaseOptions = {} ) { @@ -279,29 +214,20 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract { const actualResult = responseObjects[i]; if (isLeft(expectedResult)) { const { type, id } = expectedResult.value; - return ({ + return { type, id, error: SavedObjectsErrorHelpers.createBadRequestError( '"namespaces" can only specify a single space when used with space-isolated types' ).output.payload, - } as unknown) as SavedObject; + } as unknown as SavedObject; } return actualResult; }), }; } - /** - * Gets a single object - * - * @param {string} type - * @param {string} id - * @param {object} [options={}] - * @property {string} [options.namespace] - * @returns {promise} - { id, type, version, attributes } - */ - public async get(type: string, id: string, options: SavedObjectsBaseOptions = {}) { + async get(type: string, id: string, options: SavedObjectsBaseOptions = {}) { throwErrorIfNamespaceSpecified(options); return await this.client.get(type, id, { @@ -310,39 +236,28 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract { }); } - /** - * Resolves a single object, using any legacy URL alias if it exists - * - * @param type - The type of SavedObject to retrieve - * @param id - The ID of the SavedObject to retrieve - * @param {object} [options={}] - * @property {string} [options.namespace] - * @returns {promise} - { saved_object, outcome } - */ - public async resolve( - type: string, - id: string, + async bulkResolve( + objects: SavedObjectsBulkResolveObject[], options: SavedObjectsBaseOptions = {} ) { throwErrorIfNamespaceSpecified(options); + return await this.client.bulkResolve(objects, { + ...options, + namespace: spaceIdToNamespace(this.spaceId), + }); + } + + async resolve(type: string, id: string, options: SavedObjectsBaseOptions = {}) { + throwErrorIfNamespaceSpecified(options); + return await this.client.resolve(type, id, { ...options, namespace: spaceIdToNamespace(this.spaceId), }); } - /** - * Updates an object - * - * @param {string} type - * @param {string} id - * @param {object} [options={}] - * @property {string} options.version - ensures version matches that of persisted object - * @property {string} [options.namespace] - * @returns {promise} - */ - public async update( + async update( type: string, id: string, attributes: Partial, @@ -356,19 +271,7 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract { }); } - /** - * Updates an array of objects by id - * - * @param {array} objects - an array ids, or an array of objects containing id, type, attributes and optionally version, references and namespace - * @returns {promise} - { saved_objects: [{ id, type, version, attributes }] } - * @example - * - * bulkUpdate([ - * { id: 'one', type: 'config', attributes: { title: 'My new title'}, version: 'd7rhfk47d=' }, - * { id: 'foo', type: 'index-pattern', attributes: {} } - * ]) - */ - public async bulkUpdate( + async bulkUpdate( objects: Array> = [], options: SavedObjectsBaseOptions = {} ) { @@ -379,14 +282,7 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract { }); } - /** - * Remove outward references to given object. - * - * @param type - * @param id - * @param options - */ - public async removeReferencesTo( + async removeReferencesTo( type: string, id: string, options: SavedObjectsRemoveReferencesToOptions = {} @@ -398,13 +294,7 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract { }); } - /** - * Gets all references and transitive references of the listed objects. Ignores any object that is not a multi-namespace type. - * - * @param objects - * @param options - */ - public async collectMultiNamespaceReferences( + async collectMultiNamespaceReferences( objects: SavedObjectsCollectMultiNamespaceReferencesObject[], options: SavedObjectsCollectMultiNamespaceReferencesOptions = {} ): Promise { @@ -415,15 +305,7 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract { }); } - /** - * Updates one or more objects to add and/or remove them from specified spaces. - * - * @param objects - * @param spacesToAdd - * @param spacesToRemove - * @param options - */ - public async updateObjectsSpaces( + async updateObjectsSpaces( objects: SavedObjectsUpdateObjectsSpacesObject[], spacesToAdd: string[], spacesToRemove: string[], @@ -436,16 +318,6 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract { }); } - /** - * Opens a Point In Time (PIT) against the indices for the specified Saved Object types. - * The returned `id` can then be passed to `SavedObjects.find` to search against that PIT. - * - * @param {string|Array} type - * @param {object} [options] - {@link SavedObjectsOpenPointInTimeOptions} - * @property {string} [options.keepAlive] - * @property {string} [options.preference] - * @returns {promise} - { id: string } - */ async openPointInTimeForType( type: string | string[], options: SavedObjectsOpenPointInTimeOptions = {} @@ -471,15 +343,6 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract { }); } - /** - * Closes a Point In Time (PIT) by ID. This simply proxies the request to ES - * via the Elasticsearch client, and is included in the Saved Objects Client - * as a convenience for consumers who are using `openPointInTimeForType`. - * - * @param {string} id - ID returned from `openPointInTimeForType` - * @param {object} [options] - {@link SavedObjectsClosePointInTimeOptions} - * @returns {promise} - { succeeded: boolean; num_freed: number } - */ async closePointInTime(id: string, options: SavedObjectsClosePointInTimeOptions = {}) { throwErrorIfNamespaceSpecified(options); return await this.client.closePointInTime(id, { @@ -488,17 +351,6 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract { }); } - /** - * Returns a generator to help page through large sets of saved objects. - * - * The generator wraps calls to `SavedObjects.find` and iterates over - * multiple pages of results using `_pit` and `search_after`. This will - * open a new Point In Time (PIT), and continue paging until a set of - * results is received that's smaller than the designated `perPage`. - * - * @param {object} findOptions - {@link SavedObjectsCreatePointInTimeFinderOptions} - * @param {object} [dependencies] - {@link SavedObjectsCreatePointInTimeFinderDependencies} - */ createPointInTimeFinder( findOptions: SavedObjectsCreatePointInTimeFinderOptions, dependencies?: SavedObjectsCreatePointInTimeFinderDependencies diff --git a/x-pack/plugins/spaces/server/spaces_client/spaces_client.mock.ts b/x-pack/plugins/spaces/server/spaces_client/spaces_client.mock.ts index a4e209ff11d32..ed47aed72fba2 100644 --- a/x-pack/plugins/spaces/server/spaces_client/spaces_client.mock.ts +++ b/x-pack/plugins/spaces/server/spaces_client/spaces_client.mock.ts @@ -10,7 +10,7 @@ import { DEFAULT_SPACE_ID } from '../../common/constants'; import type { SpacesClient } from './spaces_client'; const createSpacesClientMock = () => - (({ + ({ getAll: jest.fn().mockResolvedValue([ { id: DEFAULT_SPACE_ID, @@ -30,7 +30,7 @@ const createSpacesClientMock = () => update: jest.fn().mockImplementation((space: Space) => Promise.resolve(space)), delete: jest.fn(), disableLegacyUrlAliases: jest.fn(), - } as unknown) as jest.Mocked); + } as unknown as jest.Mocked); export const spacesClientMock = { create: createSpacesClientMock, diff --git a/x-pack/plugins/spaces/server/spaces_client/spaces_client_service.test.ts b/x-pack/plugins/spaces/server/spaces_client/spaces_client_service.test.ts index 263b470f0a6f6..7ce9ea798c884 100644 --- a/x-pack/plugins/spaces/server/spaces_client/spaces_client_service.test.ts +++ b/x-pack/plugins/spaces/server/spaces_client/spaces_client_service.test.ts @@ -101,7 +101,7 @@ describe('SpacesClientService', () => { const service = new SpacesClientService(debugLogger); const setup = service.setup({ config$: Rx.of(spacesConfig) }); - const wrapper = (Symbol() as unknown) as ISpacesClient; + const wrapper = Symbol() as unknown as ISpacesClient; const clientWrapper = jest.fn().mockReturnValue(wrapper); setup.registerClientWrapper(clientWrapper); @@ -129,7 +129,7 @@ describe('SpacesClientService', () => { const customRepositoryFactory = jest.fn(); setup.setClientRepositoryFactory(customRepositoryFactory); - const wrapper = (Symbol() as unknown) as ISpacesClient; + const wrapper = Symbol() as unknown as ISpacesClient; const clientWrapper = jest.fn().mockReturnValue(wrapper); setup.registerClientWrapper(clientWrapper); diff --git a/x-pack/plugins/spaces/server/spaces_service/spaces_service.test.ts b/x-pack/plugins/spaces/server/spaces_service/spaces_service.test.ts index a6631360d14e1..7b8fd40105bc4 100644 --- a/x-pack/plugins/spaces/server/spaces_service/spaces_service.test.ts +++ b/x-pack/plugins/spaces/server/spaces_service/spaces_service.test.ts @@ -22,7 +22,7 @@ const createService = (serverBasePath: string = '') => { const coreStart = coreMock.createStart(); - const respositoryMock = ({ + const respositoryMock = { get: jest.fn().mockImplementation((type, id) => { if (type === 'space' && id === 'foo') { return Promise.resolve({ @@ -45,7 +45,7 @@ const createService = (serverBasePath: string = '') => { } throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); }), - } as unknown) as SavedObjectsRepository; + } as unknown as SavedObjectsRepository; coreStart.savedObjects.createInternalRepository.mockReturnValue(respositoryMock); coreStart.savedObjects.createScopedRepository.mockReturnValue(respositoryMock); diff --git a/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.test.ts b/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.test.ts index 655463ed64a30..bdcb8afac3009 100644 --- a/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.test.ts +++ b/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.test.ts @@ -63,9 +63,9 @@ function setup({ license$: Rx.of(license), } as LicensingPluginSetup; - const featuresSetup = ({ + const featuresSetup = { getKibanaFeatures: jest.fn().mockReturnValue(features), - } as unknown) as PluginsSetup['features']; + } as unknown as PluginsSetup['features']; const usageStatsClient = usageStatsClientMock.create(); usageStatsClient.getUsageStats.mockResolvedValue(MOCK_USAGE_STATS); diff --git a/x-pack/plugins/spaces/server/usage_stats/usage_stats_client.mock.ts b/x-pack/plugins/spaces/server/usage_stats/usage_stats_client.mock.ts index c9f73451c7553..4776be5e13fa5 100644 --- a/x-pack/plugins/spaces/server/usage_stats/usage_stats_client.mock.ts +++ b/x-pack/plugins/spaces/server/usage_stats/usage_stats_client.mock.ts @@ -8,12 +8,12 @@ import type { UsageStatsClient } from './usage_stats_client'; const createUsageStatsClientMock = () => - (({ + ({ getUsageStats: jest.fn().mockResolvedValue({}), incrementCopySavedObjects: jest.fn().mockResolvedValue(null), incrementResolveCopySavedObjectsErrors: jest.fn().mockResolvedValue(null), incrementDisableLegacyUrlAliases: jest.fn().mockResolvedValue(null), - } as unknown) as jest.Mocked); + } as unknown as jest.Mocked); export const usageStatsClientMock = { create: createUsageStatsClientMock, diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/validation.ts b/x-pack/plugins/stack_alerts/public/alert_types/es_query/validation.ts index a392563323aa7..d4ab8801fcdde 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/es_query/validation.ts +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/validation.ts @@ -10,15 +10,8 @@ import { EsQueryAlertParams } from './types'; import { ValidationResult, builtInComparators } from '../../../../triggers_actions_ui/public'; export const validateExpression = (alertParams: EsQueryAlertParams): ValidationResult => { - const { - index, - timeField, - esQuery, - size, - threshold, - timeWindowSize, - thresholdComparator, - } = alertParams; + const { index, timeField, esQuery, size, threshold, timeWindowSize, thresholdComparator } = + alertParams; const validationResult = { errors: {} }; const errors = { index: new Array(), diff --git a/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/expressions/boundary_index_expression.tsx b/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/expressions/boundary_index_expression.tsx index 76f8eaacb8789..e1eb91af5958e 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/expressions/boundary_index_expression.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/expressions/boundary_index_expression.tsx @@ -16,8 +16,8 @@ import { ES_GEO_SHAPE_TYPES, GeoContainmentAlertParams } from '../../types'; import { GeoIndexPatternSelect } from '../util_components/geo_index_pattern_select'; import { SingleFieldSelect } from '../util_components/single_field_select'; import { ExpressionWithPopover } from '../util_components/expression_with_popover'; -import { IFieldType } from '../../../../../../../../src/plugins/data/common/index_patterns/fields'; -import { IIndexPattern } from '../../../../../../../../src/plugins/data/common/index_patterns'; +import { IFieldType } from '../../../../../../../../src/plugins/data/common'; +import { IIndexPattern } from '../../../../../../../../src/plugins/data/common'; interface Props { alertParams: GeoContainmentAlertParams; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/expressions/entity_by_expression.tsx b/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/expressions/entity_by_expression.tsx index a194bd40d9931..333dd69ce50f5 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/expressions/entity_by_expression.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/expressions/entity_by_expression.tsx @@ -12,7 +12,7 @@ import _ from 'lodash'; import { IErrorObject } from '../../../../../../triggers_actions_ui/public'; import { SingleFieldSelect } from '../util_components/single_field_select'; import { ExpressionWithPopover } from '../util_components/expression_with_popover'; -import { IFieldType } from '../../../../../../../../src/plugins/data/common/index_patterns/fields'; +import { IFieldType } from '../../../../../../../../src/plugins/data/common'; interface Props { errors: IErrorObject; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/expressions/entity_index_expression.tsx b/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/expressions/entity_index_expression.tsx index a3ffe7a3ade0c..e2068ca1b178f 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/expressions/entity_index_expression.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/expressions/entity_index_expression.tsx @@ -20,8 +20,8 @@ import { ES_GEO_FIELD_TYPES } from '../../types'; import { GeoIndexPatternSelect } from '../util_components/geo_index_pattern_select'; import { SingleFieldSelect } from '../util_components/single_field_select'; import { ExpressionWithPopover } from '../util_components/expression_with_popover'; -import { IFieldType } from '../../../../../../../../src/plugins/data/common/index_patterns/fields'; -import { IIndexPattern } from '../../../../../../../../src/plugins/data/common/index_patterns'; +import { IFieldType } from '../../../../../../../../src/plugins/data/common'; +import { IIndexPattern } from '../../../../../../../../src/plugins/data/common'; interface Props { dateField: string; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/geo_containment_alert_type_expression.test.tsx b/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/geo_containment_alert_type_expression.test.tsx index 2b9218942db04..f199d2132f995 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/geo_containment_alert_type_expression.test.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/geo_containment_alert_type_expression.test.tsx @@ -37,7 +37,7 @@ test('should render EntityIndexExpression', async () => { setAlertParamsGeoField={() => {}} setAlertProperty={() => {}} setIndexPattern={() => {}} - indexPattern={('' as unknown) as IIndexPattern} + indexPattern={'' as unknown as IIndexPattern} isInvalid={false} data={dataStartMock} /> @@ -56,7 +56,7 @@ test('should render EntityIndexExpression w/ invalid flag if invalid', async () setAlertParamsGeoField={() => {}} setAlertProperty={() => {}} setIndexPattern={() => {}} - indexPattern={('' as unknown) as IIndexPattern} + indexPattern={'' as unknown as IIndexPattern} isInvalid={true} data={dataStartMock} /> @@ -70,7 +70,7 @@ test('should render BoundaryIndexExpression', async () => { {}} setBoundaryGeoField={() => {}} setBoundaryNameField={() => {}} diff --git a/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/index.tsx b/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/index.tsx index 25234126689b9..cdc5c12659ce6 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/index.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/index.tsx @@ -14,7 +14,7 @@ import { GeoContainmentAlertParams } from '../types'; import { EntityIndexExpression } from './expressions/entity_index_expression'; import { EntityByExpression } from './expressions/entity_by_expression'; import { BoundaryIndexExpression } from './expressions/boundary_index_expression'; -import { IIndexPattern } from '../../../../../../../src/plugins/data/common/index_patterns'; +import { IIndexPattern } from '../../../../../../../src/plugins/data/common'; import { esQuery, esKuery, diff --git a/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/util_components/geo_index_pattern_select.test.tsx b/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/util_components/geo_index_pattern_select.test.tsx index f587e8ed35e17..9465b1df6fb6f 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/util_components/geo_index_pattern_select.test.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/util_components/geo_index_pattern_select.test.tsx @@ -24,7 +24,7 @@ function makeMockIndexPattern(id: string, fields: unknown) { }; } -const mockIndexPatternService: IndexPatternsContract = ({ +const mockIndexPatternService: IndexPatternsContract = { get(id: string) { if (id === 'foobar_with_geopoint') { return makeMockIndexPattern(id, [{ type: 'geo_point' }]); @@ -32,12 +32,12 @@ const mockIndexPatternService: IndexPatternsContract = ({ return makeMockIndexPattern(id, [{ type: 'string' }]); } }, -} as unknown) as IndexPatternsContract; +} as unknown as IndexPatternsContract; test('should render without error after mounting', async () => { const component = shallow( {}} value={'foobar_with_geopoint'} includedGeoTypes={['geo_point']} @@ -56,7 +56,7 @@ test('should render without error after mounting', async () => { test('should render with error when index pattern does not have geo_point field', async () => { const component = shallow( {}} value={'foobar_without_geopoint'} includedGeoTypes={['geo_point']} diff --git a/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/validation.ts b/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/validation.ts index efd3be858c189..6ebab54ee6ed9 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/validation.ts +++ b/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/validation.ts @@ -10,15 +10,8 @@ import { ValidationResult } from '../../../../triggers_actions_ui/public'; import { GeoContainmentAlertParams } from './types'; export const validateExpression = (alertParams: GeoContainmentAlertParams): ValidationResult => { - const { - index, - geoField, - entity, - dateField, - boundaryType, - boundaryIndexTitle, - boundaryGeoField, - } = alertParams; + const { index, geoField, entity, dateField, boundaryType, boundaryIndexTitle, boundaryGeoField } = + alertParams; const validationResult = { errors: {} }; const errors = { index: new Array(), diff --git a/x-pack/plugins/stack_alerts/public/alert_types/threshold/visualization.test.tsx b/x-pack/plugins/stack_alerts/public/alert_types/threshold/visualization.test.tsx index 8434e5ba17dcc..fa34c25cd6154 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/threshold/visualization.test.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/threshold/visualization.test.tsx @@ -36,11 +36,11 @@ const { getThresholdAlertVisualizationData } = jest.requireMock('./index_thresho const dataMock = dataPluginMock.createStartContract(); const chartsStartMock = chartPluginMock.createStartContract(); -dataMock.fieldFormats = ({ +dataMock.fieldFormats = { getDefaultInstance: jest.fn(() => ({ convert: jest.fn((s: unknown) => JSON.stringify(s)), })), -} as unknown) as DataPublicPluginStart['fieldFormats']; +} as unknown as DataPublicPluginStart['fieldFormats']; describe('ThresholdVisualization', () => { beforeAll(() => { diff --git a/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.test.ts b/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.test.ts index 5b450ceba192a..85ecd67117ab6 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.test.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.test.ts @@ -143,7 +143,7 @@ describe('alertType', () => { alertId: uuid.v4(), startedAt: new Date(), previousStartedAt: new Date(), - services: (alertServices as unknown) as AlertServices< + services: alertServices as unknown as AlertServices< EsQueryAlertState, ActionContext, typeof ActionGroupId @@ -222,7 +222,7 @@ describe('alertType', () => { alertId: uuid.v4(), startedAt: new Date(), previousStartedAt: new Date(), - services: (alertServices as unknown) as AlertServices< + services: alertServices as unknown as AlertServices< EsQueryAlertState, ActionContext, typeof ActionGroupId @@ -301,7 +301,7 @@ describe('alertType', () => { alertId: uuid.v4(), startedAt: new Date(), previousStartedAt: new Date(), - services: (alertServices as unknown) as AlertServices< + services: alertServices as unknown as AlertServices< EsQueryAlertState, ActionContext, typeof ActionGroupId @@ -375,7 +375,7 @@ describe('alertType', () => { alertId: uuid.v4(), startedAt: new Date(), previousStartedAt: new Date(), - services: (alertServices as unknown) as AlertServices< + services: alertServices as unknown as AlertServices< EsQueryAlertState, ActionContext, typeof ActionGroupId @@ -453,7 +453,7 @@ describe('alertType', () => { alertId: uuid.v4(), startedAt: new Date(), previousStartedAt: new Date(), - services: (alertServices as unknown) as AlertServices< + services: alertServices as unknown as AlertServices< EsQueryAlertState, ActionContext, typeof ActionGroupId @@ -568,7 +568,7 @@ describe('alertType', () => { alertId: uuid.v4(), startedAt: new Date(), previousStartedAt: new Date(), - services: (alertServices as unknown) as AlertServices< + services: alertServices as unknown as AlertServices< EsQueryAlertState, ActionContext, typeof ActionGroupId @@ -652,7 +652,7 @@ describe('alertType', () => { alertId: uuid.v4(), startedAt: new Date(), previousStartedAt: new Date(), - services: (alertServices as unknown) as AlertServices< + services: alertServices as unknown as AlertServices< EsQueryAlertState, ActionContext, typeof ActionGroupId diff --git a/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.ts b/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.ts index 5e2e4699c25ca..26efdc0c056a5 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.ts @@ -25,9 +25,7 @@ export const ES_QUERY_ID = '.es-query'; export const ActionGroupId = 'query matched'; export const ConditionMetAlertInstanceId = 'query matched'; -export function getAlertType( - logger: Logger -): AlertType< +export function getAlertType(logger: Logger): AlertType< EsQueryAlertParams, never, // Only use if defining useSavedObjectReferences hook EsQueryAlertState, diff --git a/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type_params.ts b/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type_params.ts index 58d9c725cfd1b..de9c583ef885e 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type_params.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type_params.ts @@ -38,11 +38,8 @@ const betweenComparators = new Set(['between', 'notBetween']); // using direct type not allowed, circular reference, so body is typed to any function validateParams(anyParams: unknown): string | undefined { - const { - esQuery, - thresholdComparator, - threshold, - }: EsQueryAlertParams = anyParams as EsQueryAlertParams; + const { esQuery, thresholdComparator, threshold }: EsQueryAlertParams = + anyParams as EsQueryAlertParams; if (betweenComparators.has(thresholdComparator) && threshold.length === 1) { return i18n.translate('xpack.stackAlerts.esQuery.invalidThreshold2ErrorMessage', { diff --git a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/tests/geo_containment.test.ts b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/tests/geo_containment.test.ts index 5338ab0c401e5..364c484a02080 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/tests/geo_containment.test.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/tests/geo_containment.test.ts @@ -23,27 +23,26 @@ import { GeoContainmentParams, } from '../alert_type'; -const alertInstanceFactory = (contextKeys: unknown[], testAlertActionArr: unknown[]) => ( - instanceId: string -) => { - const alertInstance = alertsMock.createAlertInstanceFactory< - GeoContainmentInstanceState, - GeoContainmentInstanceContext - >(); - alertInstance.scheduleActions.mockImplementation( - (actionGroupId: string, context?: GeoContainmentInstanceContext) => { - // Check subset of alert for comparison to expected results - // @ts-ignore - const contextSubset = _.pickBy(context, (v, k) => contextKeys.includes(k)); - testAlertActionArr.push({ - actionGroupId, - instanceId, - context: contextSubset, - }); - } - ); - return alertInstance; -}; +const alertInstanceFactory = + (contextKeys: unknown[], testAlertActionArr: unknown[]) => (instanceId: string) => { + const alertInstance = alertsMock.createAlertInstanceFactory< + GeoContainmentInstanceState, + GeoContainmentInstanceContext + >(); + alertInstance.scheduleActions.mockImplementation( + (actionGroupId: string, context?: GeoContainmentInstanceContext) => { + // Check subset of alert for comparison to expected results + // @ts-ignore + const contextSubset = _.pickBy(context, (v, k) => contextKeys.includes(k)); + testAlertActionArr.push({ + actionGroupId, + instanceId, + context: contextSubset, + }); + } + ); + return alertInstance; + }; describe('geo_containment', () => { describe('transformResults', () => { diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.test.ts b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.test.ts index fb9754edae6f1..7ab382ec77172 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.test.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.test.ts @@ -171,11 +171,7 @@ describe('alertType', () => { alertId: uuid.v4(), startedAt: new Date(), previousStartedAt: new Date(), - services: (alertServices as unknown) as AlertServices< - {}, - ActionContext, - typeof ActionGroupId - >, + services: alertServices as unknown as AlertServices<{}, ActionContext, typeof ActionGroupId>, params, state: { latestTimestamp: undefined, @@ -236,7 +232,7 @@ describe('alertType', () => { alertId: uuid.v4(), startedAt: new Date(), previousStartedAt: new Date(), - services: (customAlertServices as unknown) as AlertServices< + services: customAlertServices as unknown as AlertServices< {}, ActionContext, typeof ActionGroupId @@ -301,7 +297,7 @@ describe('alertType', () => { alertId: uuid.v4(), startedAt: new Date(), previousStartedAt: new Date(), - services: (customAlertServices as unknown) as AlertServices< + services: customAlertServices as unknown as AlertServices< {}, ActionContext, typeof ActionGroupId diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.ts b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.ts index 46c3c3467cefe..983fcd4124032 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.ts @@ -165,7 +165,9 @@ export function getAlertType( interval: undefined, }; // console.log(`index_threshold: query: ${JSON.stringify(queryParams, null, 4)}`); - const result = await (await data).timeSeriesQuery({ + const result = await ( + await data + ).timeSeriesQuery({ logger, esClient, query: queryParams, diff --git a/x-pack/plugins/stack_alerts/server/index.test.ts b/x-pack/plugins/stack_alerts/server/index.test.ts index 3d3be3acb30ea..9f13996558ae6 100644 --- a/x-pack/plugins/stack_alerts/server/index.test.ts +++ b/x-pack/plugins/stack_alerts/server/index.test.ts @@ -20,7 +20,9 @@ const applyStackAlertDeprecations = (settings: Record = {}) => deprecation, path: CONFIG_PATH, })), - () => ({ message }) => deprecationMessages.push(message) + () => + ({ message }) => + deprecationMessages.push(message) ); return { messages: deprecationMessages, diff --git a/x-pack/plugins/stack_alerts/server/plugin.ts b/x-pack/plugins/stack_alerts/server/plugin.ts index 0a73186996852..1a671466b69b3 100644 --- a/x-pack/plugins/stack_alerts/server/plugin.ts +++ b/x-pack/plugins/stack_alerts/server/plugin.ts @@ -12,7 +12,8 @@ import { registerBuiltInAlertTypes } from './alert_types'; import { BUILT_IN_ALERTS_FEATURE } from './feature'; export class AlertingBuiltinsPlugin - implements Plugin { + implements Plugin +{ private readonly logger: Logger; constructor(ctx: PluginInitializerContext) { diff --git a/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.mock.ts b/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.mock.ts index c1ae0c4141bf1..d107bcdddf502 100644 --- a/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.mock.ts +++ b/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.mock.ts @@ -11,7 +11,7 @@ import { of, Observable } from 'rxjs'; export const ephemeralTaskLifecycleMock = { create(opts: { events$?: Observable; getQueuedTasks?: () => number }) { - return ({ + return { attemptToRun: jest.fn(), get events() { return opts.events$ ?? of(); @@ -19,6 +19,6 @@ export const ephemeralTaskLifecycleMock = { get queuedTasks() { return opts.getQueuedTasks ? opts.getQueuedTasks() : 0; }, - } as unknown) as jest.Mocked; + } as unknown as jest.Mocked; }, }; diff --git a/x-pack/plugins/task_manager/server/index.test.ts b/x-pack/plugins/task_manager/server/index.test.ts index 9d5e42b962c55..8419e826dfd31 100644 --- a/x-pack/plugins/task_manager/server/index.test.ts +++ b/x-pack/plugins/task_manager/server/index.test.ts @@ -22,7 +22,9 @@ const applyTaskManagerDeprecations = (settings: Record = {}) => deprecation, path: CONFIG_PATH, })), - () => ({ message }) => deprecationMessages.push(message) + () => + ({ message }) => + deprecationMessages.push(message) ); return { messages: deprecationMessages, diff --git a/x-pack/plugins/task_manager/server/lib/fill_pool.test.ts b/x-pack/plugins/task_manager/server/lib/fill_pool.test.ts index 8e0396a453b3d..9fdb16fb5f677 100644 --- a/x-pack/plugins/task_manager/server/lib/fill_pool.test.ts +++ b/x-pack/plugins/task_manager/server/lib/fill_pool.test.ts @@ -78,7 +78,7 @@ describe('fillPool', () => { const fetchAvailableTasks = mockFetchAvailableTasks(tasks); const run = sinon.spy(async () => TaskPoolRunResult.RanOutOfCapacity); const converter = (instance: ConcreteTaskInstance) => - (instance.id as unknown) as TaskManagerRunner; + instance.id as unknown as TaskManagerRunner; await fillPool(fetchAvailableTasks, converter, run); @@ -89,7 +89,7 @@ describe('fillPool', () => { test('throws exception from fetchAvailableTasks', async () => { const run = sinon.spy(async () => TaskPoolRunResult.RanOutOfCapacity); const converter = (instance: ConcreteTaskInstance) => - (instance.id as unknown) as TaskManagerRunner; + instance.id as unknown as TaskManagerRunner; try { const fetchAvailableTasks = () => @@ -107,7 +107,7 @@ describe('fillPool', () => { test('throws exception from run', async () => { const run = sinon.spy(() => Promise.reject('run is not working')); const converter = (instance: ConcreteTaskInstance) => - (instance.id as unknown) as TaskManagerRunner; + instance.id as unknown as TaskManagerRunner; try { const tasks = [ diff --git a/x-pack/plugins/task_manager/server/lib/log_health_metrics.test.ts b/x-pack/plugins/task_manager/server/lib/log_health_metrics.test.ts index 4e17d64870a39..2d4aa837ffb06 100644 --- a/x-pack/plugins/task_manager/server/lib/log_health_metrics.test.ts +++ b/x-pack/plugins/task_manager/server/lib/log_health_metrics.test.ts @@ -419,7 +419,7 @@ function getMockMonitoredHealth(overrides = {}): MonitoredHealth { }, }, }; - return (merge(stub, overrides) as unknown) as MonitoredHealth; + return merge(stub, overrides) as unknown as MonitoredHealth; } function getTaskManagerConfig(overrides: Partial = {}) { diff --git a/x-pack/plugins/task_manager/server/lib/middleware.test.ts b/x-pack/plugins/task_manager/server/lib/middleware.test.ts index 7ccba9b07253c..fc4176055f0c6 100644 --- a/x-pack/plugins/task_manager/server/lib/middleware.test.ts +++ b/x-pack/plugins/task_manager/server/lib/middleware.test.ts @@ -48,7 +48,7 @@ const getMockConcreteTaskInstance = () => { params: { abc: 'def' }, ownerId: null, }; - return (concrete as unknown) as ConcreteTaskInstance; + return concrete as unknown as ConcreteTaskInstance; }; const getMockRunContext = (runTask: ConcreteTaskInstance) => ({ taskInstance: runTask, diff --git a/x-pack/plugins/task_manager/server/lib/middleware.ts b/x-pack/plugins/task_manager/server/lib/middleware.ts index 40d05159dc7c0..76a1d56852671 100644 --- a/x-pack/plugins/task_manager/server/lib/middleware.ts +++ b/x-pack/plugins/task_manager/server/lib/middleware.ts @@ -31,8 +31,10 @@ export function addMiddlewareToChain(prev: Middleware, next: Partial }; } -const chain = (prev: Mapper, next: Mapper): Mapper => (params) => - next(params).then(prev); +const chain = + (prev: Mapper, next: Mapper): Mapper => + (params) => + next(params).then(prev); export function createInitialMiddleware(): Middleware { return { diff --git a/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.ts b/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.ts index 49c593d77acec..a5b3288cba528 100644 --- a/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.ts +++ b/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.ts @@ -61,10 +61,8 @@ export function estimateCapacity( non_recurring: percentageOfExecutionsUsedByNonRecurringTasks, } = capacityStats.runtime.value.execution.persistence; const { overdue, capacity_requirements: capacityRequirements } = workload; - const { - poll_interval: pollInterval, - max_workers: maxWorkers, - } = capacityStats.configuration.value; + const { poll_interval: pollInterval, max_workers: maxWorkers } = + capacityStats.configuration.value; /** * On average, how many polling cycles does it take to execute a task? @@ -203,10 +201,12 @@ export function estimateCapacity( minutes_to_drain_overdue: overdue / (assumedKibanaInstances * averageCapacityUsedByPersistedTasksPerKibana), avg_recurring_required_throughput_per_minute: averageRecurringRequiredPerMinute, - avg_recurring_required_throughput_per_minute_per_kibana: assumedAverageRecurringRequiredThroughputPerMinutePerKibana, + avg_recurring_required_throughput_per_minute_per_kibana: + assumedAverageRecurringRequiredThroughputPerMinutePerKibana, avg_required_throughput_per_minute: assumedRequiredThroughputPerMinutePerKibana * assumedKibanaInstances, - avg_required_throughput_per_minute_per_kibana: assumedRequiredThroughputPerMinutePerKibana, + avg_required_throughput_per_minute_per_kibana: + assumedRequiredThroughputPerMinutePerKibana, }, Math.ceil ), @@ -214,8 +214,10 @@ export function estimateCapacity( { provisioned_kibana: minRequiredKibanaInstances, min_required_kibana: minRequiredKibanaInstancesForRecurringTasks, - avg_recurring_required_throughput_per_minute_per_kibana: averageRecurringRequiredPerMinutePerKibana, - avg_required_throughput_per_minute_per_kibana: averageRequiredThroughputPerMinutePerKibana, + avg_recurring_required_throughput_per_minute_per_kibana: + averageRecurringRequiredPerMinutePerKibana, + avg_required_throughput_per_minute_per_kibana: + averageRequiredThroughputPerMinutePerKibana, }, Math.ceil ), diff --git a/x-pack/plugins/task_manager/server/monitoring/ephemeral_task_statistics.ts b/x-pack/plugins/task_manager/server/monitoring/ephemeral_task_statistics.ts index 2378c7f4606a6..52aa2b1eead25 100644 --- a/x-pack/plugins/task_manager/server/monitoring/ephemeral_task_statistics.ts +++ b/x-pack/plugins/task_manager/server/monitoring/ephemeral_task_statistics.ts @@ -49,16 +49,15 @@ export function createEphemeralTaskAggregator( isOk(taskEvent.event) ), map((taskEvent: TaskLifecycleEvent) => { - return ((taskEvent.event as unknown) as Ok).value; + return (taskEvent.event as unknown as Ok).value; }), // as we consume this stream twice below (in the buffer, and the zip) // we want to use share, otherwise ther'll be 2 subscribers and both will emit event share() ); - const ephemeralQueueExecutionsPerCycleQueue = createRunningAveragedStat( - runningAverageWindowSize - ); + const ephemeralQueueExecutionsPerCycleQueue = + createRunningAveragedStat(runningAverageWindowSize); const ephemeralQueuedTasksQueue = createRunningAveragedStat(runningAverageWindowSize); const ephemeralTaskLoadQueue = createRunningAveragedStat(runningAverageWindowSize); const ephemeralPollingCycleBasedStats$ = zip( @@ -89,7 +88,7 @@ export function createEphemeralTaskAggregator( isOk(taskEvent.event) ), map((taskEvent: TaskLifecycleEvent) => { - return ephemeralTaskDelayQueue(((taskEvent.event as unknown) as Ok).value); + return ephemeralTaskDelayQueue((taskEvent.event as unknown as Ok).value); }), startWith([]) ); diff --git a/x-pack/plugins/task_manager/server/monitoring/task_run_statistics.ts b/x-pack/plugins/task_manager/server/monitoring/task_run_statistics.ts index 44908706aa6ec..250ad344aa2e3 100644 --- a/x-pack/plugins/task_manager/server/monitoring/task_run_statistics.ts +++ b/x-pack/plugins/task_manager/server/monitoring/task_run_statistics.ts @@ -118,43 +118,40 @@ export function createTaskRunAggregator( runningAverageWindowSize: number ): AggregatedStatProvider { const taskRunEventToStat = createTaskRunEventToStat(runningAverageWindowSize); - const taskRunEvents$: Observable< - Pick - > = taskPollingLifecycle.events.pipe( - filter((taskEvent: TaskLifecycleEvent) => isTaskRunEvent(taskEvent) && hasTiming(taskEvent)), - map((taskEvent: TaskLifecycleEvent) => { - const { task, result, persistence }: RanTask | ErroredTask = unwrap( - (taskEvent as TaskRun).event - ); - return taskRunEventToStat(task, persistence, taskEvent.timing!, result); - }) - ); + const taskRunEvents$: Observable> = + taskPollingLifecycle.events.pipe( + filter((taskEvent: TaskLifecycleEvent) => isTaskRunEvent(taskEvent) && hasTiming(taskEvent)), + map((taskEvent: TaskLifecycleEvent) => { + const { task, result, persistence }: RanTask | ErroredTask = unwrap( + (taskEvent as TaskRun).event + ); + return taskRunEventToStat(task, persistence, taskEvent.timing!, result); + }) + ); const loadQueue = createRunningAveragedStat(runningAverageWindowSize); - const taskManagerLoadStatEvents$: Observable< - Pick - > = taskPollingLifecycle.events.pipe( - filter( - (taskEvent: TaskLifecycleEvent) => - isTaskManagerStatEvent(taskEvent) && - taskEvent.id === 'load' && - isOk(taskEvent.event) - ), - map((taskEvent: TaskLifecycleEvent) => { - return { - load: loadQueue(((taskEvent.event as unknown) as Ok).value), - }; - }) - ); + const taskManagerLoadStatEvents$: Observable> = + taskPollingLifecycle.events.pipe( + filter( + (taskEvent: TaskLifecycleEvent) => + isTaskManagerStatEvent(taskEvent) && + taskEvent.id === 'load' && + isOk(taskEvent.event) + ), + map((taskEvent: TaskLifecycleEvent) => { + return { + load: loadQueue((taskEvent.event as unknown as Ok).value), + }; + }) + ); const resultFrequencyQueue = createRunningAveragedStat(runningAverageWindowSize); const pollingDurationQueue = createRunningAveragedStat(runningAverageWindowSize); const claimDurationQueue = createRunningAveragedStat(runningAverageWindowSize); const claimConflictsQueue = createRunningAveragedStat(runningAverageWindowSize); const claimMismatchesQueue = createRunningAveragedStat(runningAverageWindowSize); - const polledTasksByPersistenceQueue = createRunningAveragedStat( - runningAverageWindowSize - ); + const polledTasksByPersistenceQueue = + createRunningAveragedStat(runningAverageWindowSize); const taskPollingEvents$: Observable> = combineLatest([ // get latest polling stats taskPollingLifecycle.events.pipe( @@ -164,10 +161,9 @@ export function createTaskRunAggregator( isOk(taskEvent.event) ), map((taskEvent: TaskLifecycleEvent) => { - const { - result, - stats: { tasksClaimed, tasksUpdated, tasksConflicted } = {}, - } = ((taskEvent.event as unknown) as Ok).value; + const { result, stats: { tasksClaimed, tasksUpdated, tasksConflicted } = {} } = ( + taskEvent.event as unknown as Ok + ).value; const duration = (taskEvent?.timing?.stop ?? 0) - (taskEvent?.timing?.start ?? 0); return { polling: { @@ -295,12 +291,10 @@ function createTaskRunEventToStat(runningAverageWindowSize: number) { const taskPersistenceQueue = createRunningAveragedStat(runningAverageWindowSize); const driftByTaskQueue = createMapOfRunningAveragedStats(runningAverageWindowSize); const taskRunDurationQueue = createMapOfRunningAveragedStats(runningAverageWindowSize); - const taskRunDurationByPersistenceQueue = createMapOfRunningAveragedStats( - runningAverageWindowSize - ); - const resultFrequencyQueue = createMapOfRunningAveragedStats( - runningAverageWindowSize - ); + const taskRunDurationByPersistenceQueue = + createMapOfRunningAveragedStats(runningAverageWindowSize); + const resultFrequencyQueue = + createMapOfRunningAveragedStats(runningAverageWindowSize); return ( task: ConcreteTaskInstance, persistence: TaskPersistence, diff --git a/x-pack/plugins/task_manager/server/monitoring/workload_statistics.test.ts b/x-pack/plugins/task_manager/server/monitoring/workload_statistics.test.ts index d24931646128a..9c697be985155 100644 --- a/x-pack/plugins/task_manager/server/monitoring/workload_statistics.test.ts +++ b/x-pack/plugins/task_manager/server/monitoring/workload_statistics.test.ts @@ -775,26 +775,7 @@ describe('estimateRecurringTaskScheduling', () => { schedule[6].nonRecurring = 3; expect(estimateRecurringTaskScheduling(schedule, 3000)).toEqual([ - 1, - 1, - 0, - 1, - 4, - 2, - 6, - 3, - 3, - 2, - 4, - 2, - 3, - 3, - 3, - 2, - 4, - 2, - 3, - 3, + 1, 1, 0, 1, 4, 2, 6, 3, 3, 2, 4, 2, 3, 3, 3, 2, 4, 2, 3, 3, ]); }); }); diff --git a/x-pack/plugins/task_manager/server/monitoring/workload_statistics.ts b/x-pack/plugins/task_manager/server/monitoring/workload_statistics.ts index 6f70df4b8c5c4..b833e4ed57530 100644 --- a/x-pack/plugins/task_manager/server/monitoring/workload_statistics.ts +++ b/x-pack/plugins/task_manager/server/monitoring/workload_statistics.ts @@ -415,9 +415,10 @@ export function estimateRecurringTaskScheduling( }); } -export function summarizeWorkloadStat( - workloadStats: WorkloadStat -): { value: SummarizedWorkloadStat; status: HealthStatus } { +export function summarizeWorkloadStat(workloadStats: WorkloadStat): { + value: SummarizedWorkloadStat; + status: HealthStatus; +} { return { value: { ...workloadStats, diff --git a/x-pack/plugins/task_manager/server/plugin.ts b/x-pack/plugins/task_manager/server/plugin.ts index f91bb783a5c4c..fb401798ea20c 100644 --- a/x-pack/plugins/task_manager/server/plugin.ts +++ b/x-pack/plugins/task_manager/server/plugin.ts @@ -49,7 +49,8 @@ export type TaskManagerStartContract = Pick< } & { supportsEphemeralTasks: () => boolean }; export class TaskManagerPlugin - implements Plugin { + implements Plugin +{ private taskPollingLifecycle?: TaskPollingLifecycle; private ephemeralTaskLifecycle?: EphemeralTaskLifecycle; private taskManagerId?: string; diff --git a/x-pack/plugins/task_manager/server/polling_lifecycle.mock.ts b/x-pack/plugins/task_manager/server/polling_lifecycle.mock.ts index 9053ad0794598..d970b2d3a90c7 100644 --- a/x-pack/plugins/task_manager/server/polling_lifecycle.mock.ts +++ b/x-pack/plugins/task_manager/server/polling_lifecycle.mock.ts @@ -10,7 +10,7 @@ import { of, Observable } from 'rxjs'; export const taskPollingLifecycleMock = { create(opts: { isStarted?: boolean; events$?: Observable }) { - return ({ + return { attemptToRun: jest.fn(), get isStarted() { return opts.isStarted ?? true; @@ -18,6 +18,6 @@ export const taskPollingLifecycleMock = { get events() { return opts.events$ ?? of(); }, - } as unknown) as jest.Mocked; + } as unknown as jest.Mocked; }, }; diff --git a/x-pack/plugins/task_manager/server/polling_lifecycle.ts b/x-pack/plugins/task_manager/server/polling_lifecycle.ts index 26986fc4709f5..c54c1e2f58414 100644 --- a/x-pack/plugins/task_manager/server/polling_lifecycle.ts +++ b/x-pack/plugins/task_manager/server/polling_lifecycle.ts @@ -144,10 +144,8 @@ export class TaskPollingLifecycle { // pipe taskClaiming events into the lifecycle event stream this.taskClaiming.events.subscribe(emitEvent); - const { - max_poll_inactivity_cycles: maxPollInactivityCycles, - poll_interval: pollInterval, - } = config; + const { max_poll_inactivity_cycles: maxPollInactivityCycles, poll_interval: pollInterval } = + config; const pollIntervalDelay$ = delayOnClaimConflicts( maxWorkersConfiguration$, @@ -158,46 +156,45 @@ export class TaskPollingLifecycle { ).pipe(tap((delay) => emitEvent(asTaskManagerStatEvent('pollingDelay', asOk(delay))))); // the task poller that polls for work on fixed intervals and on demand - const poller$: Observable< - Result> - > = createObservableMonitor>, Error>( - () => - createTaskPoller({ - logger, - pollInterval$: pollIntervalConfiguration$, - pollIntervalDelay$, - bufferCapacity: config.request_capacity, - getCapacity: () => { - const capacity = this.pool.availableWorkers; - if (!capacity) { - // if there isn't capacity, emit a load event so that we can expose how often - // high load causes the poller to skip work (work isn'tcalled when there is no capacity) - this.emitEvent(asTaskManagerStatEvent('load', asOk(this.pool.workerLoad))); - } - return capacity; + const poller$: Observable>> = + createObservableMonitor>, Error>( + () => + createTaskPoller({ + logger, + pollInterval$: pollIntervalConfiguration$, + pollIntervalDelay$, + bufferCapacity: config.request_capacity, + getCapacity: () => { + const capacity = this.pool.availableWorkers; + if (!capacity) { + // if there isn't capacity, emit a load event so that we can expose how often + // high load causes the poller to skip work (work isn'tcalled when there is no capacity) + this.emitEvent(asTaskManagerStatEvent('load', asOk(this.pool.workerLoad))); + } + return capacity; + }, + pollRequests$: this.claimRequests$, + work: this.pollForWork, + // Time out the `work` phase if it takes longer than a certain number of polling cycles + // The `work` phase includes the prework needed *before* executing a task + // (such as polling for new work, marking tasks as running etc.) but does not + // include the time of actually running the task + workTimeout: pollInterval * maxPollInactivityCycles, + }), + { + heartbeatInterval: pollInterval, + // Time out the poller itself if it has failed to complete the entire stream for a certain amount of time. + // This is different that the `work` timeout above, as the poller could enter an invalid state where + // it fails to complete a cycle even thought `work` is completing quickly. + // We grant it a single cycle longer than the time alotted to `work` so that timing out the `work` + // doesn't get short circuited by the monitor reinstantiating the poller all together (a far more expensive + // operation than just timing out the `work` internally) + inactivityTimeout: pollInterval * (maxPollInactivityCycles + 1), + onError: (error) => { + logger.error(`[Task Poller Monitor]: ${error.message}`); }, - pollRequests$: this.claimRequests$, - work: this.pollForWork, - // Time out the `work` phase if it takes longer than a certain number of polling cycles - // The `work` phase includes the prework needed *before* executing a task - // (such as polling for new work, marking tasks as running etc.) but does not - // include the time of actually running the task - workTimeout: pollInterval * maxPollInactivityCycles, - }), - { - heartbeatInterval: pollInterval, - // Time out the poller itself if it has failed to complete the entire stream for a certain amount of time. - // This is different that the `work` timeout above, as the poller could enter an invalid state where - // it fails to complete a cycle even thought `work` is completing quickly. - // We grant it a single cycle longer than the time alotted to `work` so that timing out the `work` - // doesn't get short circuited by the monitor reinstantiating the poller all together (a far more expensive - // operation than just timing out the `work` internally) - inactivityTimeout: pollInterval * (maxPollInactivityCycles + 1), - onError: (error) => { - logger.error(`[Task Poller Monitor]: ${error.message}`); - }, - } - ); + } + ); elasticsearchAndSOAvailability$.subscribe((areESAndSOAvailable) => { if (areESAndSOAvailable && !this.isStarted) { diff --git a/x-pack/plugins/task_manager/server/queries/mark_available_tasks_as_claimed.ts b/x-pack/plugins/task_manager/server/queries/mark_available_tasks_as_claimed.ts index 1793ede8161df..3183f364001d9 100644 --- a/x-pack/plugins/task_manager/server/queries/mark_available_tasks_as_claimed.ts +++ b/x-pack/plugins/task_manager/server/queries/mark_available_tasks_as_claimed.ts @@ -102,7 +102,7 @@ if (doc['task.runAt'].size()!=0) { }, }, }; -export const SortByRunAtAndRetryAt = (SortByRunAtAndRetryAtScript as unknown) as Record< +export const SortByRunAtAndRetryAt = SortByRunAtAndRetryAtScript as unknown as Record< string, estypes.SearchSort >; diff --git a/x-pack/plugins/task_manager/server/queries/task_claiming.mock.ts b/x-pack/plugins/task_manager/server/queries/task_claiming.mock.ts index 38f02780c485e..b76af825cc40a 100644 --- a/x-pack/plugins/task_manager/server/queries/task_claiming.mock.ts +++ b/x-pack/plugins/task_manager/server/queries/task_claiming.mock.ts @@ -21,13 +21,13 @@ export const taskClaimingMock = { taskManagerId = '', events = new Subject(), }: TaskClaimingOptions) { - const mocked = ({ + const mocked = { claimAvailableTasks: jest.fn(), claimAvailableTasksIfCapacityIsAvailable: jest.fn(), maxAttempts, taskManagerId, events, - } as unknown) as jest.Mocked; + } as unknown as jest.Mocked; return mocked; }, }; diff --git a/x-pack/plugins/task_manager/server/queries/task_claiming.test.ts b/x-pack/plugins/task_manager/server/queries/task_claiming.test.ts index 1ddafbda71e14..eed8858dc95d8 100644 --- a/x-pack/plugins/task_manager/server/queries/task_claiming.test.ts +++ b/x-pack/plugins/task_manager/server/queries/task_claiming.test.ts @@ -841,16 +841,18 @@ if (doc['task.runAt'].size()!=0) { ) ).map( (result, index) => - (store.updateByQuery.mock.calls[index][0] as { - query: MustNotCondition; - size: number; - sort: string | string[]; - script: { - params: { - [claimableTaskTypes: string]: string[]; + ( + store.updateByQuery.mock.calls[index][0] as { + query: MustNotCondition; + size: number; + sort: string | string[]; + script: { + params: { + [claimableTaskTypes: string]: string[]; + }; }; - }; - }).script.params.claimableTaskTypes + } + ).script.params.claimableTaskTypes ); } diff --git a/x-pack/plugins/task_manager/server/queries/task_claiming.ts b/x-pack/plugins/task_manager/server/queries/task_claiming.ts index 8380b8fdedb1d..fb0b92e87a424 100644 --- a/x-pack/plugins/task_manager/server/queries/task_claiming.ts +++ b/x-pack/plugins/task_manager/server/queries/task_claiming.ts @@ -139,16 +139,14 @@ export class TaskClaiming { } private partitionIntoClaimingBatches(definitions: TaskTypeDictionary): TaskClaimingBatches { - const { - limitedConcurrency, - unlimitedConcurrency, - skippedTypes, - } = groupBy(definitions.getAllDefinitions(), (definition) => - definition.maxConcurrency - ? 'limitedConcurrency' - : definition.maxConcurrency === 0 - ? 'skippedTypes' - : 'unlimitedConcurrency' + const { limitedConcurrency, unlimitedConcurrency, skippedTypes } = groupBy( + definitions.getAllDefinitions(), + (definition) => + definition.maxConcurrency + ? 'limitedConcurrency' + : definition.maxConcurrency === 0 + ? 'skippedTypes' + : 'unlimitedConcurrency' ); if (skippedTypes?.length) { @@ -255,15 +253,13 @@ export class TaskClaiming { taskTypes, }: OwnershipClaimingOpts): Promise => { const claimTasksByIdWithRawIds = this.taskStore.convertToSavedObjectIds(claimTasksById); - const { - updated: tasksUpdated, - version_conflicts: tasksConflicted, - } = await this.markAvailableTasksAsClaimed({ - claimOwnershipUntil, - claimTasksById: claimTasksByIdWithRawIds, - size, - taskTypes, - }); + const { updated: tasksUpdated, version_conflicts: tasksConflicted } = + await this.markAvailableTasksAsClaimed({ + claimOwnershipUntil, + claimTasksById: claimTasksByIdWithRawIds, + size, + taskTypes, + }); const docs = tasksUpdated > 0 diff --git a/x-pack/plugins/task_manager/server/routes/_mock_handler_arguments.ts b/x-pack/plugins/task_manager/server/routes/_mock_handler_arguments.ts index ed570a754de71..5cad8283f1566 100644 --- a/x-pack/plugins/task_manager/server/routes/_mock_handler_arguments.ts +++ b/x-pack/plugins/task_manager/server/routes/_mock_handler_arguments.ts @@ -16,7 +16,7 @@ export function mockHandlerArguments( res?: Array> ): [RequestHandlerContext, KibanaRequest, KibanaResponseFactory] { return [ - ({} as unknown) as RequestHandlerContext, + {} as unknown as RequestHandlerContext, req as KibanaRequest, mockResponseFactory(res), ]; @@ -31,5 +31,5 @@ export const mockResponseFactory = (resToMock: Array { summary: 'Task Manager is unhealthy', }); const debugCalls = (logger as jest.Mocked).debug.mock.calls as string[][]; - const warnMessage = /^setting HealthStatus.Warning because assumedAverageRecurringRequiredThroughputPerMinutePerKibana/; + const warnMessage = + /^setting HealthStatus.Warning because assumedAverageRecurringRequiredThroughputPerMinutePerKibana/; const found = debugCalls .map((arr) => arr[0]) .find((message) => message.match(warnMessage) != null); @@ -551,7 +552,7 @@ function mockHealthStats(overrides = {}) { }, }, }; - return (merge(stub, overrides) as unknown) as MonitoringStats; + return merge(stub, overrides) as unknown as MonitoringStats; } async function getLatest(stream$: Observable) { diff --git a/x-pack/plugins/task_manager/server/saved_objects/migrations.ts b/x-pack/plugins/task_manager/server/saved_objects/migrations.ts index a2ed91dba2737..89bbb3d783881 100644 --- a/x-pack/plugins/task_manager/server/saved_objects/migrations.ts +++ b/x-pack/plugins/task_manager/server/saved_objects/migrations.ts @@ -72,7 +72,7 @@ function alertingTaskLegacyIdToSavedObjectIds( ): SavedObjectUnsanitizedDoc { if (doc.attributes.taskType.startsWith('alerting:')) { let params: { spaceId?: string; alertId?: string } = {}; - params = JSON.parse((doc.attributes.params as unknown) as string); + params = JSON.parse(doc.attributes.params as unknown as string); if (params.alertId && params.spaceId && params.spaceId !== 'default') { const newId = SavedObjectsUtils.getConvertedObjectId(params.spaceId, 'alert', params.alertId); @@ -98,7 +98,7 @@ function actionsTasksLegacyIdToSavedObjectIds( ): SavedObjectUnsanitizedDoc { if (doc.attributes.taskType.startsWith('actions:')) { let params: { spaceId?: string; actionTaskParamsId?: string } = {}; - params = JSON.parse((doc.attributes.params as unknown) as string); + params = JSON.parse(doc.attributes.params as unknown as string); if (params.actionTaskParamsId && params.spaceId && params.spaceId !== 'default') { const newId = SavedObjectsUtils.getConvertedObjectId( diff --git a/x-pack/plugins/task_manager/server/task_pool.mock.ts b/x-pack/plugins/task_manager/server/task_pool.mock.ts index de82d5872d5dd..77568c8c6cdfa 100644 --- a/x-pack/plugins/task_manager/server/task_pool.mock.ts +++ b/x-pack/plugins/task_manager/server/task_pool.mock.ts @@ -21,7 +21,7 @@ const defaultGetCapacityOverride: () => Partial<{ }); const createTaskPoolMock = (getCapacityOverride = defaultGetCapacityOverride) => { - return ({ + return { get load() { return getCapacityOverride().load ?? 0; }, @@ -40,7 +40,7 @@ const createTaskPoolMock = (getCapacityOverride = defaultGetCapacityOverride) => getOccupiedWorkersByType: jest.fn(), run: jest.fn(), cancelRunningTasks: jest.fn(), - } as unknown) as jest.Mocked; + } as unknown as jest.Mocked; }; export const TaskPoolMock = { diff --git a/x-pack/plugins/task_manager/server/task_running/task_runner.test.ts b/x-pack/plugins/task_manager/server/task_running/task_runner.test.ts index b78232d59803e..86e2230461eb5 100644 --- a/x-pack/plugins/task_manager/server/task_running/task_runner.test.ts +++ b/x-pack/plugins/task_manager/server/task_running/task_runner.test.ts @@ -910,7 +910,11 @@ describe('TaskManagerRunner', () => { const id = _.random(1, 20).toString(); const error = new Error('Dangit!'); const onTaskEvent = jest.fn(); - const { runner, store, instance: originalInstance } = await readyToRunStageSetup({ + const { + runner, + store, + instance: originalInstance, + } = await readyToRunStageSetup({ onTaskEvent, instance: { id, @@ -1407,7 +1411,11 @@ describe('TaskManagerRunner', () => { const id = _.random(1, 20).toString(); const error = new Error('Dangit!'); const onTaskEvent = jest.fn(); - const { runner, store, instance: originalInstance } = await readyToRunStageSetup({ + const { + runner, + store, + instance: originalInstance, + } = await readyToRunStageSetup({ onTaskEvent, instance: { id, diff --git a/x-pack/plugins/task_manager/server/task_running/task_runner.ts b/x-pack/plugins/task_manager/server/task_running/task_runner.ts index 30aa0cd0c522e..919360952ebd4 100644 --- a/x-pack/plugins/task_manager/server/task_running/task_runner.ts +++ b/x-pack/plugins/task_manager/server/task_running/task_runner.ts @@ -361,9 +361,8 @@ export class TaskManagerRunner implements TaskRunner { })) as ConcreteTaskInstanceWithStartedAt ); - const timeUntilClaimExpiresAfterUpdate = howManyMsUntilOwnershipClaimExpires( - ownershipClaimedUntil - ); + const timeUntilClaimExpiresAfterUpdate = + howManyMsUntilOwnershipClaimExpires(ownershipClaimedUntil); if (timeUntilClaimExpiresAfterUpdate < 0) { this.logger.debug( `[Task Runner] Task ${id} ran after ownership expired (${Math.abs( diff --git a/x-pack/plugins/task_manager/server/task_scheduling.mock.ts b/x-pack/plugins/task_manager/server/task_scheduling.mock.ts index 60742e83664b4..1bd0f08a82004 100644 --- a/x-pack/plugins/task_manager/server/task_scheduling.mock.ts +++ b/x-pack/plugins/task_manager/server/task_scheduling.mock.ts @@ -8,12 +8,12 @@ import { TaskScheduling } from './task_scheduling'; const createTaskSchedulingMock = () => { - return ({ + return { ensureScheduled: jest.fn(), schedule: jest.fn(), runNow: jest.fn(), ephemeralRunNow: jest.fn(), - } as unknown) as jest.Mocked; + } as unknown as jest.Mocked; }; export const taskSchedulingMock = { diff --git a/x-pack/plugins/task_manager/server/task_store.mock.ts b/x-pack/plugins/task_manager/server/task_store.mock.ts index 38d570f96220b..138c1e1c12034 100644 --- a/x-pack/plugins/task_manager/server/task_store.mock.ts +++ b/x-pack/plugins/task_manager/server/task_store.mock.ts @@ -13,7 +13,7 @@ interface TaskStoreOptions { } export const taskStoreMock = { create({ index = '', taskManagerId = '' }: TaskStoreOptions = {}) { - const mocked = ({ + const mocked = { convertToSavedObjectIds: jest.fn(), update: jest.fn(), remove: jest.fn(), @@ -26,7 +26,7 @@ export const taskStoreMock = { updateByQuery: jest.fn(), index, taskManagerId, - } as unknown) as jest.Mocked; + } as unknown as jest.Mocked; return mocked; }, }; diff --git a/x-pack/plugins/task_manager/server/task_store.ts b/x-pack/plugins/task_manager/server/task_store.ts index 0a8335ebe98f3..bcd4b3b1885f9 100644 --- a/x-pack/plugins/task_manager/server/task_store.ts +++ b/x-pack/plugins/task_manager/server/task_store.ts @@ -211,19 +211,18 @@ export class TaskStore { let updatedSavedObjects: Array; try { - ({ - saved_objects: updatedSavedObjects, - } = await this.savedObjectsRepository.bulkUpdate( - docs.map((doc) => ({ - type: 'task', - id: doc.id, - options: { version: doc.version }, - attributes: attributesByDocId.get(doc.id)!, - })), - { - refresh: false, - } - )); + ({ saved_objects: updatedSavedObjects } = + await this.savedObjectsRepository.bulkUpdate( + docs.map((doc) => ({ + type: 'task', + id: doc.id, + options: { version: doc.version }, + attributes: attributesByDocId.get(doc.id)!, + })), + { + refresh: false, + } + )); } catch (e) { this.errors$.next(e); throw e; diff --git a/x-pack/plugins/task_manager/server/task_type_dictionary.test.ts b/x-pack/plugins/task_manager/server/task_type_dictionary.test.ts index 7e5f870b8ba93..d682d40a1d811 100644 --- a/x-pack/plugins/task_manager/server/task_type_dictionary.test.ts +++ b/x-pack/plugins/task_manager/server/task_type_dictionary.test.ts @@ -36,7 +36,7 @@ const getMockTaskDefinitions = (opts: Opts) => { }, }; } - return (tasks as unknown) as Record; + return tasks as unknown as Record; }; describe('taskTypeDictionary', () => { diff --git a/x-pack/plugins/task_manager/server/usage/task_manager_usage_collector.test.ts b/x-pack/plugins/task_manager/server/usage/task_manager_usage_collector.test.ts index 8e050e31b6cad..9b66792efcd9e 100644 --- a/x-pack/plugins/task_manager/server/usage/task_manager_usage_collector.test.ts +++ b/x-pack/plugins/task_manager/server/usage/task_manager_usage_collector.test.ts @@ -189,5 +189,5 @@ function getMockMonitoredHealth(overrides = {}): MonitoredHealth { }, }, }; - return (merge(stub, overrides) as unknown) as MonitoredHealth; + return merge(stub, overrides) as unknown as MonitoredHealth; } diff --git a/x-pack/plugins/timelines/common/search_strategy/timeline/index.ts b/x-pack/plugins/timelines/common/search_strategy/timeline/index.ts index c173e347fbdb5..7cf5b6b22d163 100644 --- a/x-pack/plugins/timelines/common/search_strategy/timeline/index.ts +++ b/x-pack/plugins/timelines/common/search_strategy/timeline/index.ts @@ -55,29 +55,27 @@ export interface TimelineRequestOptionsPaginated sort: Array>; } -export type TimelineStrategyResponseType< - T extends TimelineFactoryQueryTypes -> = T extends TimelineEventsQueries.all - ? TimelineEventsAllStrategyResponse - : T extends TimelineEventsQueries.details - ? TimelineEventsDetailsStrategyResponse - : T extends TimelineEventsQueries.kpi - ? TimelineKpiStrategyResponse - : T extends TimelineEventsQueries.lastEventTime - ? TimelineEventsLastEventTimeStrategyResponse - : never; - -export type TimelineStrategyRequestType< - T extends TimelineFactoryQueryTypes -> = T extends TimelineEventsQueries.all - ? TimelineEventsAllRequestOptions - : T extends TimelineEventsQueries.details - ? TimelineEventsDetailsRequestOptions - : T extends TimelineEventsQueries.kpi - ? TimelineRequestBasicOptions - : T extends TimelineEventsQueries.lastEventTime - ? TimelineEventsLastEventTimeRequestOptions - : never; +export type TimelineStrategyResponseType = + T extends TimelineEventsQueries.all + ? TimelineEventsAllStrategyResponse + : T extends TimelineEventsQueries.details + ? TimelineEventsDetailsStrategyResponse + : T extends TimelineEventsQueries.kpi + ? TimelineKpiStrategyResponse + : T extends TimelineEventsQueries.lastEventTime + ? TimelineEventsLastEventTimeStrategyResponse + : never; + +export type TimelineStrategyRequestType = + T extends TimelineEventsQueries.all + ? TimelineEventsAllRequestOptions + : T extends TimelineEventsQueries.details + ? TimelineEventsDetailsRequestOptions + : T extends TimelineEventsQueries.kpi + ? TimelineRequestBasicOptions + : T extends TimelineEventsQueries.lastEventTime + ? TimelineEventsLastEventTimeRequestOptions + : never; export interface ColumnHeaderInput { aggregatable?: Maybe; diff --git a/x-pack/plugins/timelines/common/types/timeline/columns/index.tsx b/x-pack/plugins/timelines/common/types/timeline/columns/index.tsx index cc20c856f0e13..644b38d551337 100644 --- a/x-pack/plugins/timelines/common/types/timeline/columns/index.tsx +++ b/x-pack/plugins/timelines/common/types/timeline/columns/index.tsx @@ -8,7 +8,7 @@ import { ReactNode } from 'react'; import { EuiDataGridColumn, EuiDataGridColumnCellActionProps } from '@elastic/eui'; -import { IFieldSubType } from '../../../../../../../src/plugins/data/common'; +import { Filter, IFieldSubType } from '../../../../../../../src/plugins/data/common'; import { BrowserFields } from '../../../search_strategy/index_fields'; import { TimelineNonEcsData } from '../../../search_strategy/timeline'; @@ -45,14 +45,16 @@ export type ColumnId = string; export type TGridCellAction = ({ browserFields, data, - timelineId, + globalFilters, pageSize, + timelineId, }: { browserFields: BrowserFields; /** each row of data is represented as one TimelineNonEcsData[] */ data: TimelineNonEcsData[][]; - timelineId: string; + globalFilters?: Filter[]; pageSize: number; + timelineId: string; }) => (props: EuiDataGridColumnCellActionProps) => ReactNode; /** The specification of a column header */ diff --git a/x-pack/plugins/timelines/common/utility_types.ts b/x-pack/plugins/timelines/common/utility_types.ts index 498b18dccaca5..ccdbc254033fd 100644 --- a/x-pack/plugins/timelines/common/utility_types.ts +++ b/x-pack/plugins/timelines/common/utility_types.ts @@ -25,7 +25,7 @@ export const stringEnum = (enumObj: T, enumName = 'enum') => Object.values(enumObj).includes(u) ? runtimeTypes.success(u as T[keyof T]) : runtimeTypes.failure(u, c), - (a) => (a as unknown) as string + (a) => a as unknown as string ); /** diff --git a/x-pack/plugins/timelines/public/components/t_grid/body/events/stateful_event.tsx b/x-pack/plugins/timelines/public/components/t_grid/body/events/stateful_event.tsx index 3ab8de98ed137..f448ba3831b14 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/body/events/stateful_event.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/body/events/stateful_event.tsx @@ -115,10 +115,10 @@ const StatefulEventComponent: React.FC = ({ hostIPAddresses?.has(activeExpandedDetail?.params?.ip)) || false; - const hasRowRenderers: boolean = useMemo(() => getRowRenderer(event.ecs, rowRenderers) != null, [ - event.ecs, - rowRenderers, - ]); + const hasRowRenderers: boolean = useMemo( + () => getRowRenderer(event.ecs, rowRenderers) != null, + [event.ecs, rowRenderers] + ); const handleOnEventDetailPanelOpened = useCallback(() => { const eventId = event._id; diff --git a/x-pack/plugins/timelines/public/components/t_grid/body/events/stateful_row_renderer/index.tsx b/x-pack/plugins/timelines/public/components/t_grid/body/events/stateful_row_renderer/index.tsx index 0d606ad28eff2..43a6284946b6a 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/body/events/stateful_row_renderer/index.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/body/events/stateful_row_renderer/index.tsx @@ -61,10 +61,10 @@ export const StatefulRowRenderer = ({ rowindexAttribute: ARIA_ROWINDEX_ATTRIBUTE, }); - const rowRenderer = useMemo(() => getRowRenderer(event.ecs, rowRenderers), [ - event.ecs, - rowRenderers, - ]); + const rowRenderer = useMemo( + () => getRowRenderer(event.ecs, rowRenderers), + [event.ecs, rowRenderers] + ); const content = useMemo( () => diff --git a/x-pack/plugins/timelines/public/components/t_grid/body/events/use_stateful_event_focus/index.tsx b/x-pack/plugins/timelines/public/components/t_grid/body/events/use_stateful_event_focus/index.tsx index f912d6fc22a92..d39015995daa0 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/body/events/use_stateful_event_focus/index.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/body/events/use_stateful_event_focus/index.tsx @@ -84,12 +84,10 @@ export const useStatefulEventFocus = ({ ] ); - const memoizedReturn = useMemo(() => ({ focusOwnership, onFocus, onOutsideClick, onKeyDown }), [ - focusOwnership, - onFocus, - onKeyDown, - onOutsideClick, - ]); + const memoizedReturn = useMemo( + () => ({ focusOwnership, onFocus, onOutsideClick, onKeyDown }), + [focusOwnership, onFocus, onKeyDown, onOutsideClick] + ); return memoizedReturn; }; diff --git a/x-pack/plugins/timelines/public/components/t_grid/body/helpers.test.tsx b/x-pack/plugins/timelines/public/components/t_grid/body/helpers.test.tsx index 2b58487fce53a..eb185792c152f 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/body/helpers.test.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/body/helpers.test.tsx @@ -17,7 +17,7 @@ import { addBuildingBlockStyle, } from './helpers'; -import { euiThemeVars } from '@kbn/ui-shared-deps/theme'; +import { euiThemeVars } from '@kbn/ui-shared-deps-src/theme'; import { mockDnsEvent } from '../../../mock'; describe('helpers', () => { diff --git a/x-pack/plugins/timelines/public/components/t_grid/body/index.test.tsx b/x-pack/plugins/timelines/public/components/t_grid/body/index.test.tsx index 77dde444f77ca..8fbb7f0e62b9c 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/body/index.test.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/body/index.test.tsx @@ -41,8 +41,9 @@ jest.mock('../../../hooks/use_selector', () => ({ jest.mock( 'react-visibility-sensor', - () => ({ children }: { children: (args: { isVisible: boolean }) => React.ReactNode }) => - children({ isVisible: true }) + () => + ({ children }: { children: (args: { isVisible: boolean }) => React.ReactNode }) => + children({ isVisible: true }) ); window.matchMedia = jest.fn().mockImplementation((query) => { @@ -60,7 +61,7 @@ describe('Body', () => { const props: StatefulBodyProps = { activePage: 0, browserFields: mockBrowserFields, - clearSelected: (jest.fn() as unknown) as StatefulBodyProps['clearSelected'], + clearSelected: jest.fn() as unknown as StatefulBodyProps['clearSelected'], columnHeaders: defaultHeaders, data: mockTimelineData, excludedRowRendererIds: [], @@ -74,7 +75,7 @@ describe('Body', () => { renderCellValue: TestCellRenderer, rowRenderers: [], selectedEventIds: {}, - setSelected: (jest.fn() as unknown) as StatefulBodyProps['setSelected'], + setSelected: jest.fn() as unknown as StatefulBodyProps['setSelected'], sort: mockSort, showCheckboxes: false, tabType: TimelineTabs.query, diff --git a/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx b/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx index 24f2718fbc1cf..d67cc746f352f 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx @@ -74,6 +74,7 @@ import type { EuiTheme } from '../../../../../../../src/plugins/kibana_react/com import { ViewSelection } from '../event_rendered_view/selector'; import { EventRenderedView } from '../event_rendered_view'; import { useDataGridHeightHack } from './height_hack'; +import { Filter } from '../../../../../../../src/plugins/data/public'; const StatefulAlertStatusBulkActions = lazy( () => import('../toolbar/bulk_actions/alert_status_bulk_actions') @@ -86,6 +87,7 @@ interface OwnProps { bulkActions?: BulkActionsProp; data: TimelineItem[]; defaultCellActions?: TGridCellAction[]; + filters?: Filter[]; filterQuery: string; filterStatus?: AlertStatus; id: string; @@ -300,15 +302,18 @@ export const BodyComponent = React.memo( data, defaultCellActions, filterQuery, + filters, filterStatus, + hasAlertsCrud, + hasAlertsCrudPermissions, id, indexNames, isEventViewer = false, + isLoading, isSelectAllChecked, itemsPerPageOptions, leadingControlColumns = EMPTY_CONTROL_COLUMNS, loadingEventIds, - isLoading, loadPage, onRuleChange, pageSize, @@ -322,11 +327,9 @@ export const BodyComponent = React.memo( tableView = 'gridView', tabType, totalItems, + totalSelectAllAlerts, trailingControlColumns = EMPTY_CONTROL_COLUMNS, unit = defaultUnit, - hasAlertsCrud, - hasAlertsCrudPermissions, - totalSelectAllAlerts, }) => { const dispatch = useDispatch(); const getManageTimeline = useMemo(() => tGridSelectors.getManageTimelineById(), []); @@ -334,10 +337,10 @@ export const BodyComponent = React.memo( getManageTimeline(state, id) ); - const alertCountText = useMemo(() => `${totalItems.toLocaleString()} ${unit(totalItems)}`, [ - totalItems, - unit, - ]); + const alertCountText = useMemo( + () => `${totalItems.toLocaleString()} ${unit(totalItems)}`, + [totalItems, unit] + ); const selectedCount = useMemo(() => Object.keys(selectedEventIds).length, [selectedEventIds]); @@ -641,10 +644,11 @@ export const BodyComponent = React.memo( columnHeaders.map((header) => { const buildAction = (tGridCellAction: TGridCellAction) => tGridCellAction({ - data: data.map((row) => row.data), browserFields, - timelineId: id, + data: data.map((row) => row.data), + globalFilters: filters, pageSize, + timelineId: id, }); return { @@ -653,7 +657,7 @@ export const BodyComponent = React.memo( header.tGridCellActions?.map(buildAction) ?? defaultCellActions?.map(buildAction), }; }), - [browserFields, columnHeaders, data, defaultCellActions, id, pageSize] + [browserFields, columnHeaders, data, defaultCellActions, id, pageSize, filters] ); const renderTGridCellValue = useMemo(() => { @@ -717,20 +721,16 @@ export const BodyComponent = React.memo( const onChangeItemsPerPage = useCallback( (itemsChangedPerPage) => { - clearSelected({ id }); - dispatch(tGridActions.setTGridSelectAll({ id, selectAll: false })); dispatch(tGridActions.updateItemsPerPage({ id, itemsPerPage: itemsChangedPerPage })); }, - [id, dispatch, clearSelected] + [id, dispatch] ); const onChangePage = useCallback( (page) => { - clearSelected({ id }); - dispatch(tGridActions.setTGridSelectAll({ id, selectAll: false })); loadPage(page); }, - [id, loadPage, dispatch, clearSelected] + [loadPage] ); const height = useDataGridHeightHack(pageSize, data.length); diff --git a/x-pack/plugins/timelines/public/components/t_grid/footer/index.tsx b/x-pack/plugins/timelines/public/components/t_grid/footer/index.tsx index a4bc2bf9133ab..a9e13ab5f415a 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/footer/index.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/footer/index.tsx @@ -67,7 +67,7 @@ const LoadingPanelContainer = styled.div` LoadingPanelContainer.displayName = 'LoadingPanelContainer'; -const PopoverRowItems = styled((EuiPopover as unknown) as FC)< +const PopoverRowItems = styled(EuiPopover as unknown as FC)< EuiPopoverProps & { className?: string; id?: string; @@ -240,10 +240,10 @@ export const FooterComponent = ({ [onChangePage] ); - const onButtonClick = useCallback(() => setIsPopoverOpen(!isPopoverOpen), [ - isPopoverOpen, - setIsPopoverOpen, - ]); + const onButtonClick = useCallback( + () => setIsPopoverOpen(!isPopoverOpen), + [isPopoverOpen, setIsPopoverOpen] + ); const closePopover = useCallback(() => setIsPopoverOpen(false), [setIsPopoverOpen]); @@ -272,10 +272,10 @@ export const FooterComponent = ({ [closePopover, itemsPerPage, itemsPerPageOptions, onChangeItemsPerPage] ); - const totalPages = useMemo(() => Math.ceil(totalCount / itemsPerPage), [ - itemsPerPage, - totalCount, - ]); + const totalPages = useMemo( + () => Math.ceil(totalCount / itemsPerPage), + [itemsPerPage, totalCount] + ); useEffect(() => { if (paginationLoading && !isLoading) { diff --git a/x-pack/plugins/timelines/public/components/t_grid/integrated/index.tsx b/x-pack/plugins/timelines/public/components/t_grid/integrated/index.tsx index afce668eb04e2..b649dc36abe0a 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/integrated/index.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/integrated/index.tsx @@ -213,10 +213,10 @@ const TGridIntegratedComponent: React.FC = ({ [isLoadingIndexPattern, combinedQueries, start, end] ); - const fields = useMemo(() => [...columnsHeader.map((c) => c.id), ...(queryFields ?? [])], [ - columnsHeader, - queryFields, - ]); + const fields = useMemo( + () => [...columnsHeader.map((c) => c.id), ...(queryFields ?? [])], + [columnsHeader, queryFields] + ); const sortField = useMemo( () => @@ -228,25 +228,23 @@ const TGridIntegratedComponent: React.FC = ({ [sort] ); - const [ - loading, - { events, loadPage, pageInfo, refetch, totalCount = 0, inspect }, - ] = useTimelineEvents({ - // We rely on entityType to determine Events vs Alerts - alertConsumers: SECURITY_ALERTS_CONSUMERS, - data, - docValueFields, - endDate: end, - entityType, - fields, - filterQuery: combinedQueries!.filterQuery, - id, - indexNames, - limit: itemsPerPage, - skip: !canQueryTimeline, - sort: sortField, - startDate: start, - }); + const [loading, { events, loadPage, pageInfo, refetch, totalCount = 0, inspect }] = + useTimelineEvents({ + // We rely on entityType to determine Events vs Alerts + alertConsumers: SECURITY_ALERTS_CONSUMERS, + data, + docValueFields, + endDate: end, + entityType, + fields, + filterQuery: combinedQueries!.filterQuery, + id, + indexNames, + limit: itemsPerPage, + skip: !canQueryTimeline, + sort: sortField, + startDate: start, + }); const filterQuery = useMemo( () => @@ -272,10 +270,10 @@ const TGridIntegratedComponent: React.FC = ({ const hasAlerts = totalCountMinusDeleted > 0; - const nonDeletedEvents = useMemo(() => events.filter((e) => !deletedEventIds.includes(e._id)), [ - deletedEventIds, - events, - ]); + const nonDeletedEvents = useMemo( + () => events.filter((e) => !deletedEventIds.includes(e._id)), + [deletedEventIds, events] + ); useEffect(() => { setIsQueryLoading(loading); @@ -295,6 +293,17 @@ const TGridIntegratedComponent: React.FC = ({ }, [inspect, loading, refetch, setQuery]); const timelineContext = useMemo(() => ({ timelineId: id }), [id]); + // Clear checkbox selection when new events are fetched + useEffect(() => { + dispatch(tGridActions.clearSelected({ id })); + dispatch( + tGridActions.setTGridSelectAll({ + id, + selectAll: false, + }) + ); + }, [nonDeletedEvents, dispatch, id]); + return ( = ({ > diff --git a/x-pack/plugins/timelines/public/components/t_grid/standalone/index.tsx b/x-pack/plugins/timelines/public/components/t_grid/standalone/index.tsx index 18bb56aacab2d..1a374d0c6b87a 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/standalone/index.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/standalone/index.tsx @@ -275,10 +275,10 @@ const TGridStandaloneComponent: React.FC = ({ }; }, [appId, casePermissions, afterCaseSelection, selectedEvent]); - const nonDeletedEvents = useMemo(() => events.filter((e) => !deletedEventIds.includes(e._id)), [ - deletedEventIds, - events, - ]); + const nonDeletedEvents = useMemo( + () => events.filter((e) => !deletedEventIds.includes(e._id)), + [deletedEventIds, events] + ); const filterQuery = useMemo( () => @@ -337,6 +337,17 @@ const TGridStandaloneComponent: React.FC = ({ }, [loading]); const timelineContext = { timelineId: STANDALONE_ID }; + // Clear checkbox selection when new events are fetched + useEffect(() => { + dispatch(tGridActions.clearSelected({ id: STANDALONE_ID })); + dispatch( + tGridActions.setTGridSelectAll({ + id: STANDALONE_ID, + selectAll: false, + }) + ); + }, [nonDeletedEvents, dispatch]); + return ( diff --git a/x-pack/plugins/timelines/public/components/t_grid/toolbar/bulk_actions/index.tsx b/x-pack/plugins/timelines/public/components/t_grid/toolbar/bulk_actions/index.tsx index 680da584b382e..e0865e0fce5fb 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/toolbar/bulk_actions/index.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/toolbar/bulk_actions/index.tsx @@ -44,10 +44,10 @@ const BulkActionsComponent: React.FC = ({ const [isActionsPopoverOpen, setIsActionsPopoverOpen] = useState(false); const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT); - const formattedTotalCount = useMemo(() => numeral(totalItems).format(defaultNumberFormat), [ - defaultNumberFormat, - totalItems, - ]); + const formattedTotalCount = useMemo( + () => numeral(totalItems).format(defaultNumberFormat), + [defaultNumberFormat, totalItems] + ); const formattedSelectedEventsCount = useMemo( () => numeral(selectedCount).format(defaultNumberFormat), [defaultNumberFormat, selectedCount] diff --git a/x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/helpers.tsx b/x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/helpers.tsx index 46aa5a00e0c3a..f4b608b456fed 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/helpers.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/toolbar/fields_browser/helpers.tsx @@ -423,8 +423,8 @@ export const onFieldsBrowserTabPressed = ({ } }; -export const CountBadge = (styled(EuiBadge)` +export const CountBadge = styled(EuiBadge)` margin-left: 5px; -` as unknown) as typeof EuiBadge; +` as unknown as typeof EuiBadge; CountBadge.displayName = 'CountBadge'; diff --git a/x-pack/plugins/timelines/public/components/tgrid.tsx b/x-pack/plugins/timelines/public/components/tgrid.tsx index 45e387b780a80..ff3404dc7f195 100644 --- a/x-pack/plugins/timelines/public/components/tgrid.tsx +++ b/x-pack/plugins/timelines/public/components/tgrid.tsx @@ -14,9 +14,9 @@ import { TGridStandalone, TGridStandaloneProps } from './t_grid/standalone'; export const TGrid = (props: TGridProps) => { const { type, ...componentsProps } = props; if (type === 'standalone') { - return ; + return ; } else if (type === 'embedded') { - return ; + return ; } return null; }; diff --git a/x-pack/plugins/timelines/public/container/source/index.tsx b/x-pack/plugins/timelines/public/container/source/index.tsx index 94bbd2a874178..1948da0974438 100644 --- a/x-pack/plugins/timelines/public/container/source/index.tsx +++ b/x-pack/plugins/timelines/public/container/source/index.tsx @@ -61,7 +61,7 @@ export const getBrowserFields = memoizeOne( if (accumulator[field.category].fields == null) { accumulator[field.category].fields = {}; } - accumulator[field.category].fields[field.name] = (field as unknown) as BrowserField; + accumulator[field.category].fields[field.name] = field as unknown as BrowserField; return accumulator; }, {}); }, diff --git a/x-pack/plugins/timelines/public/hooks/use_add_to_case.ts b/x-pack/plugins/timelines/public/hooks/use_add_to_case.ts index a1490834b24b9..afeb2287da739 100644 --- a/x-pack/plugins/timelines/public/hooks/use_add_to_case.ts +++ b/x-pack/plugins/timelines/public/hooks/use_add_to_case.ts @@ -143,10 +143,10 @@ export const useAddToCase = ({ ); const currentSearch = useLocation().search; const urlSearch = useMemo(() => currentSearch, [currentSearch]); - const createCaseUrl = useMemo(() => getUrlForApp('cases') + getCreateCaseUrl(urlSearch), [ - getUrlForApp, - urlSearch, - ]); + const createCaseUrl = useMemo( + () => getUrlForApp('cases') + getCreateCaseUrl(urlSearch), + [getUrlForApp, urlSearch] + ); const attachAlertToCase = useCallback( async ( diff --git a/x-pack/plugins/timelines/public/mock/kibana_react.mock.ts b/x-pack/plugins/timelines/public/mock/kibana_react.mock.ts index f76cebae81db5..3f3a8bd7ec166 100644 --- a/x-pack/plugins/timelines/public/mock/kibana_react.mock.ts +++ b/x-pack/plugins/timelines/public/mock/kibana_react.mock.ts @@ -18,7 +18,7 @@ export const mockNavigateToApp = jest.fn(); export const createStartServicesMock = (): CoreStart => { const coreServices = coreMock.createStart(); - return ({ + return { ...coreServices, cases: { getAllCases: jest.fn(), @@ -32,7 +32,7 @@ export const createStartServicesMock = (): CoreStart => { ...coreServices.application, navigateToApp: mockNavigateToApp, }, - } as unknown) as CoreStart; + } as unknown as CoreStart; }; export const createWithKibanaMock = () => { diff --git a/x-pack/plugins/timelines/public/store/t_grid/actions.ts b/x-pack/plugins/timelines/public/store/t_grid/actions.ts index cacf25896c5cc..a039a236fb186 100644 --- a/x-pack/plugins/timelines/public/store/t_grid/actions.ts +++ b/x-pack/plugins/timelines/public/store/t_grid/actions.ts @@ -54,9 +54,8 @@ export const updateColumns = actionCreator<{ columns: ColumnHeaderOptions[]; }>('UPDATE_COLUMNS'); -export const updateItemsPerPage = actionCreator<{ id: string; itemsPerPage: number }>( - 'UPDATE_ITEMS_PER_PAGE' -); +export const updateItemsPerPage = + actionCreator<{ id: string; itemsPerPage: number }>('UPDATE_ITEMS_PER_PAGE'); export const updateItemsPerPageOptions = actionCreator<{ id: string; @@ -98,13 +97,11 @@ export const clearEventsDeleted = actionCreator<{ export const initializeTGridSettings = actionCreator('INITIALIZE_TGRID'); -export const setTGridSelectAll = actionCreator<{ id: string; selectAll: boolean }>( - 'SET_TGRID_SELECT_ALL' -); +export const setTGridSelectAll = + actionCreator<{ id: string; selectAll: boolean }>('SET_TGRID_SELECT_ALL'); -export const setTimelineUpdatedAt = actionCreator<{ id: string; updated: number }>( - 'SET_TIMELINE_UPDATED_AT' -); +export const setTimelineUpdatedAt = + actionCreator<{ id: string; updated: number }>('SET_TIMELINE_UPDATED_AT'); export const addProviderToTimeline = actionCreator<{ id: string; dataProvider: DataProvider }>( 'ADD_PROVIDER_TO_TIMELINE' diff --git a/x-pack/plugins/timelines/server/index.ts b/x-pack/plugins/timelines/server/index.ts index 8ad2bafdcc13a..229a257d8f549 100644 --- a/x-pack/plugins/timelines/server/index.ts +++ b/x-pack/plugins/timelines/server/index.ts @@ -5,11 +5,12 @@ * 2.0. */ -import { PluginInitializerContext } from '../../../../src/core/server'; +import { PluginInitializerContext, PluginConfigDescriptor } from '../../../../src/core/server'; import { TimelinesPlugin } from './plugin'; -import { ConfigSchema } from './config'; +import { ConfigSchema, ConfigType } from './config'; -export const config = { +export const config: PluginConfigDescriptor = { + deprecations: ({ deprecate }) => [deprecate('enabled', '8.0.0')], schema: ConfigSchema, exposeToBrowser: { enabled: true, diff --git a/x-pack/plugins/timelines/server/plugin.ts b/x-pack/plugins/timelines/server/plugin.ts index 82f610fee632d..79d35e53fada1 100644 --- a/x-pack/plugins/timelines/server/plugin.ts +++ b/x-pack/plugins/timelines/server/plugin.ts @@ -20,7 +20,8 @@ import { timelineEqlSearchStrategyProvider } from './search_strategy/timeline/eq import { indexFieldsProvider } from './search_strategy/index_fields'; export class TimelinesPlugin - implements Plugin { + implements Plugin +{ private readonly logger: Logger; constructor(initializerContext: PluginInitializerContext) { diff --git a/x-pack/plugins/timelines/server/search_strategy/index_fields/index.test.ts b/x-pack/plugins/timelines/server/search_strategy/index_fields/index.test.ts index cb2b097d78701..e8df6200351ea 100644 --- a/x-pack/plugins/timelines/server/search_strategy/index_fields/index.test.ts +++ b/x-pack/plugins/timelines/server/search_strategy/index_fields/index.test.ts @@ -796,9 +796,9 @@ describe('Fields Provider', () => { const getFieldsForWildcardMock = jest.fn(); const esClientSearchMock = jest.fn(); - const deps = ({ + const deps = { esClient: { asCurrentUser: { search: esClientSearchMock } }, - } as unknown) as SearchStrategyDependencies; + } as unknown as SearchStrategyDependencies; beforeAll(() => { getFieldsForWildcardMock.mockResolvedValue([]); diff --git a/x-pack/plugins/timelines/server/search_strategy/index_fields/index.ts b/x-pack/plugins/timelines/server/search_strategy/index_fields/index.ts index 907907e978123..8670b709494f0 100644 --- a/x-pack/plugins/timelines/server/search_strategy/index_fields/index.ts +++ b/x-pack/plugins/timelines/server/search_strategy/index_fields/index.ts @@ -12,9 +12,8 @@ import { IndexPatternsFetcher, ISearchStrategy, SearchStrategyDependencies, + FieldDescriptor, } from '../../../../../../src/plugins/data/server'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { FieldDescriptor } from '../../../../../../src/plugins/data/server/index_patterns'; // TODO cleanup path import { diff --git a/x-pack/plugins/timelines/server/search_strategy/index_fields/mock.ts b/x-pack/plugins/timelines/server/search_strategy/index_fields/mock.ts index 76fa1d064b342..f55cd069ecd57 100644 --- a/x-pack/plugins/timelines/server/search_strategy/index_fields/mock.ts +++ b/x-pack/plugins/timelines/server/search_strategy/index_fields/mock.ts @@ -5,8 +5,7 @@ * 2.0. */ -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { FieldDescriptor } from '../../../../../../src/plugins/data/server/index_patterns'; +import { FieldDescriptor } from '../../../../../../src/plugins/data/server'; export const mockAuditbeatIndexField: FieldDescriptor[] = [ { diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/eql/__mocks__/index.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/eql/__mocks__/index.ts index 7a2a754e8e6b9..9e950132b5b69 100644 --- a/x-pack/plugins/timelines/server/search_strategy/timeline/eql/__mocks__/index.ts +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/eql/__mocks__/index.ts @@ -8,7 +8,7 @@ import { EqlSearchStrategyResponse } from '../../../../../../../../src/plugins/data/common'; import { EqlSearchResponse } from '../../../../../common'; -export const sequenceResponse = ({ +export const sequenceResponse = { rawResponse: { body: { is_partial: false, @@ -788,9 +788,9 @@ export const sequenceResponse = ({ meta: {}, hits: {}, }, -} as unknown) as EqlSearchStrategyResponse>; +} as unknown as EqlSearchStrategyResponse>; -export const eventsResponse = ({ +export const eventsResponse = { rawResponse: { body: { is_partial: false, @@ -1282,4 +1282,4 @@ export const eventsResponse = ({ meta: {}, hits: {}, }, -} as unknown) as EqlSearchStrategyResponse>; +} as unknown as EqlSearchStrategyResponse>; diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/eql/index.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/eql/index.ts index 9c59a33a1c12a..b1ffab871fb8b 100644 --- a/x-pack/plugins/timelines/server/search_strategy/timeline/eql/index.ts +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/eql/index.ts @@ -41,7 +41,7 @@ export const timelineEqlSearchStrategyProvider = ( mergeMap(async (esSearchRes) => parseEqlResponse( request, - (esSearchRes as unknown) as EqlSearchStrategyResponse> + esSearchRes as unknown as EqlSearchStrategyResponse> ) ) ); diff --git a/x-pack/plugins/transform/public/app/__mocks__/app_dependencies.tsx b/x-pack/plugins/transform/public/app/__mocks__/app_dependencies.tsx index 5a6f8cf72e36d..8dc0e277c284d 100644 --- a/x-pack/plugins/transform/public/app/__mocks__/app_dependencies.tsx +++ b/x-pack/plugins/transform/public/app/__mocks__/app_dependencies.tsx @@ -36,12 +36,12 @@ const appDependencies: AppDependencies = { notifications: coreSetup.notifications, uiSettings: coreStart.uiSettings, savedObjects: coreStart.savedObjects, - storage: ({ get: jest.fn() } as unknown) as Storage, + storage: { get: jest.fn() } as unknown as Storage, overlays: coreStart.overlays, http: coreSetup.http, history: {} as ScopedHistory, savedObjectsPlugin: savedObjectsPluginMock.createStartContract(), - share: ({ urlGenerators: { getUrlGenerator: jest.fn() } } as unknown) as SharePluginStart, + share: { urlGenerators: { getUrlGenerator: jest.fn() } } as unknown as SharePluginStart, ml: {} as GetMlSharedImportsReturnType, }; diff --git a/x-pack/plugins/transform/public/app/common/request.test.ts b/x-pack/plugins/transform/public/app/common/request.test.ts index 6a64c6af6428f..4caeb8789bb53 100644 --- a/x-pack/plugins/transform/public/app/common/request.test.ts +++ b/x-pack/plugins/transform/public/app/common/request.test.ts @@ -29,7 +29,7 @@ import { PivotQuery, } from './request'; import { LatestFunctionConfigUI } from '../../../common/types/transform'; -import { RuntimeField } from '../../../../../../src/plugins/data/common/index_patterns'; +import { RuntimeField } from '../../../../../../src/plugins/data/common'; const simpleQuery: PivotQuery = { query_string: { query: 'airline:AAL' } }; diff --git a/x-pack/plugins/transform/public/app/components/toast_notification_text.test.tsx b/x-pack/plugins/transform/public/app/components/toast_notification_text.test.tsx index d5fa6469d0a23..b085492f07e75 100644 --- a/x-pack/plugins/transform/public/app/components/toast_notification_text.test.tsx +++ b/x-pack/plugins/transform/public/app/components/toast_notification_text.test.tsx @@ -28,8 +28,7 @@ describe('ToastNotificationText', () => { test('should render the text within a modal', () => { const props = { overlays: {} as CoreStart['overlays'], - text: - 'a text message that is longer than 140 characters. a text message that is longer than 140 characters. a text message that is longer than 140 characters. ', + text: 'a text message that is longer than 140 characters. a text message that is longer than 140 characters. a text message that is longer than 140 characters. ', }; const { container } = render(); expect(container.textContent).toBe( diff --git a/x-pack/plugins/transform/public/app/hooks/use_delete_transform.tsx b/x-pack/plugins/transform/public/app/hooks/use_delete_transform.tsx index d2b58622f4e9e..f3633e8c5e943 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_delete_transform.tsx +++ b/x-pack/plugins/transform/public/app/hooks/use_delete_transform.tsx @@ -33,12 +33,14 @@ export const useDeleteIndexAndTargetIndex = (items: TransformListRow[]) => { const [userCanDeleteIndex, setUserCanDeleteIndex] = useState(false); const [indexPatternExists, setIndexPatternExists] = useState(false); - const toggleDeleteIndex = useCallback(() => setDeleteDestIndex(!deleteDestIndex), [ - deleteDestIndex, - ]); - const toggleDeleteIndexPattern = useCallback(() => setDeleteIndexPattern(!deleteIndexPattern), [ - deleteIndexPattern, - ]); + const toggleDeleteIndex = useCallback( + () => setDeleteDestIndex(!deleteDestIndex), + [deleteDestIndex] + ); + const toggleDeleteIndexPattern = useCallback( + () => setDeleteIndexPattern(!deleteIndexPattern), + [deleteIndexPattern] + ); const checkIndexPatternExists = useCallback( async (indexName: string) => { try { diff --git a/x-pack/plugins/transform/public/app/hooks/use_index_data.test.tsx b/x-pack/plugins/transform/public/app/hooks/use_index_data.test.tsx index 68f6fea3aa943..9729cc8e62b1f 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_index_data.test.tsx +++ b/x-pack/plugins/transform/public/app/hooks/use_index_data.test.tsx @@ -27,7 +27,7 @@ jest.mock('./use_api'); import { useAppDependencies } from '../__mocks__/app_dependencies'; import { MlSharedContext } from '../__mocks__/shared_context'; -import { RuntimeField } from '../../../../../../src/plugins/data/common/index_patterns'; +import { RuntimeField } from '../../../../../../src/plugins/data/common'; const query: SimpleQuery = { query_string: { @@ -57,11 +57,11 @@ describe('Transform: useIndexData()', () => { const { result, waitForNextUpdate } = renderHook( () => useIndexData( - ({ + { id: 'the-id', title: 'the-title', fields: [], - } as unknown) as SearchItems['indexPattern'], + } as unknown as SearchItems['indexPattern'], query, runtimeMappings ), diff --git a/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts b/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts index 329e2d5f87131..a79b96acd5d7d 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts +++ b/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts @@ -98,10 +98,8 @@ export const usePivotData = ( requestPayload: StepDefineExposedState['previewRequest'], combinedRuntimeMappings?: StepDefineExposedState['runtimeMappings'] ): UseIndexDataReturnType => { - const [ - previewMappingsProperties, - setPreviewMappingsProperties, - ] = useState({}); + const [previewMappingsProperties, setPreviewMappingsProperties] = + useState({}); const api = useApi(); const { ml: { diff --git a/x-pack/plugins/transform/public/app/lib/authorization/components/authorization_provider.tsx b/x-pack/plugins/transform/public/app/lib/authorization/components/authorization_provider.tsx index fd79a413fbbb6..875c0f60969ed 100644 --- a/x-pack/plugins/transform/public/app/lib/authorization/components/authorization_provider.tsx +++ b/x-pack/plugins/transform/public/app/lib/authorization/components/authorization_provider.tsx @@ -46,7 +46,11 @@ interface Props { } export const AuthorizationProvider = ({ privilegesEndpoint, children }: Props) => { - const { isLoading, error, data: privilegesData } = useRequest({ + const { + isLoading, + error, + data: privilegesData, + } = useRequest({ path: privilegesEndpoint, method: 'get', }); diff --git a/x-pack/plugins/transform/public/app/lib/authorization/components/common.ts b/x-pack/plugins/transform/public/app/lib/authorization/components/common.ts index 5599e3f423277..d059f73a76137 100644 --- a/x-pack/plugins/transform/public/app/lib/authorization/components/common.ts +++ b/x-pack/plugins/transform/public/app/lib/authorization/components/common.ts @@ -32,27 +32,27 @@ function isPrivileges(arg: unknown): arg is Privileges { export const toArray = (value: string | string[]): string[] => Array.isArray(value) ? value : [value]; -export const hasPrivilegeFactory = (privileges: Privileges | undefined | null) => ( - privilege: Privilege -) => { - const [section, requiredPrivilege] = privilege; - if (isPrivileges(privileges) && !privileges.missingPrivileges[section]) { - // if the section does not exist in our missingPrivileges, everything is OK - return true; - } - if (isPrivileges(privileges) && privileges.missingPrivileges[section]!.length === 0) { - return true; - } - if (requiredPrivilege === '*') { - // If length > 0 and we require them all... KO - return false; - } - // If we require _some_ privilege, we make sure that the one - // we require is *not* in the missingPrivilege array - return ( - isPrivileges(privileges) && !privileges.missingPrivileges[section]!.includes(requiredPrivilege) - ); -}; +export const hasPrivilegeFactory = + (privileges: Privileges | undefined | null) => (privilege: Privilege) => { + const [section, requiredPrivilege] = privilege; + if (isPrivileges(privileges) && !privileges.missingPrivileges[section]) { + // if the section does not exist in our missingPrivileges, everything is OK + return true; + } + if (isPrivileges(privileges) && privileges.missingPrivileges[section]!.length === 0) { + return true; + } + if (requiredPrivilege === '*') { + // If length > 0 and we require them all... KO + return false; + } + // If we require _some_ privilege, we make sure that the one + // we require is *not* in the missingPrivilege array + return ( + isPrivileges(privileges) && + !privileges.missingPrivileges[section]!.includes(requiredPrivilege) + ); + }; // create the text for button's tooltips if the user // doesn't have the permission to press that button diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/pivot_configuration/pivot_configuration.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/pivot_configuration/pivot_configuration.tsx index b7cbd422780c1..f75893a2e42d2 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/pivot_configuration/pivot_configuration.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/pivot_configuration/pivot_configuration.tsx @@ -31,14 +31,8 @@ export const PivotConfiguration: FC = memo( updateGroupBy, } = actions; - const { - aggList, - aggOptions, - aggOptionsData, - groupByList, - groupByOptions, - groupByOptionsData, - } = state; + const { aggList, aggOptions, aggOptionsData, groupByList, groupByOptions, groupByOptionsData } = + state; return ( diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx index 6bf1abc2cc61f..7ccf986d5d497 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx @@ -50,7 +50,7 @@ import { PutTransformsLatestRequestSchema, PutTransformsPivotRequestSchema, } from '../../../../../../common/api_schemas/transforms'; -import type { RuntimeField } from '../../../../../../../../../src/plugins/data/common/index_patterns'; +import type { RuntimeField } from '../../../../../../../../../src/plugins/data/common'; import { isPopulatedObject } from '../../../../../../common/shared_imports'; import { isLatestTransform } from '../../../../../../common/types/transform'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/common.test.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/common.test.ts index 5891e8b330b94..9b8dcc1a623e3 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/common.test.ts +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/common.test.ts @@ -8,7 +8,7 @@ import { getPivotDropdownOptions } from '../common'; import { IndexPattern } from '../../../../../../../../../../src/plugins/data/public'; import { FilterAggForm } from './filter_agg/components'; -import type { RuntimeField } from '../../../../../../../../../../src/plugins/data/common/index_patterns'; +import type { RuntimeField } from '../../../../../../../../../../src/plugins/data/common'; describe('Transform: Define Pivot Common', () => { test('getPivotDropdownOptions()', () => { diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_agg_form.test.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_agg_form.test.tsx index 7f9c4256f7755..61bc7ac0ecdd1 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_agg_form.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_agg_form.test.tsx @@ -27,7 +27,7 @@ describe('FilterAggForm', () => { } as RuntimeField, }; - const indexPattern = ({ + const indexPattern = { fields: { getByName: jest.fn((fieldName: string) => { if (fieldName === 'test_text_field') { @@ -42,7 +42,7 @@ describe('FilterAggForm', () => { } }), }, - } as unknown) as IndexPattern; + } as unknown as IndexPattern; test('should render only select dropdown on empty configuration', async () => { const onChange = jest.fn(); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_advanced_pivot_editor.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_advanced_pivot_editor.ts index 1ab68e35e2605..d69183820db71 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_advanced_pivot_editor.ts +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_advanced_pivot_editor.ts @@ -26,18 +26,15 @@ export const useAdvancedPivotEditor = ( // Advanced editor for pivot config state const [isAdvancedEditorSwitchModalVisible, setAdvancedEditorSwitchModalVisible] = useState(false); - const [ - isAdvancedPivotEditorApplyButtonEnabled, - setAdvancedPivotEditorApplyButtonEnabled, - ] = useState(false); + const [isAdvancedPivotEditorApplyButtonEnabled, setAdvancedPivotEditorApplyButtonEnabled] = + useState(false); const [isAdvancedPivotEditorEnabled, setAdvancedPivotEditorEnabled] = useState( defaults.isAdvancedPivotEditorEnabled ); - const [advancedEditorConfigLastApplied, setAdvancedEditorConfigLastApplied] = useState( - stringifiedPivotConfig - ); + const [advancedEditorConfigLastApplied, setAdvancedEditorConfigLastApplied] = + useState(stringifiedPivotConfig); const { convertToJson, diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_advanced_runtime_mappings_editor.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_advanced_runtime_mappings_editor.ts index dd58456e15adb..ec9c0392e7f17 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_advanced_runtime_mappings_editor.ts +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_advanced_runtime_mappings_editor.ts @@ -22,24 +22,18 @@ export const useAdvancedRuntimeMappingsEditor = (defaults: StepDefineExposedStat ); const [runtimeMappings, setRuntimeMappings] = useState(defaults.runtimeMappings); - const [ - isRuntimeMappingsEditorSwitchModalVisible, - setRuntimeMappingsEditorSwitchModalVisible, - ] = useState(false); + const [isRuntimeMappingsEditorSwitchModalVisible, setRuntimeMappingsEditorSwitchModalVisible] = + useState(false); const [isRuntimeMappingsEditorEnabled, setRuntimeMappingsEditorEnabled] = useState( defaults.isRuntimeMappingsEditorEnabled ); - const [ - isRuntimeMappingsEditorApplyButtonEnabled, - setRuntimeMappingsEditorApplyButtonEnabled, - ] = useState(false); + const [isRuntimeMappingsEditorApplyButtonEnabled, setRuntimeMappingsEditorApplyButtonEnabled] = + useState(false); - const [ - advancedEditorRuntimeMappingsLastApplied, - setAdvancedEditorRuntimeMappingsLastApplied, - ] = useState(stringifiedRuntimeMappings); + const [advancedEditorRuntimeMappingsLastApplied, setAdvancedEditorRuntimeMappingsLastApplied] = + useState(stringifiedRuntimeMappings); const { convertToJson, diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_advanced_source_editor.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_advanced_source_editor.ts index fbd3b0e19304a..71fc7cb1e3c70 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_advanced_source_editor.ts +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_advanced_source_editor.ts @@ -20,28 +20,21 @@ export const useAdvancedSourceEditor = ( // Advanced editor for source config state const [sourceConfigUpdated, setSourceConfigUpdated] = useState(defaults.sourceConfigUpdated); - const [ - isAdvancedSourceEditorSwitchModalVisible, - setAdvancedSourceEditorSwitchModalVisible, - ] = useState(false); + const [isAdvancedSourceEditorSwitchModalVisible, setAdvancedSourceEditorSwitchModalVisible] = + useState(false); const [isAdvancedSourceEditorEnabled, setAdvancedSourceEditorEnabled] = useState( defaults.isAdvancedSourceEditorEnabled ); - const [ - isAdvancedSourceEditorApplyButtonEnabled, - setAdvancedSourceEditorApplyButtonEnabled, - ] = useState(false); + const [isAdvancedSourceEditorApplyButtonEnabled, setAdvancedSourceEditorApplyButtonEnabled] = + useState(false); - const [ - advancedEditorSourceConfigLastApplied, - setAdvancedEditorSourceConfigLastApplied, - ] = useState(stringifiedSourceConfig); + const [advancedEditorSourceConfigLastApplied, setAdvancedEditorSourceConfigLastApplied] = + useState(stringifiedSourceConfig); - const [advancedEditorSourceConfig, setAdvancedEditorSourceConfig] = useState( - stringifiedSourceConfig - ); + const [advancedEditorSourceConfig, setAdvancedEditorSourceConfig] = + useState(stringifiedSourceConfig); const applyAdvancedSourceEditorChanges = () => { const sourceConfig = JSON.parse(advancedEditorSourceConfig); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_latest_function_config.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_latest_function_config.ts index d52bd3f5bf706..34b47b50a7c9e 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_latest_function_config.ts +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_latest_function_config.ts @@ -42,7 +42,7 @@ function getOptions( const aggConfig = aggConfigs.aggs[0]; const param = aggConfig.type.params.find((p) => p.type === 'field'); const filteredIndexPatternFields = param - ? ((param as unknown) as FieldParamType) + ? (param as unknown as FieldParamType) .getAvailableFields(aggConfig) // runtimeMappings may already include runtime fields defined by the index pattern .filter((ip) => ip.runtimeField === undefined) @@ -148,9 +148,10 @@ export function useLatestFunctionConfig( return latest ? { latest } : undefined; }, [config]); - const validationStatus = useMemo(() => validateLatestConfig(requestPayload?.latest), [ - requestPayload?.latest, - ]); + const validationStatus = useMemo( + () => validateLatestConfig(requestPayload?.latest), + [requestPayload?.latest] + ); return { config, diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_pivot_config.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_pivot_config.ts index a291f648a835e..2415f04c220a6 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_pivot_config.ts +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_pivot_config.ts @@ -346,10 +346,10 @@ export const usePivotConfig = ( const pivotAggsArr = useMemo(() => dictionaryToArray(aggList), [aggList]); const pivotGroupByArr = useMemo(() => dictionaryToArray(groupByList), [groupByList]); - const requestPayload = useMemo(() => getRequestPayload(pivotAggsArr, pivotGroupByArr), [ - pivotAggsArr, - pivotGroupByArr, - ]); + const requestPayload = useMemo( + () => getRequestPayload(pivotAggsArr, pivotGroupByArr), + [pivotAggsArr, pivotGroupByArr] + ); const validationStatus = useMemo(() => { return validatePivotConfig(requestPayload.pivot); diff --git a/x-pack/plugins/transform/public/app/services/es_index_service.ts b/x-pack/plugins/transform/public/app/services/es_index_service.ts index 46d4cab60f734..d9014602cc393 100644 --- a/x-pack/plugins/transform/public/app/services/es_index_service.ts +++ b/x-pack/plugins/transform/public/app/services/es_index_service.ts @@ -7,7 +7,7 @@ import { HttpSetup, SavedObjectsClientContract } from 'kibana/public'; import { API_BASE_PATH } from '../../../common/constants'; -import { IIndexPattern } from '../../../../../../src/plugins/data/common/index_patterns'; +import { IIndexPattern } from '../../../../../../src/plugins/data/common'; export class IndexService { async canDeleteIndex(http: HttpSetup) { diff --git a/x-pack/plugins/transform/server/routes/api/transforms.ts b/x-pack/plugins/transform/server/routes/api/transforms.ts index aa30a60b3421c..76aac9686c37e 100644 --- a/x-pack/plugins/transform/server/routes/api/transforms.ts +++ b/x-pack/plugins/transform/server/routes/api/transforms.ts @@ -60,7 +60,7 @@ import { addBasePath } from '../index'; import { isRequestTimeout, fillResultsWithTimeouts, wrapError, wrapEsError } from './error_utils'; import { registerTransformsAuditMessagesRoutes } from './transforms_audit_messages'; import { registerTransformNodesRoutes } from './transforms_nodes'; -import { IIndexPattern } from '../../../../../../src/plugins/data/common/index_patterns'; +import { IIndexPattern } from '../../../../../../src/plugins/data/common'; import { isLatestTransform } from '../../../common/types/transform'; import { isKeywordDuplicate } from '../../../common/utils/field_utils'; @@ -136,12 +136,11 @@ export function registerTransformsRoutes(routeDependencies: RouteDependencies) { license.guardApiRoute( async (ctx, req, res) => { try { - const { - body, - } = await ctx.core.elasticsearch.client.asCurrentUser.transform.getTransformStats({ - size: 1000, - transform_id: '_all', - }); + const { body } = + await ctx.core.elasticsearch.client.asCurrentUser.transform.getTransformStats({ + size: 1000, + transform_id: '_all', + }); return res.ok({ body }); } catch (e) { return res.customError(wrapError(wrapEsError(e))); @@ -167,11 +166,10 @@ export function registerTransformsRoutes(routeDependencies: RouteDependencies) { license.guardApiRoute(async (ctx, req, res) => { const { transformId } = req.params; try { - const { - body, - } = await ctx.core.elasticsearch.client.asCurrentUser.transform.getTransformStats({ - transform_id: transformId, - }); + const { body } = + await ctx.core.elasticsearch.client.asCurrentUser.transform.getTransformStats({ + transform_id: transformId, + }); return res.ok({ body }); } catch (e) { return res.customError(wrapError(wrapEsError(e))); @@ -250,13 +248,12 @@ export function registerTransformsRoutes(routeDependencies: RouteDependencies) { const { transformId } = req.params; try { - const { - body, - } = await ctx.core.elasticsearch.client.asCurrentUser.transform.updateTransform({ - // @ts-expect-error query doesn't satisfy QueryDslQueryContainer from @elastic/elasticsearch - body: req.body, - transform_id: transformId, - }); + const { body } = + await ctx.core.elasticsearch.client.asCurrentUser.transform.updateTransform({ + // @ts-expect-error query doesn't satisfy QueryDslQueryContainer from @elastic/elasticsearch + body: req.body, + transform_id: transformId, + }); return res.ok({ body, }); @@ -582,22 +579,22 @@ const previewTransformHandler: RequestHandler< } }; -const startTransformsHandler: RequestHandler< - undefined, - undefined, - StartTransformsRequestSchema -> = async (ctx, req, res) => { - const transformsInfo = req.body; +const startTransformsHandler: RequestHandler = + async (ctx, req, res) => { + const transformsInfo = req.body; - try { - const body = await startTransforms(transformsInfo, ctx.core.elasticsearch.client.asCurrentUser); - return res.ok({ - body, - }); - } catch (e) { - return res.customError(wrapError(wrapEsError(e))); - } -}; + try { + const body = await startTransforms( + transformsInfo, + ctx.core.elasticsearch.client.asCurrentUser + ); + return res.ok({ + body, + }); + } catch (e) { + return res.customError(wrapError(wrapEsError(e))); + } + }; async function startTransforms( transformsInfo: StartTransformsRequestSchema, @@ -627,21 +624,18 @@ async function startTransforms( return results; } -const stopTransformsHandler: RequestHandler< - undefined, - undefined, - StopTransformsRequestSchema -> = async (ctx, req, res) => { - const transformsInfo = req.body; +const stopTransformsHandler: RequestHandler = + async (ctx, req, res) => { + const transformsInfo = req.body; - try { - return res.ok({ - body: await stopTransforms(transformsInfo, ctx.core.elasticsearch.client.asCurrentUser), - }); - } catch (e) { - return res.customError(wrapError(wrapEsError(e))); - } -}; + try { + return res.ok({ + body: await stopTransforms(transformsInfo, ctx.core.elasticsearch.client.asCurrentUser), + }); + } catch (e) { + return res.customError(wrapError(wrapEsError(e))); + } + }; async function stopTransforms( transformsInfo: StopTransformsRequestSchema, diff --git a/x-pack/plugins/transform/server/routes/api/transforms_audit_messages.ts b/x-pack/plugins/transform/server/routes/api/transforms_audit_messages.ts index b762f20b55b48..b6a707f0f12fd 100644 --- a/x-pack/plugins/transform/server/routes/api/transforms_audit_messages.ts +++ b/x-pack/plugins/transform/server/routes/api/transforms_audit_messages.ts @@ -78,20 +78,19 @@ export function registerTransformsAuditMessagesRoutes({ router, license }: Route } try { - const { - body: resp, - } = await ctx.core.elasticsearch.client.asCurrentUser.search({ - index: ML_DF_NOTIFICATION_INDEX_PATTERN, - ignore_unavailable: true, - size: SIZE, - body: { - sort: [ - { timestamp: { order: 'desc' as const } }, - { transform_id: { order: 'asc' as const } }, - ], - query, - }, - }); + const { body: resp } = + await ctx.core.elasticsearch.client.asCurrentUser.search({ + index: ML_DF_NOTIFICATION_INDEX_PATTERN, + ignore_unavailable: true, + size: SIZE, + body: { + sort: [ + { timestamp: { order: 'desc' as const } }, + { transform_id: { order: 'asc' as const } }, + ], + query, + }, + }); let messages: AuditMessage[] = []; // TODO: remove typeof checks when appropriate overloading is added for the `search` API diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 082562b4e9819..d36304ab06f25 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -2460,7 +2460,6 @@ "discover.fieldChooser.filter.availableFieldsTitle": "利用可能なフィールド", "discover.fieldChooser.filter.fieldSelectorLabel": "{id}フィルターオプションの選択", "discover.fieldChooser.filter.filterByTypeLabel": "タイプでフィルタリング", - "discover.fieldChooser.filter.hideMissingFieldsLabel": "未入力のフィールドを非表示", "discover.fieldChooser.filter.indexAndFieldsSectionAriaLabel": "インデックスとフィールド", "discover.fieldChooser.filter.popularTitle": "人気", "discover.fieldChooser.filter.searchableLabel": "検索可能", @@ -4370,14 +4369,11 @@ "savedObjects.saveModalOrigin.originAfterSavingSwitchLabel": "保存後に{originVerb}から{origin}", "savedObjects.saveModalOrigin.returnToOriginLabel": "戻る", "savedObjects.saveModalOrigin.saveAndReturnLabel": "保存して戻る", - "savedObjectsManagement.breadcrumb.edit": "{savedObjectType}を編集", "savedObjectsManagement.breadcrumb.index": "保存されたオブジェクト", "savedObjectsManagement.deleteConfirm.modalDeleteButtonLabel": "削除", "savedObjectsManagement.deleteConfirm.modalDescription": "このアクションはオブジェクトをKibanaから永久に削除します。", "savedObjectsManagement.deleteConfirm.modalTitle": "「{title}」を削除しますか?", "savedObjectsManagement.deleteSavedObjectsConfirmModalDescription": "この操作は次の保存されたオブジェクトを削除します:", - "savedObjectsManagement.field.offLabel": "オフ", - "savedObjectsManagement.field.onLabel": "オン", "savedObjectsManagement.importSummary.createdCountHeader": "{createdCount}件の新規項目", "savedObjectsManagement.importSummary.createdOutcomeLabel": "作成済み", "savedObjectsManagement.importSummary.errorCountHeader": "{errorCount}件のエラー", @@ -4479,21 +4475,10 @@ "savedObjectsManagement.objectsTable.unableFindSavedObjectNotificationMessage": "保存されたオブジェクトが見つかりません", "savedObjectsManagement.objectsTable.unableFindSavedObjectsNotificationMessage": "保存されたオブジェクトが見つかりません", "savedObjectsManagement.objectView.unableFindSavedObjectNotificationMessage": "保存されたオブジェクトが見つかりません", - "savedObjectsManagement.view.cancelButtonAriaLabel": "キャンセル", - "savedObjectsManagement.view.cancelButtonLabel": "キャンセル", - "savedObjectsManagement.view.deleteItemButtonLabel": "{title}を削除", - "savedObjectsManagement.view.editItemTitle": "{title}の編集", "savedObjectsManagement.view.fieldDoesNotExistErrorMessage": "このオブジェクトに関連付けられたフィールドは、現在このインデックスパターンに存在しません。", - "savedObjectsManagement.view.howToFixErrorDescription": "このエラーの原因がわかる場合は修正してください。わからない場合は上の削除ボタンをクリックしてください。", - "savedObjectsManagement.view.howToModifyObjectDescription": "オブジェクトの編集は上級ユーザー向けです。オブジェクトのプロパティが検証されておらず、無効なオブジェクトはエラー、データ損失、またはそれ以上の問題の原因となります。コードを熟知した人に指示されていない限り、この設定は変更しない方が無難です。", - "savedObjectsManagement.view.howToModifyObjectTitle": "十分ご注意ください!", "savedObjectsManagement.view.indexPatternDoesNotExistErrorMessage": "このオブジェクトに関連付けられたインデックスパターンは現在存在しません。", - "savedObjectsManagement.view.saveButtonAriaLabel": "{ title }オブジェクトを保存", - "savedObjectsManagement.view.saveButtonLabel": "{ title }オブジェクトを保存", "savedObjectsManagement.view.savedObjectProblemErrorMessage": "この保存されたオブジェクトに問題があります", "savedObjectsManagement.view.savedSearchDoesNotExistErrorMessage": "このオブジェクトに関連付けられた保存された検索は現在存在しません。", - "savedObjectsManagement.view.viewItemButtonLabel": "{title}を表示", - "savedObjectsManagement.view.viewItemTitle": "{title}を表示", "security.checkup.dismissButtonText": "閉じる", "security.checkup.dontShowAgain": "今後表示しない", "security.checkup.insecureClusterMessage": "1 ビットを失わないでください。Elastic では無料でデータを保護できます。", @@ -5242,9 +5227,7 @@ "visTypeTimeseries.indexPatternSelect.label": "インデックスパターン", "visTypeTimeseries.indexPatternSelect.queryAllIndexesText": "すべてのインデックスにクエリを実行するには * を使用します", "visTypeTimeseries.indexPatternSelect.switchModePopover.areaLabel": "インデックスパターン選択モードを構成", - "visTypeTimeseries.indexPatternSelect.switchModePopover.text": "インデックスパターンは、データ探索で対象とする1つ以上のElasticsearchインデックスを定義します。ElasticsearchインデックスまたはKibanaインデックスパターン(推奨)を使用できます。", "visTypeTimeseries.indexPatternSelect.switchModePopover.title": "インデックスパターン選択モード", - "visTypeTimeseries.indexPatternSelect.switchModePopover.useKibanaIndices": "Kibanaインデックスパターンのみを使用", "visTypeTimeseries.kbnVisTypes.metricsDescription": "時系列データの高度な分析を実行します。", "visTypeTimeseries.kbnVisTypes.metricsTitle": "TSVB", "visTypeTimeseries.lastValueModeIndicator.lastBucketDate": "バケット:{lastBucketDate}", @@ -5571,7 +5554,6 @@ "visTypeTimeseries.visEditorVisualization.changesWillBeAutomaticallyAppliedMessage": "変更が自動的に適用されます。", "visTypeTimeseries.visEditorVisualization.indexPatternMode.dismissNoticeButtonText": "閉じる", "visTypeTimeseries.visEditorVisualization.indexPatternMode.link": "確認してください。", - "visTypeTimeseries.visEditorVisualization.indexPatternMode.notificationMessage": "お知らせElasticsearchインデックスまたはKibanaインデックスパターンからデータを可視化できるようになりました。{indexPatternModeLink}。", "visTypeTimeseries.visEditorVisualization.indexPatternMode.notificationTitle": "TSVBはインデックスパターンをサポートします", "visTypeTimeseries.visPicker.gaugeLabel": "ゲージ", "visTypeTimeseries.visPicker.metricLabel": "メトリック", @@ -9755,10 +9737,6 @@ "xpack.enterpriseSearch.appSearch.tokens.search.description": "エンドポイントのみの検索では、公開検索キーが使用されます。", "xpack.enterpriseSearch.appSearch.tokens.search.name": "公開検索キー", "xpack.enterpriseSearch.appSearch.tokens.update": "APIキー'{name}'が更新されました", - "xpack.enterpriseSearch.beta.buttonLabel": "これはベータ版のユーザーインターフェースです", - "xpack.enterpriseSearch.beta.popover.description": "エンタープライズサーチのKibanaインターフェースはベータ版の機能です。この機能は変更される可能性があり、公開されている機能と同じレベルのサポートは受けられません。このインターフェースは、8.0リリースのエンタープライズサーチの唯一の管理パネルです。それまでは、スタンドアロンのエンタープライズサーチUIが提供され、サポートされます。", - "xpack.enterpriseSearch.beta.popover.footerDetail": "{learnMoreLink}または{standaloneUILink}。", - "xpack.enterpriseSearch.beta.popover.title": "Kibanaのエンタープライズサーチはベータ版のユーザーインターフェースです", "xpack.enterpriseSearch.emailLabel": "メール", "xpack.enterpriseSearch.enterpriseSearch.setupGuide.description": "場所を問わず、何でも検索。組織を支える多忙なチームのために、パワフルでモダンな検索エクスペリエンスを簡単に導入できます。Webサイトやアプリ、ワークプレイスに事前調整済みの検索をすばやく追加しましょう。何でもシンプルに検索できます。", "xpack.enterpriseSearch.enterpriseSearch.setupGuide.notConfigured": "エンタープライズサーチはまだKibanaインスタンスで構成されていません。", @@ -9963,7 +9941,6 @@ "xpack.enterpriseSearch.workplaceSearch.contentSource.configCompleted.privateDisabled.button": "非公開コンテンツソースの詳細。", "xpack.enterpriseSearch.workplaceSearch.contentSource.configCompleted.privateDisabled.message": "必ずセキュリティ設定で{securityLink}してください。", "xpack.enterpriseSearch.workplaceSearch.contentSource.configCustom.button": "カスタムAPIソースの作成", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCustom.docs.link": "カスタムAPIソースの詳細については、{link}。", "xpack.enterpriseSearch.workplaceSearch.contentSource.configDocs.applicationPortal.button": "{name}アプリケーションポータル", "xpack.enterpriseSearch.workplaceSearch.contentSource.configIntro.alt.text": "接続の例", "xpack.enterpriseSearch.workplaceSearch.contentSource.configIntro.configure.button": "{name}の構成", @@ -10027,11 +10004,6 @@ "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.updated.message": "スキーマが更新されました。", "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.documentLevelPermissions.text": "ドキュメントレベルのアクセス権は、定義されたルールに基づいて、ユーザーコンテンツアクセスを管理します。個人またはグループの特定のドキュメントへのアクセスを許可または拒否します。", "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.documentLevelPermissions.title": "プラチナライセンスで提供されているドキュメントレベルのアクセス権", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.explore.button": "プラチナ機能の詳細", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.globalAccessPermissions.text": "接続しているサービスユーザーがアクセス可能なすべてのドキュメントは同期され、組織のユーザーまたはグループのユーザーが使用できるようになります。ドキュメントは直ちに検索で使用できます", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.private.text": "返された結果は自分に固有で関連性があります。このソースを接続しても、個人データは他の検索ユーザーに公開されません。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.remote.text": "メッセージデータと他の情報は、Workplace Searchエクスペリエンスからリアルタイムで検索可能です。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.searchable.text": "次の項目は検索可能です。", "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.syncFrequency.text": "このソースは、(初回の同期の後){duration}ごとに{name}から新しいコンテンツを取得します。", "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.displaySettings.description": "カスタムAPIソース検索結果の内容と表示をカスタマイズします。", "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.displaySettings.title": "表示設定", @@ -10083,7 +10055,6 @@ "xpack.enterpriseSearch.workplaceSearch.groups.filterSources.buttonText": "ソース", "xpack.enterpriseSearch.workplaceSearch.groups.groupDeleted": "グループ「{groupName}」が正常に削除されました。", "xpack.enterpriseSearch.workplaceSearch.groups.groupManagerHeaderTitle": "{label}を管理", - "xpack.enterpriseSearch.workplaceSearch.groups.groupManagerSelectAllToggle": "{action}すべて", "xpack.enterpriseSearch.workplaceSearch.groups.groupManagerSourceEmpty.body": "まだ共有コンテンツソースが追加されていない可能性があります。", "xpack.enterpriseSearch.workplaceSearch.groups.groupManagerSourceEmpty.title": "おっと!", "xpack.enterpriseSearch.workplaceSearch.groups.groupManagerUpdateAddSourceButton": "共有ソースを追加", @@ -10146,7 +10117,6 @@ "xpack.enterpriseSearch.workplaceSearch.nav.settingsSourcePrioritization": "コンテンツソースコネクター", "xpack.enterpriseSearch.workplaceSearch.nav.sources": "ソース", "xpack.enterpriseSearch.workplaceSearch.nonPlatinumOauthDescription": "Workplace Search検索APIを安全に使用するために、OAuthアプリケーションを構成します。プラチナライセンスにアップグレードして、検索APIを有効にし、OAuthアプリケーションを作成します。", - "xpack.enterpriseSearch.workplaceSearch.nonPlatinumOauthLinkLabel": "プラチナ機能の詳細", "xpack.enterpriseSearch.workplaceSearch.nonPlatinumOauthTitle": "カスタム検索アプリケーションのOAuthを構成", "xpack.enterpriseSearch.workplaceSearch.oauth.description": "組織のOAuthクライアントを作成します。", "xpack.enterpriseSearch.workplaceSearch.oauthAuthorize.authorizationDescription": "{strongClientName}によるアカウントの使用を許可しますか?", @@ -10163,7 +10133,6 @@ "xpack.enterpriseSearch.workplaceSearch.organizationStats.activeUsers": "アクティブなユーザー", "xpack.enterpriseSearch.workplaceSearch.organizationStats.invitations": "招待", "xpack.enterpriseSearch.workplaceSearch.organizationStats.privateSources": "プライベートソース", - "xpack.enterpriseSearch.workplaceSearch.organizationStats.sharedSources": "共有ソース", "xpack.enterpriseSearch.workplaceSearch.organizationStats.title": "使用統計情報", "xpack.enterpriseSearch.workplaceSearch.orgNameOnboarding.buttonLabel": "組織名を指定", "xpack.enterpriseSearch.workplaceSearch.orgNameOnboarding.description": "同僚を招待する前に、組織名を指定し、認識しやすくしてください。", @@ -10178,7 +10147,6 @@ "xpack.enterpriseSearch.workplaceSearch.overviewUsersCard.title": "検索できるように、同僚を招待しました。", "xpack.enterpriseSearch.workplaceSearch.personalDashboardSourceError": "ソースに接続できませんでした。ヘルプについては管理者に問い合わせてください。エラーメッセージ:{error}", "xpack.enterpriseSearch.workplaceSearch.platinumFeature": "プラチナ機能", - "xpack.enterpriseSearch.workplaceSearch.privateDashboard.readOnlyMode.warning": "現在、定期メンテナンス中のため、Workplace Searchは検索でのみ使用できます。詳細については、システム管理者に連絡してください。", "xpack.enterpriseSearch.workplaceSearch.privatePlatinumCallout.text": "非公開ソースにはプラチナライセンスが必要です。", "xpack.enterpriseSearch.workplaceSearch.privateSource.text": "非公開ソース", "xpack.enterpriseSearch.workplaceSearch.privateSources.text": "非公開ソース", @@ -10258,7 +10226,6 @@ "xpack.enterpriseSearch.workplaceSearch.sources.additionalConfig.heading": "追加の構成が必要", "xpack.enterpriseSearch.workplaceSearch.sources.applicationLinkTitles.github": "GitHub開発者ポータル", "xpack.enterpriseSearch.workplaceSearch.sources.baseUrlTitles.github": "GitHub Enterprise URL", - "xpack.enterpriseSearch.workplaceSearch.sources.config.description": "変更するコンテンツソースコネクター設定を編集します。", "xpack.enterpriseSearch.workplaceSearch.sources.config.link": "コンテンツソースコネクター設定を編集", "xpack.enterpriseSearch.workplaceSearch.sources.config.title": "コンテンツソース構成", "xpack.enterpriseSearch.workplaceSearch.sources.configuration.title": "構成", @@ -10338,7 +10305,6 @@ "xpack.enterpriseSearch.workplaceSearch.sources.private.header.description": "非公開コンテンツソースは自分のみが使用できます。", "xpack.enterpriseSearch.workplaceSearch.sources.private.header.title": "自分の非公開コンテンツソース", "xpack.enterpriseSearch.workplaceSearch.sources.private.link": "非公開コンテンツソースを追加", - "xpack.enterpriseSearch.workplaceSearch.sources.private.privateShared.header.title": "共有コンテンツソース", "xpack.enterpriseSearch.workplaceSearch.sources.private.vewOnly.description": "グループと共有するすべてのソースのステータスを確認します。", "xpack.enterpriseSearch.workplaceSearch.sources.private.vewOnly.title": "グループソースの確認", "xpack.enterpriseSearch.workplaceSearch.sources.ready.text": "検索できます", @@ -10349,9 +10315,6 @@ "xpack.enterpriseSearch.workplaceSearch.sources.settings.heading": "設定", "xpack.enterpriseSearch.workplaceSearch.sources.settings.title": "コンテンツソース名", "xpack.enterpriseSearch.workplaceSearch.sources.settingsModal.text": "ソースドキュメントはWorkplace Searchから削除されます。{lineBreak}{name}を削除しますか?", - "xpack.enterpriseSearch.workplaceSearch.sources.shared.empty.description": "コンテンツソースが自分と共有されたら、ここに表示され、検索エクスペリエンスで使用できます。", - "xpack.enterpriseSearch.workplaceSearch.sources.shared.empty.title": "コンテンツソースがありません", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceContent.searchBar.placeholder": "{prefix}コンテンツ...", "xpack.enterpriseSearch.workplaceSearch.sources.sourceContent.title": "ソースコンテンツ", "xpack.enterpriseSearch.workplaceSearch.sources.sourceDisabled.button": "プラチナライセンスの詳細", "xpack.enterpriseSearch.workplaceSearch.sources.sourceDisabled.description": "組織のライセンスレベルが変更されました。データは安全ですが、ドキュメントレベルのアクセス権はサポートされなくなり、このソースの検索は無効になっています。このソースを再有効化するには、プラチナライセンスにアップグレードしてください。", @@ -10386,7 +10349,6 @@ "xpack.enterpriseSearch.workplaceSearch.sources.time.header": "時間", "xpack.enterpriseSearch.workplaceSearch.sources.totalDocuments.label": "合計ドキュメント数", "xpack.enterpriseSearch.workplaceSearch.sources.understandButton": "理解します", - "xpack.enterpriseSearch.workplaceSearch.sourcesOnboardingCard.buttonLabel": "{label}ソースを追加", "xpack.enterpriseSearch.workplaceSearch.sourcesView.modal.docPermissions.description": "ユーザーおよびグループマッピングが構成されるまでは、ドキュメントをWorkplace Searchから検索できません。{documentPermissionsLink}", "xpack.enterpriseSearch.workplaceSearch.sourcesView.modal.heading": "{addedSourceName}には追加の構成が必要です", "xpack.enterpriseSearch.workplaceSearch.sourcesView.modal.success": "{addedSourceName}は正常に接続されました。初期コンテンツ同期がすでに実行中です。ドキュメントレベルのアクセス権情報を同期することを選択したので、{externalIdentitiesLink}を使用してユーザーおよびグループマッピングを指定する必要があります。", @@ -10394,7 +10356,6 @@ "xpack.enterpriseSearch.workplaceSearch.title": "Workplace Search", "xpack.enterpriseSearch.workplaceSearch.update.label": "更新", "xpack.enterpriseSearch.workplaceSearch.url.label": "URL", - "xpack.enterpriseSearch.workplaceSearch.usersOnboardingCard.buttonLabel": "{label}ユーザーを招待", "xpack.eventLog.savedObjectProviderRegistry.getProvidersClient.noDefaultProvider": "イベントログにはデフォルトプロバイダーが必要です。", "xpack.features.advancedSettingsFeatureName": "高度な設定", "xpack.features.dashboardFeatureName": "ダッシュボード", @@ -17497,7 +17458,6 @@ "xpack.monitoring.alerts.kibanaVersionMismatch.label": "Kibana バージョン不一致", "xpack.monitoring.alerts.kibanaVersionMismatch.shortAction": "すべてのインスタンスのバージョンが同じことを確認してください。", "xpack.monitoring.alerts.kibanaVersionMismatch.ui.firingMessage": "このクラスターでは、複数のバージョンの Kibana({versions})が実行されています。", - "xpack.monitoring.alerts.legacyAlert.expressionText": "構成するものがありません。", "xpack.monitoring.alerts.licenseExpiration.action": "ライセンスを更新してください。", "xpack.monitoring.alerts.licenseExpiration.actionVariables.clusterName": "ライセンスが属しているクラスター。", "xpack.monitoring.alerts.licenseExpiration.actionVariables.expiredDate": "ライセンスの有効期限。", @@ -26158,13 +26118,7 @@ "xpack.uptime.createPackagePolicy.stepConfigure.tlsSettings.label": "TLS設定", "xpack.uptime.durationChart.emptyPrompt.description": "このモニターは選択された時間範囲で一度も{emphasizedText}していません。", "xpack.uptime.durationChart.emptyPrompt.title": "利用可能な期間データがありません", - "xpack.uptime.emptyState.configureHeartbeatIndexSettings": "Heartbeatがすでに設定されている場合は、データがElasticsearchに送信されていることを確認してから、Heartbeat構成に合わせてインデックスパターン設定を更新します。", - "xpack.uptime.emptyState.configureHeartbeatToGetStartedMessage": "サービスの監視を開始するには、Heartbeatを設定します。", "xpack.uptime.emptyState.loadingMessage": "読み込み中…", - "xpack.uptime.emptyState.noDataMessage": "インデックス{indexName}にはアップタイムデータが見つかりません", - "xpack.uptime.emptyState.noIndexTitle": "パターン{indexName}のインデックスが見つかりません", - "xpack.uptime.emptyState.updateIndexPattern": "インデックスパターン設定を更新", - "xpack.uptime.emptyState.viewSetupInstructions": "セットアップの手順を表示", "xpack.uptime.emptyStateError.notAuthorized": "アップタイムデータの表示が承認されていません。システム管理者にお問い合わせください。", "xpack.uptime.emptyStateError.notFoundPage": "ページが見つかりません", "xpack.uptime.emptyStateError.title": "エラー", @@ -26175,10 +26129,6 @@ "xpack.uptime.filterBar.options.portLabel": "ポート", "xpack.uptime.filterBar.options.schemeLabel": "スキーム", "xpack.uptime.filterBar.options.tagsLabel": "タグ", - "xpack.uptime.filterPopout.loadingMessage": "読み込み中...", - "xpack.uptime.filterPopout.searchMessage": "{title} の検索", - "xpack.uptime.filterPopout.searchMessage.ariaLabel": "{title} を検索", - "xpack.uptime.filterPopover.filterItem.label": "{title} {item}でフィルタリングします。", "xpack.uptime.fleetIntegration.assets.description": "アップタイムでモニターを表示", "xpack.uptime.fleetIntegration.assets.name": "監視", "xpack.uptime.integrationLink.missingDataMessage": "この統合に必要なデータが見つかりませんでした。", @@ -26329,7 +26279,6 @@ "xpack.uptime.overview.alerts.enabled.failed": "ルールを有効にできません。", "xpack.uptime.overview.alerts.enabled.success": "ルールが正常に有効にされました。 ", "xpack.uptime.overview.alerts.enabled.success.description": "この監視が停止しているときには、メッセージが {actionConnectors} に送信されます。", - "xpack.uptime.overview.filterButton.label": "{title}フィルターのフィルターグループを展開", "xpack.uptime.overview.heading": "監視", "xpack.uptime.overview.pageHeader.syntheticsCallout.announcementLink": "お知らせを読む", "xpack.uptime.overview.pageHeader.syntheticsCallout.content": "アップタイムは、スクリプト化された複数ステップの可用性チェックのサポートをプレビューしています。つまり、単に単一のページのアップ/ダウンのチェックだけではなく、Webページの要素を操作したり、全体的な可用性を確認したりできます(購入やシステムへのサインインなど)。詳細については以下をクリックしてください。これらの機能を先駆けて使用したい場合は、プレビュー合成エージェントをダウンロードし、アップタイムでチェックを表示できます。", @@ -26889,4 +26838,4 @@ "xpack.watcher.watchEdit.thresholdWatchExpression.aggType.fieldIsRequiredValidationMessage": "フィールドを選択してください。", "xpack.watcher.watcherDescription": "アラートの作成、管理、監視によりデータへの変更を検知します。" } -} \ No newline at end of file +} diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index f4bb126eb6d92..ec935797fe534 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -2485,7 +2485,6 @@ "discover.fieldChooser.filter.availableFieldsTitle": "可用字段", "discover.fieldChooser.filter.fieldSelectorLabel": "{id} 筛选选项的选择", "discover.fieldChooser.filter.filterByTypeLabel": "按类型筛选", - "discover.fieldChooser.filter.hideMissingFieldsLabel": "隐藏缺失字段", "discover.fieldChooser.filter.indexAndFieldsSectionAriaLabel": "索引和字段", "discover.fieldChooser.filter.popularTitle": "常见", "discover.fieldChooser.filter.searchableLabel": "可搜索", @@ -4410,14 +4409,11 @@ "savedObjects.saveModalOrigin.originAfterSavingSwitchLabel": "保存后{originVerb}至{origin}", "savedObjects.saveModalOrigin.returnToOriginLabel": "返回", "savedObjects.saveModalOrigin.saveAndReturnLabel": "保存并返回", - "savedObjectsManagement.breadcrumb.edit": "编辑 {savedObjectType}", "savedObjectsManagement.breadcrumb.index": "已保存对象", "savedObjectsManagement.deleteConfirm.modalDeleteButtonLabel": "删除", "savedObjectsManagement.deleteConfirm.modalDescription": "此操作会将对象从 Kibana 永久移除。", "savedObjectsManagement.deleteConfirm.modalTitle": "删除“{title}”?", "savedObjectsManagement.deleteSavedObjectsConfirmModalDescription": "此操作将删除以下已保存对象:", - "savedObjectsManagement.field.offLabel": "关闭", - "savedObjectsManagement.field.onLabel": "开启", "savedObjectsManagement.importSummary.createdCountHeader": "{createdCount} 个新", "savedObjectsManagement.importSummary.createdOutcomeLabel": "已创建", "savedObjectsManagement.importSummary.errorCountHeader": "{errorCount} 个错误", @@ -4524,21 +4520,10 @@ "savedObjectsManagement.objectsTable.unableFindSavedObjectNotificationMessage": "找不到已保存对象", "savedObjectsManagement.objectsTable.unableFindSavedObjectsNotificationMessage": "找不到已保存对象", "savedObjectsManagement.objectView.unableFindSavedObjectNotificationMessage": "找不到已保存对象", - "savedObjectsManagement.view.cancelButtonAriaLabel": "取消", - "savedObjectsManagement.view.cancelButtonLabel": "取消", - "savedObjectsManagement.view.deleteItemButtonLabel": "删除“{title}”", - "savedObjectsManagement.view.editItemTitle": "编辑“{title}”", "savedObjectsManagement.view.fieldDoesNotExistErrorMessage": "与此对象关联的字段在该索引模式中已不存在。", - "savedObjectsManagement.view.howToFixErrorDescription": "如果您清楚此错误的含义,请修复该错误 — 否则单击上面的删除按钮。", - "savedObjectsManagement.view.howToModifyObjectDescription": "修改对象仅适用于高级用户。对象属性未得到验证,无效的对象可能会导致错误、数据丢失或更坏的情况发生。除非熟悉该代码的人让您来这里,否则您可能不应到访此处。", - "savedObjectsManagement.view.howToModifyObjectTitle": "谨慎操作!", "savedObjectsManagement.view.indexPatternDoesNotExistErrorMessage": "与此对象关联的索引模式已不存在。", - "savedObjectsManagement.view.saveButtonAriaLabel": "保存 { title } 对象", - "savedObjectsManagement.view.saveButtonLabel": "保存 { title } 对象", "savedObjectsManagement.view.savedObjectProblemErrorMessage": "此已保存对象有问题", "savedObjectsManagement.view.savedSearchDoesNotExistErrorMessage": "与此对象关联的已保存搜索已不存在。", - "savedObjectsManagement.view.viewItemButtonLabel": "查看“{title}”", - "savedObjectsManagement.view.viewItemTitle": "查看“{title}”", "security.checkup.dismissButtonText": "关闭", "security.checkup.dontShowAgain": "不再显示", "security.checkup.insecureClusterMessage": "不要丢失一位。使用 Elastic,免费保护您的数据。", @@ -5287,9 +5272,7 @@ "visTypeTimeseries.indexPatternSelect.label": "索引模式", "visTypeTimeseries.indexPatternSelect.queryAllIndexesText": "要查询所有索引,请使用 *", "visTypeTimeseries.indexPatternSelect.switchModePopover.areaLabel": "配置索引模式选择模式", - "visTypeTimeseries.indexPatternSelect.switchModePopover.text": "索引模式可识别一个或多个您希望浏览的 Elasticsearch 索引。可用使用 Elasticsearch 索引或 Kibana 索引模式(推荐)。", "visTypeTimeseries.indexPatternSelect.switchModePopover.title": "索引模式选择模式", - "visTypeTimeseries.indexPatternSelect.switchModePopover.useKibanaIndices": "仅使用 Kibana 索引模式", "visTypeTimeseries.kbnVisTypes.metricsDescription": "对时间序列数据执行高级分析。", "visTypeTimeseries.kbnVisTypes.metricsTitle": "TSVB", "visTypeTimeseries.lastValueModeIndicator.lastBucketDate": "存储桶:{lastBucketDate}", @@ -5617,7 +5600,6 @@ "visTypeTimeseries.visEditorVisualization.changesWillBeAutomaticallyAppliedMessage": "将自动应用更改。", "visTypeTimeseries.visEditorVisualization.indexPatternMode.dismissNoticeButtonText": "关闭", "visTypeTimeseries.visEditorVisualization.indexPatternMode.link": "请查看。", - "visTypeTimeseries.visEditorVisualization.indexPatternMode.notificationMessage": "好消息!现在可以可视化 Elasticsearch 索引或 Kibana 索引模式的数据。{indexPatternModeLink}。", "visTypeTimeseries.visEditorVisualization.indexPatternMode.notificationTitle": "TSVB 现在支持索引模式", "visTypeTimeseries.visPicker.gaugeLabel": "仪表盘", "visTypeTimeseries.visPicker.metricLabel": "指标", @@ -9855,10 +9837,6 @@ "xpack.enterpriseSearch.appSearch.tokens.search.description": "公有搜索密钥仅用于搜索终端。", "xpack.enterpriseSearch.appSearch.tokens.search.name": "公有搜索密钥", "xpack.enterpriseSearch.appSearch.tokens.update": "API 密钥“{name}”已更新", - "xpack.enterpriseSearch.beta.buttonLabel": "这是公测版用户界面", - "xpack.enterpriseSearch.beta.popover.description": "Kibana 的企业搜索界面是公测版功能。其可能会进行更改,不提供与正式发行的功能一样的支持等级。此界面将成为 8.0 版本中企业搜索的唯一管理面板。届时,单机版企业搜索 UI 仍可用且受支持。", - "xpack.enterpriseSearch.beta.popover.footerDetail": "{learnMoreLink}或{standaloneUILink}。", - "xpack.enterpriseSearch.beta.popover.title": "Kibana 中的 Enterprise Search 是公测版用户界面", "xpack.enterpriseSearch.emailLabel": "电子邮件", "xpack.enterpriseSearch.enterpriseSearch.setupGuide.description": "随时随地进行全面搜索。为工作繁忙的团队轻松实现强大的现代搜索体验。将预先调整的搜索功能快速添加到您的网站、应用或工作区。全面搜索就是这么简单。", "xpack.enterpriseSearch.enterpriseSearch.setupGuide.notConfigured": "企业搜索尚未在您的 Kibana 实例中配置。", @@ -10064,7 +10042,6 @@ "xpack.enterpriseSearch.workplaceSearch.contentSource.configCompleted.privateDisabled.button": "详细了解专用内容源。", "xpack.enterpriseSearch.workplaceSearch.contentSource.configCompleted.privateDisabled.message": "切记在安全设置中{securityLink}。", "xpack.enterpriseSearch.workplaceSearch.contentSource.configCustom.button": "创建定制 API 源", - "xpack.enterpriseSearch.workplaceSearch.contentSource.configCustom.docs.link": "{link}以详细了解定制 API 源。", "xpack.enterpriseSearch.workplaceSearch.contentSource.configDocs.applicationPortal.button": "{name} 应用程序门户", "xpack.enterpriseSearch.workplaceSearch.contentSource.configIntro.alt.text": "连接图示", "xpack.enterpriseSearch.workplaceSearch.contentSource.configIntro.configure.button": "配置 {name}", @@ -10128,11 +10105,6 @@ "xpack.enterpriseSearch.workplaceSearch.contentSource.schema.updated.message": "架构已更新。", "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.documentLevelPermissions.text": "文档级别权限根据定义的规则管理用户内容访问权限。允许或拒绝个人和组对特定文档的访问。", "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.documentLevelPermissions.title": "适用于白金级许可证的文档级别权限", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.explore.button": "了解白金级功能", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.globalAccessPermissions.text": "连接服务可访问的所有文档将同步,并提供给组织的用户或组的用户。文档立即可供搜索", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.private.text": "返回的结果特定于您且与您相关。连接此源不会将您的个人数据暴露给其他搜索用户 - 只有您可以看到。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.remote.text": "在 Workplace Search 中,可实时搜索消息数据和其他信息。", - "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.searchable.text": "以下项目可搜索:", "xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.syncFrequency.text": "此源每 {duration} 从 {name} 获取新内容(在初始同步后)。", "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.displaySettings.description": "对定制 API 源搜索结果的内容和样式进行定制。", "xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.displaySettings.title": "显示设置", @@ -10184,7 +10156,6 @@ "xpack.enterpriseSearch.workplaceSearch.groups.filterSources.buttonText": "源", "xpack.enterpriseSearch.workplaceSearch.groups.groupDeleted": "组“{groupName}”已成功删除。", "xpack.enterpriseSearch.workplaceSearch.groups.groupManagerHeaderTitle": "管理 {label}", - "xpack.enterpriseSearch.workplaceSearch.groups.groupManagerSelectAllToggle": "全部{action}", "xpack.enterpriseSearch.workplaceSearch.groups.groupManagerSourceEmpty.body": "可能您尚未添加任何共享内容源。", "xpack.enterpriseSearch.workplaceSearch.groups.groupManagerSourceEmpty.title": "哎哟!", "xpack.enterpriseSearch.workplaceSearch.groups.groupManagerUpdateAddSourceButton": "添加共享源", @@ -10247,7 +10218,6 @@ "xpack.enterpriseSearch.workplaceSearch.nav.settingsSourcePrioritization": "内容源连接器", "xpack.enterpriseSearch.workplaceSearch.nav.sources": "源", "xpack.enterpriseSearch.workplaceSearch.nonPlatinumOauthDescription": "配置 OAuth 应用程序,以安全使用 Workplace Search 搜索 API。升级到白金级许可证,以启用搜索 API 并创建您的 OAuth 应用程序。", - "xpack.enterpriseSearch.workplaceSearch.nonPlatinumOauthLinkLabel": "了解白金级功能", "xpack.enterpriseSearch.workplaceSearch.nonPlatinumOauthTitle": "正在为定制搜索应用程序配置 OAuth", "xpack.enterpriseSearch.workplaceSearch.oauth.description": "为您的组织创建 OAuth 客户端。", "xpack.enterpriseSearch.workplaceSearch.oauthAuthorize.authorizationDescription": "授权 {strongClientName} 使用您的帐户?", @@ -10264,7 +10234,6 @@ "xpack.enterpriseSearch.workplaceSearch.organizationStats.activeUsers": "活动用户", "xpack.enterpriseSearch.workplaceSearch.organizationStats.invitations": "邀请", "xpack.enterpriseSearch.workplaceSearch.organizationStats.privateSources": "专用源", - "xpack.enterpriseSearch.workplaceSearch.organizationStats.sharedSources": "共享源", "xpack.enterpriseSearch.workplaceSearch.organizationStats.title": "使用统计", "xpack.enterpriseSearch.workplaceSearch.orgNameOnboarding.buttonLabel": "命名您的组织", "xpack.enterpriseSearch.workplaceSearch.orgNameOnboarding.description": "在邀请同事之前,请命名您的组织以提升辨识度。", @@ -10279,7 +10248,6 @@ "xpack.enterpriseSearch.workplaceSearch.overviewUsersCard.title": "很好,您已邀请同事一同搜索。", "xpack.enterpriseSearch.workplaceSearch.personalDashboardSourceError": "无法连接源,请联系管理员以获取帮助。错误消息:{error}", "xpack.enterpriseSearch.workplaceSearch.platinumFeature": "白金级功能", - "xpack.enterpriseSearch.workplaceSearch.privateDashboard.readOnlyMode.warning": "由于常规维护,Workplace Search 当前仅可供搜索。请与您的系统管理员联系,以获取更多信息。", "xpack.enterpriseSearch.workplaceSearch.privatePlatinumCallout.text": "专用源需要白金级许可证。", "xpack.enterpriseSearch.workplaceSearch.privateSource.text": "专用源", "xpack.enterpriseSearch.workplaceSearch.privateSources.text": "专用源", @@ -10359,7 +10327,6 @@ "xpack.enterpriseSearch.workplaceSearch.sources.additionalConfig.heading": "需要其他配置", "xpack.enterpriseSearch.workplaceSearch.sources.applicationLinkTitles.github": "GitHub 开发者门户", "xpack.enterpriseSearch.workplaceSearch.sources.baseUrlTitles.github": "GitHub Enterprise URL", - "xpack.enterpriseSearch.workplaceSearch.sources.config.description": "编辑要更改的内容源连接器设置。", "xpack.enterpriseSearch.workplaceSearch.sources.config.link": "编辑内容源连接器设置", "xpack.enterpriseSearch.workplaceSearch.sources.config.title": "内容源配置", "xpack.enterpriseSearch.workplaceSearch.sources.configuration.title": "配置", @@ -10439,8 +10406,6 @@ "xpack.enterpriseSearch.workplaceSearch.sources.private.header.description": "专用源仅供您使用。", "xpack.enterpriseSearch.workplaceSearch.sources.private.header.title": "我的专用内容源", "xpack.enterpriseSearch.workplaceSearch.sources.private.link": "添加专用内容源", - "xpack.enterpriseSearch.workplaceSearch.sources.private.privateShared.header.description": "您可以通过{groups, plural, other {组}} {groupsSentence}{newline}访问以下源。", - "xpack.enterpriseSearch.workplaceSearch.sources.private.privateShared.header.title": "共享内容源", "xpack.enterpriseSearch.workplaceSearch.sources.private.vewOnly.description": "查看与您的组共享的所有源的状态。", "xpack.enterpriseSearch.workplaceSearch.sources.private.vewOnly.title": "查看组源", "xpack.enterpriseSearch.workplaceSearch.sources.ready.text": "可供搜索", @@ -10451,9 +10416,6 @@ "xpack.enterpriseSearch.workplaceSearch.sources.settings.heading": "设置", "xpack.enterpriseSearch.workplaceSearch.sources.settings.title": "内容源名称", "xpack.enterpriseSearch.workplaceSearch.sources.settingsModal.text": "将从 Workplace Search 中删除您的源文档。{lineBreak}确定要移除 {name}?", - "xpack.enterpriseSearch.workplaceSearch.sources.shared.empty.description": "内容源共享给您后,其将显示在此处,并可通过搜索体验获取。", - "xpack.enterpriseSearch.workplaceSearch.sources.shared.empty.title": "没有可用的内容源", - "xpack.enterpriseSearch.workplaceSearch.sources.sourceContent.searchBar.placeholder": "{prefix} 内容......", "xpack.enterpriseSearch.workplaceSearch.sources.sourceContent.title": "源内容", "xpack.enterpriseSearch.workplaceSearch.sources.sourceDisabled.button": "了解白金级许可证", "xpack.enterpriseSearch.workplaceSearch.sources.sourceDisabled.description": "您的组织的许可证级别已更改。您的数据是安全的,但不再支持文档级别权限,且已禁止搜索此源。升级到白金级许可证,以重新启用此源。", @@ -10488,7 +10450,6 @@ "xpack.enterpriseSearch.workplaceSearch.sources.time.header": "时间", "xpack.enterpriseSearch.workplaceSearch.sources.totalDocuments.label": "总文档数", "xpack.enterpriseSearch.workplaceSearch.sources.understandButton": "我理解", - "xpack.enterpriseSearch.workplaceSearch.sourcesOnboardingCard.buttonLabel": "添加 {label} 源", "xpack.enterpriseSearch.workplaceSearch.sourcesOnboardingCard.description": "您已添加 {sourcesCount, number} 个共享{sourcesCount, plural, other {源}}。祝您搜索愉快。", "xpack.enterpriseSearch.workplaceSearch.sourcesView.modal.docPermissions.description": "只有在配置用户和组映射后,才能在 Workplace Search 中搜索文档。{documentPermissionsLink}。", "xpack.enterpriseSearch.workplaceSearch.sourcesView.modal.heading": "{addedSourceName} 需要其他配置。", @@ -10497,7 +10458,6 @@ "xpack.enterpriseSearch.workplaceSearch.title": "Workplace Search", "xpack.enterpriseSearch.workplaceSearch.update.label": "更新", "xpack.enterpriseSearch.workplaceSearch.url.label": "URL", - "xpack.enterpriseSearch.workplaceSearch.usersOnboardingCard.buttonLabel": "邀请 {label} 用户", "xpack.eventLog.savedObjectProviderRegistry.getProvidersClient.noDefaultProvider": "事件日志需要默认提供程序。", "xpack.features.advancedSettingsFeatureName": "高级设置", "xpack.features.dashboardFeatureName": "仪表板", @@ -17772,7 +17732,6 @@ "xpack.monitoring.alerts.kibanaVersionMismatch.label": "Kibana 版本不匹配", "xpack.monitoring.alerts.kibanaVersionMismatch.shortAction": "确认所有实例具有相同的版本。", "xpack.monitoring.alerts.kibanaVersionMismatch.ui.firingMessage": "在此集群中正运行着多个 Kibana 版本 ({versions})。", - "xpack.monitoring.alerts.legacyAlert.expressionText": "没有可配置的内容。", "xpack.monitoring.alerts.licenseExpiration.action": "请更新您的许可证。", "xpack.monitoring.alerts.licenseExpiration.actionVariables.clusterName": "许可证所属的集群。", "xpack.monitoring.alerts.licenseExpiration.actionVariables.expiredDate": "许可证过期日期。", @@ -26594,13 +26553,7 @@ "xpack.uptime.createPackagePolicy.stepConfigure.tlsSettings.label": "TLS 设置", "xpack.uptime.durationChart.emptyPrompt.description": "在选定时间范围内此监测从未{emphasizedText}。", "xpack.uptime.durationChart.emptyPrompt.title": "没有持续时间数据", - "xpack.uptime.emptyState.configureHeartbeatIndexSettings": "如果已设置 Heartbeat,请确认其正向 Elasticsearch 发送数据,然后更新索引模式设置以匹配 Heartbeat 配置。", - "xpack.uptime.emptyState.configureHeartbeatToGetStartedMessage": "设置 Heartbeat 以开始监测您的服务。", "xpack.uptime.emptyState.loadingMessage": "正在加载……", - "xpack.uptime.emptyState.noDataMessage": "在索引 {indexName} 中找不到运行时间数据", - "xpack.uptime.emptyState.noIndexTitle": "找不到模式 {indexName} 的索引", - "xpack.uptime.emptyState.updateIndexPattern": "更新索引模式设置", - "xpack.uptime.emptyState.viewSetupInstructions": "查看设置说明", "xpack.uptime.emptyStateError.notAuthorized": "您无权查看 Uptime 数据,请联系系统管理员。", "xpack.uptime.emptyStateError.notFoundPage": "未找到页面", "xpack.uptime.emptyStateError.title": "错误", @@ -26611,10 +26564,6 @@ "xpack.uptime.filterBar.options.portLabel": "端口", "xpack.uptime.filterBar.options.schemeLabel": "方案", "xpack.uptime.filterBar.options.tagsLabel": "标签", - "xpack.uptime.filterPopout.loadingMessage": "正在加载……", - "xpack.uptime.filterPopout.searchMessage": "搜索 {title}", - "xpack.uptime.filterPopout.searchMessage.ariaLabel": "搜索 {title}", - "xpack.uptime.filterPopover.filterItem.label": "按 {title} {item} 筛选。", "xpack.uptime.fleetIntegration.assets.description": "在 Uptime 中查看监测", "xpack.uptime.fleetIntegration.assets.name": "监测", "xpack.uptime.integrationLink.missingDataMessage": "未找到此集成的所需数据。", @@ -26765,7 +26714,6 @@ "xpack.uptime.overview.alerts.enabled.failed": "无法启用规则!", "xpack.uptime.overview.alerts.enabled.success": "已成功启用规则 ", "xpack.uptime.overview.alerts.enabled.success.description": "此监测关闭时,将有消息发送到 {actionConnectors}。", - "xpack.uptime.overview.filterButton.label": "展开筛选 {title} 的筛选组", "xpack.uptime.overview.heading": "监测", "xpack.uptime.overview.pageHeader.syntheticsCallout.announcementLink": "阅读公告", "xpack.uptime.overview.pageHeader.syntheticsCallout.content": "Uptime 现在正在预览对脚本化多步骤可用性检查的支持。这意味着您可以与网页元素进行交互,并检查整个过程(例如购买或登录系统)的可用性,而不仅仅是简单的单个页面启动/关闭检查。请单击下面的内容以了解详情,如果您想率先使用这些功能,则可以下载我们的预览组合代理,并在 Uptime 中查看组合检查。", @@ -27335,4 +27283,4 @@ "xpack.watcher.watchEdit.thresholdWatchExpression.aggType.fieldIsRequiredValidationMessage": "此字段必填。", "xpack.watcher.watcherDescription": "通过创建、管理和监测警报来检测数据中的更改。" } -} \ No newline at end of file +} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email.test.tsx index 0e1bf9ef53e15..ed56ca05538b1 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email.test.tsx @@ -245,7 +245,7 @@ describe('connector validation', () => { }; expect( - await actionTypeModel.validateConnector((actionConnector as unknown) as EmailActionConnector) + await actionTypeModel.validateConnector(actionConnector as unknown as EmailActionConnector) ).toEqual({ config: { errors: { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/use_email_config.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/use_email_config.test.ts index 7d9cf15852748..ef9dda3bb2d1c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/use_email_config.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/use_email_config.test.ts @@ -16,9 +16,7 @@ const http = { const editActionConfig = jest.fn(); const renderUseEmailConfigHook = (currentService?: string) => - renderHook(() => - useEmailConfig((http as unknown) as HttpSetup, currentService, editActionConfig) - ); + renderHook(() => useEmailConfig(http as unknown as HttpSetup, currentService, editActionConfig)); describe('useEmailConfig', () => { beforeEach(() => jest.clearAllMocks()); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_connector.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_connector.test.tsx index e9212bf633a79..e804ce2a9f54d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_connector.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_connector.test.tsx @@ -117,9 +117,9 @@ describe('IndexActionConnectorFields renders', () => { .find(EuiSwitch) .filter('[data-test-subj="hasTimeFieldCheckbox"]'); await act(async () => { - timeFieldSwitch.prop('onChange')!(({ + timeFieldSwitch.prop('onChange')!({ target: { checked: true }, - } as unknown) as EuiSwitchEvent); + } as unknown as EuiSwitchEvent); await nextTick(); wrapper.update(); }); 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 56f333396908b..7a04c2f24725e 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 @@ -46,7 +46,7 @@ export const IndexParamsFields = ({ const [isActionConnectorChanged, setIsActionConnectorChanged] = useState(false); const getDocumentToIndex = (doc: Array> | undefined) => - doc && doc.length > 0 ? ((doc[0] as unknown) as string) : undefined; + doc && doc.length > 0 ? (doc[0] as unknown as string) : undefined; const [documentToIndex, setDocumentToIndex] = useState( getDocumentToIndex(documents) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira.test.tsx index 15d87605495fe..1105f17e038e7 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira.test.tsx @@ -62,7 +62,7 @@ describe('jira connector validation', () => { }); test('connector validation fails when connector config is not valid', async () => { - const actionConnector = ({ + const actionConnector = { secrets: { email: 'user', }, @@ -70,7 +70,7 @@ describe('jira connector validation', () => { actionTypeId: '.jira', name: 'jira', config: {}, - } as unknown) as JiraActionConnector; + } as unknown as JiraActionConnector; expect(await actionTypeModel.validateConnector(actionConnector)).toEqual({ config: { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_params.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_params.test.tsx index 88a2aee0191e9..a05db00f141ab 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_params.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_params.test.tsx @@ -337,17 +337,21 @@ describe('JiraParamsFields renders', () => { const wrapper = mount(); const parent = wrapper.find('[data-test-subj="parent-search"]'); - ((parent.props() as unknown) as { - onChange: (val: string) => void; - }).onChange('Cool'); + ( + parent.props() as unknown as { + onChange: (val: string) => void; + } + ).onChange('Cool'); expect(editAction.mock.calls[1][1].incident.parent).toEqual('Cool'); }); test('Label update triggers editAction', () => { const wrapper = mount(); const labels = wrapper.find('[data-test-subj="labelsComboBox"]'); - ((labels.at(0).props() as unknown) as { - onChange: (a: EuiComboBoxOptionOption[]) => void; - }).onChange([{ label: 'Cool' }]); + ( + labels.at(0).props() as unknown as { + onChange: (a: EuiComboBoxOptionOption[]) => void; + } + ).onChange([{ label: 'Cool' }]); expect(editAction.mock.calls[1][1].incident.labels).toEqual(['Cool']); }); test('Label undefined update triggers editAction', () => { @@ -367,18 +371,22 @@ describe('JiraParamsFields renders', () => { const wrapper = mount(); const labels = wrapper.find('[data-test-subj="labelsComboBox"]'); - ((labels.at(0).props() as unknown) as { - onBlur: () => void; - }).onBlur(); + ( + labels.at(0).props() as unknown as { + onBlur: () => void; + } + ).onBlur(); expect(editAction.mock.calls[1][1].incident.labels).toEqual([]); }); test('New label creation triggers editAction', () => { const wrapper = mount(); const labels = wrapper.find('[data-test-subj="labelsComboBox"]'); const searchValue = 'neato'; - ((labels.at(0).props() as unknown) as { - onCreateOption: (searchValue: string) => void; - }).onCreateOption(searchValue); + ( + labels.at(0).props() as unknown as { + onCreateOption: (searchValue: string) => void; + } + ).onCreateOption(searchValue); expect(editAction.mock.calls[1][1].incident.labels).toEqual(['kibana', searchValue]); }); test('A comment triggers editAction', () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_params.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_params.tsx index 99d7e9510454f..834892f2bf374 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_params.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_params.tsx @@ -43,10 +43,10 @@ const JiraParamsFields: React.FunctionComponent actionParams.subActionParams ?? - (({ + ({ incident: {}, comments: [], - } as unknown) as JiraActionParams['subActionParams']), + } as unknown as JiraActionParams['subActionParams']), [actionParams.subActionParams] ); const actionConnectorRef = useRef(actionConnector?.id ?? ''); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/search_issues.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/search_issues.tsx index 283df0b404702..1090414104c24 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/search_issues.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/search_issues.tsx @@ -52,9 +52,10 @@ const SearchIssuesComponent: React.FC = ({ id: selectedValue, }); - useEffect(() => setOptions(issues.map((issue) => ({ label: issue.title, value: issue.key }))), [ - issues, - ]); + useEffect( + () => setOptions(issues.map((issue) => ({ label: issue.title, value: issue.key }))), + [issues] + ); useEffect(() => { if (isLoadingSingleIssue || singleIssue == null) { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_params.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_params.tsx index 8605832b92ea5..a7e6aaeec4129 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_params.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_params.tsx @@ -20,16 +20,8 @@ const PagerDutyParamsFields: React.FunctionComponent { - const { - eventAction, - dedupKey, - summary, - source, - severity, - timestamp, - component, - group, - } = actionParams; + const { eventAction, dedupKey, summary, source, severity, timestamp, component, group } = + actionParams; const severityOptions = [ { value: 'critical', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient.test.tsx index 93fb419f509bc..dc8d210ade98a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient.test.tsx @@ -62,7 +62,7 @@ describe('resilient connector validation', () => { }); test('connector validation fails when connector config is not valid', async () => { - const actionConnector = ({ + const actionConnector = { secrets: { apiKeyId: 'user', }, @@ -70,7 +70,7 @@ describe('resilient connector validation', () => { actionTypeId: '.jira', name: 'jira', config: {}, - } as unknown) as ResilientActionConnector; + } as unknown as ResilientActionConnector; expect(await actionTypeModel.validateConnector(actionConnector)).toEqual({ config: { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_params.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_params.test.tsx index 5ba3b2b609fbd..277a566c6827e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_params.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_params.test.tsx @@ -187,9 +187,11 @@ describe('ResilientParamsFields renders', () => { test('incidentTypeComboBox creation triggers editAction', () => { const wrapper = mount(); const incidentTypes = wrapper.find('[data-test-subj="incidentTypeComboBox"]'); - ((incidentTypes.at(0).props() as unknown) as { - onChange: (a: EuiComboBoxOptionOption[]) => void; - }).onChange([{ label: 'Cool' }]); + ( + incidentTypes.at(0).props() as unknown as { + onChange: (a: EuiComboBoxOptionOption[]) => void; + } + ).onChange([{ label: 'Cool' }]); expect(editAction.mock.calls[0][1].incident.incidentTypes).toEqual(['Cool']); }); test('incidentTypes undefined triggers editAction', () => { @@ -208,9 +210,11 @@ describe('ResilientParamsFields renders', () => { }; const wrapper = mount(); const incidentTypes = wrapper.find('[data-test-subj="incidentTypeComboBox"]'); - ((incidentTypes.at(0).props() as unknown) as { - onBlur: () => void; - }).onBlur(); + ( + incidentTypes.at(0).props() as unknown as { + onBlur: () => void; + } + ).onBlur(); expect(editAction.mock.calls[0][1].incident.incidentTypes).toEqual([]); }); test('A comment triggers editAction', () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_params.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_params.tsx index b0f5198b6b5fd..68a47efb73b18 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_params.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_params.tsx @@ -42,20 +42,18 @@ const ResilientParamsFields: React.FunctionComponent actionParams.subActionParams ?? - (({ + ({ incident: {}, comments: [], - } as unknown) as ResilientActionParams['subActionParams']), + } as unknown as ResilientActionParams['subActionParams']), [actionParams.subActionParams] ); - const { - isLoading: isLoadingIncidentTypes, - incidentTypes: allIncidentTypes, - } = useGetIncidentTypes({ - http, - toastNotifications: toasts, - actionConnector, - }); + const { isLoading: isLoadingIncidentTypes, incidentTypes: allIncidentTypes } = + useGetIncidentTypes({ + http, + toastNotifications: toasts, + actionConnector, + }); const { isLoading: isLoadingSeverity, severity } = useGetSeverity({ http, @@ -79,22 +77,21 @@ const ResilientParamsFields: React.FunctionComponent - > = useMemo(() => { - const allIncidentTypesAsObject = allIncidentTypes.reduce( - (acc, type) => ({ ...acc, [type.id.toString()]: type.name }), - {} as Record - ); - return incident.incidentTypes - ? incident.incidentTypes - .map((type) => ({ - label: allIncidentTypesAsObject[type.toString()], - value: type.toString(), - })) - .filter((type) => type.label != null) - : []; - }, [allIncidentTypes, incident.incidentTypes]); + const selectedIncidentTypesComboBoxOptions: Array> = + useMemo(() => { + const allIncidentTypesAsObject = allIncidentTypes.reduce( + (acc, type) => ({ ...acc, [type.id.toString()]: type.name }), + {} as Record + ); + return incident.incidentTypes + ? incident.incidentTypes + .map((type) => ({ + label: allIncidentTypesAsObject[type.toString()], + value: type.toString(), + })) + .filter((type) => type.label != null) + : []; + }, [allIncidentTypes, incident.incidentTypes]); const editSubActionProperty = useCallback( (key: string, value: any) => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.test.tsx index e25e8120b1650..f1516f880dce4 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.test.tsx @@ -63,7 +63,7 @@ describe('servicenow connector validation', () => { test(`${id}: connector validation fails when connector config is not valid`, async () => { const actionTypeModel = actionTypeRegistry.get(id); - const actionConnector = ({ + const actionConnector = { secrets: { username: 'user', }, @@ -71,7 +71,7 @@ describe('servicenow connector validation', () => { actionTypeId: id, name: 'servicenow', config: {}, - } as unknown) as ServiceNowActionConnector; + } as unknown as ServiceNowActionConnector; expect(await actionTypeModel.validateConnector(actionConnector)).toEqual({ config: { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_connectors.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_connectors.tsx index c9aafc58f3ede..29a6bca4b16ab 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_connectors.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_connectors.tsx @@ -26,145 +26,144 @@ import { ServiceNowActionConnector } from './types'; import { useKibana } from '../../../../common/lib/kibana'; import { getEncryptedFieldNotifyLabel } from '../../get_encrypted_field_notify_label'; -const ServiceNowConnectorFields: React.FC< - ActionConnectorFieldsProps -> = ({ action, editActionSecrets, editActionConfig, errors, consumer, readOnly }) => { - const { docLinks } = useKibana().services; - const { apiUrl } = action.config; +const ServiceNowConnectorFields: React.FC> = + ({ action, editActionSecrets, editActionConfig, errors, consumer, readOnly }) => { + const { docLinks } = useKibana().services; + const { apiUrl } = action.config; - const isApiUrlInvalid: boolean = - errors.apiUrl !== undefined && errors.apiUrl.length > 0 && apiUrl !== undefined; + const isApiUrlInvalid: boolean = + errors.apiUrl !== undefined && errors.apiUrl.length > 0 && apiUrl !== undefined; - const { username, password } = action.secrets; + const { username, password } = action.secrets; - const isUsernameInvalid: boolean = - errors.username !== undefined && errors.username.length > 0 && username !== undefined; - const isPasswordInvalid: boolean = - errors.password !== undefined && errors.password.length > 0 && password !== undefined; + const isUsernameInvalid: boolean = + errors.username !== undefined && errors.username.length > 0 && username !== undefined; + const isPasswordInvalid: boolean = + errors.password !== undefined && errors.password.length > 0 && password !== undefined; - const handleOnChangeActionConfig = useCallback( - (key: string, value: string) => editActionConfig(key, value), - [editActionConfig] - ); + const handleOnChangeActionConfig = useCallback( + (key: string, value: string) => editActionConfig(key, value), + [editActionConfig] + ); - const handleOnChangeSecretConfig = useCallback( - (key: string, value: string) => editActionSecrets(key, value), - [editActionSecrets] - ); - return ( - <> - - - - - - } - > - editActionSecrets(key, value), + [editActionSecrets] + ); + return ( + <> + + + handleOnChangeActionConfig('apiUrl', evt.target.value)} - onBlur={() => { - if (!apiUrl) { - editActionConfig('apiUrl', ''); - } - }} - /> - - - - - - - -

{i18n.AUTHENTICATION_LABEL}

-
-
-
- - - - - {getEncryptedFieldNotifyLabel( - !action.id, - 2, - action.isMissingSecrets ?? false, - i18n.REENTER_VALUES_LABEL - )} - - - - - - - - + + + } + > + handleOnChangeActionConfig('apiUrl', evt.target.value)} + onBlur={() => { + if (!apiUrl) { + editActionConfig('apiUrl', ''); + } + }} + /> + + + + + + + +

{i18n.AUTHENTICATION_LABEL}

+
+
+
+ + + + + {getEncryptedFieldNotifyLabel( + !action.id, + 2, + action.isMissingSecrets ?? false, + i18n.REENTER_VALUES_LABEL + )} + + + + + + + handleOnChangeSecretConfig('username', evt.target.value)} - onBlur={() => { - if (!username) { - editActionSecrets('username', ''); - } - }} - /> - - - - - - - - + handleOnChangeSecretConfig('username', evt.target.value)} + onBlur={() => { + if (!username) { + editActionSecrets('username', ''); + } + }} + /> + + + + + + + handleOnChangeSecretConfig('password', evt.target.value)} - onBlur={() => { - if (!password) { - editActionSecrets('password', ''); - } - }} - /> - - - - - ); -}; + label={i18n.PASSWORD_LABEL} + > + handleOnChangeSecretConfig('password', evt.target.value)} + onBlur={() => { + if (!password) { + editActionSecrets('password', ''); + } + }} + /> +
+
+
+ + ); + }; // eslint-disable-next-line import/no-default-export export { ServiceNowConnectorFields as default }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_itsm_params.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_itsm_params.tsx index f0fc5ed42d24c..b243afb375e6d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_itsm_params.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_itsm_params.tsx @@ -46,10 +46,10 @@ const ServiceNowParamsFields: React.FunctionComponent< const { incident, comments } = useMemo( () => actionParams.subActionParams ?? - (({ + ({ incident: {}, comments: [], - } as unknown) as ServiceNowITSMActionParams['subActionParams']), + } as unknown as ServiceNowITSMActionParams['subActionParams']), [actionParams.subActionParams] ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_sir_params.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_sir_params.tsx index a991ee29c85f8..0ba52014fa1f9 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_sir_params.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_sir_params.tsx @@ -43,10 +43,10 @@ const ServiceNowSIRParamsFields: React.FunctionComponent< const { incident, comments } = useMemo( () => actionParams.subActionParams ?? - (({ + ({ incident: {}, comments: [], - } as unknown) as ServiceNowSIRActionParams['subActionParams']), + } as unknown as ServiceNowSIRActionParams['subActionParams']), [actionParams.subActionParams] ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_connectors.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_connectors.tsx index e87b00dca9343..82af2e09ba711 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_connectors.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_connectors.tsx @@ -14,67 +14,66 @@ import { SlackActionConnector } from '../types'; import { useKibana } from '../../../../common/lib/kibana'; import { getEncryptedFieldNotifyLabel } from '../../get_encrypted_field_notify_label'; -const SlackActionFields: React.FunctionComponent< - ActionConnectorFieldsProps -> = ({ action, editActionSecrets, errors, readOnly }) => { - const { docLinks } = useKibana().services; - const { webhookUrl } = action.secrets; - const isWebhookUrlInvalid: boolean = - errors.webhookUrl !== undefined && errors.webhookUrl.length > 0 && webhookUrl !== undefined; +const SlackActionFields: React.FunctionComponent> = + ({ action, editActionSecrets, errors, readOnly }) => { + const { docLinks } = useKibana().services; + const { webhookUrl } = action.secrets; + const isWebhookUrlInvalid: boolean = + errors.webhookUrl !== undefined && errors.webhookUrl.length > 0 && webhookUrl !== undefined; - return ( - <> - - - - } - error={errors.webhookUrl} - isInvalid={isWebhookUrlInvalid} - label={i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.slackAction.webhookUrlTextFieldLabel', - { - defaultMessage: 'Webhook URL', + return ( + <> + + + } - )} - > - <> - {getEncryptedFieldNotifyLabel( - !action.id, - 1, - action.isMissingSecrets ?? false, - i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.slackAction.reenterValueLabel', - { defaultMessage: 'This URL is encrypted. Please reenter a value for this field.' } - ) + error={errors.webhookUrl} + isInvalid={isWebhookUrlInvalid} + label={i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.slackAction.webhookUrlTextFieldLabel', + { + defaultMessage: 'Webhook URL', + } )} - { - editActionSecrets('webhookUrl', e.target.value); - }} - onBlur={() => { - if (!webhookUrl) { - editActionSecrets('webhookUrl', ''); - } - }} - /> - - - - ); -}; + > + <> + {getEncryptedFieldNotifyLabel( + !action.id, + 1, + action.isMissingSecrets ?? false, + i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.slackAction.reenterValueLabel', + { defaultMessage: 'This URL is encrypted. Please reenter a value for this field.' } + ) + )} + { + editActionSecrets('webhookUrl', e.target.value); + }} + onBlur={() => { + if (!webhookUrl) { + editActionSecrets('webhookUrl', ''); + } + }} + /> + + + + ); + }; // eslint-disable-next-line import/no-default-export export { SlackActionFields as default }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/swimlane_params.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/swimlane_params.tsx index 9bd14a06d657a..2a4521ef1edaf 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/swimlane_params.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/swimlane_params.tsx @@ -23,19 +23,17 @@ const SwimlaneParamsFields: React.FunctionComponent actionParams.subActionParams ?? - (({ + ({ incident: {}, comments: [], - } as unknown) as SwimlaneActionParams['subActionParams']), + } as unknown as SwimlaneActionParams['subActionParams']), [actionParams.subActionParams] ); const actionConnectorRef = useRef(actionConnector?.id ?? ''); - const { - mappings, - connectorType, - } = ((actionConnector as unknown) as SwimlaneActionConnector).config; + const { mappings, connectorType } = (actionConnector as unknown as SwimlaneActionConnector) + .config; const { hasAlertId, hasRuleName, hasComments, hasSeverity } = useMemo( () => ({ hasAlertId: mappings.alertIdConfig != null, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/teams_connectors.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/teams_connectors.tsx index 8de1c68926f14..5550b841d81b4 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/teams_connectors.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/teams_connectors.tsx @@ -14,68 +14,67 @@ import { TeamsActionConnector } from '../types'; import { useKibana } from '../../../../common/lib/kibana'; import { getEncryptedFieldNotifyLabel } from '../../get_encrypted_field_notify_label'; -const TeamsActionFields: React.FunctionComponent< - ActionConnectorFieldsProps -> = ({ action, editActionSecrets, errors, readOnly }) => { - const { webhookUrl } = action.secrets; - const { docLinks } = useKibana().services; +const TeamsActionFields: React.FunctionComponent> = + ({ action, editActionSecrets, errors, readOnly }) => { + const { webhookUrl } = action.secrets; + const { docLinks } = useKibana().services; - const isWebhookUrlInvalid: boolean = - errors.webhookUrl !== undefined && errors.webhookUrl.length > 0 && webhookUrl !== undefined; + const isWebhookUrlInvalid: boolean = + errors.webhookUrl !== undefined && errors.webhookUrl.length > 0 && webhookUrl !== undefined; - return ( - <> - - - - } - error={errors.webhookUrl} - isInvalid={isWebhookUrlInvalid} - label={i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.teamsAction.webhookUrlTextFieldLabel', - { - defaultMessage: 'Webhook URL', + return ( + <> + + + } - )} - > - <> - {getEncryptedFieldNotifyLabel( - !action.id, - 1, - action.isMissingSecrets ?? false, - i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.teamsAction.reenterValueLabel', - { defaultMessage: 'This URL is encrypted. Please reenter a value for this field.' } - ) + error={errors.webhookUrl} + isInvalid={isWebhookUrlInvalid} + label={i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.teamsAction.webhookUrlTextFieldLabel', + { + defaultMessage: 'Webhook URL', + } )} - { - editActionSecrets('webhookUrl', e.target.value); - }} - onBlur={() => { - if (!webhookUrl) { - editActionSecrets('webhookUrl', ''); - } - }} - /> - - - - ); -}; + > + <> + {getEncryptedFieldNotifyLabel( + !action.id, + 1, + action.isMissingSecrets ?? false, + i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.teamsAction.reenterValueLabel', + { defaultMessage: 'This URL is encrypted. Please reenter a value for this field.' } + ) + )} + { + editActionSecrets('webhookUrl', e.target.value); + }} + onBlur={() => { + if (!webhookUrl) { + editActionSecrets('webhookUrl', ''); + } + }} + /> + + + + ); + }; // eslint-disable-next-line import/no-default-export export { TeamsActionFields as default }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.test.tsx index b998067424edd..ff5992a6542b7 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.test.tsx @@ -114,7 +114,7 @@ describe('health check', () => { ); expect(action.getAttribute('href')).toMatchInlineSnapshot( - `"https://www.elastic.co/guide/en/kibana/mocked-test-branch/configuring-tls.html"` + `"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/security-basic-setup.html#encrypt-internode-communication"` ); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_connector_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_connector_form.tsx index 29232940da5c3..f61a0f8f52904 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_connector_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_connector_form.tsx @@ -56,12 +56,12 @@ export async function getConnectorErrors( actionTypeModel: ActionTypeModel ) { const connectorValidationResult = await actionTypeModel?.validateConnector(connector); - const configErrors = (connectorValidationResult.config - ? connectorValidationResult.config.errors - : {}) as IErrorObject; - const secretsErrors = (connectorValidationResult.secrets - ? connectorValidationResult.secrets.errors - : {}) as IErrorObject; + const configErrors = ( + connectorValidationResult.config ? connectorValidationResult.config.errors : {} + ) as IErrorObject; + const secretsErrors = ( + connectorValidationResult.secrets ? connectorValidationResult.secrets.errors : {} + ) as IErrorObject; const connectorBaseErrors = validateBaseProperties(connector).errors; const connectorErrors = { ...configErrors, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx index bedde696e51c0..6e6c6aa52b3e3 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx @@ -222,7 +222,7 @@ describe('action_form', () => { ]); actionTypeRegistry.has.mockReturnValue(true); actionTypeRegistry.get.mockReturnValue(actionType); - const initialAlert = ({ + const initialAlert = { name: 'test', params: {}, consumer: 'alerts', @@ -246,7 +246,7 @@ describe('action_form', () => { muteAll: false, enabled: false, mutedInstanceIds: [], - } as unknown) as Alert; + } as unknown as Alert; const defaultActionMessage = 'Alert [{{context.metadata.name}}] has exceeded the threshold'; const wrapper = mountWithIntl( @@ -484,8 +484,9 @@ describe('action_form', () => { ); actionOption.first().simulate('click'); const combobox = wrapper.find(`[data-test-subj="selectActionConnector-${actionType.id}"]`); - const numConnectors = allActions.filter((action) => action.actionTypeId === actionType.id) - .length; + const numConnectors = allActions.filter( + (action) => action.actionTypeId === actionType.id + ).length; const numConnectorsWithMissingSecrets = allActions.filter( (action) => action.actionTypeId === actionType.id && action.isMissingSecrets ).length; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx index f12ce25abc492..4dcf501fa0023 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx @@ -105,18 +105,19 @@ export const ActionForm = ({ const [actionTypesIndex, setActionTypesIndex] = useState(undefined); const [emptyActionsIds, setEmptyActionsIds] = useState([]); - const closeAddConnectorModal = useCallback(() => setAddModalVisibility(false), [ - setAddModalVisibility, - ]); + const closeAddConnectorModal = useCallback( + () => setAddModalVisibility(false), + [setAddModalVisibility] + ); // load action types useEffect(() => { (async () => { try { setIsLoadingActionTypes(true); - const registeredActionTypes = ( - actionTypes ?? (await loadActionTypes({ http })) - ).sort((a, b) => a.name.localeCompare(b.name)); + const registeredActionTypes = (actionTypes ?? (await loadActionTypes({ http }))).sort( + (a, b) => a.name.localeCompare(b.name) + ); const index: ActionTypeIndex = {}; for (const actionTypeItem of registeredActionTypes) { index[actionTypeItem.id] = actionTypeItem; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx index ca729f9a61662..25c8103f0c8dc 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx @@ -121,9 +121,8 @@ const ConnectorEditFlyout = ({ const [testExecutionActionParams, setTestExecutionActionParams] = useState< Record >({}); - const [testExecutionResult, setTestExecutionResult] = useState< - Option> - >(none); + const [testExecutionResult, setTestExecutionResult] = + useState>>(none); const [isExecutingAction, setIsExecutinAction] = useState(false); const handleSetTab = useCallback( () => diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_reducer.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_reducer.ts index 62e21628ea426..54ed950a4e96d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_reducer.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_reducer.ts @@ -56,78 +56,80 @@ export type ConnectorReducer = Reducer< ConnectorReducerAction >; -export const createConnectorReducer = () => < - ConnectorPhase extends - | InitialConnector - | UserConfiguredActionConnector ->( - state: { connector: ConnectorPhase }, - action: ConnectorReducerAction -) => { - const { connector } = state; +export const createConnectorReducer = + () => + < + ConnectorPhase extends + | InitialConnector + | UserConfiguredActionConnector + >( + state: { connector: ConnectorPhase }, + action: ConnectorReducerAction + ) => { + const { connector } = state; - switch (action.command.type) { - case 'setConnector': { - const { key, value } = action.payload as Payload<'connector', ConnectorPhase>; - if (key === 'connector') { - return { - ...state, - connector: value, - }; - } else { - return state; + switch (action.command.type) { + case 'setConnector': { + const { key, value } = action.payload as Payload<'connector', ConnectorPhase>; + if (key === 'connector') { + return { + ...state, + connector: value, + }; + } else { + return state; + } } - } - case 'setProperty': { - const { key, value } = action.payload as TPayload< - UserConfiguredActionConnector, - keyof UserConfiguredActionConnector - >; - if (isEqual(connector[key], value)) { - return state; - } else { - return { - ...state, - connector: { - ...connector, - [key]: value, - }, - }; - } - } - case 'setConfigProperty': { - const { key, value } = action.payload as TPayload; - if (isEqual(connector.config[key], value)) { - return state; - } else { - return { - ...state, - connector: { - ...connector, - config: { - ...(connector.config as Config), + case 'setProperty': { + const { key, value } = action.payload as TPayload< + UserConfiguredActionConnector, + keyof UserConfiguredActionConnector + >; + if (isEqual(connector[key], value)) { + return state; + } else { + return { + ...state, + connector: { + ...connector, [key]: value, }, - }, - }; + }; + } } - } - case 'setSecretsProperty': { - const { key, value } = action.payload as TPayload; - if (isEqual(connector.secrets[key], value)) { - return state; - } else { - return { - ...state, - connector: { - ...connector, - secrets: { - ...(connector.secrets as Secrets), - [key]: value, + case 'setConfigProperty': { + const { key, value } = action.payload as TPayload; + if (isEqual(connector.config[key], value)) { + return state; + } else { + return { + ...state, + connector: { + ...connector, + config: { + ...(connector.config as Config), + [key]: value, + }, + }, + }; + } + } + case 'setSecretsProperty': { + const { key, value } = action.payload as TPayload; + if (isEqual(connector.secrets[key], value)) { + return state; + } else { + return { + ...state, + connector: { + ...connector, + secrets: { + ...(connector.secrets as Secrets), + [key]: value, + }, }, - }, - }; + }; + } } } - } -}; + }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx index d198c82366fbf..c7c41ac4e8171 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx @@ -39,6 +39,10 @@ jest.mock('react-router-dom', () => ({ }), })); +jest.mock('../../../lib/action_connector_api', () => ({ + loadAllActions: jest.fn().mockResolvedValue([]), +})); + jest.mock('../../../lib/capabilities', () => ({ hasAllPrivilege: jest.fn(() => true), hasSaveAlertsCapability: jest.fn(() => true), @@ -60,24 +64,22 @@ const authorizedConsumers = { }; const recoveryActionGroup: ActionGroup<'recovered'> = { id: 'recovered', name: 'Recovered' }; -describe('alert_details', () => { - // mock Api handlers +const alertType: AlertType = { + id: '.noop', + name: 'No Op', + actionGroups: [{ id: 'default', name: 'Default' }], + recoveryActionGroup, + actionVariables: { context: [], state: [], params: [] }, + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + producer: ALERTS_FEATURE_ID, + authorizedConsumers, + enabledInLicense: true, +}; +describe('alert_details', () => { it('renders the alert name as a title', () => { const alert = mockAlert(); - const alertType: AlertType = { - id: '.noop', - name: 'No Op', - actionGroups: [{ id: 'default', name: 'Default' }], - recoveryActionGroup, - actionVariables: { context: [], state: [], params: [] }, - defaultActionGroupId: 'default', - minimumLicenseRequired: 'basic', - producer: ALERTS_FEATURE_ID, - authorizedConsumers, - enabledInLicense: true, - }; - expect( shallow( @@ -87,19 +89,6 @@ describe('alert_details', () => { it('renders the alert type badge', () => { const alert = mockAlert(); - const alertType: AlertType = { - id: '.noop', - name: 'No Op', - actionGroups: [{ id: 'default', name: 'Default' }], - recoveryActionGroup, - actionVariables: { context: [], state: [], params: [] }, - defaultActionGroupId: 'default', - minimumLicenseRequired: 'basic', - producer: ALERTS_FEATURE_ID, - authorizedConsumers, - enabledInLicense: true, - }; - expect( shallow( @@ -118,19 +107,6 @@ describe('alert_details', () => { }, }, }); - const alertType: AlertType = { - id: '.noop', - name: 'No Op', - actionGroups: [{ id: 'default', name: 'Default' }], - recoveryActionGroup, - actionVariables: { context: [], state: [], params: [] }, - defaultActionGroupId: 'default', - minimumLicenseRequired: 'basic', - producer: ALERTS_FEATURE_ID, - authorizedConsumers, - enabledInLicense: true, - }; - expect( shallow( @@ -155,19 +131,6 @@ describe('alert_details', () => { ], }); - const alertType: AlertType = { - id: '.noop', - name: 'No Op', - actionGroups: [{ id: 'default', name: 'Default' }], - recoveryActionGroup, - actionVariables: { context: [], state: [], params: [] }, - defaultActionGroupId: 'default', - minimumLicenseRequired: 'basic', - producer: ALERTS_FEATURE_ID, - authorizedConsumers, - enabledInLicense: true, - }; - const actionTypes: ActionType[] = [ { id: '.server-log', @@ -212,18 +175,6 @@ describe('alert_details', () => { }, ], }); - const alertType: AlertType = { - id: '.noop', - name: 'No Op', - actionGroups: [{ id: 'default', name: 'Default' }], - recoveryActionGroup, - actionVariables: { context: [], state: [], params: [] }, - defaultActionGroupId: 'default', - producer: ALERTS_FEATURE_ID, - minimumLicenseRequired: 'basic', - authorizedConsumers, - enabledInLicense: true, - }; const actionTypes: ActionType[] = [ { id: '.server-log', @@ -273,20 +224,6 @@ describe('alert_details', () => { describe('links', () => { it('links to the app that created the alert', () => { const alert = mockAlert(); - - const alertType: AlertType = { - id: '.noop', - name: 'No Op', - actionGroups: [{ id: 'default', name: 'Default' }], - recoveryActionGroup, - actionVariables: { context: [], state: [], params: [] }, - defaultActionGroupId: 'default', - producer: ALERTS_FEATURE_ID, - authorizedConsumers, - minimumLicenseRequired: 'basic', - enabledInLicense: true, - }; - expect( shallow( @@ -296,19 +233,6 @@ describe('alert_details', () => { it('links to the Edit flyout', () => { const alert = mockAlert(); - - const alertType: AlertType = { - id: '.noop', - name: 'No Op', - actionGroups: [{ id: 'default', name: 'Default' }], - recoveryActionGroup, - actionVariables: { context: [], state: [], params: [] }, - defaultActionGroupId: 'default', - producer: ALERTS_FEATURE_ID, - authorizedConsumers, - minimumLicenseRequired: 'basic', - enabledInLicense: true, - }; const pageHeaderProps = shallow( ) @@ -316,22 +240,22 @@ describe('alert_details', () => { .props() as EuiPageHeaderProps; const rightSideItems = pageHeaderProps.rightSideItems; expect(!!rightSideItems && rightSideItems[2]!).toMatchInlineSnapshot(` - - - - - - `); + + + + + + `); }); }); }); @@ -341,20 +265,6 @@ describe('disable button', () => { const alert = mockAlert({ enabled: true, }); - - const alertType: AlertType = { - id: '.noop', - name: 'No Op', - actionGroups: [{ id: 'default', name: 'Default' }], - recoveryActionGroup, - actionVariables: { context: [], state: [], params: [] }, - defaultActionGroupId: 'default', - producer: ALERTS_FEATURE_ID, - authorizedConsumers, - minimumLicenseRequired: 'basic', - enabledInLicense: true, - }; - const enableButton = shallow( ) @@ -368,55 +278,43 @@ describe('disable button', () => { }); }); - it('should render a enable button when alert is disabled', () => { + it('should render a enable button and empty state when alert is disabled', async () => { const alert = mockAlert({ enabled: false, }); - - const alertType: AlertType = { - id: '.noop', - name: 'No Op', - actionGroups: [{ id: 'default', name: 'Default' }], - recoveryActionGroup, - actionVariables: { context: [], state: [], params: [] }, - defaultActionGroupId: 'default', - producer: ALERTS_FEATURE_ID, - authorizedConsumers, - minimumLicenseRequired: 'basic', - enabledInLicense: true, - }; - - const enableButton = shallow( + const wrapper = mountWithIntl( - ) - .find(EuiSwitch) - .find('[name="enable"]') - .first(); + ); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + const enableButton = wrapper.find(EuiSwitch).find('[name="enable"]').first(); + const disabledEmptyPrompt = wrapper.find('[data-test-subj="disabledEmptyPrompt"]'); + const disabledEmptyPromptAction = wrapper.find('[data-test-subj="disabledEmptyPromptAction"]'); expect(enableButton.props()).toMatchObject({ checked: false, disabled: false, }); + expect(disabledEmptyPrompt.exists()).toBeTruthy(); + expect(disabledEmptyPromptAction.exists()).toBeTruthy(); + + disabledEmptyPromptAction.first().simulate('click'); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + expect(mockAlertApis.enableAlert).toHaveBeenCalledTimes(1); }); - it('should enable the alert when alert is disabled and button is clicked', () => { + it('should disable the alert when alert is enabled and button is clicked', () => { const alert = mockAlert({ enabled: true, }); - - const alertType: AlertType = { - id: '.noop', - name: 'No Op', - actionGroups: [{ id: 'default', name: 'Default' }], - recoveryActionGroup, - actionVariables: { context: [], state: [], params: [] }, - defaultActionGroupId: 'default', - producer: ALERTS_FEATURE_ID, - authorizedConsumers, - minimumLicenseRequired: 'basic', - enabledInLicense: true, - }; - const disableAlert = jest.fn(); const enableButton = shallow( { expect(disableAlert).toHaveBeenCalledTimes(1); }); - it('should disable the alert when alert is enabled and button is clicked', () => { + it('should enable the alert when alert is disabled and button is clicked', () => { const alert = mockAlert({ enabled: false, }); - - const alertType: AlertType = { - id: '.noop', - name: 'No Op', - actionGroups: [{ id: 'default', name: 'Default' }], - recoveryActionGroup, - actionVariables: { context: [], state: [], params: [] }, - defaultActionGroupId: 'default', - producer: ALERTS_FEATURE_ID, - authorizedConsumers, - minimumLicenseRequired: 'basic', - enabledInLicense: true, - }; - const enableAlert = jest.fn(); const enableButton = shallow( { }, }); - const alertType: AlertType = { - id: '.noop', - name: 'No Op', - actionGroups: [{ id: 'default', name: 'Default' }], - recoveryActionGroup, - actionVariables: { context: [], state: [], params: [] }, - defaultActionGroupId: 'default', - producer: ALERTS_FEATURE_ID, - authorizedConsumers, - minimumLicenseRequired: 'basic', - enabledInLicense: true, - }; - const disableAlert = jest.fn(); const enableAlert = jest.fn(); const wrapper = mountWithIntl( @@ -565,19 +436,6 @@ describe('disable button', () => { }, }); - const alertType: AlertType = { - id: '.noop', - name: 'No Op', - actionGroups: [{ id: 'default', name: 'Default' }], - recoveryActionGroup, - actionVariables: { context: [], state: [], params: [] }, - defaultActionGroupId: 'default', - producer: ALERTS_FEATURE_ID, - authorizedConsumers, - minimumLicenseRequired: 'basic', - enabledInLicense: true, - }; - const disableAlert = jest.fn(async () => { await new Promise((resolve) => setTimeout(resolve, 6000)); }); @@ -630,27 +488,12 @@ describe('mute button', () => { enabled: true, muteAll: false, }); - - const alertType: AlertType = { - id: '.noop', - name: 'No Op', - actionGroups: [{ id: 'default', name: 'Default' }], - recoveryActionGroup, - actionVariables: { context: [], state: [], params: [] }, - defaultActionGroupId: 'default', - producer: ALERTS_FEATURE_ID, - authorizedConsumers, - minimumLicenseRequired: 'basic', - enabledInLicense: true, - }; - const enableButton = shallow( ) .find(EuiSwitch) .find('[name="mute"]') .first(); - expect(enableButton.props()).toMatchObject({ checked: false, disabled: false, @@ -662,27 +505,12 @@ describe('mute button', () => { enabled: true, muteAll: true, }); - - const alertType: AlertType = { - id: '.noop', - name: 'No Op', - actionGroups: [{ id: 'default', name: 'Default' }], - recoveryActionGroup, - actionVariables: { context: [], state: [], params: [] }, - defaultActionGroupId: 'default', - producer: ALERTS_FEATURE_ID, - authorizedConsumers, - minimumLicenseRequired: 'basic', - enabledInLicense: true, - }; - const enableButton = shallow( ) .find(EuiSwitch) .find('[name="mute"]') .first(); - expect(enableButton.props()).toMatchObject({ checked: true, disabled: false, @@ -694,20 +522,6 @@ describe('mute button', () => { enabled: true, muteAll: false, }); - - const alertType: AlertType = { - id: '.noop', - name: 'No Op', - actionGroups: [{ id: 'default', name: 'Default' }], - recoveryActionGroup, - actionVariables: { context: [], state: [], params: [] }, - defaultActionGroupId: 'default', - producer: ALERTS_FEATURE_ID, - authorizedConsumers, - minimumLicenseRequired: 'basic', - enabledInLicense: true, - }; - const muteAlert = jest.fn(); const enableButton = shallow( { .find(EuiSwitch) .find('[name="mute"]') .first(); - enableButton.simulate('click'); const handler = enableButton.prop('onChange'); expect(typeof handler).toEqual('function'); @@ -735,20 +548,6 @@ describe('mute button', () => { enabled: true, muteAll: true, }); - - const alertType: AlertType = { - id: '.noop', - name: 'No Op', - actionGroups: [{ id: 'default', name: 'Default' }], - recoveryActionGroup, - actionVariables: { context: [], state: [], params: [] }, - defaultActionGroupId: 'default', - producer: ALERTS_FEATURE_ID, - authorizedConsumers, - minimumLicenseRequired: 'basic', - enabledInLicense: true, - }; - const unmuteAlert = jest.fn(); const enableButton = shallow( { .find(EuiSwitch) .find('[name="mute"]') .first(); - enableButton.simulate('click'); const handler = enableButton.prop('onChange'); expect(typeof handler).toEqual('function'); @@ -776,27 +574,12 @@ describe('mute button', () => { enabled: false, muteAll: false, }); - - const alertType: AlertType = { - id: '.noop', - name: 'No Op', - actionGroups: [{ id: 'default', name: 'Default' }], - recoveryActionGroup, - actionVariables: { context: [], state: [], params: [] }, - defaultActionGroupId: 'default', - producer: ALERTS_FEATURE_ID, - authorizedConsumers, - minimumLicenseRequired: 'basic', - enabledInLicense: true, - }; - const enableButton = shallow( ) .find(EuiSwitch) .find('[name="mute"]') .first(); - expect(enableButton.props()).toMatchObject({ checked: false, disabled: true, @@ -843,20 +626,6 @@ describe('edit button', () => { }, ], }); - - const alertType: AlertType = { - id: '.noop', - name: 'No Op', - actionGroups: [{ id: 'default', name: 'Default' }], - recoveryActionGroup, - actionVariables: { context: [], state: [], params: [] }, - defaultActionGroupId: 'default', - producer: 'alerting', - authorizedConsumers, - minimumLicenseRequired: 'basic', - enabledInLicense: true, - }; - const pageHeaderProps = shallow( { .props() as EuiPageHeaderProps; const rightSideItems = pageHeaderProps.rightSideItems; expect(!!rightSideItems && rightSideItems[2]!).toMatchInlineSnapshot(` - - - - - - `); + + + + + + `); }); it('should not render an edit button when alert editable but actions arent', () => { const { hasExecuteActionsCapability } = jest.requireMock('../../../lib/capabilities'); - hasExecuteActionsCapability.mockReturnValue(false); + hasExecuteActionsCapability.mockReturnValueOnce(false); const alert = mockAlert({ enabled: true, muteAll: false, @@ -902,20 +671,6 @@ describe('edit button', () => { }, ], }); - - const alertType: AlertType = { - id: '.noop', - name: 'No Op', - actionGroups: [{ id: 'default', name: 'Default' }], - recoveryActionGroup, - actionVariables: { context: [], state: [], params: [] }, - defaultActionGroupId: 'default', - producer: 'alerting', - authorizedConsumers, - minimumLicenseRequired: 'basic', - enabledInLicense: true, - }; - expect( shallow( { it('should render an edit button when alert editable but actions arent when there are no actions on the alert', async () => { const { hasExecuteActionsCapability } = jest.requireMock('../../../lib/capabilities'); - hasExecuteActionsCapability.mockReturnValue(false); + hasExecuteActionsCapability.mockReturnValueOnce(false); const alert = mockAlert({ enabled: true, muteAll: false, actions: [], }); - - const alertType: AlertType = { - id: '.noop', - name: 'No Op', - actionGroups: [{ id: 'default', name: 'Default' }], - recoveryActionGroup, - actionVariables: { context: [], state: [], params: [] }, - defaultActionGroupId: 'default', - producer: 'alerting', - authorizedConsumers, - minimumLicenseRequired: 'basic', - enabledInLicense: true, - }; - const pageHeaderProps = shallow( { .props() as EuiPageHeaderProps; const rightSideItems = pageHeaderProps.rightSideItems; expect(!!rightSideItems && rightSideItems[2]!).toMatchInlineSnapshot(` - - - - - - `); + + + + + + `); }); }); -describe('refresh button', () => { - it('should call requestRefresh when clicked', () => { - const alert = mockAlert(); - const alertType: AlertType = { - id: '.noop', - name: 'No Op', - actionGroups: [{ id: 'default', name: 'Default' }], - recoveryActionGroup, - actionVariables: { context: [], state: [], params: [] }, - defaultActionGroupId: 'default', - minimumLicenseRequired: 'basic', - producer: ALERTS_FEATURE_ID, - authorizedConsumers, +describe('broken connector indicator', () => { + const actionTypes: ActionType[] = [ + { + id: '.server-log', + name: 'Server log', + enabled: true, + enabledInConfig: true, enabledInLicense: true, - }; + minimumLicenseRequired: 'basic', + }, + ]; + ruleTypeRegistry.has.mockReturnValue(true); + const alertTypeR: AlertTypeModel = { + id: 'my-alert-type', + iconClass: 'test', + description: 'Alert when testing', + documentationUrl: 'https://localhost.local/docs', + validate: () => { + return { errors: {} }; + }, + alertParamsExpression: jest.fn(), + requiresAppContext: false, + }; + ruleTypeRegistry.get.mockReturnValue(alertTypeR); + useKibanaMock().services.ruleTypeRegistry = ruleTypeRegistry; + const { loadAllActions } = jest.requireMock('../../../lib/action_connector_api'); + loadAllActions.mockResolvedValue([ + { + secrets: {}, + isMissingSecrets: false, + id: 'connector-id-1', + actionTypeId: '.server-log', + name: 'Test connector', + config: {}, + isPreconfigured: false, + }, + { + secrets: {}, + isMissingSecrets: false, + id: 'connector-id-2', + actionTypeId: '.server-log', + name: 'Test connector 2', + config: {}, + isPreconfigured: false, + }, + ]); + it('should not render broken connector indicator or warning if all rule actions connectors exist', async () => { + const alert = mockAlert({ + enabled: true, + muteAll: false, + actions: [ + { + group: 'default', + id: 'connector-id-1', + params: {}, + actionTypeId: '.server-log', + }, + { + group: 'default', + id: 'connector-id-2', + params: {}, + actionTypeId: '.server-log', + }, + ], + }); + const wrapper = mountWithIntl( + + ); + await act(async () => { + await nextTick(); + wrapper.update(); + }); + const brokenConnectorIndicator = wrapper + .find('[data-test-subj="actionWithBrokenConnector"]') + .first(); + const brokenConnectorWarningBanner = wrapper + .find('[data-test-subj="actionWithBrokenConnectorWarningBanner"]') + .first(); + expect(brokenConnectorIndicator.exists()).toBeFalsy(); + expect(brokenConnectorWarningBanner.exists()).toBeFalsy(); + }); + + it('should render broken connector indicator and warning if any rule actions connector does not exist', async () => { + const alert = mockAlert({ + enabled: true, + muteAll: false, + actions: [ + { + group: 'default', + id: 'connector-id-1', + params: {}, + actionTypeId: '.server-log', + }, + { + group: 'default', + id: 'connector-id-2', + params: {}, + actionTypeId: '.server-log', + }, + { + group: 'default', + id: 'connector-id-doesnt-exist', + params: {}, + actionTypeId: '.server-log', + }, + ], + }); + const wrapper = mountWithIntl( + + ); + await act(async () => { + await nextTick(); + wrapper.update(); + }); + const brokenConnectorIndicator = wrapper + .find('[data-test-subj="actionWithBrokenConnector"]') + .first(); + const brokenConnectorWarningBanner = wrapper + .find('[data-test-subj="actionWithBrokenConnectorWarningBanner"]') + .first(); + const brokenConnectorWarningBannerAction = wrapper + .find('[data-test-subj="actionWithBrokenConnectorWarningBannerEdit"]') + .first(); + expect(brokenConnectorIndicator.exists()).toBeTruthy(); + expect(brokenConnectorWarningBanner.exists()).toBeTruthy(); + expect(brokenConnectorWarningBannerAction.exists()).toBeTruthy(); + }); + + it('should render broken connector indicator and warning with no edit button if any rule actions connector does not exist and user has no edit access', async () => { + const alert = mockAlert({ + enabled: true, + muteAll: false, + actions: [ + { + group: 'default', + id: 'connector-id-1', + params: {}, + actionTypeId: '.server-log', + }, + { + group: 'default', + id: 'connector-id-2', + params: {}, + actionTypeId: '.server-log', + }, + { + group: 'default', + id: 'connector-id-doesnt-exist', + params: {}, + actionTypeId: '.server-log', + }, + ], + }); + const { hasExecuteActionsCapability } = jest.requireMock('../../../lib/capabilities'); + hasExecuteActionsCapability.mockReturnValue(false); + const wrapper = mountWithIntl( + + ); + await act(async () => { + await nextTick(); + wrapper.update(); + }); + const brokenConnectorIndicator = wrapper + .find('[data-test-subj="actionWithBrokenConnector"]') + .first(); + const brokenConnectorWarningBanner = wrapper + .find('[data-test-subj="actionWithBrokenConnectorWarningBanner"]') + .first(); + const brokenConnectorWarningBannerAction = wrapper + .find('[data-test-subj="actionWithBrokenConnectorWarningBannerEdit"]') + .first(); + expect(brokenConnectorIndicator.exists()).toBeTruthy(); + expect(brokenConnectorWarningBanner.exists()).toBeTruthy(); + expect(brokenConnectorWarningBannerAction.exists()).toBeFalsy(); + }); +}); + +describe('refresh button', () => { + it('should call requestRefresh when clicked', async () => { + const alert = mockAlert(); const requestRefresh = jest.fn(); const wrapper = mountWithIntl( { /> ); + await act(async () => { + await nextTick(); + wrapper.update(); + }); const refreshButton = wrapper.find('[data-test-subj="refreshAlertsButton"]').first(); expect(refreshButton.exists()).toBeTruthy(); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx index 2558993a13fe6..2b13bdf613d96 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx @@ -22,13 +22,16 @@ import { EuiButtonEmpty, EuiButton, EuiLoadingSpinner, + EuiIconTip, + EuiEmptyPrompt, + EuiPageTemplate, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { AlertExecutionStatusErrorReasons } from '../../../../../../alerting/common'; import { hasAllPrivilege, hasExecuteActionsCapability } from '../../../lib/capabilities'; import { getAlertingSectionBreadcrumb, getAlertDetailsBreadcrumb } from '../../../lib/breadcrumb'; import { getCurrentDocTitle } from '../../../lib/doc_title'; -import { Alert, AlertType, ActionType } from '../../../../types'; +import { Alert, AlertType, ActionType, ActionConnector } from '../../../../types'; import { ComponentOpts as BulkOperationsComponentOpts, withBulkAlertOperations, @@ -40,6 +43,7 @@ import { routeToRuleDetails } from '../../../constants'; import { alertsErrorReasonTranslationsMapping } from '../../alerts_list/translations'; import { useKibana } from '../../../../common/lib/kibana'; import { alertReducer } from '../../alert_form/alert_reducer'; +import { loadAllActions as loadConnectors } from '../../../lib/action_connector_api'; export type AlertDetailsProps = { alert: Alert; @@ -72,6 +76,9 @@ export const AlertDetails: React.FunctionComponent = ({ dispatch({ command: { type: 'setAlert' }, payload: { key: 'alert', value } }); }; + const [hasActionsWithBrokenConnector, setHasActionsWithBrokenConnector] = + useState(false); + // Set breadcrumb and page title useEffect(() => { setBreadcrumbs([ @@ -82,6 +89,28 @@ export const AlertDetails: React.FunctionComponent = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + // Determine if any attached action has an issue with its connector + useEffect(() => { + (async () => { + let loadedConnectors: ActionConnector[] = []; + try { + loadedConnectors = await loadConnectors({ http }); + } catch (err) { + loadedConnectors = []; + } + + if (loadedConnectors.length > 0) { + const hasActionWithBrokenConnector = alert.actions.some( + (action) => !loadedConnectors.find((connector) => connector.id === action.id) + ); + if (setHasActionsWithBrokenConnector) { + setHasActionsWithBrokenConnector(hasActionWithBrokenConnector); + } + } + })(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const canExecuteActions = hasExecuteActionsCapability(capabilities); const canSaveAlert = hasAllPrivilege(alert, alertType) && @@ -197,13 +226,27 @@ export const AlertDetails: React.FunctionComponent = ({ {uniqueActions && uniqueActions.length ? ( <> -

- {' '} + {hasActionsWithBrokenConnector && ( + -

+ )}
+ {uniqueActions.map((action, index) => ( @@ -358,6 +401,42 @@ export const AlertDetails: React.FunctionComponent = ({ ) : null} + {hasActionsWithBrokenConnector && ( + + + + + {hasEditButton && ( + + + setEditFlyoutVisibility(true)} + > + + + + + )} + + + + )} {alert.enabled ? ( @@ -370,23 +449,46 @@ export const AlertDetails: React.FunctionComponent = ({ ) : ( <> - + + +
} - )} - color="warning" - iconType="help" - > -

- -

- + body={ + <> +

+ +

+ + } + actions={[ + { + setIsEnabledUpdating(true); + setIsEnabled(true); + await enableAlert(alert); + requestRefresh(); + setIsEnabledUpdating(false); + }} + > + Enable + , + ]} + /> + )} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details_route.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details_route.test.tsx index 9ecaa3d915551..c07138990f88d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details_route.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details_route.test.tsx @@ -143,9 +143,9 @@ describe('getRuleData useEffect handler', () => { loadAlert.mockImplementationOnce(async () => rule); - const toastNotifications = ({ + const toastNotifications = { addDanger: jest.fn(), - } as unknown) as ToastsApi; + } as unknown as ToastsApi; await getRuleData( rule.id, @@ -191,9 +191,9 @@ describe('getRuleData useEffect handler', () => { loadAlertTypes.mockImplementation(async () => [ruleType]); loadActionTypes.mockImplementation(async () => [connectorType]); - const toastNotifications = ({ + const toastNotifications = { addDanger: jest.fn(), - } as unknown) as ToastsApi; + } as unknown as ToastsApi; await getRuleData( rule.id, @@ -241,9 +241,9 @@ describe('getRuleData useEffect handler', () => { }); resolveRule.mockImplementationOnce(async () => rule); - const toastNotifications = ({ + const toastNotifications = { addDanger: jest.fn(), - } as unknown) as ToastsApi; + } as unknown as ToastsApi; await getRuleData( rule.id, loadAlert, @@ -285,9 +285,9 @@ describe('getRuleData useEffect handler', () => { throw new Error('OMG'); }); - const toastNotifications = ({ + const toastNotifications = { addDanger: jest.fn(), - } as unknown) as ToastsApi; + } as unknown as ToastsApi; await getRuleData( rule.id, loadAlert, @@ -332,9 +332,9 @@ describe('getRuleData useEffect handler', () => { }); loadActionTypes.mockImplementation(async () => [connectorType]); - const toastNotifications = ({ + const toastNotifications = { addDanger: jest.fn(), - } as unknown) as ToastsApi; + } as unknown as ToastsApi; await getRuleData( rule.id, loadAlert, @@ -383,9 +383,9 @@ describe('getRuleData useEffect handler', () => { throw new Error('OMG no connector type'); }); - const toastNotifications = ({ + const toastNotifications = { addDanger: jest.fn(), - } as unknown) as ToastsApi; + } as unknown as ToastsApi; await getRuleData( rule.id, loadAlert, @@ -432,9 +432,9 @@ describe('getRuleData useEffect handler', () => { loadAlertTypes.mockImplementation(async () => [ruleType]); loadActionTypes.mockImplementation(async () => [connectorType]); - const toastNotifications = ({ + const toastNotifications = { addDanger: jest.fn(), - } as unknown) as ToastsApi; + } as unknown as ToastsApi; await getRuleData( rule.id, loadAlert, @@ -492,9 +492,9 @@ describe('getRuleData useEffect handler', () => { loadAlertTypes.mockImplementation(async () => [ruleType]); loadActionTypes.mockImplementation(async () => [availableConnectorType]); - const toastNotifications = ({ + const toastNotifications = { addDanger: jest.fn(), - } as unknown) as ToastsApi; + } as unknown as ToastsApi; await getRuleData( rule.id, loadAlert, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.test.tsx index f5f901559bd4d..d673c30b997cb 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.test.tsx @@ -43,9 +43,9 @@ describe('getAlertState useEffect handler', () => { loadAlertInstanceSummary.mockImplementationOnce(async () => alertInstanceSummary); - const toastNotifications = ({ + const toastNotifications = { addDanger: jest.fn(), - } as unknown) as ToastsApi; + } as unknown as ToastsApi; await getAlertInstanceSummary( alert.id, @@ -82,9 +82,9 @@ describe('getAlertState useEffect handler', () => { throw new Error('OMG'); }); - const toastNotifications = ({ + const toastNotifications = { addDanger: jest.fn(), - } as unknown) as ToastsApi; + } as unknown as ToastsApi; await getAlertInstanceSummary( alert.id, loadAlertInstanceSummary, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx index 101abfa071ed6..8b79950d03ac9 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx @@ -50,9 +50,8 @@ export const AlertEdit = ({ }); const [isSaving, setIsSaving] = useState(false); const [hasActionsDisabled, setHasActionsDisabled] = useState(false); - const [hasActionsWithBrokenConnector, setHasActionsWithBrokenConnector] = useState( - false - ); + const [hasActionsWithBrokenConnector, setHasActionsWithBrokenConnector] = + useState(false); const [isConfirmAlertCloseModalOpen, setIsConfirmAlertCloseModalOpen] = useState(false); const [alertActionsErrors, setAlertActionsErrors] = useState([]); const [isLoading, setIsLoading] = useState(false); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx index e8353ee7f9bcb..3d32d4ba4f908 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx @@ -168,7 +168,7 @@ describe('alert_form', () => { actionTypeRegistry.list.mockReturnValue([actionType]); actionTypeRegistry.has.mockReturnValue(true); actionTypeRegistry.get.mockReturnValue(actionType); - const initialAlert = ({ + const initialAlert = { name: 'test', params: {}, consumer: ALERTS_FEATURE_ID, @@ -180,7 +180,7 @@ describe('alert_form', () => { muteAll: false, enabled: false, mutedInstanceIds: [], - } as unknown) as Alert; + } as unknown as Alert; wrapper = mountWithIntl( { ruleTypeRegistry.has.mockReturnValue(true); actionTypeRegistry.get.mockReturnValue(actionType); - const initialAlert = ({ + const initialAlert = { name: 'non alerting consumer test', params: {}, consumer: 'test', @@ -352,7 +352,7 @@ describe('alert_form', () => { muteAll: false, enabled: false, mutedInstanceIds: [], - } as unknown) as Alert; + } as unknown as Alert; wrapper = mountWithIntl( { actionTypeRegistry.has.mockReturnValue(true); actionTypeRegistry.get.mockReturnValue(actionType); - const initialAlert = ({ + const initialAlert = { name: 'test', alertTypeId: alertType.id, params: {}, @@ -414,7 +414,7 @@ describe('alert_form', () => { muteAll: false, enabled: false, mutedInstanceIds: [], - } as unknown) as Alert; + } as unknown as Alert; wrapper = mountWithIntl( - (await actionTypeRegistry.get(alertAction.actionTypeId)?.validateParams(alertAction.params)) - .errors + ( + await actionTypeRegistry.get(alertAction.actionTypeId)?.validateParams(alertAction.params) + ).errors ) ); } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_notify_when.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_notify_when.test.tsx index fc5b7a0925e77..43624a7b69347 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_notify_when.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_notify_when.test.tsx @@ -25,7 +25,7 @@ describe('alert_notify_when', () => { let wrapper: ReactWrapper; async function setup(overrides = {}) { - const initialAlert = ({ + const initialAlert = { name: 'test', params: {}, consumer: ALERTS_FEATURE_ID, @@ -39,7 +39,7 @@ describe('alert_notify_when', () => { mutedInstanceIds: [], notifyWhen: 'onActionGroupChange', ...overrides, - } as unknown) as Alert; + } as unknown as Alert; wrapper = mountWithIntl( { const [alertThrottle, setAlertThrottle] = useState(throttle || 1); const [showCustomThrottleOpts, setShowCustomThrottleOpts] = useState(false); - const [notifyWhenValue, setNotifyWhenValue] = useState( - DEFAULT_NOTIFY_WHEN_VALUE - ); + const [notifyWhenValue, setNotifyWhenValue] = + useState(DEFAULT_NOTIFY_WHEN_VALUE); useEffect(() => { if (alert.notifyWhen) { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_reducer.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_reducer.test.ts index 1b28733cc46c1..e23db935fafa3 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_reducer.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_reducer.test.ts @@ -11,7 +11,7 @@ import { Alert } from '../../../types'; describe('alert reducer', () => { let initialAlert: Alert; beforeAll(() => { - initialAlert = ({ + initialAlert = { params: {}, consumer: 'alerts', alertTypeId: null, @@ -21,7 +21,7 @@ describe('alert reducer', () => { actions: [], tags: [], notifyWhen: 'onActionGroupChange', - } as unknown) as Alert; + } as unknown as Alert; }); // setAlert diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_alert_api_operations.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_alert_api_operations.tsx index 59919c202277c..0f3119b58e073 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_alert_api_operations.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_alert_api_operations.tsx @@ -41,9 +41,7 @@ export interface ComponentOpts { unmuteAlerts: (alerts: Alert[]) => Promise; enableAlerts: (alerts: Alert[]) => Promise; disableAlerts: (alerts: Alert[]) => Promise; - deleteAlerts: ( - alerts: Alert[] - ) => Promise<{ + deleteAlerts: (alerts: Alert[]) => Promise<{ successes: string[]; errors: string[]; }>; @@ -53,9 +51,7 @@ export interface ComponentOpts { unmuteAlertInstance: (alert: Alert, alertInstanceId: string) => Promise; enableAlert: (alert: Alert) => Promise; disableAlert: (alert: Alert) => Promise; - deleteAlert: ( - alert: Alert - ) => Promise<{ + deleteAlert: (alert: Alert) => Promise<{ successes: string[]; errors: string[]; }>; diff --git a/x-pack/plugins/triggers_actions_ui/public/common/lib/kibana/kibana_react.mock.ts b/x-pack/plugins/triggers_actions_ui/public/common/lib/kibana/kibana_react.mock.ts index de64906f75de3..b4bfbe069682c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/lib/kibana/kibana_react.mock.ts +++ b/x-pack/plugins/triggers_actions_ui/public/common/lib/kibana/kibana_react.mock.ts @@ -42,9 +42,9 @@ export const createStartServicesMock = (): TriggersAndActionsUiServices => { charts: chartPluginMock.createStartContract(), isCloud: false, kibanaFeatures: [], - element: ({ + element: { style: { cursor: 'pointer' }, - } as unknown) as HTMLElement, + } as unknown as HTMLElement, } as TriggersAndActionsUiServices; }; diff --git a/x-pack/plugins/triggers_actions_ui/public/plugin.ts b/x-pack/plugins/triggers_actions_ui/public/plugin.ts index 17f0766a826e3..82bd4c504a5ee 100644 --- a/x-pack/plugins/triggers_actions_ui/public/plugin.ts +++ b/x-pack/plugins/triggers_actions_ui/public/plugin.ts @@ -85,7 +85,8 @@ export class Plugin TriggersAndActionsUIPublicPluginStart, PluginsSetup, PluginsStart - > { + > +{ private actionTypeRegistry: TypeRegistry; private ruleTypeRegistry: TypeRegistry; diff --git a/x-pack/plugins/triggers_actions_ui/server/data/lib/core_query_types.ts b/x-pack/plugins/triggers_actions_ui/server/data/lib/core_query_types.ts index e403d2275e31c..91085f80fe814 100644 --- a/x-pack/plugins/triggers_actions_ui/server/data/lib/core_query_types.ts +++ b/x-pack/plugins/triggers_actions_ui/server/data/lib/core_query_types.ts @@ -44,13 +44,8 @@ export type CoreQueryParams = TypeOf; // above. // Using direct type not allowed, circular reference, so body is typed to unknown. export function validateCoreQueryBody(anyParams: unknown): string | undefined { - const { - aggType, - aggField, - groupBy, - termField, - termSize, - }: CoreQueryParams = anyParams as CoreQueryParams; + const { aggType, aggField, groupBy, termField, termSize }: CoreQueryParams = + anyParams as CoreQueryParams; if (aggType !== 'count' && !aggField) { return i18n.translate( 'xpack.triggersActionsUI.data.coreQueryParams.aggTypeRequiredErrorMessage', diff --git a/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.ts b/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.ts index 6b2849b7b9670..a90d903d71db5 100644 --- a/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.ts +++ b/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.ts @@ -24,15 +24,8 @@ export async function timeSeriesQuery( params: TimeSeriesQueryParameters ): Promise { const { logger, esClient, query: queryParams } = params; - const { - index, - timeWindowSize, - timeWindowUnit, - interval, - timeField, - dateStart, - dateEnd, - } = queryParams; + const { index, timeWindowSize, timeWindowUnit, interval, timeField, dateStart, dateEnd } = + queryParams; const window = `${timeWindowSize}${timeWindowUnit}`; const dateRangeInfo = getDateRangeInfo({ dateStart, dateEnd, window, interval }); diff --git a/x-pack/plugins/triggers_actions_ui/server/data/routes/indices.ts b/x-pack/plugins/triggers_actions_ui/server/data/routes/indices.ts index 3245a9b8a983f..7709afa955727 100644 --- a/x-pack/plugins/triggers_actions_ui/server/data/routes/indices.ts +++ b/x-pack/plugins/triggers_actions_ui/server/data/routes/indices.ts @@ -105,7 +105,7 @@ async function getIndicesFromPattern( return []; } - return ((response.aggregations as unknown) as IndiciesAggregation).indices.buckets.map( + return (response.aggregations as unknown as IndiciesAggregation).indices.buckets.map( (bucket) => bucket.key ); } diff --git a/x-pack/plugins/triggers_actions_ui/server/index.test.ts b/x-pack/plugins/triggers_actions_ui/server/index.test.ts index eb0f4882a5ba8..1149843d85a50 100644 --- a/x-pack/plugins/triggers_actions_ui/server/index.test.ts +++ b/x-pack/plugins/triggers_actions_ui/server/index.test.ts @@ -20,7 +20,9 @@ const applyStackAlertDeprecations = (settings: Record = {}) => deprecation, path: CONFIG_PATH, })), - () => ({ message }) => deprecationMessages.push(message) + () => + ({ message }) => + deprecationMessages.push(message) ); return { messages: deprecationMessages, diff --git a/x-pack/plugins/ui_actions_enhanced/public/components/action_wizard/action_wizard.stories.tsx b/x-pack/plugins/ui_actions_enhanced/public/components/action_wizard/action_wizard.stories.tsx index 8eb5c45ac3a65..aaf65269a17ef 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/components/action_wizard/action_wizard.stories.tsx +++ b/x-pack/plugins/ui_actions_enhanced/public/components/action_wizard/action_wizard.stories.tsx @@ -11,13 +11,13 @@ import { SerializableRecord } from '@kbn/utility-types'; import { Demo, dashboardFactory, urlFactory } from './test_data'; import { ActionFactory, BaseActionFactoryContext } from '../../dynamic_actions'; -const dashboard = (dashboardFactory as unknown) as ActionFactory< +const dashboard = dashboardFactory as unknown as ActionFactory< SerializableRecord, object, BaseActionFactoryContext >; -const url = (urlFactory as unknown) as ActionFactory< +const url = urlFactory as unknown as ActionFactory< SerializableRecord, object, BaseActionFactoryContext diff --git a/x-pack/plugins/ui_actions_enhanced/public/components/action_wizard/action_wizard.test.tsx b/x-pack/plugins/ui_actions_enhanced/public/components/action_wizard/action_wizard.test.tsx index 5f98ebacea980..782d7b92588c7 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/components/action_wizard/action_wizard.test.tsx +++ b/x-pack/plugins/ui_actions_enhanced/public/components/action_wizard/action_wizard.test.tsx @@ -23,7 +23,7 @@ test('Pick and configure action', () => { const screen = render( > } @@ -56,7 +56,7 @@ test('If only one actions factory is available then actionFactory selection is e const screen = render( { const screen = render( > } @@ -121,7 +121,7 @@ test('if action is beta, beta badge is shown', () => { const screen = render( > } diff --git a/x-pack/plugins/ui_actions_enhanced/public/components/action_wizard/test_data.tsx b/x-pack/plugins/ui_actions_enhanced/public/components/action_wizard/test_data.tsx index cdbd8ea096495..e43fc6ad9a1ba 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/components/action_wizard/test_data.tsx +++ b/x-pack/plugins/ui_actions_enhanced/public/components/action_wizard/test_data.tsx @@ -183,10 +183,10 @@ export const urlFactory = new ActionFactory(urlDrilldownActionFactory, { getFeatureUsageStart: () => licensingMock.createStart().featureUsage, }); -export const mockActionFactories: ActionFactory[] = ([ +export const mockActionFactories: ActionFactory[] = [ dashboardFactory, urlFactory, -] as unknown) as ActionFactory[]; +] as unknown as ActionFactory[]; export const mockSupportedTriggers: string[] = [ VALUE_CLICK_TRIGGER, diff --git a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/drilldown_manager/containers/edit_drilldown_form/edit_drilldown_form.tsx b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/drilldown_manager/containers/edit_drilldown_form/edit_drilldown_form.tsx index 421547c8210dd..09b64260854d6 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/drilldown_manager/containers/edit_drilldown_form/edit_drilldown_form.tsx +++ b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/drilldown_manager/containers/edit_drilldown_form/edit_drilldown_form.tsx @@ -38,10 +38,10 @@ export interface EditDrilldownFormProps { export const EditDrilldownForm: React.FC = ({ eventId }) => { const isMounted = useMountedState(); const drilldowns = useDrilldownManager(); - const drilldownState = React.useMemo(() => drilldowns.createEventDrilldownState(eventId), [ - drilldowns, - eventId, - ]); + const drilldownState = React.useMemo( + () => drilldowns.createEventDrilldownState(eventId), + [drilldowns, eventId] + ); const [disabled, setDisabled] = React.useState(false); if (!drilldownState) return null; diff --git a/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/action_factory.test.ts b/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/action_factory.test.ts index 9e3582404bf09..d82373aee1ae0 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/action_factory.test.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/action_factory.test.ts @@ -10,7 +10,7 @@ import { ActionFactoryDefinition } from './action_factory_definition'; import { licensingMock } from '../../../licensing/public/mocks'; import { PublicLicense } from '../../../licensing/public'; -const def: ActionFactoryDefinition = ({ +const def: ActionFactoryDefinition = { id: 'ACTION_FACTORY_1', CollectConfig: {}, createConfig: () => ({}), @@ -22,7 +22,7 @@ const def: ActionFactoryDefinition = ({ enhancements: {}, }), supportedTriggers: () => [], -} as unknown) as ActionFactoryDefinition; +} as unknown as ActionFactoryDefinition; const featureUsage = licensingMock.createStart().featureUsage; diff --git a/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/action_factory.ts b/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/action_factory.ts index 7b5cb3d8e121a..888d28a17547f 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/action_factory.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/action_factory.ts @@ -32,7 +32,8 @@ export class ActionFactory< > implements Omit, 'getHref'>, Configurable, - PersistableState { + PersistableState +{ constructor( protected readonly def: ActionFactoryDefinition, protected readonly deps: ActionFactoryDeps diff --git a/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/dynamic_action_manager.test.ts b/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/dynamic_action_manager.test.ts index 533a0617d1aff..64b8214fe4d4c 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/dynamic_action_manager.test.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/dynamic_action_manager.test.ts @@ -16,7 +16,7 @@ import { SerializedAction, SerializedEvent } from './types'; import { licensingMock } from '../../../licensing/public/mocks'; import { dynamicActionGrouping } from './dynamic_action_grouping'; -const actionFactoryDefinition1: ActionFactoryDefinition = ({ +const actionFactoryDefinition1: ActionFactoryDefinition = { id: 'ACTION_FACTORY_1', CollectConfig: {}, createConfig: () => ({}), @@ -29,9 +29,9 @@ const actionFactoryDefinition1: ActionFactoryDefinition = ({ supportedTriggers() { return ['VALUE_CLICK_TRIGGER']; }, -} as unknown) as ActionFactoryDefinition; +} as unknown as ActionFactoryDefinition; -const actionFactoryDefinition2: ActionFactoryDefinition = ({ +const actionFactoryDefinition2: ActionFactoryDefinition = { id: 'ACTION_FACTORY_2', CollectConfig: {}, createConfig: () => ({}), @@ -44,7 +44,7 @@ const actionFactoryDefinition2: ActionFactoryDefinition = ({ supportedTriggers() { return ['VALUE_CLICK_TRIGGER']; }, -} as unknown) as ActionFactoryDefinition; +} as unknown as ActionFactoryDefinition; const event1: SerializedEvent = { eventId: 'EVENT_ID_1', diff --git a/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/dynamic_action_manager_state.ts b/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/dynamic_action_manager_state.ts index 47b601084ac23..53afebb7490c0 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/dynamic_action_manager_state.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/dynamic_action_manager_state.ts @@ -66,12 +66,14 @@ export const transitions: Transitions = { events, }), - failFetching: (state) => ({ message }) => ({ - ...state, - isFetchingEvents: false, - fetchCount: state.fetchCount + 1, - fetchError: { message }, - }), + failFetching: + (state) => + ({ message }) => ({ + ...state, + isFetchingEvents: false, + fetchCount: state.fetchCount + 1, + fetchError: { message }, + }), addEvent: (state) => (event: SerializedEvent) => ({ ...state, diff --git a/x-pack/plugins/ui_actions_enhanced/public/plugin.ts b/x-pack/plugins/ui_actions_enhanced/public/plugin.ts index b83f3288b05af..291a948e9e9ea 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/plugin.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/plugin.ts @@ -61,7 +61,8 @@ export interface StartContract } export class AdvancedUiActionsPublicPlugin - implements Plugin { + implements Plugin +{ readonly licenseInfo = new BehaviorSubject(undefined); private getLicenseInfo(): ILicense { if (!this.licenseInfo.getValue()) { diff --git a/x-pack/plugins/ui_actions_enhanced/public/services/ui_actions_service_enhancements.test.ts b/x-pack/plugins/ui_actions_enhanced/public/services/ui_actions_service_enhancements.test.ts index 907d689e76ab0..484a61f661a52 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/services/ui_actions_service_enhancements.test.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/services/ui_actions_service_enhancements.test.ts @@ -20,7 +20,7 @@ const deps: UiActionsServiceEnhancementsParams = { describe('UiActionsService', () => { describe('action factories', () => { - const factoryDefinition1: ActionFactoryDefinition = ({ + const factoryDefinition1: ActionFactoryDefinition = { id: 'test-factory-1', CollectConfig: {}, createConfig: () => ({}), @@ -29,8 +29,8 @@ describe('UiActionsService', () => { supportedTriggers() { return ['VALUE_CLICK_TRIGGER']; }, - } as unknown) as ActionFactoryDefinition; - const factoryDefinition2: ActionFactoryDefinition = ({ + } as unknown as ActionFactoryDefinition; + const factoryDefinition2: ActionFactoryDefinition = { id: 'test-factory-2', CollectConfig: {}, createConfig: () => ({}), @@ -39,7 +39,7 @@ describe('UiActionsService', () => { supportedTriggers() { return ['VALUE_CLICK_TRIGGER']; }, - } as unknown) as ActionFactoryDefinition; + } as unknown as ActionFactoryDefinition; test('.getActionFactories() returns empty array if no action factories registered', () => { const service = new UiActionsServiceEnhancements(deps); diff --git a/x-pack/plugins/ui_actions_enhanced/public/services/ui_actions_service_enhancements.ts b/x-pack/plugins/ui_actions_enhanced/public/services/ui_actions_service_enhancements.ts index 2b696db83e0c0..8da9e62766cc3 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/services/ui_actions_service_enhancements.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/services/ui_actions_service_enhancements.ts @@ -32,7 +32,8 @@ export interface UiActionsServiceEnhancementsParams { } export class UiActionsServiceEnhancements - implements PersistableStateDefinition { + implements PersistableStateDefinition +{ protected readonly actionFactories: ActionFactoryRegistry; protected readonly deps: Omit; @@ -63,13 +64,13 @@ export class UiActionsServiceEnhancements this.actionFactories.set( actionFactory.id, - (actionFactory as unknown) as ActionFactory< + actionFactory as unknown as ActionFactory< SerializableRecord, ExecutionContext, BaseActionFactoryContext > ); - this.registerFeatureUsage((definition as unknown) as ActionFactoryDefinition); + this.registerFeatureUsage(definition as unknown as ActionFactoryDefinition); }; public readonly getActionFactory = (actionFactoryId: string): ActionFactory => { diff --git a/x-pack/plugins/ui_actions_enhanced/public/test_helpers/time_range_embeddable_factory.ts b/x-pack/plugins/ui_actions_enhanced/public/test_helpers/time_range_embeddable_factory.ts index 3829008f67ac3..1587b144e3a11 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/test_helpers/time_range_embeddable_factory.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/test_helpers/time_range_embeddable_factory.ts @@ -18,7 +18,8 @@ interface EmbeddableTimeRangeInput extends EmbeddableInput { } export class TimeRangeEmbeddableFactory - implements EmbeddableFactoryDefinition { + implements EmbeddableFactoryDefinition +{ public readonly type = TIME_RANGE_EMBEDDABLE; public async isEditable() { diff --git a/x-pack/plugins/ui_actions_enhanced/server/plugin.ts b/x-pack/plugins/ui_actions_enhanced/server/plugin.ts index 3faa5ce6aa3ef..f29b1c03b941a 100644 --- a/x-pack/plugins/ui_actions_enhanced/server/plugin.ts +++ b/x-pack/plugins/ui_actions_enhanced/server/plugin.ts @@ -22,7 +22,8 @@ interface SetupDependencies { } export class AdvancedUiActionsServerPlugin - implements Plugin { + implements Plugin +{ protected readonly actionFactories: ActionFactoryRegistry = new Map(); constructor() {} diff --git a/x-pack/plugins/ui_actions_enhanced/server/telemetry/dynamic_action_factories_collector.test.ts b/x-pack/plugins/ui_actions_enhanced/server/telemetry/dynamic_action_factories_collector.test.ts index c74c56723eb8e..63064e06184a6 100644 --- a/x-pack/plugins/ui_actions_enhanced/server/telemetry/dynamic_action_factories_collector.test.ts +++ b/x-pack/plugins/ui_actions_enhanced/server/telemetry/dynamic_action_factories_collector.test.ts @@ -14,26 +14,26 @@ import { ActionFactory } from '../types'; type GetActionFactory = (id: string) => undefined | ActionFactory; const factories: Record = { - FACTORY_ID_1: ({ + FACTORY_ID_1: { id: 'FACTORY_ID_1', telemetry: jest.fn((state: DynamicActionsState, stats: Record) => { stats.myStat_1 = 1; stats.myStat_2 = 123; return stats; }), - } as unknown) as ActionFactory, - FACTORY_ID_2: ({ + } as unknown as ActionFactory, + FACTORY_ID_2: { id: 'FACTORY_ID_2', telemetry: jest.fn((state: DynamicActionsState, stats: Record) => stats), - } as unknown) as ActionFactory, - FACTORY_ID_3: ({ + } as unknown as ActionFactory, + FACTORY_ID_3: { id: 'FACTORY_ID_3', telemetry: jest.fn((state: DynamicActionsState, stats: Record) => { stats.myStat_1 = 2; stats.stringStat = 'abc'; return stats; }), - } as unknown) as ActionFactory, + } as unknown as ActionFactory, }; const getActionFactory: GetActionFactory = (id: string) => factories[id]; @@ -77,7 +77,7 @@ const state: DynamicActionsState = { beforeEach(() => { Object.values(factories).forEach((factory) => { - ((factory.telemetry as unknown) as jest.SpyInstance).mockClear(); + (factory.telemetry as unknown as jest.SpyInstance).mockClear(); }); }); @@ -100,8 +100,8 @@ describe('dynamicActionFactoriesCollector', () => { }; dynamicActionFactoriesCollector(getActionFactory, currentState, {}); - const spy1 = (factories.FACTORY_ID_1.telemetry as unknown) as jest.SpyInstance; - const spy2 = (factories.FACTORY_ID_2.telemetry as unknown) as jest.SpyInstance; + const spy1 = factories.FACTORY_ID_1.telemetry as unknown as jest.SpyInstance; + const spy2 = factories.FACTORY_ID_2.telemetry as unknown as jest.SpyInstance; expect(spy1).toHaveBeenCalledTimes(1); expect(spy2).toHaveBeenCalledTimes(0); diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/setup_environment.tsx b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/setup_environment.tsx index c5de02bebd512..a1cdfaa3446cb 100644 --- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/setup_environment.tsx +++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/setup_environment.tsx @@ -31,40 +31,40 @@ const { GlobalFlyoutProvider } = GlobalFlyout; const mockHttpClient = axios.create({ adapter: axiosXhrAdapter }); -export const WithAppDependencies = (Comp: any, overrides: Record = {}) => ( - props: Record -) => { - apiService.setup((mockHttpClient as unknown) as HttpSetup); - breadcrumbService.setup(() => ''); +export const WithAppDependencies = + (Comp: any, overrides: Record = {}) => + (props: Record) => { + apiService.setup(mockHttpClient as unknown as HttpSetup); + breadcrumbService.setup(() => ''); - const contextValue = { - http: (mockHttpClient as unknown) as HttpSetup, - docLinks: docLinksServiceMock.createStartContract(), - kibanaVersionInfo: { - currentMajor: mockKibanaSemverVersion.major, - prevMajor: mockKibanaSemverVersion.major - 1, - nextMajor: mockKibanaSemverVersion.major + 1, - }, - notifications: notificationServiceMock.createStartContract(), - isReadOnlyMode: false, - api: apiService, - breadcrumbs: breadcrumbService, - getUrlForApp: applicationServiceMock.createStartContract().getUrlForApp, - deprecations: deprecationsServiceMock.createStartContract(), - }; + const contextValue = { + http: mockHttpClient as unknown as HttpSetup, + docLinks: docLinksServiceMock.createStartContract(), + kibanaVersionInfo: { + currentMajor: mockKibanaSemverVersion.major, + prevMajor: mockKibanaSemverVersion.major - 1, + nextMajor: mockKibanaSemverVersion.major + 1, + }, + notifications: notificationServiceMock.createStartContract(), + isReadOnlyMode: false, + api: apiService, + breadcrumbs: breadcrumbService, + getUrlForApp: applicationServiceMock.createStartContract().getUrlForApp, + deprecations: deprecationsServiceMock.createStartContract(), + }; - const { servicesOverrides, ...contextOverrides } = overrides; + const { servicesOverrides, ...contextOverrides } = overrides; - return ( - - - - - - - - ); -}; + return ( + + + + + + + + ); + }; export const setupEnvironment = () => { const { server, httpRequestsMockHelpers } = initHttpRequests(); diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/review_logs_step/mocked_responses.ts b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/review_logs_step/mocked_responses.ts index f62bb0a4eeeb2..5f1e30ec52b1d 100644 --- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/review_logs_step/mocked_responses.ts +++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/review_logs_step/mocked_responses.ts @@ -16,8 +16,7 @@ export const esDeprecations: ESUpgradeStatus = { type: 'cluster_settings', resolveDuringUpgrade: false, message: 'Index Lifecycle Management poll interval is set too low', - url: - 'https://www.elastic.co/guide/en/elasticsearch/reference/master/breaking-changes-8.0.html#ilm-poll-interval-limit', + url: 'https://www.elastic.co/guide/en/elasticsearch/reference/master/breaking-changes-8.0.html#ilm-poll-interval-limit', details: 'The Index Lifecycle Management poll interval setting [indices.lifecycle.poll_interval] is currently set to [500ms], but must be 1s or greater', }, @@ -26,8 +25,7 @@ export const esDeprecations: ESUpgradeStatus = { type: 'index_settings', resolveDuringUpgrade: false, message: 'translog retention settings are ignored', - url: - 'https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules-translog.html', + url: 'https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules-translog.html', details: 'translog retention settings [index.translog.retention.size] and [index.translog.retention.age] are ignored because translog is no longer used in peer recoveries with soft-deletes enabled (default in 7.0 or later)', index: 'settings', diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/default/table_row.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/default/table_row.tsx index 7f4b2e3be3479..d4bacb21238cd 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/default/table_row.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/default/table_row.tsx @@ -23,10 +23,8 @@ interface Props { export const DefaultTableRow: React.FunctionComponent = ({ rowFieldNames, deprecation }) => { const [showFlyout, setShowFlyout] = useState(false); - const { - addContent: addContentToGlobalFlyout, - removeContent: removeContentFromGlobalFlyout, - } = useGlobalFlyout(); + const { addContent: addContentToGlobalFlyout, removeContent: removeContentFromGlobalFlyout } = + useGlobalFlyout(); const closeFlyout = useCallback(() => { setShowFlyout(false); diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/index_settings/table_row.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/index_settings/table_row.tsx index 3a1706b08c0ee..b118d01a2d540 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/index_settings/table_row.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/index_settings/table_row.tsx @@ -35,10 +35,8 @@ export const IndexSettingsTableRow: React.FunctionComponent = ({ const { api } = useAppContext(); - const { - addContent: addContentToGlobalFlyout, - removeContent: removeContentFromGlobalFlyout, - } = useGlobalFlyout(); + const { addContent: addContentToGlobalFlyout, removeContent: removeContentFromGlobalFlyout } = + useGlobalFlyout(); const closeFlyout = useCallback(() => { setShowFlyout(false); diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/ml_snapshots/table_row.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/ml_snapshots/table_row.tsx index 73921b235d88c..9d961aed8ffc9 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/ml_snapshots/table_row.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/ml_snapshots/table_row.tsx @@ -30,10 +30,8 @@ export const MlSnapshotsTableRowCells: React.FunctionComponent = const [showFlyout, setShowFlyout] = useState(false); const snapshotState = useMlSnapshotContext(); - const { - addContent: addContentToGlobalFlyout, - removeContent: removeContentFromGlobalFlyout, - } = useGlobalFlyout(); + const { addContent: addContentToGlobalFlyout, removeContent: removeContentFromGlobalFlyout } = + useGlobalFlyout(); const closeFlyout = useCallback(() => { setShowFlyout(false); diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/reindex/table_row.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/reindex/table_row.tsx index 95d65f1e77771..1cf555b6cb340 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/reindex/table_row.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/reindex/table_row.tsx @@ -31,10 +31,8 @@ const ReindexTableRowCells: React.FunctionComponent = ({ const reindexState = useReindexContext(); const { api } = useAppContext(); - const { - addContent: addContentToGlobalFlyout, - removeContent: removeContentFromGlobalFlyout, - } = useGlobalFlyout(); + const { addContent: addContentToGlobalFlyout, removeContent: removeContentFromGlobalFlyout } = + useGlobalFlyout(); const closeFlyout = useCallback(async () => { removeContentFromGlobalFlyout('reindexFlyout'); diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/es_deprecations_table.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/es_deprecations_table.tsx index 5f742a3c63ae6..f1654b2030166 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/es_deprecations_table.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/es_deprecations_table.tsx @@ -160,11 +160,10 @@ export const EsDeprecationsTable: React.FunctionComponent = ({ getSortedItems(deprecations, sortConfig) ); - const pager = useMemo(() => new Pager(deprecations.length, itemsPerPage, currentPageIndex), [ - currentPageIndex, - deprecations, - itemsPerPage, - ]); + const pager = useMemo( + () => new Pager(deprecations.length, itemsPerPage, currentPageIndex), + [currentPageIndex, deprecations, itemsPerPage] + ); const visibleDeprecations = useMemo( () => filteredDeprecations.slice(pager.firstItemIndex, pager.lastItemIndex + 1), @@ -225,9 +224,9 @@ export const EsDeprecationsTable: React.FunctionComponent = ({ field: 'type', name: i18nTexts.typeFilterLabel, multiSelect: false, - options: (Object.keys(DEPRECATION_TYPE_MAP) as Array< - keyof typeof DEPRECATION_TYPE_MAP - >).map((type) => ({ + options: ( + Object.keys(DEPRECATION_TYPE_MAP) as Array + ).map((type) => ({ value: type, name: DEPRECATION_TYPE_MAP[type], })), diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_deprecation_logs_step/use_deprecation_logging.ts b/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_deprecation_logs_step/use_deprecation_logging.ts index 5545eb0353741..1aa34f2ec97c1 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_deprecation_logs_step/use_deprecation_logging.ts +++ b/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_deprecation_logs_step/use_deprecation_logging.ts @@ -39,10 +39,8 @@ export const useDeprecationLogging = (): DeprecationLoggingPreviewProps => { useEffect(() => { if (!isLoading && data) { - const { - isDeprecationLogIndexingEnabled: isIndexingEnabled, - isDeprecationLoggingEnabled, - } = data; + const { isDeprecationLogIndexingEnabled: isIndexingEnabled, isDeprecationLoggingEnabled } = + data; setIsDeprecationLogIndexingEnabled(isIndexingEnabled); if (!isIndexingEnabled && isDeprecationLoggingEnabled) { @@ -54,12 +52,10 @@ export const useDeprecationLogging = (): DeprecationLoggingPreviewProps => { const toggleLogging = async () => { setIsUpdating(true); - const { - data: updatedLoggingState, - error: updateDeprecationError, - } = await api.updateDeprecationLogging({ - isEnabled: !isDeprecationLogIndexingEnabled, - }); + const { data: updatedLoggingState, error: updateDeprecationError } = + await api.updateDeprecationLogging({ + isEnabled: !isDeprecationLogIndexingEnabled, + }); setIsUpdating(false); setOnlyDeprecationLogWritingEnabled(false); diff --git a/x-pack/plugins/upgrade_assistant/public/application/mount_management_section.ts b/x-pack/plugins/upgrade_assistant/public/application/mount_management_section.ts index 9fa52e90c9d0e..7d6d071fcf95f 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/mount_management_section.ts +++ b/x-pack/plugins/upgrade_assistant/public/application/mount_management_section.ts @@ -20,9 +20,8 @@ export async function mountManagementSection( readonly: boolean, services: AppServicesContext ) { - const [ - { i18n, docLinks, notifications, application, deprecations }, - ] = await coreSetup.getStartServices(); + const [{ i18n, docLinks, notifications, application, deprecations }] = + await coreSetup.getStartServices(); const { element, history, setBreadcrumbs } = params; const { http } = coreSetup; diff --git a/x-pack/plugins/upgrade_assistant/public/plugin.ts b/x-pack/plugins/upgrade_assistant/public/plugin.ts index 558deffe43d94..5edb638e1bc5b 100644 --- a/x-pack/plugins/upgrade_assistant/public/plugin.ts +++ b/x-pack/plugins/upgrade_assistant/public/plugin.ts @@ -13,14 +13,11 @@ import { SetupDependencies, StartDependencies, AppServicesContext } from './type import { Config } from '../common/config'; export class UpgradeAssistantUIPlugin - implements Plugin { + implements Plugin +{ constructor(private ctx: PluginInitializerContext) {} setup(coreSetup: CoreSetup, { management, cloud }: SetupDependencies) { - const { enabled, readonly } = this.ctx.config.get(); - - if (!enabled) { - return; - } + const { readonly } = this.ctx.config.get(); const appRegistrar = management.sections.section.stack; const kibanaVersion = new SemVer(this.ctx.env.packageInfo.version); diff --git a/x-pack/plugins/upgrade_assistant/server/index.ts b/x-pack/plugins/upgrade_assistant/server/index.ts index 035a6515de152..5591276b2fa34 100644 --- a/x-pack/plugins/upgrade_assistant/server/index.ts +++ b/x-pack/plugins/upgrade_assistant/server/index.ts @@ -14,9 +14,9 @@ export const plugin = (ctx: PluginInitializerContext) => { }; export const config: PluginConfigDescriptor = { + deprecations: ({ deprecate }) => [deprecate('enabled', '8.0.0')], schema: configSchema, exposeToBrowser: { - enabled: true, readonly: true, }, }; diff --git a/x-pack/plugins/upgrade_assistant/server/lib/es_version_precheck.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/es_version_precheck.test.ts index f4631f3ba459d..e1817ef63927d 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/es_version_precheck.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/es_version_precheck.test.ts @@ -35,7 +35,7 @@ const xpackMocks = { describe('getAllNodeVersions', () => { it('returns a list of unique node versions', async () => { - const adminClient = ({ + const adminClient = { asInternalUser: { nodes: { info: jest.fn().mockResolvedValue({ @@ -49,7 +49,7 @@ describe('getAllNodeVersions', () => { }), }, }, - } as unknown) as IScopedClusterClient; + } as unknown as IScopedClusterClient; await expect(getAllNodeVersions(adminClient)).resolves.toEqual([ new SemVer('6.0.0'), diff --git a/x-pack/plugins/upgrade_assistant/server/lib/es_version_precheck.ts b/x-pack/plugins/upgrade_assistant/server/lib/es_version_precheck.ts index 295e504df22cd..cefc5576159e7 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/es_version_precheck.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/es_version_precheck.ts @@ -101,14 +101,16 @@ export const esVersionCheck = async ( } }; -export const versionCheckHandlerWrapper = (handler: RequestHandler) => async ( - ctx: RequestHandlerContext, - request: KibanaRequest, - response: KibanaResponseFactory -) => { - const errorResponse = await esVersionCheck(ctx, response); - if (errorResponse) { - return errorResponse; - } - return handler(ctx, request, response); -}; +export const versionCheckHandlerWrapper = + (handler: RequestHandler) => + async ( + ctx: RequestHandlerContext, + request: KibanaRequest, + response: KibanaResponseFactory + ) => { + const errorResponse = await esVersionCheck(ctx, response); + if (errorResponse) { + return errorResponse; + } + return handler(ctx, request, response); + }; diff --git a/x-pack/plugins/upgrade_assistant/server/lib/kibana_status.ts b/x-pack/plugins/upgrade_assistant/server/lib/kibana_status.ts index 2d48182e6fd6b..9908f026d3996 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/kibana_status.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/kibana_status.ts @@ -9,7 +9,8 @@ import { DeprecationsClient } from 'kibana/server'; import { DomainDeprecationDetails } from 'src/core/server/types'; export const getKibanaUpgradeStatus = async (deprecationsClient: DeprecationsClient) => { - const kibanaDeprecations: DomainDeprecationDetails[] = await deprecationsClient.getAllDeprecations(); + const kibanaDeprecations: DomainDeprecationDetails[] = + await deprecationsClient.getAllDeprecations(); const totalCriticalDeprecations = kibanaDeprecations.filter((d) => d.level === 'critical').length; diff --git a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.test.ts index cffd49e5bd38a..3cfdb1fdd3167 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.test.ts @@ -251,26 +251,7 @@ describe('ReindexActions', () => { // Really prettier?? await expect(actions.findAllByStatus(ReindexStatus.completed)).resolves.toEqual([ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ]); }); }); diff --git a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/worker.ts b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/worker.ts index 233d1c88001d2..c598da93388c3 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/worker.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/worker.ts @@ -231,19 +231,18 @@ export class ReindexWorker { * Swallows any exceptions that may occur during the reindex process. This prevents any errors from * stopping the worker from continuing to process more jobs. */ -const swallowExceptions = ( - func: (reindexOp: ReindexSavedObject) => Promise, - log: Logger -) => async (reindexOp: ReindexSavedObject) => { - try { - return await func(reindexOp); - } catch (e) { - if (reindexOp.attributes.locked) { - log.debug(`Skipping reindexOp with unexpired lock: ${reindexOp.id}`); - } else { - log.warn(`Error when trying to process reindexOp (${reindexOp.id}): ${e.toString()}`); - } +const swallowExceptions = + (func: (reindexOp: ReindexSavedObject) => Promise, log: Logger) => + async (reindexOp: ReindexSavedObject) => { + try { + return await func(reindexOp); + } catch (e) { + if (reindexOp.attributes.locked) { + log.debug(`Skipping reindexOp with unexpired lock: ${reindexOp.id}`); + } else { + log.warn(`Error when trying to process reindexOp (${reindexOp.id}): ${e.toString()}`); + } - return reindexOp; - } -}; + return reindexOp; + } + }; diff --git a/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.ts b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.ts index ee997f5da7ab7..56932f5e54b06 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.ts @@ -128,8 +128,8 @@ export function registerUpgradeAssistantUsageCollector({ usageCollection, savedObjects, }: Dependencies) { - const upgradeAssistantUsageCollector = usageCollection.makeUsageCollector( - { + const upgradeAssistantUsageCollector = + usageCollection.makeUsageCollector({ type: 'upgrade-assistant-telemetry', isReady: () => true, schema: { @@ -171,8 +171,7 @@ export function registerUpgradeAssistantUsageCollector({ }, }, fetch: async () => fetchUpgradeAssistantMetrics(elasticsearch, savedObjects), - } - ); + }); usageCollection.registerCollector(upgradeAssistantUsageCollector); } diff --git a/x-pack/plugins/upgrade_assistant/server/routes/__mocks__/routes.mock.ts b/x-pack/plugins/upgrade_assistant/server/routes/__mocks__/routes.mock.ts index 8a62a12778883..e4e9ae95ef30d 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/__mocks__/routes.mock.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/__mocks__/routes.mock.ts @@ -12,7 +12,7 @@ import { deprecationsServiceMock, } from '../../../../../../src/core/server/mocks'; -export const routeHandlerContextMock = ({ +export const routeHandlerContextMock = { core: { elasticsearch: { client: elasticsearchServiceMock.createScopedClusterClient(), @@ -20,7 +20,7 @@ export const routeHandlerContextMock = ({ savedObjects: { client: savedObjectsClientMock.create() }, deprecations: { client: deprecationsServiceMock.createClient() }, }, -} as unknown) as RequestHandlerContext; +} as unknown as RequestHandlerContext; /** * Creates a very crude mock of the new platform router implementation. This enables use to test @@ -33,15 +33,14 @@ export const routeHandlerContextMock = ({ export const createMockRouter = () => { const paths: Record>> = {}; - const assign = (method: string) => ( - { path }: { path: string }, - handler: RequestHandler - ) => { - paths[method] = { - ...(paths[method] || {}), - ...{ [path]: handler }, + const assign = + (method: string) => + ({ path }: { path: string }, handler: RequestHandler) => { + paths[method] = { + ...(paths[method] || {}), + ...{ [path]: handler }, + }; }; - }; return { getHandler({ method, pathPattern }: { method: string; pathPattern: string }) { diff --git a/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.test.ts b/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.test.ts index e146415b1ccfc..1d51666dec3e5 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.test.ts @@ -38,8 +38,10 @@ describe('deprecation logging API', () => { describe('GET /api/upgrade_assistant/deprecation_logging', () => { it('returns that indexing and writing logs is enabled', async () => { - (routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.cluster - .getSettings as jest.Mock).mockResolvedValue({ + ( + routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.cluster + .getSettings as jest.Mock + ).mockResolvedValue({ body: { default: { cluster: { deprecation_indexing: { enabled: 'true' } }, @@ -60,8 +62,10 @@ describe('deprecation logging API', () => { }); it('returns an error if it throws', async () => { - (routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.cluster - .getSettings as jest.Mock).mockRejectedValue(new Error(`scary error!`)); + ( + routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.cluster + .getSettings as jest.Mock + ).mockRejectedValue(new Error(`scary error!`)); await expect( routeDependencies.router.getHandler({ method: 'get', @@ -73,8 +77,10 @@ describe('deprecation logging API', () => { describe('PUT /api/upgrade_assistant/deprecation_logging', () => { it('returns that indexing and writing logs is enabled', async () => { - (routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.cluster - .putSettings as jest.Mock).mockResolvedValue({ + ( + routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.cluster + .putSettings as jest.Mock + ).mockResolvedValue({ body: { default: { logger: { deprecation: 'WARN' }, @@ -95,8 +101,10 @@ describe('deprecation logging API', () => { }); it('returns an error if it throws', async () => { - (routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.cluster - .putSettings as jest.Mock).mockRejectedValue(new Error(`scary error!`)); + ( + routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.cluster + .putSettings as jest.Mock + ).mockRejectedValue(new Error(`scary error!`)); await expect( routeDependencies.router.getHandler({ method: 'put', diff --git a/x-pack/plugins/upgrade_assistant/server/routes/ml_snapshots.test.ts b/x-pack/plugins/upgrade_assistant/server/routes/ml_snapshots.test.ts index 741f704adac90..2f8cdd2aba808 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/ml_snapshots.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/ml_snapshots.test.ts @@ -36,8 +36,10 @@ describe('ML snapshots APIs', () => { describe('POST /api/upgrade_assistant/ml_snapshots', () => { it('returns 200 status and in_progress status', async () => { - (routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.ml - .upgradeJobSnapshot as jest.Mock).mockResolvedValue({ + ( + routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.ml + .upgradeJobSnapshot as jest.Mock + ).mockResolvedValue({ body: { node: NODE_ID, completed: false, @@ -68,8 +70,10 @@ describe('ML snapshots APIs', () => { }); it('returns 200 status and complete status', async () => { - (routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.ml - .upgradeJobSnapshot as jest.Mock).mockResolvedValue({ + ( + routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.ml + .upgradeJobSnapshot as jest.Mock + ).mockResolvedValue({ body: { node: NODE_ID, completed: true, @@ -100,8 +104,10 @@ describe('ML snapshots APIs', () => { }); it('returns an error if it throws', async () => { - (routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.ml - .upgradeJobSnapshot as jest.Mock).mockRejectedValue(new Error('scary error!')); + ( + routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.ml + .upgradeJobSnapshot as jest.Mock + ).mockRejectedValue(new Error('scary error!')); await expect( routeDependencies.router.getHandler({ method: 'post', @@ -122,8 +128,10 @@ describe('ML snapshots APIs', () => { describe('DELETE /api/upgrade_assistant/ml_snapshots/:jobId/:snapshotId', () => { it('returns 200 status', async () => { - (routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.ml - .deleteModelSnapshot as jest.Mock).mockResolvedValue({ + ( + routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.ml + .deleteModelSnapshot as jest.Mock + ).mockResolvedValue({ body: { acknowledged: true }, }); @@ -145,8 +153,10 @@ describe('ML snapshots APIs', () => { }); it('returns an error if it throws', async () => { - (routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.ml - .deleteModelSnapshot as jest.Mock).mockRejectedValue(new Error('scary error!')); + ( + routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.ml + .deleteModelSnapshot as jest.Mock + ).mockRejectedValue(new Error('scary error!')); await expect( routeDependencies.router.getHandler({ method: 'delete', @@ -164,8 +174,10 @@ describe('ML snapshots APIs', () => { describe('GET /api/upgrade_assistant/ml_snapshots/:jobId/:snapshotId', () => { it('returns "idle" status if saved object does not exist', async () => { - (routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.ml - .getModelSnapshots as jest.Mock).mockResolvedValue({ + ( + routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.ml + .getModelSnapshots as jest.Mock + ).mockResolvedValue({ body: { count: 1, model_snapshots: [ @@ -209,8 +221,10 @@ describe('ML snapshots APIs', () => { }); it('returns "in_progress" status if snapshot upgrade is in progress', async () => { - (routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.ml - .getModelSnapshots as jest.Mock).mockResolvedValue({ + ( + routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.ml + .getModelSnapshots as jest.Mock + ).mockResolvedValue({ body: { count: 1, model_snapshots: [ @@ -243,8 +257,9 @@ describe('ML snapshots APIs', () => { ], }); - (routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.tasks - .list as jest.Mock).mockResolvedValue({ + ( + routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.tasks.list as jest.Mock + ).mockResolvedValue({ body: { nodes: { [NODE_ID]: { @@ -282,8 +297,10 @@ describe('ML snapshots APIs', () => { }); it('returns "complete" status if snapshot upgrade has completed', async () => { - (routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.ml - .getModelSnapshots as jest.Mock).mockResolvedValue({ + ( + routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.ml + .getModelSnapshots as jest.Mock + ).mockResolvedValue({ body: { count: 1, model_snapshots: [ @@ -316,8 +333,9 @@ describe('ML snapshots APIs', () => { ], }); - (routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.tasks - .list as jest.Mock).mockResolvedValue({ + ( + routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.tasks.list as jest.Mock + ).mockResolvedValue({ body: { nodes: { [NODE_ID]: { @@ -327,8 +345,10 @@ describe('ML snapshots APIs', () => { }, }); - (routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.migration - .deprecations as jest.Mock).mockResolvedValue({ + ( + routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.migration + .deprecations as jest.Mock + ).mockResolvedValue({ body: { cluster_settings: [], ml_settings: [], diff --git a/x-pack/plugins/upgrade_assistant/server/routes/ml_snapshots.ts b/x-pack/plugins/upgrade_assistant/server/routes/ml_snapshots.ts index 80f5f2eb60e09..f23de49d97dc8 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/ml_snapshots.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/ml_snapshots.ts @@ -238,13 +238,11 @@ export function registerMlSnapshotRoutes({ router }: RouteDependencies) { }); } else { // The task ID was not found; verify the deprecation was resolved - const { - isSuccessful: isSnapshotDeprecationResolved, - error: upgradeSnapshotError, - } = await verifySnapshotUpgrade(esClient, { - snapshotId, - jobId, - }); + const { isSuccessful: isSnapshotDeprecationResolved, error: upgradeSnapshotError } = + await verifySnapshotUpgrade(esClient, { + snapshotId, + jobId, + }); // Delete the SO; if it's complete, no need to store it anymore. If there's an error, this will give the user a chance to retry await deleteMlOperation(savedObjectsClient, snapshotOp.id); @@ -269,13 +267,11 @@ export function registerMlSnapshotRoutes({ router }: RouteDependencies) { } } else { // No tasks found; verify the deprecation was resolved - const { - isSuccessful: isSnapshotDeprecationResolved, - error: upgradeSnapshotError, - } = await verifySnapshotUpgrade(esClient, { - snapshotId, - jobId, - }); + const { isSuccessful: isSnapshotDeprecationResolved, error: upgradeSnapshotError } = + await verifySnapshotUpgrade(esClient, { + snapshotId, + jobId, + }); // Delete the SO; if it's complete, no need to store it anymore. If there's an error, this will give the user a chance to retry await deleteMlOperation(savedObjectsClient, snapshotOp.id); @@ -329,12 +325,11 @@ export function registerMlSnapshotRoutes({ router }: RouteDependencies) { try { const { snapshotId, jobId } = request.params; - const { - body: deleteSnapshotResponse, - } = await client.asCurrentUser.ml.deleteModelSnapshot({ - job_id: jobId, - snapshot_id: snapshotId, - }); + const { body: deleteSnapshotResponse } = + await client.asCurrentUser.ml.deleteModelSnapshot({ + job_id: jobId, + snapshot_id: snapshotId, + }); return response.ok({ body: deleteSnapshotResponse, diff --git a/x-pack/plugins/upgrade_assistant/server/routes/status.ts b/x-pack/plugins/upgrade_assistant/server/routes/status.ts index 6684c97a0a83d..1e0a0060de030 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/status.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/status.ts @@ -36,9 +36,8 @@ export function registerUpgradeStatusRoute({ router }: RouteDependencies) { esClient ); // Fetch Kibana upgrade status - const { - totalCriticalDeprecations: kibanaTotalCriticalDeps, - } = await getKibanaUpgradeStatus(deprecationsClient); + const { totalCriticalDeprecations: kibanaTotalCriticalDeps } = + await getKibanaUpgradeStatus(deprecationsClient); const readyForUpgrade = esTotalCriticalDeps === 0 && kibanaTotalCriticalDeps === 0; const getStatusMessage = () => { diff --git a/x-pack/plugins/uptime/common/constants/alerts.ts b/x-pack/plugins/uptime/common/constants/alerts.ts index cb31d83839590..7c0f7e8168809 100644 --- a/x-pack/plugins/uptime/common/constants/alerts.ts +++ b/x-pack/plugins/uptime/common/constants/alerts.ts @@ -7,10 +7,12 @@ import { ActionGroup } from '../../../alerting/common'; -export type MonitorStatusActionGroup = ActionGroup<'xpack.uptime.alerts.actionGroups.monitorStatus'>; +export type MonitorStatusActionGroup = + ActionGroup<'xpack.uptime.alerts.actionGroups.monitorStatus'>; export type TLSLegacyActionGroup = ActionGroup<'xpack.uptime.alerts.actionGroups.tls'>; export type TLSActionGroup = ActionGroup<'xpack.uptime.alerts.actionGroups.tlsCertificate'>; -export type DurationAnomalyActionGroup = ActionGroup<'xpack.uptime.alerts.actionGroups.durationAnomaly'>; +export type DurationAnomalyActionGroup = + ActionGroup<'xpack.uptime.alerts.actionGroups.durationAnomaly'>; export const MONITOR_STATUS: MonitorStatusActionGroup = { id: 'xpack.uptime.alerts.actionGroups.monitorStatus', diff --git a/x-pack/plugins/uptime/common/constants/rest_api.ts b/x-pack/plugins/uptime/common/constants/rest_api.ts index b49ccc3222a07..52b0620586eb4 100644 --- a/x-pack/plugins/uptime/common/constants/rest_api.ts +++ b/x-pack/plugins/uptime/common/constants/rest_api.ts @@ -6,8 +6,6 @@ */ export enum API_URLS { - CERTS = '/api/uptime/certs', - INDEX_PATTERN = `/api/uptime/index_pattern`, INDEX_STATUS = '/api/uptime/index_status', MONITOR_LIST = `/api/uptime/monitor/list`, MONITOR_LOCATIONS = `/api/uptime/monitor/locations`, @@ -17,7 +15,6 @@ export enum API_URLS { PINGS = '/api/uptime/pings', PING_HISTOGRAM = `/api/uptime/ping/histogram`, SNAPSHOT_COUNT = `/api/uptime/snapshot/count`, - FILTERS = `/api/uptime/filters`, LOG_PAGE_VIEW = `/api/uptime/log_page_view`, ML_MODULE_JOBS = `/api/ml/modules/jobs_exist/`, diff --git a/x-pack/plugins/uptime/common/constants/ui.ts b/x-pack/plugins/uptime/common/constants/ui.ts index dcaf4bb310ad7..29df2614d0617 100644 --- a/x-pack/plugins/uptime/common/constants/ui.ts +++ b/x-pack/plugins/uptime/common/constants/ui.ts @@ -67,3 +67,10 @@ export enum CERT_STATUS { } export const KQL_SYNTAX_LOCAL_STORAGE = 'xpack.uptime.kql.syntax'; + +export const FILTER_FIELDS = { + TAGS: 'tags', + PORT: 'url.port', + LOCATION: 'observer.geo.name', + TYPE: 'monitor.type', +}; diff --git a/x-pack/plugins/uptime/common/requests/get_certs_request_body.ts b/x-pack/plugins/uptime/common/requests/get_certs_request_body.ts new file mode 100644 index 0000000000000..5a729c7e3b96d --- /dev/null +++ b/x-pack/plugins/uptime/common/requests/get_certs_request_body.ts @@ -0,0 +1,183 @@ +/* + * 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 { estypes } from '@elastic/elasticsearch'; +import { CertResult, GetCertsParams, Ping } from '../runtime_types'; +import { createEsQuery } from '../utils/es_search'; + +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { CertificatesResults } from '../../server/lib/requests/get_certs'; +import { asMutableArray } from '../utils/as_mutable_array'; + +enum SortFields { + 'issuer' = 'tls.server.x509.issuer.common_name', + 'not_after' = 'tls.server.x509.not_after', + 'not_before' = 'tls.server.x509.not_before', + 'common_name' = 'tls.server.x509.subject.common_name', +} + +export const DEFAULT_SORT = 'not_after'; +export const DEFAULT_DIRECTION = 'asc'; +export const DEFAULT_SIZE = 20; +export const DEFAULT_FROM = 'now-5m'; +export const DEFAULT_TO = 'now'; + +export const getCertsRequestBody = ({ + pageIndex, + search, + notValidBefore, + notValidAfter, + size = DEFAULT_SIZE, + to = DEFAULT_TO, + from = DEFAULT_FROM, + sortBy = DEFAULT_SORT, + direction = DEFAULT_DIRECTION, +}: GetCertsParams) => { + const sort = SortFields[sortBy as keyof typeof SortFields]; + + const searchRequest = createEsQuery({ + body: { + from: pageIndex * size, + size, + sort: asMutableArray([ + { + [sort]: { + order: direction, + }, + }, + ]), + query: { + bool: { + ...(search + ? { + minimum_should_match: 1, + should: [ + { + multi_match: { + query: escape(search), + type: 'phrase_prefix' as const, + fields: [ + 'monitor.id.text', + 'monitor.name.text', + 'url.full.text', + 'tls.server.x509.subject.common_name.text', + 'tls.server.x509.issuer.common_name.text', + ], + }, + }, + ], + } + : {}), + filter: [ + { + exists: { + field: 'tls.server.hash.sha256', + }, + }, + { + range: { + 'monitor.timespan': { + gte: from, + lte: to, + }, + }, + }, + ...(notValidBefore + ? [ + { + range: { + 'tls.certificate_not_valid_before': { + lte: notValidBefore, + }, + }, + }, + ] + : []), + ...(notValidAfter + ? [ + { + range: { + 'tls.certificate_not_valid_after': { + lte: notValidAfter, + }, + }, + }, + ] + : []), + ] as estypes.QueryDslQueryContainer, + }, + }, + _source: [ + 'monitor.id', + 'monitor.name', + 'tls.server.x509.issuer.common_name', + 'tls.server.x509.subject.common_name', + 'tls.server.hash.sha1', + 'tls.server.hash.sha256', + 'tls.server.x509.not_after', + 'tls.server.x509.not_before', + ], + collapse: { + field: 'tls.server.hash.sha256', + inner_hits: { + _source: { + includes: ['monitor.id', 'monitor.name', 'url.full'], + }, + collapse: { + field: 'monitor.id', + }, + name: 'monitors', + sort: [{ 'monitor.id': 'asc' as const }], + }, + }, + aggs: { + total: { + cardinality: { + field: 'tls.server.hash.sha256', + }, + }, + }, + }, + }); + + return searchRequest.body; +}; + +export const processCertsResult = (result: CertificatesResults): CertResult => { + const certs = result.hits?.hits?.map((hit) => { + const ping = hit._source; + const server = ping.tls?.server!; + + const notAfter = server?.x509?.not_after; + const notBefore = server?.x509?.not_before; + const issuer = server?.x509?.issuer?.common_name; + const commonName = server?.x509?.subject?.common_name; + const sha1 = server?.hash?.sha1; + const sha256 = server?.hash?.sha256; + + const monitors = hit.inner_hits!.monitors.hits.hits.map((monitor) => { + const monitorPing = monitor._source as Ping; + + return { + name: monitorPing?.monitor.name, + id: monitorPing?.monitor.id, + url: monitorPing?.url?.full, + }; + }); + + return { + monitors, + issuer, + sha1, + sha256: sha256 as string, + not_after: notAfter, + not_before: notBefore, + common_name: commonName, + }; + }); + const total = result.aggregations?.total?.value ?? 0; + return { certs, total }; +}; diff --git a/x-pack/plugins/uptime/common/runtime_types/certs.ts b/x-pack/plugins/uptime/common/runtime_types/certs.ts index 3a5e885292500..c1a411effb903 100644 --- a/x-pack/plugins/uptime/common/runtime_types/certs.ts +++ b/x-pack/plugins/uptime/common/runtime_types/certs.ts @@ -9,10 +9,7 @@ import * as t from 'io-ts'; export const GetCertsParamsType = t.intersection([ t.type({ - index: t.number, - size: t.number, - sortBy: t.string, - direction: t.string, + pageIndex: t.number, }), t.partial({ search: t.string, @@ -20,6 +17,9 @@ export const GetCertsParamsType = t.intersection([ notValidAfter: t.string, from: t.string, to: t.string, + sortBy: t.string, + direction: t.string, + size: t.number, }), ]); diff --git a/x-pack/plugins/uptime/common/runtime_types/index.ts b/x-pack/plugins/uptime/common/runtime_types/index.ts index 51dacd2f4e9b6..1c1b05cddcd44 100644 --- a/x-pack/plugins/uptime/common/runtime_types/index.ts +++ b/x-pack/plugins/uptime/common/runtime_types/index.ts @@ -10,7 +10,6 @@ export * from './certs'; export * from './common'; export * from './dynamic_settings'; export * from './monitor'; -export * from './overview_filters'; export * from './ping'; export * from './snapshot'; export * from './network_events'; diff --git a/x-pack/plugins/uptime/common/runtime_types/overview_filters/overview_filters.ts b/x-pack/plugins/uptime/common/runtime_types/overview_filters/overview_filters.ts deleted file mode 100644 index e3610a98f5ceb..0000000000000 --- a/x-pack/plugins/uptime/common/runtime_types/overview_filters/overview_filters.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; - -export const OverviewFiltersType = t.partial({ - locations: t.array(t.string), - ports: t.array(t.number), - schemes: t.array(t.string), - tags: t.array(t.string), -}); - -export type OverviewFilters = t.TypeOf; diff --git a/x-pack/plugins/uptime/common/runtime_types/ping/ping.ts b/x-pack/plugins/uptime/common/runtime_types/ping/ping.ts index d6875840a138c..d71e720f50e6e 100644 --- a/x-pack/plugins/uptime/common/runtime_types/ping/ping.ts +++ b/x-pack/plugins/uptime/common/runtime_types/ping/ping.ts @@ -304,6 +304,7 @@ export const GetPingsParamsType = t.intersection([ dateRange: DateRangeType, }), t.partial({ + excludedLocations: t.string, index: t.number, size: t.number, locations: t.string, diff --git a/x-pack/plugins/uptime/common/utils/es_search.ts b/x-pack/plugins/uptime/common/utils/es_search.ts new file mode 100644 index 0000000000000..ba72d09a4e8ef --- /dev/null +++ b/x-pack/plugins/uptime/common/utils/es_search.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { estypes } from '@elastic/elasticsearch'; + +export function createEsQuery(params: T): T { + return params; +} diff --git a/x-pack/plugins/uptime/public/apps/plugin.ts b/x-pack/plugins/uptime/public/apps/plugin.ts index e611363b4ab49..ed936fe164983 100644 --- a/x-pack/plugins/uptime/public/apps/plugin.ts +++ b/x-pack/plugins/uptime/public/apps/plugin.ts @@ -69,7 +69,8 @@ export type ClientSetup = void; export type ClientStart = void; export class UptimePlugin - implements Plugin { + implements Plugin +{ constructor(_context: PluginInitializerContext) {} public setup(core: CoreSetup, plugins: ClientPluginsSetup): void { diff --git a/x-pack/plugins/uptime/public/apps/uptime_app.tsx b/x-pack/plugins/uptime/public/apps/uptime_app.tsx index b31dd068ebb08..f82a312ef91f5 100644 --- a/x-pack/plugins/uptime/public/apps/uptime_app.tsx +++ b/x-pack/plugins/uptime/public/apps/uptime_app.tsx @@ -32,6 +32,7 @@ import { kibanaService } from '../state/kibana_service'; import { ActionMenu } from '../components/common/header/action_menu'; import { EuiThemeProvider } from '../../../../../src/plugins/kibana_react/common'; import { Storage } from '../../../../../src/plugins/kibana_utils/public'; +import { UptimeIndexPatternContextProvider } from '../contexts/uptime_index_pattern_context'; export interface UptimeAppColors { danger: string; @@ -119,16 +120,20 @@ const Application = (props: UptimeAppProps) => { -
- - - - - -
+ +
+ +
+ + + +
+
+
+
diff --git a/x-pack/plugins/uptime/public/apps/uptime_page_template.tsx b/x-pack/plugins/uptime/public/apps/uptime_page_template.tsx new file mode 100644 index 0000000000000..829f587e248e7 --- /dev/null +++ b/x-pack/plugins/uptime/public/apps/uptime_page_template.tsx @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useMemo } from 'react'; +import styled from 'styled-components'; +import { EuiPageHeaderProps } from '@elastic/eui'; +import { OVERVIEW_ROUTE } from '../../common/constants'; +import { useKibana } from '../../../../../src/plugins/kibana_react/public'; +import { ClientPluginsStart } from './plugin'; +import { useNoDataConfig } from './use_no_data_config'; +import { EmptyStateLoading } from '../components/overview/empty_state/empty_state_loading'; +import { EmptyStateError } from '../components/overview/empty_state/empty_state_error'; +import { useHasData } from '../components/overview/empty_state/use_has_data'; + +interface Props { + path: string; + pageHeader?: EuiPageHeaderProps; +} + +export const UptimePageTemplateComponent: React.FC = ({ path, pageHeader, children }) => { + const { + services: { observability }, + } = useKibana(); + + const PageTemplateComponent = observability.navigation.PageTemplate; + + const StyledPageTemplateComponent = useMemo(() => { + return styled(PageTemplateComponent)` + .euiPageHeaderContent > .euiFlexGroup { + flex-wrap: wrap; + } + `; + }, [PageTemplateComponent]); + + const noDataConfig = useNoDataConfig(); + + const { loading, error } = useHasData(); + + if (error) { + return ; + } + + return ( + <> +
+ + {loading && path === OVERVIEW_ROUTE && } +
+ {children} +
+
+ + ); +}; diff --git a/x-pack/plugins/uptime/public/apps/use_no_data_config.ts b/x-pack/plugins/uptime/public/apps/use_no_data_config.ts new file mode 100644 index 0000000000000..dc00a25e3a111 --- /dev/null +++ b/x-pack/plugins/uptime/public/apps/use_no_data_config.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { useContext } from 'react'; +import { useSelector } from 'react-redux'; +import { KibanaPageTemplateProps, useKibana } from '../../../../../src/plugins/kibana_react/public'; +import { UptimeSettingsContext } from '../contexts'; +import { ClientPluginsStart } from './plugin'; +import { indexStatusSelector } from '../state/selectors'; + +export function useNoDataConfig(): KibanaPageTemplateProps['noDataConfig'] { + const { basePath } = useContext(UptimeSettingsContext); + + const { + services: { docLinks }, + } = useKibana(); + + const { data } = useSelector(indexStatusSelector); + + // Returns no data config when there is no historical data + if (data && !data.indexExists) { + return { + solution: i18n.translate('xpack.uptime.noDataConfig.solutionName', { + defaultMessage: 'Observability', + }), + actions: { + beats: { + title: i18n.translate('xpack.uptime.noDataConfig.beatsCard.title', { + defaultMessage: 'Add monitors with Heartbeat', + }), + description: i18n.translate('xpack.uptime.noDataConfig.beatsCard.description', { + defaultMessage: + 'Proactively monitor the availability of your sites and services. Receive alerts and resolve issues faster to optimize your users experience.', + }), + href: basePath + `/app/home#/tutorial/uptimeMonitors`, + }, + }, + docsLink: docLinks!.links.observability.guide, + }; + } +} diff --git a/x-pack/plugins/uptime/public/components/certificates/__snapshots__/certificates_list.test.tsx.snap b/x-pack/plugins/uptime/public/components/certificates/__snapshots__/certificates_list.test.tsx.snap deleted file mode 100644 index dc808ffcdd22b..0000000000000 --- a/x-pack/plugins/uptime/public/components/certificates/__snapshots__/certificates_list.test.tsx.snap +++ /dev/null @@ -1,105 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`CertificateList shallow renders expected elements for valid props 1`] = ` - - - - - -`; diff --git a/x-pack/plugins/uptime/public/components/certificates/cert_search.tsx b/x-pack/plugins/uptime/public/components/certificates/cert_search.tsx index 08650edbaac23..391d38921fee5 100644 --- a/x-pack/plugins/uptime/public/components/certificates/cert_search.tsx +++ b/x-pack/plugins/uptime/public/components/certificates/cert_search.tsx @@ -5,9 +5,10 @@ * 2.0. */ -import React, { ChangeEvent } from 'react'; +import React, { ChangeEvent, useState } from 'react'; import { EuiFieldSearch } from '@elastic/eui'; import styled from 'styled-components'; +import useDebounce from 'react-use/lib/useDebounce'; import * as labels from './translations'; const WrapFieldSearch = styled('div')` @@ -19,10 +20,20 @@ interface Props { } export const CertificateSearch: React.FC = ({ setSearch }) => { + const [debouncedValue, setDebouncedValue] = useState(''); + const onChange = (e: ChangeEvent) => { - setSearch(e.target.value); + setDebouncedValue(e.target.value); }; + useDebounce( + () => { + setSearch(debouncedValue); + }, + 350, + [debouncedValue] + ); + return ( { - const { data: certificates } = useSelector(certificatesSelector); + const total = useSelector(certificatesSelector); return ( {certificates?.total ?? 0}, + total: {total ?? 0}, }} /> ); diff --git a/x-pack/plugins/uptime/public/components/certificates/certificates_list.test.tsx b/x-pack/plugins/uptime/public/components/certificates/certificates_list.test.tsx index ec6a5d91a67c3..ff54267b8b8b4 100644 --- a/x-pack/plugins/uptime/public/components/certificates/certificates_list.test.tsx +++ b/x-pack/plugins/uptime/public/components/certificates/certificates_list.test.tsx @@ -6,11 +6,11 @@ */ import React from 'react'; -import { shallowWithRouter } from '../../lib'; import { CertificateList, CertSort } from './certificates_list'; +import { render } from '../../lib/helper/rtl_helpers'; describe('CertificateList', () => { - it('shallow renders expected elements for valid props', () => { + it('render empty state', () => { const page = { index: 0, size: 10, @@ -20,8 +20,59 @@ describe('CertificateList', () => { direction: 'asc', }; + const { getByText } = render( + + ); + expect( - shallowWithRouter() - ).toMatchSnapshot(); + getByText('No Certificates found. Note: Certificates are only visible for Heartbeat 7.8+') + ).toBeInTheDocument(); + }); + + it('renders certificates list', () => { + const page = { + index: 0, + size: 10, + }; + const sort: CertSort = { + field: 'not_after', + direction: 'asc', + }; + + const { getByText } = render( + + ); + + expect(getByText('BadSSL Expired')).toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/uptime/public/components/certificates/certificates_list.tsx b/x-pack/plugins/uptime/public/components/certificates/certificates_list.tsx index f01cdc5dfb9c8..69b39538cb3e0 100644 --- a/x-pack/plugins/uptime/public/components/certificates/certificates_list.tsx +++ b/x-pack/plugins/uptime/public/components/certificates/certificates_list.tsx @@ -7,13 +7,11 @@ import React from 'react'; import moment from 'moment'; -import { useSelector } from 'react-redux'; import { Direction, EuiBasicTable } from '@elastic/eui'; -import { certificatesSelector } from '../../state/certificates/certificates'; import { CertStatus } from './cert_status'; import { CertMonitors } from './cert_monitors'; import * as labels from './translations'; -import { Cert, CertMonitor } from '../../../common/runtime_types'; +import { Cert, CertMonitor, CertResult } from '../../../common/runtime_types'; import { FingerprintCol } from './fingerprint_col'; import { LOADING_CERTIFICATES, NO_CERTS_AVAILABLE } from './translations'; @@ -40,11 +38,10 @@ interface Props { page: Page; sort: CertSort; onChange: (page: Page, sort: CertSort) => void; + certificates: CertResult & { loading?: boolean }; } -export const CertificateList: React.FC = ({ page, sort, onChange }) => { - const { data: certificates, loading } = useSelector(certificatesSelector); - +export const CertificateList: React.FC = ({ page, certificates, sort, onChange }) => { const onTableChange = (newVal: Partial) => { onChange(newVal.page as Page, newVal.sort as CertSort); }; @@ -100,7 +97,7 @@ export const CertificateList: React.FC = ({ page, sort, onChange }) => { return ( = ({ page, sort, onChange }) => { }, }} noItemsMessage={ - loading ? ( + certificates.loading ? ( LOADING_CERTIFICATES ) : ( {NO_CERTS_AVAILABLE} diff --git a/x-pack/plugins/uptime/public/components/certificates/use_cert_search.ts b/x-pack/plugins/uptime/public/components/certificates/use_cert_search.ts new file mode 100644 index 0000000000000..22531faff2da1 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/certificates/use_cert_search.ts @@ -0,0 +1,60 @@ +/* + * 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 { useSelector } from 'react-redux'; +import { useContext } from 'react'; +import { useEsSearch, createEsParams } from '../../../../observability/public'; + +import { CertResult, GetCertsParams, Ping } from '../../../common/runtime_types'; + +import { selectDynamicSettings } from '../../state/selectors'; +import { + DEFAULT_DIRECTION, + DEFAULT_FROM, + DEFAULT_SIZE, + DEFAULT_SORT, + DEFAULT_TO, + getCertsRequestBody, + processCertsResult, +} from '../../../common/requests/get_certs_request_body'; +import { UptimeRefreshContext } from '../../contexts'; + +export const useCertSearch = ({ + pageIndex, + size = DEFAULT_SIZE, + search, + sortBy = DEFAULT_SORT, + direction = DEFAULT_DIRECTION, +}: GetCertsParams): CertResult & { loading?: boolean } => { + const settings = useSelector(selectDynamicSettings); + const { lastRefresh } = useContext(UptimeRefreshContext); + + const searchBody = getCertsRequestBody({ + pageIndex, + size, + search, + sortBy, + direction, + to: DEFAULT_TO, + from: DEFAULT_FROM, + }); + + const esParams = createEsParams({ + index: settings.settings?.heartbeatIndices, + body: searchBody, + }); + + const { data: result, loading } = useEsSearch(esParams, [ + settings.settings?.heartbeatIndices, + size, + pageIndex, + lastRefresh, + search, + ]); + + return result ? { ...processCertsResult(result), loading } : { certs: [], total: 0, loading }; +}; diff --git a/x-pack/plugins/uptime/public/components/common/header/action_menu_content.tsx b/x-pack/plugins/uptime/public/components/common/header/action_menu_content.tsx index 9f00dd2e8f061..ef5e10394739a 100644 --- a/x-pack/plugins/uptime/public/components/common/header/action_menu_content.tsx +++ b/x-pack/plugins/uptime/public/components/common/header/action_menu_content.tsx @@ -40,11 +40,11 @@ export function ActionMenuContent(): React.ReactElement { const syntheticExploratoryViewLink = createExploratoryViewUrl( { - 'synthetics-series': ({ + 'synthetics-series': { dataType: 'synthetics', isNew: true, time: { from: dateRangeStart, to: dateRangeEnd }, - } as unknown) as SeriesUrl, + } as unknown as SeriesUrl, }, basePath ); diff --git a/x-pack/plugins/uptime/public/components/common/higher_order/responsive_wrapper.tsx b/x-pack/plugins/uptime/public/components/common/higher_order/responsive_wrapper.tsx index 0e33cc3e38f03..d678c48137d6b 100644 --- a/x-pack/plugins/uptime/public/components/common/higher_order/responsive_wrapper.tsx +++ b/x-pack/plugins/uptime/public/components/common/higher_order/responsive_wrapper.tsx @@ -28,15 +28,15 @@ export interface ResponsiveWrapperProps { * HOC that wraps a component in either a responsive div or an EuiPanel. * @param Component The component to wrap. */ -export const withResponsiveWrapper =

( - Component: FC

-): FC => ({ isResponsive, ...rest }: ResponsiveWrapperProps) => - isResponsive ? ( - - - - ) : ( - - - - ); +export const withResponsiveWrapper = +

(Component: FC

): FC => + ({ isResponsive, ...rest }: ResponsiveWrapperProps) => + isResponsive ? ( + + + + ) : ( + + + + ); diff --git a/x-pack/plugins/uptime/public/components/common/monitor_tags.tsx b/x-pack/plugins/uptime/public/components/common/monitor_tags.tsx index 892271f9ef8c5..793c27a031546 100644 --- a/x-pack/plugins/uptime/public/components/common/monitor_tags.tsx +++ b/x-pack/plugins/uptime/public/components/common/monitor_tags.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { EuiBadge, EuiBadgeGroup, EuiLink } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { useHistory } from 'react-router-dom'; @@ -69,9 +69,14 @@ export const MonitorTags = ({ ping, summary }: Props) => { const currFilters = parseCurrentFilters(params.filters); - const [filterType, setFilterType] = useState(currFilters.get('tags') ?? []); + const [tagFilters, setTagFilters] = useState(currFilters.get('tags') ?? []); - useFilterUpdate('tags', filterType); + const excludedTagFilters = useMemo(() => { + const currExcludedFilters = parseCurrentFilters(params.excludedFilters); + return currExcludedFilters.get('tags') ?? []; + }, [params.excludedFilters]); + + useFilterUpdate('tags', tagFilters, excludedTagFilters); if (tags.length === 0) { return summary ? null : ( @@ -93,7 +98,7 @@ export const MonitorTags = ({ ping, summary }: Props) => { key={tag} title={getFilterLabel(tag)} onClick={() => { - setFilterType([tag]); + setTagFilters([tag]); }} onClickAriaLabel={getFilterLabel(tag)} color="hollow" diff --git a/x-pack/plugins/uptime/public/components/common/uptime_date_picker.test.tsx b/x-pack/plugins/uptime/public/components/common/uptime_date_picker.test.tsx index 4bfe7de33cba5..f1b37d0c98f46 100644 --- a/x-pack/plugins/uptime/public/components/common/uptime_date_picker.test.tsx +++ b/x-pack/plugins/uptime/public/components/common/uptime_date_picker.test.tsx @@ -39,7 +39,7 @@ describe('UptimeDatePicker component', () => { const component = mountWithRouterRedux( )} + {...(startPlugins as unknown as Partial)} > , @@ -56,7 +56,7 @@ describe('UptimeDatePicker component', () => { expect(customHistory.push).toHaveBeenCalledWith({ pathname: '/', - search: 'dateRangeStart=now-30m&dateRangeEnd=now-15m', + search: 'dateRangeEnd=now-15m&dateRangeStart=now-30m', }); }); @@ -69,7 +69,7 @@ describe('UptimeDatePicker component', () => { const component = mountWithRouterRedux( )} + {...(startPlugins as unknown as Partial)} > , diff --git a/x-pack/plugins/uptime/public/components/fleet_package/request_body_field.test.tsx b/x-pack/plugins/uptime/public/components/fleet_package/request_body_field.test.tsx index fa666ac764ac7..d6f25449a98c0 100644 --- a/x-pack/plugins/uptime/public/components/fleet_package/request_body_field.test.tsx +++ b/x-pack/plugins/uptime/public/components/fleet_package/request_body_field.test.tsx @@ -30,9 +30,10 @@ describe('', () => { setConfig({ type: code.type as Mode, value: code.value }), [ - setConfig, - ])} + onChange={useCallback( + (code) => setConfig({ type: code.type as Mode, value: code.value }), + [setConfig] + )} /> ); }; diff --git a/x-pack/plugins/uptime/public/components/fleet_package/synthetics_policy_create_extension_wrapper.tsx b/x-pack/plugins/uptime/public/components/fleet_package/synthetics_policy_create_extension_wrapper.tsx index 0bc8f31f3d6cf..c845abc3320c2 100644 --- a/x-pack/plugins/uptime/public/components/fleet_package/synthetics_policy_create_extension_wrapper.tsx +++ b/x-pack/plugins/uptime/public/components/fleet_package/synthetics_policy_create_extension_wrapper.tsx @@ -21,8 +21,8 @@ import { * Exports Synthetics-specific package policy instructions * for use in the Ingest app create / edit package policy */ -export const SyntheticsPolicyCreateExtensionWrapper = memo( - ({ newPolicy, onChange }) => { +export const SyntheticsPolicyCreateExtensionWrapper = + memo(({ newPolicy, onChange }) => { return ( @@ -38,6 +38,5 @@ export const SyntheticsPolicyCreateExtensionWrapper = memo ); - } -); + }); SyntheticsPolicyCreateExtensionWrapper.displayName = 'SyntheticsPolicyCreateExtensionWrapper'; diff --git a/x-pack/plugins/uptime/public/components/fleet_package/synthetics_policy_edit_extension_wrapper.tsx b/x-pack/plugins/uptime/public/components/fleet_package/synthetics_policy_edit_extension_wrapper.tsx index d83130b21a0f1..219fa86958c34 100644 --- a/x-pack/plugins/uptime/public/components/fleet_package/synthetics_policy_edit_extension_wrapper.tsx +++ b/x-pack/plugins/uptime/public/components/fleet_package/synthetics_policy_edit_extension_wrapper.tsx @@ -47,15 +47,13 @@ export const SyntheticsPolicyEditExtensionWrapper = memo( - (acc: ICustomFields, key: ConfigKeys) => { + const formattedDefaultConfigForMonitorType: ICustomFields = + configKeys.reduce((acc: ICustomFields, key: ConfigKeys) => { return { ...acc, [key]: normalizers[key]?.(vars), }; - }, - {} as ICustomFields - ); + }, {} as ICustomFields); const tlsConfig: ITLSFields = { [ConfigKeys.TLS_CERTIFICATE_AUTHORITIES]: diff --git a/x-pack/plugins/uptime/public/components/fleet_package/tls_fields.tsx b/x-pack/plugins/uptime/public/components/fleet_package/tls_fields.tsx index a2db0d99088f7..d2cfd65c61c37 100644 --- a/x-pack/plugins/uptime/public/components/fleet_package/tls_fields.tsx +++ b/x-pack/plugins/uptime/public/components/fleet_package/tls_fields.tsx @@ -37,10 +37,8 @@ export const TLSFields: React.FunctionComponent<{ tlsRole: TLSRole; }> = memo(({ isEnabled, tlsRole }) => { const { fields, setFields } = useTLSFieldsContext(); - const [ - verificationVersionInputRef, - setVerificationVersionInputRef, - ] = useState(null); + const [verificationVersionInputRef, setVerificationVersionInputRef] = + useState(null); const [hasVerificationVersionError, setHasVerificationVersionError] = useState< string | undefined >(undefined); diff --git a/x-pack/plugins/uptime/public/components/monitor/monitor_duration/monitor_duration_container.tsx b/x-pack/plugins/uptime/public/components/monitor/monitor_duration/monitor_duration_container.tsx index 1590e225f9ca8..cbfba4ffcb239 100644 --- a/x-pack/plugins/uptime/public/components/monitor/monitor_duration/monitor_duration_container.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/monitor_duration/monitor_duration_container.tsx @@ -28,12 +28,8 @@ import { createExploratoryViewUrl } from '../../../../../observability/public'; import { useUptimeSettingsContext } from '../../../contexts/uptime_settings_context'; export const MonitorDuration: React.FC = ({ monitorId }) => { - const { - dateRangeStart, - dateRangeEnd, - absoluteDateRangeStart, - absoluteDateRangeEnd, - } = useGetUrlParams(); + const { dateRangeStart, dateRangeEnd, absoluteDateRangeStart, absoluteDateRangeEnd } = + useGetUrlParams(); const { durationLines, loading } = useSelector(selectDurationLines); diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/ping_list_header.tsx b/x-pack/plugins/uptime/public/components/monitor/ping_list/ping_list_header.tsx index 8e599cba6e97e..0284211d6259c 100644 --- a/x-pack/plugins/uptime/public/components/monitor/ping_list/ping_list_header.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/ping_list/ping_list_header.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; import { StatusFilter } from '../../overview/monitor_list/status_filter'; -import { FilterGroup } from '../../overview/filter_group'; +import { FilterGroup } from '../../overview/filter_group/filter_group'; export const PingListHeader = () => { return ( @@ -27,7 +27,7 @@ export const PingListHeader = () => { - + diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/use_pings.ts b/x-pack/plugins/uptime/public/components/monitor/ping_list/use_pings.ts index 9a8b1aa0e46f4..026ba57d3458f 100644 --- a/x-pack/plugins/uptime/public/components/monitor/ping_list/use_pings.ts +++ b/x-pack/plugins/uptime/public/components/monitor/ping_list/use_pings.ts @@ -35,15 +35,19 @@ export const usePingsList = ({ pageSize, pageIndex }: Props) => { const { statusFilter } = useGetUrlParams(); - const { selectedLocations } = useSelectedFilters(); + const selectedFilters = useSelectedFilters(); const dispatch = useDispatch(); const monitorId = useMonitorId(); - const getPings = useCallback((params: GetPingsParams) => dispatch(getPingsAction(params)), [ - dispatch, - ]); + const getPings = useCallback( + (params: GetPingsParams) => dispatch(getPingsAction(params)), + [dispatch] + ); + + const locations = JSON.stringify(selectedFilters.selectedLocations); + const excludedLocations = JSON.stringify(selectedFilters.excludedLocations); useEffect(() => { getPings({ @@ -52,7 +56,8 @@ export const usePingsList = ({ pageSize, pageIndex }: Props) => { from, to, }, - locations: JSON.stringify(selectedLocations), + excludedLocations, + locations, index: pageIndex, size: pageSize, status: statusFilter !== 'all' ? statusFilter : '', @@ -66,7 +71,8 @@ export const usePingsList = ({ pageSize, pageIndex }: Props) => { pageIndex, pageSize, statusFilter, - selectedLocations, + locations, + excludedLocations, ]); const { data } = useFetcher(() => { diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_container.test.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_container.test.tsx index b35fdb6100826..3580df2c317a7 100644 --- a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_container.test.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_container.test.tsx @@ -15,8 +15,7 @@ const networkEvents = { { timestamp: '2021-01-21T10:31:21.537Z', method: 'GET', - url: - 'https://apv-static.minute.ly/videos/v-c2a526c7-450d-428e-1244649-a390-fb639ffead96-s45.746-54.421m.mp4', + url: 'https://apv-static.minute.ly/videos/v-c2a526c7-450d-428e-1244649-a390-fb639ffead96-s45.746-54.421m.mp4', status: 206, mimeType: 'video/mp4', requestSentTime: 241114127.474, diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_wrapper.test.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_wrapper.test.tsx index da7ccd3679f57..fbb6f2c75a540 100644 --- a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_wrapper.test.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_wrapper.test.tsx @@ -201,8 +201,7 @@ const NETWORK_EVENTS = { { timestamp: '2021-01-21T10:31:21.537Z', method: 'GET', - url: - 'https://apv-static.minute.ly/videos/v-c2a526c7-450d-428e-1244649-a390-fb639ffead96-s45.746-54.421m.mp4', + url: 'https://apv-static.minute.ly/videos/v-c2a526c7-450d-428e-1244649-a390-fb639ffead96-s45.746-54.421m.mp4', status: 206, mimeType: 'video/mp4', requestSentTime: 241114127.474, diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/use_flyout.ts b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/use_flyout.ts index 206fc588c3053..6e1795c4933ec 100644 --- a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/use_flyout.ts +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/use_flyout.ts @@ -30,9 +30,8 @@ export type OnFlyoutClose = () => void; export const useFlyout = (metadata: WaterfallMetadata) => { const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); const [flyoutData, setFlyoutData] = useState(undefined); - const [currentSidebarItemRef, setCurrentSidebarItemRef] = useState< - RefObject - >(); + const [currentSidebarItemRef, setCurrentSidebarItemRef] = + useState>(); const handleFlyout = useCallback( (flyoutEntry: WaterfallMetadataEntry) => { diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/context/waterfall_chart.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/context/waterfall_chart.tsx index b960491162010..16b3a24de7d0c 100644 --- a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/context/waterfall_chart.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/context/waterfall_chart.tsx @@ -83,4 +83,4 @@ export const WaterfallProvider: React.FC = ({ }; export const useWaterfallContext = () => - useContext((WaterfallContext as unknown) as Context); + useContext(WaterfallContext as unknown as Context); diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/alert_query_bar/query_bar.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/alert_query_bar/query_bar.tsx index 0a0bbadb6216f..4c6072a018642 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/alert_query_bar/query_bar.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/alert_query_bar/query_bar.tsx @@ -9,9 +9,9 @@ import React, { useEffect, useState } from 'react'; import { EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { QueryStringInput } from '../../../../../../../../src/plugins/data/public'; -import { useIndexPattern } from '../../query_bar/use_index_pattern'; import { isValidKuery } from '../../query_bar/query_bar'; import * as labels from '../translations'; +import { useIndexPattern } from '../../../../hooks'; interface Props { query: string; @@ -19,7 +19,7 @@ interface Props { } export const AlertQueryBar = ({ query = '', onChange }: Props) => { - const { index_pattern: indexPattern } = useIndexPattern(); + const indexPattern = useIndexPattern(); const [inputVal, setInputVal] = useState(query); diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/alert_monitor_status.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/alert_monitor_status.tsx index ff2ef4d2359a8..39d19464caa2e 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/alert_monitor_status.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/alerts_containers/alert_monitor_status.tsx @@ -8,15 +8,18 @@ import React, { useEffect } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import { isRight } from 'fp-ts/lib/Either'; -import { overviewFiltersSelector, selectedFiltersSelector } from '../../../../state/selectors'; +import { selectedFiltersSelector } from '../../../../state/selectors'; import { AlertMonitorStatusComponent } from '../monitor_status_alert/alert_monitor_status'; -import { fetchOverviewFilters, setSearchTextAction } from '../../../../state/actions'; +import { setSearchTextAction } from '../../../../state/actions'; import { AtomicStatusCheckParamsType, GetMonitorAvailabilityParamsType, } from '../../../../../common/runtime_types'; import { useSnapShotCount } from './use_snap_shot'; +import { FILTER_FIELDS } from '../../../../../common/constants'; + +const { TYPE, TAGS, LOCATION, PORT } = FILTER_FIELDS; interface Props { alertParams: { [key: string]: any }; @@ -37,23 +40,6 @@ export const AlertMonitorStatus: React.FC = ({ alertParams, }) => { const dispatch = useDispatch(); - useEffect(() => { - if (!window.location.pathname.includes('/app/uptime')) { - // filters inside uptime app already loaded - dispatch( - fetchOverviewFilters({ - dateRangeStart: 'now-24h', - dateRangeEnd: 'now', - locations: alertParams.filters?.['observer.geo.name'] ?? [], - ports: alertParams.filters?.['url.port'] ?? [], - tags: alertParams.filters?.tags ?? [], - schemes: alertParams.filters?.['monitor.type'] ?? [], - }) - ); - } - }, [alertParams, dispatch]); - - const overviewFilters = useSelector(overviewFiltersSelector); useEffect(() => { if (alertParams.search) { @@ -78,14 +64,10 @@ export const AlertMonitorStatus: React.FC = ({ useEffect(() => { if (!alertParams.filters && selectedFilters !== null) { setAlertParams('filters', { - // @ts-ignore - 'url.port': selectedFilters?.ports ?? [], - // @ts-ignore - 'observer.geo.name': selectedFilters?.locations ?? [], - // @ts-ignore - 'monitor.type': selectedFilters?.schemes ?? [], - // @ts-ignore - tags: selectedFilters?.tags ?? [], + [PORT]: selectedFilters?.ports ?? [], + [LOCATION]: selectedFilters?.locations ?? [], + [TYPE]: selectedFilters?.schemes ?? [], + [TAGS]: selectedFilters?.tags ?? [], }); } }, [alertParams, setAlertParams, selectedFilters]); @@ -94,7 +76,6 @@ export const AlertMonitorStatus: React.FC = ({ = () => { const dispatch = useDispatch(); - const setFlyoutVisible = useCallback((value: boolean) => dispatch(setAlertFlyoutVisible(value)), [ - dispatch, - ]); + const setFlyoutVisible = useCallback( + (value: boolean) => dispatch(setAlertFlyoutVisible(value)), + [dispatch] + ); const { settings } = useSelector(selectDynamicSettings); return ( void; + hasFilters: boolean; } const TimeRangeOptions: TimeRangeOption[] = [ @@ -55,6 +56,7 @@ export const AvailabilityExpressionSelect: React.FC = ({ alertParams, isOldAlert, setAlertParams, + hasFilters, }) => { const [range, setRange] = useState(alertParams?.availability?.range ?? DEFAULT_RANGE); const [rangeUnit, setRangeUnit] = useState( @@ -114,7 +116,11 @@ export const AvailabilityExpressionSelect: React.FC = ({ /> } data-test-subj="xpack.uptime.alerts.monitorStatus.availability.threshold" - description={labels.ENTER_AVAILABILITY_THRESHOLD_DESCRIPTION} + description={ + hasFilters + ? labels.ENTER_AVAILABILITY_THRESHOLD_DESCRIPTION + : labels.ENTER_ANY_AVAILABILITY_THRESHOLD_DESCRIPTION + } id="threshold" isEnabled={isEnabled} isInvalid={thresholdIsInvalid} diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/filters_expression_select.test.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/filters_expression_select.test.tsx index c0bf73d6c5308..6aa829adc4544 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/filters_expression_select.test.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/filters_expression_select.test.tsx @@ -6,12 +6,11 @@ */ import React from 'react'; -import { shallowWithIntl } from '@kbn/test/jest'; import { fireEvent, waitFor } from '@testing-library/react'; import { FiltersExpressionsSelect } from './filters_expression_select'; import { render } from '../../../../lib/helper/rtl_helpers'; import { filterAriaLabels as aria } from './translations'; -import { filterLabels } from '../../filter_group/translations'; +import * as Hooks from '../../../../../../observability/public/hooks/use_values_list'; describe('FiltersExpressionSelect', () => { const LOCATION_FIELD_NAME = 'observer.geo.name'; @@ -19,23 +18,22 @@ describe('FiltersExpressionSelect', () => { const SCHEME_FIELD_NAME = 'monitor.type'; const TAG_FIELD_NAME = 'tags'; - it('is empty when no filters available', () => { - const component = shallowWithIntl( + it('is empty when no filters available', async () => { + const { queryByLabelText } = render( ); - expect(component).toMatchInlineSnapshot(``); + + await waitFor(() => { + for (const label of Object.values(aria)) { + expect(queryByLabelText(label)).toBeNull(); + } + }); }); it.each([ @@ -51,24 +49,20 @@ describe('FiltersExpressionSelect', () => { [aria.LOCATION, aria.TAG], ], [[TAG_FIELD_NAME], [aria.TAG], [aria.LOCATION, aria.PORT, aria.SCHEME]], - ])('contains provided new filter values', (newFilters, expectedLabels, absentLabels) => { + ])('contains provided new filter values', async (newFilters, expectedLabels, absentLabels) => { const { getByLabelText, queryByLabelText } = render( ); - expectedLabels.forEach((label) => expect(getByLabelText(label))); - absentLabels.forEach((label) => expect(queryByLabelText(label)).toBeNull()); + await waitFor(() => { + expectedLabels.forEach((label) => expect(getByLabelText(label))); + absentLabels.forEach((label) => expect(queryByLabelText(label)).toBeNull()); + }); }); it.each([ @@ -84,12 +78,6 @@ describe('FiltersExpressionSelect', () => { alertParams={{}} newFilters={[LOCATION_FIELD_NAME, SCHEME_FIELD_NAME, PORT_FIELD_NAME, TAG_FIELD_NAME]} onRemoveFilter={onRemoveFilterMock} - filters={{ - tags: ['prod'], - ports: [5601], - schemes: ['http'], - locations: ['nyc'], - }} setAlertParams={setAlertParamsMock} shouldUpdateUrl={false} /> @@ -108,60 +96,11 @@ describe('FiltersExpressionSelect', () => { }); }); - const TEST_TAGS = ['foo', 'bar']; - const TEST_PORTS = [5601, 9200]; - const TEST_SCHEMES = ['http', 'tcp']; - const TEST_LOCATIONS = ['nyc', 'fairbanks']; - it.each([ - [ - { - tags: TEST_TAGS, - ports: [5601, 9200], - schemes: ['http', 'tcp'], - locations: ['nyc', 'fairbanks'], - }, - [TAG_FIELD_NAME], - aria.TAG, - filterLabels.TAG, - TEST_TAGS, - ], - [ - { - tags: [], - ports: TEST_PORTS, - schemes: [], - locations: [], - }, - [PORT_FIELD_NAME], - aria.PORT, - filterLabels.PORT, - TEST_PORTS, - ], - [ - { - tags: [], - ports: [], - schemes: TEST_SCHEMES, - locations: [], - }, - [SCHEME_FIELD_NAME], - aria.SCHEME, - filterLabels.SCHEME, - TEST_SCHEMES, - ], - [ - { - tags: [], - ports: [], - schemes: [], - locations: TEST_LOCATIONS, - }, - [LOCATION_FIELD_NAME], - aria.LOCATION, - filterLabels.LOCATION, - TEST_LOCATIONS, - ], + [[TAG_FIELD_NAME], aria.TAG], + [[PORT_FIELD_NAME], aria.PORT], + [[SCHEME_FIELD_NAME], aria.SCHEME], + [[LOCATION_FIELD_NAME], aria.LOCATION], ])( 'applies accessible label to filter expressions, and contains selected filters', /** @@ -171,19 +110,14 @@ describe('FiltersExpressionSelect', () => { * @param filterLabel the name of the filter label expected in each item's aria-label * @param expectedFilterItems the set of filter options the component should render */ - async ( - filters, - newFilters, - expectedFilterButtonAriaLabel, - filterLabel, - expectedFilterItems - ) => { - const { getByLabelText } = render( + async (newFilters, expectedFilterButtonAriaLabel) => { + const spy = jest.spyOn(Hooks, 'useValuesList'); + spy.mockReturnValue({ loading: false, values: [{ label: 'test-label', count: 3 }] }); + const { getByLabelText, getByText } = render( @@ -194,9 +128,7 @@ describe('FiltersExpressionSelect', () => { fireEvent.click(filterButton); await waitFor(() => { - expectedFilterItems.forEach((filterItem: string | number) => - expect(getByLabelText(`Filter by ${filterLabel} ${filterItem}.`)) - ); + expect(getByText('Apply')); }); } ); diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/filters_expression_select.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/filters_expression_select.tsx index b09d44488e803..cd0a78a42c5f7 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/filters_expression_select.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/filters_expression_select.tsx @@ -7,31 +7,38 @@ import React, { useState } from 'react'; import { EuiButtonIcon, EuiExpression, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; -import { FilterPopover } from '../../filter_group/filter_popover'; import { filterLabels } from '../../filter_group/translations'; import { alertFilterLabels, filterAriaLabels } from './translations'; -import { FilterExpressionsSelectProps } from './filters_expression_select_container'; -import { OverviewFiltersState } from '../../../../state/reducers/overview_filters'; +import { FieldValueSuggestions } from '../../../../../../observability/public'; +import { useIndexPattern } from '../../../../contexts/uptime_index_pattern_context'; +import { FILTER_FIELDS } from '../../../../../common/constants'; +import { useGetUrlParams } from '../../../../hooks'; -type Props = FilterExpressionsSelectProps & Pick; +export interface FilterExpressionsSelectProps { + alertParams: { [key: string]: any }; + newFilters: string[]; + onRemoveFilter: (val: string) => void; + setAlertParams: (key: string, value: any) => void; + shouldUpdateUrl: boolean; +} -export const FiltersExpressionsSelect: React.FC = ({ +const { TYPE, TAGS, LOCATION, PORT } = FILTER_FIELDS; + +export const FiltersExpressionsSelect: React.FC = ({ alertParams, - filters: overviewFilters, newFilters, onRemoveFilter, setAlertParams, }) => { - const { tags, ports, schemes, locations } = overviewFilters; - const alertFilters = alertParams?.filters; - const selectedPorts = alertFilters?.['url.port'] ?? []; - const selectedLocations = alertFilters?.['observer.geo.name'] ?? []; - const selectedSchemes = alertFilters?.['monitor.type'] ?? []; - const selectedTags = alertFilters?.tags ?? []; + const selectedPorts = alertFilters?.[PORT] ?? []; + const selectedLocations = alertFilters?.[LOCATION] ?? []; + const selectedSchemes = alertFilters?.[TYPE] ?? []; + const selectedTags = alertFilters?.[TAGS] ?? []; - const onFilterFieldChange = (fieldName: string, values: string[]) => { + const { dateRangeStart: from, dateRangeEnd: to } = useGetUrlParams(); + const onFilterFieldChange = (fieldName: string, values?: string[]) => { // the `filters` field is no longer a string if (alertParams.filters && typeof alertParams.filters !== 'string') { setAlertParams('filters', { ...alertParams.filters, [fieldName]: values }); @@ -41,12 +48,12 @@ export const FiltersExpressionsSelect: React.FC = ({ Object.assign( {}, { - tags: [], - 'url.port': [], - 'observer.geo.name': [], - 'monitor.type': [], + [TAGS]: [], + [PORT]: [], + [LOCATION]: [], + [TYPE]: [], }, - { [fieldName]: values } + { [fieldName]: values ?? [] } ) ); } @@ -54,13 +61,11 @@ export const FiltersExpressionsSelect: React.FC = ({ const monitorFilters = [ { - 'aria-label': filterAriaLabels.PORT, + ariaLabel: filterAriaLabels.PORT, onFilterFieldChange, loading: false, fieldName: 'url.port', id: 'filter_port', - disabled: ports?.length === 0, - items: ports?.map((p: number) => p.toString()) ?? [], selectedItems: selectedPorts, title: filterLabels.PORT, description: @@ -68,39 +73,33 @@ export const FiltersExpressionsSelect: React.FC = ({ value: selectedPorts.length === 0 ? alertFilterLabels.ANY_PORT : selectedPorts?.join(','), }, { - 'aria-label': filterAriaLabels.TAG, + ariaLabel: filterAriaLabels.TAG, onFilterFieldChange, loading: false, fieldName: 'tags', id: 'filter_tags', - disabled: tags?.length === 0, - items: tags ?? [], selectedItems: selectedTags, title: filterLabels.TAG, description: selectedTags.length === 0 ? alertFilterLabels.WITH : alertFilterLabels.WITH_TAG, value: selectedTags.length === 0 ? alertFilterLabels.ANY_TAG : selectedTags?.join(','), }, { - 'aria-label': filterAriaLabels.SCHEME, + ariaLabel: filterAriaLabels.SCHEME, onFilterFieldChange, loading: false, fieldName: 'monitor.type', id: 'filter_scheme', - disabled: schemes?.length === 0, - items: schemes ?? [], selectedItems: selectedSchemes, title: filterLabels.SCHEME, description: selectedSchemes.length === 0 ? alertFilterLabels.OF : alertFilterLabels.OF_TYPE, value: selectedSchemes.length === 0 ? alertFilterLabels.ANY_TYPE : selectedSchemes?.join(','), }, { - 'aria-label': filterAriaLabels.LOCATION, + ariaLabel: filterAriaLabels.LOCATION, onFilterFieldChange, loading: false, fieldName: 'observer.geo.name', id: 'filter_location', - disabled: locations?.length === 0, - items: locations ?? [], selectedItems: selectedLocations, title: filterLabels.LOCATION, description: @@ -123,43 +122,61 @@ export const FiltersExpressionsSelect: React.FC = ({ (curr) => curr.selectedItems.length > 0 || newFilters?.includes(curr.fieldName) ); + const indexPattern = useIndexPattern(); + return ( <> - {filtersToDisplay.map(({ description, value, ...item }) => ( - - - setIsOpen({ ...isOpen, [item.id]: !isOpen[item.id] })} + {filtersToDisplay.map( + ({ description, id, title, value, fieldName, ariaLabel, selectedItems }) => ( + + + {indexPattern && ( + { + onFilterFieldChange(fieldName, vals); + }} + selectedValue={selectedItems} + button={ + setIsOpen({ ...isOpen, [id]: !isOpen[id] })} + /> + } + forceOpen={isOpen[id]} + setForceOpen={() => { + setIsOpen({ ...isOpen, [id]: !isOpen[id] }); + }} + asCombobox={false} + cardinalityField="monitor.id" + time={{ from, to }} + allowExclusions={false} /> - } - forceOpen={isOpen[item.id]} - setForceOpen={() => { - setIsOpen({ ...isOpen, [item.id]: !isOpen[item.id] }); - }} - /> - - - { - onRemoveFilter(item.fieldName); - onFilterFieldChange(item.fieldName, []); - }} - /> - - - - ))} + )} + + + { + onRemoveFilter(fieldName); + onFilterFieldChange(fieldName, []); + }} + /> + + + + ) + )} ); }; diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/filters_expression_select_container.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/filters_expression_select_container.tsx deleted file mode 100644 index 0c03d55ba38f5..0000000000000 --- a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/filters_expression_select_container.tsx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { useSelector } from 'react-redux'; -import { FiltersExpressionsSelect } from './filters_expression_select'; -import { overviewFiltersSelector } from '../../../../state/selectors'; - -export interface FilterExpressionsSelectProps { - alertParams: { [key: string]: any }; - newFilters: string[]; - onRemoveFilter: (val: string) => void; - setAlertParams: (key: string, value: any) => void; - shouldUpdateUrl: boolean; -} - -export const FiltersExpressionSelectContainer: React.FC = (props) => { - const overviewFilters = useSelector(overviewFiltersSelector); - - return ; -}; diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/index.ts b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/index.ts index 85d0e82471e5c..6797517116ccd 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/index.ts +++ b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/index.ts @@ -7,6 +7,5 @@ export { DownNoExpressionSelect } from './down_number_select'; export { FiltersExpressionsSelect } from './filters_expression_select'; -export { FiltersExpressionSelectContainer } from './filters_expression_select_container'; export { TimeExpressionSelect } from './time_expression_select'; export { StatusExpressionSelect } from './status_expression_select'; diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_status_alert/alert_monitor_status.test.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_status_alert/alert_monitor_status.test.tsx index e161727b46b1b..b339410ef2409 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_status_alert/alert_monitor_status.test.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_status_alert/alert_monitor_status.test.tsx @@ -7,10 +7,45 @@ import React from 'react'; import { screen } from '@testing-library/dom'; -import { AlertMonitorStatusComponent, AlertMonitorStatusProps } from './alert_monitor_status'; +import { + AlertMonitorStatusComponent, + AlertMonitorStatusProps, + hasFilters, +} from './alert_monitor_status'; import { render } from '../../../../lib/helper/rtl_helpers'; describe('alert monitor status component', () => { + describe('hasFilters', () => { + const EMPTY_FILTERS = { + tags: [], + 'url.port': [], + 'observer.geo.name': [], + 'monitor.type': [], + }; + + it('returns false when filters are empty', () => { + expect(hasFilters({})).toBe(false); + }); + + it('returns false when all fields are empty', () => { + expect(hasFilters(EMPTY_FILTERS)).toBe(false); + }); + + it.each([ + { tags: ['prod'] }, + { 'url.port': ['5678'] }, + { 'observer.geo.name': ['Fairbanks'] }, + { 'monitor.type': ['HTTP'] }, + ])('returns true if a filter has a field', (testObj) => { + expect( + hasFilters({ + ...EMPTY_FILTERS, + ...testObj, + }) + ).toBe(true); + }); + }); + describe('AlertMonitorStatus', () => { const defaultProps: AlertMonitorStatusProps = { alertParams: { @@ -20,7 +55,6 @@ describe('alert monitor status component', () => { timerangeCount: 21, }, enabled: true, - hasFilters: false, isOldAlert: true, snapshotCount: 0, snapshotLoading: false, @@ -38,7 +72,7 @@ describe('alert monitor status component', () => { expect(await screen.findByText('Add filter')).toBeInTheDocument(); expect(await screen.findByText('Availability')).toBeInTheDocument(); expect(await screen.findByText('Status check')).toBeInTheDocument(); - expect(await screen.findByText('matching monitors are up in')).toBeInTheDocument(); + expect(await screen.findByText('any monitor is up in')).toBeInTheDocument(); expect(await screen.findByText('days')).toBeInTheDocument(); expect(await screen.findByText('hours')).toBeInTheDocument(); expect(await screen.findByText('within the last')).toBeInTheDocument(); diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_status_alert/alert_monitor_status.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_status_alert/alert_monitor_status.tsx index eaae1650b02ed..8ed4b8f7a0032 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_status_alert/alert_monitor_status.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_status_alert/alert_monitor_status.tsx @@ -8,17 +8,17 @@ import React, { useCallback, useEffect, useState } from 'react'; import { EuiCallOut, EuiSpacer, EuiHorizontalRule, EuiLoadingSpinner } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { FiltersExpressionSelectContainer, StatusExpressionSelect } from '../monitor_expressions'; +import { FiltersExpressionsSelect, StatusExpressionSelect } from '../monitor_expressions'; import { AddFilterButton } from './add_filter_btn'; import { OldAlertCallOut } from './old_alert_call_out'; import { AvailabilityExpressionSelect } from '../monitor_expressions/availability_expression_select'; import { AlertQueryBar } from '../alert_query_bar/query_bar'; import { useGetUrlParams } from '../../../../hooks'; +import { FILTER_FIELDS } from '../../../../../common/constants'; export interface AlertMonitorStatusProps { alertParams: { [key: string]: any }; enabled: boolean; - hasFilters: boolean; isOldAlert: boolean; snapshotCount: number; snapshotLoading?: boolean; @@ -30,15 +30,16 @@ export interface AlertMonitorStatusProps { }; } +export const hasFilters = (filters?: { [key: string]: string[] }) => { + if (!filters || Object.keys(filters).length === 0) { + return false; + } + + return Object.values(FILTER_FIELDS).some((f) => filters[f].length); +}; + export const AlertMonitorStatusComponent: React.FC = (props) => { - const { - alertParams, - hasFilters, - isOldAlert, - setAlertParams, - snapshotCount, - snapshotLoading, - } = props; + const { alertParams, isOldAlert, setAlertParams, snapshotCount, snapshotLoading } = props; const alertFilters = alertParams?.filters ?? {}; const [newFilters, setNewFilters] = useState( @@ -94,7 +95,7 @@ export const AlertMonitorStatusComponent: React.FC = (p }} /> - { @@ -110,8 +111,8 @@ export const AlertMonitorStatusComponent: React.FC = (p @@ -120,6 +121,7 @@ export const AlertMonitorStatusComponent: React.FC = (p alertParams={alertParams} isOldAlert={isOldAlert} setAlertParams={setAlertParams} + hasFilters={hasFilters(alertParams?.filters)} /> diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/translations.ts b/x-pack/plugins/uptime/public/components/overview/alerts/translations.ts index 7cfcdabe5562b..76d8ebaea3719 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/translations.ts +++ b/x-pack/plugins/uptime/public/components/overview/alerts/translations.ts @@ -197,6 +197,15 @@ export const ENTER_AVAILABILITY_THRESHOLD_DESCRIPTION = i18n.translate( } ); +export const ENTER_ANY_AVAILABILITY_THRESHOLD_DESCRIPTION = i18n.translate( + 'xpack.uptime.alerts.monitorStatus.availability.threshold.anyMonitorDescription', + { + defaultMessage: 'any monitor is up in', + description: + 'This fragment explains that an alert will fire for monitors matching user-specified criteria', + } +); + export const ENTER_AVAILABILITY_THRESHOLD_VALUE = (value: string) => i18n.translate('xpack.uptime.alerts.monitorStatus.availability.threshold.value', { defaultMessage: '< {value}% of checks', diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/uptime_alerts_flyout_wrapper.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/uptime_alerts_flyout_wrapper.tsx index 25bb7a399e6c4..459f73a78ad8b 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/uptime_alerts_flyout_wrapper.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/uptime_alerts_flyout_wrapper.tsx @@ -25,9 +25,10 @@ export const UptimeAlertsFlyoutWrapperComponent = ({ setAlertFlyoutVisibility, }: Props) => { const { triggersActionsUi } = useKibana().services; - const onCloseAlertFlyout = useCallback(() => setAlertFlyoutVisibility(false), [ - setAlertFlyoutVisibility, - ]); + const onCloseAlertFlyout = useCallback( + () => setAlertFlyoutVisibility(false), + [setAlertFlyoutVisibility] + ); const AddAlertFlyout = useMemo( () => triggersActionsUi.getAddAlertFlyout({ diff --git a/x-pack/plugins/uptime/public/components/overview/empty_state/data_or_index_missing.test.tsx b/x-pack/plugins/uptime/public/components/overview/empty_state/data_or_index_missing.test.tsx deleted file mode 100644 index caff055ce987c..0000000000000 --- a/x-pack/plugins/uptime/public/components/overview/empty_state/data_or_index_missing.test.tsx +++ /dev/null @@ -1,26 +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 React from 'react'; -import { screen } from '@testing-library/react'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { render } from '../../../lib/helper/rtl_helpers'; -import { DataOrIndexMissing } from './data_or_index_missing'; - -describe('DataOrIndexMissing component', () => { - it('renders headingMessage', () => { - const headingMessage = ( - heartbeat-* }} - /> - ); - render(); - expect(screen.getByText(/heartbeat-*/)).toBeInTheDocument(); - }); -}); diff --git a/x-pack/plugins/uptime/public/components/overview/empty_state/data_or_index_missing.tsx b/x-pack/plugins/uptime/public/components/overview/empty_state/data_or_index_missing.tsx deleted file mode 100644 index 44e55de990bbf..0000000000000 --- a/x-pack/plugins/uptime/public/components/overview/empty_state/data_or_index_missing.tsx +++ /dev/null @@ -1,87 +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 { - EuiFlexGroup, - EuiEmptyPrompt, - EuiFlexItem, - EuiSpacer, - EuiPanel, - EuiTitle, - EuiButton, -} from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; -import React, { useContext } from 'react'; -import { UptimeSettingsContext } from '../../../contexts'; -import { DynamicSettings } from '../../../../common/runtime_types'; - -interface DataMissingProps { - headingMessage: JSX.Element; - settings?: DynamicSettings; -} - -export const DataOrIndexMissing = ({ headingMessage, settings }: DataMissingProps) => { - const { basePath } = useContext(UptimeSettingsContext); - return ( - - - - - -

{headingMessage}

- - } - body={ - <> -

- -

-

- -

- - } - actions={ - - - - - - - - - - - - - } - /> - - - - ); -}; diff --git a/x-pack/plugins/uptime/public/components/overview/empty_state/empty_state.test.tsx b/x-pack/plugins/uptime/public/components/overview/empty_state/empty_state.test.tsx deleted file mode 100644 index 45b107928d79a..0000000000000 --- a/x-pack/plugins/uptime/public/components/overview/empty_state/empty_state.test.tsx +++ /dev/null @@ -1,99 +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 React from 'react'; -import { screen } from '@testing-library/react'; -import { EmptyStateComponent } from './empty_state'; -import { StatesIndexStatus } from '../../../../common/runtime_types'; -import { HttpFetchError, IHttpFetchError } from 'src/core/public'; -import { render } from '../../../lib/helper/rtl_helpers'; - -describe('EmptyState component', () => { - let statesIndexStatus: StatesIndexStatus; - - beforeEach(() => { - statesIndexStatus = { - indexExists: true, - docCount: 1, - indices: 'heartbeat-*,synthetics-*', - }; - }); - - it('renders child components when count is truthy', () => { - render( - -
Foo
-
Bar
-
Baz
-
- ); - - expect(screen.getByText('Foo')).toBeInTheDocument(); - expect(screen.getByText('Bar')).toBeInTheDocument(); - expect(screen.getByText('Baz')).toBeInTheDocument(); - }); - - it(`doesn't render child components when count is falsy`, () => { - render( - -
Should not be rendered
-
- ); - expect(screen.queryByText('Should not be rendered')).toBeNull(); - }); - - it(`renders error message when an error occurs`, () => { - const errors: IHttpFetchError[] = [ - new HttpFetchError('There was an error fetching your data.', 'error', {} as any, {} as any, { - body: { message: 'There was an error fetching your data.' }, - }), - ]; - render( - -
Should not appear...
-
- ); - expect(screen.queryByText('Should not appear...')).toBeNull(); - }); - - it('renders loading state if no errors or doc count', () => { - render( - -
Should appear even while loading...
-
- ); - expect(screen.queryByText('Should appear even while loading...')).toBeInTheDocument(); - }); - - it('does not render empty state with appropriate base path and no docs', () => { - statesIndexStatus = { - docCount: 0, - indexExists: true, - indices: 'heartbeat-*,synthetics-*', - }; - const text = 'If this is in the snapshot the test should fail'; - render( - -
{text}
-
- ); - expect(screen.queryByText(text)).toBeNull(); - }); - - it('notifies when index does not exist', () => { - statesIndexStatus.indexExists = false; - - const text = 'This text should not render'; - - render( - -
{text}
-
- ); - expect(screen.queryByText(text)).toBeNull(); - }); -}); diff --git a/x-pack/plugins/uptime/public/components/overview/empty_state/empty_state.tsx b/x-pack/plugins/uptime/public/components/overview/empty_state/empty_state.tsx deleted file mode 100644 index a6fd6579c49fa..0000000000000 --- a/x-pack/plugins/uptime/public/components/overview/empty_state/empty_state.tsx +++ /dev/null @@ -1,73 +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 React, { Fragment } from 'react'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { IHttpFetchError } from 'src/core/public'; -import { EmptyStateError } from './empty_state_error'; -import { EmptyStateLoading } from './empty_state_loading'; -import { DataOrIndexMissing } from './data_or_index_missing'; -import { DynamicSettings, StatesIndexStatus } from '../../../../common/runtime_types'; - -interface EmptyStateProps { - children: JSX.Element[] | JSX.Element; - statesIndexStatus: StatesIndexStatus | null; - loading: boolean; - errors?: IHttpFetchError[]; - settings?: DynamicSettings; -} - -export const EmptyStateComponent = ({ - children, - statesIndexStatus, - loading, - errors, - settings, -}: EmptyStateProps) => { - if (errors?.length) { - return ; - } - const { indexExists, docCount } = statesIndexStatus ?? {}; - - const isLoading = loading && (!indexExists || docCount === 0 || !statesIndexStatus); - - const noIndicesMessage = ( - {settings?.heartbeatIndices} }} - /> - ); - - const noUptimeDataMessage = ( - {settings?.heartbeatIndices} }} - /> - ); - - if (!indexExists && !isLoading) { - return ; - } else if (indexExists && docCount === 0 && !isLoading) { - return ; - } - /** - * We choose to render the children any time the count > 0, even if - * the component is loading. If we render the loading state for this component, - * it will blow away the state of child components and trigger an ugly - * jittery UX any time the components refresh. This way we'll keep the stale - * state displayed during the fetching process. - */ - return ( - - {isLoading && } -
{children}
-
- ); - // } -}; diff --git a/x-pack/plugins/uptime/public/components/overview/empty_state/empty_state_container.tsx b/x-pack/plugins/uptime/public/components/overview/empty_state/empty_state_container.tsx deleted file mode 100644 index 562e45727dda7..0000000000000 --- a/x-pack/plugins/uptime/public/components/overview/empty_state/empty_state_container.tsx +++ /dev/null @@ -1,54 +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 React, { useContext, useEffect } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { indexStatusAction } from '../../../state/actions'; -import { indexStatusSelector, selectDynamicSettings } from '../../../state/selectors'; -import { EmptyStateComponent } from './index'; -import { UptimeRefreshContext } from '../../../contexts'; -import { getDynamicSettings } from '../../../state/actions/dynamic_settings'; - -export const EmptyState: React.FC = ({ children }) => { - const { data, loading, error } = useSelector(indexStatusSelector); - const { lastRefresh } = useContext(UptimeRefreshContext); - - const { settings } = useSelector(selectDynamicSettings); - - const heartbeatIndices = settings?.heartbeatIndices || ''; - - const dispatch = useDispatch(); - - const noDataInfo = !data || data?.docCount === 0 || data?.indexExists === false; - - useEffect(() => { - if (noDataInfo) { - // only call when we haven't fetched it already - dispatch(indexStatusAction.get()); - } - }, [dispatch, lastRefresh, noDataInfo]); - - useEffect(() => { - // using separate side effect, we want to call index status, - // every statue indices setting changes - dispatch(indexStatusAction.get()); - }, [dispatch, heartbeatIndices]); - - useEffect(() => { - dispatch(getDynamicSettings()); - }, [dispatch]); - - return ( - - ); -}; diff --git a/x-pack/plugins/uptime/public/components/overview/empty_state/index.ts b/x-pack/plugins/uptime/public/components/overview/empty_state/index.ts deleted file mode 100644 index 5ffcc15ed404b..0000000000000 --- a/x-pack/plugins/uptime/public/components/overview/empty_state/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export { EmptyStateComponent } from './empty_state'; -export { EmptyState } from './empty_state_container'; diff --git a/x-pack/plugins/uptime/public/components/overview/empty_state/use_has_data.tsx b/x-pack/plugins/uptime/public/components/overview/empty_state/use_has_data.tsx new file mode 100644 index 0000000000000..66c68834f285f --- /dev/null +++ b/x-pack/plugins/uptime/public/components/overview/empty_state/use_has_data.tsx @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useContext, useEffect } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { indexStatusAction } from '../../../state/actions'; +import { indexStatusSelector, selectDynamicSettings } from '../../../state/selectors'; +import { UptimeRefreshContext } from '../../../contexts'; +import { getDynamicSettings } from '../../../state/actions/dynamic_settings'; + +export const useHasData = () => { + const { loading, error } = useSelector(indexStatusSelector); + const { lastRefresh } = useContext(UptimeRefreshContext); + + const { settings } = useSelector(selectDynamicSettings); + + const dispatch = useDispatch(); + + useEffect(() => { + dispatch(indexStatusAction.get()); + }, [dispatch, lastRefresh]); + + useEffect(() => { + dispatch(getDynamicSettings()); + }, [dispatch]); + + return { + error, + loading, + settings, + }; +}; diff --git a/x-pack/plugins/uptime/public/components/overview/filter_group/__snapshots__/parse_filter_map.test.ts.snap b/x-pack/plugins/uptime/public/components/overview/filter_group/__snapshots__/parse_filter_map.test.ts.snap deleted file mode 100644 index cabad2818fa90..0000000000000 --- a/x-pack/plugins/uptime/public/components/overview/filter_group/__snapshots__/parse_filter_map.test.ts.snap +++ /dev/null @@ -1,20 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`parseFiltersMap provides values from valid filter string 1`] = ` -Object { - "locations": Array [ - "us-east-2", - ], - "ports": Array [ - "5601", - "80", - ], - "schemes": Array [ - "http", - "tcp", - ], - "tags": Array [], -} -`; - -exports[`parseFiltersMap returns an empty object for invalid filter 1`] = `"Unable to parse invalid filter string"`; diff --git a/x-pack/plugins/uptime/public/components/overview/filter_group/filter_group.test.tsx b/x-pack/plugins/uptime/public/components/overview/filter_group/filter_group.test.tsx index 807f95c22bc61..cdc399a750756 100644 --- a/x-pack/plugins/uptime/public/components/overview/filter_group/filter_group.test.tsx +++ b/x-pack/plugins/uptime/public/components/overview/filter_group/filter_group.test.tsx @@ -8,67 +8,103 @@ import React from 'react'; import { fireEvent, waitFor } from '@testing-library/react'; import { render } from '../../../lib/helper/rtl_helpers'; -import { FilterGroupComponent } from './filter_group'; +import { FilterGroup } from './filter_group'; +import * as Hooks from '../../../../../observability/public/hooks/use_values_list'; -describe('FilterGroupComponent', () => { - const overviewFilters = { - locations: ['nyc', 'fairbanks'], - ports: [5601, 9200], - schemes: ['http', 'tcp'], - tags: ['prod', 'dev'], - }; +describe('FilterGroup', () => { it.each([ - ['expands filter group for Location filter', 'Search for location'], - ['expands filter group for Port filter', 'Search for port'], - ['expands filter group for Scheme filter', 'Search for scheme'], - ['expands filter group for Tag filter', 'Search for tag'], - ])('handles loading', async (popoverButtonLabel, searchInputLabel) => { - const { getByLabelText } = render( - - ); + ['expands filter group for Location filter'], + ['expands filter group for Port filter'], + ['expands filter group for Scheme filter'], + ['expands filter group for Tag filter'], + ])('handles loading', async (popoverButtonLabel) => { + jest.spyOn(Hooks, 'useValuesList').mockReturnValue({ + values: [], + loading: true, + }); + const { getByLabelText, getByText } = render(); - const popoverButton = getByLabelText(popoverButtonLabel); - fireEvent.click(popoverButton); await waitFor(() => { - const searchInput = getByLabelText(searchInputLabel); - expect(searchInput).toHaveAttribute('placeholder', 'Loading...'); + const popoverButton = getByLabelText(popoverButtonLabel); + fireEvent.click(popoverButton); + }); + await waitFor(() => { + expect(getByText('Loading options')); }); }); it.each([ [ 'expands filter group for Location filter', - 'Search for location', - ['Filter by Location nyc.', 'Filter by Location fairbanks.'], + [ + [ + { + label: 'Fairbanks', + count: 10, + }, + { + label: 'NYC', + count: 2, + }, + ], + [], + [], + [], + ], ], [ 'expands filter group for Port filter', - 'Search for port', - ['Filter by Port 5601.', 'Filter by Port 9200.'], + [ + [], + [ + { label: '80', count: 12 }, + { label: '443', count: 8 }, + ], + [], + [], + ], ], [ 'expands filter group for Scheme filter', - 'Search for scheme', - ['Filter by Scheme http.', 'Filter by Scheme tcp.'], + [ + [], + [], + [ + { label: 'HTTP', count: 15 }, + { label: 'TCP', count: 10 }, + ], + [], + ], ], [ 'expands filter group for Tag filter', - 'Search for tag', - ['Filter by Tag prod.', 'Filter by Tag dev.'], + [ + [], + [], + [], + [ + { label: 'test', count: 23 }, + { label: 'prod', count: 10 }, + ], + ], ], - ])( - 'displays filter items when clicked', - async (popoverButtonLabel, searchInputLabel, filterItemButtonLabels) => { - const { getByLabelText } = render( - - ); + ])('displays filter item counts when clicked', async (popoverButtonLabel, values) => { + const spy = jest.spyOn(Hooks, 'useValuesList'); + for (let i = 0; i < 4; i++) { + spy.mockReturnValueOnce({ + values: values[i], + loading: false, + }); + } + const { getByLabelText, getAllByLabelText } = render(); + + await waitFor(() => { const popoverButton = getByLabelText(popoverButtonLabel); fireEvent.click(popoverButton); - await waitFor(() => { - expect(getByLabelText(searchInputLabel)); - filterItemButtonLabels.forEach((itemLabel) => expect(getByLabelText(itemLabel))); - }); - } - ); + }); + + expect(getByLabelText('2 available filters')); + expect(getAllByLabelText('0 available filters')).toHaveLength(3); + }); }); diff --git a/x-pack/plugins/uptime/public/components/overview/filter_group/filter_group.tsx b/x-pack/plugins/uptime/public/components/overview/filter_group/filter_group.tsx index b60d2b3050f5c..3980b4bf9d3da 100644 --- a/x-pack/plugins/uptime/public/components/overview/filter_group/filter_group.tsx +++ b/x-pack/plugins/uptime/public/components/overview/filter_group/filter_group.tsx @@ -5,100 +5,72 @@ * 2.0. */ -import React, { useState } from 'react'; +import React, { useCallback, useState } from 'react'; import { EuiFilterGroup } from '@elastic/eui'; import styled from 'styled-components'; -import { useRouteMatch } from 'react-router-dom'; -import { FilterPopoverProps, FilterPopover } from './filter_popover'; -import { OverviewFilters } from '../../../../common/runtime_types/overview_filters'; -import { filterLabels } from './translations'; import { useFilterUpdate } from '../../../hooks/use_filter_update'; -import { MONITOR_ROUTE } from '../../../../common/constants'; import { useSelectedFilters } from '../../../hooks/use_selected_filters'; - -interface Props { - loading: boolean; - overviewFilters: OverviewFilters; -} +import { FieldValueSuggestions } from '../../../../../observability/public'; +import { SelectedFilters } from './selected_filters'; +import { useIndexPattern } from '../../../contexts/uptime_index_pattern_context'; +import { useGetUrlParams } from '../../../hooks'; const Container = styled(EuiFilterGroup)` margin-bottom: 10px; `; -function isDisabled(array?: T[]) { - return array ? array.length === 0 : true; -} - -export const FilterGroupComponent: React.FC = ({ overviewFilters, loading }) => { - const { locations, ports, schemes, tags } = overviewFilters; - +export const FilterGroup = () => { const [updatedFieldValues, setUpdatedFieldValues] = useState<{ fieldName: string; values: string[]; - }>({ fieldName: '', values: [] }); + notValues: string[]; + }>({ fieldName: '', values: [], notValues: [] }); - useFilterUpdate(updatedFieldValues.fieldName, updatedFieldValues.values); + useFilterUpdate( + updatedFieldValues.fieldName, + updatedFieldValues.values, + updatedFieldValues.notValues + ); - const { selectedLocations, selectedPorts, selectedSchemes, selectedTags } = useSelectedFilters(); + const { dateRangeStart, dateRangeEnd } = useGetUrlParams(); - const onFilterFieldChange = (fieldName: string, values: string[]) => { - setUpdatedFieldValues({ fieldName, values }); - }; + const { filtersList } = useSelectedFilters(); - const isMonitorPage = useRouteMatch(MONITOR_ROUTE); + const indexPattern = useIndexPattern(); - const filterPopoverProps: FilterPopoverProps[] = [ - { - loading, - onFilterFieldChange, - fieldName: 'observer.geo.name', - id: 'location', - items: locations || [], - selectedItems: selectedLocations, - title: filterLabels.LOCATION, + const onFilterFieldChange = useCallback( + (fieldName: string, values: string[], notValues: string[]) => { + setUpdatedFieldValues({ fieldName, values, notValues }); }, - // on monitor page we only display location filter in ping list - ...(!isMonitorPage - ? [ - { - loading, - onFilterFieldChange, - fieldName: 'url.port', - id: 'port', - disabled: isDisabled(ports), - items: ports?.map((p: number) => p.toString()) ?? [], - selectedItems: selectedPorts, - title: filterLabels.PORT, - }, - { - loading, - onFilterFieldChange, - fieldName: 'monitor.type', - id: 'scheme', - disabled: isDisabled(schemes), - items: schemes ?? [], - selectedItems: selectedSchemes, - title: filterLabels.SCHEME, - }, - { - loading, - onFilterFieldChange, - fieldName: 'tags', - id: 'tags', - disabled: isDisabled(tags), - items: tags ?? [], - selectedItems: selectedTags, - title: filterLabels.TAG, - }, - ] - : []), - ]; + [] + ); return ( - - {filterPopoverProps.map((item) => ( - - ))} - + <> + + {indexPattern && + filtersList.map(({ field, label, selectedItems, excludedItems }) => ( + + onFilterFieldChange(field, values ?? [], notValues ?? []) + } + asCombobox={false} + asFilterButton={true} + forceOpen={false} + filters={[]} + cardinalityField="monitor.id" + time={{ from: dateRangeStart, to: dateRangeEnd }} + /> + ))} + + + ); }; diff --git a/x-pack/plugins/uptime/public/components/overview/filter_group/filter_group_container.tsx b/x-pack/plugins/uptime/public/components/overview/filter_group/filter_group_container.tsx deleted file mode 100644 index db1892526a1e6..0000000000000 --- a/x-pack/plugins/uptime/public/components/overview/filter_group/filter_group_container.tsx +++ /dev/null @@ -1,57 +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 React, { useContext, useEffect } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { useGetUrlParams } from '../../../hooks'; -import { parseFiltersMap } from './parse_filter_map'; -import { fetchOverviewFilters } from '../../../state/actions'; -import { FilterGroupComponent } from './index'; -import { UptimeRefreshContext } from '../../../contexts'; -import { esKuerySelector, overviewFiltersSelector } from '../../../state/selectors'; - -interface Props { - esFilters?: string; -} - -export const FilterGroup: React.FC = ({ esFilters }: Props) => { - const { lastRefresh } = useContext(UptimeRefreshContext); - - const { filters: overviewFilters, loading } = useSelector(overviewFiltersSelector); - const esKuery = useSelector(esKuerySelector); - - const { dateRangeStart, dateRangeEnd, statusFilter, filters: urlFilters } = useGetUrlParams(); - - const dispatch = useDispatch(); - - useEffect(() => { - const filterSelections = parseFiltersMap(urlFilters); - dispatch( - fetchOverviewFilters({ - dateRangeStart, - dateRangeEnd, - locations: filterSelections.locations ?? [], - ports: filterSelections.ports ?? [], - schemes: filterSelections.schemes ?? [], - search: esKuery, - statusFilter, - tags: filterSelections.tags ?? [], - }) - ); - }, [ - lastRefresh, - dateRangeStart, - dateRangeEnd, - esKuery, - esFilters, - statusFilter, - urlFilters, - dispatch, - ]); - - return ; -}; diff --git a/x-pack/plugins/uptime/public/components/overview/filter_group/filter_popover.test.tsx b/x-pack/plugins/uptime/public/components/overview/filter_group/filter_popover.test.tsx deleted file mode 100644 index bccebb21718bf..0000000000000 --- a/x-pack/plugins/uptime/public/components/overview/filter_group/filter_popover.test.tsx +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { fireEvent, waitFor, waitForElementToBeRemoved } from '@testing-library/react'; -import { FilterPopoverProps, FilterPopover } from './filter_popover'; -import { render } from '../../../lib/helper/rtl_helpers'; - -describe('FilterPopover component', () => { - let props: FilterPopoverProps; - - beforeEach(() => { - props = { - fieldName: 'test-fieldName', - id: 'test', - loading: false, - items: ['first', 'second', 'third', 'fourth'], - onFilterFieldChange: jest.fn(), - selectedItems: ['first', 'third'], - title: 'test-title', - }; - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it('expands on button click', () => { - const { getByRole, getByLabelText, getByText, queryByLabelText, queryByText } = render( - - ); - - const screenReaderOnlyText = 'You are in a dialog. To close this dialog, hit escape.'; - - expect(queryByText(screenReaderOnlyText)).toBeNull(); - expect(queryByLabelText('Filter by bar fourth.')).toBeNull(); - - fireEvent.click(getByRole('button')); - - expect(getByText(screenReaderOnlyText)); - expect(getByLabelText('Filter by test-title fourth.')); - }); - - it('does not show item list when loading, and displays placeholder', async () => { - props.loading = true; - const { getByRole, queryByText, getByLabelText } = render(); - - fireEvent.click(getByRole('button')); - - await waitFor(() => { - const search = getByLabelText('Search for test-title'); - expect(search).toHaveAttribute('placeholder', 'Loading...'); - }); - - expect(queryByText('Filter by test-title second.')).toBeNull(); - }); - - it.each([ - [[], ['third'], ['third']], - [['first', 'third'], ['first'], ['third']], - [['fourth'], ['first', 'second'], ['first', 'second', 'fourth']], - ])( - 'returns selected items on popover close', - async (selectedPropsItems, expectedSelections, itemsToClick) => { - if (itemsToClick.length < 1) { - throw new Error('This test assumes at least one item will be clicked'); - } - props.selectedItems = selectedPropsItems; - - const { getByLabelText, queryByLabelText } = render(); - - const uptimeFilterButton = getByLabelText(`expands filter group for ${props.title} filter`); - - fireEvent.click(uptimeFilterButton); - - const generateLabelText = (item: string) => `Filter by ${props.title} ${item}.`; - - itemsToClick.forEach((item) => { - const optionButtonLabelText = generateLabelText(item); - const optionButton = getByLabelText(optionButtonLabelText); - fireEvent.click(optionButton); - }); - - fireEvent.click(uptimeFilterButton); - - await waitForElementToBeRemoved(() => queryByLabelText(generateLabelText(itemsToClick[0]))); - - expect(props.onFilterFieldChange).toHaveBeenCalledTimes(1); - expect(props.onFilterFieldChange).toHaveBeenCalledWith(props.fieldName, expectedSelections); - } - ); -}); diff --git a/x-pack/plugins/uptime/public/components/overview/filter_group/filter_popover.tsx b/x-pack/plugins/uptime/public/components/overview/filter_group/filter_popover.tsx deleted file mode 100644 index 23e17802a6835..0000000000000 --- a/x-pack/plugins/uptime/public/components/overview/filter_group/filter_popover.tsx +++ /dev/null @@ -1,146 +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 { EuiFieldSearch, EuiFilterSelectItem, EuiPopover, EuiPopoverTitle } from '@elastic/eui'; -import React, { useState, useEffect } from 'react'; -import { i18n } from '@kbn/i18n'; -import { UptimeFilterButton } from './uptime_filter_button'; -import { toggleSelectedItems } from './toggle_selected_item'; -import { LocationLink } from '../monitor_list'; - -export interface FilterPopoverProps { - fieldName: string; - id: string; - loading: boolean; - disabled?: boolean; - items: string[]; - onFilterFieldChange: (fieldName: string, values: string[]) => void; - selectedItems: string[]; - title: string; - btnContent?: JSX.Element; - forceOpen?: boolean; - setForceOpen?: (val: boolean) => void; -} - -const isItemSelected = (selectedItems: string[], item: string): 'on' | undefined => - selectedItems.find((selected) => selected === item) ? 'on' : undefined; - -export const FilterPopover = ({ - fieldName, - id, - disabled, - loading, - items: allItems, - onFilterFieldChange, - selectedItems, - title, - btnContent, - forceOpen, - setForceOpen, -}: FilterPopoverProps) => { - const [isOpen, setIsOpen] = useState(false); - const [itemsToDisplay, setItemsToDisplay] = useState([]); - const [searchQuery, setSearchQuery] = useState(''); - const [tempSelectedItems, setTempSelectedItems] = useState(selectedItems); - - const [items, setItems] = useState([]); - - useEffect(() => { - // Merge incoming items with selected items, to enable deselection - - const mItems = selectedItems.concat(allItems ?? []); - const newItems = mItems.filter((item, index) => mItems.indexOf(item) === index); - setItems(newItems); - setTempSelectedItems(selectedItems); - }, [allItems, selectedItems]); - - useEffect(() => { - if (searchQuery !== '') { - const toDisplay = items.filter((item) => item.indexOf(searchQuery) >= 0); - setItemsToDisplay(toDisplay); - } else { - setItemsToDisplay(items); - } - }, [searchQuery, items]); - - return ( - 0} - numFilters={items.length} - numActiveFilters={isOpen ? tempSelectedItems.length : selectedItems.length} - onClick={() => { - if (isOpen) { - // only update these values on close - onFilterFieldChange(fieldName, tempSelectedItems); - } - setIsOpen(!isOpen); - }} - title={title} - /> - ) - } - closePopover={() => { - setIsOpen(false); - onFilterFieldChange(fieldName, tempSelectedItems); - if (setForceOpen) { - setForceOpen(false); - } - }} - data-test-subj={`filter-popover_${id}`} - id={id} - isOpen={isOpen || forceOpen} - ownFocus={true} - zIndex={10000} - > - - setSearchQuery(query)} - aria-label={i18n.translate('xpack.uptime.filterPopout.searchMessage.ariaLabel', { - defaultMessage: 'Search for {title}', - values: { - title: title.toLowerCase(), - }, - })} - placeholder={ - loading - ? i18n.translate('xpack.uptime.filterPopout.loadingMessage', { - defaultMessage: 'Loading...', - }) - : i18n.translate('xpack.uptime.filterPopout.searchMessage', { - defaultMessage: 'Search {title}', - values: { - title: title.toLowerCase(), - }, - }) - } - /> - - {!loading && - itemsToDisplay.map((item) => ( - toggleSelectedItems(item, tempSelectedItems, setTempSelectedItems)} - > - {item} - - ))} - {id === 'location' && items.length === 0 && } - - ); -}; diff --git a/x-pack/plugins/uptime/public/components/overview/filter_group/parse_filter_map.test.ts b/x-pack/plugins/uptime/public/components/overview/filter_group/parse_filter_map.test.ts deleted file mode 100644 index d06af65cee73d..0000000000000 --- a/x-pack/plugins/uptime/public/components/overview/filter_group/parse_filter_map.test.ts +++ /dev/null @@ -1,22 +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 { parseFiltersMap } from './parse_filter_map'; - -describe('parseFiltersMap', () => { - it('provides values from valid filter string', () => { - expect( - parseFiltersMap( - '[["url.port",["5601","80"]],["observer.geo.name",["us-east-2"]],["monitor.type",["http","tcp"]]]' - ) - ).toMatchSnapshot(); - }); - - it('returns an empty object for invalid filter', () => { - expect(() => parseFiltersMap('some invalid string')).toThrowErrorMatchingSnapshot(); - }); -}); diff --git a/x-pack/plugins/uptime/public/components/overview/filter_group/parse_filter_map.ts b/x-pack/plugins/uptime/public/components/overview/filter_group/parse_filter_map.ts deleted file mode 100644 index 5d08847b6b713..0000000000000 --- a/x-pack/plugins/uptime/public/components/overview/filter_group/parse_filter_map.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -interface FilterField { - name: string; - fieldName: string; -} - -/** - * These are the only filter fields we are looking to catch at the moment. - * If your code needs to support custom fields, introduce a second parameter to - * `parseFiltersMap` to take a list of FilterField objects. - */ -const filterAllowList: FilterField[] = [ - { name: 'ports', fieldName: 'url.port' }, - { name: 'locations', fieldName: 'observer.geo.name' }, - { name: 'tags', fieldName: 'tags' }, - { name: 'schemes', fieldName: 'monitor.type' }, -]; - -export const parseFiltersMap = (filterMapString: string) => { - if (!filterMapString) { - return {}; - } - const filterSlices: { [key: string]: any } = {}; - try { - const map = new Map(JSON.parse(filterMapString)); - filterAllowList.forEach(({ name, fieldName }) => { - filterSlices[name] = map.get(fieldName) ?? []; - }); - return filterSlices; - } catch { - throw new Error('Unable to parse invalid filter string'); - } -}; diff --git a/x-pack/plugins/uptime/public/components/overview/filter_group/selected_filters.tsx b/x-pack/plugins/uptime/public/components/overview/filter_group/selected_filters.tsx new file mode 100644 index 0000000000000..7e70673016d2c --- /dev/null +++ b/x-pack/plugins/uptime/public/components/overview/filter_group/selected_filters.tsx @@ -0,0 +1,79 @@ +/* + * 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 { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { FilterValueLabel } from '../../../../../observability/public'; +import { useIndexPattern } from '../../../contexts/uptime_index_pattern_context'; +import { useSelectedFilters } from '../../../hooks/use_selected_filters'; + +interface Props { + onChange: (fieldName: string, values: string[], notValues: string[]) => void; +} +export const SelectedFilters = ({ onChange }: Props) => { + const indexPattern = useIndexPattern(); + const { filtersList } = useSelectedFilters(); + + if (!indexPattern) return null; + + return ( + + {filtersList.map(({ field, selectedItems, excludedItems, label }) => [ + ...selectedItems.map((value) => ( + + { + onChange( + field, + selectedItems.filter((valT) => valT !== value), + excludedItems + ); + }} + invertFilter={(val) => { + onChange( + field, + selectedItems.filter((valT) => valT !== value), + [...excludedItems, value] + ); + }} + field={field} + value={value} + negate={false} + label={label} + /> + + )), + ...excludedItems.map((value) => ( + + { + onChange( + field, + selectedItems, + excludedItems.filter((valT) => valT !== value) + ); + }} + invertFilter={(val) => { + onChange( + field, + [...selectedItems, value], + excludedItems.filter((valT) => valT !== value) + ); + }} + field={field} + value={value} + negate={true} + label={label} + /> + + )), + ])} + + ); +}; diff --git a/x-pack/plugins/uptime/public/components/overview/filter_group/toggle_selected_item.test.ts b/x-pack/plugins/uptime/public/components/overview/filter_group/toggle_selected_item.test.ts deleted file mode 100644 index 0a80f2062320d..0000000000000 --- a/x-pack/plugins/uptime/public/components/overview/filter_group/toggle_selected_item.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { toggleSelectedItems } from './toggle_selected_item'; - -describe('toggleSelectedItems', () => { - it(`adds the item if it's not in the list`, () => { - const mock = jest.fn(); - toggleSelectedItems('abc', ['aba', 'abd'], mock); - expect(mock).toHaveBeenCalledTimes(1); - expect(mock).toHaveBeenCalledWith(['aba', 'abd', 'abc']); - }); - - it(`removes the item if it's already in the list`, () => { - const mock = jest.fn(); - toggleSelectedItems('abc', ['aba', 'abc', 'abd'], mock); - expect(mock).toHaveBeenCalledTimes(1); - expect(mock).toHaveBeenCalledWith(['aba', 'abd']); - }); -}); diff --git a/x-pack/plugins/uptime/public/components/overview/filter_group/toggle_selected_item.ts b/x-pack/plugins/uptime/public/components/overview/filter_group/toggle_selected_item.ts deleted file mode 100644 index 08b031f936dc5..0000000000000 --- a/x-pack/plugins/uptime/public/components/overview/filter_group/toggle_selected_item.ts +++ /dev/null @@ -1,23 +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 { Dispatch, SetStateAction } from 'react'; - -export const toggleSelectedItems = ( - item: string, - tempSelectedItems: string[], - setTempSelectedItems: Dispatch> -) => { - const index = tempSelectedItems.indexOf(item); - const nextSelectedItems = [...tempSelectedItems]; - if (index >= 0) { - nextSelectedItems.splice(index, 1); - } else { - nextSelectedItems.push(item); - } - setTempSelectedItems(nextSelectedItems); -}; diff --git a/x-pack/plugins/uptime/public/components/overview/filter_group/uptime_filter_button.tsx b/x-pack/plugins/uptime/public/components/overview/filter_group/uptime_filter_button.tsx deleted file mode 100644 index 326ad7a292455..0000000000000 --- a/x-pack/plugins/uptime/public/components/overview/filter_group/uptime_filter_button.tsx +++ /dev/null @@ -1,44 +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 { EuiFilterButton } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import React from 'react'; - -interface UptimeFilterButtonProps { - isDisabled?: boolean; - isSelected: boolean; - numFilters: number; - numActiveFilters: number; - onClick: () => void; - title: string; -} - -export const UptimeFilterButton = ({ - isDisabled, - isSelected, - numFilters, - numActiveFilters, - onClick, - title, -}: UptimeFilterButtonProps) => ( - - {title} - -); diff --git a/x-pack/plugins/uptime/public/components/overview/index.ts b/x-pack/plugins/uptime/public/components/overview/index.ts index 729db44aaa964..6ff4524df5277 100644 --- a/x-pack/plugins/uptime/public/components/overview/index.ts +++ b/x-pack/plugins/uptime/public/components/overview/index.ts @@ -6,7 +6,5 @@ */ export * from './monitor_list'; -export * from './empty_state'; -export * from './filter_group'; export * from './alerts'; export * from './snapshot'; diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/enable_alert.test.tsx b/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/enable_alert.test.tsx index 53ba1b8ed57eb..c5dff902cfb14 100644 --- a/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/enable_alert.test.tsx +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/enable_alert.test.tsx @@ -77,7 +77,7 @@ describe('EnableAlertComponent', () => { alerts: { ...mockState.alerts, alerts: { - data: ({ data: alerts } as unknown) as AlertsResult, + data: { data: alerts } as unknown as AlertsResult, loading: false, }, }, diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/monitor_name_col.tsx b/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/monitor_name_col.tsx index 7adf248d37fc5..65bbaf45b63a2 100644 --- a/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/monitor_name_col.tsx +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/monitor_name_col.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useState } from 'react'; +import React, { useState, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiButtonEmpty, EuiText } from '@elastic/eui'; import { MonitorPageLink } from '../../../common/monitor_page_link'; @@ -44,7 +44,12 @@ export const MonitorNameColumn = ({ summary }: Props) => { const [filterType, setFilterType] = useState(currFilters.get('monitor.type') ?? []); - useFilterUpdate('monitor.type', filterType); + const excludedTypeFilters = useMemo(() => { + const currExcludedFilters = parseCurrentFilters(params.excludedFilters); + return currExcludedFilters.get('monitor.type') ?? []; + }, [params.excludedFilters]); + + useFilterUpdate('monitor.type', filterType, excludedTypeFilters); const filterLabel = i18n.translate('xpack.uptime.monitorList.monitorType.filter', { defaultMessage: 'Filter all monitors with type {type}', diff --git a/x-pack/plugins/uptime/public/components/overview/query_bar/query_bar.tsx b/x-pack/plugins/uptime/public/components/overview/query_bar/query_bar.tsx index 9436f420f7740..3c8c0599efa69 100644 --- a/x-pack/plugins/uptime/public/components/overview/query_bar/query_bar.tsx +++ b/x-pack/plugins/uptime/public/components/overview/query_bar/query_bar.tsx @@ -9,10 +9,9 @@ import React, { useState } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiFlexItem } from '@elastic/eui'; import { QueryStringInput } from '../../../../../../../src/plugins/data/public/'; -import { useIndexPattern } from './use_index_pattern'; import { SyntaxType, useQueryBar } from './use_query_bar'; import { KQL_PLACE_HOLDER, SIMPLE_SEARCH_PLACEHOLDER } from './translations'; -import { useGetUrlParams } from '../../../hooks'; +import { useGetUrlParams, useIndexPattern } from '../../../hooks'; const SYNTAX_STORAGE = 'uptime:queryBarSyntax'; @@ -36,7 +35,7 @@ export const QueryBar = () => { const { query, setQuery, submitImmediately } = useQueryBar(); - const { index_pattern: indexPattern } = useIndexPattern(); + const indexPattern = useIndexPattern(); const [inputVal, setInputVal] = useState(query.query as string); diff --git a/x-pack/plugins/uptime/public/components/overview/query_bar/use_index_pattern.ts b/x-pack/plugins/uptime/public/components/overview/query_bar/use_index_pattern.ts deleted file mode 100644 index b0e567c40ed73..0000000000000 --- a/x-pack/plugins/uptime/public/components/overview/query_bar/use_index_pattern.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { useEffect } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { getIndexPattern } from '../../../state/actions'; -import { selectIndexPattern } from '../../../state/selectors'; - -export const useIndexPattern = () => { - const dispatch = useDispatch(); - const indexPattern = useSelector(selectIndexPattern); - - useEffect(() => { - // we only use index pattern for kql queries - if (!indexPattern.index_pattern) { - dispatch(getIndexPattern()); - } - }, [indexPattern.index_pattern, dispatch]); - - return indexPattern; -}; diff --git a/x-pack/plugins/uptime/public/components/overview/query_bar/use_query_bar.test.tsx b/x-pack/plugins/uptime/public/components/overview/query_bar/use_query_bar.test.tsx new file mode 100644 index 0000000000000..e4c57dab0ffcf --- /dev/null +++ b/x-pack/plugins/uptime/public/components/overview/query_bar/use_query_bar.test.tsx @@ -0,0 +1,126 @@ +/* + * 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 { waitFor } from '@testing-library/react'; +import { act, renderHook } from '@testing-library/react-hooks'; +import { MockRouter, MockKibanaProvider } from '../../../lib/helper/rtl_helpers'; +import { SyntaxType, useQueryBar, DEBOUNCE_INTERVAL } from './use_query_bar'; +import { MountWithReduxProvider } from '../../../lib'; +import * as URL from '../../../hooks/use_url_params'; +import * as ES_FILTERS from '../../../hooks/update_kuery_string'; +import { UptimeUrlParams } from '../../../lib/helper/url_params'; + +const SAMPLE_ES_FILTERS = `{"bool":{"should":[{"match_phrase":{"monitor.id":"NodeServer"}}],"minimum_should_match":1}}`; + +describe('useQueryBar', () => { + let DEFAULT_URL_PARAMS: UptimeUrlParams; + let wrapper: any; + let useUrlParamsSpy: jest.SpyInstance<[URL.GetUrlParams, URL.UpdateUrlParams]>; + let useGetUrlParamsSpy: jest.SpyInstance; + let updateUrlParamsMock: jest.Mock; + let useUpdateKueryStringSpy: jest.SpyInstance; + + beforeEach(() => { + DEFAULT_URL_PARAMS = { + absoluteDateRangeStart: 100, + absoluteDateRangeEnd: 200, + autorefreshInterval: 10000, + autorefreshIsPaused: true, + dateRangeStart: 'now-15m', + dateRangeEnd: 'now', + excludedFilters: '', + filters: '', + query: '', + search: 'monitor.id: "My-Monitor"', + statusFilter: '', + }; + wrapper = ({ children }: any) => ( + + + {children} + + + ); + useUrlParamsSpy = jest.spyOn(URL, 'useUrlParams'); + useGetUrlParamsSpy = jest.spyOn(URL, 'useGetUrlParams'); + useUpdateKueryStringSpy = jest.spyOn(ES_FILTERS, 'useUpdateKueryString'); + updateUrlParamsMock = jest.fn(); + + useUrlParamsSpy.mockImplementation(() => [jest.fn(), updateUrlParamsMock]); + useGetUrlParamsSpy.mockReturnValue(DEFAULT_URL_PARAMS); + useUpdateKueryStringSpy.mockReturnValue([SAMPLE_ES_FILTERS]); + }); + + it.each([ + [SyntaxType.text, undefined, SAMPLE_ES_FILTERS, '', 'monitor.id: "My-Other-Monitor"', false, 0], + [ + SyntaxType.kuery, + new Error('there was a problem'), + SAMPLE_ES_FILTERS, + '', + 'monitor.id: "My-Other-Monitor"', + false, + 0, + ], + [SyntaxType.kuery, undefined, undefined, '', 'monitor.id: "My-Other-Monitor"', false, 0], + [SyntaxType.text, undefined, undefined, '', 'monitor.id: "My-Other-Monitor"', false, 0], + [SyntaxType.text, undefined, undefined, 'my-search', 'monitor.id: "My-Other-Monitor"', true, 1], + [SyntaxType.kuery, undefined, undefined, 'my-search', '', true, 1], + [ + SyntaxType.kuery, + undefined, + SAMPLE_ES_FILTERS, + 'my-search', + 'monitor.id: "My-Monitor"', + true, + 1, + ], + ])( + 'updates URL only when conditions are appropriate', + /** + * This test is designed to prevent massive duplication of boilerplate; each set of parameters should trigger + * a different response from the hook. At the end, we wait for the debounce interval to elapse and then check + * whether the URL was updated. + * + * @param language the query syntax + * @param error an error resulting from parsing es filters + * @param esFilters the AST string generated from parsing kuery syntax + * @param search the simple text search + * @param query the new kuery entered by the user + * @param shouldExpectCall boolean denoting whether or not the test should expect the url to be updated + * @param calledTimes the number of times the test should expect the url to be updated + */ + async (language, error, esFilters, search, query, shouldExpectCall, calledTimes) => { + const { + result: { current }, + } = renderHook(() => useQueryBar(), { wrapper }); + + useUpdateKueryStringSpy.mockReturnValue([esFilters, error]); + useGetUrlParamsSpy.mockReturnValue({ + ...DEFAULT_URL_PARAMS, + search, + }); + + act(() => { + current.setQuery({ + query, + language, + }); + }); + + await waitFor(async () => { + await new Promise((r) => setInterval(r, DEBOUNCE_INTERVAL + 50)); + if (shouldExpectCall) { + expect(updateUrlParamsMock).toHaveBeenCalledTimes(calledTimes); + } else { + expect(updateUrlParamsMock).not.toHaveBeenCalled(); + } + }); + } + ); +}); diff --git a/x-pack/plugins/uptime/public/components/overview/query_bar/use_query_bar.ts b/x-pack/plugins/uptime/public/components/overview/query_bar/use_query_bar.ts index 2f2d8bf092ddf..b24231105770a 100644 --- a/x-pack/plugins/uptime/public/components/overview/query_bar/use_query_bar.ts +++ b/x-pack/plugins/uptime/public/components/overview/query_bar/use_query_bar.ts @@ -9,9 +9,13 @@ import React, { useCallback, useState } from 'react'; import useDebounce from 'react-use/lib/useDebounce'; import { useDispatch } from 'react-redux'; import { Query } from 'src/plugins/data/common'; -import { useGetUrlParams, useUpdateKueryString, useUrlParams } from '../../../hooks'; +import { + useGetUrlParams, + useIndexPattern, + useUpdateKueryString, + useUrlParams, +} from '../../../hooks'; import { setEsKueryString } from '../../../state/actions'; -import { useIndexPattern } from './use_index_pattern'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; import { UptimePluginServices } from '../../../apps/plugin'; @@ -35,6 +39,8 @@ interface UseQueryBarUtils { submitImmediately: () => void; } +export const DEBOUNCE_INTERVAL = 250; + /** * Provides state management and automatic dispatching of a Query object. * @@ -44,7 +50,7 @@ export const useQueryBar = (): UseQueryBarUtils => { const dispatch = useDispatch(); const { absoluteDateRangeStart, absoluteDateRangeEnd, ...params } = useGetUrlParams(); - const { search, query: queryParam, filters: paramFilters } = params; + const { search, query: queryParam, filters: paramFilters, excludedFilters } = params; const { services: { storage }, @@ -64,14 +70,15 @@ export const useQueryBar = (): UseQueryBarUtils => { } ); - const { index_pattern: indexPattern } = useIndexPattern(); + const indexPattern = useIndexPattern(); - const updateUrlParams = useUrlParams()[1]; + const [, updateUrlParams] = useUrlParams(); const [esFilters, error] = useUpdateKueryString( indexPattern, query.language === SyntaxType.kuery ? (query.query as string) : undefined, - paramFilters + paramFilters, + excludedFilters ); const setEsKueryFilters = useCallback( @@ -79,10 +86,10 @@ export const useQueryBar = (): UseQueryBarUtils => { [dispatch] ); - const setEs = useCallback(() => setEsKueryFilters(esFilters ?? ''), [ - esFilters, - setEsKueryFilters, - ]); + const setEs = useCallback( + () => setEsKueryFilters(esFilters ?? ''), + [esFilters, setEsKueryFilters] + ); const [, cancelEsKueryUpdate] = useDebounce(setEs, DEFAULT_QUERY_UPDATE_DEBOUNCE_INTERVAL, [ esFilters, setEsKueryFilters, @@ -92,7 +99,7 @@ export const useQueryBar = (): UseQueryBarUtils => { if (query.language === SyntaxType.text && queryParam !== query.query) { updateUrlParams({ query: query.query as string }); } - if (query.language === SyntaxType.kuery) { + if (query.language === SyntaxType.kuery && queryParam !== '') { updateUrlParams({ query: '' }); } }, [query.language, query.query, queryParam, updateUrlParams]); @@ -112,17 +119,18 @@ export const useQueryBar = (): UseQueryBarUtils => { useDebounce( () => { - if (query.language === SyntaxType.kuery && !error && esFilters) { + if (query.language === SyntaxType.kuery && !error && esFilters && search !== query.query) { updateUrlParams({ search: query.query as string }); } - if (query.language === SyntaxType.text) { + if (query.language === SyntaxType.text && search !== '') { updateUrlParams({ search: '' }); } - if (query.language === SyntaxType.kuery && query.query === '') { + // this calls when it probably doesn't need to + if (query.language === SyntaxType.kuery && query.query === '' && search !== '') { updateUrlParams({ search: '' }); } }, - 250, + DEBOUNCE_INTERVAL, [esFilters, error] ); diff --git a/x-pack/plugins/uptime/public/contexts/uptime_index_pattern_context.tsx b/x-pack/plugins/uptime/public/contexts/uptime_index_pattern_context.tsx new file mode 100644 index 0000000000000..580160bac4012 --- /dev/null +++ b/x-pack/plugins/uptime/public/contexts/uptime_index_pattern_context.tsx @@ -0,0 +1,42 @@ +/* + * 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, { createContext, useContext, useEffect } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { useFetcher } from '../../../observability/public'; +import { DataPublicPluginStart, IndexPattern } from '../../../../../src/plugins/data/public'; +import { selectDynamicSettings } from '../state/selectors'; +import { getDynamicSettings } from '../state/actions/dynamic_settings'; + +export const UptimeIndexPatternContext = createContext({} as IndexPattern); + +export const UptimeIndexPatternContextProvider: React.FC<{ data: DataPublicPluginStart }> = ({ + children, + data: { indexPatterns }, +}) => { + const { settings } = useSelector(selectDynamicSettings); + const dispatch = useDispatch(); + + useEffect(() => { + if (typeof settings === 'undefined') { + dispatch(getDynamicSettings()); + } + }, [dispatch, settings]); + + const heartbeatIndices = settings?.heartbeatIndices || ''; + + const { data } = useFetcher>(async () => { + if (heartbeatIndices) { + // this only creates an index pattern in memory, not as saved object + return indexPatterns.create({ title: heartbeatIndices }); + } + }, [heartbeatIndices]); + + return ; +}; + +export const useIndexPattern = () => useContext(UptimeIndexPatternContext); diff --git a/x-pack/plugins/uptime/public/hooks/__snapshots__/use_url_params.test.tsx.snap b/x-pack/plugins/uptime/public/hooks/__snapshots__/use_url_params.test.tsx.snap index 0b8893e36a366..d8b148675dc62 100644 --- a/x-pack/plugins/uptime/public/hooks/__snapshots__/use_url_params.test.tsx.snap +++ b/x-pack/plugins/uptime/public/hooks/__snapshots__/use_url_params.test.tsx.snap @@ -197,6 +197,7 @@ exports[`useUrlParams deletes keys that do not have truthy values 1`] = ` }, ], }, + Symbol(observable): [MockFunction], } } > @@ -209,7 +210,7 @@ exports[`useUrlParams deletes keys that do not have truthy values 1`] = ` } >
- {"pagination":"foo","absoluteDateRangeStart":20,"absoluteDateRangeEnd":20,"autorefreshInterval":60000,"autorefreshIsPaused":false,"dateRangeStart":"now-12","dateRangeEnd":"now","filters":"","search":"","statusFilter":"","focusConnectorField":false,"query":""} + {"pagination":"foo","absoluteDateRangeStart":20,"absoluteDateRangeEnd":20,"autorefreshInterval":60000,"autorefreshIsPaused":false,"dateRangeStart":"now-12","dateRangeEnd":"now","filters":"","excludedFilters":"","search":"","statusFilter":"","focusConnectorField":false,"query":""}