From 1ede10ccbcdaa763a280c78e187469dc6b694c1f Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Thu, 12 Mar 2020 15:55:41 +0100 Subject: [PATCH 01/19] [ML] Transforms: Replace KqlFilterBar with QueryStringInput. (#59723) - Replaces the custom KqlFilterBar with Kibana's QueryStringInput. This means the wizard now supports both lucene and kuery input. - Using this component we no longer need to do cross-imports from the ML plugin. The use of setDependencyCache is no longer necessary. - Replaces the custom AppDependencies provider code with Kibana's KibanaContextProvider. --- .../public/__mocks__/shared_imports.ts | 4 - .../app_dependencies.tsx} | 20 +-- x-pack/plugins/transform/public/app/app.tsx | 19 ++- .../transform/public/app/app_dependencies.tsx | 54 +------- .../public/app/common/request.test.ts | 1 + .../pivot_preview/pivot_preview.test.tsx | 9 +- .../toast_notification_text.test.tsx | 16 +-- .../source_index_preview.test.tsx | 9 +- .../step_create/step_create_form.test.tsx | 10 +- .../step_define/step_define_form.test.tsx | 45 ++++++- .../step_define/step_define_form.tsx | 116 ++++++++++++------ .../step_define/step_define_summary.test.tsx | 8 +- .../step_define/step_define_summary.tsx | 85 +++++-------- .../components/step_define/switch_modal.tsx | 4 +- .../transform_list/action_delete.test.tsx | 13 +- .../transform_list/action_start.test.tsx | 12 +- .../transform_list/action_stop.test.tsx | 12 +- x-pack/plugins/transform/public/plugin.ts | 5 + .../transform/public/shared_imports.ts | 14 --- .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - .../services/transform_ui/wizard.ts | 6 +- 22 files changed, 212 insertions(+), 252 deletions(-) rename x-pack/plugins/transform/public/app/{app_dependencies.mock.ts => __mocks__/app_dependencies.tsx} (55%) diff --git a/x-pack/plugins/transform/public/__mocks__/shared_imports.ts b/x-pack/plugins/transform/public/__mocks__/shared_imports.ts index bc8ace2932c0e6..62ddc13566be86 100644 --- a/x-pack/plugins/transform/public/__mocks__/shared_imports.ts +++ b/x-pack/plugins/transform/public/__mocks__/shared_imports.ts @@ -4,11 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -jest.mock('ui/new_platform'); - export const expandLiteralStrings = jest.fn(); export const XJsonMode = jest.fn(); -export const setDependencyCache = jest.fn(); export const useRequest = jest.fn(() => ({ isLoading: false, error: null, @@ -16,4 +13,3 @@ export const useRequest = jest.fn(() => ({ })); export { mlInMemoryTableBasicFactory } from '../../../../legacy/plugins/ml/public/application/components/ml_in_memory_table'; export const SORT_DIRECTION = { ASC: 'asc' }; -export const KqlFilterBar = jest.fn(() => null); diff --git a/x-pack/plugins/transform/public/app/app_dependencies.mock.ts b/x-pack/plugins/transform/public/app/__mocks__/app_dependencies.tsx similarity index 55% rename from x-pack/plugins/transform/public/app/app_dependencies.mock.ts rename to x-pack/plugins/transform/public/app/__mocks__/app_dependencies.tsx index 4e5af1eca7bd02..75fefc99b54580 100644 --- a/x-pack/plugins/transform/public/app/app_dependencies.mock.ts +++ b/x-pack/plugins/transform/public/app/__mocks__/app_dependencies.tsx @@ -4,25 +4,31 @@ * you may not use this file except in compliance with the Elastic License. */ -import { coreMock } from '../../../../../src/core/public/mocks'; -import { dataPluginMock } from '../../../../../src/plugins/data/public/mocks'; - -import { getAppProviders, AppDependencies } from './app_dependencies'; +import { coreMock } from '../../../../../../src/core/public/mocks'; +import { dataPluginMock } from '../../../../../../src/plugins/data/public/mocks'; +import { Storage } from '../../../../../../src/plugins/kibana_utils/public'; const coreSetup = coreMock.createSetup(); const coreStart = coreMock.createStart(); const dataStart = dataPluginMock.createStartContract(); -const appDependencies: AppDependencies = { +const appDependencies = { chrome: coreStart.chrome, data: dataStart, docLinks: coreStart.docLinks, i18n: coreStart.i18n, - notifications: coreStart.notifications, + notifications: coreSetup.notifications, uiSettings: coreStart.uiSettings, savedObjects: coreStart.savedObjects, + storage: ({ get: jest.fn() } as unknown) as Storage, overlays: coreStart.overlays, http: coreSetup.http, }; -export const Providers = getAppProviders(appDependencies); +export const useAppDependencies = () => { + return appDependencies; +}; + +export const useToastNotifications = () => { + return coreSetup.notifications; +}; diff --git a/x-pack/plugins/transform/public/app/app.tsx b/x-pack/plugins/transform/public/app/app.tsx index 644aedec3eac08..01ff7f5bff27f7 100644 --- a/x-pack/plugins/transform/public/app/app.tsx +++ b/x-pack/plugins/transform/public/app/app.tsx @@ -10,10 +10,13 @@ import { HashRouter, Redirect, Route, Switch } from 'react-router-dom'; import { FormattedMessage } from '@kbn/i18n/react'; +import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public'; + +import { API_BASE_PATH } from '../../common/constants'; + import { SectionError } from './components'; import { CLIENT_BASE_PATH, SECTION_SLUG } from './constants'; -import { getAppProviders } from './app_dependencies'; -import { AuthorizationContext } from './lib/authorization'; +import { AuthorizationContext, AuthorizationProvider } from './lib/authorization'; import { AppDependencies } from './app_dependencies'; import { CloneTransformSection } from './sections/clone_transform'; @@ -61,12 +64,16 @@ export const App: FC = () => { }; export const renderApp = (element: HTMLElement, appDependencies: AppDependencies) => { - const Providers = getAppProviders(appDependencies); + const I18nContext = appDependencies.i18n.Context; render( - - - , + + + + + + + , element ); diff --git a/x-pack/plugins/transform/public/app/app_dependencies.tsx b/x-pack/plugins/transform/public/app/app_dependencies.tsx index 37258dc777d87d..87db02988adf0b 100644 --- a/x-pack/plugins/transform/public/app/app_dependencies.tsx +++ b/x-pack/plugins/transform/public/app/app_dependencies.tsx @@ -4,17 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { createContext, useContext, ReactNode } from 'react'; -import { HashRouter } from 'react-router-dom'; - import { CoreSetup, CoreStart } from 'src/core/public'; import { DataPublicPluginStart } from 'src/plugins/data/public'; -import { API_BASE_PATH } from '../../common/constants'; - -import { setDependencyCache } from '../shared_imports'; - -import { AuthorizationProvider } from './lib/authorization'; +import { useKibana } from '../../../../../src/plugins/kibana_react/public'; +import { Storage } from '../../../../../src/plugins/kibana_utils/public'; export interface AppDependencies { chrome: CoreStart['chrome']; @@ -22,36 +16,15 @@ export interface AppDependencies { docLinks: CoreStart['docLinks']; http: CoreSetup['http']; i18n: CoreStart['i18n']; - notifications: CoreStart['notifications']; + notifications: CoreSetup['notifications']; uiSettings: CoreStart['uiSettings']; savedObjects: CoreStart['savedObjects']; + storage: Storage; overlays: CoreStart['overlays']; } -let DependenciesContext: React.Context; - -const setAppDependencies = (deps: AppDependencies) => { - const legacyBasePath = { - prepend: deps.http.basePath.prepend, - get: deps.http.basePath.get, - remove: () => {}, - }; - - setDependencyCache({ - autocomplete: deps.data.autocomplete, - docLinks: deps.docLinks, - basePath: legacyBasePath as any, - }); - DependenciesContext = createContext(deps); - return DependenciesContext.Provider; -}; - export const useAppDependencies = () => { - if (!DependenciesContext) { - throw new Error(`The app dependencies Context hasn't been set. - Use the "setAppDependencies()" method when bootstrapping the app.`); - } - return useContext(DependenciesContext); + return useKibana().services as AppDependencies; }; export const useToastNotifications = () => { @@ -60,20 +33,3 @@ export const useToastNotifications = () => { } = useAppDependencies(); return toastNotifications; }; - -export const getAppProviders = (deps: AppDependencies) => { - const I18nContext = deps.i18n.Context; - - // Create App dependencies context and get its provider - const AppDependenciesProvider = setAppDependencies(deps); - - return ({ children }: { children: ReactNode }) => ( - - - - {children} - - - - ); -}; 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 0720c654d5029e..4c3fba3bbf8dd3 100644 --- a/x-pack/plugins/transform/public/app/common/request.test.ts +++ b/x-pack/plugins/transform/public/app/common/request.test.ts @@ -143,6 +143,7 @@ describe('Transform: Common', () => { isAdvancedPivotEditorEnabled: false, isAdvancedSourceEditorEnabled: false, sourceConfigUpdated: false, + searchLanguage: 'kuery', searchString: 'the-query', searchQuery: 'the-search-query', valid: true, diff --git a/x-pack/plugins/transform/public/app/components/pivot_preview/pivot_preview.test.tsx b/x-pack/plugins/transform/public/app/components/pivot_preview/pivot_preview.test.tsx index b37cdbb132babd..5ed50eaab46ba7 100644 --- a/x-pack/plugins/transform/public/app/components/pivot_preview/pivot_preview.test.tsx +++ b/x-pack/plugins/transform/public/app/components/pivot_preview/pivot_preview.test.tsx @@ -8,7 +8,6 @@ import React from 'react'; import { render, wait } from '@testing-library/react'; import '@testing-library/jest-dom/extend-expect'; -import { Providers } from '../../app_dependencies.mock'; import { getPivotQuery, PivotAggsConfig, @@ -19,8 +18,8 @@ import { import { PivotPreview } from './pivot_preview'; -jest.mock('ui/new_platform'); jest.mock('../../../shared_imports'); +jest.mock('../../../app/app_dependencies'); describe('Transform: ', () => { // Using the async/await wait()/done() pattern to avoid act() errors. @@ -45,11 +44,7 @@ describe('Transform: ', () => { query: getPivotQuery('the-query'), }; - const { getByText } = render( - - - - ); + const { getByText } = render(); // Act // Assert 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 5b8721cb0fe8c0..e51119d67d5678 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 @@ -7,23 +7,17 @@ import React from 'react'; import { render } from '@testing-library/react'; -import { Providers } from '../app_dependencies.mock'; - import { ToastNotificationText } from './toast_notification_text'; jest.mock('../../shared_imports'); -jest.mock('ui/new_platform'); +jest.mock('../../app/app_dependencies'); describe('ToastNotificationText', () => { test('should render the text as plain text', () => { const props = { text: 'a short text message', }; - const { container } = render( - - - - ); + const { container } = render(); expect(container.textContent).toBe('a short text message'); }); @@ -32,11 +26,7 @@ describe('ToastNotificationText', () => { 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( - - - - ); + const { container } = render(); expect(container.textContent).toBe( '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 ...View details' ); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/source_index_preview.test.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/source_index_preview.test.tsx index 7a1532705916f2..32f6ff9490a0f4 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/source_index_preview.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/source_index_preview.test.tsx @@ -8,14 +8,13 @@ import React from 'react'; import { render, wait } from '@testing-library/react'; import '@testing-library/jest-dom/extend-expect'; -import { Providers } from '../../../../app_dependencies.mock'; import { getPivotQuery } from '../../../../common'; import { SearchItems } from '../../../../hooks/use_search_items'; import { SourceIndexPreview } from './source_index_preview'; -jest.mock('ui/new_platform'); jest.mock('../../../../../shared_imports'); +jest.mock('../../../../../app/app_dependencies'); describe('Transform: ', () => { // Using the async/await wait()/done() pattern to avoid act() errors. @@ -28,11 +27,7 @@ describe('Transform: ', () => { } as SearchItems['indexPattern'], query: getPivotQuery('the-query'), }; - const { getByText } = render( - - - - ); + const { getByText } = render(); // Act // Assert diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.test.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.test.tsx index 6223dfc5623b7b..f2837d30402def 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.test.tsx @@ -8,12 +8,10 @@ import React from 'react'; import { render } from '@testing-library/react'; import '@testing-library/jest-dom/extend-expect'; -import { Providers } from '../../../../app_dependencies.mock'; - import { StepCreateForm } from './step_create_form'; -jest.mock('ui/new_platform'); jest.mock('../../../../../shared_imports'); +jest.mock('../../../../../app/app_dependencies'); describe('Transform: ', () => { test('Minimal initialization', () => { @@ -26,11 +24,7 @@ describe('Transform: ', () => { onChange() {}, }; - const { getByText } = render( - - - - ); + const { getByText } = render(); // Act // Assert diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.test.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.test.tsx index d5cffad1668311..a15e958c16b73e 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.test.tsx @@ -8,7 +8,14 @@ import React from 'react'; import { render, wait } from '@testing-library/react'; import '@testing-library/jest-dom/extend-expect'; -import { Providers } from '../../../../app_dependencies.mock'; +import { I18nProvider } from '@kbn/i18n/react'; + +import { KibanaContextProvider } from '../../../../../../../../../src/plugins/kibana_react/public'; + +import { coreMock } from '../../../../../../../../../src/core/public/mocks'; +import { dataPluginMock } from '../../../../../../../../../src/plugins/data/public/mocks'; +const startMock = coreMock.createStart(); + import { PivotAggsConfigDict, PivotGroupByConfigDict, @@ -19,8 +26,25 @@ import { SearchItems } from '../../../../hooks/use_search_items'; import { StepDefineForm, getAggNameConflictToastMessages } from './step_define_form'; -jest.mock('ui/new_platform'); jest.mock('../../../../../shared_imports'); +jest.mock('../../../../../app/app_dependencies'); + +const createMockWebStorage = () => ({ + clear: jest.fn(), + getItem: jest.fn(), + key: jest.fn(), + removeItem: jest.fn(), + setItem: jest.fn(), + length: 0, +}); + +const createMockStorage = () => ({ + storage: createMockWebStorage(), + get: jest.fn(), + set: jest.fn(), + remove: jest.fn(), + clear: jest.fn(), +}); describe('Transform: ', () => { // Using the async/await wait()/done() pattern to avoid act() errors. @@ -32,10 +56,21 @@ describe('Transform: ', () => { fields: [] as any[], } as SearchItems['indexPattern'], }; + + // mock services for QueryStringInput + const services = { + ...startMock, + data: dataPluginMock.createStartContract(), + appName: 'the-test-app', + storage: createMockStorage(), + }; + const { getByLabelText } = render( - - - + + + + + ); // Act diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx index 5b6283fc4777d9..1f9a60fa869b73 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx @@ -20,13 +20,19 @@ import { EuiHorizontalRule, EuiLink, EuiPanel, - // @ts-ignore - EuiSearchBar, EuiSpacer, EuiSwitch, } from '@elastic/eui'; +import { + esKuery, + esQuery, + Query, + QueryStringInput, +} from '../../../../../../../../../src/plugins/data/public'; + import { PivotPreview } from '../../../../components/pivot_preview'; + import { useDocumentationLinks } from '../../../../hooks/use_documentation_links'; import { SavedSearchQuery, SearchItems } from '../../../../hooks/use_search_items'; import { useXJsonMode, xJsonMode } from '../../../../hooks/use_x_json_mode'; @@ -37,13 +43,11 @@ import { DropDown } from '../aggregation_dropdown'; import { AggListForm } from '../aggregation_list'; import { GroupByListForm } from '../group_by_list'; import { SourceIndexPreview } from '../source_index_preview'; -import { KqlFilterBar } from '../../../../../shared_imports'; import { SwitchModal } from './switch_modal'; import { getPivotQuery, getPreviewRequestBody, - isMatchAllQuery, matchAllQuery, AggName, DropDownLabel, @@ -65,14 +69,18 @@ export interface StepDefineExposedState { groupByList: PivotGroupByConfigDict; isAdvancedPivotEditorEnabled: boolean; isAdvancedSourceEditorEnabled: boolean; - searchString: string | SavedSearchQuery; + searchLanguage: QUERY_LANGUAGE; + searchString: string | undefined; searchQuery: string | SavedSearchQuery; sourceConfigUpdated: boolean; valid: boolean; } const defaultSearch = '*'; -const emptySearch = ''; + +const QUERY_LANGUAGE_KUERY = 'kuery'; +const QUERY_LANGUAGE_LUCENE = 'lucene'; +type QUERY_LANGUAGE = 'kuery' | 'lucene'; export function getDefaultStepDefineState(searchItems: SearchItems): StepDefineExposedState { return { @@ -80,7 +88,8 @@ export function getDefaultStepDefineState(searchItems: SearchItems): StepDefineE groupByList: {} as PivotGroupByConfigDict, isAdvancedPivotEditorEnabled: false, isAdvancedSourceEditorEnabled: false, - searchString: searchItems.savedSearch !== undefined ? searchItems.combinedQuery : defaultSearch, + searchLanguage: QUERY_LANGUAGE_KUERY, + searchString: undefined, searchQuery: searchItems.savedSearch !== undefined ? searchItems.combinedQuery : defaultSearch, sourceConfigUpdated: false, valid: false, @@ -126,7 +135,6 @@ export function applyTransformConfigToDefineState( const query = transformConfig.source.query; if (query !== undefined && !isEqual(query, matchAllQuery)) { state.isAdvancedSourceEditorEnabled = true; - state.searchString = ''; state.searchQuery = query; state.sourceConfigUpdated = true; } @@ -243,24 +251,45 @@ export const StepDefineForm: FC = React.memo(({ overrides = {}, onChange, const defaults = { ...getDefaultStepDefineState(searchItems), ...overrides }; - // The search filter - const [searchString, setSearchString] = useState(defaults.searchString); + // The internal state of the input query bar updated on every key stroke. + const [searchInput, setSearchInput] = useState({ + query: defaults.searchString || '', + language: defaults.searchLanguage, + }); + + // The state of the input query bar updated on every submit and to be exposed. + const [searchLanguage, setSearchLanguage] = useState( + defaults.searchLanguage + ); + const [searchString, setSearchString] = useState( + defaults.searchString + ); const [searchQuery, setSearchQuery] = useState(defaults.searchQuery); - const [useKQL] = useState(true); - - const searchHandler = (d: Record) => { - const { filterQuery, queryString } = d; - const newSearch = queryString === emptySearch ? defaultSearch : queryString; - const newSearchQuery = isMatchAllQuery(filterQuery) ? defaultSearch : filterQuery; - setSearchString(newSearch); - setSearchQuery(newSearchQuery); + + const { indexPattern } = searchItems; + + const searchChangeHandler = (query: Query) => setSearchInput(query); + const searchSubmitHandler = (query: Query) => { + setSearchLanguage(query.language as QUERY_LANGUAGE); + setSearchString(query.query !== '' ? (query.query as string) : undefined); + switch (query.language) { + case QUERY_LANGUAGE_KUERY: + setSearchQuery( + esKuery.toElasticsearchQuery( + esKuery.fromKueryExpression(query.query as string), + indexPattern + ) + ); + return; + case QUERY_LANGUAGE_LUCENE: + setSearchQuery(esQuery.luceneStringToDsl(query.query as string)); + return; + } }; // The list of selected group by fields const [groupByList, setGroupByList] = useState(defaults.groupByList); - const { indexPattern } = searchItems; - const { groupByOptions, groupByOptionsData, @@ -349,7 +378,7 @@ export const StepDefineForm: FC = React.memo(({ overrides = {}, onChange, const pivotAggsArr = dictionaryToArray(aggList); const pivotGroupByArr = dictionaryToArray(groupByList); - const pivotQuery = useKQL ? getPivotQuery(searchQuery) : getPivotQuery(searchString); + const pivotQuery = getPivotQuery(searchQuery); // Advanced editor for pivot config state const [isAdvancedEditorSwitchModalVisible, setAdvancedEditorSwitchModalVisible] = useState(false); @@ -409,8 +438,6 @@ export const StepDefineForm: FC = React.memo(({ overrides = {}, onChange, const applyAdvancedSourceEditorChanges = () => { const sourceConfig = JSON.parse(advancedEditorSourceConfig); const prettySourceConfig = JSON.stringify(sourceConfig, null, 2); - // Switched to editor so we clear out the search string as the bar won't be visible - setSearchString(emptySearch); setSearchQuery(sourceConfig); setSourceConfigUpdated(true); setAdvancedEditorSourceConfig(prettySourceConfig); @@ -471,7 +498,6 @@ export const StepDefineForm: FC = React.memo(({ overrides = {}, onChange, const toggleAdvancedSourceEditor = (reset = false) => { if (reset === true) { setSearchQuery(defaultSearch); - setSearchString(defaultSearch); setSourceConfigUpdated(false); } if (isAdvancedSourceEditorEnabled === false) { @@ -532,6 +558,7 @@ export const StepDefineForm: FC = React.memo(({ overrides = {}, onChange, groupByList, isAdvancedPivotEditorEnabled, isAdvancedSourceEditorEnabled, + searchLanguage, searchString, searchQuery, sourceConfigUpdated, @@ -544,6 +571,7 @@ export const StepDefineForm: FC = React.memo(({ overrides = {}, onChange, JSON.stringify(pivotGroupByArr), isAdvancedPivotEditorEnabled, isAdvancedSourceEditorEnabled, + searchLanguage, searchString, searchQuery, valid, @@ -560,7 +588,7 @@ export const StepDefineForm: FC = React.memo(({ overrides = {}, onChange,
- {searchItems.savedSearch === undefined && typeof searchString === 'string' && ( + {searchItems.savedSearch === undefined && ( = React.memo(({ overrides = {}, onChange, defaultMessage: 'Use a query to filter the source data (optional).', })} > - )} diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.test.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.test.tsx index 36a662e0cb7e64..f2e5d30b0601fd 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.test.tsx @@ -8,7 +8,6 @@ import React from 'react'; import { render, wait } from '@testing-library/react'; import '@testing-library/jest-dom/extend-expect'; -import { Providers } from '../../../../app_dependencies.mock'; import { PivotAggsConfig, PivotGroupByConfig, @@ -20,8 +19,8 @@ import { SearchItems } from '../../../../hooks/use_search_items'; import { StepDefineExposedState } from './step_define_form'; import { StepDefineSummary } from './step_define_summary'; -jest.mock('ui/new_platform'); jest.mock('../../../../../shared_imports'); +jest.mock('../../../../../app/app_dependencies'); describe('Transform: ', () => { // Using the async/await wait()/done() pattern to avoid act() errors. @@ -51,15 +50,14 @@ describe('Transform: ', () => { isAdvancedPivotEditorEnabled: false, isAdvancedSourceEditorEnabled: false, sourceConfigUpdated: false, + searchLanguage: 'kuery', searchString: 'the-query', searchQuery: 'the-search-query', valid: true, }; const { getByText } = render( - - - + ); // Act diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.tsx index 00948109c811de..54cc1e8c071faf 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.tsx @@ -26,9 +26,6 @@ import { GroupByListSummary } from '../group_by_list'; import { StepDefineExposedState } from './step_define_form'; -const defaultSearch = '*'; -const emptySearch = ''; - interface Props { formState: StepDefineExposedState; searchItems: SearchItems; @@ -39,66 +36,50 @@ export const StepDefineSummary: FC = ({ searchItems, }) => { const pivotQuery = getPivotQuery(searchQuery); - let useCodeBlock = false; - let displaySearch; - // searchString set to empty once source config editor used - display query instead - if (searchString === emptySearch) { - displaySearch = JSON.stringify(searchQuery, null, 2); - useCodeBlock = true; - } else if (searchString === defaultSearch) { - displaySearch = emptySearch; - } else { - displaySearch = searchString; - } return (
- {searchItems.savedSearch !== undefined && - searchItems.savedSearch.id === undefined && - typeof searchString === 'string' && ( - + {searchItems.savedSearch === undefined && ( + + + {searchItems.indexPattern.title} + + {typeof searchString === 'string' && ( - {searchItems.indexPattern.title} + {searchString} - {useCodeBlock === false && displaySearch !== emptySearch && ( - - {displaySearch} - - )} - {useCodeBlock === true && displaySearch !== emptySearch && ( - + - - {displaySearch} - - - )} - - )} + {JSON.stringify(searchQuery, null, 2)} + + + )} + + )} {searchItems.savedSearch !== undefined && searchItems.savedSearch.id !== undefined && ( ', () => { test('Minimal initialization', () => { @@ -26,14 +24,7 @@ describe('Transform: Transform List Actions ', () => { deleteTransform(d: TransformListRow) {}, }; - const wrapper = shallow( - - - - ) - .find(DeleteAction) - .shallow(); - + const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/action_start.test.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/action_start.test.tsx index bbdfdbbc3c121f..2de115236c4dc0 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/action_start.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/action_start.test.tsx @@ -7,15 +7,13 @@ import { shallow } from 'enzyme'; import React from 'react'; -import { Providers } from '../../../../app_dependencies.mock'; - import { TransformListRow } from '../../../../common'; import { StartAction } from './action_start'; import transformListRow from '../../../../common/__mocks__/transform_list_row.json'; -jest.mock('ui/new_platform'); jest.mock('../../../../../shared_imports'); +jest.mock('../../../../../app/app_dependencies'); describe('Transform: Transform List Actions ', () => { test('Minimal initialization', () => { @@ -26,13 +24,7 @@ describe('Transform: Transform List Actions ', () => { startTransform(d: TransformListRow) {}, }; - const wrapper = shallow( - - - - ) - .find(StartAction) - .shallow(); + const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); }); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/action_stop.test.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/action_stop.test.tsx index 840fbc4b9034b0..a97097d9098481 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/action_stop.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/action_stop.test.tsx @@ -7,15 +7,13 @@ import { shallow } from 'enzyme'; import React from 'react'; -import { Providers } from '../../../../app_dependencies.mock'; - import { TransformListRow } from '../../../../common'; import { StopAction } from './action_stop'; import transformListRow from '../../../../common/__mocks__/transform_list_row.json'; -jest.mock('ui/new_platform'); jest.mock('../../../../../shared_imports'); +jest.mock('../../../../../app/app_dependencies'); describe('Transform: Transform List Actions ', () => { test('Minimal initialization', () => { @@ -26,13 +24,7 @@ describe('Transform: Transform List Actions ', () => { stopTransform(d: TransformListRow) {}, }; - const wrapper = shallow( - - - - ) - .find(StopAction) - .shallow(); + const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); }); diff --git a/x-pack/plugins/transform/public/plugin.ts b/x-pack/plugins/transform/public/plugin.ts index 1a34e7a6413657..9a83f5b0e05f33 100644 --- a/x-pack/plugins/transform/public/plugin.ts +++ b/x-pack/plugins/transform/public/plugin.ts @@ -9,12 +9,16 @@ import { CoreSetup } from 'src/core/public'; import { DataPublicPluginStart } from 'src/plugins/data/public'; import { ManagementSetup } from 'src/plugins/management/public'; +import { Storage } from '../../../../src/plugins/kibana_utils/public'; + import { renderApp } from './app/app'; import { AppDependencies } from './app/app_dependencies'; import { breadcrumbService } from './app/services/navigation'; import { docTitleService } from './app/services/navigation'; import { textService } from './app/services/text'; +const localStorage = new Storage(window.localStorage); + export interface PluginsDependencies { data: DataPublicPluginStart; management: ManagementSetup; @@ -56,6 +60,7 @@ export class TransformUiPlugin { notifications, overlays, savedObjects, + storage: localStorage, uiSettings, }; diff --git a/x-pack/plugins/transform/public/shared_imports.ts b/x-pack/plugins/transform/public/shared_imports.ts index 4def1bc98ef8cf..938ec77344d8f6 100644 --- a/x-pack/plugins/transform/public/shared_imports.ts +++ b/x-pack/plugins/transform/public/shared_imports.ts @@ -12,20 +12,6 @@ export { } from '../../../../src/plugins/es_ui_shared/console_lang/lib'; export { - SendRequestConfig, - SendRequestResponse, UseRequestConfig, - sendRequest, useRequest, } from '../../../../src/plugins/es_ui_shared/public/request/np_ready_request'; - -export { - CronEditor, - DAY, -} from '../../../../src/plugins/es_ui_shared/public/components/cron_editor'; - -// Needs to be imported because we're reusing KqlFilterBar which depends on it. -export { setDependencyCache } from '../../../legacy/plugins/ml/public/application/util/dependency_cache'; - -// @ts-ignore: could not find declaration file for module -export { KqlFilterBar } from '../../../legacy/plugins/ml/public/application/components/kql_filter_bar'; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index e1627bc273db92..4ea5d6708b8cd5 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -12428,7 +12428,6 @@ "xpack.transform.stepDefineForm.nestedGroupByListConflictErrorMessage": "「{groupByListName}」とネスティングの矛盾があるため、構成「{aggName}」を追加できませんでした。", "xpack.transform.stepDefineForm.queryHelpText": "クエリ文字列でソースデータをフィルタリングしてください (オプション)。", "xpack.transform.stepDefineForm.queryLabel": "クエリ", - "xpack.transform.stepDefineForm.queryPlaceholder": "例: {example}.", "xpack.transform.stepDefineForm.savedSearchLabel": "保存検索", "xpack.transform.stepDefineSummary.aggregationsLabel": "アグリゲーション(集計)", "xpack.transform.stepDefineSummary.groupByLabel": "グループ分けの条件", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 65f67d5be5e563..7dd4a876d580d3 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -12428,7 +12428,6 @@ "xpack.transform.stepDefineForm.nestedGroupByListConflictErrorMessage": "无法添加配置“{aggName}”,因为与“{groupByListName}”有嵌套冲突。", "xpack.transform.stepDefineForm.queryHelpText": "使用查询字符串筛选源数据(可选)。", "xpack.transform.stepDefineForm.queryLabel": "查询", - "xpack.transform.stepDefineForm.queryPlaceholder": "例如,{example}", "xpack.transform.stepDefineForm.savedSearchLabel": "已保存搜索", "xpack.transform.stepDefineSummary.aggregationsLabel": "聚合", "xpack.transform.stepDefineSummary.groupByLabel": "分组依据", diff --git a/x-pack/test/functional/services/transform_ui/wizard.ts b/x-pack/test/functional/services/transform_ui/wizard.ts index 2d20f3617cf06a..ba9096a372d9a4 100644 --- a/x-pack/test/functional/services/transform_ui/wizard.ts +++ b/x-pack/test/functional/services/transform_ui/wizard.ts @@ -160,15 +160,15 @@ export function TransformWizardProvider({ getService }: FtrProviderContext) { }, async assertQueryInputExists() { - await testSubjects.existOrFail('tarnsformQueryInput'); + await testSubjects.existOrFail('transformQueryInput'); }, async assertQueryInputMissing() { - await testSubjects.missingOrFail('tarnsformQueryInput'); + await testSubjects.missingOrFail('transformQueryInput'); }, async assertQueryValue(expectedQuery: string) { - const actualQuery = await testSubjects.getVisibleText('tarnsformQueryInput'); + const actualQuery = await testSubjects.getVisibleText('transformQueryInput'); expect(actualQuery).to.eql( expectedQuery, `Query input text should be '${expectedQuery}' (got ${actualQuery})` From 8d19fb05a17c2f1882b3e9479b9bae10164ff9b8 Mon Sep 17 00:00:00 2001 From: Brian Seeders Date: Thu, 12 Mar 2020 11:06:53 -0400 Subject: [PATCH 02/19] Skip CI based on changes in PR (#59939) --- Jenkinsfile | 2 +- vars/githubPr.groovy | 8 ----- vars/kibanaPipeline.groovy | 11 ++++++- vars/prChanges.groovy | 52 +++++++++++++++++++++++++++++++ vars/withGithubCredentials.groovy | 9 ++++++ 5 files changed, 72 insertions(+), 10 deletions(-) create mode 100644 vars/prChanges.groovy create mode 100644 vars/withGithubCredentials.groovy diff --git a/Jenkinsfile b/Jenkinsfile index 742aec1d4e7ab4..d43da6e0bee04d 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -3,7 +3,7 @@ library 'kibana-pipeline-library' kibanaLibrary.load() -kibanaPipeline(timeoutMinutes: 135) { +kibanaPipeline(timeoutMinutes: 135, checkPrChanges: true) { githubPr.withDefaultPrComments { catchError { retryable.enable() diff --git a/vars/githubPr.groovy b/vars/githubPr.groovy index 7759edbbf5bfc7..0176424452d07f 100644 --- a/vars/githubPr.groovy +++ b/vars/githubPr.groovy @@ -194,14 +194,6 @@ def getNextCommentMessage(previousCommentInfo = [:]) { .join("\n\n") } -def withGithubCredentials(closure) { - withCredentials([ - string(credentialsId: '2a9602aa-ab9f-4e52-baf3-b71ca88469c7', variable: 'GITHUB_TOKEN'), - ]) { - closure() - } -} - def postComment(message) { if (!isPr()) { error "Trying to post a GitHub PR comment on a non-PR or non-elastic PR build" diff --git a/vars/kibanaPipeline.groovy b/vars/kibanaPipeline.groovy index 2b9b0eba38f465..cb5508642711a0 100644 --- a/vars/kibanaPipeline.groovy +++ b/vars/kibanaPipeline.groovy @@ -202,12 +202,20 @@ def runErrorReporter() { } def call(Map params = [:], Closure closure) { - def config = [timeoutMinutes: 135] + params + def config = [timeoutMinutes: 135, checkPrChanges: false] + params stage("Kibana Pipeline") { timeout(time: config.timeoutMinutes, unit: 'MINUTES') { timestamps { ansiColor('xterm') { + if (config.checkPrChanges && githubPr.isPr()) { + print "Checking PR for changes to determine if CI needs to be run..." + + if (prChanges.areChangesSkippable()) { + print "No changes requiring CI found in PR, skipping." + return + } + } closure() } } @@ -215,4 +223,5 @@ def call(Map params = [:], Closure closure) { } } + return this diff --git a/vars/prChanges.groovy b/vars/prChanges.groovy new file mode 100644 index 00000000000000..a9eb9027a05974 --- /dev/null +++ b/vars/prChanges.groovy @@ -0,0 +1,52 @@ + +def getSkippablePaths() { + return [ + /^docs\//, + /^rfcs\//, + /^.ci\/.+\.yml$/, + /^\.github\//, + /\.md$/, + ] +} + +def areChangesSkippable() { + if (!githubPr.isPr()) { + return false + } + + try { + def skippablePaths = getSkippablePaths() + def files = getChangedFiles() + + // 3000 is the max files GH API will return + if (files.size() >= 3000) { + return false + } + + files = files.findAll { file -> + return !skippablePaths.find { regex -> file =~ regex} + } + + return files.size() < 1 + } catch (ex) { + buildUtils.printStacktrace(ex) + print "Error while checking to see if CI is skippable based on changes. Will run CI." + return false + } +} + +def getChanges() { + withGithubCredentials { + return githubPrs.getChanges(env.ghprbPullId) + } +} + +def getChangedFiles() { + def changes = getChanges() + def changedFiles = changes.collect { it.filename } + def renamedFiles = changes.collect { it.previousFilename }.findAll { it } + + return changedFiles + renamedFiles +} + +return this diff --git a/vars/withGithubCredentials.groovy b/vars/withGithubCredentials.groovy new file mode 100644 index 00000000000000..224e49af1bd6f2 --- /dev/null +++ b/vars/withGithubCredentials.groovy @@ -0,0 +1,9 @@ +def call(closure) { + withCredentials([ + string(credentialsId: '2a9602aa-ab9f-4e52-baf3-b71ca88469c7', variable: 'GITHUB_TOKEN'), + ]) { + closure() + } +} + +return this From 4641a35b9d57f0064e8291d63764ebdd91e2a848 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Thu, 12 Mar 2020 16:16:37 +0100 Subject: [PATCH 03/19] [Upgrade Assistant Meta] Breaking changes issue template (#59745) * First draft of issue template * [skip ci] Slight update to issue template * Add label --- .github/ISSUE_TEMPLATE/v8_breaking_change.md | 36 ++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/v8_breaking_change.md diff --git a/.github/ISSUE_TEMPLATE/v8_breaking_change.md b/.github/ISSUE_TEMPLATE/v8_breaking_change.md new file mode 100644 index 00000000000000..99f779c288f5b9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/v8_breaking_change.md @@ -0,0 +1,36 @@ +--- +name: 8.0 Breaking change +about: Breaking changes from 7.x -> 8.0 +title: "[Breaking change]" +labels: Team:Elasticsearch UI, Feature:Upgrade Assistant +assignees: '' + +--- + +## Change description + +**Which release will ship the breaking change?** + + + +**Describe the change. How will it manifest to users?** + +**What percentage of users will be affected?** + + + +**What can users to do to address the change manually?** + + + +**How could we make migration easier with the Upgrade Assistant?** + +**Are there any edge cases?** + +## Test Data + +Provide test data. We can’t build a solution without data to test it against. + +## Cross links + +Cross-link to relevant [Elasticsearch breaking changes](https://www.elastic.co/guide/en/elasticsearch/reference/master/breaking-changes-8.0.html). \ No newline at end of file From 5eb8bb164d481c4a2b4ae3a5db5facecc0685bbe Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Thu, 12 Mar 2020 16:24:33 +0100 Subject: [PATCH 04/19] [Discover] Reimplement $route.reload when index pattern changes (#59877) --- .../discover/np_ready/angular/discover.js | 26 +++---------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js index 5ccee2b92ce336..c939de9b57078b 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js @@ -237,28 +237,9 @@ function discoverController( $scope.state = { ...newState }; // detect changes that should trigger fetching of new data - const changes = ['interval', 'sort', 'index', 'query'].filter( + const changes = ['interval', 'sort', 'query'].filter( prop => !_.isEqual(newStatePartial[prop], oldStatePartial[prop]) ); - if (changes.indexOf('index') !== -1) { - try { - $scope.indexPattern = await indexPatterns.get(newStatePartial.index); - $scope.opts.timefield = getTimeField(); - $scope.enableTimeRangeSelector = !!$scope.opts.timefield; - // is needed to rerender the histogram - $scope.vis = undefined; - - // Taking care of sort when switching index pattern: - // Old indexPattern: sort by A - // If A is not available in the new index pattern, sort has to be adapted and propagated to URL - const sort = getSortArray(newStatePartial.sort, $scope.indexPattern); - if (newStatePartial.sort && !_.isEqual(sort, newStatePartial.sort)) { - return await replaceUrlAppState({ sort }); - } - } catch (e) { - toastNotifications.addWarning({ text: getIndexPatternWarning(newStatePartial.index) }); - } - } if (changes.length) { $fetchObservable.next(); @@ -267,8 +248,9 @@ function discoverController( } }); - $scope.setIndexPattern = id => { - setAppState({ index: id }); + $scope.setIndexPattern = async id => { + await replaceUrlAppState({ index: id }); + $route.reload(); }; // update data source when filters update From 69fd44e36968b272af8ce62449c36ce6156bf5e2 Mon Sep 17 00:00:00 2001 From: Lee Drengenberg Date: Thu, 12 Mar 2020 10:27:05 -0500 Subject: [PATCH 05/19] rebalance x-pack groups (#58930) * rebalance x-pack groups * fix typo in ciGroup10 * move from group 9 to group 5 Co-authored-by: Elastic Machine Co-authored-by: spalger --- x-pack/test/functional/apps/dashboard/index.ts | 2 +- x-pack/test/functional/apps/dev_tools/index.ts | 2 +- x-pack/test/functional/apps/discover/index.ts | 2 +- .../functional_with_es_ssl/apps/triggers_actions_ui/index.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/test/functional/apps/dashboard/index.ts b/x-pack/test/functional/apps/dashboard/index.ts index b596d7bcbb4621..5f4d3fcbd8c04b 100644 --- a/x-pack/test/functional/apps/dashboard/index.ts +++ b/x-pack/test/functional/apps/dashboard/index.ts @@ -7,7 +7,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function({ loadTestFile }: FtrProviderContext) { describe('dashboard', function() { - this.tags('ciGroup3'); + this.tags('ciGroup7'); loadTestFile(require.resolve('./feature_controls')); }); diff --git a/x-pack/test/functional/apps/dev_tools/index.ts b/x-pack/test/functional/apps/dev_tools/index.ts index 73ff5d5749cb86..0860283d40dd31 100644 --- a/x-pack/test/functional/apps/dev_tools/index.ts +++ b/x-pack/test/functional/apps/dev_tools/index.ts @@ -7,7 +7,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function({ loadTestFile }: FtrProviderContext) { describe('console', function() { - this.tags('ciGroup3'); + this.tags('ciGroup10'); loadTestFile(require.resolve('./feature_controls')); loadTestFile(require.resolve('./searchprofiler_editor')); diff --git a/x-pack/test/functional/apps/discover/index.ts b/x-pack/test/functional/apps/discover/index.ts index e9ba3aa88d75e8..a5024225c44527 100644 --- a/x-pack/test/functional/apps/discover/index.ts +++ b/x-pack/test/functional/apps/discover/index.ts @@ -7,7 +7,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function({ loadTestFile }: FtrProviderContext) { describe('discover', function() { - this.tags('ciGroup3'); + this.tags('ciGroup8'); loadTestFile(require.resolve('./feature_controls')); }); diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/index.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/index.ts index a771fbf85e0b6e..97ce16495dacbf 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/index.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/index.ts @@ -8,7 +8,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default ({ loadTestFile, getService }: FtrProviderContext) => { describe('Actions and Triggers app', function() { - this.tags('ciGroup3'); + this.tags('ciGroup10'); loadTestFile(require.resolve('./home_page')); loadTestFile(require.resolve('./connectors')); loadTestFile(require.resolve('./alerts')); From 5cab334248bf4ae6d5435fb8765134716f9fd67a Mon Sep 17 00:00:00 2001 From: Aaron Caldwell Date: Thu, 12 Mar 2020 10:27:27 -0600 Subject: [PATCH 06/19] [Maps] Move redux reducers and store logic to NP (#58294) * Plugin file and services in place. Some redux logic ported * Port and update index pattern util * Move reducers over to NP. Update refs in legacy * Port inspector to NP * Move some kibana services init to NP. Some cleaning * Clean up work not related to reducers/store * Ignore temp imports from NP. Clean up of changes unrelated to this PR * More cleanup. Check injected vars avab. before calling to handle dashboard case * Bind embeddables services the same way Maps app services bound. Create function for eventual init in NP * Call binding from constructor. Fix npStart plugins arg * Adapt changes from master * Register inspector views for embeddable. Add NP folder to i18n * Clean up. Add comments. Move inspector map view registration to NP * Remove unused inspector files in legacy * Move full screen action to legacy * Add in missing tooltip updates * Review feedback. Update constants and i18n_getters to latest in NP * Review feedback. Add redundancy comments to common files redundant in legacy and NP * Remove unneeded copy of parse xml string test in legacy * Review feedback. Remove redundant portions. Export from NP where possible. General clean up * Remove remaining refernce and case for 'TOUCH_LAYER'. It's never used --- x-pack/.i18nrc.json | 2 +- .../legacy/plugins/maps/common/constants.ts | 178 +---------------- .../plugins/maps/common/i18n_getters.ts | 48 +---- .../plugins/maps/common/parse_xml_string.js | 18 +- x-pack/legacy/plugins/maps/index.js | 1 - .../maps/public/actions/map_actions.js | 94 +++++---- .../plugins/maps/public/actions/ui_actions.js | 38 ++-- .../maps/public/angular/map_controller.js | 15 +- .../public/angular/services/saved_gis_map.js | 3 +- .../connected_components/gis_map/index.js | 3 +- .../layer_addpanel/import_editor/index.js | 6 +- .../layer_addpanel/index.js | 6 +- .../layer_addpanel/source_editor/index.js | 3 +- .../layer_panel/flyout_footer/index.js | 3 +- .../connected_components/map/mb/index.js | 3 +- .../widget_overlay/layer_control/index.js | 3 +- .../layer_toc/toc_entry/index.js | 3 +- .../maps/public/embeddable/map_embeddable.js | 9 +- .../embeddable/map_embeddable_factory.js | 3 +- .../embeddable/merge_input_with_saved_map.js | 3 +- .../plugins/maps/public/layers/layer.js | 3 +- .../maps/public/layers/sources/es_source.js | 3 +- .../maps/public/layers/sources/source.js | 3 +- x-pack/legacy/plugins/maps/public/plugin.ts | 9 +- .../maps/public/selectors/map_selectors.js | 9 +- .../public/selectors/map_selectors.test.js | 2 +- x-pack/plugins/maps/common/constants.ts | 188 ++++++++++++++++++ x-pack/plugins/maps/common/i18n_getters.ts | 52 +++++ .../plugins/maps/common/parse_xml_string.js | 22 ++ .../maps/common/parse_xml_string.test.js | 0 x-pack/plugins/maps/kibana.json | 8 + .../maps/public/actions/map_actions.js | 47 +++++ .../plugins/maps/public/actions/ui_actions.js | 16 ++ x-pack/plugins/maps/public/index.ts | 12 ++ .../public/inspector/adapters/map_adapter.js | 0 .../public/inspector/views/map_details.js | 0 .../maps/public/inspector/views/map_view.js | 0 .../maps/public/kibana_services.js} | 9 +- x-pack/plugins/maps/public/plugin.ts | 40 ++++ .../plugins/maps/public/reducers/map.js | 12 -- .../plugins/maps/public/reducers/map.test.js | 0 .../reducers/non_serializable_instances.js | 7 +- .../plugins/maps/public/reducers/store.js | 14 +- .../plugins/maps/public/reducers/ui.js | 0 .../plugins/maps/public/reducers/util.js | 0 .../plugins/maps/public/reducers/util.test.js | 0 46 files changed, 542 insertions(+), 356 deletions(-) create mode 100644 x-pack/plugins/maps/common/constants.ts create mode 100644 x-pack/plugins/maps/common/i18n_getters.ts create mode 100644 x-pack/plugins/maps/common/parse_xml_string.js rename x-pack/{legacy => }/plugins/maps/common/parse_xml_string.test.js (100%) create mode 100644 x-pack/plugins/maps/kibana.json create mode 100644 x-pack/plugins/maps/public/actions/map_actions.js create mode 100644 x-pack/plugins/maps/public/actions/ui_actions.js create mode 100644 x-pack/plugins/maps/public/index.ts rename x-pack/{legacy => }/plugins/maps/public/inspector/adapters/map_adapter.js (100%) rename x-pack/{legacy => }/plugins/maps/public/inspector/views/map_details.js (100%) rename x-pack/{legacy => }/plugins/maps/public/inspector/views/map_view.js (100%) rename x-pack/{legacy/plugins/maps/public/inspector/views/register_views.ts => plugins/maps/public/kibana_services.js} (58%) create mode 100644 x-pack/plugins/maps/public/plugin.ts rename x-pack/{legacy => }/plugins/maps/public/reducers/map.js (97%) rename x-pack/{legacy => }/plugins/maps/public/reducers/map.test.js (100%) rename x-pack/{legacy => }/plugins/maps/public/reducers/non_serializable_instances.js (90%) rename x-pack/{legacy => }/plugins/maps/public/reducers/store.js (81%) rename x-pack/{legacy => }/plugins/maps/public/reducers/ui.js (100%) rename x-pack/{legacy => }/plugins/maps/public/reducers/util.js (100%) rename x-pack/{legacy => }/plugins/maps/public/reducers/util.test.js (100%) diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index 53628ea970fb6f..60a8d1fcbf2295 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -26,7 +26,7 @@ "xpack.licensing": "plugins/licensing", "xpack.logstash": "legacy/plugins/logstash", "xpack.main": "legacy/plugins/xpack_main", - "xpack.maps": "legacy/plugins/maps", + "xpack.maps": ["plugins/maps", "legacy/plugins/maps"], "xpack.ml": ["plugins/ml", "legacy/plugins/ml"], "xpack.monitoring": "legacy/plugins/monitoring", "xpack.remoteClusters": "plugins/remote_clusters", diff --git a/x-pack/legacy/plugins/maps/common/constants.ts b/x-pack/legacy/plugins/maps/common/constants.ts index a73e13cc948c84..98945653c25dc8 100644 --- a/x-pack/legacy/plugins/maps/common/constants.ts +++ b/x-pack/legacy/plugins/maps/common/constants.ts @@ -3,180 +3,6 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { i18n } from '@kbn/i18n'; -export const EMS_CATALOGUE_PATH = 'ems/catalogue'; -export const EMS_FILES_CATALOGUE_PATH = 'ems/files'; -export const EMS_FILES_API_PATH = 'ems/files'; -export const EMS_FILES_DEFAULT_JSON_PATH = 'file'; -export const EMS_GLYPHS_PATH = 'fonts'; -export const EMS_SPRITES_PATH = 'sprites'; - -export const EMS_TILES_CATALOGUE_PATH = 'ems/tiles'; -export const EMS_TILES_API_PATH = 'ems/tiles'; -export const EMS_TILES_RASTER_STYLE_PATH = 'raster/style'; -export const EMS_TILES_RASTER_TILE_PATH = 'raster/tile'; - -export const EMS_TILES_VECTOR_STYLE_PATH = 'vector/style'; -export const EMS_TILES_VECTOR_SOURCE_PATH = 'vector/source'; -export const EMS_TILES_VECTOR_TILE_PATH = 'vector/tile'; - -export const MAP_SAVED_OBJECT_TYPE = 'map'; -export const APP_ID = 'maps'; -export const APP_ICON = 'gisApp'; -export const TELEMETRY_TYPE = 'maps-telemetry'; - -export const MAP_APP_PATH = `app/${APP_ID}`; -export const GIS_API_PATH = `api/${APP_ID}`; -export const INDEX_SETTINGS_API_PATH = `${GIS_API_PATH}/indexSettings`; - -export const MAP_BASE_URL = `/${MAP_APP_PATH}#/${MAP_SAVED_OBJECT_TYPE}`; - -export function createMapPath(id: string) { - return `${MAP_BASE_URL}/${id}`; -} - -export const LAYER_TYPE = { - TILE: 'TILE', - VECTOR: 'VECTOR', - VECTOR_TILE: 'VECTOR_TILE', - HEATMAP: 'HEATMAP', -}; - -export enum SORT_ORDER { - ASC = 'asc', - DESC = 'desc', -} - -export const EMS_TMS = 'EMS_TMS'; -export const EMS_FILE = 'EMS_FILE'; -export const ES_GEO_GRID = 'ES_GEO_GRID'; -export const ES_SEARCH = 'ES_SEARCH'; -export const ES_PEW_PEW = 'ES_PEW_PEW'; -export const EMS_XYZ = 'EMS_XYZ'; // identifies a custom TMS source. Name is a little unfortunate. - -export enum FIELD_ORIGIN { - SOURCE = 'source', - JOIN = 'join', -} - -export const SOURCE_DATA_ID_ORIGIN = 'source'; -export const META_ID_ORIGIN_SUFFIX = 'meta'; -export const SOURCE_META_ID_ORIGIN = `${SOURCE_DATA_ID_ORIGIN}_${META_ID_ORIGIN_SUFFIX}`; -export const FORMATTERS_ID_ORIGIN_SUFFIX = 'formatters'; -export const SOURCE_FORMATTERS_ID_ORIGIN = `${SOURCE_DATA_ID_ORIGIN}_${FORMATTERS_ID_ORIGIN_SUFFIX}`; - -export const GEOJSON_FILE = 'GEOJSON_FILE'; - -export const MIN_ZOOM = 0; -export const MAX_ZOOM = 24; - -export const DECIMAL_DEGREES_PRECISION = 5; // meters precision -export const ZOOM_PRECISION = 2; -export const DEFAULT_MAX_RESULT_WINDOW = 10000; -export const DEFAULT_MAX_INNER_RESULT_WINDOW = 100; -export const DEFAULT_MAX_BUCKETS_LIMIT = 10000; - -export const FEATURE_ID_PROPERTY_NAME = '__kbn__feature_id__'; -export const FEATURE_VISIBLE_PROPERTY_NAME = '__kbn_isvisibleduetojoin__'; - -export const MB_SOURCE_ID_LAYER_ID_PREFIX_DELIMITER = '_'; - -export const ES_GEO_FIELD_TYPE = { - GEO_POINT: 'geo_point', - GEO_SHAPE: 'geo_shape', -}; - -export const ES_SPATIAL_RELATIONS = { - INTERSECTS: 'INTERSECTS', - DISJOINT: 'DISJOINT', - WITHIN: 'WITHIN', -}; - -export const GEO_JSON_TYPE = { - POINT: 'Point', - MULTI_POINT: 'MultiPoint', - LINE_STRING: 'LineString', - MULTI_LINE_STRING: 'MultiLineString', - POLYGON: 'Polygon', - MULTI_POLYGON: 'MultiPolygon', - GEOMETRY_COLLECTION: 'GeometryCollection', -}; - -export const POLYGON_COORDINATES_EXTERIOR_INDEX = 0; -export const LON_INDEX = 0; -export const LAT_INDEX = 1; - -export const EMPTY_FEATURE_COLLECTION = { - type: 'FeatureCollection', - features: [], -}; - -export const DRAW_TYPE = { - BOUNDS: 'BOUNDS', - POLYGON: 'POLYGON', -}; - -export enum AGG_TYPE { - AVG = 'avg', - COUNT = 'count', - MAX = 'max', - MIN = 'min', - SUM = 'sum', - TERMS = 'terms', - UNIQUE_COUNT = 'cardinality', -} - -export enum RENDER_AS { - HEATMAP = 'heatmap', - POINT = 'point', - GRID = 'grid', -} - -export enum GRID_RESOLUTION { - COARSE = 'COARSE', - FINE = 'FINE', - MOST_FINE = 'MOST_FINE', -} - -export const TOP_TERM_PERCENTAGE_SUFFIX = '__percentage'; - -export const COUNT_PROP_LABEL = i18n.translate('xpack.maps.aggs.defaultCountLabel', { - defaultMessage: 'count', -}); - -export const COUNT_PROP_NAME = 'doc_count'; - -export const STYLE_TYPE = { - STATIC: 'STATIC', - DYNAMIC: 'DYNAMIC', -}; - -export const LAYER_STYLE_TYPE = { - VECTOR: 'VECTOR', - HEATMAP: 'HEATMAP', -}; - -export const COLOR_MAP_TYPE = { - CATEGORICAL: 'CATEGORICAL', - ORDINAL: 'ORDINAL', -}; - -export const COLOR_PALETTE_MAX_SIZE = 10; - -export const CATEGORICAL_DATA_TYPES = ['string', 'ip', 'boolean']; -export const ORDINAL_DATA_TYPES = ['number', 'date']; - -export enum SYMBOLIZE_AS_TYPES { - CIRCLE = 'circle', - ICON = 'icon', -} - -export enum LABEL_BORDER_SIZES { - NONE = 'NONE', - SMALL = 'SMALL', - MEDIUM = 'MEDIUM', - LARGE = 'LARGE', -} - -export const DEFAULT_ICON = 'airfield'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +export * from '../../../../plugins/maps/common/constants'; diff --git a/x-pack/legacy/plugins/maps/common/i18n_getters.ts b/x-pack/legacy/plugins/maps/common/i18n_getters.ts index 0008a119f1c7cc..f9d186dea2e2b9 100644 --- a/x-pack/legacy/plugins/maps/common/i18n_getters.ts +++ b/x-pack/legacy/plugins/maps/common/i18n_getters.ts @@ -4,49 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -import { i18n } from '@kbn/i18n'; - -import { $Values } from '@kbn/utility-types'; -import { ES_SPATIAL_RELATIONS } from './constants'; - -export function getAppTitle() { - return i18n.translate('xpack.maps.appTitle', { - defaultMessage: 'Maps', - }); -} - -export function getDataSourceLabel() { - return i18n.translate('xpack.maps.source.dataSourceLabel', { - defaultMessage: 'Data source', - }); -} - -export function getUrlLabel() { - return i18n.translate('xpack.maps.source.urlLabel', { - defaultMessage: 'Url', - }); -} - -export function getEsSpatialRelationLabel(spatialRelation: $Values) { - switch (spatialRelation) { - case ES_SPATIAL_RELATIONS.INTERSECTS: - return i18n.translate('xpack.maps.common.esSpatialRelation.intersectsLabel', { - defaultMessage: 'intersects', - }); - case ES_SPATIAL_RELATIONS.DISJOINT: - return i18n.translate('xpack.maps.common.esSpatialRelation.disjointLabel', { - defaultMessage: 'disjoint', - }); - case ES_SPATIAL_RELATIONS.WITHIN: - return i18n.translate('xpack.maps.common.esSpatialRelation.withinLabel', { - defaultMessage: 'within', - }); - // @ts-ignore - case ES_SPATIAL_RELATIONS.CONTAINS: - return i18n.translate('xpack.maps.common.esSpatialRelation.containsLabel', { - defaultMessage: 'contains', - }); - default: - return spatialRelation; - } -} +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +export * from '../../../../plugins/maps/common/i18n_getters'; diff --git a/x-pack/legacy/plugins/maps/common/parse_xml_string.js b/x-pack/legacy/plugins/maps/common/parse_xml_string.js index 9d95e0e78280d5..34ec1444728281 100644 --- a/x-pack/legacy/plugins/maps/common/parse_xml_string.js +++ b/x-pack/legacy/plugins/maps/common/parse_xml_string.js @@ -4,19 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -import { parseString } from 'xml2js'; - -// promise based wrapper around parseString -export async function parseXmlString(xmlString) { - const parsePromise = new Promise((resolve, reject) => { - parseString(xmlString, (error, result) => { - if (error) { - reject(error); - } else { - resolve(result); - } - }); - }); - - return await parsePromise; -} +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +export * from '../../../../plugins/maps/common/parse_xml_string'; diff --git a/x-pack/legacy/plugins/maps/index.js b/x-pack/legacy/plugins/maps/index.js index 8048c21fe9333d..1a7f478d3bbad4 100644 --- a/x-pack/legacy/plugins/maps/index.js +++ b/x-pack/legacy/plugins/maps/index.js @@ -52,7 +52,6 @@ export function maps(kibana) { }; }, embeddableFactories: ['plugins/maps/embeddable/map_embeddable_factory'], - inspectorViews: ['plugins/maps/inspector/views/register_views'], home: ['plugins/maps/legacy_register_feature'], styleSheetPaths: `${__dirname}/public/index.scss`, savedObjectSchemas: { diff --git a/x-pack/legacy/plugins/maps/public/actions/map_actions.js b/x-pack/legacy/plugins/maps/public/actions/map_actions.js index cfca044ea759a1..7a1e5e5266246d 100644 --- a/x-pack/legacy/plugins/maps/public/actions/map_actions.js +++ b/x-pack/legacy/plugins/maps/public/actions/map_actions.js @@ -20,13 +20,15 @@ import { getQuery, getDataRequestDescriptor, } from '../selectors/map_selectors'; -import { FLYOUT_STATE } from '../reducers/ui'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { FLYOUT_STATE } from '../../../../../plugins/maps/public/reducers/ui'; import { cancelRequest, registerCancelCallback, unregisterCancelCallback, getEventHandlers, -} from '../reducers/non_serializable_instances'; + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../../plugins/maps/public/reducers/non_serializable_instances'; import { updateFlyout } from '../actions/ui_actions'; import { FEATURE_ID_PROPERTY_NAME, @@ -34,48 +36,52 @@ import { SOURCE_DATA_ID_ORIGIN, } from '../../common/constants'; -export const SET_SELECTED_LAYER = 'SET_SELECTED_LAYER'; -export const SET_TRANSIENT_LAYER = 'SET_TRANSIENT_LAYER'; -export const UPDATE_LAYER_ORDER = 'UPDATE_LAYER_ORDER'; -export const ADD_LAYER = 'ADD_LAYER'; -export const SET_LAYER_ERROR_STATUS = 'SET_LAYER_ERROR_STATUS'; -export const ADD_WAITING_FOR_MAP_READY_LAYER = 'ADD_WAITING_FOR_MAP_READY_LAYER'; -export const CLEAR_WAITING_FOR_MAP_READY_LAYER_LIST = 'CLEAR_WAITING_FOR_MAP_READY_LAYER_LIST'; -export const REMOVE_LAYER = 'REMOVE_LAYER'; -export const SET_LAYER_VISIBILITY = 'SET_LAYER_VISIBILITY'; -export const MAP_EXTENT_CHANGED = 'MAP_EXTENT_CHANGED'; -export const MAP_READY = 'MAP_READY'; -export const MAP_DESTROYED = 'MAP_DESTROYED'; -export const LAYER_DATA_LOAD_STARTED = 'LAYER_DATA_LOAD_STARTED'; -export const LAYER_DATA_LOAD_ENDED = 'LAYER_DATA_LOAD_ENDED'; -export const LAYER_DATA_LOAD_ERROR = 'LAYER_DATA_LOAD_ERROR'; -export const UPDATE_SOURCE_DATA_REQUEST = 'UPDATE_SOURCE_DATA_REQUEST'; -export const SET_JOINS = 'SET_JOINS'; -export const SET_QUERY = 'SET_QUERY'; -export const TRIGGER_REFRESH_TIMER = 'TRIGGER_REFRESH_TIMER'; -export const UPDATE_LAYER_PROP = 'UPDATE_LAYER_PROP'; -export const UPDATE_LAYER_STYLE = 'UPDATE_LAYER_STYLE'; -export const SET_LAYER_STYLE_META = 'SET_LAYER_STYLE_META'; -export const TOUCH_LAYER = 'TOUCH_LAYER'; -export const UPDATE_SOURCE_PROP = 'UPDATE_SOURCE_PROP'; -export const SET_REFRESH_CONFIG = 'SET_REFRESH_CONFIG'; -export const SET_MOUSE_COORDINATES = 'SET_MOUSE_COORDINATES'; -export const CLEAR_MOUSE_COORDINATES = 'CLEAR_MOUSE_COORDINATES'; -export const SET_GOTO = 'SET_GOTO'; -export const CLEAR_GOTO = 'CLEAR_GOTO'; -export const TRACK_CURRENT_LAYER_STATE = 'TRACK_CURRENT_LAYER_STATE'; -export const ROLLBACK_TO_TRACKED_LAYER_STATE = 'ROLLBACK_TO_TRACKED_LAYER_STATE'; -export const REMOVE_TRACKED_LAYER_STATE = 'REMOVE_TRACKED_LAYER_STATE'; -export const SET_OPEN_TOOLTIPS = 'SET_OPEN_TOOLTIPS'; -export const UPDATE_DRAW_STATE = 'UPDATE_DRAW_STATE'; -export const SET_SCROLL_ZOOM = 'SET_SCROLL_ZOOM'; -export const SET_MAP_INIT_ERROR = 'SET_MAP_INIT_ERROR'; -export const SET_INTERACTIVE = 'SET_INTERACTIVE'; -export const DISABLE_TOOLTIP_CONTROL = 'DISABLE_TOOLTIP_CONTROL'; -export const HIDE_TOOLBAR_OVERLAY = 'HIDE_TOOLBAR_OVERLAY'; -export const HIDE_LAYER_CONTROL = 'HIDE_LAYER_CONTROL'; -export const HIDE_VIEW_CONTROL = 'HIDE_VIEW_CONTROL'; -export const SET_WAITING_FOR_READY_HIDDEN_LAYERS = 'SET_WAITING_FOR_READY_HIDDEN_LAYERS'; +import { + SET_SELECTED_LAYER, + SET_TRANSIENT_LAYER, + UPDATE_LAYER_ORDER, + ADD_LAYER, + SET_LAYER_ERROR_STATUS, + ADD_WAITING_FOR_MAP_READY_LAYER, + CLEAR_WAITING_FOR_MAP_READY_LAYER_LIST, + REMOVE_LAYER, + SET_LAYER_VISIBILITY, + MAP_EXTENT_CHANGED, + MAP_READY, + MAP_DESTROYED, + LAYER_DATA_LOAD_STARTED, + LAYER_DATA_LOAD_ENDED, + LAYER_DATA_LOAD_ERROR, + UPDATE_SOURCE_DATA_REQUEST, + SET_JOINS, + SET_QUERY, + TRIGGER_REFRESH_TIMER, + UPDATE_LAYER_PROP, + UPDATE_LAYER_STYLE, + SET_LAYER_STYLE_META, + UPDATE_SOURCE_PROP, + SET_REFRESH_CONFIG, + SET_MOUSE_COORDINATES, + CLEAR_MOUSE_COORDINATES, + SET_GOTO, + CLEAR_GOTO, + TRACK_CURRENT_LAYER_STATE, + ROLLBACK_TO_TRACKED_LAYER_STATE, + REMOVE_TRACKED_LAYER_STATE, + SET_OPEN_TOOLTIPS, + UPDATE_DRAW_STATE, + SET_SCROLL_ZOOM, + SET_MAP_INIT_ERROR, + SET_INTERACTIVE, + DISABLE_TOOLTIP_CONTROL, + HIDE_TOOLBAR_OVERLAY, + HIDE_LAYER_CONTROL, + HIDE_VIEW_CONTROL, + SET_WAITING_FOR_READY_HIDDEN_LAYERS, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../../plugins/maps/public/actions/map_actions'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +export * from '../../../../../plugins/maps/public/actions/map_actions'; function getLayerLoadingCallbacks(dispatch, getState, layerId) { return { diff --git a/x-pack/legacy/plugins/maps/public/actions/ui_actions.js b/x-pack/legacy/plugins/maps/public/actions/ui_actions.js index 2b687516f3e5ac..33ab2fd74122a6 100644 --- a/x-pack/legacy/plugins/maps/public/actions/ui_actions.js +++ b/x-pack/legacy/plugins/maps/public/actions/ui_actions.js @@ -4,16 +4,28 @@ * you may not use this file except in compliance with the Elastic License. */ -export const UPDATE_FLYOUT = 'UPDATE_FLYOUT'; -export const CLOSE_SET_VIEW = 'CLOSE_SET_VIEW'; -export const OPEN_SET_VIEW = 'OPEN_SET_VIEW'; -export const SET_IS_LAYER_TOC_OPEN = 'SET_IS_LAYER_TOC_OPEN'; -export const SET_FULL_SCREEN = 'SET_FULL_SCREEN'; -export const SET_READ_ONLY = 'SET_READ_ONLY'; -export const SET_OPEN_TOC_DETAILS = 'SET_OPEN_TOC_DETAILS'; -export const SHOW_TOC_DETAILS = 'SHOW_TOC_DETAILS'; -export const HIDE_TOC_DETAILS = 'HIDE_TOC_DETAILS'; -export const UPDATE_INDEXING_STAGE = 'UPDATE_INDEXING_STAGE'; +import { + UPDATE_FLYOUT, + CLOSE_SET_VIEW, + OPEN_SET_VIEW, + SET_IS_LAYER_TOC_OPEN, + SET_FULL_SCREEN, + SET_READ_ONLY, + SET_OPEN_TOC_DETAILS, + SHOW_TOC_DETAILS, + HIDE_TOC_DETAILS, + UPDATE_INDEXING_STAGE, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../../plugins/maps/public/actions/ui_actions'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +export * from '../../../../../plugins/maps/public/actions/ui_actions'; + +export function exitFullScreen() { + return { + type: SET_FULL_SCREEN, + isFullScreen: false, + }; +} export function updateFlyout(display) { return { @@ -37,12 +49,6 @@ export function setIsLayerTOCOpen(isLayerTOCOpen) { isLayerTOCOpen, }; } -export function exitFullScreen() { - return { - type: SET_FULL_SCREEN, - isFullScreen: false, - }; -} export function enableFullScreen() { return { type: SET_FULL_SCREEN, diff --git a/x-pack/legacy/plugins/maps/public/angular/map_controller.js b/x-pack/legacy/plugins/maps/public/angular/map_controller.js index 84ead42d3374e1..7b3dc74d777b2b 100644 --- a/x-pack/legacy/plugins/maps/public/angular/map_controller.js +++ b/x-pack/legacy/plugins/maps/public/angular/map_controller.js @@ -17,7 +17,8 @@ import { render, unmountComponentAtNode } from 'react-dom'; import { uiModules } from 'ui/modules'; import { timefilter } from 'ui/timefilter'; import { Provider } from 'react-redux'; -import { createMapStore } from '../reducers/store'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { createMapStore } from '../../../../../plugins/maps/public/reducers/store'; import { GisMap } from '../connected_components/gis_map'; import { addHelpMenuToAppChrome } from '../help_menu_util'; import { @@ -28,7 +29,11 @@ import { setQuery, clearTransientLayerStateAndCloseFlyout, } from '../actions/map_actions'; -import { DEFAULT_IS_LAYER_TOC_OPEN, FLYOUT_STATE } from '../reducers/ui'; +import { + DEFAULT_IS_LAYER_TOC_OPEN, + FLYOUT_STATE, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../../plugins/maps/public/reducers/ui'; import { enableFullScreen, updateFlyout, @@ -37,13 +42,15 @@ import { setOpenTOCDetails, } from '../actions/ui_actions'; import { getIsFullScreen } from '../selectors/ui_selectors'; -import { copyPersistentState } from '../reducers/util'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { copyPersistentState } from '../../../../../plugins/maps/public/reducers/util'; import { getQueryableUniqueIndexPatternIds, hasDirtyState, getLayerListRaw, } from '../selectors/map_selectors'; -import { getInspectorAdapters } from '../reducers/non_serializable_instances'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getInspectorAdapters } from '../../../../../plugins/maps/public/reducers/non_serializable_instances'; import { docTitle } from 'ui/doc_title'; import { indexPatternService, getInspector } from '../kibana_services'; import { toastNotifications } from 'ui/notify'; diff --git a/x-pack/legacy/plugins/maps/public/angular/services/saved_gis_map.js b/x-pack/legacy/plugins/maps/public/angular/services/saved_gis_map.js index 490ab16a1799c0..f846d3d4a617f0 100644 --- a/x-pack/legacy/plugins/maps/public/angular/services/saved_gis_map.js +++ b/x-pack/legacy/plugins/maps/public/angular/services/saved_gis_map.js @@ -18,7 +18,8 @@ import { } from '../../selectors/map_selectors'; import { getIsLayerTOCOpen, getOpenTOCDetails } from '../../selectors/ui_selectors'; import { convertMapExtentToPolygon } from '../../elasticsearch_geo_utils'; -import { copyPersistentState } from '../../reducers/util'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { copyPersistentState } from '../../../../../../plugins/maps/public/reducers/util'; import { extractReferences, injectReferences } from '../../../common/migrations/references'; import { MAP_SAVED_OBJECT_TYPE } from '../../../common/constants'; diff --git a/x-pack/legacy/plugins/maps/public/connected_components/gis_map/index.js b/x-pack/legacy/plugins/maps/public/connected_components/gis_map/index.js index ceb0a6ea9f9225..39cb2c469e0544 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/gis_map/index.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/gis_map/index.js @@ -6,7 +6,8 @@ import { connect } from 'react-redux'; import { GisMap } from './view'; -import { FLYOUT_STATE } from '../../reducers/ui'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { FLYOUT_STATE } from '../../../../../../plugins/maps/public/reducers/ui'; import { exitFullScreen } from '../../actions/ui_actions'; import { getFlyoutDisplay, getIsFullScreen } from '../../selectors/ui_selectors'; import { triggerRefreshTimer, cancelAllInFlightRequests } from '../../actions/map_actions'; diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/import_editor/index.js b/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/import_editor/index.js index 8d0dd0c266f28e..e8192795f98aef 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/import_editor/index.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/import_editor/index.js @@ -6,8 +6,10 @@ import { connect } from 'react-redux'; import { ImportEditor } from './view'; -import { getInspectorAdapters } from '../../../reducers/non_serializable_instances'; -import { INDEXING_STAGE } from '../../../reducers/ui'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getInspectorAdapters } from '../../../../../../../plugins/maps/public/reducers/non_serializable_instances'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { INDEXING_STAGE } from '../../../../../../../plugins/maps/public/reducers/ui'; import { updateIndexingStage } from '../../../actions/ui_actions'; import { getIndexingStage } from '../../../selectors/ui_selectors'; diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/index.js b/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/index.js index d2b43775c5a492..c4e2fa5169b0fa 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/index.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/index.js @@ -6,11 +6,13 @@ import { connect } from 'react-redux'; import { AddLayerPanel } from './view'; -import { FLYOUT_STATE, INDEXING_STAGE } from '../../reducers/ui'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { FLYOUT_STATE, INDEXING_STAGE } from '../../../../../../plugins/maps/public/reducers/ui'; import { updateFlyout, updateIndexingStage } from '../../actions/ui_actions'; import { getFlyoutDisplay, getIndexingStage } from '../../selectors/ui_selectors'; import { getMapColors } from '../../selectors/map_selectors'; -import { getInspectorAdapters } from '../../reducers/non_serializable_instances'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getInspectorAdapters } from '../../../../../../plugins/maps/public/reducers/non_serializable_instances'; import { setTransientLayer, addLayer, diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/source_editor/index.js b/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/source_editor/index.js index 51ed19d1c77d1f..553e54ee897665 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/source_editor/index.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/source_editor/index.js @@ -6,7 +6,8 @@ import { connect } from 'react-redux'; import { SourceEditor } from './view'; -import { getInspectorAdapters } from '../../../reducers/non_serializable_instances'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getInspectorAdapters } from '../../../../../../../plugins/maps/public/reducers/non_serializable_instances'; function mapStateToProps(state = {}) { return { diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/flyout_footer/index.js b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/flyout_footer/index.js index 76e650cad97eb5..287f0019f18ecd 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/flyout_footer/index.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/flyout_footer/index.js @@ -6,7 +6,8 @@ import { connect } from 'react-redux'; import { FlyoutFooter } from './view'; -import { FLYOUT_STATE } from '../../../reducers/ui'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { FLYOUT_STATE } from '../../../../../../../plugins/maps/public/reducers/ui'; import { updateFlyout } from '../../../actions/ui_actions'; import { hasDirtyState } from '../../../selectors/map_selectors'; import { diff --git a/x-pack/legacy/plugins/maps/public/connected_components/map/mb/index.js b/x-pack/legacy/plugins/maps/public/connected_components/map/mb/index.js index a2f121a9377fef..350cb7028abee2 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/map/mb/index.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/map/mb/index.js @@ -24,7 +24,8 @@ import { isTooltipControlDisabled, isViewControlHidden, } from '../../../selectors/map_selectors'; -import { getInspectorAdapters } from '../../../reducers/non_serializable_instances'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getInspectorAdapters } from '../../../../../../../plugins/maps/public/reducers/non_serializable_instances'; function mapStateToProps(state = {}) { return { diff --git a/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/index.js b/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/index.js index 0b090a639edb2b..e51e59ec41e18e 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/index.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/index.js @@ -6,7 +6,8 @@ import { connect } from 'react-redux'; import { LayerControl } from './view'; -import { FLYOUT_STATE } from '../../../reducers/ui'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { FLYOUT_STATE } from '../../../../../../../plugins/maps/public/reducers/ui.js'; import { updateFlyout, setIsLayerTOCOpen } from '../../../actions/ui_actions'; import { setSelectedLayer } from '../../../actions/map_actions'; import { diff --git a/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/index.js b/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/index.js index e9debdba7b9148..ececc5a90ab89b 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/index.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/index.js @@ -7,7 +7,8 @@ import _ from 'lodash'; import { connect } from 'react-redux'; import { TOCEntry } from './view'; -import { FLYOUT_STATE } from '../../../../../reducers/ui'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { FLYOUT_STATE } from '../../../../../../../../../plugins/maps/public/reducers/ui.js'; import { updateFlyout, hideTOCDetails, showTOCDetails } from '../../../../../actions/ui_actions'; import { getIsReadOnly, getOpenTOCDetails } from '../../../../../selectors/ui_selectors'; import { diff --git a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.js b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.js index 5988a128232d60..650e827cc16562 100644 --- a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.js +++ b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.js @@ -19,7 +19,8 @@ import { esFilters } from '../../../../../../src/plugins/data/public'; import { I18nContext } from 'ui/i18n'; import { GisMap } from '../connected_components/gis_map'; -import { createMapStore } from '../reducers/store'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { createMapStore } from '../../../../../plugins/maps/public/reducers/store'; import { npStart } from 'ui/new_platform'; import { setGotoWithCenter, @@ -36,7 +37,11 @@ import { } from '../actions/map_actions'; import { setReadOnly, setIsLayerTOCOpen, setOpenTOCDetails } from '../actions/ui_actions'; import { getIsLayerTOCOpen, getOpenTOCDetails } from '../selectors/ui_selectors'; -import { getInspectorAdapters, setEventHandlers } from '../reducers/non_serializable_instances'; +import { + getInspectorAdapters, + setEventHandlers, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../../plugins/maps/public/reducers/non_serializable_instances'; import { getMapCenter, getMapZoom, getHiddenLayerIds } from '../selectors/map_selectors'; import { MAP_SAVED_OBJECT_TYPE } from '../../common/constants'; diff --git a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable_factory.js b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable_factory.js index 73f222615493bf..710b7f737e8610 100644 --- a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable_factory.js +++ b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable_factory.js @@ -17,7 +17,8 @@ import { MapEmbeddable } from './map_embeddable'; import { indexPatternService } from '../kibana_services'; import { createMapPath, MAP_SAVED_OBJECT_TYPE, APP_ICON } from '../../common/constants'; -import { createMapStore } from '../reducers/store'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { createMapStore } from '../../../../../plugins/maps/public/reducers/store'; import { addLayerWithoutDataSync } from '../actions/map_actions'; import { getQueryableUniqueIndexPatternIds } from '../selectors/map_selectors'; import { getInitialLayers } from '../angular/get_initial_layers'; diff --git a/x-pack/legacy/plugins/maps/public/embeddable/merge_input_with_saved_map.js b/x-pack/legacy/plugins/maps/public/embeddable/merge_input_with_saved_map.js index 935747da936870..8e3e0a9168e30e 100644 --- a/x-pack/legacy/plugins/maps/public/embeddable/merge_input_with_saved_map.js +++ b/x-pack/legacy/plugins/maps/public/embeddable/merge_input_with_saved_map.js @@ -5,7 +5,8 @@ */ import _ from 'lodash'; -import { DEFAULT_IS_LAYER_TOC_OPEN } from '../reducers/ui'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { DEFAULT_IS_LAYER_TOC_OPEN } from '../../../../../plugins/maps/public/reducers/ui'; const MAP_EMBEDDABLE_INPUT_KEYS = [ 'hideFilterActions', diff --git a/x-pack/legacy/plugins/maps/public/layers/layer.js b/x-pack/legacy/plugins/maps/public/layers/layer.js index 71e5d7b95e44fd..5c9532a3841f36 100644 --- a/x-pack/legacy/plugins/maps/public/layers/layer.js +++ b/x-pack/legacy/plugins/maps/public/layers/layer.js @@ -14,7 +14,8 @@ import { SOURCE_DATA_ID_ORIGIN, } from '../../common/constants'; import uuid from 'uuid/v4'; -import { copyPersistentState } from '../reducers/util'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { copyPersistentState } from '../../../../../plugins/maps/public/reducers/util.js'; import { i18n } from '@kbn/i18n'; export class AbstractLayer { diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js index f575fd05c80613..1552db277e609e 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js @@ -16,7 +16,8 @@ import { timefilter } from 'ui/timefilter'; import _ from 'lodash'; import { i18n } from '@kbn/i18n'; import uuid from 'uuid/v4'; -import { copyPersistentState } from '../../reducers/util'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { copyPersistentState } from '../../../../../../plugins/maps/public/reducers/util'; import { ES_GEO_FIELD_TYPE } from '../../../common/constants'; import { DataRequestAbortError } from '../util/data_request'; import { expandToTileBoundaries } from './es_geo_grid_source/geo_tile_utils'; diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/source.js b/x-pack/legacy/plugins/maps/public/layers/sources/source.js index 4fef52e731f9b2..b6b6c10831bb5d 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/source.js @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { copyPersistentState } from '../../reducers/util'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { copyPersistentState } from '../../../../../../plugins/maps/public/reducers/util'; export class AbstractSource { static isIndexingSource = false; diff --git a/x-pack/legacy/plugins/maps/public/plugin.ts b/x-pack/legacy/plugins/maps/public/plugin.ts index c3f90d815239c8..e2d1d432956466 100644 --- a/x-pack/legacy/plugins/maps/public/plugin.ts +++ b/x-pack/legacy/plugins/maps/public/plugin.ts @@ -10,6 +10,8 @@ import { wrapInI18nContext } from 'ui/i18n'; // @ts-ignore import { MapListing } from './components/map_listing'; // @ts-ignore +import { setInjectedVarFunc } from '../../../../plugins/maps/public/kibana_services'; // eslint-disable-line @kbn/eslint/no-restricted-paths +// @ts-ignore import { setLicenseId, setInspector, setFileUpload } from './kibana_services'; import { HomePublicPluginSetup } from '../../../../../src/plugins/home/public'; import { LicensingPluginSetup } from '../../../../plugins/licensing/public'; @@ -33,9 +35,11 @@ interface MapsPluginSetupDependencies { export const bindSetupCoreAndPlugins = (core: CoreSetup, plugins: any) => { const { licensing } = plugins; + const { injectedMetadata } = core; if (licensing) { licensing.license$.subscribe(({ uid }: { uid: string }) => setLicenseId(uid)); } + setInjectedVarFunc(injectedMetadata.getInjectedVar); }; /** @internal */ @@ -53,7 +57,8 @@ export class MapsPlugin implements Plugin { } public start(core: CoreStart, plugins: any) { - setInspector(plugins.np.inspector); - setFileUpload(plugins.np.file_upload); + const { inspector, file_upload } = plugins.np; + setInspector(inspector); + setFileUpload(file_upload); } } diff --git a/x-pack/legacy/plugins/maps/public/selectors/map_selectors.js b/x-pack/legacy/plugins/maps/public/selectors/map_selectors.js index ab0926ab40070b..e5eaf8870aa77f 100644 --- a/x-pack/legacy/plugins/maps/public/selectors/map_selectors.js +++ b/x-pack/legacy/plugins/maps/public/selectors/map_selectors.js @@ -12,8 +12,13 @@ import { VectorLayer } from '../layers/vector_layer'; import { HeatmapLayer } from '../layers/heatmap_layer'; import { ALL_SOURCES } from '../layers/sources/all_sources'; import { timefilter } from 'ui/timefilter'; -import { getInspectorAdapters } from '../reducers/non_serializable_instances'; -import { copyPersistentState, TRACKED_LAYER_DESCRIPTOR } from '../reducers/util'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getInspectorAdapters } from '../../../../../plugins/maps/public/reducers/non_serializable_instances'; +import { + copyPersistentState, + TRACKED_LAYER_DESCRIPTOR, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../../plugins/maps/public/reducers/util'; import { InnerJoin } from '../layers/joins/inner_join'; function createLayerInstance(layerDescriptor, inspectorAdapters) { diff --git a/x-pack/legacy/plugins/maps/public/selectors/map_selectors.test.js b/x-pack/legacy/plugins/maps/public/selectors/map_selectors.test.js index 995030d024ddf3..5ec40a57ebc7f4 100644 --- a/x-pack/legacy/plugins/maps/public/selectors/map_selectors.test.js +++ b/x-pack/legacy/plugins/maps/public/selectors/map_selectors.test.js @@ -9,7 +9,7 @@ jest.mock('../layers/heatmap_layer', () => {}); jest.mock('../layers/vector_tile_layer', () => {}); jest.mock('../layers/sources/all_sources', () => {}); jest.mock('../layers/joins/inner_join', () => {}); -jest.mock('../reducers/non_serializable_instances', () => ({ +jest.mock('../../../../../plugins/maps/public/reducers/non_serializable_instances', () => ({ getInspectorAdapters: () => { return {}; }, diff --git a/x-pack/plugins/maps/common/constants.ts b/x-pack/plugins/maps/common/constants.ts new file mode 100644 index 00000000000000..ae3e164ffb2bc1 --- /dev/null +++ b/x-pack/plugins/maps/common/constants.ts @@ -0,0 +1,188 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { i18n } from '@kbn/i18n'; +export const EMS_CATALOGUE_PATH = 'ems/catalogue'; + +export const EMS_FILES_CATALOGUE_PATH = 'ems/files'; +export const EMS_FILES_API_PATH = 'ems/files'; +export const EMS_FILES_DEFAULT_JSON_PATH = 'file'; +export const EMS_GLYPHS_PATH = 'fonts'; +export const EMS_SPRITES_PATH = 'sprites'; + +export const EMS_TILES_CATALOGUE_PATH = 'ems/tiles'; +export const EMS_TILES_API_PATH = 'ems/tiles'; +export const EMS_TILES_RASTER_STYLE_PATH = 'raster/style'; +export const EMS_TILES_RASTER_TILE_PATH = 'raster/tile'; + +export const EMS_TILES_VECTOR_STYLE_PATH = 'vector/style'; +export const EMS_TILES_VECTOR_SOURCE_PATH = 'vector/source'; +export const EMS_TILES_VECTOR_TILE_PATH = 'vector/tile'; + +export const MAP_SAVED_OBJECT_TYPE = 'map'; +export const APP_ID = 'maps'; +export const APP_ICON = 'gisApp'; +export const TELEMETRY_TYPE = 'maps-telemetry'; + +export const MAP_APP_PATH = `app/${APP_ID}`; +export const GIS_API_PATH = `api/${APP_ID}`; +export const INDEX_SETTINGS_API_PATH = `${GIS_API_PATH}/indexSettings`; + +export const MAP_BASE_URL = `/${MAP_APP_PATH}#/${MAP_SAVED_OBJECT_TYPE}`; + +export function createMapPath(id: string) { + return `${MAP_BASE_URL}/${id}`; +} + +export const LAYER_TYPE = { + TILE: 'TILE', + VECTOR: 'VECTOR', + VECTOR_TILE: 'VECTOR_TILE', + HEATMAP: 'HEATMAP', +}; + +export enum SORT_ORDER { + ASC = 'asc', + DESC = 'desc', +} + +export const EMS_TMS = 'EMS_TMS'; +export const EMS_FILE = 'EMS_FILE'; +export const ES_GEO_GRID = 'ES_GEO_GRID'; +export const ES_SEARCH = 'ES_SEARCH'; +export const ES_PEW_PEW = 'ES_PEW_PEW'; +export const EMS_XYZ = 'EMS_XYZ'; // identifies a custom TMS source. Name is a little unfortunate. + +export enum FIELD_ORIGIN { + SOURCE = 'source', + JOIN = 'join', +} + +export const SOURCE_DATA_ID_ORIGIN = 'source'; +export const META_ID_ORIGIN_SUFFIX = 'meta'; +export const SOURCE_META_ID_ORIGIN = `${SOURCE_DATA_ID_ORIGIN}_${META_ID_ORIGIN_SUFFIX}`; +export const FORMATTERS_ID_ORIGIN_SUFFIX = 'formatters'; +export const SOURCE_FORMATTERS_ID_ORIGIN = `${SOURCE_DATA_ID_ORIGIN}_${FORMATTERS_ID_ORIGIN_SUFFIX}`; + +export const GEOJSON_FILE = 'GEOJSON_FILE'; + +export const MIN_ZOOM = 0; +export const MAX_ZOOM = 24; + +export const DECIMAL_DEGREES_PRECISION = 5; // meters precision +export const ZOOM_PRECISION = 2; +export const DEFAULT_MAX_RESULT_WINDOW = 10000; +export const DEFAULT_MAX_INNER_RESULT_WINDOW = 100; +export const DEFAULT_MAX_BUCKETS_LIMIT = 10000; + +export const FEATURE_ID_PROPERTY_NAME = '__kbn__feature_id__'; +export const FEATURE_VISIBLE_PROPERTY_NAME = '__kbn_isvisibleduetojoin__'; + +export const MB_SOURCE_ID_LAYER_ID_PREFIX_DELIMITER = '_'; + +export const ES_GEO_FIELD_TYPE = { + GEO_POINT: 'geo_point', + GEO_SHAPE: 'geo_shape', +}; + +export const ES_SPATIAL_RELATIONS = { + INTERSECTS: 'INTERSECTS', + DISJOINT: 'DISJOINT', + WITHIN: 'WITHIN', +}; + +export const GEO_JSON_TYPE = { + POINT: 'Point', + MULTI_POINT: 'MultiPoint', + LINE_STRING: 'LineString', + MULTI_LINE_STRING: 'MultiLineString', + POLYGON: 'Polygon', + MULTI_POLYGON: 'MultiPolygon', + GEOMETRY_COLLECTION: 'GeometryCollection', +}; + +export const POLYGON_COORDINATES_EXTERIOR_INDEX = 0; +export const LON_INDEX = 0; +export const LAT_INDEX = 1; + +export const EMPTY_FEATURE_COLLECTION = { + type: 'FeatureCollection', + features: [], +}; + +export const DRAW_TYPE = { + BOUNDS: 'BOUNDS', + POLYGON: 'POLYGON', +}; + +export enum AGG_TYPE { + AVG = 'avg', + COUNT = 'count', + MAX = 'max', + MIN = 'min', + SUM = 'sum', + TERMS = 'terms', + UNIQUE_COUNT = 'cardinality', +} + +export enum RENDER_AS { + HEATMAP = 'heatmap', + POINT = 'point', + GRID = 'grid', +} + +export enum GRID_RESOLUTION { + COARSE = 'COARSE', + FINE = 'FINE', + MOST_FINE = 'MOST_FINE', +} + +export const TOP_TERM_PERCENTAGE_SUFFIX = '__percentage'; + +export const COUNT_PROP_LABEL = i18n.translate('xpack.maps.aggs.defaultCountLabel', { + defaultMessage: 'count', +}); + +export const COUNT_PROP_NAME = 'doc_count'; + +export const STYLE_TYPE = { + STATIC: 'STATIC', + DYNAMIC: 'DYNAMIC', +}; + +export const LAYER_STYLE_TYPE = { + VECTOR: 'VECTOR', + HEATMAP: 'HEATMAP', +}; + +export const COLOR_MAP_TYPE = { + CATEGORICAL: 'CATEGORICAL', + ORDINAL: 'ORDINAL', +}; + +export const COLOR_PALETTE_MAX_SIZE = 10; + +export const CATEGORICAL_DATA_TYPES = ['string', 'ip', 'boolean']; +export const ORDINAL_DATA_TYPES = ['number', 'date']; + +export enum SYMBOLIZE_AS_TYPES { + CIRCLE = 'circle', + ICON = 'icon', +} + +export enum LABEL_BORDER_SIZES { + NONE = 'NONE', + SMALL = 'SMALL', + MEDIUM = 'MEDIUM', + LARGE = 'LARGE', +} + +export const DEFAULT_ICON = 'airfield'; diff --git a/x-pack/plugins/maps/common/i18n_getters.ts b/x-pack/plugins/maps/common/i18n_getters.ts new file mode 100644 index 00000000000000..0008a119f1c7cc --- /dev/null +++ b/x-pack/plugins/maps/common/i18n_getters.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +import { $Values } from '@kbn/utility-types'; +import { ES_SPATIAL_RELATIONS } from './constants'; + +export function getAppTitle() { + return i18n.translate('xpack.maps.appTitle', { + defaultMessage: 'Maps', + }); +} + +export function getDataSourceLabel() { + return i18n.translate('xpack.maps.source.dataSourceLabel', { + defaultMessage: 'Data source', + }); +} + +export function getUrlLabel() { + return i18n.translate('xpack.maps.source.urlLabel', { + defaultMessage: 'Url', + }); +} + +export function getEsSpatialRelationLabel(spatialRelation: $Values) { + switch (spatialRelation) { + case ES_SPATIAL_RELATIONS.INTERSECTS: + return i18n.translate('xpack.maps.common.esSpatialRelation.intersectsLabel', { + defaultMessage: 'intersects', + }); + case ES_SPATIAL_RELATIONS.DISJOINT: + return i18n.translate('xpack.maps.common.esSpatialRelation.disjointLabel', { + defaultMessage: 'disjoint', + }); + case ES_SPATIAL_RELATIONS.WITHIN: + return i18n.translate('xpack.maps.common.esSpatialRelation.withinLabel', { + defaultMessage: 'within', + }); + // @ts-ignore + case ES_SPATIAL_RELATIONS.CONTAINS: + return i18n.translate('xpack.maps.common.esSpatialRelation.containsLabel', { + defaultMessage: 'contains', + }); + default: + return spatialRelation; + } +} diff --git a/x-pack/plugins/maps/common/parse_xml_string.js b/x-pack/plugins/maps/common/parse_xml_string.js new file mode 100644 index 00000000000000..9d95e0e78280d5 --- /dev/null +++ b/x-pack/plugins/maps/common/parse_xml_string.js @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { parseString } from 'xml2js'; + +// promise based wrapper around parseString +export async function parseXmlString(xmlString) { + const parsePromise = new Promise((resolve, reject) => { + parseString(xmlString, (error, result) => { + if (error) { + reject(error); + } else { + resolve(result); + } + }); + }); + + return await parsePromise; +} diff --git a/x-pack/legacy/plugins/maps/common/parse_xml_string.test.js b/x-pack/plugins/maps/common/parse_xml_string.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/common/parse_xml_string.test.js rename to x-pack/plugins/maps/common/parse_xml_string.test.js diff --git a/x-pack/plugins/maps/kibana.json b/x-pack/plugins/maps/kibana.json new file mode 100644 index 00000000000000..b2aec30c113eb6 --- /dev/null +++ b/x-pack/plugins/maps/kibana.json @@ -0,0 +1,8 @@ +{ + "id": "maps", + "version": "8.0.0", + "kibanaVersion": "kibana", + "configPath": ["xpack", "maps"], + "requiredPlugins": ["inspector"], + "ui": true +} diff --git a/x-pack/plugins/maps/public/actions/map_actions.js b/x-pack/plugins/maps/public/actions/map_actions.js new file mode 100644 index 00000000000000..13cb3d5f898601 --- /dev/null +++ b/x-pack/plugins/maps/public/actions/map_actions.js @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const SET_SELECTED_LAYER = 'SET_SELECTED_LAYER'; +export const SET_TRANSIENT_LAYER = 'SET_TRANSIENT_LAYER'; +export const UPDATE_LAYER_ORDER = 'UPDATE_LAYER_ORDER'; +export const ADD_LAYER = 'ADD_LAYER'; +export const SET_LAYER_ERROR_STATUS = 'SET_LAYER_ERROR_STATUS'; +export const ADD_WAITING_FOR_MAP_READY_LAYER = 'ADD_WAITING_FOR_MAP_READY_LAYER'; +export const CLEAR_WAITING_FOR_MAP_READY_LAYER_LIST = 'CLEAR_WAITING_FOR_MAP_READY_LAYER_LIST'; +export const REMOVE_LAYER = 'REMOVE_LAYER'; +export const SET_LAYER_VISIBILITY = 'SET_LAYER_VISIBILITY'; +export const MAP_EXTENT_CHANGED = 'MAP_EXTENT_CHANGED'; +export const MAP_READY = 'MAP_READY'; +export const MAP_DESTROYED = 'MAP_DESTROYED'; +export const LAYER_DATA_LOAD_STARTED = 'LAYER_DATA_LOAD_STARTED'; +export const LAYER_DATA_LOAD_ENDED = 'LAYER_DATA_LOAD_ENDED'; +export const LAYER_DATA_LOAD_ERROR = 'LAYER_DATA_LOAD_ERROR'; +export const UPDATE_SOURCE_DATA_REQUEST = 'UPDATE_SOURCE_DATA_REQUEST'; +export const SET_JOINS = 'SET_JOINS'; +export const SET_QUERY = 'SET_QUERY'; +export const TRIGGER_REFRESH_TIMER = 'TRIGGER_REFRESH_TIMER'; +export const UPDATE_LAYER_PROP = 'UPDATE_LAYER_PROP'; +export const UPDATE_LAYER_STYLE = 'UPDATE_LAYER_STYLE'; +export const SET_LAYER_STYLE_META = 'SET_LAYER_STYLE_META'; +export const UPDATE_SOURCE_PROP = 'UPDATE_SOURCE_PROP'; +export const SET_REFRESH_CONFIG = 'SET_REFRESH_CONFIG'; +export const SET_MOUSE_COORDINATES = 'SET_MOUSE_COORDINATES'; +export const CLEAR_MOUSE_COORDINATES = 'CLEAR_MOUSE_COORDINATES'; +export const SET_GOTO = 'SET_GOTO'; +export const CLEAR_GOTO = 'CLEAR_GOTO'; +export const TRACK_CURRENT_LAYER_STATE = 'TRACK_CURRENT_LAYER_STATE'; +export const ROLLBACK_TO_TRACKED_LAYER_STATE = 'ROLLBACK_TO_TRACKED_LAYER_STATE'; +export const REMOVE_TRACKED_LAYER_STATE = 'REMOVE_TRACKED_LAYER_STATE'; +export const SET_OPEN_TOOLTIPS = 'SET_OPEN_TOOLTIPS'; +export const UPDATE_DRAW_STATE = 'UPDATE_DRAW_STATE'; +export const SET_SCROLL_ZOOM = 'SET_SCROLL_ZOOM'; +export const SET_MAP_INIT_ERROR = 'SET_MAP_INIT_ERROR'; +export const SET_INTERACTIVE = 'SET_INTERACTIVE'; +export const DISABLE_TOOLTIP_CONTROL = 'DISABLE_TOOLTIP_CONTROL'; +export const HIDE_TOOLBAR_OVERLAY = 'HIDE_TOOLBAR_OVERLAY'; +export const HIDE_LAYER_CONTROL = 'HIDE_LAYER_CONTROL'; +export const HIDE_VIEW_CONTROL = 'HIDE_VIEW_CONTROL'; +export const SET_WAITING_FOR_READY_HIDDEN_LAYERS = 'SET_WAITING_FOR_READY_HIDDEN_LAYERS'; diff --git a/x-pack/plugins/maps/public/actions/ui_actions.js b/x-pack/plugins/maps/public/actions/ui_actions.js new file mode 100644 index 00000000000000..59ae56c15056a0 --- /dev/null +++ b/x-pack/plugins/maps/public/actions/ui_actions.js @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const UPDATE_FLYOUT = 'UPDATE_FLYOUT'; +export const CLOSE_SET_VIEW = 'CLOSE_SET_VIEW'; +export const OPEN_SET_VIEW = 'OPEN_SET_VIEW'; +export const SET_IS_LAYER_TOC_OPEN = 'SET_IS_LAYER_TOC_OPEN'; +export const SET_FULL_SCREEN = 'SET_FULL_SCREEN'; +export const SET_READ_ONLY = 'SET_READ_ONLY'; +export const SET_OPEN_TOC_DETAILS = 'SET_OPEN_TOC_DETAILS'; +export const SHOW_TOC_DETAILS = 'SHOW_TOC_DETAILS'; +export const HIDE_TOC_DETAILS = 'HIDE_TOC_DETAILS'; +export const UPDATE_INDEXING_STAGE = 'UPDATE_INDEXING_STAGE'; diff --git a/x-pack/plugins/maps/public/index.ts b/x-pack/plugins/maps/public/index.ts new file mode 100644 index 00000000000000..c465700a4f9c56 --- /dev/null +++ b/x-pack/plugins/maps/public/index.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { PluginInitializer } from 'kibana/public'; +import { MapsPlugin, MapsPluginSetup, MapsPluginStart } from './plugin'; + +export const plugin: PluginInitializer = () => { + return new MapsPlugin(); +}; diff --git a/x-pack/legacy/plugins/maps/public/inspector/adapters/map_adapter.js b/x-pack/plugins/maps/public/inspector/adapters/map_adapter.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/inspector/adapters/map_adapter.js rename to x-pack/plugins/maps/public/inspector/adapters/map_adapter.js diff --git a/x-pack/legacy/plugins/maps/public/inspector/views/map_details.js b/x-pack/plugins/maps/public/inspector/views/map_details.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/inspector/views/map_details.js rename to x-pack/plugins/maps/public/inspector/views/map_details.js diff --git a/x-pack/legacy/plugins/maps/public/inspector/views/map_view.js b/x-pack/plugins/maps/public/inspector/views/map_view.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/inspector/views/map_view.js rename to x-pack/plugins/maps/public/inspector/views/map_view.js diff --git a/x-pack/legacy/plugins/maps/public/inspector/views/register_views.ts b/x-pack/plugins/maps/public/kibana_services.js similarity index 58% rename from x-pack/legacy/plugins/maps/public/inspector/views/register_views.ts rename to x-pack/plugins/maps/public/kibana_services.js index 59c05956683007..1073e44fa711e7 100644 --- a/x-pack/legacy/plugins/maps/public/inspector/views/register_views.ts +++ b/x-pack/plugins/maps/public/kibana_services.js @@ -4,9 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { npSetup } from 'ui/new_platform'; - -// @ts-ignore -import { MapView } from './map_view'; - -npSetup.plugins.inspector.registerView(MapView); +let getInjectedVar; +export const setInjectedVarFunc = getInjectedVarFunc => (getInjectedVar = getInjectedVarFunc); +export const getInjectedVarFunc = () => getInjectedVar; diff --git a/x-pack/plugins/maps/public/plugin.ts b/x-pack/plugins/maps/public/plugin.ts new file mode 100644 index 00000000000000..506b0c426f0fae --- /dev/null +++ b/x-pack/plugins/maps/public/plugin.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Plugin, CoreSetup, CoreStart } from 'src/core/public'; +import { Setup as InspectorSetupContract } from 'src/plugins/inspector/public'; +// @ts-ignore +import { MapView } from './inspector/views/map_view'; + +export interface MapsPluginSetupDependencies { + inspector: InspectorSetupContract; +} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface MapsPluginStartDependencies {} + +/** + * These are the interfaces with your public contracts. You should export these + * for other plugins to use in _their_ `SetupDeps`/`StartDeps` interfaces. + * @public + */ +export type MapsPluginSetup = ReturnType; +export type MapsPluginStart = ReturnType; + +/** @internal */ +export class MapsPlugin + implements + Plugin< + MapsPluginSetup, + MapsPluginStart, + MapsPluginSetupDependencies, + MapsPluginStartDependencies + > { + public setup(core: CoreSetup, plugins: MapsPluginSetupDependencies) { + plugins.inspector.registerView(MapView); + } + + public start(core: CoreStart, plugins: any) {} +} diff --git a/x-pack/legacy/plugins/maps/public/reducers/map.js b/x-pack/plugins/maps/public/reducers/map.js similarity index 97% rename from x-pack/legacy/plugins/maps/public/reducers/map.js rename to x-pack/plugins/maps/public/reducers/map.js index 7e81fb03dd85be..7e07569b44b830 100644 --- a/x-pack/legacy/plugins/maps/public/reducers/map.js +++ b/x-pack/plugins/maps/public/reducers/map.js @@ -25,7 +25,6 @@ import { UPDATE_LAYER_STYLE, SET_LAYER_STYLE_META, SET_JOINS, - TOUCH_LAYER, UPDATE_SOURCE_PROP, SET_REFRESH_CONFIG, TRIGGER_REFRESH_TIMER, @@ -202,17 +201,6 @@ export function map(state = INITIAL_STATE, action) { return updateWithDataResponse(state, action); case LAYER_DATA_LOAD_ENDED: return updateWithDataResponse(state, action); - case TOUCH_LAYER: - //action to enforce a reflow of the styles - const layer = state.layerList.find(layer => layer.id === action.layerId); - if (!layer) { - return state; - } - const indexOfLayer = state.layerList.indexOf(layer); - const newLayer = { ...layer }; - const newLayerList = [...state.layerList]; - newLayerList[indexOfLayer] = newLayer; - return { ...state, layerList: newLayerList }; case MAP_READY: return { ...state, ready: true }; case MAP_DESTROYED: diff --git a/x-pack/legacy/plugins/maps/public/reducers/map.test.js b/x-pack/plugins/maps/public/reducers/map.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/reducers/map.test.js rename to x-pack/plugins/maps/public/reducers/map.test.js diff --git a/x-pack/legacy/plugins/maps/public/reducers/non_serializable_instances.js b/x-pack/plugins/maps/public/reducers/non_serializable_instances.js similarity index 90% rename from x-pack/legacy/plugins/maps/public/reducers/non_serializable_instances.js rename to x-pack/plugins/maps/public/reducers/non_serializable_instances.js index c7de2beff0cf6a..8265c70ae18001 100644 --- a/x-pack/legacy/plugins/maps/public/reducers/non_serializable_instances.js +++ b/x-pack/plugins/maps/public/reducers/non_serializable_instances.js @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import chrome from 'ui/chrome'; -import { RequestAdapter } from '../../../../../../src/plugins/inspector/public'; +import { RequestAdapter } from '../../../../../src/plugins/inspector/common/adapters/request'; import { MapAdapter } from '../inspector/adapters/map_adapter'; +import { getInjectedVarFunc } from '../kibana_services'; const REGISTER_CANCEL_CALLBACK = 'REGISTER_CANCEL_CALLBACK'; const UNREGISTER_CANCEL_CALLBACK = 'UNREGISTER_CANCEL_CALLBACK'; @@ -16,7 +16,8 @@ function createInspectorAdapters() { const inspectorAdapters = { requests: new RequestAdapter(), }; - if (chrome.getInjected('showMapsInspectorAdapter', false)) { + const getInjectedVar = getInjectedVarFunc(); + if (getInjectedVar && getInjectedVar('showMapsInspectorAdapter', false)) { inspectorAdapters.map = new MapAdapter(); } return inspectorAdapters; diff --git a/x-pack/legacy/plugins/maps/public/reducers/store.js b/x-pack/plugins/maps/public/reducers/store.js similarity index 81% rename from x-pack/legacy/plugins/maps/public/reducers/store.js rename to x-pack/plugins/maps/public/reducers/store.js index 40b769f11b5292..b5c4bb435e7256 100644 --- a/x-pack/legacy/plugins/maps/public/reducers/store.js +++ b/x-pack/plugins/maps/public/reducers/store.js @@ -10,15 +10,13 @@ import { ui } from './ui'; import { map } from './map'; import { nonSerializableInstances } from './non_serializable_instances'; -const rootReducer = combineReducers({ - map, - ui, - nonSerializableInstances, -}); - -const enhancers = [applyMiddleware(thunk)]; - export function createMapStore() { + const enhancers = [applyMiddleware(thunk)]; + const rootReducer = combineReducers({ + map, + ui, + nonSerializableInstances, + }); const storeConfig = {}; return createStore(rootReducer, storeConfig, compose(...enhancers)); } diff --git a/x-pack/legacy/plugins/maps/public/reducers/ui.js b/x-pack/plugins/maps/public/reducers/ui.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/reducers/ui.js rename to x-pack/plugins/maps/public/reducers/ui.js diff --git a/x-pack/legacy/plugins/maps/public/reducers/util.js b/x-pack/plugins/maps/public/reducers/util.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/reducers/util.js rename to x-pack/plugins/maps/public/reducers/util.js diff --git a/x-pack/legacy/plugins/maps/public/reducers/util.test.js b/x-pack/plugins/maps/public/reducers/util.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/reducers/util.test.js rename to x-pack/plugins/maps/public/reducers/util.test.js From 5605dfa54d0c31d2aae7b87f12abd17ca6f8d12d Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Thu, 12 Mar 2020 17:29:17 +0100 Subject: [PATCH 07/19] [Discover] Migrate Context mocha tests to use Jest (#59658) --- .../__tests__/query_parameters/_utils.js | 34 ---- .../query_parameters/action_add_filter.js | 65 -------- .../action_set_predecessor_count.js | 64 ------- .../action_set_query_parameters.js | 82 --------- .../action_set_successor_count.js | 65 -------- .../context/api/{__tests__ => }/_stubs.js | 2 +- .../{__tests__/anchor.js => anchor.test.js} | 49 +++--- ...essors.js => context.predecessors.test.js} | 98 +++++------ ...ccessors.js => context.successors.test.js} | 96 +++++------ .../np_ready/angular/context/api/context.ts | 8 +- .../api/utils/fetch_hits_in_interval.ts | 6 +- .../context/api/utils/generate_intervals.ts | 2 +- .../np_ready/angular/context/query/actions.js | 10 +- .../context/query_parameters/actions.js | 11 +- .../context/query_parameters/actions.test.ts | 157 ++++++++++++++++++ .../discover/np_ready/angular/context_app.js | 5 +- 16 files changed, 293 insertions(+), 461 deletions(-) delete mode 100644 src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/_utils.js delete mode 100644 src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_add_filter.js delete mode 100644 src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_predecessor_count.js delete mode 100644 src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_query_parameters.js delete mode 100644 src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_successor_count.js rename src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/{__tests__ => }/_stubs.js (97%) rename src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/{__tests__/anchor.js => anchor.test.js} (73%) rename src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/{__tests__/predecessors.js => context.predecessors.test.js} (74%) rename src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/{__tests__/successors.js => context.successors.test.js} (74%) create mode 100644 src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query_parameters/actions.test.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/_utils.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/_utils.js deleted file mode 100644 index 63f8ced97e9dc4..00000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/_utils.js +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; - -export function createStateStub(overrides) { - return _.merge( - { - queryParameters: { - defaultStepSize: 3, - indexPatternId: 'INDEX_PATTERN_ID', - predecessorCount: 10, - successorCount: 10, - }, - }, - overrides - ); -} diff --git a/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_add_filter.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_add_filter.js deleted file mode 100644 index 87eb283639c789..00000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_add_filter.js +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { createStateStub } from './_utils'; -import { getQueryParameterActions } from '../../np_ready/angular/context/query_parameters/actions'; -import { pluginInstance } from 'plugins/kibana/discover/legacy'; -import { npStart } from 'ui/new_platform'; - -describe('context app', function() { - beforeEach(() => pluginInstance.initializeInnerAngular()); - beforeEach(() => pluginInstance.initializeServices()); - beforeEach(ngMock.module('app/discover')); - - describe('action addFilter', function() { - let addFilter; - - beforeEach( - ngMock.inject(function createPrivateStubs() { - addFilter = getQueryParameterActions().addFilter; - }) - ); - - it('should pass the given arguments to the filterManager', function() { - const state = createStateStub(); - const filterManagerAddStub = npStart.plugins.data.query.filterManager.addFilters; - - addFilter(state)('FIELD_NAME', 'FIELD_VALUE', 'FILTER_OPERATION'); - - //get the generated filter - const generatedFilter = filterManagerAddStub.firstCall.args[0][0]; - const queryKeys = Object.keys(generatedFilter.query.match_phrase); - expect(filterManagerAddStub.calledOnce).to.be(true); - expect(queryKeys[0]).to.eql('FIELD_NAME'); - expect(generatedFilter.query.match_phrase[queryKeys[0]]).to.eql('FIELD_VALUE'); - }); - - it('should pass the index pattern id to the filterManager', function() { - const state = createStateStub(); - const filterManagerAddStub = npStart.plugins.data.query.filterManager.addFilters; - - addFilter(state)('FIELD_NAME', 'FIELD_VALUE', 'FILTER_OPERATION'); - - const generatedFilter = filterManagerAddStub.firstCall.args[0][0]; - expect(generatedFilter.meta.index).to.eql('INDEX_PATTERN_ID'); - }); - }); -}); diff --git a/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_predecessor_count.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_predecessor_count.js deleted file mode 100644 index 9ba425bb0e489e..00000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_predecessor_count.js +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { pluginInstance } from 'plugins/kibana/discover/legacy'; -import { createStateStub } from './_utils'; -import { getQueryParameterActions } from '../../np_ready/angular/context/query_parameters/actions'; - -describe('context app', function() { - beforeEach(() => pluginInstance.initializeInnerAngular()); - beforeEach(() => pluginInstance.initializeServices()); - beforeEach(ngMock.module('app/discover')); - - describe('action setPredecessorCount', function() { - let setPredecessorCount; - - beforeEach( - ngMock.inject(function createPrivateStubs() { - setPredecessorCount = getQueryParameterActions().setPredecessorCount; - }) - ); - - it('should set the predecessorCount to the given value', function() { - const state = createStateStub(); - - setPredecessorCount(state)(20); - - expect(state.queryParameters.predecessorCount).to.equal(20); - }); - - it('should limit the predecessorCount to 0 as a lower bound', function() { - const state = createStateStub(); - - setPredecessorCount(state)(-1); - - expect(state.queryParameters.predecessorCount).to.equal(0); - }); - - it('should limit the predecessorCount to 10000 as an upper bound', function() { - const state = createStateStub(); - - setPredecessorCount(state)(20000); - - expect(state.queryParameters.predecessorCount).to.equal(10000); - }); - }); -}); diff --git a/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_query_parameters.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_query_parameters.js deleted file mode 100644 index 39dde2d8bb7cf0..00000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_query_parameters.js +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { pluginInstance } from 'plugins/kibana/discover/legacy'; - -import { createStateStub } from './_utils'; -import { getQueryParameterActions } from '../../np_ready/angular/context/query_parameters/actions'; - -describe('context app', function() { - beforeEach(() => pluginInstance.initializeInnerAngular()); - beforeEach(() => pluginInstance.initializeServices()); - beforeEach(ngMock.module('app/discover')); - - describe('action setQueryParameters', function() { - let setQueryParameters; - - beforeEach( - ngMock.inject(function createPrivateStubs() { - setQueryParameters = getQueryParameterActions().setQueryParameters; - }) - ); - - it('should update the queryParameters with valid properties from the given object', function() { - const state = createStateStub({ - queryParameters: { - additionalParameter: 'ADDITIONAL_PARAMETER', - }, - }); - - setQueryParameters(state)({ - anchorId: 'ANCHOR_ID', - columns: ['column'], - defaultStepSize: 3, - filters: ['filter'], - indexPatternId: 'INDEX_PATTERN', - predecessorCount: 100, - successorCount: 100, - sort: ['field'], - }); - - expect(state.queryParameters).to.eql({ - additionalParameter: 'ADDITIONAL_PARAMETER', - anchorId: 'ANCHOR_ID', - columns: ['column'], - defaultStepSize: 3, - filters: ['filter'], - indexPatternId: 'INDEX_PATTERN', - predecessorCount: 100, - successorCount: 100, - sort: ['field'], - }); - }); - - it('should ignore invalid properties', function() { - const state = createStateStub(); - - setQueryParameters(state)({ - additionalParameter: 'ADDITIONAL_PARAMETER', - }); - - expect(state.queryParameters).to.eql(createStateStub().queryParameters); - }); - }); -}); diff --git a/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_successor_count.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_successor_count.js deleted file mode 100644 index c05f5b4aff3bc7..00000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/__tests__/query_parameters/action_set_successor_count.js +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { pluginInstance } from 'plugins/kibana/discover/legacy'; - -import { createStateStub } from './_utils'; -import { getQueryParameterActions } from '../../np_ready/angular/context/query_parameters/actions'; - -describe('context app', function() { - beforeEach(() => pluginInstance.initializeInnerAngular()); - beforeEach(() => pluginInstance.initializeServices()); - beforeEach(ngMock.module('app/discover')); - - describe('action setSuccessorCount', function() { - let setSuccessorCount; - - beforeEach( - ngMock.inject(function createPrivateStubs() { - setSuccessorCount = getQueryParameterActions().setSuccessorCount; - }) - ); - - it('should set the successorCount to the given value', function() { - const state = createStateStub(); - - setSuccessorCount(state)(20); - - expect(state.queryParameters.successorCount).to.equal(20); - }); - - it('should limit the successorCount to 0 as a lower bound', function() { - const state = createStateStub(); - - setSuccessorCount(state)(-1); - - expect(state.queryParameters.successorCount).to.equal(0); - }); - - it('should limit the successorCount to 10000 as an upper bound', function() { - const state = createStateStub(); - - setSuccessorCount(state)(20000); - - expect(state.queryParameters.successorCount).to.equal(10000); - }); - }); -}); diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/_stubs.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/_stubs.js similarity index 97% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/_stubs.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/_stubs.js index 53be4e5bd0f2d0..f6ed570be2c373 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/_stubs.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/_stubs.js @@ -19,7 +19,7 @@ import sinon from 'sinon'; import moment from 'moment'; -import { SearchSource } from '../../../../../kibana_services'; +import { SearchSource } from '../../../../../../../../../plugins/data/public'; export function createIndexPatternsStub() { return { diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/anchor.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/anchor.test.js similarity index 73% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/anchor.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/anchor.test.js index 63834fb750e214..0bc2cbacc1eeec 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/anchor.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/anchor.test.js @@ -17,28 +17,19 @@ * under the License. */ -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { pluginInstance } from 'plugins/kibana/discover/legacy'; - import { createIndexPatternsStub, createSearchSourceStub } from './_stubs'; -import { fetchAnchorProvider } from '../anchor'; +import { fetchAnchorProvider } from './anchor'; describe('context app', function() { - beforeEach(() => pluginInstance.initializeInnerAngular()); - beforeEach(ngMock.module('app/discover')); - describe('function fetchAnchor', function() { let fetchAnchor; let searchSourceStub; - beforeEach( - ngMock.inject(function createPrivateStubs() { - searchSourceStub = createSearchSourceStub([{ _id: 'hit1' }]); - fetchAnchor = fetchAnchorProvider(createIndexPatternsStub(), searchSourceStub); - }) - ); + beforeEach(() => { + searchSourceStub = createSearchSourceStub([{ _id: 'hit1' }]); + fetchAnchor = fetchAnchorProvider(createIndexPatternsStub(), searchSourceStub); + }); afterEach(() => { searchSourceStub._restore(); @@ -49,7 +40,7 @@ describe('context app', function() { { '@timestamp': 'desc' }, { _doc: 'desc' }, ]).then(() => { - expect(searchSourceStub.fetch.calledOnce).to.be(true); + expect(searchSourceStub.fetch.calledOnce).toBe(true); }); }); @@ -59,8 +50,8 @@ describe('context app', function() { { _doc: 'desc' }, ]).then(() => { const setParentSpy = searchSourceStub.setParent; - expect(setParentSpy.calledOnce).to.be(true); - expect(setParentSpy.firstCall.args[0]).to.be(undefined); + expect(setParentSpy.calledOnce).toBe(true); + expect(setParentSpy.firstCall.args[0]).toBe(undefined); }); }); @@ -70,7 +61,7 @@ describe('context app', function() { { _doc: 'desc' }, ]).then(() => { const setFieldSpy = searchSourceStub.setField; - expect(setFieldSpy.firstCall.args[1].id).to.eql('INDEX_PATTERN_ID'); + expect(setFieldSpy.firstCall.args[1].id).toEqual('INDEX_PATTERN_ID'); }); }); @@ -80,8 +71,8 @@ describe('context app', function() { { _doc: 'desc' }, ]).then(() => { const setVersionSpy = searchSourceStub.setField.withArgs('version'); - expect(setVersionSpy.calledOnce).to.be(true); - expect(setVersionSpy.firstCall.args[1]).to.eql(true); + expect(setVersionSpy.calledOnce).toBe(true); + expect(setVersionSpy.firstCall.args[1]).toEqual(true); }); }); @@ -91,8 +82,8 @@ describe('context app', function() { { _doc: 'desc' }, ]).then(() => { const setSizeSpy = searchSourceStub.setField.withArgs('size'); - expect(setSizeSpy.calledOnce).to.be(true); - expect(setSizeSpy.firstCall.args[1]).to.eql(1); + expect(setSizeSpy.calledOnce).toBe(true); + expect(setSizeSpy.firstCall.args[1]).toEqual(1); }); }); @@ -102,8 +93,8 @@ describe('context app', function() { { _doc: 'desc' }, ]).then(() => { const setQuerySpy = searchSourceStub.setField.withArgs('query'); - expect(setQuerySpy.calledOnce).to.be(true); - expect(setQuerySpy.firstCall.args[1]).to.eql({ + expect(setQuerySpy.calledOnce).toBe(true); + expect(setQuerySpy.firstCall.args[1]).toEqual({ query: { constant_score: { filter: { @@ -124,8 +115,8 @@ describe('context app', function() { { _doc: 'desc' }, ]).then(() => { const setSortSpy = searchSourceStub.setField.withArgs('sort'); - expect(setSortSpy.calledOnce).to.be(true); - expect(setSortSpy.firstCall.args[1]).to.eql([{ '@timestamp': 'desc' }, { _doc: 'desc' }]); + expect(setSortSpy.calledOnce).toBe(true); + expect(setSortSpy.firstCall.args[1]).toEqual([{ '@timestamp': 'desc' }, { _doc: 'desc' }]); }); }); @@ -140,7 +131,7 @@ describe('context app', function() { expect().fail('expected the promise to be rejected'); }, error => { - expect(error).to.be.an(Error); + expect(error).toBeInstanceOf(Error); } ); }); @@ -152,8 +143,8 @@ describe('context app', function() { { '@timestamp': 'desc' }, { _doc: 'desc' }, ]).then(anchorDocument => { - expect(anchorDocument).to.have.property('property1', 'value1'); - expect(anchorDocument).to.have.property('$$_isAnchor', true); + expect(anchorDocument).toHaveProperty('property1', 'value1'); + expect(anchorDocument).toHaveProperty('$$_isAnchor', true); }); }); }); diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/predecessors.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.predecessors.test.js similarity index 74% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/predecessors.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.predecessors.test.js index 02d998e8f4529f..d6e91e57b22a8c 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/predecessors.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.predecessors.test.js @@ -17,15 +17,10 @@ * under the License. */ -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; import moment from 'moment'; import * as _ from 'lodash'; -import { pluginInstance } from 'plugins/kibana/discover/legacy'; - import { createIndexPatternsStub, createContextSearchSourceStub } from './_stubs'; - -import { fetchContextProvider } from '../context'; +import { fetchContextProvider } from './context'; const MS_PER_DAY = 24 * 60 * 60 * 1000; const ANCHOR_TIMESTAMP = new Date(MS_PER_DAY).toJSON(); @@ -34,46 +29,41 @@ const ANCHOR_TIMESTAMP_1000 = new Date(MS_PER_DAY * 1000).toJSON(); const ANCHOR_TIMESTAMP_3000 = new Date(MS_PER_DAY * 3000).toJSON(); describe('context app', function() { - beforeEach(() => pluginInstance.initializeInnerAngular()); - beforeEach(ngMock.module('app/discover')); - describe('function fetchPredecessors', function() { let fetchPredecessors; let searchSourceStub; - beforeEach( - ngMock.inject(function createPrivateStubs() { - searchSourceStub = createContextSearchSourceStub([], '@timestamp', MS_PER_DAY * 8); - fetchPredecessors = ( + beforeEach(() => { + searchSourceStub = createContextSearchSourceStub([], '@timestamp', MS_PER_DAY * 8); + fetchPredecessors = ( + indexPatternId, + timeField, + sortDir, + timeValIso, + timeValNr, + tieBreakerField, + tieBreakerValue, + size + ) => { + const anchor = { + _source: { + [timeField]: timeValIso, + }, + sort: [timeValNr, tieBreakerValue], + }; + + return fetchContextProvider(createIndexPatternsStub()).fetchSurroundingDocs( + 'predecessors', indexPatternId, + anchor, timeField, - sortDir, - timeValIso, - timeValNr, tieBreakerField, - tieBreakerValue, - size - ) => { - const anchor = { - _source: { - [timeField]: timeValIso, - }, - sort: [timeValNr, tieBreakerValue], - }; - - return fetchContextProvider(createIndexPatternsStub()).fetchSurroundingDocs( - 'predecessors', - indexPatternId, - anchor, - timeField, - tieBreakerField, - sortDir, - size, - [] - ); - }; - }) - ); + sortDir, + size, + [] + ); + }; + }); afterEach(() => { searchSourceStub._restore(); @@ -99,8 +89,8 @@ describe('context app', function() { 3, [] ).then(hits => { - expect(searchSourceStub.fetch.calledOnce).to.be(true); - expect(hits).to.eql(searchSourceStub._stubHits.slice(0, 3)); + expect(searchSourceStub.fetch.calledOnce).toBe(true); + expect(hits).toEqual(searchSourceStub._stubHits.slice(0, 3)); }); }); @@ -132,14 +122,14 @@ describe('context app', function() { expect( intervals.every(({ gte, lte }) => (gte && lte ? moment(gte).isBefore(lte) : true)) - ).to.be(true); + ).toBe(true); // should have started at the given time - expect(intervals[0].gte).to.eql(moment(MS_PER_DAY * 3000).toISOString()); + expect(intervals[0].gte).toEqual(moment(MS_PER_DAY * 3000).toISOString()); // should have ended with a half-open interval - expect(_.last(intervals)).to.only.have.keys('gte', 'format'); - expect(intervals.length).to.be.greaterThan(1); + expect(Object.keys(_.last(intervals))).toEqual(['format', 'gte']); + expect(intervals.length).toBeGreaterThan(1); - expect(hits).to.eql(searchSourceStub._stubHits.slice(0, 3)); + expect(hits).toEqual(searchSourceStub._stubHits.slice(0, 3)); }); }); @@ -169,11 +159,11 @@ describe('context app', function() { ); // should have started at the given time - expect(intervals[0].gte).to.eql(moment(MS_PER_DAY * 1000).toISOString()); + expect(intervals[0].gte).toEqual(moment(MS_PER_DAY * 1000).toISOString()); // should have stopped before reaching MS_PER_DAY * 1700 - expect(moment(_.last(intervals).lte).valueOf()).to.be.lessThan(MS_PER_DAY * 1700); - expect(intervals.length).to.be.greaterThan(1); - expect(hits).to.eql(searchSourceStub._stubHits.slice(-3)); + expect(moment(_.last(intervals).lte).valueOf()).toBeLessThan(MS_PER_DAY * 1700); + expect(intervals.length).toBeGreaterThan(1); + expect(hits).toEqual(searchSourceStub._stubHits.slice(-3)); }); }); @@ -189,7 +179,7 @@ describe('context app', function() { 3, [] ).then(hits => { - expect(hits).to.eql([]); + expect(hits).toEqual([]); }); }); @@ -206,8 +196,8 @@ describe('context app', function() { [] ).then(() => { const setParentSpy = searchSourceStub.setParent; - expect(setParentSpy.alwaysCalledWith(undefined)).to.be(true); - expect(setParentSpy.called).to.be(true); + expect(setParentSpy.alwaysCalledWith(undefined)).toBe(true); + expect(setParentSpy.called).toBe(true); }); }); @@ -225,7 +215,7 @@ describe('context app', function() { ).then(() => { expect( searchSourceStub.setField.calledWith('sort', [{ '@timestamp': 'asc' }, { _doc: 'asc' }]) - ).to.be(true); + ).toBe(true); }); }); }); diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/successors.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.successors.test.js similarity index 74% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/successors.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.successors.test.js index d4c00930c93839..cc2b6d31cb43b5 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/__tests__/successors.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.successors.test.js @@ -17,15 +17,12 @@ * under the License. */ -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; import moment from 'moment'; import * as _ from 'lodash'; -import { pluginInstance } from 'plugins/kibana/discover/legacy'; import { createIndexPatternsStub, createContextSearchSourceStub } from './_stubs'; -import { fetchContextProvider } from '../context'; +import { fetchContextProvider } from './context'; const MS_PER_DAY = 24 * 60 * 60 * 1000; const ANCHOR_TIMESTAMP = new Date(MS_PER_DAY).toJSON(); @@ -33,47 +30,42 @@ const ANCHOR_TIMESTAMP_3 = new Date(MS_PER_DAY * 3).toJSON(); const ANCHOR_TIMESTAMP_3000 = new Date(MS_PER_DAY * 3000).toJSON(); describe('context app', function() { - beforeEach(() => pluginInstance.initializeInnerAngular()); - beforeEach(ngMock.module('app/discover')); - describe('function fetchSuccessors', function() { let fetchSuccessors; let searchSourceStub; - beforeEach( - ngMock.inject(function createPrivateStubs() { - searchSourceStub = createContextSearchSourceStub([], '@timestamp'); + beforeEach(() => { + searchSourceStub = createContextSearchSourceStub([], '@timestamp'); + + fetchSuccessors = ( + indexPatternId, + timeField, + sortDir, + timeValIso, + timeValNr, + tieBreakerField, + tieBreakerValue, + size + ) => { + const anchor = { + _source: { + [timeField]: timeValIso, + }, + sort: [timeValNr, tieBreakerValue], + }; - fetchSuccessors = ( + return fetchContextProvider(createIndexPatternsStub()).fetchSurroundingDocs( + 'successors', indexPatternId, + anchor, timeField, - sortDir, - timeValIso, - timeValNr, tieBreakerField, - tieBreakerValue, - size - ) => { - const anchor = { - _source: { - [timeField]: timeValIso, - }, - sort: [timeValNr, tieBreakerValue], - }; - - return fetchContextProvider(createIndexPatternsStub()).fetchSurroundingDocs( - 'successors', - indexPatternId, - anchor, - timeField, - tieBreakerField, - sortDir, - size, - [] - ); - }; - }) - ); + sortDir, + size, + [] + ); + }; + }); afterEach(() => { searchSourceStub._restore(); @@ -99,8 +91,8 @@ describe('context app', function() { 3, [] ).then(hits => { - expect(searchSourceStub.fetch.calledOnce).to.be(true); - expect(hits).to.eql(searchSourceStub._stubHits.slice(-3)); + expect(searchSourceStub.fetch.calledOnce).toBe(true); + expect(hits).toEqual(searchSourceStub._stubHits.slice(-3)); }); }); @@ -132,14 +124,14 @@ describe('context app', function() { expect( intervals.every(({ gte, lte }) => (gte && lte ? moment(gte).isBefore(lte) : true)) - ).to.be(true); + ).toBe(true); // should have started at the given time - expect(intervals[0].lte).to.eql(moment(MS_PER_DAY * 3000).toISOString()); + expect(intervals[0].lte).toEqual(moment(MS_PER_DAY * 3000).toISOString()); // should have ended with a half-open interval - expect(_.last(intervals)).to.only.have.keys('lte', 'format'); - expect(intervals.length).to.be.greaterThan(1); + expect(Object.keys(_.last(intervals))).toEqual(['format', 'lte']); + expect(intervals.length).toBeGreaterThan(1); - expect(hits).to.eql(searchSourceStub._stubHits.slice(-3)); + expect(hits).toEqual(searchSourceStub._stubHits.slice(-3)); }); }); @@ -171,12 +163,12 @@ describe('context app', function() { ); // should have started at the given time - expect(intervals[0].lte).to.eql(moment(MS_PER_DAY * 3000).toISOString()); + expect(intervals[0].lte).toEqual(moment(MS_PER_DAY * 3000).toISOString()); // should have stopped before reaching MS_PER_DAY * 2200 - expect(moment(_.last(intervals).gte).valueOf()).to.be.greaterThan(MS_PER_DAY * 2200); - expect(intervals.length).to.be.greaterThan(1); + expect(moment(_.last(intervals).gte).valueOf()).toBeGreaterThan(MS_PER_DAY * 2200); + expect(intervals.length).toBeGreaterThan(1); - expect(hits).to.eql(searchSourceStub._stubHits.slice(0, 4)); + expect(hits).toEqual(searchSourceStub._stubHits.slice(0, 4)); }); }); @@ -192,7 +184,7 @@ describe('context app', function() { 3, [] ).then(hits => { - expect(hits).to.eql([]); + expect(hits).toEqual([]); }); }); @@ -209,8 +201,8 @@ describe('context app', function() { [] ).then(() => { const setParentSpy = searchSourceStub.setParent; - expect(setParentSpy.alwaysCalledWith(undefined)).to.be(true); - expect(setParentSpy.called).to.be(true); + expect(setParentSpy.alwaysCalledWith(undefined)).toBe(true); + expect(setParentSpy.called).toBe(true); }); }); @@ -228,7 +220,7 @@ describe('context app', function() { ).then(() => { expect( searchSourceStub.setField.calledWith('sort', [{ '@timestamp': 'desc' }, { _doc: 'desc' }]) - ).to.be(true); + ).toBe(true); }); }); }); diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.ts index b91ef5a6b79fbb..507f927c608e1b 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.ts @@ -17,14 +17,18 @@ * under the License. */ -import { IndexPattern, SearchSource } from '../../../../kibana_services'; import { reverseSortDir, SortDirection } from './utils/sorting'; import { extractNanos, convertIsoToMillis } from './utils/date_conversion'; import { fetchHitsInInterval } from './utils/fetch_hits_in_interval'; import { generateIntervals } from './utils/generate_intervals'; import { getEsQuerySearchAfter } from './utils/get_es_query_search_after'; import { getEsQuerySort } from './utils/get_es_query_sort'; -import { Filter, IndexPatternsContract } from '../../../../../../../../../plugins/data/public'; +import { + Filter, + IndexPatternsContract, + IndexPattern, + SearchSource, +} from '../../../../../../../../../plugins/data/public'; export type SurrDocType = 'successors' | 'predecessors'; export interface EsHitRecord { diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/fetch_hits_in_interval.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/fetch_hits_in_interval.ts index e7df44e6fe61c7..8eed5d33ab004b 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/fetch_hits_in_interval.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/fetch_hits_in_interval.ts @@ -16,7 +16,11 @@ * specific language governing permissions and limitations * under the License. */ -import { EsQuerySortValue, SortDirection, ISearchSource } from '../../../../../kibana_services'; +import { + ISearchSource, + EsQuerySortValue, + SortDirection, +} from '../../../../../../../../../../plugins/data/public'; import { convertTimeValueToIso } from './date_conversion'; import { EsHitRecordList } from '../context'; import { IntervalValue } from './generate_intervals'; diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/generate_intervals.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/generate_intervals.ts index 373dc37e56f6f3..b14180d32b4f24 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/generate_intervals.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/generate_intervals.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { SortDirection } from '../../../../../kibana_services'; +import { SortDirection } from '../../../../../../../../../../plugins/data/public'; export type IntervalValue = number | null; diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query/actions.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query/actions.js index 1cebb88cbda5a0..674f40d0186e54 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query/actions.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query/actions.js @@ -29,9 +29,13 @@ import { FAILURE_REASONS, LOADING_STATUS } from './constants'; import { MarkdownSimple } from '../../../../../../../kibana_react/public'; export function QueryActionsProvider(Promise) { - const fetchAnchor = fetchAnchorProvider(getServices().indexPatterns, new SearchSource()); - const { fetchSurroundingDocs } = fetchContextProvider(getServices().indexPatterns); - const { setPredecessorCount, setQueryParameters, setSuccessorCount } = getQueryParameterActions(); + const { filterManager, indexPatterns } = getServices(); + const fetchAnchor = fetchAnchorProvider(indexPatterns, new SearchSource()); + const { fetchSurroundingDocs } = fetchContextProvider(indexPatterns); + const { setPredecessorCount, setQueryParameters, setSuccessorCount } = getQueryParameterActions( + filterManager, + indexPatterns + ); const setFailedStatus = state => (subject, details = {}) => (state.loadingStatus[subject] = { diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query_parameters/actions.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query_parameters/actions.js index 5be1179a9ae09c..5c1700e7763610 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query_parameters/actions.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query_parameters/actions.js @@ -18,14 +18,11 @@ */ import _ from 'lodash'; -import { getServices } from '../../../../kibana_services'; import { esFilters } from '../../../../../../../../../plugins/data/public'; import { MAX_CONTEXT_SIZE, MIN_CONTEXT_SIZE, QUERY_PARAMETER_KEYS } from './constants'; -export function getQueryParameterActions() { - const filterManager = getServices().filterManager; - +export function getQueryParameterActions(filterManager, indexPatterns) { const setPredecessorCount = state => predecessorCount => (state.queryParameters.predecessorCount = clamp( MIN_CONTEXT_SIZE, @@ -57,8 +54,10 @@ export function getQueryParameterActions() { indexPatternId ); filterManager.addFilters(newFilters); - const indexPattern = await getServices().indexPatterns.get(indexPatternId); - indexPattern.popularizeField(field.name, 1); + if (indexPatterns) { + const indexPattern = await indexPatterns.get(indexPatternId); + indexPattern.popularizeField(field.name, 1); + } }; return { diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query_parameters/actions.test.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query_parameters/actions.test.ts new file mode 100644 index 00000000000000..35fbd33fb4bc97 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query_parameters/actions.test.ts @@ -0,0 +1,157 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// @ts-ignore +import { getQueryParameterActions } from './actions'; +import { FilterManager } from '../../../../../../../../../plugins/data/public'; +import { coreMock } from '../../../../../../../../../core/public/mocks'; +const setupMock = coreMock.createSetup(); + +let state: { + queryParameters: { + defaultStepSize: number; + indexPatternId: string; + predecessorCount: number; + successorCount: number; + }; +}; +let filterManager: FilterManager; +let filterManagerSpy: jest.SpyInstance; + +beforeEach(() => { + filterManager = new FilterManager(setupMock.uiSettings); + filterManagerSpy = jest.spyOn(filterManager, 'addFilters'); + + state = { + queryParameters: { + defaultStepSize: 3, + indexPatternId: 'INDEX_PATTERN_ID', + predecessorCount: 10, + successorCount: 10, + }, + }; +}); + +describe('context query_parameter actions', function() { + describe('action addFilter', () => { + it('should pass the given arguments to the filterManager', () => { + const { addFilter } = getQueryParameterActions(filterManager); + + addFilter(state)('FIELD_NAME', 'FIELD_VALUE', 'FILTER_OPERATION'); + + // get the generated filter + const generatedFilter = filterManagerSpy.mock.calls[0][0][0]; + const queryKeys = Object.keys(generatedFilter.query.match_phrase); + expect(filterManagerSpy.mock.calls.length).toBe(1); + expect(queryKeys[0]).toBe('FIELD_NAME'); + expect(generatedFilter.query.match_phrase[queryKeys[0]]).toBe('FIELD_VALUE'); + }); + + it('should pass the index pattern id to the filterManager', () => { + const { addFilter } = getQueryParameterActions(filterManager); + addFilter(state)('FIELD_NAME', 'FIELD_VALUE', 'FILTER_OPERATION'); + const generatedFilter = filterManagerSpy.mock.calls[0][0][0]; + expect(generatedFilter.meta.index).toBe('INDEX_PATTERN_ID'); + }); + }); + describe('action setPredecessorCount', () => { + it('should set the predecessorCount to the given value', () => { + const { setPredecessorCount } = getQueryParameterActions(filterManager); + setPredecessorCount(state)(20); + expect(state.queryParameters.predecessorCount).toBe(20); + }); + + it('should limit the predecessorCount to 0 as a lower bound', () => { + const { setPredecessorCount } = getQueryParameterActions(filterManager); + setPredecessorCount(state)(-1); + expect(state.queryParameters.predecessorCount).toBe(0); + }); + + it('should limit the predecessorCount to 10000 as an upper bound', () => { + const { setPredecessorCount } = getQueryParameterActions(filterManager); + setPredecessorCount(state)(20000); + expect(state.queryParameters.predecessorCount).toBe(10000); + }); + }); + describe('action setSuccessorCount', () => { + it('should set the successorCount to the given value', function() { + const { setSuccessorCount } = getQueryParameterActions(filterManager); + setSuccessorCount(state)(20); + + expect(state.queryParameters.successorCount).toBe(20); + }); + + it('should limit the successorCount to 0 as a lower bound', () => { + const { setSuccessorCount } = getQueryParameterActions(filterManager); + setSuccessorCount(state)(-1); + expect(state.queryParameters.successorCount).toBe(0); + }); + + it('should limit the successorCount to 10000 as an upper bound', () => { + const { setSuccessorCount } = getQueryParameterActions(filterManager); + setSuccessorCount(state)(20000); + expect(state.queryParameters.successorCount).toBe(10000); + }); + }); + describe('action setQueryParameters', function() { + const { setQueryParameters } = getQueryParameterActions(filterManager); + + it('should update the queryParameters with valid properties from the given object', function() { + const newState = { + ...state, + queryParameters: { + additionalParameter: 'ADDITIONAL_PARAMETER', + }, + }; + + const actualState = setQueryParameters(newState)({ + anchorId: 'ANCHOR_ID', + columns: ['column'], + defaultStepSize: 3, + filters: ['filter'], + indexPatternId: 'INDEX_PATTERN', + predecessorCount: 100, + successorCount: 100, + sort: ['field'], + }); + + expect(actualState).toEqual({ + additionalParameter: 'ADDITIONAL_PARAMETER', + anchorId: 'ANCHOR_ID', + columns: ['column'], + defaultStepSize: 3, + filters: ['filter'], + indexPatternId: 'INDEX_PATTERN', + predecessorCount: 100, + successorCount: 100, + sort: ['field'], + }); + }); + + it('should ignore invalid properties', function() { + const newState = { ...state }; + + setQueryParameters(newState)({ + additionalParameter: 'ADDITIONAL_PARAMETER', + }); + + expect(state.queryParameters).toEqual(newState.queryParameters); + }); + }); +}); diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context_app.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context_app.js index b5ba2844e8b06e..345717cafee9af 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context_app.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context_app.js @@ -18,7 +18,7 @@ */ import _ from 'lodash'; -import { getAngularModule } from '../../kibana_services'; +import { getAngularModule, getServices } from '../../kibana_services'; import contextAppTemplate from './context_app.html'; import './context/components/action_bar'; import { getFirstSortableField } from './context/api/utils/sorting'; @@ -58,7 +58,8 @@ module.directive('contextApp', function ContextApp() { }); function ContextAppController($scope, config, Private) { - const queryParameterActions = getQueryParameterActions(); + const { filterManager, indexpatterns } = getServices(); + const queryParameterActions = getQueryParameterActions(filterManager, indexpatterns); const queryActions = Private(QueryActionsProvider); this.state = createInitialState( parseInt(config.get('context:step'), 10), From 3be457382ff35c8ea02d9d13d6988ff3975ddfd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= Date: Thu, 12 Mar 2020 16:42:33 +0000 Subject: [PATCH 08/19] UI Metrics use findAll to retrieve all Saved Objects (#59891) * UI Metrics use findAll to retrieve all Saved Objects * Rename test description Co-Authored-By: Christiane (Tina) Heiligers Co-authored-by: Elastic Machine Co-authored-by: Christiane (Tina) Heiligers --- .../telemetry_application_usage_collector.ts | 24 +----- .../server/collectors/find_all.test.ts | 55 ++++++++++++ .../telemetry/server/collectors/find_all.ts | 41 +++++++++ .../server/collectors/ui_metric/index.test.ts | 86 +++++++++++++++++++ .../telemetry_ui_metric_collector.ts | 37 ++++---- .../core_plugins/telemetry/server/plugin.ts | 2 +- 6 files changed, 207 insertions(+), 38 deletions(-) create mode 100644 src/legacy/core_plugins/telemetry/server/collectors/find_all.test.ts create mode 100644 src/legacy/core_plugins/telemetry/server/collectors/find_all.ts create mode 100644 src/legacy/core_plugins/telemetry/server/collectors/ui_metric/index.test.ts diff --git a/src/legacy/core_plugins/telemetry/server/collectors/application_usage/telemetry_application_usage_collector.ts b/src/legacy/core_plugins/telemetry/server/collectors/application_usage/telemetry_application_usage_collector.ts index 04b549c9d2b32a..5c862686a37d9a 100644 --- a/src/legacy/core_plugins/telemetry/server/collectors/application_usage/telemetry_application_usage_collector.ts +++ b/src/legacy/core_plugins/telemetry/server/collectors/application_usage/telemetry_application_usage_collector.ts @@ -20,12 +20,8 @@ import moment from 'moment'; import { APPLICATION_USAGE_TYPE } from '../../../common/constants'; import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/server'; -import { - ISavedObjectsRepository, - SavedObjectAttributes, - SavedObjectsFindOptions, - SavedObject, -} from '../../../../../../core/server'; +import { ISavedObjectsRepository, SavedObjectAttributes } from '../../../../../../core/server'; +import { findAll } from '../find_all'; /** * Roll indices every 24h @@ -63,22 +59,6 @@ interface ApplicationUsageTelemetryReport { }; } -async function findAll( - savedObjectsClient: ISavedObjectsRepository, - opts: SavedObjectsFindOptions -): Promise>> { - const { page = 1, perPage = 100, ...options } = opts; - const { saved_objects: savedObjects, total } = await savedObjectsClient.find({ - ...options, - page, - perPage, - }); - if (page * perPage >= total) { - return savedObjects; - } - return [...savedObjects, ...(await findAll(savedObjectsClient, { ...opts, page: page + 1 }))]; -} - export function registerApplicationUsageCollector( usageCollection: UsageCollectionSetup, getSavedObjectsClient: () => ISavedObjectsRepository | undefined diff --git a/src/legacy/core_plugins/telemetry/server/collectors/find_all.test.ts b/src/legacy/core_plugins/telemetry/server/collectors/find_all.test.ts new file mode 100644 index 00000000000000..012cda395bc6c2 --- /dev/null +++ b/src/legacy/core_plugins/telemetry/server/collectors/find_all.test.ts @@ -0,0 +1,55 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { savedObjectsRepositoryMock } from '../../../../../core/server/mocks'; + +import { findAll } from './find_all'; + +describe('telemetry_application_usage', () => { + test('when savedObjectClient is initialised, return something', async () => { + const savedObjectClient = savedObjectsRepositoryMock.create(); + savedObjectClient.find.mockImplementation( + async () => + ({ + saved_objects: [], + total: 0, + } as any) + ); + + expect(await findAll(savedObjectClient, { type: 'test-type' })).toStrictEqual([]); + }); + + test('paging in findAll works', async () => { + const savedObjectClient = savedObjectsRepositoryMock.create(); + let total = 201; + const doc = { id: 'test-id', attributes: { test: 1 } }; + savedObjectClient.find.mockImplementation(async opts => { + if ((opts.page || 1) > 2) { + return { saved_objects: [], total } as any; + } + const savedObjects = new Array(opts.perPage).fill(doc); + total = savedObjects.length * 2 + 1; + return { saved_objects: savedObjects, total }; + }); + + expect(await findAll(savedObjectClient, { type: 'test-type' })).toStrictEqual( + new Array(total - 1).fill(doc) + ); + }); +}); diff --git a/src/legacy/core_plugins/telemetry/server/collectors/find_all.ts b/src/legacy/core_plugins/telemetry/server/collectors/find_all.ts new file mode 100644 index 00000000000000..e6363551eba9c6 --- /dev/null +++ b/src/legacy/core_plugins/telemetry/server/collectors/find_all.ts @@ -0,0 +1,41 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { + SavedObjectAttributes, + ISavedObjectsRepository, + SavedObjectsFindOptions, + SavedObject, +} from 'kibana/server'; + +export async function findAll( + savedObjectsClient: ISavedObjectsRepository, + opts: SavedObjectsFindOptions +): Promise>> { + const { page = 1, perPage = 100, ...options } = opts; + const { saved_objects: savedObjects, total } = await savedObjectsClient.find({ + ...options, + page, + perPage, + }); + if (page * perPage >= total) { + return savedObjects; + } + return [...savedObjects, ...(await findAll(savedObjectsClient, { ...opts, page: page + 1 }))]; +} diff --git a/src/legacy/core_plugins/telemetry/server/collectors/ui_metric/index.test.ts b/src/legacy/core_plugins/telemetry/server/collectors/ui_metric/index.test.ts new file mode 100644 index 00000000000000..ddb58a7d09bbda --- /dev/null +++ b/src/legacy/core_plugins/telemetry/server/collectors/ui_metric/index.test.ts @@ -0,0 +1,86 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/server'; +import { savedObjectsRepositoryMock } from '../../../../../../core/server/mocks'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { CollectorOptions } from '../../../../../../plugins/usage_collection/server/collector/collector'; + +import { registerUiMetricUsageCollector } from './'; + +describe('telemetry_ui_metric', () => { + let collector: CollectorOptions; + + const usageCollectionMock: jest.Mocked = { + makeUsageCollector: jest.fn().mockImplementation(config => (collector = config)), + registerCollector: jest.fn(), + } as any; + + const getUsageCollector = jest.fn(); + const callCluster = jest.fn(); + + beforeAll(() => registerUiMetricUsageCollector(usageCollectionMock, getUsageCollector)); + + test('registered collector is set', () => { + expect(collector).not.toBeUndefined(); + }); + + test('if no savedObjectClient initialised, return undefined', async () => { + expect(await collector.fetch(callCluster)).toBeUndefined(); + }); + + test('when savedObjectClient is initialised, return something', async () => { + const savedObjectClient = savedObjectsRepositoryMock.create(); + savedObjectClient.find.mockImplementation( + async () => + ({ + saved_objects: [], + total: 0, + } as any) + ); + getUsageCollector.mockImplementation(() => savedObjectClient); + + expect(await collector.fetch(callCluster)).toStrictEqual({}); + expect(savedObjectClient.bulkCreate).not.toHaveBeenCalled(); + }); + + test('results grouped by appName', async () => { + const savedObjectClient = savedObjectsRepositoryMock.create(); + savedObjectClient.find.mockImplementation(async () => { + return { + saved_objects: [ + { id: 'testAppName:testKeyName1', attributes: { count: 3 } }, + { id: 'testAppName:testKeyName2', attributes: { count: 5 } }, + { id: 'testAppName2:testKeyName3', attributes: { count: 1 } }, + ], + total: 3, + } as any; + }); + + getUsageCollector.mockImplementation(() => savedObjectClient); + + expect(await collector.fetch(callCluster)).toStrictEqual({ + testAppName: [ + { key: 'testKeyName1', value: 3 }, + { key: 'testKeyName2', value: 5 }, + ], + testAppName2: [{ key: 'testKeyName3', value: 1 }], + }); + }); +}); diff --git a/src/legacy/core_plugins/telemetry/server/collectors/ui_metric/telemetry_ui_metric_collector.ts b/src/legacy/core_plugins/telemetry/server/collectors/ui_metric/telemetry_ui_metric_collector.ts index 73157abce86292..a7b6850b0b20a3 100644 --- a/src/legacy/core_plugins/telemetry/server/collectors/ui_metric/telemetry_ui_metric_collector.ts +++ b/src/legacy/core_plugins/telemetry/server/collectors/ui_metric/telemetry_ui_metric_collector.ts @@ -17,24 +17,33 @@ * under the License. */ +import { ISavedObjectsRepository, SavedObjectAttributes } from 'kibana/server'; import { UI_METRIC_USAGE_TYPE } from '../../../common/constants'; import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/server'; +import { findAll } from '../find_all'; -export function registerUiMetricUsageCollector(usageCollection: UsageCollectionSetup, server: any) { +interface UIMetricsSavedObjects extends SavedObjectAttributes { + count: number; +} + +export function registerUiMetricUsageCollector( + usageCollection: UsageCollectionSetup, + getSavedObjectsClient: () => ISavedObjectsRepository | undefined +) { const collector = usageCollection.makeUsageCollector({ type: UI_METRIC_USAGE_TYPE, fetch: async () => { - const { SavedObjectsClient, getSavedObjectsRepository } = server.savedObjects; - const { callWithInternalUser } = server.plugins.elasticsearch.getCluster('admin'); - const internalRepository = getSavedObjectsRepository(callWithInternalUser); - const savedObjectsClient = new SavedObjectsClient(internalRepository); + const savedObjectsClient = getSavedObjectsClient(); + if (typeof savedObjectsClient === 'undefined') { + return; + } - const { saved_objects: rawUiMetrics } = await savedObjectsClient.find({ + const rawUiMetrics = await findAll(savedObjectsClient, { type: 'ui-metric', fields: ['count'], }); - const uiMetricsByAppName = rawUiMetrics.reduce((accum: any, rawUiMetric: any) => { + const uiMetricsByAppName = rawUiMetrics.reduce((accum, rawUiMetric) => { const { id, attributes: { count }, @@ -42,18 +51,16 @@ export function registerUiMetricUsageCollector(usageCollection: UsageCollectionS const [appName, metricType] = id.split(':'); - if (!accum[appName]) { - accum[appName] = []; - } - const pair = { key: metricType, value: count }; - accum[appName].push(pair); - return accum; - }, {}); + return { + ...accum, + [appName]: [...(accum[appName] || []), pair], + }; + }, {} as Record>); return uiMetricsByAppName; }, - isReady: () => true, + isReady: () => typeof getSavedObjectsClient() !== 'undefined', }); usageCollection.registerCollector(collector); diff --git a/src/legacy/core_plugins/telemetry/server/plugin.ts b/src/legacy/core_plugins/telemetry/server/plugin.ts index d859c0cfd46785..0b9f0526988c8a 100644 --- a/src/legacy/core_plugins/telemetry/server/plugin.ts +++ b/src/legacy/core_plugins/telemetry/server/plugin.ts @@ -59,7 +59,7 @@ export class TelemetryPlugin { registerTelemetryPluginUsageCollector(usageCollection, server); registerLocalizationUsageCollector(usageCollection, server); registerTelemetryUsageCollector(usageCollection, server); - registerUiMetricUsageCollector(usageCollection, server); + registerUiMetricUsageCollector(usageCollection, getSavedObjectsClient); registerManagementUsageCollector(usageCollection, server); registerApplicationUsageCollector(usageCollection, getSavedObjectsClient); } From 25c33fcb157f05de01f1e7ef330c4c9cfc69fd89 Mon Sep 17 00:00:00 2001 From: spalger Date: Thu, 12 Mar 2020 09:50:11 -0700 Subject: [PATCH 09/19] skip flaky suite (#59717) --- test/functional/apps/management/_handle_alias.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/functional/apps/management/_handle_alias.js b/test/functional/apps/management/_handle_alias.js index 3d9368f8d46807..55f6b56d9f0d1d 100644 --- a/test/functional/apps/management/_handle_alias.js +++ b/test/functional/apps/management/_handle_alias.js @@ -25,7 +25,8 @@ export default function({ getService, getPageObjects }) { const retry = getService('retry'); const PageObjects = getPageObjects(['common', 'home', 'settings', 'discover', 'timePicker']); - describe('Index patterns on aliases', function() { + // FLAKY: https://github.com/elastic/kibana/issues/59717 + describe.skip('Index patterns on aliases', function() { before(async function() { await esArchiver.loadIfNeeded('alias'); await esArchiver.load('empty_kibana'); From d3f53f6f6bf8ba14d4515d63695f572be244f455 Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Thu, 12 Mar 2020 10:46:24 -0700 Subject: [PATCH 10/19] Adds telemetry support to alerting and actions plugins (#58081) * Added base telemetry functionality * Fixed actions collector due to NP plugin changes * Fixed type checks * Fixed alerting plugin usage collector * Added actions and alerting telemetry tasks * Fixed failing tests and resolved comments * Extended telemetry for alerts by adding aggregations for throttle, schedule and connectors count * Fixed tests * Refactored using callCluster aggregations * Fixed compare * Fixed time convertion --- x-pack/plugins/actions/kibana.json | 2 +- x-pack/plugins/actions/server/plugin.test.ts | 8 + x-pack/plugins/actions/server/plugin.ts | 24 ++ .../actions/server/usage/actions_telemetry.ts | 145 +++++++++ .../usage/actions_usage_collector.test.ts | 34 ++ .../server/usage/actions_usage_collector.ts | 65 ++++ x-pack/plugins/actions/server/usage/index.ts | 7 + x-pack/plugins/actions/server/usage/task.ts | 100 ++++++ x-pack/plugins/actions/server/usage/types.ts | 15 + x-pack/plugins/alerting/kibana.json | 2 +- x-pack/plugins/alerting/server/plugin.test.ts | 4 + x-pack/plugins/alerting/server/plugin.ts | 41 +++ .../alerting/server/usage/alerts_telemetry.ts | 305 ++++++++++++++++++ .../usage/alerts_usage_collector.test.ts | 34 ++ .../server/usage/alerts_usage_collector.ts | 81 +++++ x-pack/plugins/alerting/server/usage/index.ts | 7 + x-pack/plugins/alerting/server/usage/task.ts | 104 ++++++ x-pack/plugins/alerting/server/usage/types.ts | 28 ++ .../components/alerts_list.test.tsx | 99 +++--- 19 files changed, 1055 insertions(+), 50 deletions(-) create mode 100644 x-pack/plugins/actions/server/usage/actions_telemetry.ts create mode 100644 x-pack/plugins/actions/server/usage/actions_usage_collector.test.ts create mode 100644 x-pack/plugins/actions/server/usage/actions_usage_collector.ts create mode 100644 x-pack/plugins/actions/server/usage/index.ts create mode 100644 x-pack/plugins/actions/server/usage/task.ts create mode 100644 x-pack/plugins/actions/server/usage/types.ts create mode 100644 x-pack/plugins/alerting/server/usage/alerts_telemetry.ts create mode 100644 x-pack/plugins/alerting/server/usage/alerts_usage_collector.test.ts create mode 100644 x-pack/plugins/alerting/server/usage/alerts_usage_collector.ts create mode 100644 x-pack/plugins/alerting/server/usage/index.ts create mode 100644 x-pack/plugins/alerting/server/usage/task.ts create mode 100644 x-pack/plugins/alerting/server/usage/types.ts diff --git a/x-pack/plugins/actions/kibana.json b/x-pack/plugins/actions/kibana.json index 0c40dec46a6ee4..14ddb8257ff377 100644 --- a/x-pack/plugins/actions/kibana.json +++ b/x-pack/plugins/actions/kibana.json @@ -5,6 +5,6 @@ "kibanaVersion": "kibana", "configPath": ["xpack", "actions"], "requiredPlugins": ["licensing", "taskManager", "encryptedSavedObjects", "eventLog"], - "optionalPlugins": ["spaces"], + "optionalPlugins": ["usageCollection", "spaces"], "ui": false } diff --git a/x-pack/plugins/actions/server/plugin.test.ts b/x-pack/plugins/actions/server/plugin.test.ts index 91944dfa8f3adc..f55a5ca1721446 100644 --- a/x-pack/plugins/actions/server/plugin.test.ts +++ b/x-pack/plugins/actions/server/plugin.test.ts @@ -11,8 +11,13 @@ import { licensingMock } from '../../licensing/server/mocks'; import { encryptedSavedObjectsMock } from '../../encrypted_saved_objects/server/mocks'; import { taskManagerMock } from '../../task_manager/server/mocks'; import { eventLogMock } from '../../event_log/server/mocks'; +import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; describe('Actions Plugin', () => { + const usageCollectionMock: jest.Mocked = ({ + makeUsageCollector: jest.fn(), + registerCollector: jest.fn(), + } as unknown) as jest.Mocked; describe('setup()', () => { let context: PluginInitializerContext; let plugin: ActionsPlugin; @@ -23,11 +28,13 @@ describe('Actions Plugin', () => { context = coreMock.createPluginInitializerContext(); plugin = new ActionsPlugin(context); coreSetup = coreMock.createSetup(); + pluginsSetup = { taskManager: taskManagerMock.createSetup(), encryptedSavedObjects: encryptedSavedObjectsMock.createSetup(), licensing: licensingMock.createSetup(), eventLog: eventLogMock.createSetup(), + usageCollection: usageCollectionMock, }; }); @@ -108,6 +115,7 @@ describe('Actions Plugin', () => { encryptedSavedObjects: encryptedSavedObjectsMock.createSetup(), licensing: licensingMock.createSetup(), eventLog: eventLogMock.createSetup(), + usageCollection: usageCollectionMock, }; pluginsStart = { taskManager: taskManagerMock.createStart(), diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index b0555921f395f7..5ea6320fbe54a9 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -5,6 +5,7 @@ */ import { first, map } from 'rxjs/operators'; +import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { PluginInitializerContext, Plugin, @@ -35,6 +36,7 @@ import { ActionTypeRegistry } from './action_type_registry'; import { ExecuteOptions } from './create_execute_function'; import { createExecuteFunction } from './create_execute_function'; import { registerBuiltInActionTypes } from './builtin_action_types'; +import { registerActionsUsageCollector } from './usage'; import { getActionsConfigurationUtilities } from './actions_config'; @@ -49,6 +51,7 @@ import { } from './routes'; import { LicenseState } from './lib/license_state'; import { IEventLogger, IEventLogService } from '../../event_log/server'; +import { initializeActionsTelemetry, scheduleActionsTelemetry } from './usage/task'; const EVENT_LOG_PROVIDER = 'actions'; export const EVENT_LOG_ACTIONS = { @@ -71,6 +74,7 @@ export interface ActionsPluginsSetup { licensing: LicensingPluginSetup; spaces?: SpacesPluginSetup; eventLog: IEventLogService; + usageCollection?: UsageCollectionSetup; } export interface ActionsPluginsStart { encryptedSavedObjects: EncryptedSavedObjectsPluginStart; @@ -91,6 +95,7 @@ export class ActionsPlugin implements Plugin, Plugi private spaces?: SpacesServiceSetup; private eventLogger?: IEventLogger; private isESOUsingEphemeralEncryptionKey?: boolean; + private readonly telemetryLogger: Logger; constructor(initContext: PluginInitializerContext) { this.config = initContext.config @@ -106,6 +111,7 @@ export class ActionsPlugin implements Plugin, Plugi .toPromise(); this.logger = initContext.logger.get('actions'); + this.telemetryLogger = initContext.logger.get('telemetry'); } public async setup(core: CoreSetup, plugins: ActionsPluginsSetup): Promise { @@ -140,6 +146,8 @@ export class ActionsPlugin implements Plugin, Plugi const actionExecutor = new ActionExecutor({ isESOUsingEphemeralEncryptionKey: this.isESOUsingEphemeralEncryptionKey, }); + + // get executions count const taskRunnerFactory = new TaskRunnerFactory(actionExecutor); const actionsConfigUtils = getActionsConfigurationUtilities( (await this.config) as ActionsConfig @@ -162,6 +170,20 @@ export class ActionsPlugin implements Plugin, Plugi actionsConfigUtils, }); + const usageCollection = plugins.usageCollection; + if (usageCollection) { + core.getStartServices().then(async ([coreStart, startPlugins]: [CoreStart, any]) => { + registerActionsUsageCollector(usageCollection, startPlugins.taskManager); + + initializeActionsTelemetry( + this.telemetryLogger, + plugins.taskManager, + core, + await this.kibanaIndex + ); + }); + } + core.http.registerRouteHandlerContext( 'actions', this.createRouteHandlerContext(await this.kibanaIndex) @@ -211,6 +233,8 @@ export class ActionsPlugin implements Plugin, Plugi getScopedSavedObjectsClient: core.savedObjects.getScopedClient, }); + scheduleActionsTelemetry(this.telemetryLogger, plugins.taskManager); + return { execute: createExecuteFunction({ taskManager: plugins.taskManager, diff --git a/x-pack/plugins/actions/server/usage/actions_telemetry.ts b/x-pack/plugins/actions/server/usage/actions_telemetry.ts new file mode 100644 index 00000000000000..ccdb4ecec20121 --- /dev/null +++ b/x-pack/plugins/actions/server/usage/actions_telemetry.ts @@ -0,0 +1,145 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { APICaller } from 'kibana/server'; + +export async function getTotalCount(callCluster: APICaller, kibanaIndex: string) { + const scriptedMetric = { + scripted_metric: { + init_script: 'state.types = [:]', + map_script: ` + String actionType = doc['action.actionTypeId'].value; + state.types.put(actionType, state.types.containsKey(actionType) ? state.types.get(actionType) + 1 : 1); + `, + // Combine script is executed per cluster, but we already have a key-value pair per cluster. + // Despite docs that say this is optional, this script can't be blank. + combine_script: 'return state', + // Reduce script is executed across all clusters, so we need to add up all the total from each cluster + // This also needs to account for having no data + reduce_script: ` + Map result = [:]; + for (Map m : states.toArray()) { + if (m !== null) { + for (String k : m.keySet()) { + result.put(k, result.containsKey(k) ? result.get(k) + m.get(k) : m.get(k)); + } + } + } + return result; + `, + }, + }; + + const searchResult = await callCluster('search', { + index: kibanaIndex, + rest_total_hits_as_int: true, + body: { + query: { + bool: { + filter: [{ term: { type: 'action' } }], + }, + }, + aggs: { + byActionTypeId: scriptedMetric, + }, + }, + }); + + return { + countTotal: Object.keys(searchResult.aggregations.byActionTypeId.value.types).reduce( + (total: number, key: string) => + parseInt(searchResult.aggregations.byActionTypeId.value.types[key], 0) + total, + 0 + ), + countByType: searchResult.aggregations.byActionTypeId.value.types, + }; +} + +export async function getInUseTotalCount(callCluster: APICaller, kibanaIndex: string) { + const scriptedMetric = { + scripted_metric: { + init_script: 'state.connectorIds = new HashMap(); state.total = 0;', + map_script: ` + String connectorId = doc['references.id'].value; + String actionRef = doc['references.name'].value; + if (state.connectorIds[connectorId] === null) { + state.connectorIds[connectorId] = actionRef; + state.total++; + } + `, + // Combine script is executed per cluster, but we already have a key-value pair per cluster. + // Despite docs that say this is optional, this script can't be blank. + combine_script: 'return state', + // Reduce script is executed across all clusters, so we need to add up all the total from each cluster + // This also needs to account for having no data + reduce_script: ` + Map connectorIds = [:]; + long total = 0; + for (state in states) { + if (state !== null) { + total += state.total; + for (String k : state.connectorIds.keySet()) { + connectorIds.put(k, connectorIds.containsKey(k) ? connectorIds.get(k) + state.connectorIds.get(k) : state.connectorIds.get(k)); + } + } + } + Map result = new HashMap(); + result.total = total; + result.connectorIds = connectorIds; + return result; + `, + }, + }; + + const actionResults = await callCluster('search', { + index: kibanaIndex, + rest_total_hits_as_int: true, + body: { + query: { + bool: { + filter: { + bool: { + must: { + nested: { + path: 'references', + query: { + bool: { + filter: { + bool: { + must: [ + { + term: { + 'references.type': 'action', + }, + }, + ], + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + aggs: { + refs: { + nested: { + path: 'references', + }, + aggs: { + actionRefIds: scriptedMetric, + }, + }, + }, + }, + }); + + return actionResults.aggregations.refs.actionRefIds.value.total; +} + +// TODO: Implement executions count telemetry with eventLog, when it will write to index diff --git a/x-pack/plugins/actions/server/usage/actions_usage_collector.test.ts b/x-pack/plugins/actions/server/usage/actions_usage_collector.test.ts new file mode 100644 index 00000000000000..214690383ceba8 --- /dev/null +++ b/x-pack/plugins/actions/server/usage/actions_usage_collector.test.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; +import { registerActionsUsageCollector } from './actions_usage_collector'; +import { taskManagerMock } from '../../../task_manager/server/task_manager.mock'; + +const mockTaskManagerStart = taskManagerMock.start(); + +beforeEach(() => jest.resetAllMocks()); + +describe('registerActionsUsageCollector', () => { + let usageCollectionMock: jest.Mocked; + beforeEach(() => { + usageCollectionMock = ({ + makeUsageCollector: jest.fn(), + registerCollector: jest.fn(), + } as unknown) as jest.Mocked; + }); + + it('should call registerCollector', () => { + registerActionsUsageCollector(usageCollectionMock, mockTaskManagerStart); + expect(usageCollectionMock.registerCollector).toHaveBeenCalledTimes(1); + }); + + it('should call makeUsageCollector with type = actions', () => { + registerActionsUsageCollector(usageCollectionMock, mockTaskManagerStart); + expect(usageCollectionMock.makeUsageCollector).toHaveBeenCalledTimes(1); + expect(usageCollectionMock.makeUsageCollector.mock.calls[0][0].type).toBe('actions'); + }); +}); diff --git a/x-pack/plugins/actions/server/usage/actions_usage_collector.ts b/x-pack/plugins/actions/server/usage/actions_usage_collector.ts new file mode 100644 index 00000000000000..e298b3ad9d00ce --- /dev/null +++ b/x-pack/plugins/actions/server/usage/actions_usage_collector.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; +import { get } from 'lodash'; +import { TaskManagerStartContract } from '../../../task_manager/server'; +import { ActionsUsage } from './types'; + +export function createActionsUsageCollector( + usageCollection: UsageCollectionSetup, + taskManager: TaskManagerStartContract +) { + return usageCollection.makeUsageCollector({ + type: 'actions', + isReady: () => true, + fetch: async (): Promise => { + try { + const doc = await getLatestTaskState(await taskManager); + // get the accumulated state from the recurring task + const state: ActionsUsage = get(doc, 'state'); + + return { + ...state, + }; + } catch (err) { + return { + count_total: 0, + count_active_total: 0, + count_active_by_type: {}, + count_by_type: {}, + }; + } + }, + }); +} + +async function getLatestTaskState(taskManager: TaskManagerStartContract) { + try { + const result = await taskManager.get('Actions-actions_telemetry'); + return result; + } catch (err) { + const errMessage = err && err.message ? err.message : err.toString(); + /* + The usage service WILL to try to fetch from this collector before the task manager has been initialized, because the + task manager has to wait for all plugins to initialize first. It's fine to ignore it as next time around it will be + initialized (or it will throw a different type of error) + */ + if (!errMessage.includes('NotInitialized')) { + throw err; + } + } + + return null; +} + +export function registerActionsUsageCollector( + usageCollection: UsageCollectionSetup, + taskManager: TaskManagerStartContract +) { + const collector = createActionsUsageCollector(usageCollection, taskManager); + usageCollection.registerCollector(collector); +} diff --git a/x-pack/plugins/actions/server/usage/index.ts b/x-pack/plugins/actions/server/usage/index.ts new file mode 100644 index 00000000000000..ddca1de3d6bdaa --- /dev/null +++ b/x-pack/plugins/actions/server/usage/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { registerActionsUsageCollector } from './actions_usage_collector'; diff --git a/x-pack/plugins/actions/server/usage/task.ts b/x-pack/plugins/actions/server/usage/task.ts new file mode 100644 index 00000000000000..a07a2aa8f1c70f --- /dev/null +++ b/x-pack/plugins/actions/server/usage/task.ts @@ -0,0 +1,100 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Logger, CoreSetup } from 'kibana/server'; +import moment from 'moment'; +import { + RunContext, + TaskManagerSetupContract, + TaskManagerStartContract, +} from '../../../task_manager/server'; +import { getTotalCount, getInUseTotalCount } from './actions_telemetry'; + +export const TELEMETRY_TASK_TYPE = 'actions_telemetry'; + +export const TASK_ID = `Actions-${TELEMETRY_TASK_TYPE}`; + +export function initializeActionsTelemetry( + logger: Logger, + taskManager: TaskManagerSetupContract, + core: CoreSetup, + kibanaIndex: string +) { + registerActionsTelemetryTask(logger, taskManager, core, kibanaIndex); +} + +export function scheduleActionsTelemetry(logger: Logger, taskManager: TaskManagerStartContract) { + scheduleTasks(logger, taskManager); +} + +function registerActionsTelemetryTask( + logger: Logger, + taskManager: TaskManagerSetupContract, + core: CoreSetup, + kibanaIndex: string +) { + taskManager.registerTaskDefinitions({ + [TELEMETRY_TASK_TYPE]: { + title: 'Actions telemetry fetch task', + type: TELEMETRY_TASK_TYPE, + timeout: '5m', + createTaskRunner: telemetryTaskRunner(logger, core, kibanaIndex), + }, + }); +} + +async function scheduleTasks(logger: Logger, taskManager: TaskManagerStartContract) { + try { + await taskManager.ensureScheduled({ + id: TASK_ID, + taskType: TELEMETRY_TASK_TYPE, + state: { byDate: {}, suggestionsByDate: {}, saved: {}, runs: 0 }, + params: {}, + }); + } catch (e) { + logger.debug(`Error scheduling task, received ${e.message}`); + } +} + +export function telemetryTaskRunner(logger: Logger, core: CoreSetup, kibanaIndex: string) { + return ({ taskInstance }: RunContext) => { + const { state } = taskInstance; + const callCluster = core.elasticsearch.adminClient.callAsInternalUser; + return { + async run() { + return Promise.all([ + getTotalCount(callCluster, kibanaIndex), + getInUseTotalCount(callCluster, kibanaIndex), + ]) + .then(([totalAggegations, countActiveTotal]) => { + return { + state: { + runs: (state.runs || 0) + 1, + count_total: totalAggegations.countTotal, + count_by_type: totalAggegations.countByType, + count_active_total: countActiveTotal, + }, + runAt: getNextMidnight(), + }; + }) + .catch(errMsg => { + logger.warn(`Error executing actions telemetry task: ${errMsg}`); + return { + state: {}, + runAt: getNextMidnight(), + }; + }); + }, + }; + }; +} + +function getNextMidnight() { + return moment() + .add(1, 'd') + .startOf('d') + .toDate(); +} diff --git a/x-pack/plugins/actions/server/usage/types.ts b/x-pack/plugins/actions/server/usage/types.ts new file mode 100644 index 00000000000000..d1ea5b03f5415d --- /dev/null +++ b/x-pack/plugins/actions/server/usage/types.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface ActionsUsage { + count_total: number; + count_active_total: number; + count_by_type: Record; + count_active_by_type: Record; + // TODO: Implement executions count telemetry with eventLog, when it will write to index + // executions_by_type: Record; + // executions_total: number; +} diff --git a/x-pack/plugins/alerting/kibana.json b/x-pack/plugins/alerting/kibana.json index fb5003ede48ce5..12f48d98dbf587 100644 --- a/x-pack/plugins/alerting/kibana.json +++ b/x-pack/plugins/alerting/kibana.json @@ -5,6 +5,6 @@ "kibanaVersion": "kibana", "configPath": ["xpack", "alerting"], "requiredPlugins": ["licensing", "taskManager", "encryptedSavedObjects", "actions"], - "optionalPlugins": ["spaces", "security"], + "optionalPlugins": ["usageCollection", "spaces", "security"], "ui": false } \ No newline at end of file diff --git a/x-pack/plugins/alerting/server/plugin.test.ts b/x-pack/plugins/alerting/server/plugin.test.ts index 40e620dd92af04..ec0ed4b761205e 100644 --- a/x-pack/plugins/alerting/server/plugin.test.ts +++ b/x-pack/plugins/alerting/server/plugin.test.ts @@ -8,6 +8,7 @@ import { AlertingPlugin } from './plugin'; import { coreMock } from '../../../../src/core/server/mocks'; import { licensingMock } from '../../../plugins/licensing/server/mocks'; import { encryptedSavedObjectsMock } from '../../../plugins/encrypted_saved_objects/server/mocks'; +import { taskManagerMock } from '../../task_manager/server/mocks'; describe('Alerting Plugin', () => { describe('setup()', () => { @@ -28,6 +29,7 @@ describe('Alerting Plugin', () => { { licensing: licensingMock.createSetup(), encryptedSavedObjects: encryptedSavedObjectsSetup, + taskManager: taskManagerMock.createSetup(), } as any ); @@ -64,6 +66,7 @@ describe('Alerting Plugin', () => { { licensing: licensingMock.createSetup(), encryptedSavedObjects: encryptedSavedObjectsSetup, + taskManager: taskManagerMock.createSetup(), } as any ); @@ -105,6 +108,7 @@ describe('Alerting Plugin', () => { { licensing: licensingMock.createSetup(), encryptedSavedObjects: encryptedSavedObjectsSetup, + taskManager: taskManagerMock.createSetup(), } as any ); diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index bed163878b5ac9..4d91c9b24add9c 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { first, map } from 'rxjs/operators'; +import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { SecurityPluginSetup } from '../../security/server'; import { EncryptedSavedObjectsPluginSetup, @@ -26,6 +28,7 @@ import { SavedObjectsServiceStart, IContextProvider, RequestHandler, + SharedGlobalConfig, } from '../../../../src/core/server'; import { @@ -50,6 +53,8 @@ import { PluginStartContract as ActionsPluginStartContract, } from '../../../plugins/actions/server'; import { Services } from './types'; +import { registerAlertsUsageCollector } from './usage'; +import { initializeAlertingTelemetry, scheduleAlertingTelemetry } from './usage/task'; export interface PluginSetupContract { registerType: AlertTypeRegistry['register']; @@ -66,6 +71,7 @@ export interface AlertingPluginsSetup { encryptedSavedObjects: EncryptedSavedObjectsPluginSetup; licensing: LicensingPluginSetup; spaces?: SpacesPluginSetup; + usageCollection?: UsageCollectionSetup; } export interface AlertingPluginsStart { actions: ActionsPluginStartContract; @@ -84,11 +90,20 @@ export class AlertingPlugin { private spaces?: SpacesServiceSetup; private security?: SecurityPluginSetup; private readonly alertsClientFactory: AlertsClientFactory; + private readonly telemetryLogger: Logger; + private readonly kibanaIndex: Promise; constructor(initializerContext: PluginInitializerContext) { this.logger = initializerContext.logger.get('plugins', 'alerting'); this.taskRunnerFactory = new TaskRunnerFactory(); this.alertsClientFactory = new AlertsClientFactory(); + this.telemetryLogger = initializerContext.logger.get('telemetry'); + this.kibanaIndex = initializerContext.config.legacy.globalConfig$ + .pipe( + first(), + map((config: SharedGlobalConfig) => config.kibana.index) + ) + .toPromise(); } public async setup(core: CoreSetup, plugins: AlertingPluginsSetup): Promise { @@ -124,6 +139,20 @@ export class AlertingPlugin { this.alertTypeRegistry = alertTypeRegistry; this.serverBasePath = core.http.basePath.serverBasePath; + const usageCollection = plugins.usageCollection; + if (usageCollection) { + core.getStartServices().then(async ([coreStart, startPlugins]: [CoreStart, any]) => { + registerAlertsUsageCollector(usageCollection, startPlugins.taskManager); + + initializeAlertingTelemetry( + this.telemetryLogger, + core, + plugins.taskManager, + await this.kibanaIndex + ); + }); + } + core.http.registerRouteHandlerContext('alerting', this.createRouteHandlerContext()); // Routes @@ -144,6 +173,16 @@ export class AlertingPlugin { muteAlertInstanceRoute(router, this.licenseState); unmuteAlertInstanceRoute(router, this.licenseState); + alertTypeRegistry.register({ + id: 'test', + actionGroups: [{ id: 'default', name: 'Default' }], + defaultActionGroupId: 'default', + name: 'Test', + executor: async options => { + return { status: 'ok' }; + }, + }); + return { registerType: alertTypeRegistry.register.bind(alertTypeRegistry), }; @@ -181,6 +220,8 @@ export class AlertingPlugin { getBasePath: this.getBasePath, }); + scheduleAlertingTelemetry(this.telemetryLogger, plugins.taskManager); + return { listTypes: alertTypeRegistry!.list.bind(this.alertTypeRegistry!), // Ability to get an alerts client from legacy code diff --git a/x-pack/plugins/alerting/server/usage/alerts_telemetry.ts b/x-pack/plugins/alerting/server/usage/alerts_telemetry.ts new file mode 100644 index 00000000000000..9ab63b7755500b --- /dev/null +++ b/x-pack/plugins/alerting/server/usage/alerts_telemetry.ts @@ -0,0 +1,305 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { APICaller } from 'kibana/server'; + +const alertTypeMetric = { + scripted_metric: { + init_script: 'state.types = [:]', + map_script: ` + String alertType = doc['alert.alertTypeId'].value; + state.types.put(alertType, state.types.containsKey(alertType) ? state.types.get(alertType) + 1 : 1); + `, + // Combine script is executed per cluster, but we already have a key-value pair per cluster. + // Despite docs that say this is optional, this script can't be blank. + combine_script: 'return state', + // Reduce script is executed across all clusters, so we need to add up all the total from each cluster + // This also needs to account for having no data + reduce_script: ` + Map result = [:]; + for (Map m : states.toArray()) { + if (m !== null) { + for (String k : m.keySet()) { + result.put(k, result.containsKey(k) ? result.get(k) + m.get(k) : m.get(k)); + } + } + } + return result; + `, + }, +}; + +export async function getTotalCountAggregations(callCluster: APICaller, kibanaInex: string) { + const throttleTimeMetric = { + scripted_metric: { + init_script: 'state.min = 0; state.max = 0; state.totalSum = 0; state.totalCount = 0;', + map_script: ` + if (doc['alert.throttle'].size() > 0) { + def throttle = doc['alert.throttle'].value; + + if (throttle.length() > 1) { + // get last char + String timeChar = throttle.substring(throttle.length() - 1); + // remove last char + throttle = throttle.substring(0, throttle.length() - 1); + + if (throttle.chars().allMatch(Character::isDigit)) { + // using of regex is not allowed in painless language + int parsed = Integer.parseInt(throttle); + + if (timeChar.equals("s")) { + parsed = parsed; + } else if (timeChar.equals("m")) { + parsed = parsed * 60; + } else if (timeChar.equals("h")) { + parsed = parsed * 60 * 60; + } else if (timeChar.equals("d")) { + parsed = parsed * 24 * 60 * 60; + } + if (state.min === 0 || parsed < state.min) { + state.min = parsed; + } + if (parsed > state.max) { + state.max = parsed; + } + state.totalSum += parsed; + state.totalCount++; + } + } + } + `, + // Combine script is executed per cluster, but we already have a key-value pair per cluster. + // Despite docs that say this is optional, this script can't be blank. + combine_script: 'return state', + // Reduce script is executed across all clusters, so we need to add up all the total from each cluster + // This also needs to account for having no data + reduce_script: ` + double min = 0; + double max = 0; + long totalSum = 0; + long totalCount = 0; + for (Map m : states.toArray()) { + if (m !== null) { + min = min > 0 ? Math.min(min, m.min) : m.min; + max = Math.max(max, m.max); + totalSum += m.totalSum; + totalCount += m.totalCount; + } + } + Map result = new HashMap(); + result.min = min; + result.max = max; + result.totalSum = totalSum; + result.totalCount = totalCount; + return result; + `, + }, + }; + + const intervalTimeMetric = { + scripted_metric: { + init_script: 'state.min = 0; state.max = 0; state.totalSum = 0; state.totalCount = 0;', + map_script: ` + if (doc['alert.schedule.interval'].size() > 0) { + def interval = doc['alert.schedule.interval'].value; + + if (interval.length() > 1) { + // get last char + String timeChar = interval.substring(interval.length() - 1); + // remove last char + interval = interval.substring(0, interval.length() - 1); + + if (interval.chars().allMatch(Character::isDigit)) { + // using of regex is not allowed in painless language + int parsed = Integer.parseInt(interval); + + if (timeChar.equals("s")) { + parsed = parsed; + } else if (timeChar.equals("m")) { + parsed = parsed * 60; + } else if (timeChar.equals("h")) { + parsed = parsed * 60 * 60; + } else if (timeChar.equals("d")) { + parsed = parsed * 24 * 60 * 60; + } + if (state.min === 0 || parsed < state.min) { + state.min = parsed; + } + if (parsed > state.max) { + state.max = parsed; + } + state.totalSum += parsed; + state.totalCount++; + } + } + } + `, + // Combine script is executed per cluster, but we already have a key-value pair per cluster. + // Despite docs that say this is optional, this script can't be blank. + combine_script: 'return state', + // Reduce script is executed across all clusters, so we need to add up all the total from each cluster + // This also needs to account for having no data + reduce_script: ` + double min = 0; + double max = 0; + long totalSum = 0; + long totalCount = 0; + for (Map m : states.toArray()) { + if (m !== null) { + min = min > 0 ? Math.min(min, m.min) : m.min; + max = Math.max(max, m.max); + totalSum += m.totalSum; + totalCount += m.totalCount; + } + } + Map result = new HashMap(); + result.min = min; + result.max = max; + result.totalSum = totalSum; + result.totalCount = totalCount; + return result; + `, + }, + }; + + const connectorsMetric = { + scripted_metric: { + init_script: + 'state.currentAlertActions = 0; state.min = 0; state.max = 0; state.totalActionsCount = 0;', + map_script: ` + String refName = doc['alert.actions.actionRef'].value; + if (refName == 'action_0') { + if (state.currentAlertActions !== 0 && state.currentAlertActions < state.min) { + state.min = state.currentAlertActions; + } + if (state.currentAlertActions !== 0 && state.currentAlertActions > state.max) { + state.max = state.currentAlertActions; + } + state.currentAlertActions = 1; + } else { + state.currentAlertActions++; + } + state.totalActionsCount++; + `, + // Combine script is executed per cluster, but we already have a key-value pair per cluster. + // Despite docs that say this is optional, this script can't be blank. + combine_script: 'return state', + // Reduce script is executed across all clusters, so we need to add up all the total from each cluster + // This also needs to account for having no data + reduce_script: ` + double min = 0; + double max = 0; + long totalActionsCount = 0; + long currentAlertActions = 0; + for (Map m : states.toArray()) { + if (m !== null) { + min = min > 0 ? Math.min(min, m.min) : m.min; + max = Math.max(max, m.max); + currentAlertActions += m.currentAlertActions; + totalActionsCount += m.totalActionsCount; + } + } + Map result = new HashMap(); + result.min = min; + result.max = max; + result.currentAlertActions = currentAlertActions; + result.totalActionsCount = totalActionsCount; + return result; + `, + }, + }; + + const results = await callCluster('search', { + index: kibanaInex, + rest_total_hits_as_int: true, + body: { + query: { + bool: { + filter: [{ term: { type: 'alert' } }], + }, + }, + aggs: { + byAlertTypeId: alertTypeMetric, + throttleTime: throttleTimeMetric, + intervalTime: intervalTimeMetric, + connectorsAgg: { + nested: { + path: 'alert.actions', + }, + aggs: { + connectors: connectorsMetric, + }, + }, + }, + }, + }); + + const totalAlertsCount = Object.keys(results.aggregations.byAlertTypeId.value.types).reduce( + (total: number, key: string) => + parseInt(results.aggregations.byAlertTypeId.value.types[key], 0) + total, + 0 + ); + + return { + count_total: totalAlertsCount, + count_by_type: results.aggregations.byAlertTypeId.value.types, + throttle_time: { + min: `${results.aggregations.throttleTime.value.min}s`, + avg: `${ + results.aggregations.throttleTime.value.totalCount > 0 + ? results.aggregations.throttleTime.value.totalSum / + results.aggregations.throttleTime.value.totalCount + : 0 + }s`, + max: `${results.aggregations.throttleTime.value.max}s`, + }, + schedule_time: { + min: `${results.aggregations.intervalTime.value.min}s`, + avg: `${ + results.aggregations.intervalTime.value.totalCount > 0 + ? results.aggregations.intervalTime.value.totalSum / + results.aggregations.intervalTime.value.totalCount + : 0 + }s`, + max: `${results.aggregations.intervalTime.value.max}s`, + }, + connectors_per_alert: { + min: results.aggregations.connectorsAgg.connectors.value.min, + avg: + totalAlertsCount > 0 + ? results.aggregations.connectorsAgg.connectors.value.totalActionsCount / totalAlertsCount + : 0, + max: results.aggregations.connectorsAgg.connectors.value.max, + }, + }; +} + +export async function getTotalCountInUse(callCluster: APICaller, kibanaInex: string) { + const searchResult = await callCluster('search', { + index: kibanaInex, + rest_total_hits_as_int: true, + body: { + query: { + bool: { + filter: [{ term: { type: 'alert' } }, { term: { 'alert.enabled': true } }], + }, + }, + aggs: { + byAlertTypeId: alertTypeMetric, + }, + }, + }); + return { + countTotal: Object.keys(searchResult.aggregations.byAlertTypeId.value.types).reduce( + (total: number, key: string) => + parseInt(searchResult.aggregations.byAlertTypeId.value.types[key], 0) + total, + 0 + ), + countByType: searchResult.aggregations.byAlertTypeId.value.types, + }; +} + +// TODO: Implement executions count telemetry with eventLog, when it will write to index diff --git a/x-pack/plugins/alerting/server/usage/alerts_usage_collector.test.ts b/x-pack/plugins/alerting/server/usage/alerts_usage_collector.test.ts new file mode 100644 index 00000000000000..e530c7afeebdcc --- /dev/null +++ b/x-pack/plugins/alerting/server/usage/alerts_usage_collector.test.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; +import { registerAlertsUsageCollector } from './alerts_usage_collector'; +import { taskManagerMock } from '../../../task_manager/server/task_manager.mock'; +const taskManagerStart = taskManagerMock.start(); + +beforeEach(() => jest.resetAllMocks()); + +describe('registerAlertsUsageCollector', () => { + let usageCollectionMock: jest.Mocked; + + beforeEach(() => { + usageCollectionMock = ({ + makeUsageCollector: jest.fn(), + registerCollector: jest.fn(), + } as unknown) as jest.Mocked; + }); + + it('should call registerCollector', () => { + registerAlertsUsageCollector(usageCollectionMock, taskManagerStart); + expect(usageCollectionMock.registerCollector).toHaveBeenCalledTimes(1); + }); + + it('should call makeUsageCollector with type = alerts', () => { + registerAlertsUsageCollector(usageCollectionMock, taskManagerStart); + expect(usageCollectionMock.makeUsageCollector).toHaveBeenCalledTimes(1); + expect(usageCollectionMock.makeUsageCollector.mock.calls[0][0].type).toBe('alerts'); + }); +}); diff --git a/x-pack/plugins/alerting/server/usage/alerts_usage_collector.ts b/x-pack/plugins/alerting/server/usage/alerts_usage_collector.ts new file mode 100644 index 00000000000000..d2cef0f717e94a --- /dev/null +++ b/x-pack/plugins/alerting/server/usage/alerts_usage_collector.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; +import { get } from 'lodash'; +import { TaskManagerStartContract } from '../../../task_manager/server'; +import { AlertsUsage } from './types'; + +export function createAlertsUsageCollector( + usageCollection: UsageCollectionSetup, + taskManager: TaskManagerStartContract +) { + return usageCollection.makeUsageCollector({ + type: 'alerts', + isReady: () => true, + fetch: async (): Promise => { + try { + const doc = await getLatestTaskState(await taskManager); + // get the accumulated state from the recurring task + const state: AlertsUsage = get(doc, 'state'); + + return { + ...state, + }; + } catch (err) { + return { + count_total: 0, + count_active_total: 0, + count_disabled_total: 0, + throttle_time: { + min: 0, + avg: 0, + max: 0, + }, + schedule_time: { + min: 0, + avg: 0, + max: 0, + }, + connectors_per_alert: { + min: 0, + avg: 0, + max: 0, + }, + count_active_by_type: {}, + count_by_type: {}, + }; + } + }, + }); +} + +async function getLatestTaskState(taskManager: TaskManagerStartContract) { + try { + const result = await taskManager.get('Alerting-alerting_telemetry'); + return result; + } catch (err) { + const errMessage = err && err.message ? err.message : err.toString(); + /* + The usage service WILL to try to fetch from this collector before the task manager has been initialized, because the + task manager has to wait for all plugins to initialize first. It's fine to ignore it as next time around it will be + initialized (or it will throw a different type of error) + */ + if (!errMessage.includes('NotInitialized')) { + throw err; + } + } + + return null; +} + +export function registerAlertsUsageCollector( + usageCollection: UsageCollectionSetup, + taskManager: TaskManagerStartContract +) { + const collector = createAlertsUsageCollector(usageCollection, taskManager); + usageCollection.registerCollector(collector); +} diff --git a/x-pack/plugins/alerting/server/usage/index.ts b/x-pack/plugins/alerting/server/usage/index.ts new file mode 100644 index 00000000000000..c54900ebce09f7 --- /dev/null +++ b/x-pack/plugins/alerting/server/usage/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { registerAlertsUsageCollector } from './alerts_usage_collector'; diff --git a/x-pack/plugins/alerting/server/usage/task.ts b/x-pack/plugins/alerting/server/usage/task.ts new file mode 100644 index 00000000000000..3da60aef301e23 --- /dev/null +++ b/x-pack/plugins/alerting/server/usage/task.ts @@ -0,0 +1,104 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Logger, CoreSetup } from 'kibana/server'; +import moment from 'moment'; +import { + RunContext, + TaskManagerSetupContract, + TaskManagerStartContract, +} from '../../../task_manager/server'; + +import { getTotalCountAggregations, getTotalCountInUse } from './alerts_telemetry'; + +export const TELEMETRY_TASK_TYPE = 'alerting_telemetry'; + +export const TASK_ID = `Alerting-${TELEMETRY_TASK_TYPE}`; + +export function initializeAlertingTelemetry( + logger: Logger, + core: CoreSetup, + taskManager: TaskManagerSetupContract, + kibanaIndex: string +) { + registerAlertingTelemetryTask(logger, core, taskManager, kibanaIndex); +} + +export function scheduleAlertingTelemetry(logger: Logger, taskManager?: TaskManagerStartContract) { + if (taskManager) { + scheduleTasks(logger, taskManager); + } +} + +function registerAlertingTelemetryTask( + logger: Logger, + core: CoreSetup, + taskManager: TaskManagerSetupContract, + kibanaIndex: string +) { + taskManager.registerTaskDefinitions({ + [TELEMETRY_TASK_TYPE]: { + title: 'Alerting telemetry fetch task', + type: TELEMETRY_TASK_TYPE, + timeout: '5m', + createTaskRunner: telemetryTaskRunner(logger, core, kibanaIndex), + }, + }); +} + +async function scheduleTasks(logger: Logger, taskManager: TaskManagerStartContract) { + try { + await taskManager.ensureScheduled({ + id: TASK_ID, + taskType: TELEMETRY_TASK_TYPE, + state: { byDate: {}, suggestionsByDate: {}, saved: {}, runs: 0 }, + params: {}, + }); + } catch (e) { + logger.debug(`Error scheduling task, received ${e.message}`); + } +} + +export function telemetryTaskRunner(logger: Logger, core: CoreSetup, kibanaIndex: string) { + return ({ taskInstance }: RunContext) => { + const { state } = taskInstance; + const callCluster = core.elasticsearch.adminClient.callAsInternalUser; + return { + async run() { + return Promise.all([ + getTotalCountAggregations(callCluster, kibanaIndex), + getTotalCountInUse(callCluster, kibanaIndex), + ]) + .then(([totalCountAggregations, totalInUse]) => { + return { + state: { + runs: (state.runs || 0) + 1, + ...totalCountAggregations, + count_active_by_type: totalInUse.countByType, + count_active_total: totalInUse.countTotal, + count_disabled_total: totalCountAggregations.count_total - totalInUse.countTotal, + }, + runAt: getNextMidnight(), + }; + }) + .catch(errMsg => { + logger.warn(`Error executing alerting telemetry task: ${errMsg}`); + return { + state: {}, + runAt: getNextMidnight(), + }; + }); + }, + }; + }; +} + +function getNextMidnight() { + return moment() + .add(1, 'd') + .startOf('d') + .toDate(); +} diff --git a/x-pack/plugins/alerting/server/usage/types.ts b/x-pack/plugins/alerting/server/usage/types.ts new file mode 100644 index 00000000000000..71edefd336212c --- /dev/null +++ b/x-pack/plugins/alerting/server/usage/types.ts @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface AlertsUsage { + count_total: number; + count_active_total: number; + count_disabled_total: number; + count_by_type: Record; + count_active_by_type: Record; + throttle_time: { + min: number; + avg: number; + max: number; + }; + schedule_time: { + min: number; + avg: number; + max: number; + }; + connectors_per_alert: { + min: number; + avg: number; + max: number; + }; +} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx index 9bdad54f033522..865ab6ea04ceac 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import * as React from 'react'; -import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; import { coreMock } from '../../../../../../../../src/core/public/mocks'; import { ReactWrapper } from 'enzyme'; import { act } from 'react-dom/test-utils'; @@ -49,7 +49,7 @@ actionTypeRegistry.list.mockReturnValue([]); describe('alerts_list component empty', () => { let wrapper: ReactWrapper; - beforeEach(async () => { + async function setup() { const { loadAlerts, loadAlertTypes } = jest.requireMock('../../../lib/alert_api'); const { loadActionTypes, loadAllActions } = jest.requireMock( '../../../lib/action_connector_api' @@ -114,22 +114,25 @@ describe('alerts_list component empty', () => { alertTypeRegistry: alertTypeRegistry as any, }; + wrapper = mountWithIntl( + + + + ); + await act(async () => { - wrapper = mountWithIntl( - - - - ); + await nextTick(); + wrapper.update(); }); + } - await waitForRender(wrapper); - }); - - it('renders empty list', () => { + it('renders empty list', async () => { + await setup(); expect(wrapper.find('[data-test-subj="createFirstAlertEmptyPrompt"]').exists()).toBeTruthy(); }); - it('renders Create alert button', () => { + it('renders Create alert button', async () => { + await setup(); expect( wrapper.find('[data-test-subj="createFirstAlertButton"]').find('EuiButton') ).toHaveLength(1); @@ -140,7 +143,7 @@ describe('alerts_list component empty', () => { describe('alerts_list component with items', () => { let wrapper: ReactWrapper; - beforeEach(async () => { + async function setup() { const { loadAlerts, loadAlertTypes } = jest.requireMock('../../../lib/alert_api'); const { loadActionTypes, loadAllActions } = jest.requireMock( '../../../lib/action_connector_api' @@ -239,21 +242,23 @@ describe('alerts_list component with items', () => { alertTypeRegistry: alertTypeRegistry as any, }; + wrapper = mountWithIntl( + + + + ); + await act(async () => { - wrapper = mountWithIntl( - - - - ); + await nextTick(); + wrapper.update(); }); - await waitForRender(wrapper); - expect(loadAlerts).toHaveBeenCalled(); expect(loadActionTypes).toHaveBeenCalled(); - }); + } - it('renders table of connectors', () => { + it('renders table of connectors', async () => { + await setup(); expect(wrapper.find('EuiBasicTable')).toHaveLength(1); expect(wrapper.find('EuiTableRow')).toHaveLength(2); }); @@ -262,7 +267,7 @@ describe('alerts_list component with items', () => { describe('alerts_list component empty with show only capability', () => { let wrapper: ReactWrapper; - beforeEach(async () => { + async function setup() { const { loadAlerts, loadAlertTypes } = jest.requireMock('../../../lib/alert_api'); const { loadActionTypes, loadAllActions } = jest.requireMock( '../../../lib/action_connector_api' @@ -330,18 +335,20 @@ describe('alerts_list component empty with show only capability', () => { alertTypeRegistry: {} as any, }; + wrapper = mountWithIntl( + + + + ); + await act(async () => { - wrapper = mountWithIntl( - - - - ); + await nextTick(); + wrapper.update(); }); + } - await waitForRender(wrapper); - }); - - it('not renders create alert button', () => { + it('not renders create alert button', async () => { + await setup(); expect(wrapper.find('[data-test-subj="createAlertButton"]')).toHaveLength(0); }); }); @@ -349,7 +356,7 @@ describe('alerts_list component empty with show only capability', () => { describe('alerts_list with show only capability', () => { let wrapper: ReactWrapper; - beforeEach(async () => { + async function setup() { const { loadAlerts, loadAlertTypes } = jest.requireMock('../../../lib/alert_api'); const { loadActionTypes, loadAllActions } = jest.requireMock( '../../../lib/action_connector_api' @@ -448,26 +455,22 @@ describe('alerts_list with show only capability', () => { alertTypeRegistry: alertTypeRegistry as any, }; + wrapper = mountWithIntl( + + + + ); + await act(async () => { - wrapper = mountWithIntl( - - - - ); + await nextTick(); + wrapper.update(); }); + } - await waitForRender(wrapper); - }); - - it('renders table of alerts with delete button disabled', () => { + it('renders table of alerts with delete button disabled', async () => { + await setup(); expect(wrapper.find('EuiBasicTable')).toHaveLength(1); expect(wrapper.find('EuiTableRow')).toHaveLength(2); // TODO: check delete button }); }); - -async function waitForRender(wrapper: ReactWrapper) { - await Promise.resolve(); - await Promise.resolve(); - wrapper.update(); -} From 2d0bf9d247fcad8a6a19ad9dac073b5b6b3ef17b Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Thu, 12 Mar 2020 13:13:58 -0500 Subject: [PATCH 11/19] fix karma debug typo (#60029) --- tasks/test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks/test.js b/tasks/test.js index 504247f5b53551..5618ebba4e6ebe 100644 --- a/tasks/test.js +++ b/tasks/test.js @@ -61,7 +61,7 @@ module.exports = function(grunt) { 'run:apiIntegrationTests', ]); - grunt.registerTask('test:karmaDebug', ['checkPlugins', 'run:karmaDebugServer', 'karma:dev']); + grunt.registerTask('test:karmaDebug', ['checkPlugins', 'run:karmaTestDebugServer', 'karma:dev']); grunt.registerTask('test:mochaCoverage', ['run:mochaCoverage']); grunt.registerTask('test', subTask => { From 73d101314066f56b838501931db7054e9b4fe6fa Mon Sep 17 00:00:00 2001 From: Chris Roberson Date: Thu, 12 Mar 2020 14:18:15 -0400 Subject: [PATCH 12/19] [Monitoring] Re-enable logstash tests (#59815) * Revert "Skip all logstash pipeline tests" This reverts commit d8aa1fb2da7a6941cf9944bb8bc170b53e3b345b. * Reenable tests and fix them based on recent changes * Fix more tests Co-authored-by: Elastic Machine --- .../lib/logstash/get_paginated_pipelines.js | 2 +- .../fixtures/multicluster_pipelines.json | 54 ++++++++++++++++++- .../logstash/fixtures/pipelines.json | 2 +- .../logstash/multicluster_pipelines.js | 2 +- .../apis/monitoring/logstash/pipelines.js | 2 +- .../apps/monitoring/logstash/pipelines.js | 14 ++--- 6 files changed, 64 insertions(+), 12 deletions(-) diff --git a/x-pack/legacy/plugins/monitoring/server/lib/logstash/get_paginated_pipelines.js b/x-pack/legacy/plugins/monitoring/server/lib/logstash/get_paginated_pipelines.js index c4e211039de31a..5ceb9536be2ae4 100644 --- a/x-pack/legacy/plugins/monitoring/server/lib/logstash/get_paginated_pipelines.js +++ b/x-pack/legacy/plugins/monitoring/server/lib/logstash/get_paginated_pipelines.js @@ -33,7 +33,7 @@ export async function getPaginatedPipelines( { clusterUuid, logstashUuid }, { throughputMetric, nodesCountMetric }, pagination, - sort, + sort = { field: null }, queryText ) { const sortField = sort.field; diff --git a/x-pack/test/api_integration/apis/monitoring/logstash/fixtures/multicluster_pipelines.json b/x-pack/test/api_integration/apis/monitoring/logstash/fixtures/multicluster_pipelines.json index 1094e2b1a40043..4062c1196247b2 100644 --- a/x-pack/test/api_integration/apis/monitoring/logstash/fixtures/multicluster_pipelines.json +++ b/x-pack/test/api_integration/apis/monitoring/logstash/fixtures/multicluster_pipelines.json @@ -1 +1,53 @@ -{"pipelines":[{"id":"uno","metrics":{"throughput":{"bucket_size":"10 seconds","timeRange":{"min":1573485225266,"max":1573485425399},"metric":{"app":"logstash","field":"logstash_stats.pipelines.events.out","label":"Pipeline Throughput","description":"Number of events emitted per second by the Logstash pipeline at the outputs stage.","units":"e/s","format":"0,0.[00]","hasCalculation":true,"isDerivative":false},"data":[[1573485230000,0]]},"nodesCount":{"bucket_size":"10 seconds","timeRange":{"min":1573485225266,"max":1573485425399},"metric":{"app":"logstash","field":"logstash_stats.logstash.uuid","label":"Pipeline Node Count","description":"Number of nodes on which the Logstash pipeline is running.","units":"","format":"0,0.[00]","hasCalculation":true,"isDerivative":false},"data":[[1573485230000,1]]}},"latestThroughput":0,"latestNodesCount":1}],"clusterStatus":{"node_count":1,"events_in_total":0,"events_out_total":0,"avg_memory":1037959168,"avg_memory_used":203687512,"max_uptime":863288,"pipeline_count":1,"queue_types":{"memory":1,"persisted":0},"versions":["8.0.0"]},"totalPipelineCount":1} +{ + "pipelines": [ + { + "id": "uno", + "metrics": { + "throughput": { + "bucket_size": "10 seconds", + "timeRange": { "min": 1573485225266, "max": 1573485425399 }, + "metric": { + "app": "logstash", + "field": "logstash_stats.pipelines.events.out", + "label": "Pipeline Throughput", + "description": "Number of events emitted per second by the Logstash pipeline at the outputs stage.", + "units": "e/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [[1573485230000, 0]] + }, + "nodesCount": { + "bucket_size": "10 seconds", + "timeRange": { "min": 1573485225266, "max": 1573485425399 }, + "metric": { + "app": "logstash", + "field": "logstash_stats.logstash.uuid", + "label": "Pipeline Node Count", + "description": "Number of nodes on which the Logstash pipeline is running.", + "units": "", + "format": "0,0.[00]", + "hasCalculation": true, + "isDerivative": false + }, + "data": [[1573485230000, 1]] + } + }, + "latestThroughput": 0, + "latestNodesCount": 1 + } + ], + "clusterStatus": { + "node_count": 1, + "events_in_total": 0, + "events_out_total": 0, + "avg_memory": 1037959168, + "avg_memory_used": 203687512, + "max_uptime": 863288, + "pipeline_count": 1, + "queue_types": { "memory": 1, "persisted": 0 }, + "versions": ["8.0.0"] + }, + "totalPipelineCount": 1 +} diff --git a/x-pack/test/api_integration/apis/monitoring/logstash/fixtures/pipelines.json b/x-pack/test/api_integration/apis/monitoring/logstash/fixtures/pipelines.json index ae2ffe535ff941..382faa4b1b605a 100644 --- a/x-pack/test/api_integration/apis/monitoring/logstash/fixtures/pipelines.json +++ b/x-pack/test/api_integration/apis/monitoring/logstash/fixtures/pipelines.json @@ -1 +1 @@ -{"pipelines":[{"id":"eight","metrics":{"throughput":{"bucket_size":"10 seconds","timeRange":{"min":1572882044855,"max":1572882638667},"metric":{"app":"logstash","field":"logstash_stats.pipelines.events.out","label":"Pipeline Throughput","description":"Number of events emitted per second by the Logstash pipeline at the outputs stage.","units":"e/s","format":"0,0.[00]","hasCalculation":true,"isDerivative":false},"data":[[1572882220000,0],[1572882260000,0],[1572882270000,0]]},"nodesCount":{"bucket_size":"10 seconds","timeRange":{"min":1572882044855,"max":1572882638667},"metric":{"app":"logstash","field":"logstash_stats.logstash.uuid","label":"Pipeline Node Count","description":"Number of nodes on which the Logstash pipeline is running.","units":"","format":"0,0.[00]","hasCalculation":true,"isDerivative":false},"data":[[1572882220000,1],[1572882260000,1],[1572882270000,1]]}},"latestThroughput":0,"latestNodesCount":1},{"id":"eighteen","metrics":{"throughput":{"bucket_size":"10 seconds","timeRange":{"min":1572882044855,"max":1572882638667},"metric":{"app":"logstash","field":"logstash_stats.pipelines.events.out","label":"Pipeline Throughput","description":"Number of events emitted per second by the Logstash pipeline at the outputs stage.","units":"e/s","format":"0,0.[00]","hasCalculation":true,"isDerivative":false},"data":[[1572882220000,0],[1572882260000,0],[1572882270000,0]]},"nodesCount":{"bucket_size":"10 seconds","timeRange":{"min":1572882044855,"max":1572882638667},"metric":{"app":"logstash","field":"logstash_stats.logstash.uuid","label":"Pipeline Node Count","description":"Number of nodes on which the Logstash pipeline is running.","units":"","format":"0,0.[00]","hasCalculation":true,"isDerivative":false},"data":[[1572882220000,1],[1572882260000,1],[1572882270000,1]]}},"latestThroughput":0,"latestNodesCount":1},{"id":"eleven","metrics":{"throughput":{"bucket_size":"10 seconds","timeRange":{"min":1572882044855,"max":1572882638667},"metric":{"app":"logstash","field":"logstash_stats.pipelines.events.out","label":"Pipeline Throughput","description":"Number of events emitted per second by the Logstash pipeline at the outputs stage.","units":"e/s","format":"0,0.[00]","hasCalculation":true,"isDerivative":false},"data":[[1572882220000,0],[1572882260000,0],[1572882270000,0]]},"nodesCount":{"bucket_size":"10 seconds","timeRange":{"min":1572882044855,"max":1572882638667},"metric":{"app":"logstash","field":"logstash_stats.logstash.uuid","label":"Pipeline Node Count","description":"Number of nodes on which the Logstash pipeline is running.","units":"","format":"0,0.[00]","hasCalculation":true,"isDerivative":false},"data":[[1572882220000,1],[1572882260000,1],[1572882270000,1]]}},"latestThroughput":0,"latestNodesCount":1},{"id":"fifteen","metrics":{"throughput":{"bucket_size":"10 seconds","timeRange":{"min":1572882044855,"max":1572882638667},"metric":{"app":"logstash","field":"logstash_stats.pipelines.events.out","label":"Pipeline Throughput","description":"Number of events emitted per second by the Logstash pipeline at the outputs stage.","units":"e/s","format":"0,0.[00]","hasCalculation":true,"isDerivative":false},"data":[[1572882220000,0],[1572882260000,0],[1572882270000,0]]},"nodesCount":{"bucket_size":"10 seconds","timeRange":{"min":1572882044855,"max":1572882638667},"metric":{"app":"logstash","field":"logstash_stats.logstash.uuid","label":"Pipeline Node Count","description":"Number of nodes on which the Logstash pipeline is running.","units":"","format":"0,0.[00]","hasCalculation":true,"isDerivative":false},"data":[[1572882220000,1],[1572882260000,1],[1572882270000,1]]}},"latestThroughput":0,"latestNodesCount":1},{"id":"five","metrics":{"throughput":{"bucket_size":"10 seconds","timeRange":{"min":1572882044855,"max":1572882638667},"metric":{"app":"logstash","field":"logstash_stats.pipelines.events.out","label":"Pipeline Throughput","description":"Number of events emitted per second by the Logstash pipeline at the outputs stage.","units":"e/s","format":"0,0.[00]","hasCalculation":true,"isDerivative":false},"data":[[1572882220000,0],[1572882260000,0],[1572882270000,0]]},"nodesCount":{"bucket_size":"10 seconds","timeRange":{"min":1572882044855,"max":1572882638667},"metric":{"app":"logstash","field":"logstash_stats.logstash.uuid","label":"Pipeline Node Count","description":"Number of nodes on which the Logstash pipeline is running.","units":"","format":"0,0.[00]","hasCalculation":true,"isDerivative":false},"data":[[1572882220000,1],[1572882260000,1],[1572882270000,1]]}},"latestThroughput":0,"latestNodesCount":1},{"id":"four","metrics":{"throughput":{"bucket_size":"10 seconds","timeRange":{"min":1572882044855,"max":1572882638667},"metric":{"app":"logstash","field":"logstash_stats.pipelines.events.out","label":"Pipeline Throughput","description":"Number of events emitted per second by the Logstash pipeline at the outputs stage.","units":"e/s","format":"0,0.[00]","hasCalculation":true,"isDerivative":false},"data":[[1572882220000,0],[1572882260000,0],[1572882270000,0]]},"nodesCount":{"bucket_size":"10 seconds","timeRange":{"min":1572882044855,"max":1572882638667},"metric":{"app":"logstash","field":"logstash_stats.logstash.uuid","label":"Pipeline Node Count","description":"Number of nodes on which the Logstash pipeline is running.","units":"","format":"0,0.[00]","hasCalculation":true,"isDerivative":false},"data":[[1572882220000,1],[1572882260000,1],[1572882270000,1]]}},"latestThroughput":0,"latestNodesCount":1},{"id":"fourteen","metrics":{"throughput":{"bucket_size":"10 seconds","timeRange":{"min":1572882044855,"max":1572882638667},"metric":{"app":"logstash","field":"logstash_stats.pipelines.events.out","label":"Pipeline Throughput","description":"Number of events emitted per second by the Logstash pipeline at the outputs stage.","units":"e/s","format":"0,0.[00]","hasCalculation":true,"isDerivative":false},"data":[[1572882220000,0],[1572882260000,0],[1572882270000,0]]},"nodesCount":{"bucket_size":"10 seconds","timeRange":{"min":1572882044855,"max":1572882638667},"metric":{"app":"logstash","field":"logstash_stats.logstash.uuid","label":"Pipeline Node Count","description":"Number of nodes on which the Logstash pipeline is running.","units":"","format":"0,0.[00]","hasCalculation":true,"isDerivative":false},"data":[[1572882220000,1],[1572882260000,1],[1572882270000,1]]}},"latestThroughput":0,"latestNodesCount":1},{"id":"nine","metrics":{"throughput":{"bucket_size":"10 seconds","timeRange":{"min":1572882044855,"max":1572882638667},"metric":{"app":"logstash","field":"logstash_stats.pipelines.events.out","label":"Pipeline Throughput","description":"Number of events emitted per second by the Logstash pipeline at the outputs stage.","units":"e/s","format":"0,0.[00]","hasCalculation":true,"isDerivative":false},"data":[[1572882220000,0],[1572882260000,0],[1572882270000,0]]},"nodesCount":{"bucket_size":"10 seconds","timeRange":{"min":1572882044855,"max":1572882638667},"metric":{"app":"logstash","field":"logstash_stats.logstash.uuid","label":"Pipeline Node Count","description":"Number of nodes on which the Logstash pipeline is running.","units":"","format":"0,0.[00]","hasCalculation":true,"isDerivative":false},"data":[[1572882220000,1],[1572882260000,1],[1572882270000,1]]}},"latestThroughput":0,"latestNodesCount":1},{"id":"nineteen","metrics":{"throughput":{"bucket_size":"10 seconds","timeRange":{"min":1572882044855,"max":1572882638667},"metric":{"app":"logstash","field":"logstash_stats.pipelines.events.out","label":"Pipeline Throughput","description":"Number of events emitted per second by the Logstash pipeline at the outputs stage.","units":"e/s","format":"0,0.[00]","hasCalculation":true,"isDerivative":false},"data":[[1572882220000,0],[1572882260000,0],[1572882270000,0]]},"nodesCount":{"bucket_size":"10 seconds","timeRange":{"min":1572882044855,"max":1572882638667},"metric":{"app":"logstash","field":"logstash_stats.logstash.uuid","label":"Pipeline Node Count","description":"Number of nodes on which the Logstash pipeline is running.","units":"","format":"0,0.[00]","hasCalculation":true,"isDerivative":false},"data":[[1572882220000,1],[1572882260000,1],[1572882270000,1]]}},"latestThroughput":0,"latestNodesCount":1},{"id":"one","metrics":{"throughput":{"bucket_size":"10 seconds","timeRange":{"min":1572882044855,"max":1572882638667},"metric":{"app":"logstash","field":"logstash_stats.pipelines.events.out","label":"Pipeline Throughput","description":"Number of events emitted per second by the Logstash pipeline at the outputs stage.","units":"e/s","format":"0,0.[00]","hasCalculation":true,"isDerivative":false},"data":[[1572882220000,0],[1572882260000,0],[1572882270000,0]]},"nodesCount":{"bucket_size":"10 seconds","timeRange":{"min":1572882044855,"max":1572882638667},"metric":{"app":"logstash","field":"logstash_stats.logstash.uuid","label":"Pipeline Node Count","description":"Number of nodes on which the Logstash pipeline is running.","units":"","format":"0,0.[00]","hasCalculation":true,"isDerivative":false},"data":[[1572882220000,1],[1572882260000,1],[1572882270000,1]]}},"latestThroughput":0,"latestNodesCount":1}],"clusterStatus":{"node_count":1,"events_in_total":312,"events_out_total":234,"avg_memory":1037959168,"avg_memory_used":205063840,"max_uptime":40598,"pipeline_count":26,"queue_types":{"memory":26,"persisted":0},"versions":["8.0.0"]},"totalPipelineCount":26} +{"pipelines":[{"id":"eight","metrics":{"throughput":{"bucket_size":"10 seconds","timeRange":{"min":1572882044855,"max":1572882638667},"metric":{"app":"logstash","field":"logstash_stats.pipelines.events.out","label":"Pipeline Throughput","description":"Number of events emitted per second by the Logstash pipeline at the outputs stage.","units":"e/s","format":"0,0.[00]","hasCalculation":false,"isDerivative":true},"data":[[1572882220000,null],[1572882230000,null],[1572882240000,null],[1572882250000,null],[1572882260000,null],[1572882270000,0.5]]},"nodesCount":{"bucket_size":"10 seconds","timeRange":{"min":1572882044855,"max":1572882638667},"metric":{"app":"logstash","field":"logstash_stats.logstash.uuid","label":"Pipeline Node Count","description":"Number of nodes on which the Logstash pipeline is running.","units":"","format":"0,0.[00]","hasCalculation":true,"isDerivative":false},"data":[[1572882220000,1],[1572882230000,null],[1572882240000,null],[1572882250000,null],[1572882260000,1],[1572882270000,1]]}},"latestThroughput":0.5,"latestNodesCount":1},{"id":"eighteen","metrics":{"throughput":{"bucket_size":"10 seconds","timeRange":{"min":1572882044855,"max":1572882638667},"metric":{"app":"logstash","field":"logstash_stats.pipelines.events.out","label":"Pipeline Throughput","description":"Number of events emitted per second by the Logstash pipeline at the outputs stage.","units":"e/s","format":"0,0.[00]","hasCalculation":false,"isDerivative":true},"data":[[1572882220000,null],[1572882230000,null],[1572882240000,null],[1572882250000,null],[1572882260000,null],[1572882270000,0.5]]},"nodesCount":{"bucket_size":"10 seconds","timeRange":{"min":1572882044855,"max":1572882638667},"metric":{"app":"logstash","field":"logstash_stats.logstash.uuid","label":"Pipeline Node Count","description":"Number of nodes on which the Logstash pipeline is running.","units":"","format":"0,0.[00]","hasCalculation":true,"isDerivative":false},"data":[[1572882220000,1],[1572882230000,null],[1572882240000,null],[1572882250000,null],[1572882260000,1],[1572882270000,1]]}},"latestThroughput":0.5,"latestNodesCount":1},{"id":"eleven","metrics":{"throughput":{"bucket_size":"10 seconds","timeRange":{"min":1572882044855,"max":1572882638667},"metric":{"app":"logstash","field":"logstash_stats.pipelines.events.out","label":"Pipeline Throughput","description":"Number of events emitted per second by the Logstash pipeline at the outputs stage.","units":"e/s","format":"0,0.[00]","hasCalculation":false,"isDerivative":true},"data":[[1572882220000,null],[1572882230000,null],[1572882240000,null],[1572882250000,null],[1572882260000,null],[1572882270000,0.5]]},"nodesCount":{"bucket_size":"10 seconds","timeRange":{"min":1572882044855,"max":1572882638667},"metric":{"app":"logstash","field":"logstash_stats.logstash.uuid","label":"Pipeline Node Count","description":"Number of nodes on which the Logstash pipeline is running.","units":"","format":"0,0.[00]","hasCalculation":true,"isDerivative":false},"data":[[1572882220000,1],[1572882230000,null],[1572882240000,null],[1572882250000,null],[1572882260000,1],[1572882270000,1]]}},"latestThroughput":0.5,"latestNodesCount":1},{"id":"fifteen","metrics":{"throughput":{"bucket_size":"10 seconds","timeRange":{"min":1572882044855,"max":1572882638667},"metric":{"app":"logstash","field":"logstash_stats.pipelines.events.out","label":"Pipeline Throughput","description":"Number of events emitted per second by the Logstash pipeline at the outputs stage.","units":"e/s","format":"0,0.[00]","hasCalculation":false,"isDerivative":true},"data":[[1572882220000,null],[1572882230000,null],[1572882240000,null],[1572882250000,null],[1572882260000,null],[1572882270000,0.5]]},"nodesCount":{"bucket_size":"10 seconds","timeRange":{"min":1572882044855,"max":1572882638667},"metric":{"app":"logstash","field":"logstash_stats.logstash.uuid","label":"Pipeline Node Count","description":"Number of nodes on which the Logstash pipeline is running.","units":"","format":"0,0.[00]","hasCalculation":true,"isDerivative":false},"data":[[1572882220000,1],[1572882230000,null],[1572882240000,null],[1572882250000,null],[1572882260000,1],[1572882270000,1]]}},"latestThroughput":0.5,"latestNodesCount":1},{"id":"five","metrics":{"throughput":{"bucket_size":"10 seconds","timeRange":{"min":1572882044855,"max":1572882638667},"metric":{"app":"logstash","field":"logstash_stats.pipelines.events.out","label":"Pipeline Throughput","description":"Number of events emitted per second by the Logstash pipeline at the outputs stage.","units":"e/s","format":"0,0.[00]","hasCalculation":false,"isDerivative":true},"data":[[1572882220000,null],[1572882230000,null],[1572882240000,null],[1572882250000,null],[1572882260000,null],[1572882270000,0.5]]},"nodesCount":{"bucket_size":"10 seconds","timeRange":{"min":1572882044855,"max":1572882638667},"metric":{"app":"logstash","field":"logstash_stats.logstash.uuid","label":"Pipeline Node Count","description":"Number of nodes on which the Logstash pipeline is running.","units":"","format":"0,0.[00]","hasCalculation":true,"isDerivative":false},"data":[[1572882220000,1],[1572882230000,null],[1572882240000,null],[1572882250000,null],[1572882260000,1],[1572882270000,1]]}},"latestThroughput":0.5,"latestNodesCount":1},{"id":"four","metrics":{"throughput":{"bucket_size":"10 seconds","timeRange":{"min":1572882044855,"max":1572882638667},"metric":{"app":"logstash","field":"logstash_stats.pipelines.events.out","label":"Pipeline Throughput","description":"Number of events emitted per second by the Logstash pipeline at the outputs stage.","units":"e/s","format":"0,0.[00]","hasCalculation":false,"isDerivative":true},"data":[[1572882220000,null],[1572882230000,null],[1572882240000,null],[1572882250000,null],[1572882260000,null],[1572882270000,0.5]]},"nodesCount":{"bucket_size":"10 seconds","timeRange":{"min":1572882044855,"max":1572882638667},"metric":{"app":"logstash","field":"logstash_stats.logstash.uuid","label":"Pipeline Node Count","description":"Number of nodes on which the Logstash pipeline is running.","units":"","format":"0,0.[00]","hasCalculation":true,"isDerivative":false},"data":[[1572882220000,1],[1572882230000,null],[1572882240000,null],[1572882250000,null],[1572882260000,1],[1572882270000,1]]}},"latestThroughput":0.5,"latestNodesCount":1},{"id":"fourteen","metrics":{"throughput":{"bucket_size":"10 seconds","timeRange":{"min":1572882044855,"max":1572882638667},"metric":{"app":"logstash","field":"logstash_stats.pipelines.events.out","label":"Pipeline Throughput","description":"Number of events emitted per second by the Logstash pipeline at the outputs stage.","units":"e/s","format":"0,0.[00]","hasCalculation":false,"isDerivative":true},"data":[[1572882220000,null],[1572882230000,null],[1572882240000,null],[1572882250000,null],[1572882260000,null],[1572882270000,0.5]]},"nodesCount":{"bucket_size":"10 seconds","timeRange":{"min":1572882044855,"max":1572882638667},"metric":{"app":"logstash","field":"logstash_stats.logstash.uuid","label":"Pipeline Node Count","description":"Number of nodes on which the Logstash pipeline is running.","units":"","format":"0,0.[00]","hasCalculation":true,"isDerivative":false},"data":[[1572882220000,1],[1572882230000,null],[1572882240000,null],[1572882250000,null],[1572882260000,1],[1572882270000,1]]}},"latestThroughput":0.5,"latestNodesCount":1},{"id":"nine","metrics":{"throughput":{"bucket_size":"10 seconds","timeRange":{"min":1572882044855,"max":1572882638667},"metric":{"app":"logstash","field":"logstash_stats.pipelines.events.out","label":"Pipeline Throughput","description":"Number of events emitted per second by the Logstash pipeline at the outputs stage.","units":"e/s","format":"0,0.[00]","hasCalculation":false,"isDerivative":true},"data":[[1572882220000,null],[1572882230000,null],[1572882240000,null],[1572882250000,null],[1572882260000,null],[1572882270000,0.5]]},"nodesCount":{"bucket_size":"10 seconds","timeRange":{"min":1572882044855,"max":1572882638667},"metric":{"app":"logstash","field":"logstash_stats.logstash.uuid","label":"Pipeline Node Count","description":"Number of nodes on which the Logstash pipeline is running.","units":"","format":"0,0.[00]","hasCalculation":true,"isDerivative":false},"data":[[1572882220000,1],[1572882230000,null],[1572882240000,null],[1572882250000,null],[1572882260000,1],[1572882270000,1]]}},"latestThroughput":0.5,"latestNodesCount":1},{"id":"nineteen","metrics":{"throughput":{"bucket_size":"10 seconds","timeRange":{"min":1572882044855,"max":1572882638667},"metric":{"app":"logstash","field":"logstash_stats.pipelines.events.out","label":"Pipeline Throughput","description":"Number of events emitted per second by the Logstash pipeline at the outputs stage.","units":"e/s","format":"0,0.[00]","hasCalculation":false,"isDerivative":true},"data":[[1572882220000,null],[1572882230000,null],[1572882240000,null],[1572882250000,null],[1572882260000,null],[1572882270000,0.5]]},"nodesCount":{"bucket_size":"10 seconds","timeRange":{"min":1572882044855,"max":1572882638667},"metric":{"app":"logstash","field":"logstash_stats.logstash.uuid","label":"Pipeline Node Count","description":"Number of nodes on which the Logstash pipeline is running.","units":"","format":"0,0.[00]","hasCalculation":true,"isDerivative":false},"data":[[1572882220000,1],[1572882230000,null],[1572882240000,null],[1572882250000,null],[1572882260000,1],[1572882270000,1]]}},"latestThroughput":0.5,"latestNodesCount":1},{"id":"one","metrics":{"throughput":{"bucket_size":"10 seconds","timeRange":{"min":1572882044855,"max":1572882638667},"metric":{"app":"logstash","field":"logstash_stats.pipelines.events.out","label":"Pipeline Throughput","description":"Number of events emitted per second by the Logstash pipeline at the outputs stage.","units":"e/s","format":"0,0.[00]","hasCalculation":false,"isDerivative":true},"data":[[1572882220000,null],[1572882230000,null],[1572882240000,null],[1572882250000,null],[1572882260000,null],[1572882270000,0.5]]},"nodesCount":{"bucket_size":"10 seconds","timeRange":{"min":1572882044855,"max":1572882638667},"metric":{"app":"logstash","field":"logstash_stats.logstash.uuid","label":"Pipeline Node Count","description":"Number of nodes on which the Logstash pipeline is running.","units":"","format":"0,0.[00]","hasCalculation":true,"isDerivative":false},"data":[[1572882220000,1],[1572882230000,null],[1572882240000,null],[1572882250000,null],[1572882260000,1],[1572882270000,1]]}},"latestThroughput":0.5,"latestNodesCount":1}],"totalPipelineCount":26,"clusterStatus":{"node_count":1,"events_in_total":312,"events_out_total":234,"avg_memory":1037959168,"avg_memory_used":205063840,"max_uptime":40598,"pipeline_count":26,"queue_types":{"memory":26,"persisted":0},"versions":["8.0.0"]}} diff --git a/x-pack/test/api_integration/apis/monitoring/logstash/multicluster_pipelines.js b/x-pack/test/api_integration/apis/monitoring/logstash/multicluster_pipelines.js index b6dcc1c48d39ff..4d513488bca2d5 100644 --- a/x-pack/test/api_integration/apis/monitoring/logstash/multicluster_pipelines.js +++ b/x-pack/test/api_integration/apis/monitoring/logstash/multicluster_pipelines.js @@ -11,7 +11,7 @@ export default function({ getService }) { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); - describe.skip('pipelines listing multicluster', () => { + describe('pipelines listing multicluster', () => { const archive = 'monitoring/logstash_pipelines_multicluster'; const timeRange = { min: '2019-11-11T15:13:45.266Z', diff --git a/x-pack/test/api_integration/apis/monitoring/logstash/pipelines.js b/x-pack/test/api_integration/apis/monitoring/logstash/pipelines.js index 3516f420f21beb..b93f03151f6cbb 100644 --- a/x-pack/test/api_integration/apis/monitoring/logstash/pipelines.js +++ b/x-pack/test/api_integration/apis/monitoring/logstash/pipelines.js @@ -11,7 +11,7 @@ export default function({ getService }) { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); - describe.skip('pipelines', () => { + describe('pipelines', () => { const archive = 'monitoring/logstash/changing_pipelines'; const timeRange = { min: '2019-11-04T15:40:44.855Z', diff --git a/x-pack/test/functional/apps/monitoring/logstash/pipelines.js b/x-pack/test/functional/apps/monitoring/logstash/pipelines.js index 07d5c882763c79..b92b1bb0c5c0c5 100644 --- a/x-pack/test/functional/apps/monitoring/logstash/pipelines.js +++ b/x-pack/test/functional/apps/monitoring/logstash/pipelines.js @@ -14,7 +14,7 @@ export default function({ getService, getPageObjects }) { const pipelinesList = getService('monitoringLogstashPipelines'); const lsClusterSummaryStatus = getService('monitoringLogstashSummaryStatus'); - describe.skip('Logstash pipelines', () => { + describe('Logstash pipelines', () => { const { setup, tearDown } = getLifecycleMethods(getService, getPageObjects); before(async () => { @@ -50,10 +50,10 @@ export default function({ getService, getPageObjects }) { const pipelinesAll = await pipelinesList.getPipelinesAll(); const tableData = [ - { id: 'main', eventsEmittedRate: '108.3 e/s', nodeCount: '1' }, - { id: 'nginx_logs', eventsEmittedRate: '29.2 e/s', nodeCount: '1' }, + { id: 'main', eventsEmittedRate: '162.5 e/s', nodeCount: '1' }, + { id: 'nginx_logs', eventsEmittedRate: '62.5 e/s', nodeCount: '1' }, { id: 'test_interpolation', eventsEmittedRate: '0 e/s', nodeCount: '1' }, - { id: 'tweets_about_labradoodles', eventsEmittedRate: '0.6 e/s', nodeCount: '1' }, + { id: 'tweets_about_labradoodles', eventsEmittedRate: '1.2 e/s', nodeCount: '1' }, ]; // check the all data in the table @@ -74,9 +74,9 @@ export default function({ getService, getPageObjects }) { const tableData = [ { id: 'test_interpolation', eventsEmittedRate: '0 e/s', nodeCount: '1' }, - { id: 'tweets_about_labradoodles', eventsEmittedRate: '0.6 e/s', nodeCount: '1' }, - { id: 'nginx_logs', eventsEmittedRate: '29.2 e/s', nodeCount: '1' }, - { id: 'main', eventsEmittedRate: '108.3 e/s', nodeCount: '1' }, + { id: 'tweets_about_labradoodles', eventsEmittedRate: '1.2 e/s', nodeCount: '1' }, + { id: 'nginx_logs', eventsEmittedRate: '62.5 e/s', nodeCount: '1' }, + { id: 'main', eventsEmittedRate: '162.5 e/s', nodeCount: '1' }, ]; // check the all data in the table From 728d073c124fcb5fff4db0fd68256ce36d63e65d Mon Sep 17 00:00:00 2001 From: Brandon Kobel Date: Thu, 12 Mar 2020 11:38:53 -0700 Subject: [PATCH 13/19] Using re2 for Timelion regular expressions (#55208) * Using re2 for Timelion's regexs * Patching native modules * Restructuring to be more generic * Fixing download_node_builds_task tests * Updating linux sha after properly gzipping the archive * Using a Centos7 machine with devtoolset-6. That's what node does * Using new archives which Travis built for us * Not using a standard import to prevent Kibana from not starting up If the "portable" version of RE2 for some reason isn't truly portable and can't load, we don't want to prevent the rest of Kibana from working properly. This will only prevent Timelion from working, which isn't great, but is less worse * Isolating the require even further * Detecting the package version mismatches, thanks Larry! Co-authored-by: Elastic Machine --- .gitignore | 1 + package.json | 1 + src/dev/build/build_distributables.js | 2 + .../nodejs => lib}/__tests__/download.js | 0 .../build/{tasks/nodejs => lib}/download.js | 2 +- src/dev/build/lib/index.js | 1 + src/dev/build/tasks/index.js | 1 + .../__tests__/download_node_builds_task.js | 2 +- .../tasks/nodejs/download_node_builds_task.js | 2 +- .../build/tasks/patch_native_modules_task.js | 85 +++++++++++++++++++ .../timelion/server/series_functions/label.js | 5 +- yarn.lock | 9 +- 12 files changed, 106 insertions(+), 5 deletions(-) rename src/dev/build/{tasks/nodejs => lib}/__tests__/download.js (100%) rename src/dev/build/{tasks/nodejs => lib}/download.js (98%) create mode 100644 src/dev/build/tasks/patch_native_modules_task.js diff --git a/.gitignore b/.gitignore index efb5c577746334..02c9057e3f83a8 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ /.es .DS_Store .node_binaries +.native_modules node_modules !/src/dev/npm/integration_tests/__fixtures__/fixture1/node_modules !/src/dev/notice/__fixtures__/node_modules diff --git a/package.json b/package.json index a97c21164d406d..1f8973de3d22a0 100644 --- a/package.json +++ b/package.json @@ -227,6 +227,7 @@ "pug": "^2.0.4", "query-string": "6.10.1", "raw-loader": "3.1.0", + "re2": "1.10.5", "react": "^16.12.0", "react-color": "^2.13.8", "react-dom": "^16.12.0", diff --git a/src/dev/build/build_distributables.js b/src/dev/build/build_distributables.js index 6c2efeebc60c32..008281db8bb935 100644 --- a/src/dev/build/build_distributables.js +++ b/src/dev/build/build_distributables.js @@ -45,6 +45,7 @@ import { InstallDependenciesTask, BuildKibanaPlatformPluginsTask, OptimizeBuildTask, + PatchNativeModulesTask, RemovePackageJsonDepsTask, RemoveWorkspacesTask, TranspileBabelTask, @@ -132,6 +133,7 @@ export async function buildDistributables(options) { * directories and perform platform-specific steps */ await run(CreateArchivesSourcesTask); + await run(PatchNativeModulesTask); await run(CleanExtraBinScriptsTask); await run(CleanExtraBrowsersTask); await run(CleanNodeBuildsTask); diff --git a/src/dev/build/tasks/nodejs/__tests__/download.js b/src/dev/build/lib/__tests__/download.js similarity index 100% rename from src/dev/build/tasks/nodejs/__tests__/download.js rename to src/dev/build/lib/__tests__/download.js diff --git a/src/dev/build/tasks/nodejs/download.js b/src/dev/build/lib/download.js similarity index 98% rename from src/dev/build/tasks/nodejs/download.js rename to src/dev/build/lib/download.js index 0bd10e5b840151..97f82ed14b4096 100644 --- a/src/dev/build/tasks/nodejs/download.js +++ b/src/dev/build/lib/download.js @@ -24,7 +24,7 @@ import chalk from 'chalk'; import { createHash } from 'crypto'; import Axios from 'axios'; -import { mkdirp } from '../../lib'; +import { mkdirp } from './fs'; function tryUnlink(path) { try { diff --git a/src/dev/build/lib/index.js b/src/dev/build/lib/index.js index afebd090d797da..25c4b74eefd22c 100644 --- a/src/dev/build/lib/index.js +++ b/src/dev/build/lib/index.js @@ -33,5 +33,6 @@ export { compress, isFileAccessible, } from './fs'; +export { download } from './download'; export { scanDelete } from './scan_delete'; export { scanCopy } from './scan_copy'; diff --git a/src/dev/build/tasks/index.js b/src/dev/build/tasks/index.js index 8105fa8a7d5d47..1faae655c9abbc 100644 --- a/src/dev/build/tasks/index.js +++ b/src/dev/build/tasks/index.js @@ -32,6 +32,7 @@ export * from './nodejs_modules'; export * from './notice_file_task'; export * from './optimize_task'; export * from './os_packages'; +export * from './patch_native_modules_task'; export * from './transpile_babel_task'; export * from './transpile_scss_task'; export * from './verify_env_task'; diff --git a/src/dev/build/tasks/nodejs/__tests__/download_node_builds_task.js b/src/dev/build/tasks/nodejs/__tests__/download_node_builds_task.js index 4c94ed776417dc..1048a8c7386bcb 100644 --- a/src/dev/build/tasks/nodejs/__tests__/download_node_builds_task.js +++ b/src/dev/build/tasks/nodejs/__tests__/download_node_builds_task.js @@ -22,7 +22,7 @@ import expect from '@kbn/expect'; import * as NodeShasumsNS from '../node_shasums'; import * as NodeDownloadInfoNS from '../node_download_info'; -import * as DownloadNS from '../download'; +import * as DownloadNS from '../../../lib/download'; // sinon can't stub '../../../lib' properly import { DownloadNodeBuildsTask } from '../download_node_builds_task'; describe('src/dev/build/tasks/nodejs/download_node_builds_task', () => { diff --git a/src/dev/build/tasks/nodejs/download_node_builds_task.js b/src/dev/build/tasks/nodejs/download_node_builds_task.js index df841960677c18..12e9245262c566 100644 --- a/src/dev/build/tasks/nodejs/download_node_builds_task.js +++ b/src/dev/build/tasks/nodejs/download_node_builds_task.js @@ -17,7 +17,7 @@ * under the License. */ -import { download } from './download'; +import { download } from '../../lib'; import { getNodeShasums } from './node_shasums'; import { getNodeDownloadInfo } from './node_download_info'; diff --git a/src/dev/build/tasks/patch_native_modules_task.js b/src/dev/build/tasks/patch_native_modules_task.js new file mode 100644 index 00000000000000..28fc9ebf3d61fd --- /dev/null +++ b/src/dev/build/tasks/patch_native_modules_task.js @@ -0,0 +1,85 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import fs from 'fs'; +import path from 'path'; +import util from 'util'; +import { deleteAll, download, untar } from '../lib'; + +const BASE_URL = `https://storage.googleapis.com/native-modules`; +const DOWNLOAD_DIRECTORY = '.native_modules'; + +const packages = [ + { + name: 're2', + version: '1.10.5', + destinationPath: 'node_modules/re2/build/Release/', + shas: { + darwin: '066533b592094f91e00412499e44c338ce2466d63c9eaf0dc32be8214bde2099', + linux: '0322cac3c2e106129b650a8eac509f598ed283791d6116984fec4c151b24e574', + windows: '65b5bef7de2352f4787224c2c76a619b6683a868c8d4d71e0fdd500786fc422b', + }, + }, +]; + +async function getInstalledVersion(config, packageName) { + const packageJSONPath = config.resolveFromRepo( + path.join('node_modules', packageName, 'package.json') + ); + const buffer = await util.promisify(fs.readFile)(packageJSONPath); + const packageJSON = JSON.parse(buffer); + return packageJSON.version; +} + +async function patchModule(config, log, build, platform, pkg) { + const installedVersion = await getInstalledVersion(config, pkg.name); + if (installedVersion !== pkg.version) { + throw new Error( + `Can't patch ${pkg.name}'s native module, we were expecting version ${pkg.version} and found ${installedVersion}` + ); + } + const platformName = platform.getName(); + const archiveName = `${pkg.version}-${platformName}.tar.gz`; + const downloadUrl = `${BASE_URL}/${pkg.name}/${archiveName}`; + const downloadPath = config.resolveFromRepo(DOWNLOAD_DIRECTORY, archiveName); + const extractedPath = build.resolvePathForPlatform(platform, pkg.destinationPath); + log.debug(`Patching ${pkg.name} binaries from ${downloadUrl} to ${extractedPath}`); + + await deleteAll([extractedPath], log); + await download({ + log, + url: downloadUrl, + destination: downloadPath, + sha256: pkg.shas[platformName], + retries: 3, + }); + await untar(downloadPath, extractedPath); +} + +export const PatchNativeModulesTask = { + description: 'Patching platform-specific native modules', + async run(config, log, build) { + for (const pkg of packages) { + await Promise.all( + config.getTargetPlatforms().map(async platform => { + await patchModule(config, log, build, platform, pkg); + }) + ); + } + }, +}; diff --git a/src/plugins/timelion/server/series_functions/label.js b/src/plugins/timelion/server/series_functions/label.js index 1e4782e5a381e3..6e46a92b48add8 100644 --- a/src/plugins/timelion/server/series_functions/label.js +++ b/src/plugins/timelion/server/series_functions/label.js @@ -51,7 +51,10 @@ export default new Chainable('label', { const config = args.byName; return alter(args, function(eachSeries) { if (config.regex) { - eachSeries.label = eachSeries.label.replace(new RegExp(config.regex), config.label); + // not using a standard `import` so that if there's an issue with the re2 native module + // that it doesn't prevent Kibana from starting up and we only have an issue using Timelion labels + const RE2 = require('re2'); + eachSeries.label = eachSeries.label.replace(new RE2(config.regex), config.label); } else { eachSeries.label = config.label; } diff --git a/yarn.lock b/yarn.lock index 40873d59496ec4..a87d877f89727a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -21086,7 +21086,7 @@ mute-stream@0.0.8: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== -nan@^2.13.2, nan@^2.9.2: +nan@^2.13.2, nan@^2.14.0, nan@^2.9.2: version "2.14.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== @@ -24096,6 +24096,13 @@ re-resizable@^6.1.1: dependencies: fast-memoize "^2.5.1" +re2@1.10.5: + version "1.10.5" + resolved "https://registry.yarnpkg.com/re2/-/re2-1.10.5.tgz#b3730438121c6bf59d459aff3471177eef513445" + integrity sha512-ssO3AD8/YJzuQUgEasS8PxA8n1yg8JB2VNSJhCebuuHLwaGiufhtFGUypS2bONrCPDbjwlMy7OZD9LkcrQMr1g== + dependencies: + nan "^2.14.0" + react-ace@^5.5.0: version "5.10.0" resolved "https://registry.yarnpkg.com/react-ace/-/react-ace-5.10.0.tgz#e328b37ac52759f700be5afdb86ada2f5ec84c5e" From 9cd6e32de0eb2299ba83f4fc5d00b011b56db2a2 Mon Sep 17 00:00:00 2001 From: Robert Austin Date: Thu, 12 Mar 2020 15:13:53 -0400 Subject: [PATCH 14/19] Endpoint: Change the input type for @kbn/config-schema to work with more schemas (#60007) --- x-pack/plugins/endpoint/common/types.ts | 46 ++++++++++++------------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/x-pack/plugins/endpoint/common/types.ts b/x-pack/plugins/endpoint/common/types.ts index 1c438c40fa38f7..1cb000b0a0357a 100644 --- a/x-pack/plugins/endpoint/common/types.ts +++ b/x-pack/plugins/endpoint/common/types.ts @@ -6,7 +6,6 @@ import { SearchResponse } from 'elasticsearch'; import { TypeOf } from '@kbn/config-schema'; -import * as kbnConfigSchemaTypes from '@kbn/config-schema/target/types/types'; import { alertingIndexGetQuerySchema } from './schema/alert_index'; /** @@ -351,16 +350,16 @@ export type PageId = 'alertsPage' | 'managementPage' | 'policyListPage'; * const input: KbnConfigSchemaInputTypeOf = value * schema.validate(input) // should be valid * ``` + * Note that because the types coming from `@kbn/config-schema`'s schemas sometimes have deeply nested + * `Type` types, we process the result of `TypeOf` instead, as this will be consistent. */ -type KbnConfigSchemaInputTypeOf< - T extends kbnConfigSchemaTypes.Type -> = T extends kbnConfigSchemaTypes.ObjectType +type KbnConfigSchemaInputTypeOf = T extends Record ? KbnConfigSchemaInputObjectTypeOf< T > /** `schema.number()` accepts strings, so this type should accept them as well. */ - : kbnConfigSchemaTypes.Type extends T - ? TypeOf | string - : TypeOf; + : number extends T + ? T | string + : T; /** * Works like ObjectResultType, except that 'maybe' schema will create an optional key. @@ -368,20 +367,15 @@ type KbnConfigSchemaInputTypeOf< * * Instead of using this directly, use `InputTypeOf`. */ -type KbnConfigSchemaInputObjectTypeOf< - T extends kbnConfigSchemaTypes.ObjectType -> = T extends kbnConfigSchemaTypes.ObjectType - ? { - /** Use ? to make the field optional if the prop accepts undefined. - * This allows us to avoid writing `field: undefined` for optional fields. - */ - [K in Exclude< - keyof P, - keyof KbnConfigSchemaNonOptionalProps

- >]?: KbnConfigSchemaInputTypeOf; - } & - { [K in keyof KbnConfigSchemaNonOptionalProps

]: KbnConfigSchemaInputTypeOf } - : never; +type KbnConfigSchemaInputObjectTypeOf

> = { + /** Use ? to make the field optional if the prop accepts undefined. + * This allows us to avoid writing `field: undefined` for optional fields. + */ + [K in Exclude>]?: KbnConfigSchemaInputTypeOf< + P[K] + >; +} & + { [K in keyof KbnConfigSchemaNonOptionalProps

]: KbnConfigSchemaInputTypeOf }; /** * Takes the props of a schema.object type, and returns a version that excludes @@ -389,10 +383,14 @@ type KbnConfigSchemaInputObjectTypeOf< * * Instead of using this directly, use `InputTypeOf`. */ -type KbnConfigSchemaNonOptionalProps = Pick< +type KbnConfigSchemaNonOptionalProps> = Pick< Props, { - [Key in keyof Props]: undefined extends TypeOf ? never : Key; + [Key in keyof Props]: undefined extends Props[Key] + ? never + : null extends Props[Key] + ? never + : Key; }[keyof Props] >; @@ -400,7 +398,7 @@ type KbnConfigSchemaNonOptionalProps = * Query params to pass to the alert API when fetching new data. */ export type AlertingIndexGetQueryInput = KbnConfigSchemaInputTypeOf< - typeof alertingIndexGetQuerySchema + TypeOf >; /** From 80e6ff729a059d29b70ac3cf673a7614ddb00dde Mon Sep 17 00:00:00 2001 From: Rudolf Meijering Date: Thu, 12 Mar 2020 20:39:29 +0100 Subject: [PATCH 15/19] Make context.core required argument to context providers (#59996) * Make context.core required argument to context providers * Refactor plugins: context.core isn't optional anymore * Update docs --- .../kibana-plugin-core-public.icontextprovider.md | 2 +- .../kibana-plugin-core-server.icontextprovider.md | 2 +- src/core/public/public.api.md | 4 +++- src/core/server/server.api.md | 4 +++- src/core/utils/context.ts | 10 ++++++++-- src/plugins/data/server/search/search_service.ts | 2 +- .../plugins/core_plugin_a/server/plugin.ts | 2 +- x-pack/plugins/actions/server/plugin.ts | 2 +- x-pack/plugins/alerting/server/plugin.ts | 2 +- .../server/licensing_route_handler_context.test.ts | 4 ++-- 10 files changed, 22 insertions(+), 12 deletions(-) diff --git a/docs/development/core/public/kibana-plugin-core-public.icontextprovider.md b/docs/development/core/public/kibana-plugin-core-public.icontextprovider.md index 4778415ab2391c..97f7bad8e99115 100644 --- a/docs/development/core/public/kibana-plugin-core-public.icontextprovider.md +++ b/docs/development/core/public/kibana-plugin-core-public.icontextprovider.md @@ -9,7 +9,7 @@ A function that returns a context value for a specific key of given context type Signature: ```typescript -export declare type IContextProvider, TContextName extends keyof HandlerContextType> = (context: Partial>, ...rest: HandlerParameters) => Promise[TContextName]> | HandlerContextType[TContextName]; +export declare type IContextProvider, TContextName extends keyof HandlerContextType> = (context: PartialExceptFor, 'core'>, ...rest: HandlerParameters) => Promise[TContextName]> | HandlerContextType[TContextName]; ``` ## Remarks diff --git a/docs/development/core/server/kibana-plugin-core-server.icontextprovider.md b/docs/development/core/server/kibana-plugin-core-server.icontextprovider.md index b2194c9ac0504d..7d124b266bcc1a 100644 --- a/docs/development/core/server/kibana-plugin-core-server.icontextprovider.md +++ b/docs/development/core/server/kibana-plugin-core-server.icontextprovider.md @@ -9,7 +9,7 @@ A function that returns a context value for a specific key of given context type Signature: ```typescript -export declare type IContextProvider, TContextName extends keyof HandlerContextType> = (context: Partial>, ...rest: HandlerParameters) => Promise[TContextName]> | HandlerContextType[TContextName]; +export declare type IContextProvider, TContextName extends keyof HandlerContextType> = (context: PartialExceptFor, 'core'>, ...rest: HandlerParameters) => Promise[TContextName]> | HandlerContextType[TContextName]; ``` ## Remarks diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index f71a50e2927d88..69668176a397e7 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -735,8 +735,10 @@ export interface IContextContainer> { registerContext>(pluginOpaqueId: PluginOpaqueId, contextName: TContextName, provider: IContextProvider): this; } +// Warning: (ae-forgotten-export) The symbol "PartialExceptFor" needs to be exported by the entry point index.d.ts +// // @public -export type IContextProvider, TContextName extends keyof HandlerContextType> = (context: Partial>, ...rest: HandlerParameters) => Promise[TContextName]> | HandlerContextType[TContextName]; +export type IContextProvider, TContextName extends keyof HandlerContextType> = (context: PartialExceptFor, 'core'>, ...rest: HandlerParameters) => Promise[TContextName]> | HandlerContextType[TContextName]; // @public (undocumented) export interface IHttpFetchError extends Error { diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 5ede98a1e6e6da..86e99e0dab5509 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -882,8 +882,10 @@ export interface IContextContainer> { registerContext>(pluginOpaqueId: PluginOpaqueId, contextName: TContextName, provider: IContextProvider): this; } +// Warning: (ae-forgotten-export) The symbol "PartialExceptFor" needs to be exported by the entry point index.d.ts +// // @public -export type IContextProvider, TContextName extends keyof HandlerContextType> = (context: Partial>, ...rest: HandlerParameters) => Promise[TContextName]> | HandlerContextType[TContextName]; +export type IContextProvider, TContextName extends keyof HandlerContextType> = (context: PartialExceptFor, 'core'>, ...rest: HandlerParameters) => Promise[TContextName]> | HandlerContextType[TContextName]; // @public export interface ICspConfig { diff --git a/src/core/utils/context.ts b/src/core/utils/context.ts index 775c8906754100..de311f91d56fa4 100644 --- a/src/core/utils/context.ts +++ b/src/core/utils/context.ts @@ -22,6 +22,11 @@ import { ShallowPromise } from '@kbn/utility-types'; import { pick } from '.'; import { CoreId, PluginOpaqueId } from '../server'; +/** + * Make all properties in T optional, except for the properties whose keys are in the union K + */ +type PartialExceptFor = Partial & Pick; + /** * A function that returns a context value for a specific key of given context type. * @@ -39,7 +44,8 @@ export type IContextProvider< THandler extends HandlerFunction, TContextName extends keyof HandlerContextType > = ( - context: Partial>, + // context.core will always be available, but plugin contexts are typed as optional + context: PartialExceptFor, 'core'>, ...rest: HandlerParameters ) => | Promise[TContextName]> @@ -261,7 +267,7 @@ export class ContextContainer> // registered that provider. const exposedContext = pick(resolvedContext, [ ...this.getContextNamesForSource(providerSource), - ]) as Partial>; + ]) as PartialExceptFor, 'core'>; return { ...resolvedContext, diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index 09bb150594177e..46f90e3c6fc627 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -55,7 +55,7 @@ export class SearchService implements Plugin { core.http.registerRouteHandlerContext<'search'>('search', context => { return createApi({ - caller: context.core!.elasticsearch.dataClient.callAsCurrentUser, + caller: context.core.elasticsearch.dataClient.callAsCurrentUser, searchStrategies: this.searchStrategies, }); }); diff --git a/test/plugin_functional/plugins/core_plugin_a/server/plugin.ts b/test/plugin_functional/plugins/core_plugin_a/server/plugin.ts index e057e63c03f4a5..6535a54f6b7443 100644 --- a/test/plugin_functional/plugins/core_plugin_a/server/plugin.ts +++ b/test/plugin_functional/plugins/core_plugin_a/server/plugin.ts @@ -34,7 +34,7 @@ export class CorePluginAPlugin implements Plugin { core.http.registerRouteHandlerContext('pluginA', context => { return { ping: () => - context.core!.elasticsearch.adminClient.callAsInternalUser('ping') as Promise, + context.core.elasticsearch.adminClient.callAsInternalUser('ping') as Promise, }; }); } diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index 5ea6320fbe54a9..10826ce7957572 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -282,7 +282,7 @@ export class ActionsPlugin implements Plugin, Plugi ); } return new ActionsClient({ - savedObjectsClient: context.core!.savedObjects.client, + savedObjectsClient: context.core.savedObjects.client, actionTypeRegistry: actionTypeRegistry!, defaultKibanaIndex, scopedClusterClient: adminClient!.asScoped(request), diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index 4d91c9b24add9c..885391325fcd66 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -244,7 +244,7 @@ export class AlertingPlugin { return async function alertsRouteHandlerContext(context, request) { return { getAlertsClient: () => { - return alertsClientFactory!.create(request, context.core!.savedObjects.client); + return alertsClientFactory!.create(request, context.core.savedObjects.client); }, listTypes: alertTypeRegistry!.list.bind(alertTypeRegistry!), }; diff --git a/x-pack/plugins/licensing/server/licensing_route_handler_context.test.ts b/x-pack/plugins/licensing/server/licensing_route_handler_context.test.ts index 91fb1672f020d4..29bff402939581 100644 --- a/x-pack/plugins/licensing/server/licensing_route_handler_context.test.ts +++ b/x-pack/plugins/licensing/server/licensing_route_handler_context.test.ts @@ -17,9 +17,9 @@ describe('createRouteHandlerContext', () => { const routeHandler = createRouteHandlerContext(license$); - const firstCtx = await routeHandler({}, {} as any, {} as any); + const firstCtx = await routeHandler({} as any, {} as any, {} as any); license$.next(secondLicense); - const secondCtx = await routeHandler({}, {} as any, {} as any); + const secondCtx = await routeHandler({} as any, {} as any, {} as any); expect(firstCtx.license).toBe(firstLicense); expect(secondCtx.license).toBe(secondLicense); From 99c35f65a987148e3e974b36234615b91456ce12 Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Thu, 12 Mar 2020 15:02:56 -0500 Subject: [PATCH 16/19] Convert Timeline to TypeScript (#59966) Will need to be touching some of these soon as part of elastic/observability-dev#532 so I'm converting to TypeScript. Get rid of default exports on all of them as well. --- .../Waterfall/WaterfallItem.tsx | 12 +--- .../WaterfallContainer/Waterfall/index.tsx | 2 +- .../{LastTickValue.js => LastTickValue.tsx} | 7 +- .../Timeline.test.js => Timeline.test.tsx} | 7 +- .../{TimelineAxis.js => TimelineAxis.tsx} | 45 ++++++------ .../{VerticalLines.js => VerticalLines.tsx} | 30 ++++---- .../Timeline.test.tsx.snap} | 2 +- .../shared/charts/Timeline/index.js | 49 ------------- .../shared/charts/Timeline/index.tsx | 70 +++++++++++++++++++ .../Timeline/{plotUtils.js => plotUtils.ts} | 15 +++- 10 files changed, 135 insertions(+), 104 deletions(-) rename x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/{LastTickValue.js => LastTickValue.tsx} (75%) rename x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/{__test__/Timeline.test.js => Timeline.test.tsx} (94%) rename x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/{TimelineAxis.js => TimelineAxis.tsx} (81%) rename x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/{VerticalLines.js => VerticalLines.tsx} (73%) rename x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/{__test__/__snapshots__/Timeline.test.js.snap => __snapshots__/Timeline.test.tsx.snap} (99%) delete mode 100644 x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/index.js create mode 100644 x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/index.tsx rename x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/{plotUtils.js => plotUtils.ts} (69%) diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/WaterfallItem.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/WaterfallItem.tsx index b7b3e8d82ce61b..5c6e0cc5ce4350 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/WaterfallItem.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/WaterfallItem.tsx @@ -18,12 +18,13 @@ import { IWaterfallItem } from './waterfall_helpers/waterfall_helpers'; import { ErrorOverviewLink } from '../../../../../shared/Links/apm/ErrorOverviewLink'; import { TRACE_ID } from '../../../../../../../../../../plugins/apm/common/elasticsearch_fieldnames'; import { SyncBadge } from './SyncBadge'; +import { Margins } from '../../../../../shared/charts/Timeline'; type ItemType = 'transaction' | 'span' | 'error'; interface IContainerStyleProps { type: ItemType; - timelineMargins: ITimelineMargins; + timelineMargins: Margins; isSelected: boolean; } @@ -72,15 +73,8 @@ const ItemText = styled.span` } `; -interface ITimelineMargins { - right: number; - left: number; - top: number; - bottom: number; -} - interface IWaterfallItemProps { - timelineMargins: ITimelineMargins; + timelineMargins: Margins; totalDuration?: number; item: IWaterfallItem; color: string; diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/index.tsx index 4f584786f2f9a5..8bb62bf3b4305c 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/index.tsx @@ -14,7 +14,7 @@ import styled from 'styled-components'; import { px } from '../../../../../../style/variables'; import { history } from '../../../../../../utils/history'; // @ts-ignore -import Timeline from '../../../../../shared/charts/Timeline'; +import { Timeline } from '../../../../../shared/charts/Timeline'; import { fromQuery, toQuery } from '../../../../../shared/Links/url_helpers'; import { getAgentMarks } from '../Marks/get_agent_marks'; import { getErrorMarks } from '../Marks/get_error_marks'; diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/LastTickValue.js b/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/LastTickValue.tsx similarity index 75% rename from x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/LastTickValue.js rename to x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/LastTickValue.tsx index 8a8a745eb2347a..6780c79e5f35d4 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/LastTickValue.js +++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/LastTickValue.tsx @@ -6,7 +6,12 @@ import React from 'react'; -export default function LastTickValue({ x, marginTop, value }) { +interface LastTickValueProps { + x: number; + marginTop: number; + value: string; +} +export function LastTickValue({ x, marginTop, value }: LastTickValueProps) { return ( diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/__test__/Timeline.test.js b/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/Timeline.test.tsx similarity index 94% rename from x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/__test__/Timeline.test.js rename to x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/Timeline.test.tsx index 5428dd8317f641..af98a22dd0bac3 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/__test__/Timeline.test.js +++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/Timeline.test.tsx @@ -4,12 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; import { mount } from 'enzyme'; +import React from 'react'; import { StickyContainer } from 'react-sticky'; - -import Timeline from '../index'; -import { mockMoment, toJson } from '../../../../../utils/testHelpers'; +import { mockMoment, toJson } from '../../../../utils/testHelpers'; +import { Timeline } from '.'; describe('Timeline', () => { beforeAll(() => { diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/TimelineAxis.js b/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/TimelineAxis.tsx similarity index 81% rename from x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/TimelineAxis.js rename to x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/TimelineAxis.tsx index 40df82b4cf1e08..dda4c6855ac4c0 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/TimelineAxis.js +++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/TimelineAxis.tsx @@ -4,19 +4,23 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; -import PropTypes from 'prop-types'; +import theme from '@elastic/eui/dist/eui_theme_light.json'; import { inRange } from 'lodash'; +import React, { ReactNode } from 'react'; import { Sticky } from 'react-sticky'; -import { XYPlot, XAxis } from 'react-vis'; -import LastTickValue from './LastTickValue'; -import { Marker } from './Marker'; +import { XAxis, XYPlot } from 'react-vis'; import { px } from '../../../../style/variables'; import { getDurationFormatter } from '../../../../utils/formatters'; -import theme from '@elastic/eui/dist/eui_theme_light.json'; +import { Mark } from './'; +import { LastTickValue } from './LastTickValue'; +import { Marker } from './Marker'; +import { PlotValues } from './plotUtils'; // Remove any tick that is too close to topTraceDuration -const getXAxisTickValues = (tickValues, topTraceDuration) => { +const getXAxisTickValues = ( + tickValues: number[], + topTraceDuration?: number +) => { if (topTraceDuration == null) { return tickValues; } @@ -31,7 +35,18 @@ const getXAxisTickValues = (tickValues, topTraceDuration) => { }); }; -function TimelineAxis({ plotValues, marks, topTraceDuration }) { +interface TimelineAxisProps { + header?: ReactNode; + plotValues: PlotValues; + marks?: Mark[]; + topTraceDuration: number; +} + +export function TimelineAxis({ + plotValues, + marks = [], + topTraceDuration +}: TimelineAxisProps) { const { margins, tickValues, width, xDomain, xMax, xScale } = plotValues; const tickFormatter = getDurationFormatter(xMax); const xAxisTickValues = getXAxisTickValues(tickValues, topTraceDuration); @@ -67,7 +82,7 @@ function TimelineAxis({ plotValues, marks, topTraceDuration }) { orientation="top" tickSize={0} tickValues={xAxisTickValues} - tickFormat={time => tickFormatter(time).formatted} + tickFormat={(time?: number) => tickFormatter(time).formatted} tickPadding={20} style={{ text: { fill: theme.euiColorDarkShade } @@ -92,15 +107,3 @@ function TimelineAxis({ plotValues, marks, topTraceDuration }) { ); } - -TimelineAxis.propTypes = { - header: PropTypes.node, - plotValues: PropTypes.object.isRequired, - marks: PropTypes.array -}; - -TimelineAxis.defaultProps = { - marks: [] -}; - -export default TimelineAxis; diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/VerticalLines.js b/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/VerticalLines.tsx similarity index 73% rename from x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/VerticalLines.js rename to x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/VerticalLines.tsx index baf3a26cb6cbf5..5618aee8278d07 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/VerticalLines.js +++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/VerticalLines.tsx @@ -4,14 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; -import { XYPlot, VerticalGridLines } from 'react-vis'; import theme from '@elastic/eui/dist/eui_theme_light.json'; +import React, { PureComponent } from 'react'; +import { VerticalGridLines, XYPlot } from 'react-vis'; +import { Mark } from '../../../app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks'; +import { PlotValues } from './plotUtils'; + +interface VerticalLinesProps { + marks?: Mark[]; + plotValues: PlotValues; + topTraceDuration: number; +} -class VerticalLines extends PureComponent { +export class VerticalLines extends PureComponent { render() { - const { topTraceDuration, marks } = this.props; + const { topTraceDuration, marks = [] } = this.props; const { width, height, @@ -52,7 +59,7 @@ class VerticalLines extends PureComponent { {topTraceDuration > 0 && ( )} @@ -60,14 +67,3 @@ class VerticalLines extends PureComponent { ); } } - -VerticalLines.propTypes = { - plotValues: PropTypes.object.isRequired, - marks: PropTypes.array -}; - -VerticalLines.defaultProps = { - marks: [] -}; - -export default VerticalLines; diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/__test__/__snapshots__/Timeline.test.js.snap b/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/__snapshots__/Timeline.test.tsx.snap similarity index 99% rename from x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/__test__/__snapshots__/Timeline.test.js.snap rename to x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/__snapshots__/Timeline.test.tsx.snap index 0cc51dda4a9b50..63b8dc54a55b96 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/__test__/__snapshots__/Timeline.test.js.snap +++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/__snapshots__/Timeline.test.tsx.snap @@ -906,7 +906,7 @@ exports[`Timeline should render with data 1`] = ` className="rv-xy-plot__grid-lines__line" style={ Object { - "stroke": undefined, + "stroke": "#98a2b3", } } x1={900} diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/index.js b/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/index.js deleted file mode 100644 index 327049a61a1ac6..00000000000000 --- a/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/index.js +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; - -import { makeWidthFlexible } from 'react-vis'; -import { getPlotValues } from './plotUtils'; -import TimelineAxis from './TimelineAxis'; -import VerticalLines from './VerticalLines'; - -class Timeline extends PureComponent { - render() { - const { width, duration, marks, height, margins } = this.props; - if (duration == null || !width) { - return null; - } - const plotValues = getPlotValues({ width, duration, height, margins }); - - return ( -

- - -
- ); - } -} - -Timeline.propTypes = { - marks: PropTypes.array, - duration: PropTypes.number, - height: PropTypes.number.isRequired, - header: PropTypes.node, - margins: PropTypes.object.isRequired, - width: PropTypes.number -}; - -export default makeWidthFlexible(Timeline); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/index.tsx new file mode 100644 index 00000000000000..491dd97d0f62da --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/index.tsx @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import PropTypes from 'prop-types'; +import React, { PureComponent, ReactNode } from 'react'; +import { makeWidthFlexible } from 'react-vis'; +import { getPlotValues } from './plotUtils'; +import { TimelineAxis } from './TimelineAxis'; +import { VerticalLines } from './VerticalLines'; +import { ErrorMark } from '../../../app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks/get_error_marks'; +import { AgentMark } from '../../../app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks/get_agent_marks'; + +export type Mark = AgentMark | ErrorMark; + +export interface Margins { + top: number; + right: number; + bottom: number; + left: number; +} + +interface TimelineProps { + marks?: Mark[]; + duration?: number; + height: number; + header?: ReactNode; + margins: Margins; + width?: number; +} + +class TL extends PureComponent { + // We normally do not define propTypes for TypeScript components, but the + // `makeWidthFlexible` HOC from react-vis depends on them. + static propTypes = { + marks: PropTypes.array, + duration: PropTypes.number, + height: PropTypes.number.isRequired, + header: PropTypes.node, + margins: PropTypes.object.isRequired, + width: PropTypes.number + }; + + render() { + const { width, duration, marks, height, margins } = this.props; + if (duration == null || !width) { + return null; + } + const plotValues = getPlotValues({ width, duration, height, margins }); + + return ( +
+ + +
+ ); + } +} + +export const Timeline = makeWidthFlexible(TL); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/plotUtils.js b/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/plotUtils.ts similarity index 69% rename from x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/plotUtils.js rename to x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/plotUtils.ts index e3004edd7b3d9d..53fbd483b548c7 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/plotUtils.js +++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/plotUtils.ts @@ -5,8 +5,21 @@ */ import { scaleLinear } from 'd3-scale'; +import { Margins } from './'; -export function getPlotValues({ width, duration, height, margins }) { +export type PlotValues = ReturnType; + +export function getPlotValues({ + width, + duration, + height, + margins +}: { + width: number; + duration: number; + height: number; + margins: Margins; +}) { const xMin = 0; const xMax = duration; const xScale = scaleLinear() From 410019ab5abf238673f799ad264a83db853b94d9 Mon Sep 17 00:00:00 2001 From: spalger Date: Thu, 12 Mar 2020 13:17:23 -0700 Subject: [PATCH 17/19] skip flaky suite (#59541) --- .../ml/server/models/data_recognizer/data_recognizer.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 fd1023251cc811..9a1e30121ae4da 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 @@ -8,7 +8,8 @@ import { APICaller, SavedObjectsClientContract } from 'kibana/server'; import { Module } from '../../../../../legacy/plugins/ml/common/types/modules'; import { DataRecognizer } from '../data_recognizer'; -describe('ML - data recognizer', () => { +// FLAKY: https://github.com/elastic/kibana/issues/59541 +describe.skip('ML - data recognizer', () => { const dr = new DataRecognizer( jest.fn() as APICaller, ({ From d46f84814c64b71abfea84f95870a6df73e5a47b Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Thu, 12 Mar 2020 13:35:40 -0700 Subject: [PATCH 18/19] Aggregate queue types being used by Beats (#59850) * Aggregate queue types being used by Beats * Making tests pass again * Updating fixture + tests for queue type * Adding queue field to BeatsBaseStats type * Add undefined guard * Adding queue field to source fields list * Fixing type error --- .../__mocks__/fixtures/beats_stats_results.json | 9 +++++++++ .../telemetry_collection/get_beats_stats.test.ts | 12 ++++++++++++ .../telemetry_collection/get_beats_stats.ts | 16 ++++++++++++++++ 3 files changed, 37 insertions(+) diff --git a/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__mocks__/fixtures/beats_stats_results.json b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__mocks__/fixtures/beats_stats_results.json index 584618057256a1..c9f0cf0a5e6ad2 100644 --- a/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__mocks__/fixtures/beats_stats_results.json +++ b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__mocks__/fixtures/beats_stats_results.json @@ -12,6 +12,9 @@ "functions": { "count": 1 } + }, + "queue": { + "name": "mem" } } } @@ -27,6 +30,9 @@ "functions": { "count": 3 } + }, + "queue": { + "name": "mem" } } } @@ -53,6 +59,9 @@ "endpoints" : 1 }, "monitors" : 2 + }, + "queue": { + "name": "spool" } } } diff --git a/x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_beats_stats.test.ts b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_beats_stats.test.ts index 30888e1af3f533..310c01571c71db 100644 --- a/x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_beats_stats.test.ts +++ b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_beats_stats.test.ts @@ -110,6 +110,10 @@ describe('Get Beats Stats', () => { count: 0, names: [], }, + queue: { + mem: 0, + spool: 0, + }, architecture: { count: 0, architectures: [], @@ -142,6 +146,10 @@ describe('Get Beats Stats', () => { count: 1, names: ['firehose'], }, + queue: { + mem: 2, + spool: 1, + }, architecture: { count: 1, architectures: [ @@ -198,6 +206,10 @@ describe('Get Beats Stats', () => { count: 0, names: [], }, + queue: { + mem: 0, + spool: 0, + }, architecture: { count: 0, architectures: [], diff --git a/x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_beats_stats.ts b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_beats_stats.ts index 975a3bfee6333c..e817c402e22cca 100644 --- a/x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_beats_stats.ts +++ b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_beats_stats.ts @@ -29,6 +29,10 @@ const getBaseStats = () => ({ count: 0, names: [], }, + queue: { + mem: 0, + spool: 0, + }, architecture: { count: 0, architectures: [], @@ -69,6 +73,9 @@ export interface BeatsStats { names: string[]; count: number; }; + queue?: { + name?: string; + }; heartbeat?: HeartbeatBase; functionbeat?: { functions?: { @@ -107,6 +114,9 @@ export interface BeatsBaseStats { count: number; names: string[]; }; + queue: { + [queueType: string]: number; + }; architecture: { count: number; architectures: BeatsArchitecture[]; @@ -215,6 +225,11 @@ export function processResults( clusters[clusterUuid].module.count += stateModule.count; } + const stateQueue = hit._source.beats_state?.state?.queue?.name; + if (stateQueue !== undefined) { + clusters[clusterUuid].queue[stateQueue] += 1; + } + const heartbeatState = hit._source.beats_state?.state?.heartbeat; if (heartbeatState !== undefined) { if (!clusters[clusterUuid].hasOwnProperty('heartbeat')) { @@ -325,6 +340,7 @@ async function fetchBeatsByType( 'hits.hits._source.beats_stats.metrics.libbeat.output.type', 'hits.hits._source.beats_state.state.input', 'hits.hits._source.beats_state.state.module', + 'hits.hits._source.beats_state.state.queue', 'hits.hits._source.beats_state.state.host', 'hits.hits._source.beats_state.state.heartbeat', 'hits.hits._source.beats_state.beat.type', From cf5c801c7eb3005ae03dcc4e49ae2e44d8c8ac0c Mon Sep 17 00:00:00 2001 From: Pedro Jaramillo Date: Thu, 12 Mar 2020 22:45:44 +0200 Subject: [PATCH 19/19] [Endpoint] ERT-82 Alerts search bar (#59702) * Add SearchBar UI. Query, filter, and dateRange work * Sync url with search bar, fix excluded filters on BE * fix welcome title * Use KibanaReactContext, fix indexPattern fetch * Mock data plugin, api and ui tests pass * Add view and functional tests * use observables to handle filter updates * address comments --- .../endpoint/common/schema/alert_index.ts | 7 +- x-pack/plugins/endpoint/kibana.json | 2 +- .../public/applications/endpoint/index.tsx | 19 +++-- .../public/applications/endpoint/mocks.ts | 58 +++++++++++++ .../endpoint/store/alerts/action.ts | 11 ++- .../store/alerts/alert_details.test.ts | 12 ++- .../endpoint/store/alerts/alert_list.test.ts | 28 +++++- .../alerts/alert_list_pagination.test.ts | 5 +- .../endpoint/store/alerts/middleware.ts | 20 ++++- .../endpoint/store/alerts/reducer.ts | 11 +++ .../endpoint/store/alerts/selectors.ts | 73 +++++++++++++++- .../applications/endpoint/store/index.ts | 57 +++++++------ .../store/managing/middleware.test.ts | 6 +- .../endpoint/store/policy_list/index.test.ts | 5 +- .../public/applications/endpoint/types.ts | 15 +++- .../endpoint/view/alerts/index.test.tsx | 72 +++++++++++++++- .../endpoint/view/alerts/index.tsx | 2 + .../endpoint/view/alerts/index_search_bar.tsx | 85 +++++++++++++++++++ .../endpoint/view/managing/index.test.tsx | 3 +- x-pack/plugins/endpoint/public/plugin.ts | 17 ++-- .../routes/alerts/details/lib/pagination.ts | 3 + .../server/routes/alerts/lib/index.ts | 64 ++++++-------- .../server/routes/alerts/list/lib/index.ts | 7 +- .../routes/alerts/list/lib/pagination.ts | 2 +- .../endpoint/server/routes/alerts/types.ts | 6 +- .../api_integration/apis/endpoint/alerts.ts | 7 +- .../functional/apps/endpoint/alert_list.ts | 14 ++- .../page_objects/endpoint_alerts_page.ts | 20 +++++ x-pack/test/functional/page_objects/index.ts | 2 + 29 files changed, 526 insertions(+), 107 deletions(-) create mode 100644 x-pack/plugins/endpoint/public/applications/endpoint/mocks.ts create mode 100644 x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index_search_bar.tsx create mode 100644 x-pack/test/functional/page_objects/endpoint_alerts_page.ts diff --git a/x-pack/plugins/endpoint/common/schema/alert_index.ts b/x-pack/plugins/endpoint/common/schema/alert_index.ts index e8e2e1af102d63..ca27bb646d625a 100644 --- a/x-pack/plugins/endpoint/common/schema/alert_index.ts +++ b/x-pack/plugins/endpoint/common/schema/alert_index.ts @@ -7,7 +7,6 @@ import { schema, Type } from '@kbn/config-schema'; import { i18n } from '@kbn/i18n'; import { decode } from 'rison-node'; -import { fromKueryExpression } from '../../../../../src/plugins/data/common'; import { EndpointAppConstants } from '../types'; /** @@ -44,10 +43,10 @@ export const alertingIndexGetQuerySchema = schema.object( schema.string({ validate(value) { try { - fromKueryExpression(value); + decode(value); } catch (err) { - return i18n.translate('xpack.endpoint.alerts.errors.bad_kql', { - defaultMessage: 'must be valid KQL', + return i18n.translate('xpack.endpoint.alerts.errors.bad_rison', { + defaultMessage: 'must be a valid rison-encoded string', }); } }, diff --git a/x-pack/plugins/endpoint/kibana.json b/x-pack/plugins/endpoint/kibana.json index f7a4acd6293243..5b8bec7777406b 100644 --- a/x-pack/plugins/endpoint/kibana.json +++ b/x-pack/plugins/endpoint/kibana.json @@ -3,7 +3,7 @@ "version": "1.0.0", "kibanaVersion": "kibana", "configPath": ["xpack", "endpoint"], - "requiredPlugins": ["features", "embeddable"], + "requiredPlugins": ["features", "embeddable", "data", "dataEnhanced"], "server": true, "ui": true } diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/index.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/index.tsx index a126cb36a86fe8..308487c601a697 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/index.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/index.tsx @@ -13,6 +13,7 @@ import { Provider } from 'react-redux'; import { Store } from 'redux'; import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; import { RouteCapture } from './view/route_capture'; +import { EndpointPluginStartDependencies } from '../../plugin'; import { appStoreFactory } from './store'; import { AlertIndex } from './view/alerts'; import { ManagementList } from './view/managing'; @@ -22,10 +23,17 @@ import { HeaderNavigation } from './components/header_nav'; /** * This module will be loaded asynchronously to reduce the bundle size of your plugin's main bundle. */ -export function renderApp(coreStart: CoreStart, { appBasePath, element }: AppMountParameters) { +export function renderApp( + coreStart: CoreStart, + depsStart: EndpointPluginStartDependencies, + { appBasePath, element }: AppMountParameters +) { coreStart.http.get('/api/endpoint/hello-world'); - const store = appStoreFactory(coreStart); - ReactDOM.render(, element); + const store = appStoreFactory({ coreStart, depsStart }); + ReactDOM.render( + , + element + ); return () => { ReactDOM.unmountComponentAtNode(element); }; @@ -35,13 +43,14 @@ interface RouterProps { basename: string; store: Store; coreStart: CoreStart; + depsStart: EndpointPluginStartDependencies; } const AppRoot: React.FunctionComponent = React.memo( - ({ basename, store, coreStart: { http, notifications } }) => ( + ({ basename, store, coreStart: { http, notifications }, depsStart: { data } }) => ( - + diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/mocks.ts b/x-pack/plugins/endpoint/public/applications/endpoint/mocks.ts new file mode 100644 index 00000000000000..e1a90b4a416dc2 --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/mocks.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + dataPluginMock, + Start as DataPublicStartMock, +} from '../../../../../../src/plugins/data/public/mocks'; + +type DataMock = Omit & { + indexPatterns: Omit & { + getFieldsForWildcard: jest.Mock; + }; + // We can't Omit (override) 'query' here because FilterManager is a class not an interface. + // Because of this, wherever FilterManager is used tsc expects some FilterManager private fields + // like filters, updated$, fetch$ to be part of the type. Omit removes these private fields when used. + query: DataPublicStartMock['query'] & { + filterManager: { + setFilters: jest.Mock; + getUpdates$: jest.Mock; + }; + }; + ui: DataPublicStartMock['ui'] & { + SearchBar: jest.Mock; + }; +}; + +/** + * Type for our app's depsStart (plugin start dependencies) + */ +export interface DepsStartMock { + data: DataMock; +} + +/** + * Returns a mock of our app's depsStart (plugin start dependencies) + */ +export const depsStartMock: () => DepsStartMock = () => { + 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(() => { + return { + subscribe: jest.fn(() => { + return { + unsubscribe: jest.fn(), + }; + }), + }; + }) as DataMock['query']['filterManager']['getUpdates$']; + dataMock.ui.SearchBar = jest.fn(); + + return { + data: dataMock, + }; +}; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/action.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/action.ts index 6c6310a7349ed2..42c24400d12d34 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/action.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/action.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { IIndexPattern } from 'src/plugins/data/public'; import { Immutable, AlertData } from '../../../../../common/types'; import { AlertListData } from '../../types'; @@ -17,4 +18,12 @@ interface ServerReturnedAlertDetailsData { readonly payload: Immutable; } -export type AlertAction = ServerReturnedAlertsData | ServerReturnedAlertDetailsData; +interface ServerReturnedSearchBarIndexPatterns { + type: 'serverReturnedSearchBarIndexPatterns'; + payload: IIndexPattern[]; +} + +export type AlertAction = + | ServerReturnedAlertsData + | ServerReturnedAlertDetailsData + | ServerReturnedSearchBarIndexPatterns; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_details.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_details.test.ts index 4edc31831eb14a..79e9de9c673520 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_details.test.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_details.test.ts @@ -11,11 +11,14 @@ import { AlertListState } from '../../types'; import { alertMiddlewareFactory } from './middleware'; import { AppAction } from '../action'; import { coreMock } from 'src/core/public/mocks'; +import { DepsStartMock, depsStartMock } from '../../mocks'; import { createBrowserHistory } from 'history'; +import { mockAlertResultList } from './mock_alert_result_list'; describe('alert details tests', () => { let store: Store; let coreStart: ReturnType; + let depsStart: DepsStartMock; let history: History; /** * A function that waits until a selector returns true. @@ -23,8 +26,9 @@ describe('alert details tests', () => { let selectorIsTrue: (selector: (state: AlertListState) => boolean) => Promise; beforeEach(() => { coreStart = coreMock.createStart(); + depsStart = depsStartMock(); history = createBrowserHistory(); - const middleware = alertMiddlewareFactory(coreStart); + const middleware = alertMiddlewareFactory(coreStart, depsStart); store = createStore(alertListReducer, applyMiddleware(middleware)); selectorIsTrue = async selector => { @@ -42,9 +46,9 @@ describe('alert details tests', () => { }); describe('when the user is on the alert list page with a selected alert in the url', () => { beforeEach(() => { - const firstResponse: Promise = Promise.resolve(1); - const secondResponse: Promise = Promise.resolve(2); - coreStart.http.get.mockReturnValueOnce(firstResponse).mockReturnValueOnce(secondResponse); + const firstResponse: Promise = Promise.resolve(mockAlertResultList()); + coreStart.http.get.mockReturnValue(firstResponse); + depsStart.data.indexPatterns.getFieldsForWildcard.mockReturnValue(Promise.resolve([])); // Simulates user navigating to the /alerts page store.dispatch({ diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_list.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_list.test.ts index 0aeeb6881ad96e..b1cc2d46f614aa 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_list.test.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_list.test.ts @@ -11,6 +11,7 @@ import { AlertListState } from '../../types'; import { alertMiddlewareFactory } from './middleware'; import { AppAction } from '../action'; import { coreMock } from 'src/core/public/mocks'; +import { DepsStartMock, depsStartMock } from '../../mocks'; import { AlertResultList } from '../../../../../common/types'; import { isOnAlertPage } from './selectors'; import { createBrowserHistory } from 'history'; @@ -19,12 +20,31 @@ import { mockAlertResultList } from './mock_alert_result_list'; describe('alert list tests', () => { let store: Store; let coreStart: ReturnType; + let depsStart: DepsStartMock; let history: History; + /** + * A function that waits until a selector returns true. + */ + let selectorIsTrue: (selector: (state: AlertListState) => boolean) => Promise; beforeEach(() => { coreStart = coreMock.createStart(); + depsStart = depsStartMock(); history = createBrowserHistory(); - const middleware = alertMiddlewareFactory(coreStart); + const middleware = alertMiddlewareFactory(coreStart, depsStart); store = createStore(alertListReducer, applyMiddleware(middleware)); + + selectorIsTrue = async selector => { + // If the selector returns true, we're done + while (selector(store.getState()) !== true) { + // otherwise, wait til the next state change occurs + await new Promise(resolve => { + const unsubscribe = store.subscribe(() => { + unsubscribe(); + resolve(); + }); + }); + } + }; }); describe('when the user navigates to the alert list page', () => { beforeEach(() => { @@ -32,6 +52,7 @@ describe('alert list tests', () => { const response: AlertResultList = mockAlertResultList(); return response; }); + depsStart.data.indexPatterns.getFieldsForWildcard.mockReturnValue(Promise.resolve([])); // Simulates user navigating to the /alerts page store.dispatch({ @@ -48,9 +69,8 @@ describe('alert list tests', () => { expect(actual).toBe(true); }); - it('should return alertListData', () => { - const actualResponseLength = store.getState().alerts.length; - expect(actualResponseLength).toEqual(1); + it('should return alertListData', async () => { + await selectorIsTrue(state => state.alerts.length === 1); }); }); }); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_list_pagination.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_list_pagination.test.ts index 5c257c3d65fdc5..bb5893f14287b3 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_list_pagination.test.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_list_pagination.test.ts @@ -11,6 +11,7 @@ import { AlertListState, AlertingIndexUIQueryParams } from '../../types'; import { alertMiddlewareFactory } from './middleware'; import { AppAction } from '../action'; import { coreMock } from 'src/core/public/mocks'; +import { DepsStartMock, depsStartMock } from '../../mocks'; import { createBrowserHistory } from 'history'; import { uiQueryParams } from './selectors'; import { urlFromQueryParams } from '../../view/alerts/url_from_query_params'; @@ -18,6 +19,7 @@ import { urlFromQueryParams } from '../../view/alerts/url_from_query_params'; describe('alert list pagination', () => { let store: Store; let coreStart: ReturnType; + let depsStart: DepsStartMock; let history: History; let queryParams: () => AlertingIndexUIQueryParams; /** @@ -26,9 +28,10 @@ describe('alert list pagination', () => { let historyPush: (params: AlertingIndexUIQueryParams) => void; beforeEach(() => { coreStart = coreMock.createStart(); + depsStart = depsStartMock(); history = createBrowserHistory(); - const middleware = alertMiddlewareFactory(coreStart); + const middleware = alertMiddlewareFactory(coreStart, depsStart); store = createStore(alertListReducer, applyMiddleware(middleware)); history.listen(location => { diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/middleware.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/middleware.ts index 339be7a4ec7f10..b37ba0c0983d3f 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/middleware.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/middleware.ts @@ -4,22 +4,40 @@ * you may not use this file except in compliance with the Elastic License. */ +import { IIndexPattern } from 'src/plugins/data/public'; import { AlertResultList, AlertData } from '../../../../../common/types'; import { AppAction } from '../action'; import { MiddlewareFactory, AlertListState } from '../../types'; import { isOnAlertPage, apiQueryParams, hasSelectedAlert, uiQueryParams } from './selectors'; import { cloneHttpFetchQuery } from '../../../../common/clone_http_fetch_query'; +import { EndpointAppConstants } from '../../../../../common/types'; + +export const alertMiddlewareFactory: MiddlewareFactory = (coreStart, depsStart) => { + async function fetchIndexPatterns(): Promise { + const { indexPatterns } = depsStart.data; + const indexName = EndpointAppConstants.ALERT_INDEX_NAME; + const fields = await indexPatterns.getFieldsForWildcard({ pattern: indexName }); + const indexPattern: IIndexPattern = { + title: indexName, + fields, + }; + + return [indexPattern]; + } -export const alertMiddlewareFactory: MiddlewareFactory = coreStart => { return api => next => async (action: AppAction) => { next(action); const state = api.getState(); if (action.type === 'userChangedUrl' && isOnAlertPage(state)) { + const patterns = await fetchIndexPatterns(); + api.dispatch({ type: 'serverReturnedSearchBarIndexPatterns', payload: patterns }); + const response: AlertResultList = await coreStart.http.get(`/api/endpoint/alerts`, { query: cloneHttpFetchQuery(apiQueryParams(state)), }); api.dispatch({ type: 'serverReturnedAlertsData', payload: response }); } + if (action.type === 'userChangedUrl' && isOnAlertPage(state) && hasSelectedAlert(state)) { const uiParams = uiQueryParams(state); const response: AlertData = await coreStart.http.get( diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/reducer.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/reducer.ts index ee172fa80f1fed..4430a4d39cf4a3 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/reducer.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/reducer.ts @@ -16,6 +16,9 @@ const initialState = (): AlertListState => { pageIndex: 0, total: 0, location: undefined, + searchBar: { + patterns: [], + }, }; }; @@ -49,6 +52,14 @@ export const alertListReducer: Reducer = ( ...state, alertDetails: action.payload, }; + } else if (action.type === 'serverReturnedSearchBarIndexPatterns') { + return { + ...state, + searchBar: { + ...state.searchBar, + patterns: action.payload, + }, + }; } return state; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/selectors.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/selectors.ts index ca836f8b62bd20..68731bb3f307f5 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/selectors.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/selectors.ts @@ -9,6 +9,8 @@ import { createSelector, createStructuredSelector as createStructuredSelectorWithBadType, } from 'reselect'; +import { encode, decode } from 'rison-node'; +import { Query, TimeRange, Filter } from 'src/plugins/data/public'; import { AlertListState, AlertingIndexUIQueryParams, CreateStructuredSelector } from '../../types'; import { Immutable, AlertingIndexGetQueryInput } from '../../../../../common/types'; @@ -59,6 +61,9 @@ export const uiQueryParams: ( 'page_size', 'page_index', 'selected_alert', + 'query', + 'date_range', + 'filters', ]; for (const key of keys) { const value = query[key]; @@ -73,6 +78,66 @@ export const uiQueryParams: ( } ); +/** + * Parses the ui query params and returns a object that represents the query used by the SearchBar component. + * If the query url param is undefined, a default is returned. + */ +export const searchBarQuery: (state: AlertListState) => Query = createSelector( + uiQueryParams, + ({ query }) => { + if (query !== undefined) { + return (decode(query) as unknown) as Query; + } else { + return { query: '', language: 'kuery' }; + } + } +); + +/** + * Parses the ui query params and returns a rison encoded string that represents the search bar's date range. + * A default is provided if 'date_range' is not present in the url params. + */ +export const encodedSearchBarDateRange: (state: AlertListState) => string = createSelector( + uiQueryParams, + ({ date_range: dateRange }) => { + if (dateRange === undefined) { + return encode({ from: 'now-24h', to: 'now' }); + } else { + return dateRange; + } + } +); + +/** + * Parses the ui query params and returns a object that represents the dateRange used by the SearchBar component. + */ +export const searchBarDateRange: (state: AlertListState) => TimeRange = createSelector( + encodedSearchBarDateRange, + encodedDateRange => { + return (decode(encodedDateRange) as unknown) as TimeRange; + } +); + +/** + * Parses the ui query params and returns an array of filters used by the SearchBar component. + * If the 'filters' param is not present, a default is returned. + */ +export const searchBarFilters: (state: AlertListState) => Filter[] = createSelector( + uiQueryParams, + ({ filters }) => { + if (filters !== undefined) { + return (decode(filters) as unknown) as Filter[]; + } else { + return []; + } + } +); + +/** + * Returns the indexPatterns used by the SearchBar component + */ +export const searchBarIndexPatterns = (state: AlertListState) => state.searchBar.patterns; + /** * query params to use when requesting alert data. */ @@ -80,9 +145,15 @@ export const apiQueryParams: ( state: AlertListState ) => Immutable = createSelector( uiQueryParams, - ({ page_size, page_index }) => ({ + encodedSearchBarDateRange, + ({ page_size, page_index, query, filters }, encodedDateRange) => ({ page_size, page_index, + query, + // Always send a default date range param to the API + // even if there is no date_range param in the url + date_range: encodedDateRange, + filters, }) ); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/index.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/index.ts index b95ff7cb2d45ca..f275fbcbbb5e68 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/index.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/index.ts @@ -20,6 +20,7 @@ import { managementMiddlewareFactory } from './managing'; import { policyListMiddlewareFactory } from './policy_list'; import { GlobalState } from '../types'; import { AppAction } from './action'; +import { EndpointPluginStartDependencies } from '../../../plugin'; const composeWithReduxDevTools = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ name: 'EndpointApp' }) @@ -48,37 +49,43 @@ export const substateMiddlewareFactory = ( }; }; -export const appStoreFactory: ( +/** + * @param middlewareDeps Optionally create the store without any middleware. This is useful for testing the store w/o side effects. + */ +export const appStoreFactory: (middlewareDeps?: { /** * Allow middleware to communicate with Kibana core. */ - coreStart: CoreStart, + coreStart: CoreStart; /** - * Create the store without any middleware. This is useful for testing the store w/o side effects. + * Give middleware access to plugin start dependencies. */ - disableMiddleware?: boolean -) => Store = (coreStart, disableMiddleware = false) => { - const store = createStore( - appReducer, - disableMiddleware - ? undefined - : composeWithReduxDevTools( - applyMiddleware( - substateMiddlewareFactory( - globalState => globalState.managementList, - managementMiddlewareFactory(coreStart) - ), - substateMiddlewareFactory( - globalState => globalState.policyList, - policyListMiddlewareFactory(coreStart) - ), - substateMiddlewareFactory( - globalState => globalState.alertList, - alertMiddlewareFactory(coreStart) - ) - ) + depsStart: EndpointPluginStartDependencies; +}) => Store = middlewareDeps => { + let middleware; + if (middlewareDeps) { + const { coreStart, depsStart } = middlewareDeps; + middleware = composeWithReduxDevTools( + applyMiddleware( + substateMiddlewareFactory( + globalState => globalState.managementList, + managementMiddlewareFactory(coreStart, depsStart) + ), + substateMiddlewareFactory( + globalState => globalState.policyList, + policyListMiddlewareFactory(coreStart, depsStart) + ), + substateMiddlewareFactory( + globalState => globalState.alertList, + alertMiddlewareFactory(coreStart, depsStart) ) - ); + ) + ); + } else { + // Create the store without any middleware. This is useful for testing the store w/o side effects. + middleware = undefined; + } + const store = createStore(appReducer, middleware); return store; }; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/managing/middleware.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/managing/middleware.test.ts index 3b37e0d79bacc8..d98dc82624149e 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/managing/middleware.test.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/managing/middleware.test.ts @@ -13,9 +13,12 @@ import { EndpointDocGenerator } from '../../../../../common/generate_data'; import { ManagementListState } from '../../types'; import { AppAction } from '../action'; import { listData } from './selectors'; +import { DepsStartMock, depsStartMock } from '../../mocks'; + describe('endpoint list saga', () => { const sleep = (ms = 100) => new Promise(wakeup => setTimeout(wakeup, ms)); let fakeCoreStart: jest.Mocked; + let depsStart: DepsStartMock; let fakeHttpServices: jest.Mocked; let store: Store; let getState: typeof store['getState']; @@ -38,10 +41,11 @@ describe('endpoint list saga', () => { }; beforeEach(() => { fakeCoreStart = coreMock.createStart({ basePath: '/mock' }); + depsStart = depsStartMock(); fakeHttpServices = fakeCoreStart.http as jest.Mocked; store = createStore( managementListReducer, - applyMiddleware(managementMiddlewareFactory(fakeCoreStart)) + applyMiddleware(managementMiddlewareFactory(fakeCoreStart, depsStart)) ); getState = store.getState; dispatch = store.dispatch; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/index.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/index.test.ts index 48586935675d56..0cf0eb8bfa3cdb 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/index.test.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/index.test.ts @@ -12,19 +12,22 @@ import { policyListMiddlewareFactory } from './middleware'; import { coreMock } from '../../../../../../../../src/core/public/mocks'; import { CoreStart } from 'kibana/public'; import { selectIsLoading } from './selectors'; +import { DepsStartMock, depsStartMock } from '../../mocks'; describe('policy list store concerns', () => { const sleep = () => new Promise(resolve => setTimeout(resolve, 1000)); let fakeCoreStart: jest.Mocked; + let depsStart: DepsStartMock; let store: Store; let getState: typeof store['getState']; let dispatch: Dispatch; beforeEach(() => { fakeCoreStart = coreMock.createStart({ basePath: '/mock' }); + depsStart = depsStartMock(); store = createStore( policyListReducer, - applyMiddleware(policyListMiddlewareFactory(fakeCoreStart)) + applyMiddleware(policyListMiddlewareFactory(fakeCoreStart, depsStart)) ); getState = store.getState; dispatch = store.dispatch; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/types.ts b/x-pack/plugins/endpoint/public/applications/endpoint/types.ts index 4ceb4cec23d0b8..3b70a580436feb 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/types.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/types.ts @@ -5,6 +5,7 @@ */ import { Dispatch, MiddlewareAPI } from 'redux'; +import { IIndexPattern } from 'src/plugins/data/public'; import { EndpointMetadata, AlertData, @@ -12,12 +13,14 @@ import { Immutable, ImmutableArray, } from '../../../common/types'; +import { EndpointPluginStartDependencies } from '../../plugin'; import { AppAction } from './store/action'; import { CoreStart } from '../../../../../../src/core/public'; export { AppAction }; export type MiddlewareFactory = ( - coreStart: CoreStart + coreStart: CoreStart, + depsStart: EndpointPluginStartDependencies ) => ( api: MiddlewareAPI, S> ) => (next: Dispatch) => (action: AppAction) => unknown; @@ -101,6 +104,10 @@ export interface EndpointAppLocation { key?: string; } +interface AlertsSearchBarState { + patterns: IIndexPattern[]; +} + export type AlertListData = AlertResultList; export interface AlertListState { @@ -121,6 +128,9 @@ export interface AlertListState { /** Specific Alert data to be shown in the details view */ readonly alertDetails?: Immutable; + + /** Search bar state including indexPatterns */ + readonly searchBar: AlertsSearchBarState; } /** @@ -139,4 +149,7 @@ export interface AlertingIndexUIQueryParams { * If any value is present, show the alert detail view for the selected alert. Should be an ID for an alert event. */ selected_alert?: string; + query?: string; + date_range?: string; + filters?: string; } diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index.test.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index.test.tsx index 920650bbbe3295..7fc5e18a6ba88c 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index.test.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index.test.tsx @@ -9,8 +9,8 @@ import * as reactTestingLibrary from '@testing-library/react'; import { Provider } from 'react-redux'; import { I18nProvider } from '@kbn/i18n/react'; import { AlertIndex } from './index'; +import { IIndexPattern } from 'src/plugins/data/public'; import { appStoreFactory } from '../../store'; -import { coreMock } from 'src/core/public/mocks'; import { KibanaContextProvider } from '../../../../../../../../src/plugins/kibana_react/public'; import { fireEvent, act } from '@testing-library/react'; import { RouteCapture } from '../route_capture'; @@ -18,11 +18,13 @@ import { createMemoryHistory, MemoryHistory } from 'history'; import { Router } from 'react-router-dom'; import { AppAction } from '../../types'; import { mockAlertResultList } from '../../store/alerts/mock_alert_result_list'; +import { DepsStartMock, depsStartMock } from '../../mocks'; describe('when on the alerting page', () => { let render: () => reactTestingLibrary.RenderResult; let history: MemoryHistory; let store: ReturnType; + let depsStart: DepsStartMock; beforeEach(async () => { /** @@ -32,7 +34,10 @@ describe('when on the alerting page', () => { /** * Create a store, with the middleware disabled. We don't want side effects being created by our code in this test. */ - store = appStoreFactory(coreMock.createStart(), true); + store = appStoreFactory(); + + depsStart = depsStartMock(); + depsStart.data.ui.SearchBar.mockImplementation(() =>
); /** * Render the test component, use this after setting up anything in `beforeEach`. @@ -46,7 +51,7 @@ describe('when on the alerting page', () => { */ return reactTestingLibrary.render( - + @@ -164,4 +169,65 @@ describe('when on the alerting page', () => { }); }); }); + describe('when there are filtering params in the url', () => { + let indexPatterns: IIndexPattern[]; + beforeEach(() => { + /** + * Dispatch the `serverReturnedSearchBarIndexPatterns` action, which is normally dispatched by the middleware + * when the page loads. The SearchBar will not render if there are no indexPatterns in the state. + */ + indexPatterns = [ + { title: 'endpoint-events-1', fields: [{ name: 'host.hostname', type: 'string' }] }, + ]; + reactTestingLibrary.act(() => { + const action: AppAction = { + type: 'serverReturnedSearchBarIndexPatterns', + payload: indexPatterns, + }; + store.dispatch(action); + }); + + const searchBarQueryParam = + '(language%3Akuery%2Cquery%3A%27host.hostname%20%3A%20"DESKTOP-QBBSCUT"%27)'; + const searchBarDateRangeParam = '(from%3Anow-1y%2Cto%3Anow)'; + reactTestingLibrary.act(() => { + history.push({ + ...history.location, + search: `?query=${searchBarQueryParam}&date_range=${searchBarDateRangeParam}`, + }); + }); + }); + it("should render the SearchBar component with the correct 'indexPatterns' prop", async () => { + render(); + const callProps = depsStart.data.ui.SearchBar.mock.calls[0][0]; + expect(callProps.indexPatterns).toEqual(indexPatterns); + }); + it("should render the SearchBar component with the correct 'query' prop", async () => { + render(); + const callProps = depsStart.data.ui.SearchBar.mock.calls[0][0]; + const expectedProp = { query: 'host.hostname : "DESKTOP-QBBSCUT"', language: 'kuery' }; + expect(callProps.query).toEqual(expectedProp); + }); + it("should render the SearchBar component with the correct 'dateRangeFrom' prop", async () => { + render(); + const callProps = depsStart.data.ui.SearchBar.mock.calls[0][0]; + const expectedProp = 'now-1y'; + expect(callProps.dateRangeFrom).toEqual(expectedProp); + }); + it("should render the SearchBar component with the correct 'dateRangeTo' prop", async () => { + render(); + const callProps = depsStart.data.ui.SearchBar.mock.calls[0][0]; + const expectedProp = 'now'; + expect(callProps.dateRangeTo).toEqual(expectedProp); + }); + it('should render the SearchBar component with the correct display props', async () => { + render(); + const callProps = depsStart.data.ui.SearchBar.mock.calls[0][0]; + expect(callProps.showFilterBar).toBe(true); + expect(callProps.showDatePicker).toBe(true); + expect(callProps.showQueryBar).toBe(true); + expect(callProps.showQueryInput).toBe(true); + expect(callProps.showSaveQuery).toBe(false); + }); + }); }); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index.tsx index 4cda2001de3c38..9718b4e4ef8cdc 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index.tsx @@ -32,6 +32,7 @@ import * as selectors from '../../store/alerts/selectors'; import { useAlertListSelector } from './hooks/use_alerts_selector'; import { AlertDetailsOverview } from './details'; import { FormattedDate } from './formatted_date'; +import { AlertIndexSearchBar } from './index_search_bar'; export const AlertIndex = memo(() => { const history = useHistory(); @@ -242,6 +243,7 @@ export const AlertIndex = memo(() => { + { + const history = useHistory(); + const queryParams = useAlertListSelector(selectors.uiQueryParams); + const searchBarIndexPatterns = useAlertListSelector(selectors.searchBarIndexPatterns); + const searchBarQuery = useAlertListSelector(selectors.searchBarQuery); + const searchBarDateRange = useAlertListSelector(selectors.searchBarDateRange); + const searchBarFilters = useAlertListSelector(selectors.searchBarFilters); + + const kibanaContext = useKibana(); + const { + ui: { SearchBar }, + query: { filterManager }, + } = kibanaContext.services.data; + + useEffect(() => { + // Update the the filters in filterManager when the filters url value (searchBarFilters) changes + filterManager.setFilters(searchBarFilters); + + const filterSubscription = filterManager.getUpdates$().subscribe({ + next: () => { + history.push( + urlFromQueryParams({ + ...queryParams, + filters: encode((filterManager.getFilters() as unknown) as RisonValue), + }) + ); + }, + }); + return () => { + filterSubscription.unsubscribe(); + }; + }, [filterManager, history, queryParams, searchBarFilters]); + + const onQuerySubmit = useCallback( + (params: { dateRange: TimeRange; query?: Query }) => { + history.push( + urlFromQueryParams({ + ...queryParams, + query: encode((params.query as unknown) as RisonValue), + date_range: encode((params.dateRange as unknown) as RisonValue), + }) + ); + }, + [history, queryParams] + ); + + return ( +
+ {searchBarIndexPatterns.length > 0 && ( + + )} +
+ ); +}); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/managing/index.test.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/managing/index.test.tsx index 74742a0ea1ef89..ced27ae8945b57 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/managing/index.test.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/managing/index.test.tsx @@ -9,7 +9,6 @@ import * as reactTestingLibrary from '@testing-library/react'; import { Provider } from 'react-redux'; import { I18nProvider } from '@kbn/i18n/react'; import { appStoreFactory } from '../../store'; -import { coreMock } from 'src/core/public/mocks'; import { RouteCapture } from '../route_capture'; import { createMemoryHistory, MemoryHistory } from 'history'; import { Router } from 'react-router-dom'; @@ -24,7 +23,7 @@ describe('when on the managing page', () => { beforeEach(async () => { history = createMemoryHistory(); - store = appStoreFactory(coreMock.createStart(), true); + store = appStoreFactory(); render = () => { return reactTestingLibrary.render( diff --git a/x-pack/plugins/endpoint/public/plugin.ts b/x-pack/plugins/endpoint/public/plugin.ts index 0e10fe680e9f04..155d709042fe7b 100644 --- a/x-pack/plugins/endpoint/public/plugin.ts +++ b/x-pack/plugins/endpoint/public/plugin.ts @@ -6,6 +6,7 @@ import { Plugin, CoreSetup, AppMountParameters, CoreStart } from 'kibana/public'; import { IEmbeddableSetup } from 'src/plugins/embeddable/public'; +import { DataPublicPluginStart } from 'src/plugins/data/public'; import { i18n } from '@kbn/i18n'; import { ResolverEmbeddableFactory } from './embeddables/resolver'; @@ -13,9 +14,11 @@ export type EndpointPluginStart = void; export type EndpointPluginSetup = void; export interface EndpointPluginSetupDependencies { embeddable: IEmbeddableSetup; + data: DataPublicPluginStart; +} +export interface EndpointPluginStartDependencies { + data: DataPublicPluginStart; } - -export interface EndpointPluginStartDependencies {} // eslint-disable-line @typescript-eslint/no-empty-interface /** * Functionality that the endpoint plugin uses from core. @@ -24,6 +27,7 @@ export interface EndpointPluginServices extends Partial { http: CoreStart['http']; overlays: CoreStart['overlays'] | undefined; notifications: CoreStart['notifications'] | undefined; + data: DataPublicPluginStart; } export class EndpointPlugin @@ -34,16 +38,19 @@ export class EndpointPlugin EndpointPluginSetupDependencies, EndpointPluginStartDependencies > { - public setup(core: CoreSetup, plugins: EndpointPluginSetupDependencies) { + public setup( + core: CoreSetup, + plugins: EndpointPluginSetupDependencies + ) { core.application.register({ id: 'endpoint', title: i18n.translate('xpack.endpoint.pluginTitle', { defaultMessage: 'Endpoint', }), async mount(params: AppMountParameters) { - const [coreStart] = await core.getStartServices(); + const [coreStart, depsStart] = await core.getStartServices(); const { renderApp } = await import('./applications/endpoint'); - return renderApp(coreStart, params); + return renderApp(coreStart, depsStart, params); }, }); diff --git a/x-pack/plugins/endpoint/server/routes/alerts/details/lib/pagination.ts b/x-pack/plugins/endpoint/server/routes/alerts/details/lib/pagination.ts index 16328587597f24..446d61080e650b 100644 --- a/x-pack/plugins/endpoint/server/routes/alerts/details/lib/pagination.ts +++ b/x-pack/plugins/endpoint/server/routes/alerts/details/lib/pagination.ts @@ -16,6 +16,7 @@ import { searchESForAlerts, Pagination } from '../../lib'; import { AlertSearchQuery, SearchCursor, AlertDetailsRequestParams } from '../../types'; import { BASE_ALERTS_ROUTE } from '../..'; import { RequestHandlerContext } from '../../../../../../../../src/core/server'; +import { Filter } from '../../../../../../../../src/plugins/data/server'; /** * Pagination class for alert details. @@ -41,6 +42,8 @@ export class AlertDetailsPagination extends Pagination< pageSize: 1, sort: EndpointAppConstants.ALERT_LIST_DEFAULT_SORT, order: 'desc', + query: { query: '', language: 'kuery' }, + filters: [] as Filter[], }; if (direction === 'asc') { diff --git a/x-pack/plugins/endpoint/server/routes/alerts/lib/index.ts b/x-pack/plugins/endpoint/server/routes/alerts/lib/index.ts index 39067e9c27709a..d3ed7e7b953c39 100644 --- a/x-pack/plugins/endpoint/server/routes/alerts/lib/index.ts +++ b/x-pack/plugins/endpoint/server/routes/alerts/lib/index.ts @@ -6,7 +6,7 @@ import { SearchResponse } from 'elasticsearch'; import { IScopedClusterClient } from 'kibana/server'; import { JsonObject } from '../../../../../../../src/plugins/kibana_utils/public'; -import { esKuery, esQuery } from '../../../../../../../src/plugins/data/server'; +import { esQuery } from '../../../../../../../src/plugins/data/server'; import { AlertEvent, Direction, EndpointAppConstants } from '../../../../common/types'; import { AlertSearchQuery, @@ -25,53 +25,41 @@ function reverseSortDirection(order: Direction): Direction { } function buildQuery(query: AlertSearchQuery): JsonObject { - const queries: JsonObject[] = []; - - // only alerts - queries.push({ + const alertKindClause = { term: { 'event.kind': { value: 'alert', }, }, - }); - - if (query.filters !== undefined && query.filters.length > 0) { - const filtersQuery = esQuery.buildQueryFromFilters(query.filters, undefined); - queries.push((filtersQuery.filter as unknown) as JsonObject); - } - - if (query.query) { - queries.push(esKuery.toElasticsearchQuery(esKuery.fromKueryExpression(query.query))); - } - - if (query.dateRange) { - const dateRangeFilter: JsonObject = { - range: { - ['@timestamp']: { - gte: query.dateRange.from, - lte: query.dateRange.to, + }; + const dateRangeClause = query.dateRange + ? [ + { + range: { + ['@timestamp']: { + gte: query.dateRange.from, + lte: query.dateRange.to, + }, + }, }, - }, - }; - - queries.push(dateRangeFilter); - } + ] + : []; + const queryAndFiltersClauses = esQuery.buildEsQuery(undefined, query.query, query.filters); + + const fullQuery = { + ...queryAndFiltersClauses, + bool: { + ...queryAndFiltersClauses.bool, + must: [...queryAndFiltersClauses.bool.must, alertKindClause, ...dateRangeClause], + }, + }; // Optimize - if (queries.length > 1) { - return { - bool: { - must: queries, - }, - }; - } else if (queries.length === 1) { - return queries[0]; + if (fullQuery.bool.must.length > 1) { + return (fullQuery as unknown) as JsonObject; } - return { - match_all: {}, - }; + return alertKindClause; } function buildSort(query: AlertSearchQuery): AlertSort { diff --git a/x-pack/plugins/endpoint/server/routes/alerts/list/lib/index.ts b/x-pack/plugins/endpoint/server/routes/alerts/list/lib/index.ts index 1c7a157a988ae3..b076561ddbee2e 100644 --- a/x-pack/plugins/endpoint/server/routes/alerts/list/lib/index.ts +++ b/x-pack/plugins/endpoint/server/routes/alerts/list/lib/index.ts @@ -7,7 +7,7 @@ import { decode } from 'rison-node'; import { SearchResponse } from 'elasticsearch'; import { KibanaRequest } from 'kibana/server'; import { RequestHandlerContext } from 'src/core/server'; -import { Filter, TimeRange } from '../../../../../../../../src/plugins/data/server'; +import { Query, Filter, TimeRange } from '../../../../../../../../src/plugins/data/server'; import { AlertEvent, AlertData, @@ -36,7 +36,10 @@ export const getRequestData = async ( : config.alertResultListDefaultDateRange) as unknown) as TimeRange, // Filtering - query: request.query.query, + query: + request.query.query !== undefined + ? ((decode(request.query.query) as unknown) as Query) + : { query: '', language: 'kuery' }, filters: request.query.filters !== undefined ? ((decode(request.query.filters) as unknown) as Filter[]) diff --git a/x-pack/plugins/endpoint/server/routes/alerts/list/lib/pagination.ts b/x-pack/plugins/endpoint/server/routes/alerts/list/lib/pagination.ts index f1161540b95e49..7bebe3d9288c33 100644 --- a/x-pack/plugins/endpoint/server/routes/alerts/list/lib/pagination.ts +++ b/x-pack/plugins/endpoint/server/routes/alerts/list/lib/pagination.ts @@ -32,7 +32,7 @@ export class AlertListPagination extends Pagination protected getBasePaginationParams(): string { let pageParams: string = ''; if (this.state.query) { - pageParams += `query=${this.state.query}&`; + pageParams += `query=${encode((this.state.query as unknown) as RisonValue)}&`; } if (this.state.filters !== undefined && this.state.filters.length > 0) { diff --git a/x-pack/plugins/endpoint/server/routes/alerts/types.ts b/x-pack/plugins/endpoint/server/routes/alerts/types.ts index ae1f4e4cf5d551..3d099447a9bd06 100644 --- a/x-pack/plugins/endpoint/server/routes/alerts/types.ts +++ b/x-pack/plugins/endpoint/server/routes/alerts/types.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { Filter, TimeRange } from '../../../../../../src/plugins/data/server'; +import { Query, Filter, TimeRange } from '../../../../../../src/plugins/data/server'; import { JsonObject } from '../../../../../../src/plugins/kibana_utils/public'; import { Direction } from '../../../common/types'; @@ -33,8 +33,8 @@ export interface AlertSearchQuery { pageSize: number; pageIndex?: number; fromIndex?: number; - query?: string; - filters?: Filter[]; + query: Query; + filters: Filter[]; dateRange?: TimeRange; sort: string; order: Direction; diff --git a/x-pack/test/api_integration/apis/endpoint/alerts.ts b/x-pack/test/api_integration/apis/endpoint/alerts.ts index 1890b6f5d557d8..06134a093f7a51 100644 --- a/x-pack/test/api_integration/apis/endpoint/alerts.ts +++ b/x-pack/test/api_integration/apis/endpoint/alerts.ts @@ -10,11 +10,12 @@ export default function({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const supertest = getService('supertest'); + const nextPrevPrefixQuery = "query=(language:kuery,query:'')"; const nextPrevPrefixDateRange = "date_range=(from:'2018-01-10T00:00:00.000Z',to:now)"; const nextPrevPrefixSort = 'sort=@timestamp'; const nextPrevPrefixOrder = 'order=desc'; const nextPrevPrefixPageSize = 'page_size=10'; - const nextPrevPrefix = `${nextPrevPrefixDateRange}&${nextPrevPrefixSort}&${nextPrevPrefixOrder}&${nextPrevPrefixPageSize}`; + const nextPrevPrefix = `${nextPrevPrefixQuery}&${nextPrevPrefixDateRange}&${nextPrevPrefixSort}&${nextPrevPrefixOrder}&${nextPrevPrefixPageSize}`; describe('test alerts api', () => { describe('Tests for alerts API', () => { @@ -167,7 +168,9 @@ export default function({ getService }: FtrProviderContext) { it('alerts api should filter results of alert data using KQL', async () => { const { body } = await supertest - .get(`/api/endpoint/alerts?query=agent.id:c89dc040-2350-4d59-baea-9ff2e369136f`) + .get( + `/api/endpoint/alerts?query=(language%3Akuery%2Cquery%3A%27agent.id%20%3A%20"c89dc040-2350-4d59-baea-9ff2e369136f"%27)` + ) .set('kbn-xsrf', 'xxx') .expect(200); expect(body.total).to.eql(72); diff --git a/x-pack/test/functional/apps/endpoint/alert_list.ts b/x-pack/test/functional/apps/endpoint/alert_list.ts index eae7713c37a062..4a92a8152b1cec 100644 --- a/x-pack/test/functional/apps/endpoint/alert_list.ts +++ b/x-pack/test/functional/apps/endpoint/alert_list.ts @@ -3,12 +3,14 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function({ getPageObjects, getService }: FtrProviderContext) { - const pageObjects = getPageObjects(['common', 'endpoint']); + const pageObjects = getPageObjects(['common', 'endpointAlerts']); const testSubjects = getService('testSubjects'); const esArchiver = getService('esArchiver'); + const browser = getService('browser'); describe('Endpoint Alert List', function() { this.tags(['ciGroup7']); @@ -20,9 +22,19 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { it('loads the Alert List Page', async () => { await testSubjects.existOrFail('alertListPage'); }); + it('includes alerts search bar', async () => { + await testSubjects.existOrFail('alertsSearchBar'); + }); it('includes Alert list data grid', async () => { await testSubjects.existOrFail('alertListGrid'); }); + it('updates the url upon submitting a new search bar query', async () => { + await pageObjects.endpointAlerts.enterSearchBarQuery(); + await pageObjects.endpointAlerts.submitSearchBarFilter(); + const currentUrl = await browser.getCurrentUrl(); + expect(currentUrl).to.contain('query='); + expect(currentUrl).to.contain('date_range='); + }); after(async () => { await esArchiver.unload('endpoint/alerts/api_feature'); diff --git a/x-pack/test/functional/page_objects/endpoint_alerts_page.ts b/x-pack/test/functional/page_objects/endpoint_alerts_page.ts new file mode 100644 index 00000000000000..d04a2d5ac2f275 --- /dev/null +++ b/x-pack/test/functional/page_objects/endpoint_alerts_page.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FtrProviderContext } from '../ftr_provider_context'; + +export function EndpointAlertsPageProvider({ getService }: FtrProviderContext) { + const testSubjects = getService('testSubjects'); + + return { + async enterSearchBarQuery() { + return await testSubjects.setValue('alertsSearchBar', 'test query'); + }, + async submitSearchBarFilter() { + return await testSubjects.click('querySubmitButton'); + }, + }; +} diff --git a/x-pack/test/functional/page_objects/index.ts b/x-pack/test/functional/page_objects/index.ts index 1f21e12d0d8dc1..07c5719ae53c5f 100644 --- a/x-pack/test/functional/page_objects/index.ts +++ b/x-pack/test/functional/page_objects/index.ts @@ -47,6 +47,7 @@ import { InfraMetricExplorerProvider } from './infra_metric_explorer'; import { RoleMappingsPageProvider } from './role_mappings_page'; import { SpaceSelectorPageProvider } from './space_selector_page'; import { EndpointPageProvider } from './endpoint_page'; +import { EndpointAlertsPageProvider } from './endpoint_alerts_page'; // just like services, PageObjects are defined as a map of // names to Providers. Merge in Kibana's or pick specific ones @@ -81,4 +82,5 @@ export const pageObjects = { lens: LensPageProvider, roleMappings: RoleMappingsPageProvider, endpoint: EndpointPageProvider, + endpointAlerts: EndpointAlertsPageProvider, };