From a03f395b52db606051e47e0015aada58da23331b Mon Sep 17 00:00:00 2001 From: Zacqary Adam Xeper Date: Fri, 3 Jan 2020 15:45:11 -0600 Subject: [PATCH 01/17] [Logs UI] Refactor query bar state to hooks (#52656) * [Logs UI] Refactor query bar state to hooks * Update typedef * Typecheck fix * Typecheck fix * Simplify log filter state * Remove WithLogFilter HOC and simplify hook further * Rename js to ts * Fix redirect imports * Fix link-to test accuracy * Fix link-to test * Simplify destructuring signature * Stylistic fixes * Move URL state to hook * Fix log filter URL state infinite loop * Revert "Fix log filter URL state infinite loop" This reverts commit 43302b354af64f353397ef6936000e13b0996ff6. * Revert "Move URL state to hook" This reverts commit c61f5b190b75594c5a699f0b3ce69cc98a96ef88. Co-authored-by: Elastic Machine --- .../containers/logs/log_filter/index.ts | 22 +--- .../logs/log_filter/log_filter_state.ts | 101 ++++++++++++++++++ .../log_filter/with_log_filter_url_state.tsx | 46 ++++++++ .../logs/log_highlights/redux_bridges.tsx | 16 +-- .../logs/log_summary/with_summary.ts | 7 +- .../containers/logs/with_log_filter.tsx | 100 ----------------- .../public/pages/link_to/redirect_to_logs.tsx | 2 +- .../pages/link_to/redirect_to_node_logs.tsx | 2 +- .../pages/logs/stream/page_logs_content.tsx | 54 ++++------ .../pages/logs/stream/page_providers.tsx | 17 ++- .../public/pages/logs/stream/page_toolbar.tsx | 61 +++++------ .../plugins/infra/public/store/actions.ts | 1 - .../infra/public/store/local/actions.ts | 1 - .../public/store/local/log_filter/actions.ts | 15 --- .../public/store/local/log_filter/index.ts | 11 -- .../public/store/local/log_filter/reducer.ts | 43 -------- .../store/local/log_filter/selectors.ts | 32 ------ .../infra/public/store/local/reducer.ts | 4 - .../infra/public/store/local/selectors.ts | 6 -- .../plugins/infra/public/store/selectors.ts | 2 - .../plugins/infra/public/store/store.ts | 2 - x-pack/test/functional/apps/infra/link_to.ts | 6 +- 22 files changed, 223 insertions(+), 328 deletions(-) create mode 100644 x-pack/legacy/plugins/infra/public/containers/logs/log_filter/log_filter_state.ts create mode 100644 x-pack/legacy/plugins/infra/public/containers/logs/log_filter/with_log_filter_url_state.tsx delete mode 100644 x-pack/legacy/plugins/infra/public/containers/logs/with_log_filter.tsx delete mode 100644 x-pack/legacy/plugins/infra/public/store/local/log_filter/actions.ts delete mode 100644 x-pack/legacy/plugins/infra/public/store/local/log_filter/index.ts delete mode 100644 x-pack/legacy/plugins/infra/public/store/local/log_filter/reducer.ts delete mode 100644 x-pack/legacy/plugins/infra/public/store/local/log_filter/selectors.ts diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_filter/index.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_filter/index.ts index a737d19a5923d..4c4c317759bb3 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_filter/index.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_filter/index.ts @@ -4,23 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useContext } from 'react'; -import createContainer from 'constate'; -import { ReduxStateContext } from '../../../utils/redux_context'; -import { logFilterSelectors as logFilterReduxSelectors } from '../../../store/local/selectors'; - -export const useLogFilterState = () => { - const { local: state } = useContext(ReduxStateContext); - const filterQuery = logFilterReduxSelectors.selectLogFilterQueryAsJson(state); - return { filterQuery }; -}; - -export interface LogFilterStateParams { - filterQuery: string | null; -} - -export const logFilterInitialState = { - filterQuery: null, -}; - -export const LogFilterState = createContainer(useLogFilterState); +export * from './log_filter_state'; +export * from './with_log_filter_url_state'; diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_filter/log_filter_state.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_filter/log_filter_state.ts new file mode 100644 index 0000000000000..2911ee729638a --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_filter/log_filter_state.ts @@ -0,0 +1,101 @@ +/* + * 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 { useState, useMemo } from 'react'; +import createContainer from 'constate'; +import { IIndexPattern } from 'src/plugins/data/public'; +import { esKuery } from '../../../../../../../../src/plugins/data/public'; +import { convertKueryToElasticSearchQuery } from '../../../utils/kuery'; + +export interface KueryFilterQuery { + kind: 'kuery'; + expression: string; +} + +export interface SerializedFilterQuery { + query: KueryFilterQuery; + serializedQuery: string; +} + +interface LogFilterInternalStateParams { + filterQuery: SerializedFilterQuery | null; + filterQueryDraft: KueryFilterQuery | null; +} + +export const logFilterInitialState: LogFilterInternalStateParams = { + filterQuery: null, + filterQueryDraft: null, +}; + +export type LogFilterStateParams = Omit & { + filterQuery: SerializedFilterQuery['serializedQuery'] | null; + filterQueryAsKuery: SerializedFilterQuery['query'] | null; + isFilterQueryDraftValid: boolean; +}; +export interface LogFilterCallbacks { + setLogFilterQueryDraft: (expression: string) => void; + applyLogFilterQuery: (expression: string) => void; +} + +export const useLogFilterState: (props: { + indexPattern: IIndexPattern; +}) => LogFilterStateParams & LogFilterCallbacks = ({ indexPattern }) => { + const [state, setState] = useState(logFilterInitialState); + const { filterQuery, filterQueryDraft } = state; + + const setLogFilterQueryDraft = useMemo(() => { + const setDraft = (payload: KueryFilterQuery) => + setState(prevState => ({ ...prevState, filterQueryDraft: payload })); + return (expression: string) => + setDraft({ + kind: 'kuery', + expression, + }); + }, []); + const applyLogFilterQuery = useMemo(() => { + const applyQuery = (payload: SerializedFilterQuery) => + setState(prevState => ({ + ...prevState, + filterQueryDraft: payload.query, + filterQuery: payload, + })); + return (expression: string) => + applyQuery({ + query: { + kind: 'kuery', + expression, + }, + serializedQuery: convertKueryToElasticSearchQuery(expression, indexPattern), + }); + }, [indexPattern]); + + const isFilterQueryDraftValid = useMemo(() => { + if (filterQueryDraft?.kind === 'kuery') { + try { + esKuery.fromKueryExpression(filterQueryDraft.expression); + } catch (err) { + return false; + } + } + + return true; + }, [filterQueryDraft]); + + const serializedFilterQuery = useMemo(() => (filterQuery ? filterQuery.serializedQuery : null), [ + filterQuery, + ]); + + return { + ...state, + filterQueryAsKuery: state.filterQuery ? state.filterQuery.query : null, + filterQuery: serializedFilterQuery, + isFilterQueryDraftValid, + setLogFilterQueryDraft, + applyLogFilterQuery, + }; +}; + +export const LogFilterState = createContainer(useLogFilterState); diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_filter/with_log_filter_url_state.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_filter/with_log_filter_url_state.tsx new file mode 100644 index 0000000000000..d1da6c715cfc5 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_filter/with_log_filter_url_state.tsx @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useContext } from 'react'; +import { LogFilterState, LogFilterStateParams } from './log_filter_state'; +import { replaceStateKeyInQueryString, UrlStateContainer } from '../../../utils/url_state'; + +type LogFilterUrlState = LogFilterStateParams['filterQueryAsKuery']; + +export const WithLogFilterUrlState: React.FC = () => { + const { filterQueryAsKuery, applyLogFilterQuery } = useContext(LogFilterState.Context); + return ( + { + if (urlState) { + applyLogFilterQuery(urlState.expression); + } + }} + onInitialize={urlState => { + if (urlState) { + applyLogFilterQuery(urlState.expression); + } + }} + /> + ); +}; + +const mapToFilterQuery = (value: any): LogFilterUrlState | undefined => + value?.kind === 'kuery' && typeof value.expression === 'string' + ? { + kind: value.kind, + expression: value.expression, + } + : undefined; + +export const replaceLogFilterInQueryString = (expression: string) => + replaceStateKeyInQueryString('logFilter', { + kind: 'kuery', + expression, + }); diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/redux_bridges.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/redux_bridges.tsx index 9ea8987d4f326..393caae52511f 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/redux_bridges.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/redux_bridges.tsx @@ -7,7 +7,6 @@ import React, { useEffect, useContext } from 'react'; import { TimeKey } from '../../../../common/time'; -import { withLogFilter } from '../with_log_filter'; import { withLogPosition } from '../with_log_position'; import { LogHighlightsState } from './log_highlights'; @@ -35,21 +34,8 @@ export const LogHighlightsPositionBridge = withLogPosition( } ); -export const LogHighlightsFilterQueryBridge = withLogFilter( - ({ serializedFilterQuery }: { serializedFilterQuery: string | null }) => { - const { setFilterQuery } = useContext(LogHighlightsState.Context); - - useEffect(() => { - setFilterQuery(serializedFilterQuery); - }, [serializedFilterQuery, setFilterQuery]); - - return null; - } -); - -export const LogHighlightsBridge = ({ indexPattern }: { indexPattern: any }) => ( +export const LogHighlightsBridge = () => ( <> - ); diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_summary/with_summary.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_summary/with_summary.ts index 61c603130df52..7971c12bdfda0 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_summary/with_summary.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_summary/with_summary.ts @@ -7,19 +7,18 @@ import { useContext } from 'react'; import { connect } from 'react-redux'; -import { logFilterSelectors, logPositionSelectors, State } from '../../../store'; +import { logPositionSelectors, State } from '../../../store'; import { RendererFunction } from '../../../utils/typed_react'; import { Source } from '../../source'; import { LogViewConfiguration } from '../log_view_configuration'; import { LogSummaryBuckets, useLogSummary } from './log_summary'; +import { LogFilterState } from '../log_filter'; export const WithSummary = connect((state: State) => ({ visibleMidpointTime: logPositionSelectors.selectVisibleMidpointOrTargetTime(state), - filterQuery: logFilterSelectors.selectLogFilterQueryAsJson(state), }))( ({ children, - filterQuery, visibleMidpointTime, }: { children: RendererFunction<{ @@ -27,11 +26,11 @@ export const WithSummary = connect((state: State) => ({ start: number | null; end: number | null; }>; - filterQuery: string | null; visibleMidpointTime: number | null; }) => { const { intervalSize } = useContext(LogViewConfiguration.Context); const { sourceId } = useContext(Source.Context); + const { filterQuery } = useContext(LogFilterState.Context); const { buckets, start, end } = useLogSummary( sourceId, diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/with_log_filter.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/with_log_filter.tsx deleted file mode 100644 index 60261fc728ebb..0000000000000 --- a/x-pack/legacy/plugins/infra/public/containers/logs/with_log_filter.tsx +++ /dev/null @@ -1,100 +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 from 'react'; -import { connect } from 'react-redux'; - -import { IIndexPattern } from 'src/plugins/data/public'; -import { logFilterActions, logFilterSelectors, State } from '../../store'; -import { FilterQuery } from '../../store/local/log_filter'; -import { convertKueryToElasticSearchQuery } from '../../utils/kuery'; -import { asChildFunctionRenderer } from '../../utils/typed_react'; -import { bindPlainActionCreators } from '../../utils/typed_redux'; -import { replaceStateKeyInQueryString, UrlStateContainer } from '../../utils/url_state'; - -interface WithLogFilterProps { - indexPattern: IIndexPattern; -} - -export const withLogFilter = connect( - (state: State) => ({ - filterQuery: logFilterSelectors.selectLogFilterQuery(state), - serializedFilterQuery: logFilterSelectors.selectLogFilterQueryAsJson(state), - filterQueryDraft: logFilterSelectors.selectLogFilterQueryDraft(state), - isFilterQueryDraftValid: logFilterSelectors.selectIsLogFilterQueryDraftValid(state), - }), - (dispatch, ownProps: WithLogFilterProps) => - bindPlainActionCreators({ - applyFilterQuery: (query: FilterQuery) => - logFilterActions.applyLogFilterQuery({ - query, - serializedQuery: convertKueryToElasticSearchQuery( - query.expression, - ownProps.indexPattern - ), - }), - applyFilterQueryFromKueryExpression: (expression: string) => - logFilterActions.applyLogFilterQuery({ - query: { - kind: 'kuery', - expression, - }, - serializedQuery: convertKueryToElasticSearchQuery(expression, ownProps.indexPattern), - }), - setFilterQueryDraft: logFilterActions.setLogFilterQueryDraft, - setFilterQueryDraftFromKueryExpression: (expression: string) => - logFilterActions.setLogFilterQueryDraft({ - kind: 'kuery', - expression, - }), - })(dispatch) -); - -export const WithLogFilter = asChildFunctionRenderer(withLogFilter); - -/** - * Url State - */ - -type LogFilterUrlState = ReturnType; - -type WithLogFilterUrlStateProps = WithLogFilterProps; - -export const WithLogFilterUrlState: React.FC = ({ indexPattern }) => ( - - {({ applyFilterQuery, filterQuery }) => ( - { - if (urlState) { - applyFilterQuery(urlState); - } - }} - onInitialize={urlState => { - if (urlState) { - applyFilterQuery(urlState); - } - }} - /> - )} - -); - -const mapToFilterQuery = (value: any): LogFilterUrlState | undefined => - value && value.kind === 'kuery' && typeof value.expression === 'string' - ? { - kind: value.kind, - expression: value.expression, - } - : undefined; - -export const replaceLogFilterInQueryString = (expression: string) => - replaceStateKeyInQueryString('logFilter', { - kind: 'kuery', - expression, - }); diff --git a/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_logs.tsx b/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_logs.tsx index c409470eb24c7..bfa1ede4236aa 100644 --- a/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_logs.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_logs.tsx @@ -8,7 +8,7 @@ import compose from 'lodash/fp/compose'; import React from 'react'; import { match as RouteMatch, Redirect, RouteComponentProps } from 'react-router-dom'; -import { replaceLogFilterInQueryString } from '../../containers/logs/with_log_filter'; +import { replaceLogFilterInQueryString } from '../../containers/logs/log_filter'; import { replaceLogPositionInQueryString } from '../../containers/logs/with_log_position'; import { replaceSourceIdInQueryString } from '../../containers/source_id'; import { getFilterFromLocation, getTimeFromLocation } from './query_params'; diff --git a/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_node_logs.tsx b/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_node_logs.tsx index 4af50df343859..73d0633cb901b 100644 --- a/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_node_logs.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_node_logs.tsx @@ -11,7 +11,7 @@ import React from 'react'; import { Redirect, RouteComponentProps } from 'react-router-dom'; import { LoadingPage } from '../../components/loading_page'; -import { replaceLogFilterInQueryString } from '../../containers/logs/with_log_filter'; +import { replaceLogFilterInQueryString } from '../../containers/logs/log_filter'; import { replaceLogPositionInQueryString } from '../../containers/logs/with_log_position'; import { replaceSourceIdInQueryString } from '../../containers/source_id'; import { InfraNodeType, SourceConfigurationFields } from '../../graphql/types'; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_logs_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_logs_content.tsx index 88212849d4594..aff2a3b431067 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_logs_content.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_logs_content.tsx @@ -15,7 +15,7 @@ import { PageContent } from '../../../components/page'; import { WithSummary } from '../../../containers/logs/log_summary'; import { LogViewConfiguration } from '../../../containers/logs/log_view_configuration'; -import { WithLogFilter, WithLogFilterUrlState } from '../../../containers/logs/with_log_filter'; +import { LogFilterState } from '../../../containers/logs/log_filter'; import { LogFlyout as LogFlyoutState, WithFlyoutOptionsUrlState, @@ -31,7 +31,7 @@ import { LogsToolbar } from './page_toolbar'; import { LogHighlightsBridge, LogHighlightsState } from '../../../containers/logs/log_highlights'; export const LogsPageLogsContent: React.FunctionComponent = () => { - const { createDerivedIndexPattern, source, sourceId, version } = useContext(Source.Context); + const { source, sourceId, version } = useContext(Source.Context); const { intervalSize, textScale, textWrap } = useContext(LogViewConfiguration.Context); const { setFlyoutVisibility, @@ -43,37 +43,32 @@ export const LogsPageLogsContent: React.FunctionComponent = () => { isLoading, } = useContext(LogFlyoutState.Context); const { logSummaryHighlights } = useContext(LogHighlightsState.Context); - const derivedIndexPattern = createDerivedIndexPattern('logs'); + const { applyLogFilterQuery } = useContext(LogFilterState.Context); return ( <> - - + - - {({ applyFilterQueryFromKueryExpression }) => ( - - {({ jumpToTargetPosition, stopLiveStreaming }) => - flyoutVisible ? ( - { - jumpToTargetPosition(timeKey); - setSurroundingLogsId(flyoutItemId); - stopLiveStreaming(); - }} - setFlyoutVisibility={setFlyoutVisibility} - flyoutItem={flyoutItem} - loading={isLoading} - /> - ) : null - } - - )} - + + {({ jumpToTargetPosition, stopLiveStreaming }) => + flyoutVisible ? ( + { + jumpToTargetPosition(timeKey); + setSurroundingLogsId(flyoutItemId); + stopLiveStreaming(); + }} + setFlyoutVisibility={setFlyoutVisibility} + flyoutItem={flyoutItem} + loading={isLoading} + /> + ) : null + } + {({ @@ -132,12 +127,7 @@ export const LogsPageLogsContent: React.FunctionComponent = () => { {({ buckets }) => ( - {({ - isAutoReloading, - jumpToTargetPosition, - visibleMidpointTime, - visibleTimeInterval, - }) => ( + {({ jumpToTargetPosition, visibleMidpointTime, visibleTimeInterval }) => ( {({ isReloading }) => ( { + const { createDerivedIndexPattern } = useContext(Source.Context); + const derivedIndexPattern = createDerivedIndexPattern('logs'); + return ( + + + {children} + + ); +}; + const LogEntriesStateProvider: React.FC = ({ children }) => { const { sourceId } = useContext(Source.Context); const { timeKey, pagesBeforeStart, pagesAfterEnd, isAutoReloading } = useContext( @@ -51,11 +62,11 @@ export const LogsPageProviders: React.FunctionComponent = ({ children }) => { - + {children} - + diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_toolbar.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_toolbar.tsx index 60c0ebdfc349a..84be3eeaf238d 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_toolbar.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_toolbar.tsx @@ -19,7 +19,7 @@ import { LogTextWrapControls } from '../../../components/logging/log_text_wrap_c import { LogTimeControls } from '../../../components/logging/log_time_controls'; import { LogFlyout } from '../../../containers/logs/log_flyout'; import { LogViewConfiguration } from '../../../containers/logs/log_view_configuration'; -import { WithLogFilter } from '../../../containers/logs/with_log_filter'; +import { LogFilterState } from '../../../containers/logs/log_filter'; import { WithLogPosition } from '../../../containers/logs/with_log_position'; import { Source } from '../../../containers/source'; import { WithKueryAutocompletion } from '../../../containers/with_kuery_autocompletion'; @@ -37,7 +37,12 @@ export const LogsToolbar = () => { textScale, textWrap, } = useContext(LogViewConfiguration.Context); - + const { + filterQueryDraft, + isFilterQueryDraftValid, + applyLogFilterQuery, + setLogFilterQueryDraft, + } = useContext(LogFilterState.Context); const { setSurroundingLogsId } = useContext(LogFlyout.Context); const { @@ -55,38 +60,28 @@ export const LogsToolbar = () => { {({ isLoadingSuggestions, loadSuggestions, suggestions }) => ( - - {({ - applyFilterQueryFromKueryExpression, - filterQueryDraft, - isFilterQueryDraftValid, - setFilterQueryDraftFromKueryExpression, - }) => ( - { - setSurroundingLogsId(null); - setFilterQueryDraftFromKueryExpression(expression); - }} - onSubmit={(expression: string) => { - setSurroundingLogsId(null); - applyFilterQueryFromKueryExpression(expression); - }} - placeholder={i18n.translate( - 'xpack.infra.logsPage.toolbar.kqlSearchFieldPlaceholder', - { defaultMessage: 'Search for log entries… (e.g. host.name:host-1)' } - )} - suggestions={suggestions} - value={filterQueryDraft ? filterQueryDraft.expression : ''} - aria-label={i18n.translate( - 'xpack.infra.logsPage.toolbar.kqlSearchFieldAriaLabel', - { defaultMessage: 'Search for log entries' } - )} - /> + { + setSurroundingLogsId(null); + setLogFilterQueryDraft(expression); + }} + onSubmit={(expression: string) => { + setSurroundingLogsId(null); + applyLogFilterQuery(expression); + }} + placeholder={i18n.translate( + 'xpack.infra.logsPage.toolbar.kqlSearchFieldPlaceholder', + { defaultMessage: 'Search for log entries… (e.g. host.name:host-1)' } )} - + suggestions={suggestions} + value={filterQueryDraft ? filterQueryDraft.expression : ''} + aria-label={i18n.translate('xpack.infra.logsPage.toolbar.kqlSearchFieldAriaLabel', { + defaultMessage: 'Search for log entries', + })} + /> )} diff --git a/x-pack/legacy/plugins/infra/public/store/actions.ts b/x-pack/legacy/plugins/infra/public/store/actions.ts index e2be0d64b8f1e..ce0c0d8a4e01b 100644 --- a/x-pack/legacy/plugins/infra/public/store/actions.ts +++ b/x-pack/legacy/plugins/infra/public/store/actions.ts @@ -5,7 +5,6 @@ */ export { - logFilterActions, logPositionActions, waffleFilterActions, waffleTimeActions, diff --git a/x-pack/legacy/plugins/infra/public/store/local/actions.ts b/x-pack/legacy/plugins/infra/public/store/local/actions.ts index 1c6918ea4dc12..1827005e5e5e1 100644 --- a/x-pack/legacy/plugins/infra/public/store/local/actions.ts +++ b/x-pack/legacy/plugins/infra/public/store/local/actions.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export { logFilterActions } from './log_filter'; export { logPositionActions } from './log_position'; export { waffleFilterActions } from './waffle_filter'; export { waffleTimeActions } from './waffle_time'; diff --git a/x-pack/legacy/plugins/infra/public/store/local/log_filter/actions.ts b/x-pack/legacy/plugins/infra/public/store/local/log_filter/actions.ts deleted file mode 100644 index 32da478c61969..0000000000000 --- a/x-pack/legacy/plugins/infra/public/store/local/log_filter/actions.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import actionCreatorFactory from 'typescript-fsa'; - -import { FilterQuery, SerializedFilterQuery } from './reducer'; - -const actionCreator = actionCreatorFactory('x-pack/infra/local/log_filter'); - -export const setLogFilterQueryDraft = actionCreator('SET_LOG_FILTER_QUERY_DRAFT'); - -export const applyLogFilterQuery = actionCreator('APPLY_LOG_FILTER_QUERY'); diff --git a/x-pack/legacy/plugins/infra/public/store/local/log_filter/index.ts b/x-pack/legacy/plugins/infra/public/store/local/log_filter/index.ts deleted file mode 100644 index 369f5f013d669..0000000000000 --- a/x-pack/legacy/plugins/infra/public/store/local/log_filter/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import * as logFilterActions from './actions'; -import * as logFilterSelectors from './selectors'; - -export { logFilterActions, logFilterSelectors }; -export * from './reducer'; diff --git a/x-pack/legacy/plugins/infra/public/store/local/log_filter/reducer.ts b/x-pack/legacy/plugins/infra/public/store/local/log_filter/reducer.ts deleted file mode 100644 index afb77dd9ddc6a..0000000000000 --- a/x-pack/legacy/plugins/infra/public/store/local/log_filter/reducer.ts +++ /dev/null @@ -1,43 +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 { reducerWithInitialState } from 'typescript-fsa-reducers/dist'; - -import { applyLogFilterQuery, setLogFilterQueryDraft } from './actions'; - -export interface KueryFilterQuery { - kind: 'kuery'; - expression: string; -} - -export type FilterQuery = KueryFilterQuery; - -export interface SerializedFilterQuery { - query: FilterQuery; - serializedQuery: string; -} - -export interface LogFilterState { - filterQuery: SerializedFilterQuery | null; - filterQueryDraft: KueryFilterQuery | null; -} - -export const initialLogFilterState: LogFilterState = { - filterQuery: null, - filterQueryDraft: null, -}; - -export const logFilterReducer = reducerWithInitialState(initialLogFilterState) - .case(setLogFilterQueryDraft, (state, filterQueryDraft) => ({ - ...state, - filterQueryDraft, - })) - .case(applyLogFilterQuery, (state, filterQuery) => ({ - ...state, - filterQuery, - filterQueryDraft: filterQuery.query, - })) - .build(); diff --git a/x-pack/legacy/plugins/infra/public/store/local/log_filter/selectors.ts b/x-pack/legacy/plugins/infra/public/store/local/log_filter/selectors.ts deleted file mode 100644 index f17f7be4defe9..0000000000000 --- a/x-pack/legacy/plugins/infra/public/store/local/log_filter/selectors.ts +++ /dev/null @@ -1,32 +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 { createSelector } from 'reselect'; -import { LogFilterState } from './reducer'; -import { esKuery } from '../../../../../../../../src/plugins/data/public'; - -export const selectLogFilterQuery = (state: LogFilterState) => - state.filterQuery ? state.filterQuery.query : null; - -export const selectLogFilterQueryAsJson = (state: LogFilterState) => - state.filterQuery ? state.filterQuery.serializedQuery : null; - -export const selectLogFilterQueryDraft = (state: LogFilterState) => state.filterQueryDraft; - -export const selectIsLogFilterQueryDraftValid = createSelector( - selectLogFilterQueryDraft, - filterQueryDraft => { - if (filterQueryDraft && filterQueryDraft.kind === 'kuery') { - try { - esKuery.fromKueryExpression(filterQueryDraft.expression); - } catch (err) { - return false; - } - } - - return true; - } -); diff --git a/x-pack/legacy/plugins/infra/public/store/local/reducer.ts b/x-pack/legacy/plugins/infra/public/store/local/reducer.ts index 6308f1bc75427..5cc839af4c7cc 100644 --- a/x-pack/legacy/plugins/infra/public/store/local/reducer.ts +++ b/x-pack/legacy/plugins/infra/public/store/local/reducer.ts @@ -6,7 +6,6 @@ import { combineReducers } from 'redux'; -import { initialLogFilterState, logFilterReducer, LogFilterState } from './log_filter'; import { initialLogPositionState, logPositionReducer, LogPositionState } from './log_position'; import { initialWaffleFilterState, waffleFilterReducer, WaffleFilterState } from './waffle_filter'; import { @@ -17,7 +16,6 @@ import { import { initialWaffleTimeState, waffleTimeReducer, WaffleTimeState } from './waffle_time'; export interface LocalState { - logFilter: LogFilterState; logPosition: LogPositionState; waffleFilter: WaffleFilterState; waffleTime: WaffleTimeState; @@ -25,7 +23,6 @@ export interface LocalState { } export const initialLocalState: LocalState = { - logFilter: initialLogFilterState, logPosition: initialLogPositionState, waffleFilter: initialWaffleFilterState, waffleTime: initialWaffleTimeState, @@ -33,7 +30,6 @@ export const initialLocalState: LocalState = { }; export const localReducer = combineReducers({ - logFilter: logFilterReducer, logPosition: logPositionReducer, waffleFilter: waffleFilterReducer, waffleTime: waffleTimeReducer, diff --git a/x-pack/legacy/plugins/infra/public/store/local/selectors.ts b/x-pack/legacy/plugins/infra/public/store/local/selectors.ts index ef57835496f60..c367901353b3d 100644 --- a/x-pack/legacy/plugins/infra/public/store/local/selectors.ts +++ b/x-pack/legacy/plugins/infra/public/store/local/selectors.ts @@ -5,18 +5,12 @@ */ import { globalizeSelectors } from '../../utils/typed_redux'; -import { logFilterSelectors as innerLogFilterSelectors } from './log_filter'; import { logPositionSelectors as innerLogPositionSelectors } from './log_position'; import { LocalState } from './reducer'; import { waffleFilterSelectors as innerWaffleFilterSelectors } from './waffle_filter'; import { waffleOptionsSelectors as innerWaffleOptionsSelectors } from './waffle_options'; import { waffleTimeSelectors as innerWaffleTimeSelectors } from './waffle_time'; -export const logFilterSelectors = globalizeSelectors( - (state: LocalState) => state.logFilter, - innerLogFilterSelectors -); - export const logPositionSelectors = globalizeSelectors( (state: LocalState) => state.logPosition, innerLogPositionSelectors diff --git a/x-pack/legacy/plugins/infra/public/store/selectors.ts b/x-pack/legacy/plugins/infra/public/store/selectors.ts index aecba1779d036..d98e8ae9a0aae 100644 --- a/x-pack/legacy/plugins/infra/public/store/selectors.ts +++ b/x-pack/legacy/plugins/infra/public/store/selectors.ts @@ -6,7 +6,6 @@ import { globalizeSelectors } from '../utils/typed_redux'; import { - logFilterSelectors as localLogFilterSelectors, logPositionSelectors as localLogPositionSelectors, waffleFilterSelectors as localWaffleFilterSelectors, waffleOptionsSelectors as localWaffleOptionsSelectors, @@ -19,7 +18,6 @@ import { State } from './reducer'; const selectLocal = (state: State) => state.local; -export const logFilterSelectors = globalizeSelectors(selectLocal, localLogFilterSelectors); export const logPositionSelectors = globalizeSelectors(selectLocal, localLogPositionSelectors); export const waffleFilterSelectors = globalizeSelectors(selectLocal, localWaffleFilterSelectors); export const waffleTimeSelectors = globalizeSelectors(selectLocal, localWaffleTimeSelectors); diff --git a/x-pack/legacy/plugins/infra/public/store/store.ts b/x-pack/legacy/plugins/infra/public/store/store.ts index 601db0f56a693..2781b0b2cef3f 100644 --- a/x-pack/legacy/plugins/infra/public/store/store.ts +++ b/x-pack/legacy/plugins/infra/public/store/store.ts @@ -12,7 +12,6 @@ import { map } from 'rxjs/operators'; import { createRootEpic, initialState, - logFilterSelectors, logPositionSelectors, reducer, State, @@ -39,7 +38,6 @@ export function createStore({ apolloClient, observableApi }: StoreDependencies) apolloClient$: apolloClient, selectIsAutoReloadingLogEntries: logPositionSelectors.selectIsAutoReloading, selectIsAutoReloadingScrollLocked: logPositionSelectors.selectAutoReloadScrollLock, - selectLogFilterQueryAsJson: logFilterSelectors.selectLogFilterQueryAsJson, selectLogTargetPosition: logPositionSelectors.selectTargetPosition, selectVisibleLogMidpointOrTarget: logPositionSelectors.selectVisibleMidpointOrTarget, selectWaffleTimeUpdatePolicyInterval: waffleTimeSelectors.selectTimeUpdatePolicyInterval, diff --git a/x-pack/test/functional/apps/infra/link_to.ts b/x-pack/test/functional/apps/infra/link_to.ts index a68637600be8b..d1ae7138ecc65 100644 --- a/x-pack/test/functional/apps/infra/link_to.ts +++ b/x-pack/test/functional/apps/infra/link_to.ts @@ -23,7 +23,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }; const expectedSearchString = "logFilter=(expression:'trace.id:433b4651687e18be2c6c8e3b11f53d09',kind:kuery)&logPosition=(position:(tiebreaker:0,time:1565707203194))&sourceId=default"; - const expectedRedirect = `/logs/stream?${expectedSearchString}`; + const expectedRedirectPath = '/logs/stream?'; await pageObjects.common.navigateToActualUrl( 'infraOps', @@ -32,7 +32,9 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await retry.tryForTime(5000, async () => { const currentUrl = await browser.getCurrentUrl(); const [, currentHash] = decodeURIComponent(currentUrl).split('#'); - expect(currentHash).to.contain(expectedRedirect); + // Account for unpredictable location of the g parameter in the search string + expect(currentHash.slice(0, expectedRedirectPath.length)).to.be(expectedRedirectPath); + expect(currentHash.slice(expectedRedirectPath.length)).to.contain(expectedSearchString); }); }); }); From 9843a5b64c67c832fef1005fe71aae489943e0b2 Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Sat, 4 Jan 2020 11:14:39 +0300 Subject: [PATCH 02/17] Set consistent EOL symbol in core API docs (#53815) * update api-extractor, api-documenter versions * set EOL: lf. sync with editorconfig * regen docs * generate docs with --docs * rebuild kbn/pm to fix CI fauilure Co-authored-by: Spencer --- .../kibana-plugin-public.chromedoctitle.md | 14 +- .../kibana-plugin-public.chromenavcontrols.md | 14 +- .../kibana-plugin-public.chromestart.md | 50 +- .../kibana-plugin-public.contextsetup.md | 12 +- .../kibana-plugin-public.icontextcontainer.md | 14 +- .../kibana-plugin-public.legacycoresetup.md | 8 +- .../kibana-plugin-public.legacycorestart.md | 8 +- ...kibana-plugin-public.savedobjectsclient.md | 8 +- .../server/kibana-plugin-server.basepath.md | 8 +- ...-plugin-server.configdeprecationfactory.md | 18 +- .../kibana-plugin-server.contextsetup.md | 12 +- .../server/kibana-plugin-server.cspconfig.md | 8 +- ...plugin-server.elasticsearcherrorhelpers.md | 14 +- .../kibana-plugin-server.httpservicesetup.md | 30 +- .../kibana-plugin-server.icontextcontainer.md | 14 +- ...na-plugin-server.pluginconfigdescriptor.md | 16 +- .../kibana-plugin-server.pluginmanifest.md | 8 +- ...kibana-plugin-server.savedobjectsclient.md | 8 +- ...-plugin-server.savedobjectsservicesetup.md | 18 +- package.json | 4 +- packages/kbn-pm/dist/index.js | 588 ++- src/core/server/server.api.md | 3959 ++++++++--------- src/dev/run_check_core_api_changes.ts | 1 + yarn.lock | 139 +- 24 files changed, 2520 insertions(+), 2453 deletions(-) diff --git a/docs/development/core/public/kibana-plugin-public.chromedoctitle.md b/docs/development/core/public/kibana-plugin-public.chromedoctitle.md index 3c6cfab486288..feb3b3ab966ef 100644 --- a/docs/development/core/public/kibana-plugin-public.chromedoctitle.md +++ b/docs/development/core/public/kibana-plugin-public.chromedoctitle.md @@ -12,13 +12,6 @@ APIs for accessing and updating the document title. export interface ChromeDocTitle ``` -## Methods - -| Method | Description | -| --- | --- | -| [change(newTitle)](./kibana-plugin-public.chromedoctitle.change.md) | Changes the current document title. | -| [reset()](./kibana-plugin-public.chromedoctitle.reset.md) | Resets the document title to it's initial value. (meaning the one present in the title meta at application load.) | - ## Example 1 How to change the title of the document @@ -37,3 +30,10 @@ chrome.docTitle.reset() ``` +## Methods + +| Method | Description | +| --- | --- | +| [change(newTitle)](./kibana-plugin-public.chromedoctitle.change.md) | Changes the current document title. | +| [reset()](./kibana-plugin-public.chromedoctitle.reset.md) | Resets the document title to it's initial value. (meaning the one present in the title meta at application load.) | + diff --git a/docs/development/core/public/kibana-plugin-public.chromenavcontrols.md b/docs/development/core/public/kibana-plugin-public.chromenavcontrols.md index a34db9bb33d9d..30b9a6869d1ff 100644 --- a/docs/development/core/public/kibana-plugin-public.chromenavcontrols.md +++ b/docs/development/core/public/kibana-plugin-public.chromenavcontrols.md @@ -12,13 +12,6 @@ export interface ChromeNavControls ``` -## Methods - -| Method | Description | -| --- | --- | -| [registerLeft(navControl)](./kibana-plugin-public.chromenavcontrols.registerleft.md) | Register a nav control to be presented on the left side of the chrome header. | -| [registerRight(navControl)](./kibana-plugin-public.chromenavcontrols.registerright.md) | Register a nav control to be presented on the right side of the chrome header. | - ## Example Register a left-side nav control rendered with React. @@ -33,3 +26,10 @@ chrome.navControls.registerLeft({ ``` +## Methods + +| Method | Description | +| --- | --- | +| [registerLeft(navControl)](./kibana-plugin-public.chromenavcontrols.registerleft.md) | Register a nav control to be presented on the left side of the chrome header. | +| [registerRight(navControl)](./kibana-plugin-public.chromenavcontrols.registerright.md) | Register a nav control to be presented on the right side of the chrome header. | + diff --git a/docs/development/core/public/kibana-plugin-public.chromestart.md b/docs/development/core/public/kibana-plugin-public.chromestart.md index d5d99f3d5be65..4e44e5bf05074 100644 --- a/docs/development/core/public/kibana-plugin-public.chromestart.md +++ b/docs/development/core/public/kibana-plugin-public.chromestart.md @@ -12,6 +12,31 @@ ChromeStart allows plugins to customize the global chrome header UI and enrich t export interface ChromeStart ``` +## Remarks + +While ChromeStart exposes many APIs, they should be used sparingly and the developer should understand how they affect other plugins and applications. + +## Example 1 + +How to add a recently accessed item to the sidebar: + +```ts +core.chrome.recentlyAccessed.add('/app/map/1234', 'Map 1234', '1234'); + +``` + +## Example 2 + +How to set the help dropdown extension: + +```tsx +core.chrome.setHelpExtension(elem => { + ReactDOM.render(, elem); + return () => ReactDOM.unmountComponentAtNode(elem); +}); + +``` + ## Properties | Property | Type | Description | @@ -43,28 +68,3 @@ export interface ChromeStart | [setIsCollapsed(isCollapsed)](./kibana-plugin-public.chromestart.setiscollapsed.md) | Set the collapsed state of the chrome navigation. | | [setIsVisible(isVisible)](./kibana-plugin-public.chromestart.setisvisible.md) | Set the temporary visibility for the chrome. This does nothing if the chrome is hidden by default and should be used to hide the chrome for things like full-screen modes with an exit button. | -## Remarks - -While ChromeStart exposes many APIs, they should be used sparingly and the developer should understand how they affect other plugins and applications. - -## Example 1 - -How to add a recently accessed item to the sidebar: - -```ts -core.chrome.recentlyAccessed.add('/app/map/1234', 'Map 1234', '1234'); - -``` - -## Example 2 - -How to set the help dropdown extension: - -```tsx -core.chrome.setHelpExtension(elem => { - ReactDOM.render(, elem); - return () => ReactDOM.unmountComponentAtNode(elem); -}); - -``` - diff --git a/docs/development/core/public/kibana-plugin-public.contextsetup.md b/docs/development/core/public/kibana-plugin-public.contextsetup.md index a006fa7205ca6..d4399b6ba70c4 100644 --- a/docs/development/core/public/kibana-plugin-public.contextsetup.md +++ b/docs/development/core/public/kibana-plugin-public.contextsetup.md @@ -12,12 +12,6 @@ An object that handles registration of context providers and configuring handler export interface ContextSetup ``` -## Methods - -| Method | Description | -| --- | --- | -| [createContextContainer()](./kibana-plugin-public.contextsetup.createcontextcontainer.md) | Creates a new [IContextContainer](./kibana-plugin-public.icontextcontainer.md) for a service owner. | - ## Remarks A [IContextContainer](./kibana-plugin-public.icontextcontainer.md) can be used by any Core service or plugin (known as the "service owner") which wishes to expose APIs in a handler function. The container object will manage registering context providers and configuring a handler with all of the contexts that should be exposed to the handler's plugin. This is dependent on the dependencies that the handler's plugin declares. @@ -136,3 +130,9 @@ class VizRenderingPlugin { ``` +## Methods + +| Method | Description | +| --- | --- | +| [createContextContainer()](./kibana-plugin-public.contextsetup.createcontextcontainer.md) | Creates a new [IContextContainer](./kibana-plugin-public.icontextcontainer.md) for a service owner. | + diff --git a/docs/development/core/public/kibana-plugin-public.icontextcontainer.md b/docs/development/core/public/kibana-plugin-public.icontextcontainer.md index f16c07b3d7906..7a21df6b93bb5 100644 --- a/docs/development/core/public/kibana-plugin-public.icontextcontainer.md +++ b/docs/development/core/public/kibana-plugin-public.icontextcontainer.md @@ -12,13 +12,6 @@ An object that handles registration of context providers and configuring handler export interface IContextContainer> ``` -## Methods - -| Method | Description | -| --- | --- | -| [createHandler(pluginOpaqueId, handler)](./kibana-plugin-public.icontextcontainer.createhandler.md) | Create a new handler function pre-wired to context for the plugin. | -| [registerContext(pluginOpaqueId, contextName, provider)](./kibana-plugin-public.icontextcontainer.registercontext.md) | Register a new context provider. | - ## Remarks A [IContextContainer](./kibana-plugin-public.icontextcontainer.md) can be used by any Core service or plugin (known as the "service owner") which wishes to expose APIs in a handler function. The container object will manage registering context providers and configuring a handler with all of the contexts that should be exposed to the handler's plugin. This is dependent on the dependencies that the handler's plugin declares. @@ -78,3 +71,10 @@ class MyPlugin { ``` +## Methods + +| Method | Description | +| --- | --- | +| [createHandler(pluginOpaqueId, handler)](./kibana-plugin-public.icontextcontainer.createhandler.md) | Create a new handler function pre-wired to context for the plugin. | +| [registerContext(pluginOpaqueId, contextName, provider)](./kibana-plugin-public.icontextcontainer.registercontext.md) | Register a new context provider. | + diff --git a/docs/development/core/public/kibana-plugin-public.legacycoresetup.md b/docs/development/core/public/kibana-plugin-public.legacycoresetup.md index a753300437c1c..803c96cd0b22c 100644 --- a/docs/development/core/public/kibana-plugin-public.legacycoresetup.md +++ b/docs/development/core/public/kibana-plugin-public.legacycoresetup.md @@ -16,13 +16,13 @@ Setup interface exposed to the legacy platform via the `ui/new_platform` module. export interface LegacyCoreSetup extends CoreSetup ``` +## Remarks + +Some methods are not supported in the legacy platform and while present to make this type compatibile with [CoreSetup](./kibana-plugin-public.coresetup.md), unsupported methods will throw exceptions when called. + ## Properties | Property | Type | Description | | --- | --- | --- | | [injectedMetadata](./kibana-plugin-public.legacycoresetup.injectedmetadata.md) | InjectedMetadataSetup | | -## Remarks - -Some methods are not supported in the legacy platform and while present to make this type compatibile with [CoreSetup](./kibana-plugin-public.coresetup.md), unsupported methods will throw exceptions when called. - diff --git a/docs/development/core/public/kibana-plugin-public.legacycorestart.md b/docs/development/core/public/kibana-plugin-public.legacycorestart.md index 775c3fb1ffe3d..438a3d6110776 100644 --- a/docs/development/core/public/kibana-plugin-public.legacycorestart.md +++ b/docs/development/core/public/kibana-plugin-public.legacycorestart.md @@ -16,13 +16,13 @@ Start interface exposed to the legacy platform via the `ui/new_platform` module. export interface LegacyCoreStart extends CoreStart ``` +## Remarks + +Some methods are not supported in the legacy platform and while present to make this type compatibile with [CoreStart](./kibana-plugin-public.corestart.md), unsupported methods will throw exceptions when called. + ## Properties | Property | Type | Description | | --- | --- | --- | | [injectedMetadata](./kibana-plugin-public.legacycorestart.injectedmetadata.md) | InjectedMetadataStart | | -## Remarks - -Some methods are not supported in the legacy platform and while present to make this type compatibile with [CoreStart](./kibana-plugin-public.corestart.md), unsupported methods will throw exceptions when called. - diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsclient.md b/docs/development/core/public/kibana-plugin-public.savedobjectsclient.md index 6033c667c1866..3b916db972673 100644 --- a/docs/development/core/public/kibana-plugin-public.savedobjectsclient.md +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsclient.md @@ -12,6 +12,10 @@ Saved Objects is Kibana's data persisentence mechanism allowing plugins to use E export declare class SavedObjectsClient ``` +## Remarks + +The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `SavedObjectsClient` class. + ## Properties | Property | Modifiers | Type | Description | @@ -30,7 +34,3 @@ export declare class SavedObjectsClient | [bulkUpdate(objects)](./kibana-plugin-public.savedobjectsclient.bulkupdate.md) | | Update multiple documents at once | | [update(type, id, attributes, { version, migrationVersion, references })](./kibana-plugin-public.savedobjectsclient.update.md) | | Updates an object | -## Remarks - -The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `SavedObjectsClient` class. - diff --git a/docs/development/core/server/kibana-plugin-server.basepath.md b/docs/development/core/server/kibana-plugin-server.basepath.md index 77f50abc60369..50a30f7c43fe6 100644 --- a/docs/development/core/server/kibana-plugin-server.basepath.md +++ b/docs/development/core/server/kibana-plugin-server.basepath.md @@ -12,6 +12,10 @@ Access or manipulate the Kibana base path export declare class BasePath ``` +## Remarks + +The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `BasePath` class. + ## Properties | Property | Modifiers | Type | Description | @@ -22,7 +26,3 @@ export declare class BasePath | [serverBasePath](./kibana-plugin-server.basepath.serverbasepath.md) | | string | returns the server's basePathSee [BasePath.get](./kibana-plugin-server.basepath.get.md) for getting the basePath value for a specific request | | [set](./kibana-plugin-server.basepath.set.md) | | (request: KibanaRequest<unknown, unknown, unknown, any> | LegacyRequest, requestSpecificBasePath: string) => void | sets basePath value, specific for an incoming request. | -## Remarks - -The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `BasePath` class. - diff --git a/docs/development/core/server/kibana-plugin-server.configdeprecationfactory.md b/docs/development/core/server/kibana-plugin-server.configdeprecationfactory.md index f022d6c1d064a..0302797147cff 100644 --- a/docs/development/core/server/kibana-plugin-server.configdeprecationfactory.md +++ b/docs/development/core/server/kibana-plugin-server.configdeprecationfactory.md @@ -14,15 +14,6 @@ See methods documentation for more detailed examples. export interface ConfigDeprecationFactory ``` -## Methods - -| Method | Description | -| --- | --- | -| [rename(oldKey, newKey)](./kibana-plugin-server.configdeprecationfactory.rename.md) | Rename a configuration property from inside a plugin's configuration path. Will log a deprecation warning if the oldKey was found and deprecation applied. | -| [renameFromRoot(oldKey, newKey)](./kibana-plugin-server.configdeprecationfactory.renamefromroot.md) | Rename a configuration property from the root configuration. Will log a deprecation warning if the oldKey was found and deprecation applied.This should be only used when renaming properties from different configuration's path. To rename properties from inside a plugin's configuration, use 'rename' instead. | -| [unused(unusedKey)](./kibana-plugin-server.configdeprecationfactory.unused.md) | Remove a configuration property from inside a plugin's configuration path. Will log a deprecation warning if the unused key was found and deprecation applied. | -| [unusedFromRoot(unusedKey)](./kibana-plugin-server.configdeprecationfactory.unusedfromroot.md) | Remove a configuration property from the root configuration. Will log a deprecation warning if the unused key was found and deprecation applied.This should be only used when removing properties from outside of a plugin's configuration. To remove properties from inside a plugin's configuration, use 'unused' instead. | - ## Example @@ -34,3 +25,12 @@ const provider: ConfigDeprecationProvider = ({ rename, unused }) => [ ``` +## Methods + +| Method | Description | +| --- | --- | +| [rename(oldKey, newKey)](./kibana-plugin-server.configdeprecationfactory.rename.md) | Rename a configuration property from inside a plugin's configuration path. Will log a deprecation warning if the oldKey was found and deprecation applied. | +| [renameFromRoot(oldKey, newKey)](./kibana-plugin-server.configdeprecationfactory.renamefromroot.md) | Rename a configuration property from the root configuration. Will log a deprecation warning if the oldKey was found and deprecation applied.This should be only used when renaming properties from different configuration's path. To rename properties from inside a plugin's configuration, use 'rename' instead. | +| [unused(unusedKey)](./kibana-plugin-server.configdeprecationfactory.unused.md) | Remove a configuration property from inside a plugin's configuration path. Will log a deprecation warning if the unused key was found and deprecation applied. | +| [unusedFromRoot(unusedKey)](./kibana-plugin-server.configdeprecationfactory.unusedfromroot.md) | Remove a configuration property from the root configuration. Will log a deprecation warning if the unused key was found and deprecation applied.This should be only used when removing properties from outside of a plugin's configuration. To remove properties from inside a plugin's configuration, use 'unused' instead. | + diff --git a/docs/development/core/server/kibana-plugin-server.contextsetup.md b/docs/development/core/server/kibana-plugin-server.contextsetup.md index 1f285efe92b68..1b2a1e2f1b621 100644 --- a/docs/development/core/server/kibana-plugin-server.contextsetup.md +++ b/docs/development/core/server/kibana-plugin-server.contextsetup.md @@ -12,12 +12,6 @@ An object that handles registration of context providers and configuring handler export interface ContextSetup ``` -## Methods - -| Method | Description | -| --- | --- | -| [createContextContainer()](./kibana-plugin-server.contextsetup.createcontextcontainer.md) | Creates a new [IContextContainer](./kibana-plugin-server.icontextcontainer.md) for a service owner. | - ## Remarks A [IContextContainer](./kibana-plugin-server.icontextcontainer.md) can be used by any Core service or plugin (known as the "service owner") which wishes to expose APIs in a handler function. The container object will manage registering context providers and configuring a handler with all of the contexts that should be exposed to the handler's plugin. This is dependent on the dependencies that the handler's plugin declares. @@ -136,3 +130,9 @@ class VizRenderingPlugin { ``` +## Methods + +| Method | Description | +| --- | --- | +| [createContextContainer()](./kibana-plugin-server.contextsetup.createcontextcontainer.md) | Creates a new [IContextContainer](./kibana-plugin-server.icontextcontainer.md) for a service owner. | + diff --git a/docs/development/core/server/kibana-plugin-server.cspconfig.md b/docs/development/core/server/kibana-plugin-server.cspconfig.md index e5276991be404..7e491cb0df912 100644 --- a/docs/development/core/server/kibana-plugin-server.cspconfig.md +++ b/docs/development/core/server/kibana-plugin-server.cspconfig.md @@ -12,6 +12,10 @@ CSP configuration for use in Kibana. export declare class CspConfig implements ICspConfig ``` +## Remarks + +The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `CspConfig` class. + ## Properties | Property | Modifiers | Type | Description | @@ -22,7 +26,3 @@ export declare class CspConfig implements ICspConfig | [strict](./kibana-plugin-server.cspconfig.strict.md) | | boolean | | | [warnLegacyBrowsers](./kibana-plugin-server.cspconfig.warnlegacybrowsers.md) | | boolean | | -## Remarks - -The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `CspConfig` class. - diff --git a/docs/development/core/server/kibana-plugin-server.elasticsearcherrorhelpers.md b/docs/development/core/server/kibana-plugin-server.elasticsearcherrorhelpers.md index c823da392042a..2e615acfeac6b 100644 --- a/docs/development/core/server/kibana-plugin-server.elasticsearcherrorhelpers.md +++ b/docs/development/core/server/kibana-plugin-server.elasticsearcherrorhelpers.md @@ -12,13 +12,6 @@ Helpers for working with errors returned from the Elasticsearch service.Since th export declare class ElasticsearchErrorHelpers ``` -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [decorateNotAuthorizedError(error, reason)](./kibana-plugin-server.elasticsearcherrorhelpers.decoratenotauthorizederror.md) | static | | -| [isNotAuthorizedError(error)](./kibana-plugin-server.elasticsearcherrorhelpers.isnotauthorizederror.md) | static | | - ## Example Handle errors @@ -33,3 +26,10 @@ try { ``` +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [decorateNotAuthorizedError(error, reason)](./kibana-plugin-server.elasticsearcherrorhelpers.decoratenotauthorizederror.md) | static | | +| [isNotAuthorizedError(error)](./kibana-plugin-server.elasticsearcherrorhelpers.isnotauthorizederror.md) | static | | + diff --git a/docs/development/core/server/kibana-plugin-server.httpservicesetup.md b/docs/development/core/server/kibana-plugin-server.httpservicesetup.md index 99d4caf40c0d3..3b1993841339d 100644 --- a/docs/development/core/server/kibana-plugin-server.httpservicesetup.md +++ b/docs/development/core/server/kibana-plugin-server.httpservicesetup.md @@ -12,21 +12,6 @@ Kibana HTTP Service provides own abstraction for work with HTTP stack. Plugins d export interface HttpServiceSetup ``` -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [basePath](./kibana-plugin-server.httpservicesetup.basepath.md) | IBasePath | Access or manipulate the Kibana base path See [IBasePath](./kibana-plugin-server.ibasepath.md). | -| [createCookieSessionStorageFactory](./kibana-plugin-server.httpservicesetup.createcookiesessionstoragefactory.md) | <T>(cookieOptions: SessionStorageCookieOptions<T>) => Promise<SessionStorageFactory<T>> | Creates cookie based session storage factory [SessionStorageFactory](./kibana-plugin-server.sessionstoragefactory.md) | -| [createRouter](./kibana-plugin-server.httpservicesetup.createrouter.md) | () => IRouter | Provides ability to declare a handler function for a particular path and HTTP request method. | -| [csp](./kibana-plugin-server.httpservicesetup.csp.md) | ICspConfig | The CSP config used for Kibana. | -| [isTlsEnabled](./kibana-plugin-server.httpservicesetup.istlsenabled.md) | boolean | Flag showing whether a server was configured to use TLS connection. | -| [registerAuth](./kibana-plugin-server.httpservicesetup.registerauth.md) | (handler: AuthenticationHandler) => void | To define custom authentication and/or authorization mechanism for incoming requests. | -| [registerOnPostAuth](./kibana-plugin-server.httpservicesetup.registeronpostauth.md) | (handler: OnPostAuthHandler) => void | To define custom logic to perform for incoming requests. | -| [registerOnPreAuth](./kibana-plugin-server.httpservicesetup.registeronpreauth.md) | (handler: OnPreAuthHandler) => void | To define custom logic to perform for incoming requests. | -| [registerOnPreResponse](./kibana-plugin-server.httpservicesetup.registeronpreresponse.md) | (handler: OnPreResponseHandler) => void | To define custom logic to perform for the server response. | -| [registerRouteHandlerContext](./kibana-plugin-server.httpservicesetup.registerroutehandlercontext.md) | <T extends keyof RequestHandlerContext>(contextName: T, provider: RequestHandlerContextProvider<T>) => RequestHandlerContextContainer | Register a context provider for a route handler. | - ## Example To handle an incoming request in your plugin you should: - Create a `Router` instance. @@ -92,3 +77,18 @@ async (context, request, response) => { ``` +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [basePath](./kibana-plugin-server.httpservicesetup.basepath.md) | IBasePath | Access or manipulate the Kibana base path See [IBasePath](./kibana-plugin-server.ibasepath.md). | +| [createCookieSessionStorageFactory](./kibana-plugin-server.httpservicesetup.createcookiesessionstoragefactory.md) | <T>(cookieOptions: SessionStorageCookieOptions<T>) => Promise<SessionStorageFactory<T>> | Creates cookie based session storage factory [SessionStorageFactory](./kibana-plugin-server.sessionstoragefactory.md) | +| [createRouter](./kibana-plugin-server.httpservicesetup.createrouter.md) | () => IRouter | Provides ability to declare a handler function for a particular path and HTTP request method. | +| [csp](./kibana-plugin-server.httpservicesetup.csp.md) | ICspConfig | The CSP config used for Kibana. | +| [isTlsEnabled](./kibana-plugin-server.httpservicesetup.istlsenabled.md) | boolean | Flag showing whether a server was configured to use TLS connection. | +| [registerAuth](./kibana-plugin-server.httpservicesetup.registerauth.md) | (handler: AuthenticationHandler) => void | To define custom authentication and/or authorization mechanism for incoming requests. | +| [registerOnPostAuth](./kibana-plugin-server.httpservicesetup.registeronpostauth.md) | (handler: OnPostAuthHandler) => void | To define custom logic to perform for incoming requests. | +| [registerOnPreAuth](./kibana-plugin-server.httpservicesetup.registeronpreauth.md) | (handler: OnPreAuthHandler) => void | To define custom logic to perform for incoming requests. | +| [registerOnPreResponse](./kibana-plugin-server.httpservicesetup.registeronpreresponse.md) | (handler: OnPreResponseHandler) => void | To define custom logic to perform for the server response. | +| [registerRouteHandlerContext](./kibana-plugin-server.httpservicesetup.registerroutehandlercontext.md) | <T extends keyof RequestHandlerContext>(contextName: T, provider: RequestHandlerContextProvider<T>) => RequestHandlerContextContainer | Register a context provider for a route handler. | + diff --git a/docs/development/core/server/kibana-plugin-server.icontextcontainer.md b/docs/development/core/server/kibana-plugin-server.icontextcontainer.md index 114da31442ff9..8235c40131536 100644 --- a/docs/development/core/server/kibana-plugin-server.icontextcontainer.md +++ b/docs/development/core/server/kibana-plugin-server.icontextcontainer.md @@ -12,13 +12,6 @@ An object that handles registration of context providers and configuring handler export interface IContextContainer> ``` -## Methods - -| Method | Description | -| --- | --- | -| [createHandler(pluginOpaqueId, handler)](./kibana-plugin-server.icontextcontainer.createhandler.md) | Create a new handler function pre-wired to context for the plugin. | -| [registerContext(pluginOpaqueId, contextName, provider)](./kibana-plugin-server.icontextcontainer.registercontext.md) | Register a new context provider. | - ## Remarks A [IContextContainer](./kibana-plugin-server.icontextcontainer.md) can be used by any Core service or plugin (known as the "service owner") which wishes to expose APIs in a handler function. The container object will manage registering context providers and configuring a handler with all of the contexts that should be exposed to the handler's plugin. This is dependent on the dependencies that the handler's plugin declares. @@ -78,3 +71,10 @@ class MyPlugin { ``` +## Methods + +| Method | Description | +| --- | --- | +| [createHandler(pluginOpaqueId, handler)](./kibana-plugin-server.icontextcontainer.createhandler.md) | Create a new handler function pre-wired to context for the plugin. | +| [registerContext(pluginOpaqueId, contextName, provider)](./kibana-plugin-server.icontextcontainer.registercontext.md) | Register a new context provider. | + diff --git a/docs/development/core/server/kibana-plugin-server.pluginconfigdescriptor.md b/docs/development/core/server/kibana-plugin-server.pluginconfigdescriptor.md index 671298a67381a..3d661ac66d2b7 100644 --- a/docs/development/core/server/kibana-plugin-server.pluginconfigdescriptor.md +++ b/docs/development/core/server/kibana-plugin-server.pluginconfigdescriptor.md @@ -12,14 +12,6 @@ Describes a plugin configuration properties. export interface PluginConfigDescriptor ``` -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [deprecations](./kibana-plugin-server.pluginconfigdescriptor.deprecations.md) | ConfigDeprecationProvider | Provider for the [ConfigDeprecation](./kibana-plugin-server.configdeprecation.md) to apply to the plugin configuration. | -| [exposeToBrowser](./kibana-plugin-server.pluginconfigdescriptor.exposetobrowser.md) | {
[P in keyof T]?: boolean;
} | List of configuration properties that will be available on the client-side plugin. | -| [schema](./kibana-plugin-server.pluginconfigdescriptor.schema.md) | PluginConfigSchema<T> | Schema to use to validate the plugin configuration.[PluginConfigSchema](./kibana-plugin-server.pluginconfigschema.md) | - ## Example @@ -48,3 +40,11 @@ export const config: PluginConfigDescriptor = { ``` +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [deprecations](./kibana-plugin-server.pluginconfigdescriptor.deprecations.md) | ConfigDeprecationProvider | Provider for the [ConfigDeprecation](./kibana-plugin-server.configdeprecation.md) to apply to the plugin configuration. | +| [exposeToBrowser](./kibana-plugin-server.pluginconfigdescriptor.exposetobrowser.md) | {
[P in keyof T]?: boolean;
} | List of configuration properties that will be available on the client-side plugin. | +| [schema](./kibana-plugin-server.pluginconfigdescriptor.schema.md) | PluginConfigSchema<T> | Schema to use to validate the plugin configuration.[PluginConfigSchema](./kibana-plugin-server.pluginconfigschema.md) | + diff --git a/docs/development/core/server/kibana-plugin-server.pluginmanifest.md b/docs/development/core/server/kibana-plugin-server.pluginmanifest.md index 4a9498f0e9fab..9bb208a809b22 100644 --- a/docs/development/core/server/kibana-plugin-server.pluginmanifest.md +++ b/docs/development/core/server/kibana-plugin-server.pluginmanifest.md @@ -12,6 +12,10 @@ Describes the set of required and optional properties plugin can define in its m export interface PluginManifest ``` +## Remarks + +Should never be used in code outside of Core but is exported for documentation purposes. + ## Properties | Property | Type | Description | @@ -25,7 +29,3 @@ export interface PluginManifest | [ui](./kibana-plugin-server.pluginmanifest.ui.md) | boolean | Specifies whether plugin includes some client/browser specific functionality that should be included into client bundle via public/ui_plugin.js file. | | [version](./kibana-plugin-server.pluginmanifest.version.md) | string | Version of the plugin. | -## Remarks - -Should never be used in code outside of Core but is exported for documentation purposes. - diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsclient.md b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.md index 17d29bb912c83..e68486ecff874 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsclient.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.md @@ -10,6 +10,10 @@ export declare class SavedObjectsClient ``` +## Remarks + +The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `SavedObjectsClient` class. + ## Properties | Property | Modifiers | Type | Description | @@ -30,7 +34,3 @@ export declare class SavedObjectsClient | [get(type, id, options)](./kibana-plugin-server.savedobjectsclient.get.md) | | Retrieves a single object | | [update(type, id, attributes, options)](./kibana-plugin-server.savedobjectsclient.update.md) | | Updates an SavedObject | -## Remarks - -The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `SavedObjectsClient` class. - diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.md b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.md index dd97b45f590e2..95bd817a43da6 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.md @@ -12,15 +12,6 @@ Saved Objects is Kibana's data persisentence mechanism allowing plugins to use E export interface SavedObjectsServiceSetup ``` -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [addClientWrapper](./kibana-plugin-server.savedobjectsservicesetup.addclientwrapper.md) | (priority: number, id: string, factory: SavedObjectsClientWrapperFactory<KibanaRequest>) => void | Add a client wrapper with the given priority. | -| [createInternalRepository](./kibana-plugin-server.savedobjectsservicesetup.createinternalrepository.md) | (extraTypes?: string[]) => ISavedObjectsRepository | Creates a [Saved Objects repository](./kibana-plugin-server.isavedobjectsrepository.md) that uses the internal Kibana user for authenticating with Elasticsearch. | -| [createScopedRepository](./kibana-plugin-server.savedobjectsservicesetup.createscopedrepository.md) | (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository | Creates a [Saved Objects repository](./kibana-plugin-server.isavedobjectsrepository.md) that uses the credentials from the passed in request to authenticate with Elasticsearch. | -| [setClientFactory](./kibana-plugin-server.savedobjectsservicesetup.setclientfactory.md) | (customClientFactory: SavedObjectsClientFactory<KibanaRequest>) => void | Set a default factory for creating Saved Objects clients. Only one client factory can be set, subsequent calls to this method will fail. | - ## Remarks Note: The Saved Object setup API's should only be used for creating and registering client wrappers. Constructing a Saved Objects client or repository for use within your own plugin won't have any of the registered wrappers applied and is considered an anti-pattern. Use the Saved Objects client from the [SavedObjectsServiceStart\#getScopedClient](./kibana-plugin-server.savedobjectsservicestart.md) method or the [route handler context](./kibana-plugin-server.requesthandlercontext.md) instead. @@ -33,3 +24,12 @@ import {SavedObjectsClient, CoreSetup} from 'src/core/server'; export class Plugin() { setup: (core: CoreSetup) => { core.savedObjects.setClientFactory(({request: KibanaRequest}) => { return new SavedObjectsClient(core.savedObjects.scopedRepository(request)); }) } } +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [addClientWrapper](./kibana-plugin-server.savedobjectsservicesetup.addclientwrapper.md) | (priority: number, id: string, factory: SavedObjectsClientWrapperFactory<KibanaRequest>) => void | Add a client wrapper with the given priority. | +| [createInternalRepository](./kibana-plugin-server.savedobjectsservicesetup.createinternalrepository.md) | (extraTypes?: string[]) => ISavedObjectsRepository | Creates a [Saved Objects repository](./kibana-plugin-server.isavedobjectsrepository.md) that uses the internal Kibana user for authenticating with Elasticsearch. | +| [createScopedRepository](./kibana-plugin-server.savedobjectsservicesetup.createscopedrepository.md) | (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository | Creates a [Saved Objects repository](./kibana-plugin-server.isavedobjectsrepository.md) that uses the credentials from the passed in request to authenticate with Elasticsearch. | +| [setClientFactory](./kibana-plugin-server.savedobjectsservicesetup.setclientfactory.md) | (customClientFactory: SavedObjectsClientFactory<KibanaRequest>) => void | Set a default factory for creating Saved Objects clients. Only one client factory can be set, subsequent calls to this method will fail. | + diff --git a/package.json b/package.json index a0f5dd3af14c0..5b90b9b08ea29 100644 --- a/package.json +++ b/package.json @@ -292,8 +292,8 @@ "@kbn/plugin-generator": "1.0.0", "@kbn/test": "1.0.0", "@kbn/utility-types": "1.0.0", - "@microsoft/api-documenter": "7.4.3", - "@microsoft/api-extractor": "7.4.2", + "@microsoft/api-documenter": "7.7.2", + "@microsoft/api-extractor": "7.7.0", "@percy/agent": "^0.11.0", "@testing-library/react": "^9.3.2", "@testing-library/react-hooks": "^3.2.1", diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index 4c5e22d20f779..7c5937af441a2 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -3102,11 +3102,25 @@ function times(n, ok, cb) { var fs = __webpack_require__(23) var polyfills = __webpack_require__(24) -var legacy = __webpack_require__(27) -var queue = [] +var legacy = __webpack_require__(26) +var clone = __webpack_require__(28) var util = __webpack_require__(29) +/* istanbul ignore next - node 0.x polyfill */ +var gracefulQueue +var previousSymbol + +/* istanbul ignore else - node 0.x polyfill */ +if (typeof Symbol === 'function' && typeof Symbol.for === 'function') { + gracefulQueue = Symbol.for('graceful-fs.queue') + // This is used in testing by future versions + previousSymbol = Symbol.for('graceful-fs.previous') +} else { + gracefulQueue = '___graceful-fs.queue' + previousSymbol = '___graceful-fs.previous' +} + function noop () {} var debug = noop @@ -3119,48 +3133,71 @@ else if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) console.error(m) } -if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) { - process.on('exit', function() { - debug(queue) - __webpack_require__(30).equal(queue.length, 0) +// Once time initialization +if (!global[gracefulQueue]) { + // This queue can be shared by multiple loaded instances + var queue = [] + Object.defineProperty(global, gracefulQueue, { + get: function() { + return queue + } }) -} -module.exports = patch(__webpack_require__(25)) -if (process.env.TEST_GRACEFUL_FS_GLOBAL_PATCH) { - module.exports = patch(fs) -} + // Patch fs.close/closeSync to shared queue version, because we need + // to retry() whenever a close happens *anywhere* in the program. + // This is essential when multiple graceful-fs instances are + // in play at the same time. + fs.close = (function (fs$close) { + function close (fd, cb) { + return fs$close.call(fs, fd, function (err) { + // This function uses the graceful-fs shared queue + if (!err) { + retry() + } -// Always patch fs.close/closeSync, because we want to -// retry() whenever a close happens *anywhere* in the program. -// This is essential when multiple graceful-fs instances are -// in play at the same time. -module.exports.close = -fs.close = (function (fs$close) { return function (fd, cb) { - return fs$close.call(fs, fd, function (err) { - if (!err) + if (typeof cb === 'function') + cb.apply(this, arguments) + }) + } + + Object.defineProperty(close, previousSymbol, { + value: fs$close + }) + return close + })(fs.close) + + fs.closeSync = (function (fs$closeSync) { + function closeSync (fd) { + // This function uses the graceful-fs shared queue + fs$closeSync.apply(fs, arguments) retry() + } - if (typeof cb === 'function') - cb.apply(this, arguments) - }) -}})(fs.close) + Object.defineProperty(closeSync, previousSymbol, { + value: fs$closeSync + }) + return closeSync + })(fs.closeSync) -module.exports.closeSync = -fs.closeSync = (function (fs$closeSync) { return function (fd) { - // Note that graceful-fs also retries when fs.closeSync() fails. - // Looks like a bug to me, although it's probably a harmless one. - var rval = fs$closeSync.apply(fs, arguments) - retry() - return rval -}})(fs.closeSync) + if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) { + process.on('exit', function() { + debug(global[gracefulQueue]) + __webpack_require__(30).equal(global[gracefulQueue].length, 0) + }) + } +} + +module.exports = patch(clone(fs)) +if (process.env.TEST_GRACEFUL_FS_GLOBAL_PATCH && !fs.__patched) { + module.exports = patch(fs) + fs.__patched = true; +} function patch (fs) { // Everything that references the open() function needs to be in here polyfills(fs) fs.gracefulify = patch - fs.FileReadStream = ReadStream; // Legacy name. - fs.FileWriteStream = WriteStream; // Legacy name. + fs.createReadStream = createReadStream fs.createWriteStream = createWriteStream var fs$readFile = fs.readFile @@ -3246,6 +3283,7 @@ function patch (fs) { if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) enqueue([go$readdir, [args]]) + else { if (typeof cb === 'function') cb.apply(this, arguments) @@ -3265,15 +3303,61 @@ function patch (fs) { } var fs$ReadStream = fs.ReadStream - ReadStream.prototype = Object.create(fs$ReadStream.prototype) - ReadStream.prototype.open = ReadStream$open + if (fs$ReadStream) { + ReadStream.prototype = Object.create(fs$ReadStream.prototype) + ReadStream.prototype.open = ReadStream$open + } var fs$WriteStream = fs.WriteStream - WriteStream.prototype = Object.create(fs$WriteStream.prototype) - WriteStream.prototype.open = WriteStream$open + if (fs$WriteStream) { + WriteStream.prototype = Object.create(fs$WriteStream.prototype) + WriteStream.prototype.open = WriteStream$open + } - fs.ReadStream = ReadStream - fs.WriteStream = WriteStream + Object.defineProperty(fs, 'ReadStream', { + get: function () { + return ReadStream + }, + set: function (val) { + ReadStream = val + }, + enumerable: true, + configurable: true + }) + Object.defineProperty(fs, 'WriteStream', { + get: function () { + return WriteStream + }, + set: function (val) { + WriteStream = val + }, + enumerable: true, + configurable: true + }) + + // legacy names + var FileReadStream = ReadStream + Object.defineProperty(fs, 'FileReadStream', { + get: function () { + return FileReadStream + }, + set: function (val) { + FileReadStream = val + }, + enumerable: true, + configurable: true + }) + var FileWriteStream = WriteStream + Object.defineProperty(fs, 'FileWriteStream', { + get: function () { + return FileWriteStream + }, + set: function (val) { + FileWriteStream = val + }, + enumerable: true, + configurable: true + }) function ReadStream (path, options) { if (this instanceof ReadStream) @@ -3319,11 +3403,11 @@ function patch (fs) { } function createReadStream (path, options) { - return new ReadStream(path, options) + return new fs.ReadStream(path, options) } function createWriteStream (path, options) { - return new WriteStream(path, options) + return new fs.WriteStream(path, options) } var fs$open = fs.open @@ -3352,11 +3436,11 @@ function patch (fs) { function enqueue (elem) { debug('ENQUEUE', elem[0].name, elem[1]) - queue.push(elem) + global[gracefulQueue].push(elem) } function retry () { - var elem = queue.shift() + var elem = global[gracefulQueue].shift() if (elem) { debug('RETRY', elem[0].name, elem[1]) elem[0].apply(null, elem[1]) @@ -3374,8 +3458,7 @@ module.exports = require("fs"); /* 24 */ /***/ (function(module, exports, __webpack_require__) { -var fs = __webpack_require__(25) -var constants = __webpack_require__(26) +var constants = __webpack_require__(25) var origCwd = process.cwd var cwd = null @@ -3492,20 +3575,26 @@ function patch (fs) { } // if read() returns EAGAIN, then just try it again. - fs.read = (function (fs$read) { return function (fd, buffer, offset, length, position, callback_) { - var callback - if (callback_ && typeof callback_ === 'function') { - var eagCounter = 0 - callback = function (er, _, __) { - if (er && er.code === 'EAGAIN' && eagCounter < 10) { - eagCounter ++ - return fs$read.call(fs, fd, buffer, offset, length, position, callback) + fs.read = (function (fs$read) { + function read (fd, buffer, offset, length, position, callback_) { + var callback + if (callback_ && typeof callback_ === 'function') { + var eagCounter = 0 + callback = function (er, _, __) { + if (er && er.code === 'EAGAIN' && eagCounter < 10) { + eagCounter ++ + return fs$read.call(fs, fd, buffer, offset, length, position, callback) + } + callback_.apply(this, arguments) } - callback_.apply(this, arguments) } + return fs$read.call(fs, fd, buffer, offset, length, position, callback) } - return fs$read.call(fs, fd, buffer, offset, length, position, callback) - }})(fs.read) + + // This ensures `util.promisify` works as it does for native `fs.read`. + read.__proto__ = fs$read + return read + })(fs.read) fs.readSync = (function (fs$readSync) { return function (fd, buffer, offset, length, position) { var eagCounter = 0 @@ -3521,73 +3610,36 @@ function patch (fs) { } } }})(fs.readSync) -} - -function patchLchmod (fs) { - fs.lchmod = function (path, mode, callback) { - fs.open( path - , constants.O_WRONLY | constants.O_SYMLINK - , mode - , function (err, fd) { - if (err) { - if (callback) callback(err) - return - } - // prefer to return the chmod error, if one occurs, - // but still try to close, and report closing errors if they occur. - fs.fchmod(fd, mode, function (err) { - fs.close(fd, function(err2) { - if (callback) callback(err || err2) - }) - }) - }) - } - - fs.lchmodSync = function (path, mode) { - var fd = fs.openSync(path, constants.O_WRONLY | constants.O_SYMLINK, mode) - - // prefer to return the chmod error, if one occurs, - // but still try to close, and report closing errors if they occur. - var threw = true - var ret - try { - ret = fs.fchmodSync(fd, mode) - threw = false - } finally { - if (threw) { - try { - fs.closeSync(fd) - } catch (er) {} - } else { - fs.closeSync(fd) - } - } - return ret - } -} -function patchLutimes (fs) { - if (constants.hasOwnProperty("O_SYMLINK")) { - fs.lutimes = function (path, at, mt, cb) { - fs.open(path, constants.O_SYMLINK, function (er, fd) { - if (er) { - if (cb) cb(er) + function patchLchmod (fs) { + fs.lchmod = function (path, mode, callback) { + fs.open( path + , constants.O_WRONLY | constants.O_SYMLINK + , mode + , function (err, fd) { + if (err) { + if (callback) callback(err) return } - fs.futimes(fd, at, mt, function (er) { - fs.close(fd, function (er2) { - if (cb) cb(er || er2) + // prefer to return the chmod error, if one occurs, + // but still try to close, and report closing errors if they occur. + fs.fchmod(fd, mode, function (err) { + fs.close(fd, function(err2) { + if (callback) callback(err || err2) }) }) }) } - fs.lutimesSync = function (path, at, mt) { - var fd = fs.openSync(path, constants.O_SYMLINK) - var ret + fs.lchmodSync = function (path, mode) { + var fd = fs.openSync(path, constants.O_WRONLY | constants.O_SYMLINK, mode) + + // prefer to return the chmod error, if one occurs, + // but still try to close, and report closing errors if they occur. var threw = true + var ret try { - ret = fs.futimesSync(fd, at, mt) + ret = fs.fchmodSync(fd, mode) threw = false } finally { if (threw) { @@ -3600,151 +3652,167 @@ function patchLutimes (fs) { } return ret } + } - } else { - fs.lutimes = function (_a, _b, _c, cb) { if (cb) process.nextTick(cb) } - fs.lutimesSync = function () {} + function patchLutimes (fs) { + if (constants.hasOwnProperty("O_SYMLINK")) { + fs.lutimes = function (path, at, mt, cb) { + fs.open(path, constants.O_SYMLINK, function (er, fd) { + if (er) { + if (cb) cb(er) + return + } + fs.futimes(fd, at, mt, function (er) { + fs.close(fd, function (er2) { + if (cb) cb(er || er2) + }) + }) + }) + } + + fs.lutimesSync = function (path, at, mt) { + var fd = fs.openSync(path, constants.O_SYMLINK) + var ret + var threw = true + try { + ret = fs.futimesSync(fd, at, mt) + threw = false + } finally { + if (threw) { + try { + fs.closeSync(fd) + } catch (er) {} + } else { + fs.closeSync(fd) + } + } + return ret + } + + } else { + fs.lutimes = function (_a, _b, _c, cb) { if (cb) process.nextTick(cb) } + fs.lutimesSync = function () {} + } } -} -function chmodFix (orig) { - if (!orig) return orig - return function (target, mode, cb) { - return orig.call(fs, target, mode, function (er) { - if (chownErOk(er)) er = null - if (cb) cb.apply(this, arguments) - }) + function chmodFix (orig) { + if (!orig) return orig + return function (target, mode, cb) { + return orig.call(fs, target, mode, function (er) { + if (chownErOk(er)) er = null + if (cb) cb.apply(this, arguments) + }) + } } -} -function chmodFixSync (orig) { - if (!orig) return orig - return function (target, mode) { - try { - return orig.call(fs, target, mode) - } catch (er) { - if (!chownErOk(er)) throw er + function chmodFixSync (orig) { + if (!orig) return orig + return function (target, mode) { + try { + return orig.call(fs, target, mode) + } catch (er) { + if (!chownErOk(er)) throw er + } } } -} -function chownFix (orig) { - if (!orig) return orig - return function (target, uid, gid, cb) { - return orig.call(fs, target, uid, gid, function (er) { - if (chownErOk(er)) er = null - if (cb) cb.apply(this, arguments) - }) + function chownFix (orig) { + if (!orig) return orig + return function (target, uid, gid, cb) { + return orig.call(fs, target, uid, gid, function (er) { + if (chownErOk(er)) er = null + if (cb) cb.apply(this, arguments) + }) + } } -} -function chownFixSync (orig) { - if (!orig) return orig - return function (target, uid, gid) { - try { - return orig.call(fs, target, uid, gid) - } catch (er) { - if (!chownErOk(er)) throw er + function chownFixSync (orig) { + if (!orig) return orig + return function (target, uid, gid) { + try { + return orig.call(fs, target, uid, gid) + } catch (er) { + if (!chownErOk(er)) throw er + } } } -} + function statFix (orig) { + if (!orig) return orig + // Older versions of Node erroneously returned signed integers for + // uid + gid. + return function (target, options, cb) { + if (typeof options === 'function') { + cb = options + options = null + } + function callback (er, stats) { + if (stats) { + if (stats.uid < 0) stats.uid += 0x100000000 + if (stats.gid < 0) stats.gid += 0x100000000 + } + if (cb) cb.apply(this, arguments) + } + return options ? orig.call(fs, target, options, callback) + : orig.call(fs, target, callback) + } + } -function statFix (orig) { - if (!orig) return orig - // Older versions of Node erroneously returned signed integers for - // uid + gid. - return function (target, cb) { - return orig.call(fs, target, function (er, stats) { - if (!stats) return cb.apply(this, arguments) + function statFixSync (orig) { + if (!orig) return orig + // Older versions of Node erroneously returned signed integers for + // uid + gid. + return function (target, options) { + var stats = options ? orig.call(fs, target, options) + : orig.call(fs, target) if (stats.uid < 0) stats.uid += 0x100000000 if (stats.gid < 0) stats.gid += 0x100000000 - if (cb) cb.apply(this, arguments) - }) + return stats; + } } -} -function statFixSync (orig) { - if (!orig) return orig - // Older versions of Node erroneously returned signed integers for - // uid + gid. - return function (target) { - var stats = orig.call(fs, target) - if (stats.uid < 0) stats.uid += 0x100000000 - if (stats.gid < 0) stats.gid += 0x100000000 - return stats; - } -} + // ENOSYS means that the fs doesn't support the op. Just ignore + // that, because it doesn't matter. + // + // if there's no getuid, or if getuid() is something other + // than 0, and the error is EINVAL or EPERM, then just ignore + // it. + // + // This specific case is a silent failure in cp, install, tar, + // and most other unix tools that manage permissions. + // + // When running as root, or if other types of errors are + // encountered, then it's strict. + function chownErOk (er) { + if (!er) + return true -// ENOSYS means that the fs doesn't support the op. Just ignore -// that, because it doesn't matter. -// -// if there's no getuid, or if getuid() is something other -// than 0, and the error is EINVAL or EPERM, then just ignore -// it. -// -// This specific case is a silent failure in cp, install, tar, -// and most other unix tools that manage permissions. -// -// When running as root, or if other types of errors are -// encountered, then it's strict. -function chownErOk (er) { - if (!er) - return true + if (er.code === "ENOSYS") + return true - if (er.code === "ENOSYS") - return true + var nonroot = !process.getuid || process.getuid() !== 0 + if (nonroot) { + if (er.code === "EINVAL" || er.code === "EPERM") + return true + } - var nonroot = !process.getuid || process.getuid() !== 0 - if (nonroot) { - if (er.code === "EINVAL" || er.code === "EPERM") - return true + return false } - - return false } /***/ }), /* 25 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var fs = __webpack_require__(23) - -module.exports = clone(fs) - -function clone (obj) { - if (obj === null || typeof obj !== 'object') - return obj - - if (obj instanceof Object) - var copy = { __proto__: obj.__proto__ } - else - var copy = Object.create(null) - - Object.getOwnPropertyNames(obj).forEach(function (key) { - Object.defineProperty(copy, key, Object.getOwnPropertyDescriptor(obj, key)) - }) - - return copy -} - - -/***/ }), -/* 26 */ /***/ (function(module, exports) { module.exports = require("constants"); /***/ }), -/* 27 */ +/* 26 */ /***/ (function(module, exports, __webpack_require__) { -var Stream = __webpack_require__(28).Stream +var Stream = __webpack_require__(27).Stream module.exports = legacy @@ -3865,11 +3933,37 @@ function legacy (fs) { /***/ }), -/* 28 */ +/* 27 */ /***/ (function(module, exports) { module.exports = require("stream"); +/***/ }), +/* 28 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +module.exports = clone + +function clone (obj) { + if (obj === null || typeof obj !== 'object') + return obj + + if (obj instanceof Object) + var copy = { __proto__: obj.__proto__ } + else + var copy = Object.create(null) + + Object.getOwnPropertyNames(obj).forEach(function (key) { + Object.defineProperty(copy, key, Object.getOwnPropertyDescriptor(obj, key)) + }) + + return copy +} + + /***/ }), /* 29 */ /***/ (function(module, exports) { @@ -34687,7 +34781,7 @@ module.exports = eos; "use strict"; -const {PassThrough: PassThroughStream} = __webpack_require__(28); +const {PassThrough: PassThroughStream} = __webpack_require__(27); module.exports = options => { options = {...options}; @@ -34747,7 +34841,7 @@ module.exports = options => { "use strict"; -const { PassThrough } = __webpack_require__(28); +const { PassThrough } = __webpack_require__(27); module.exports = function (/*streams...*/) { var sources = [] @@ -37771,7 +37865,7 @@ function retry () { /* 431 */ /***/ (function(module, exports, __webpack_require__) { -var constants = __webpack_require__(26) +var constants = __webpack_require__(25) var origCwd = process.cwd var cwd = null @@ -38106,7 +38200,7 @@ function patch (fs) { /* 432 */ /***/ (function(module, exports, __webpack_require__) { -var Stream = __webpack_require__(28).Stream +var Stream = __webpack_require__(27).Stream module.exports = legacy @@ -41741,7 +41835,7 @@ var url = __webpack_require__(454); var http = __webpack_require__(472); var https = __webpack_require__(473); var assert = __webpack_require__(30); -var Writable = __webpack_require__(28).Writable; +var Writable = __webpack_require__(27).Writable; var debug = __webpack_require__(475)("follow-redirects"); // RFC7231§4.2.1: Of the request methods defined by this specification, @@ -52721,7 +52815,7 @@ function retry () { /* 547 */ /***/ (function(module, exports, __webpack_require__) { -var constants = __webpack_require__(26) +var constants = __webpack_require__(25) var origCwd = process.cwd var cwd = null @@ -53056,7 +53150,7 @@ function patch (fs) { /* 548 */ /***/ (function(module, exports, __webpack_require__) { -var Stream = __webpack_require__(28).Stream +var Stream = __webpack_require__(27).Stream module.exports = legacy @@ -53720,7 +53814,7 @@ function retry () { /***/ (function(module, exports, __webpack_require__) { var fs = __webpack_require__(553) -var constants = __webpack_require__(26) +var constants = __webpack_require__(25) var origCwd = process.cwd var cwd = null @@ -54083,7 +54177,7 @@ function clone (obj) { /* 554 */ /***/ (function(module, exports, __webpack_require__) { -var Stream = __webpack_require__(28).Stream +var Stream = __webpack_require__(27).Stream module.exports = legacy @@ -57230,7 +57324,7 @@ module.exports.cli = __webpack_require__(576); -var stream = __webpack_require__(28); +var stream = __webpack_require__(27); var util = __webpack_require__(29); var fs = __webpack_require__(23); @@ -57428,7 +57522,7 @@ function lineMerger(host) { /* 573 */ /***/ (function(module, exports, __webpack_require__) { -var Stream = __webpack_require__(28) +var Stream = __webpack_require__(27) // through // @@ -57542,7 +57636,7 @@ function through (write, end, opts) { /* 574 */ /***/ (function(module, exports, __webpack_require__) { -var Stream = __webpack_require__(28) +var Stream = __webpack_require__(27) var writeMethods = ["write", "end", "destroy"] var readMethods = ["resume", "pause"] var readEvents = ["data", "close"] @@ -60403,7 +60497,7 @@ exports.default = Lockfile; /* 17 */ /***/ (function(module, exports) { -module.exports = __webpack_require__(28); +module.exports = __webpack_require__(27); /***/ }), /* 18 */, @@ -69246,7 +69340,7 @@ module.exports = (...arguments_) => { * Copyright (c) 2014-2016 Teambition * Licensed under the MIT license. */ -const Stream = __webpack_require__(28) +const Stream = __webpack_require__(27) const PassThrough = Stream.PassThrough const slice = Array.prototype.slice @@ -73646,7 +73740,7 @@ exports.default = ProviderAsync; "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const stream_1 = __webpack_require__(28); +const stream_1 = __webpack_require__(27); const fsStat = __webpack_require__(623); const fsWalk = __webpack_require__(628); const reader_1 = __webpack_require__(648); @@ -74647,7 +74741,7 @@ exports.default = Reader; "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const stream_1 = __webpack_require__(28); +const stream_1 = __webpack_require__(27); const async_1 = __webpack_require__(630); class StreamProvider { constructor(_root, _settings) { @@ -75081,7 +75175,7 @@ exports.default = EntryTransformer; "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const stream_1 = __webpack_require__(28); +const stream_1 = __webpack_require__(27); const stream_2 = __webpack_require__(622); const provider_1 = __webpack_require__(649); class ProviderStream extends provider_1.default { @@ -76121,7 +76215,7 @@ module.exports = path => { "use strict"; -const {Transform} = __webpack_require__(28); +const {Transform} = __webpack_require__(27); class ObjectTransform extends Transform { constructor() { @@ -76522,7 +76616,7 @@ function retry () { /* 665 */ /***/ (function(module, exports, __webpack_require__) { -var constants = __webpack_require__(26) +var constants = __webpack_require__(25) var origCwd = process.cwd var cwd = null @@ -76870,7 +76964,7 @@ function patch (fs) { /* 666 */ /***/ (function(module, exports, __webpack_require__) { -var Stream = __webpack_require__(28).Stream +var Stream = __webpack_require__(27).Stream module.exports = legacy @@ -101698,7 +101792,7 @@ function readdirSync (dir, options, internalOptions) { "use strict"; -const Readable = __webpack_require__(28).Readable; +const Readable = __webpack_require__(27).Readable; const EventEmitter = __webpack_require__(379).EventEmitter; const path = __webpack_require__(16); const normalizeOptions = __webpack_require__(868); @@ -103099,7 +103193,7 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var stream = __webpack_require__(28); +var stream = __webpack_require__(27); var fsStat = __webpack_require__(883); var fs_1 = __webpack_require__(887); var FileSystemStream = /** @class */ (function (_super) { @@ -103333,7 +103427,7 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var stream = __webpack_require__(28); +var stream = __webpack_require__(27); var readdir = __webpack_require__(865); var reader_1 = __webpack_require__(878); var fs_stream_1 = __webpack_require__(882); diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 48293eda44d33..35359c5d6c417 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -1,1980 +1,1979 @@ -## API Report File for "kibana" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -import Boom from 'boom'; -import { BulkIndexDocumentsParams } from 'elasticsearch'; -import { CatAliasesParams } from 'elasticsearch'; -import { CatAllocationParams } from 'elasticsearch'; -import { CatCommonParams } from 'elasticsearch'; -import { CatFielddataParams } from 'elasticsearch'; -import { CatHealthParams } from 'elasticsearch'; -import { CatHelpParams } from 'elasticsearch'; -import { CatIndicesParams } from 'elasticsearch'; -import { CatRecoveryParams } from 'elasticsearch'; -import { CatSegmentsParams } from 'elasticsearch'; -import { CatShardsParams } from 'elasticsearch'; -import { CatSnapshotsParams } from 'elasticsearch'; -import { CatTasksParams } from 'elasticsearch'; -import { CatThreadPoolParams } from 'elasticsearch'; -import { ClearScrollParams } from 'elasticsearch'; -import { Client } from 'elasticsearch'; -import { ClusterAllocationExplainParams } from 'elasticsearch'; -import { ClusterGetSettingsParams } from 'elasticsearch'; -import { ClusterHealthParams } from 'elasticsearch'; -import { ClusterPendingTasksParams } from 'elasticsearch'; -import { ClusterPutSettingsParams } from 'elasticsearch'; -import { ClusterRerouteParams } from 'elasticsearch'; -import { ClusterStateParams } from 'elasticsearch'; -import { ClusterStatsParams } from 'elasticsearch'; -import { ConfigOptions } from 'elasticsearch'; -import { CountParams } from 'elasticsearch'; -import { CreateDocumentParams } from 'elasticsearch'; -import { DeleteDocumentByQueryParams } from 'elasticsearch'; -import { DeleteDocumentParams } from 'elasticsearch'; -import { DeleteScriptParams } from 'elasticsearch'; -import { DeleteTemplateParams } from 'elasticsearch'; -import { DetailedPeerCertificate } from 'tls'; -import { Duration } from 'moment'; -import { ExistsParams } from 'elasticsearch'; -import { ExplainParams } from 'elasticsearch'; -import { FieldStatsParams } from 'elasticsearch'; -import { GenericParams } from 'elasticsearch'; -import { GetParams } from 'elasticsearch'; -import { GetResponse } from 'elasticsearch'; -import { GetScriptParams } from 'elasticsearch'; -import { GetSourceParams } from 'elasticsearch'; -import { GetTemplateParams } from 'elasticsearch'; -import { IncomingHttpHeaders } from 'http'; -import { IndexDocumentParams } from 'elasticsearch'; -import { IndicesAnalyzeParams } from 'elasticsearch'; -import { IndicesClearCacheParams } from 'elasticsearch'; -import { IndicesCloseParams } from 'elasticsearch'; -import { IndicesCreateParams } from 'elasticsearch'; -import { IndicesDeleteAliasParams } from 'elasticsearch'; -import { IndicesDeleteParams } from 'elasticsearch'; -import { IndicesDeleteTemplateParams } from 'elasticsearch'; -import { IndicesExistsAliasParams } from 'elasticsearch'; -import { IndicesExistsParams } from 'elasticsearch'; -import { IndicesExistsTemplateParams } from 'elasticsearch'; -import { IndicesExistsTypeParams } from 'elasticsearch'; -import { IndicesFlushParams } from 'elasticsearch'; -import { IndicesFlushSyncedParams } from 'elasticsearch'; -import { IndicesForcemergeParams } from 'elasticsearch'; -import { IndicesGetAliasParams } from 'elasticsearch'; -import { IndicesGetFieldMappingParams } from 'elasticsearch'; -import { IndicesGetMappingParams } from 'elasticsearch'; -import { IndicesGetParams } from 'elasticsearch'; -import { IndicesGetSettingsParams } from 'elasticsearch'; -import { IndicesGetTemplateParams } from 'elasticsearch'; -import { IndicesGetUpgradeParams } from 'elasticsearch'; -import { IndicesOpenParams } from 'elasticsearch'; -import { IndicesPutAliasParams } from 'elasticsearch'; -import { IndicesPutMappingParams } from 'elasticsearch'; -import { IndicesPutSettingsParams } from 'elasticsearch'; -import { IndicesPutTemplateParams } from 'elasticsearch'; -import { IndicesRecoveryParams } from 'elasticsearch'; -import { IndicesRefreshParams } from 'elasticsearch'; -import { IndicesRolloverParams } from 'elasticsearch'; -import { IndicesSegmentsParams } from 'elasticsearch'; -import { IndicesShardStoresParams } from 'elasticsearch'; -import { IndicesShrinkParams } from 'elasticsearch'; -import { IndicesStatsParams } from 'elasticsearch'; -import { IndicesUpdateAliasesParams } from 'elasticsearch'; -import { IndicesUpgradeParams } from 'elasticsearch'; -import { IndicesValidateQueryParams } from 'elasticsearch'; -import { InfoParams } from 'elasticsearch'; -import { IngestDeletePipelineParams } from 'elasticsearch'; -import { IngestGetPipelineParams } from 'elasticsearch'; -import { IngestPutPipelineParams } from 'elasticsearch'; -import { IngestSimulateParams } from 'elasticsearch'; -import { KibanaConfigType } from 'src/core/server/kibana_config'; -import { Logger as Logger_2 } from 'src/core/server/logging'; -import { MGetParams } from 'elasticsearch'; -import { MGetResponse } from 'elasticsearch'; -import { MSearchParams } from 'elasticsearch'; -import { MSearchResponse } from 'elasticsearch'; -import { MSearchTemplateParams } from 'elasticsearch'; -import { MTermVectorsParams } from 'elasticsearch'; -import { NodesHotThreadsParams } from 'elasticsearch'; -import { NodesInfoParams } from 'elasticsearch'; -import { NodesStatsParams } from 'elasticsearch'; -import { ObjectType } from '@kbn/config-schema'; -import { Observable } from 'rxjs'; -import { PeerCertificate } from 'tls'; -import { PingParams } from 'elasticsearch'; -import { PutScriptParams } from 'elasticsearch'; -import { PutTemplateParams } from 'elasticsearch'; -import { Readable } from 'stream'; -import { RecursiveReadonly as RecursiveReadonly_2 } from 'kibana/public'; -import { ReindexParams } from 'elasticsearch'; -import { ReindexRethrottleParams } from 'elasticsearch'; -import { RenderSearchTemplateParams } from 'elasticsearch'; -import { Request } from 'hapi'; -import { ResponseObject } from 'hapi'; -import { ResponseToolkit } from 'hapi'; -import { SchemaTypeError } from '@kbn/config-schema'; -import { ScrollParams } from 'elasticsearch'; -import { SearchParams } from 'elasticsearch'; -import { SearchResponse } from 'elasticsearch'; -import { SearchShardsParams } from 'elasticsearch'; -import { SearchTemplateParams } from 'elasticsearch'; -import { Server } from 'hapi'; -import { ShallowPromise } from '@kbn/utility-types'; -import { SnapshotCreateParams } from 'elasticsearch'; -import { SnapshotCreateRepositoryParams } from 'elasticsearch'; -import { SnapshotDeleteParams } from 'elasticsearch'; -import { SnapshotDeleteRepositoryParams } from 'elasticsearch'; -import { SnapshotGetParams } from 'elasticsearch'; -import { SnapshotGetRepositoryParams } from 'elasticsearch'; -import { SnapshotRestoreParams } from 'elasticsearch'; -import { SnapshotStatusParams } from 'elasticsearch'; -import { SnapshotVerifyRepositoryParams } from 'elasticsearch'; -import { Stream } from 'stream'; -import { SuggestParams } from 'elasticsearch'; -import { TasksCancelParams } from 'elasticsearch'; -import { TasksGetParams } from 'elasticsearch'; -import { TasksListParams } from 'elasticsearch'; -import { TermvectorsParams } from 'elasticsearch'; -import { Type } from '@kbn/config-schema'; -import { TypeOf } from '@kbn/config-schema'; -import { UpdateDocumentByQueryParams } from 'elasticsearch'; -import { UpdateDocumentParams } from 'elasticsearch'; -import { Url } from 'url'; - -// @public (undocumented) -export interface APICaller { - // (undocumented) - (endpoint: 'cluster.state', params: ClusterStateParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'bulk', params: BulkIndexDocumentsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'count', params: CountParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'create', params: CreateDocumentParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'delete', params: DeleteDocumentParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'deleteByQuery', params: DeleteDocumentByQueryParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'deleteScript', params: DeleteScriptParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'deleteTemplate', params: DeleteTemplateParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'exists', params: ExistsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'explain', params: ExplainParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'fieldStats', params: FieldStatsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'get', params: GetParams, options?: CallAPIOptions): Promise>; - // (undocumented) - (endpoint: 'getScript', params: GetScriptParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'getSource', params: GetSourceParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'getTemplate', params: GetTemplateParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'index', params: IndexDocumentParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'info', params: InfoParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'mget', params: MGetParams, options?: CallAPIOptions): Promise>; - // (undocumented) - (endpoint: 'msearch', params: MSearchParams, options?: CallAPIOptions): Promise>; - // (undocumented) - (endpoint: 'msearchTemplate', params: MSearchTemplateParams, options?: CallAPIOptions): Promise>; - // (undocumented) - (endpoint: 'mtermvectors', params: MTermVectorsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'ping', params: PingParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'putScript', params: PutScriptParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'putTemplate', params: PutTemplateParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'reindex', params: ReindexParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'reindexRethrottle', params: ReindexRethrottleParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'renderSearchTemplate', params: RenderSearchTemplateParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'scroll', params: ScrollParams, options?: CallAPIOptions): Promise>; - // (undocumented) - (endpoint: 'search', params: SearchParams, options?: CallAPIOptions): Promise>; - // (undocumented) - (endpoint: 'searchShards', params: SearchShardsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'searchTemplate', params: SearchTemplateParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'suggest', params: SuggestParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'termvectors', params: TermvectorsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'update', params: UpdateDocumentParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'updateByQuery', params: UpdateDocumentByQueryParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.aliases', params: CatAliasesParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.allocation', params: CatAllocationParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.count', params: CatAllocationParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.fielddata', params: CatFielddataParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.health', params: CatHealthParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.help', params: CatHelpParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.indices', params: CatIndicesParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.master', params: CatCommonParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.nodeattrs', params: CatCommonParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.nodes', params: CatCommonParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.pendingTasks', params: CatCommonParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.plugins', params: CatCommonParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.recovery', params: CatRecoveryParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.repositories', params: CatCommonParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.segments', params: CatSegmentsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.shards', params: CatShardsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.snapshots', params: CatSnapshotsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.tasks', params: CatTasksParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.threadPool', params: CatThreadPoolParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cluster.allocationExplain', params: ClusterAllocationExplainParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cluster.getSettings', params: ClusterGetSettingsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cluster.health', params: ClusterHealthParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cluster.pendingTasks', params: ClusterPendingTasksParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cluster.putSettings', params: ClusterPutSettingsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cluster.reroute', params: ClusterRerouteParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'clearScroll', params: ClearScrollParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cluster.stats', params: ClusterStatsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.analyze', params: IndicesAnalyzeParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.clearCache', params: IndicesClearCacheParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.close', params: IndicesCloseParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.create', params: IndicesCreateParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.delete', params: IndicesDeleteParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.deleteAlias', params: IndicesDeleteAliasParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.deleteTemplate', params: IndicesDeleteTemplateParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.exists', params: IndicesExistsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.existsAlias', params: IndicesExistsAliasParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.existsTemplate', params: IndicesExistsTemplateParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.existsType', params: IndicesExistsTypeParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.flush', params: IndicesFlushParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.flushSynced', params: IndicesFlushSyncedParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.forcemerge', params: IndicesForcemergeParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.get', params: IndicesGetParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.getAlias', params: IndicesGetAliasParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.getFieldMapping', params: IndicesGetFieldMappingParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.getMapping', params: IndicesGetMappingParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.getSettings', params: IndicesGetSettingsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.getTemplate', params: IndicesGetTemplateParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.getUpgrade', params: IndicesGetUpgradeParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.open', params: IndicesOpenParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.putAlias', params: IndicesPutAliasParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.putMapping', params: IndicesPutMappingParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.putSettings', params: IndicesPutSettingsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.putTemplate', params: IndicesPutTemplateParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.recovery', params: IndicesRecoveryParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.refresh', params: IndicesRefreshParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.rollover', params: IndicesRolloverParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.segments', params: IndicesSegmentsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.shardStores', params: IndicesShardStoresParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.shrink', params: IndicesShrinkParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.stats', params: IndicesStatsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.updateAliases', params: IndicesUpdateAliasesParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.upgrade', params: IndicesUpgradeParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.validateQuery', params: IndicesValidateQueryParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'ingest.deletePipeline', params: IngestDeletePipelineParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'ingest.getPipeline', params: IngestGetPipelineParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'ingest.putPipeline', params: IngestPutPipelineParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'ingest.simulate', params: IngestSimulateParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'nodes.hotThreads', params: NodesHotThreadsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'nodes.info', params: NodesInfoParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'nodes.stats', params: NodesStatsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'snapshot.create', params: SnapshotCreateParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'snapshot.createRepository', params: SnapshotCreateRepositoryParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'snapshot.delete', params: SnapshotDeleteParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'snapshot.deleteRepository', params: SnapshotDeleteRepositoryParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'snapshot.get', params: SnapshotGetParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'snapshot.getRepository', params: SnapshotGetRepositoryParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'snapshot.restore', params: SnapshotRestoreParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'snapshot.status', params: SnapshotStatusParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'snapshot.verifyRepository', params: SnapshotVerifyRepositoryParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'tasks.cancel', params: TasksCancelParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'tasks.get', params: TasksGetParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'tasks.list', params: TasksListParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'transport.request', clientParams: AssistantAPIClientParams, options?: CallAPIOptions): Promise; - // (undocumented) - (endpoint: 'transport.request', clientParams: DeprecationAPIClientParams, options?: CallAPIOptions): Promise; - // (undocumented) - (endpoint: string, clientParams?: Record, options?: CallAPIOptions): Promise; -} - -// @public (undocumented) -export interface AssistanceAPIResponse { - // (undocumented) - indices: { - [indexName: string]: { - action_required: MIGRATION_ASSISTANCE_INDEX_ACTION; - }; - }; -} - -// @public (undocumented) -export interface AssistantAPIClientParams extends GenericParams { - // (undocumented) - method: 'GET'; - // (undocumented) - path: '/_migration/assistance'; -} - -// @public (undocumented) -export interface Authenticated extends AuthResultParams { - // (undocumented) - type: AuthResultType.authenticated; -} - -// @public -export type AuthenticationHandler = (request: KibanaRequest, response: LifecycleResponseFactory, toolkit: AuthToolkit) => AuthResult | IKibanaResponse | Promise; - -// @public -export type AuthHeaders = Record; - -// @public (undocumented) -export type AuthResult = Authenticated; - -// @public -export interface AuthResultParams { - requestHeaders?: AuthHeaders; - responseHeaders?: AuthHeaders; - state?: Record; -} - -// @public (undocumented) -export enum AuthResultType { - // (undocumented) - authenticated = "authenticated" -} - -// @public -export enum AuthStatus { - authenticated = "authenticated", - unauthenticated = "unauthenticated", - unknown = "unknown" -} - -// @public -export interface AuthToolkit { - authenticated: (data?: AuthResultParams) => AuthResult; -} - -// @public -export class BasePath { - // @internal - constructor(serverBasePath?: string); - get: (request: KibanaRequest | LegacyRequest) => string; - prepend: (path: string) => string; - remove: (path: string) => string; - readonly serverBasePath: string; - set: (request: KibanaRequest | LegacyRequest, requestSpecificBasePath: string) => void; -} - -// Warning: (ae-forgotten-export) The symbol "BootstrapArgs" needs to be exported by the entry point index.d.ts -// -// @internal (undocumented) -export function bootstrap({ configs, cliArgs, applyConfigOverrides, features, }: BootstrapArgs): Promise; - -// @public -export interface CallAPIOptions { - signal?: AbortSignal; - wrap401Errors?: boolean; -} - -// @public -export interface Capabilities { - [key: string]: Record>; - catalogue: Record; - management: { - [sectionId: string]: Record; - }; - navLinks: Record; -} - -// @public -export type CapabilitiesProvider = () => Partial; - -// @public -export interface CapabilitiesSetup { - registerProvider(provider: CapabilitiesProvider): void; - registerSwitcher(switcher: CapabilitiesSwitcher): void; -} - -// @public -export interface CapabilitiesStart { - resolveCapabilities(request: KibanaRequest): Promise; -} - -// @public -export type CapabilitiesSwitcher = (request: KibanaRequest, uiCapabilities: Capabilities) => Partial | Promise>; - -// @public -export class ClusterClient implements IClusterClient { - constructor(config: ElasticsearchClientConfig, log: Logger, getAuthHeaders?: GetAuthHeaders); - asScoped(request?: KibanaRequest | LegacyRequest | FakeRequest): IScopedClusterClient; - callAsInternalUser: APICaller; - close(): void; - } - -// @public -export type ConfigDeprecation = (config: Record, fromPath: string, logger: ConfigDeprecationLogger) => Record; - -// @public -export interface ConfigDeprecationFactory { - rename(oldKey: string, newKey: string): ConfigDeprecation; - renameFromRoot(oldKey: string, newKey: string): ConfigDeprecation; - unused(unusedKey: string): ConfigDeprecation; - unusedFromRoot(unusedKey: string): ConfigDeprecation; -} - -// @public -export type ConfigDeprecationLogger = (message: string) => void; - -// @public -export type ConfigDeprecationProvider = (factory: ConfigDeprecationFactory) => ConfigDeprecation[]; - -// @public (undocumented) -export type ConfigPath = string | string[]; - -// @internal (undocumented) -export class ConfigService { - // Warning: (ae-forgotten-export) The symbol "RawConfigurationProvider" needs to be exported by the entry point index.d.ts - // Warning: (ae-forgotten-export) The symbol "Env" needs to be exported by the entry point index.d.ts - constructor(rawConfigProvider: RawConfigurationProvider, env: Env, logger: LoggerFactory); - addDeprecationProvider(path: ConfigPath, provider: ConfigDeprecationProvider): void; - atPath(path: ConfigPath): Observable; - // Warning: (ae-forgotten-export) The symbol "Config" needs to be exported by the entry point index.d.ts - getConfig$(): Observable; - // (undocumented) - getUnusedPaths(): Promise; - // (undocumented) - getUsedPaths(): Promise; - // (undocumented) - isEnabledAtPath(path: ConfigPath): Promise; - optionalAtPath(path: ConfigPath): Observable; - setSchema(path: ConfigPath, schema: Type): Promise; - validate(): Promise; - } - -// @public -export interface ContextSetup { - createContextContainer>(): IContextContainer; -} - -// @internal (undocumented) -export type CoreId = symbol; - -// @public -export interface CoreSetup { - // (undocumented) - capabilities: CapabilitiesSetup; - // (undocumented) - context: ContextSetup; - // (undocumented) - elasticsearch: ElasticsearchServiceSetup; - // (undocumented) - http: HttpServiceSetup; - // (undocumented) - savedObjects: SavedObjectsServiceSetup; - // (undocumented) - uiSettings: UiSettingsServiceSetup; - // (undocumented) - uuid: UuidServiceSetup; -} - -// @public -export interface CoreStart { - // (undocumented) - capabilities: CapabilitiesStart; - // (undocumented) - savedObjects: SavedObjectsServiceStart; - // (undocumented) - uiSettings: UiSettingsServiceStart; -} - -// @public -export class CspConfig implements ICspConfig { - // @internal - constructor(rawCspConfig?: Partial>); - // (undocumented) - static readonly DEFAULT: CspConfig; - // (undocumented) - readonly header: string; - // (undocumented) - readonly rules: string[]; - // (undocumented) - readonly strict: boolean; - // (undocumented) - readonly warnLegacyBrowsers: boolean; -} - -// @public -export interface CustomHttpResponseOptions { - body?: T; - headers?: ResponseHeaders; - // (undocumented) - statusCode: number; -} - -// @public (undocumented) -export interface DeprecationAPIClientParams extends GenericParams { - // (undocumented) - method: 'GET'; - // (undocumented) - path: '/_migration/deprecations'; -} - -// @public (undocumented) -export interface DeprecationAPIResponse { - // (undocumented) - cluster_settings: DeprecationInfo[]; - // (undocumented) - index_settings: IndexSettingsDeprecationInfo; - // (undocumented) - ml_settings: DeprecationInfo[]; - // (undocumented) - node_settings: DeprecationInfo[]; -} - -// @public (undocumented) -export interface DeprecationInfo { - // (undocumented) - details?: string; - // (undocumented) - level: MIGRATION_DEPRECATION_LEVEL; - // (undocumented) - message: string; - // (undocumented) - url: string; -} - -// @public -export interface DiscoveredPlugin { - readonly configPath: ConfigPath; - readonly id: PluginName; - readonly optionalPlugins: readonly PluginName[]; - readonly requiredPlugins: readonly PluginName[]; -} - -// Warning: (ae-forgotten-export) The symbol "ElasticsearchConfig" needs to be exported by the entry point index.d.ts -// -// @public (undocumented) -export type ElasticsearchClientConfig = Pick & Pick & { - pingTimeout?: ElasticsearchConfig['pingTimeout'] | ConfigOptions['pingTimeout']; - requestTimeout?: ElasticsearchConfig['requestTimeout'] | ConfigOptions['requestTimeout']; - sniffInterval?: ElasticsearchConfig['sniffInterval'] | ConfigOptions['sniffInterval']; - ssl?: Partial; -}; - -// @public (undocumented) -export interface ElasticsearchError extends Boom { - // (undocumented) - [code]?: string; -} - -// @public -export class ElasticsearchErrorHelpers { - // (undocumented) - static decorateNotAuthorizedError(error: Error, reason?: string): ElasticsearchError; - // (undocumented) - static isNotAuthorizedError(error: any): error is ElasticsearchError; -} - -// @public (undocumented) -export interface ElasticsearchServiceSetup { - readonly adminClient$: Observable; - readonly createClient: (type: string, clientConfig?: Partial) => IClusterClient; - readonly dataClient$: Observable; -} - -// @public (undocumented) -export interface EnvironmentMode { - // (undocumented) - dev: boolean; - // (undocumented) - name: 'development' | 'production'; - // (undocumented) - prod: boolean; -} - -// @public -export interface ErrorHttpResponseOptions { - body?: ResponseError; - headers?: ResponseHeaders; -} - -// @public -export interface FakeRequest { - headers: Headers; -} - -// @public -export type GetAuthHeaders = (request: KibanaRequest | LegacyRequest) => AuthHeaders | undefined; - -// @public -export type GetAuthState = (request: KibanaRequest | LegacyRequest) => { - status: AuthStatus; - state: unknown; -}; - -// @public -export type HandlerContextType> = T extends HandlerFunction ? U : never; - -// @public -export type HandlerFunction = (context: T, ...args: any[]) => any; - -// @public -export type HandlerParameters> = T extends (context: any, ...args: infer U) => any ? U : never; - -// @public -export type Headers = { - [header in KnownHeaders]?: string | string[] | undefined; -} & { - [header: string]: string | string[] | undefined; -}; - -// @public -export interface HttpResponseOptions { - body?: HttpResponsePayload; - headers?: ResponseHeaders; -} - -// @public -export type HttpResponsePayload = undefined | string | Record | Buffer | Stream; - -// @public -export interface HttpServiceSetup { - basePath: IBasePath; - createCookieSessionStorageFactory: (cookieOptions: SessionStorageCookieOptions) => Promise>; - createRouter: () => IRouter; - csp: ICspConfig; - isTlsEnabled: boolean; - registerAuth: (handler: AuthenticationHandler) => void; - registerOnPostAuth: (handler: OnPostAuthHandler) => void; - registerOnPreAuth: (handler: OnPreAuthHandler) => void; - registerOnPreResponse: (handler: OnPreResponseHandler) => void; - registerRouteHandlerContext: (contextName: T, provider: RequestHandlerContextProvider) => RequestHandlerContextContainer; -} - -// @public (undocumented) -export interface HttpServiceStart { - isListening: (port: number) => boolean; -} - -// @public -export type IBasePath = Pick; - -// @public -export type IClusterClient = Pick; - -// @public -export interface IContextContainer> { - createHandler(pluginOpaqueId: PluginOpaqueId, handler: THandler): (...rest: HandlerParameters) => ShallowPromise>; - registerContext>(pluginOpaqueId: PluginOpaqueId, contextName: TContextName, provider: IContextProvider): this; -} - -// @public -export type IContextProvider, TContextName extends keyof HandlerContextType> = (context: Partial>, ...rest: HandlerParameters) => Promise[TContextName]> | HandlerContextType[TContextName]; - -// @public -export interface ICspConfig { - readonly header: string; - readonly rules: string[]; - readonly strict: boolean; - readonly warnLegacyBrowsers: boolean; -} - -// @public -export interface IKibanaResponse { - // (undocumented) - readonly options: HttpResponseOptions; - // (undocumented) - readonly payload?: T; - // (undocumented) - readonly status: number; -} - -// @public -export interface IKibanaSocket { - readonly authorizationError?: Error; - readonly authorized?: boolean; - // (undocumented) - getPeerCertificate(detailed: true): DetailedPeerCertificate | null; - // (undocumented) - getPeerCertificate(detailed: false): PeerCertificate | null; - getPeerCertificate(detailed?: boolean): PeerCertificate | DetailedPeerCertificate | null; -} - -// @public (undocumented) -export interface IndexSettingsDeprecationInfo { - // (undocumented) - [indexName: string]: DeprecationInfo[]; -} - -// @public (undocumented) -export interface IRenderOptions { - includeUserSettings?: boolean; -} - -// @public -export interface IRouter { - delete: RouteRegistrar<'delete'>; - get: RouteRegistrar<'get'>; - // Warning: (ae-forgotten-export) The symbol "RouterRoute" needs to be exported by the entry point index.d.ts - // - // @internal - getRoutes: () => RouterRoute[]; - handleLegacyErrors: (handler: RequestHandler) => RequestHandler; - patch: RouteRegistrar<'patch'>; - post: RouteRegistrar<'post'>; - put: RouteRegistrar<'put'>; - routerPath: string; -} - -// @public -export type IsAuthenticated = (request: KibanaRequest | LegacyRequest) => boolean; - -// @public -export type ISavedObjectsRepository = Pick; - -// @public -export type IScopedClusterClient = Pick; - -// @public (undocumented) -export interface IScopedRenderingClient { - render(options?: IRenderOptions): Promise; -} - -// @public -export interface IUiSettingsClient { - get: (key: string) => Promise; - getAll: () => Promise>; - getRegistered: () => Readonly>; - getUserProvided: () => Promise>>; - isOverridden: (key: string) => boolean; - remove: (key: string) => Promise; - removeMany: (keys: string[]) => Promise; - set: (key: string, value: any) => Promise; - setMany: (changes: Record) => Promise; -} - -// @public -export class KibanaRequest { - // @internal (undocumented) - protected readonly [requestSymbol]: Request; - constructor(request: Request, params: Params, query: Query, body: Body, withoutSecretHeaders: boolean); - // (undocumented) - readonly body: Body; - // Warning: (ae-forgotten-export) The symbol "RouteValidator" needs to be exported by the entry point index.d.ts - // - // @internal - static from(req: Request, routeSchemas?: RouteValidator | RouteValidatorFullConfig, withoutSecretHeaders?: boolean): KibanaRequest; - readonly headers: Headers; - // (undocumented) - readonly params: Params; - // (undocumented) - readonly query: Query; - readonly route: RecursiveReadonly>; - // (undocumented) - readonly socket: IKibanaSocket; - readonly url: Url; - } - -// @public -export interface KibanaRequestRoute { - // (undocumented) - method: Method; - // (undocumented) - options: KibanaRequestRouteOptions; - // (undocumented) - path: string; -} - -// @public -export type KibanaRequestRouteOptions = Method extends 'get' | 'options' ? Required, 'body'>> : Required>; - -// @public -export type KibanaResponseFactory = typeof kibanaResponseFactory; - -// @public -export const kibanaResponseFactory: { - custom: | Buffer | Stream | { - message: string | Error; - attributes?: Record | undefined; - } | undefined>(options: CustomHttpResponseOptions) => KibanaResponse; - badRequest: (options?: ErrorHttpResponseOptions) => KibanaResponse; - unauthorized: (options?: ErrorHttpResponseOptions) => KibanaResponse; - forbidden: (options?: ErrorHttpResponseOptions) => KibanaResponse; - notFound: (options?: ErrorHttpResponseOptions) => KibanaResponse; - conflict: (options?: ErrorHttpResponseOptions) => KibanaResponse; - internalError: (options?: ErrorHttpResponseOptions) => KibanaResponse; - customError: (options: CustomHttpResponseOptions) => KibanaResponse; - redirected: (options: RedirectResponseOptions) => KibanaResponse | Buffer | Stream>; - ok: (options?: HttpResponseOptions) => KibanaResponse | Buffer | Stream>; - accepted: (options?: HttpResponseOptions) => KibanaResponse | Buffer | Stream>; - noContent: (options?: HttpResponseOptions) => KibanaResponse; -}; - -// Warning: (ae-forgotten-export) The symbol "KnownKeys" needs to be exported by the entry point index.d.ts -// -// @public -export type KnownHeaders = KnownKeys; - -// @internal @deprecated -export interface LegacyConfig { - // (undocumented) - get(key?: string): T; - // (undocumented) - has(key: string): boolean; - // (undocumented) - set(key: string, value: any): void; - // Warning: (ae-forgotten-export) The symbol "LegacyVars" needs to be exported by the entry point index.d.ts - // - // (undocumented) - set(config: LegacyVars): void; -} - -// Warning: (ae-forgotten-export) The symbol "ILegacyInternals" needs to be exported by the entry point index.d.ts -// -// @internal @deprecated (undocumented) -export class LegacyInternals implements ILegacyInternals { - constructor(uiExports: LegacyUiExports, config: LegacyConfig, server: Server); - // (undocumented) - getInjectedUiAppVars(id: string): Promise>; - // (undocumented) - getVars(id: string, request: LegacyRequest, injected?: LegacyVars): Promise>; - private get defaultVars(); - // Warning: (ae-forgotten-export) The symbol "VarsInjector" needs to be exported by the entry point index.d.ts - // - // (undocumented) - injectUiAppVars(id: string, injector: VarsInjector): void; - } - -// @internal @deprecated (undocumented) -export interface LegacyRenderOptions extends IRenderOptions { - app?: { - getId(): string; - }; - vars?: Record; -} - -// @public @deprecated (undocumented) -export interface LegacyRequest extends Request { -} - -// Warning: (ae-forgotten-export) The symbol "LegacyPlugins" needs to be exported by the entry point index.d.ts -// -// @internal @deprecated (undocumented) -export interface LegacyServiceDiscoverPlugins extends LegacyPlugins { - // (undocumented) - pluginExtendedConfig: LegacyConfig; - // (undocumented) - settings: LegacyVars; -} - -// @public @deprecated (undocumented) -export interface LegacyServiceSetupDeps { - // Warning: (ae-forgotten-export) The symbol "LegacyCoreSetup" needs to be exported by the entry point index.d.ts - // - // (undocumented) - core: LegacyCoreSetup; - // (undocumented) - plugins: Record; -} - -// @public @deprecated (undocumented) -export interface LegacyServiceStartDeps { - // Warning: (ae-forgotten-export) The symbol "LegacyCoreStart" needs to be exported by the entry point index.d.ts - // - // (undocumented) - core: LegacyCoreStart; - // (undocumented) - plugins: Record; -} - -// Warning: (ae-forgotten-export) The symbol "SavedObjectsLegacyUiExports" needs to be exported by the entry point index.d.ts -// -// @internal @deprecated (undocumented) -export type LegacyUiExports = SavedObjectsLegacyUiExports & { - defaultInjectedVarProviders?: VarsProvider[]; - injectedVarsReplacers?: VarsReplacer[]; - navLinkSpecs?: LegacyNavLinkSpec[] | null; - uiAppSpecs?: Array; - unknown?: [{ - pluginSpec: LegacyPluginSpec; - type: unknown; - }]; -}; - -// Warning: (ae-forgotten-export) The symbol "lifecycleResponseFactory" needs to be exported by the entry point index.d.ts -// -// @public -export type LifecycleResponseFactory = typeof lifecycleResponseFactory; - -// @public -export interface Logger { - debug(message: string, meta?: LogMeta): void; - error(errorOrMessage: string | Error, meta?: LogMeta): void; - fatal(errorOrMessage: string | Error, meta?: LogMeta): void; - get(...childContextPaths: string[]): Logger; - info(message: string, meta?: LogMeta): void; - // @internal (undocumented) - log(record: LogRecord): void; - trace(message: string, meta?: LogMeta): void; - warn(errorOrMessage: string | Error, meta?: LogMeta): void; -} - -// @public -export interface LoggerFactory { - get(...contextParts: string[]): Logger; -} - -// @internal -export class LogLevel { - // (undocumented) - static readonly All: LogLevel; - // (undocumented) - static readonly Debug: LogLevel; - // (undocumented) - static readonly Error: LogLevel; - // (undocumented) - static readonly Fatal: LogLevel; - static fromId(level: LogLevelId): LogLevel; - // Warning: (ae-forgotten-export) The symbol "LogLevelId" needs to be exported by the entry point index.d.ts - // - // (undocumented) - readonly id: LogLevelId; - // (undocumented) - static readonly Info: LogLevel; - // (undocumented) - static readonly Off: LogLevel; - supports(level: LogLevel): boolean; - // (undocumented) - static readonly Trace: LogLevel; - // (undocumented) - readonly value: number; - // (undocumented) - static readonly Warn: LogLevel; -} - -// @public -export interface LogMeta { - // (undocumented) - [key: string]: any; -} - -// @internal -export interface LogRecord { - // (undocumented) - context: string; - // (undocumented) - error?: Error; - // (undocumented) - level: LogLevel; - // (undocumented) - message: string; - // (undocumented) - meta?: { - [name: string]: any; - }; - // (undocumented) - timestamp: Date; -} - -// @public (undocumented) -export type MIGRATION_ASSISTANCE_INDEX_ACTION = 'upgrade' | 'reindex'; - -// @public (undocumented) -export type MIGRATION_DEPRECATION_LEVEL = 'none' | 'info' | 'warning' | 'critical'; - -// @public -export type MutatingOperationRefreshSetting = boolean | 'wait_for'; - -// Warning: (ae-forgotten-export) The symbol "OnPostAuthResult" needs to be exported by the entry point index.d.ts -// -// @public -export type OnPostAuthHandler = (request: KibanaRequest, response: LifecycleResponseFactory, toolkit: OnPostAuthToolkit) => OnPostAuthResult | KibanaResponse | Promise; - -// @public -export interface OnPostAuthToolkit { - next: () => OnPostAuthResult; -} - -// Warning: (ae-forgotten-export) The symbol "OnPreAuthResult" needs to be exported by the entry point index.d.ts -// -// @public -export type OnPreAuthHandler = (request: KibanaRequest, response: LifecycleResponseFactory, toolkit: OnPreAuthToolkit) => OnPreAuthResult | KibanaResponse | Promise; - -// @public -export interface OnPreAuthToolkit { - next: () => OnPreAuthResult; - rewriteUrl: (url: string) => OnPreAuthResult; -} - -// @public -export interface OnPreResponseExtensions { - headers?: ResponseHeaders; -} - -// Warning: (ae-forgotten-export) The symbol "OnPreResponseResult" needs to be exported by the entry point index.d.ts -// -// @public -export type OnPreResponseHandler = (request: KibanaRequest, preResponse: OnPreResponseInfo, toolkit: OnPreResponseToolkit) => OnPreResponseResult | Promise; - -// @public -export interface OnPreResponseInfo { - // (undocumented) - statusCode: number; -} - -// @public -export interface OnPreResponseToolkit { - next: (responseExtensions?: OnPreResponseExtensions) => OnPreResponseResult; -} - -// @public (undocumented) -export interface PackageInfo { - // (undocumented) - branch: string; - // (undocumented) - buildNum: number; - // (undocumented) - buildSha: string; - // (undocumented) - dist: boolean; - // (undocumented) - version: string; -} - -// @public -export interface Plugin { - // (undocumented) - setup(core: CoreSetup, plugins: TPluginsSetup): TSetup | Promise; - // (undocumented) - start(core: CoreStart, plugins: TPluginsStart): TStart | Promise; - // (undocumented) - stop?(): void; -} - -// @public -export interface PluginConfigDescriptor { - deprecations?: ConfigDeprecationProvider; - exposeToBrowser?: { - [P in keyof T]?: boolean; - }; - schema: PluginConfigSchema; -} - -// @public -export type PluginConfigSchema = Type; - -// @public -export type PluginInitializer = (core: PluginInitializerContext) => Plugin; - -// @public -export interface PluginInitializerContext { - // (undocumented) - config: { - legacy: { - globalConfig$: Observable; - }; - create: () => Observable; - createIfExists: () => Observable; - }; - // (undocumented) - env: { - mode: EnvironmentMode; - packageInfo: Readonly; - }; - // (undocumented) - logger: LoggerFactory; - // (undocumented) - opaqueId: PluginOpaqueId; -} - -// @public -export interface PluginManifest { - readonly configPath: ConfigPath; - readonly id: PluginName; - readonly kibanaVersion: string; - readonly optionalPlugins: readonly PluginName[]; - readonly requiredPlugins: readonly PluginName[]; - readonly server: boolean; - readonly ui: boolean; - readonly version: string; -} - -// @public -export type PluginName = string; - -// @public (undocumented) -export type PluginOpaqueId = symbol; - -// @public (undocumented) -export interface PluginsServiceSetup { - // (undocumented) - contracts: Map; - // (undocumented) - uiPlugins: { - internal: Map; - public: Map; - browserConfigs: Map>; - }; -} - -// @public (undocumented) -export interface PluginsServiceStart { - // (undocumented) - contracts: Map; -} - -// Warning: (ae-forgotten-export) The symbol "RecursiveReadonlyArray" needs to be exported by the entry point index.d.ts -// -// @public (undocumented) -export type RecursiveReadonly = T extends (...args: any[]) => any ? T : T extends any[] ? RecursiveReadonlyArray : T extends object ? Readonly<{ - [K in keyof T]: RecursiveReadonly; -}> : T; - -// @public -export type RedirectResponseOptions = HttpResponseOptions & { - headers: { - location: string; - }; -}; - -// @internal (undocumented) -export interface RenderingServiceSetup { - render(request: R, uiSettings: IUiSettingsClient, options?: R extends LegacyRequest ? LegacyRenderOptions : IRenderOptions): Promise; -} - -// @public -export type RequestHandler

= (context: RequestHandlerContext, request: KibanaRequest, response: KibanaResponseFactory) => IKibanaResponse | Promise>; - -// @public -export interface RequestHandlerContext { - // (undocumented) - core: { - rendering: IScopedRenderingClient; - savedObjects: { - client: SavedObjectsClientContract; - }; - elasticsearch: { - dataClient: IScopedClusterClient; - adminClient: IScopedClusterClient; - }; - uiSettings: { - client: IUiSettingsClient; - }; - }; -} - -// @public -export type RequestHandlerContextContainer = IContextContainer>; - -// @public -export type RequestHandlerContextProvider = IContextProvider, TContextName>; - -// @public -export type ResponseError = string | Error | { - message: string | Error; - attributes?: ResponseErrorAttributes; -}; - -// @public -export type ResponseErrorAttributes = Record; - -// @public -export type ResponseHeaders = { - [header in KnownHeaders]?: string | string[]; -} & { - [header: string]: string | string[]; -}; - -// @public -export interface RouteConfig { - options?: RouteConfigOptions; - path: string; - validate: RouteValidatorFullConfig | false; -} - -// @public -export interface RouteConfigOptions { - authRequired?: boolean; - body?: Method extends 'get' | 'options' ? undefined : RouteConfigOptionsBody; - tags?: readonly string[]; -} - -// @public -export interface RouteConfigOptionsBody { - accepts?: RouteContentType | RouteContentType[] | string | string[]; - maxBytes?: number; - output?: typeof validBodyOutput[number]; - parse?: boolean | 'gunzip'; -} - -// @public -export type RouteContentType = 'application/json' | 'application/*+json' | 'application/octet-stream' | 'application/x-www-form-urlencoded' | 'multipart/form-data' | 'text/*'; - -// @public -export type RouteMethod = 'get' | 'post' | 'put' | 'delete' | 'patch' | 'options'; - -// @public -export type RouteRegistrar = (route: RouteConfig, handler: RequestHandler) => void; - -// @public -export class RouteValidationError extends SchemaTypeError { - constructor(error: Error | string, path?: string[]); -} - -// @public -export type RouteValidationFunction = (data: any, validationResult: RouteValidationResultFactory) => { - value: T; - error?: never; -} | { - value?: never; - error: RouteValidationError; -}; - -// @public -export interface RouteValidationResultFactory { - // (undocumented) - badRequest: (error: Error | string, path?: string[]) => { - error: RouteValidationError; - }; - // (undocumented) - ok: (value: T) => { - value: T; - }; -} - -// @public -export type RouteValidationSpec = ObjectType | Type | RouteValidationFunction; - -// @public -export interface RouteValidatorConfig { - body?: RouteValidationSpec; - params?: RouteValidationSpec

; - query?: RouteValidationSpec; -} - -// @public -export type RouteValidatorFullConfig = RouteValidatorConfig & RouteValidatorOptions; - -// @public -export interface RouteValidatorOptions { - unsafe?: { - params?: boolean; - query?: boolean; - body?: boolean; - }; -} - -// @public (undocumented) -export interface SavedObject { - attributes: T; - // (undocumented) - error?: { - message: string; - statusCode: number; - }; - id: string; - migrationVersion?: SavedObjectsMigrationVersion; - references: SavedObjectReference[]; - type: string; - updated_at?: string; - version?: string; -} - -// @public -export type SavedObjectAttribute = SavedObjectAttributeSingle | SavedObjectAttributeSingle[]; - -// @public -export interface SavedObjectAttributes { - // (undocumented) - [key: string]: SavedObjectAttribute; -} - -// @public -export type SavedObjectAttributeSingle = string | number | boolean | null | undefined | SavedObjectAttributes; - -// @public -export interface SavedObjectReference { - // (undocumented) - id: string; - // (undocumented) - name: string; - // (undocumented) - type: string; -} - -// @public (undocumented) -export interface SavedObjectsBaseOptions { - namespace?: string; -} - -// @public (undocumented) -export interface SavedObjectsBulkCreateObject { - // (undocumented) - attributes: T; - // (undocumented) - id?: string; - migrationVersion?: SavedObjectsMigrationVersion; - // (undocumented) - references?: SavedObjectReference[]; - // (undocumented) - type: string; -} - -// @public (undocumented) -export interface SavedObjectsBulkGetObject { - fields?: string[]; - // (undocumented) - id: string; - // (undocumented) - type: string; -} - -// @public (undocumented) -export interface SavedObjectsBulkResponse { - // (undocumented) - saved_objects: Array>; -} - -// @public (undocumented) -export interface SavedObjectsBulkResponse { - // (undocumented) - saved_objects: Array>; -} - -// @public (undocumented) -export interface SavedObjectsBulkUpdateObject extends Pick { - attributes: Partial; - id: string; - type: string; -} - -// @public (undocumented) -export interface SavedObjectsBulkUpdateOptions extends SavedObjectsBaseOptions { - refresh?: MutatingOperationRefreshSetting; -} - -// @public (undocumented) -export interface SavedObjectsBulkUpdateResponse { - // (undocumented) - saved_objects: Array>; -} - -// @public (undocumented) -export class SavedObjectsClient { - // @internal - constructor(repository: ISavedObjectsRepository); - bulkCreate(objects: Array>, options?: SavedObjectsCreateOptions): Promise>; - bulkGet(objects?: SavedObjectsBulkGetObject[], options?: SavedObjectsBaseOptions): Promise>; - bulkUpdate(objects: Array>, options?: SavedObjectsBulkUpdateOptions): Promise>; - create(type: string, attributes: T, options?: SavedObjectsCreateOptions): Promise>; - delete(type: string, id: string, options?: SavedObjectsDeleteOptions): Promise<{}>; - // (undocumented) - errors: typeof SavedObjectsErrorHelpers; - // (undocumented) - static errors: typeof SavedObjectsErrorHelpers; - find(options: SavedObjectsFindOptions): Promise>; - get(type: string, id: string, options?: SavedObjectsBaseOptions): Promise>; - update(type: string, id: string, attributes: Partial, options?: SavedObjectsUpdateOptions): Promise>; -} - -// @public -export type SavedObjectsClientContract = Pick; - -// @public -export type SavedObjectsClientFactory = ({ request, }: { - request: Request; -}) => SavedObjectsClientContract; - -// @public -export interface SavedObjectsClientProviderOptions { - // (undocumented) - excludedWrappers?: string[]; -} - -// @public -export type SavedObjectsClientWrapperFactory = (options: SavedObjectsClientWrapperOptions) => SavedObjectsClientContract; - -// @public -export interface SavedObjectsClientWrapperOptions { - // (undocumented) - client: SavedObjectsClientContract; - // (undocumented) - request: Request; -} - -// @public (undocumented) -export interface SavedObjectsCreateOptions extends SavedObjectsBaseOptions { - id?: string; - migrationVersion?: SavedObjectsMigrationVersion; - overwrite?: boolean; - // (undocumented) - references?: SavedObjectReference[]; - refresh?: MutatingOperationRefreshSetting; -} - -// @public (undocumented) -export interface SavedObjectsDeleteByNamespaceOptions extends SavedObjectsBaseOptions { - refresh?: MutatingOperationRefreshSetting; -} - -// @public (undocumented) -export interface SavedObjectsDeleteOptions extends SavedObjectsBaseOptions { - refresh?: MutatingOperationRefreshSetting; -} - -// @public (undocumented) -export class SavedObjectsErrorHelpers { - // (undocumented) - static createBadRequestError(reason?: string): DecoratedError; - // (undocumented) - static createEsAutoCreateIndexError(): DecoratedError; - // (undocumented) - static createGenericNotFoundError(type?: string | null, id?: string | null): DecoratedError; - // (undocumented) - static createInvalidVersionError(versionInput?: string): DecoratedError; - // (undocumented) - static createUnsupportedTypeError(type: string): DecoratedError; - // (undocumented) - static decorateBadRequestError(error: Error, reason?: string): DecoratedError; - // (undocumented) - static decorateConflictError(error: Error, reason?: string): DecoratedError; - // (undocumented) - static decorateEsUnavailableError(error: Error, reason?: string): DecoratedError; - // (undocumented) - static decorateForbiddenError(error: Error, reason?: string): DecoratedError; - // (undocumented) - static decorateGeneralError(error: Error, reason?: string): DecoratedError; - // (undocumented) - static decorateNotAuthorizedError(error: Error, reason?: string): DecoratedError; - // (undocumented) - static decorateRequestEntityTooLargeError(error: Error, reason?: string): DecoratedError; - // (undocumented) - static isBadRequestError(error: Error | DecoratedError): boolean; - // (undocumented) - static isConflictError(error: Error | DecoratedError): boolean; - // (undocumented) - static isEsAutoCreateIndexError(error: Error | DecoratedError): boolean; - // (undocumented) - static isEsUnavailableError(error: Error | DecoratedError): boolean; - // (undocumented) - static isForbiddenError(error: Error | DecoratedError): boolean; - // (undocumented) - static isInvalidVersionError(error: Error | DecoratedError): boolean; - // (undocumented) - static isNotAuthorizedError(error: Error | DecoratedError): boolean; - // (undocumented) - static isNotFoundError(error: Error | DecoratedError): boolean; - // (undocumented) - static isRequestEntityTooLargeError(error: Error | DecoratedError): boolean; - // Warning: (ae-forgotten-export) The symbol "DecoratedError" needs to be exported by the entry point index.d.ts - // - // (undocumented) - static isSavedObjectsClientError(error: any): error is DecoratedError; -} - -// @public -export interface SavedObjectsExportOptions { - excludeExportDetails?: boolean; - exportSizeLimit: number; - includeReferencesDeep?: boolean; - namespace?: string; - objects?: Array<{ - id: string; - type: string; - }>; - savedObjectsClient: SavedObjectsClientContract; - search?: string; - types?: string[]; -} - -// @public -export interface SavedObjectsExportResultDetails { - exportedCount: number; - missingRefCount: number; - missingReferences: Array<{ - id: string; - type: string; - }>; -} - -// @public (undocumented) -export interface SavedObjectsFindOptions extends SavedObjectsBaseOptions { - // (undocumented) - defaultSearchOperator?: 'AND' | 'OR'; - fields?: string[]; - // (undocumented) - filter?: string; - // (undocumented) - hasReference?: { - type: string; - id: string; - }; - // (undocumented) - page?: number; - // (undocumented) - perPage?: number; - search?: string; - searchFields?: string[]; - // (undocumented) - sortField?: string; - // (undocumented) - sortOrder?: string; - // (undocumented) - type: string | string[]; -} - -// @public -export interface SavedObjectsFindResponse { - // (undocumented) - page: number; - // (undocumented) - per_page: number; - // (undocumented) - saved_objects: Array>; - // (undocumented) - total: number; -} - -// @public -export interface SavedObjectsImportConflictError { - // (undocumented) - type: 'conflict'; -} - -// @public -export interface SavedObjectsImportError { - // (undocumented) - error: SavedObjectsImportConflictError | SavedObjectsImportUnsupportedTypeError | SavedObjectsImportMissingReferencesError | SavedObjectsImportUnknownError; - // (undocumented) - id: string; - // (undocumented) - title?: string; - // (undocumented) - type: string; -} - -// @public -export interface SavedObjectsImportMissingReferencesError { - // (undocumented) - blocking: Array<{ - type: string; - id: string; - }>; - // (undocumented) - references: Array<{ - type: string; - id: string; - }>; - // (undocumented) - type: 'missing_references'; -} - -// @public -export interface SavedObjectsImportOptions { - // (undocumented) - namespace?: string; - // (undocumented) - objectLimit: number; - // (undocumented) - overwrite: boolean; - // (undocumented) - readStream: Readable; - // (undocumented) - savedObjectsClient: SavedObjectsClientContract; - // (undocumented) - supportedTypes: string[]; -} - -// @public -export interface SavedObjectsImportResponse { - // (undocumented) - errors?: SavedObjectsImportError[]; - // (undocumented) - success: boolean; - // (undocumented) - successCount: number; -} - -// @public -export interface SavedObjectsImportRetry { - // (undocumented) - id: string; - // (undocumented) - overwrite: boolean; - // (undocumented) - replaceReferences: Array<{ - type: string; - from: string; - to: string; - }>; - // (undocumented) - type: string; -} - -// @public -export interface SavedObjectsImportUnknownError { - // (undocumented) - message: string; - // (undocumented) - statusCode: number; - // (undocumented) - type: 'unknown'; -} - -// @public -export interface SavedObjectsImportUnsupportedTypeError { - // (undocumented) - type: 'unsupported_type'; -} - -// @public (undocumented) -export interface SavedObjectsIncrementCounterOptions extends SavedObjectsBaseOptions { - // (undocumented) - migrationVersion?: SavedObjectsMigrationVersion; - refresh?: MutatingOperationRefreshSetting; -} - -// @internal @deprecated (undocumented) -export interface SavedObjectsLegacyService { - // Warning: (ae-forgotten-export) The symbol "SavedObjectsClientProvider" needs to be exported by the entry point index.d.ts - // - // (undocumented) - addScopedSavedObjectsClientWrapperFactory: SavedObjectsClientProvider['addClientWrapperFactory']; - // (undocumented) - getSavedObjectsRepository(...rest: any[]): any; - // (undocumented) - getScopedSavedObjectsClient: SavedObjectsClientProvider['getClient']; - // (undocumented) - importExport: { - objectLimit: number; - importSavedObjects(options: SavedObjectsImportOptions): Promise; - resolveImportErrors(options: SavedObjectsResolveImportErrorsOptions): Promise; - getSortedObjectsForExport(options: SavedObjectsExportOptions): Promise; - }; - // (undocumented) - SavedObjectsClient: typeof SavedObjectsClient; - // (undocumented) - schema: SavedObjectsSchema; - // (undocumented) - setScopedSavedObjectsClientFactory: SavedObjectsClientProvider['setClientFactory']; - // (undocumented) - types: string[]; -} - -// @public (undocumented) -export interface SavedObjectsMigrationLogger { - // (undocumented) - debug: (msg: string) => void; - // (undocumented) - info: (msg: string) => void; - // (undocumented) - warning: (msg: string) => void; -} - -// @public -export interface SavedObjectsMigrationVersion { - // (undocumented) - [pluginName: string]: string; -} - -// Warning: (ae-missing-release-tag) "RawDoc" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public -export interface SavedObjectsRawDoc { - // (undocumented) - _id: string; - // (undocumented) - _primary_term?: number; - // (undocumented) - _seq_no?: number; - // (undocumented) - _source: any; - // (undocumented) - _type?: string; -} - -// @public (undocumented) -export class SavedObjectsRepository { - bulkCreate(objects: Array>, options?: SavedObjectsCreateOptions): Promise>; - bulkGet(objects?: SavedObjectsBulkGetObject[], options?: SavedObjectsBaseOptions): Promise>; - bulkUpdate(objects: Array>, options?: SavedObjectsBulkUpdateOptions): Promise>; - create(type: string, attributes: T, options?: SavedObjectsCreateOptions): Promise>; - // Warning: (ae-forgotten-export) The symbol "KibanaMigrator" needs to be exported by the entry point index.d.ts - // - // @internal - static createRepository(migrator: KibanaMigrator, schema: SavedObjectsSchema, config: LegacyConfig, indexName: string, callCluster: APICaller, extraTypes?: string[], injectedConstructor?: any): any; - delete(type: string, id: string, options?: SavedObjectsDeleteOptions): Promise<{}>; - deleteByNamespace(namespace: string, options?: SavedObjectsDeleteByNamespaceOptions): Promise; - // (undocumented) - find({ search, defaultSearchOperator, searchFields, hasReference, page, perPage, sortField, sortOrder, fields, namespace, type, filter, }: SavedObjectsFindOptions): Promise>; - get(type: string, id: string, options?: SavedObjectsBaseOptions): Promise>; - incrementCounter(type: string, id: string, counterFieldName: string, options?: SavedObjectsIncrementCounterOptions): Promise<{ - id: string; - type: string; - updated_at: string; - references: any; - version: string; - attributes: any; - }>; - update(type: string, id: string, attributes: Partial, options?: SavedObjectsUpdateOptions): Promise>; - } - -// @public -export interface SavedObjectsResolveImportErrorsOptions { - // (undocumented) - namespace?: string; - // (undocumented) - objectLimit: number; - // (undocumented) - readStream: Readable; - // (undocumented) - retries: SavedObjectsImportRetry[]; - // (undocumented) - savedObjectsClient: SavedObjectsClientContract; - // (undocumented) - supportedTypes: string[]; -} - -// @internal (undocumented) -export class SavedObjectsSchema { - // Warning: (ae-forgotten-export) The symbol "SavedObjectsSchemaDefinition" needs to be exported by the entry point index.d.ts - constructor(schemaDefinition?: SavedObjectsSchemaDefinition); - // (undocumented) - getConvertToAliasScript(type: string): string | undefined; - // (undocumented) - getIndexForType(config: LegacyConfig, type: string): string | undefined; - // (undocumented) - isHiddenType(type: string): boolean; - // (undocumented) - isNamespaceAgnostic(type: string): boolean; -} - -// @internal (undocumented) -export class SavedObjectsSerializer { - constructor(schema: SavedObjectsSchema); - generateRawId(namespace: string | undefined, type: string, id?: string): string; - isRawSavedObject(rawDoc: SavedObjectsRawDoc): any; - // Warning: (ae-forgotten-export) The symbol "SanitizedSavedObjectDoc" needs to be exported by the entry point index.d.ts - rawToSavedObject(doc: SavedObjectsRawDoc): SanitizedSavedObjectDoc; - savedObjectToRaw(savedObj: SanitizedSavedObjectDoc): SavedObjectsRawDoc; - } - -// @public -export interface SavedObjectsServiceSetup { - addClientWrapper: (priority: number, id: string, factory: SavedObjectsClientWrapperFactory) => void; - createInternalRepository: (extraTypes?: string[]) => ISavedObjectsRepository; - createScopedRepository: (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository; - setClientFactory: (customClientFactory: SavedObjectsClientFactory) => void; -} - -// @public -export interface SavedObjectsServiceStart { - getScopedClient: (req: KibanaRequest, options?: SavedObjectsClientProviderOptions) => SavedObjectsClientContract; -} - -// @public (undocumented) -export interface SavedObjectsUpdateOptions extends SavedObjectsBaseOptions { - references?: SavedObjectReference[]; - refresh?: MutatingOperationRefreshSetting; - version?: string; -} - -// @public (undocumented) -export interface SavedObjectsUpdateResponse extends Omit, 'attributes' | 'references'> { - // (undocumented) - attributes: Partial; - // (undocumented) - references: SavedObjectReference[] | undefined; -} - -// @public -export class ScopedClusterClient implements IScopedClusterClient { - constructor(internalAPICaller: APICaller, scopedAPICaller: APICaller, headers?: Headers | undefined); - callAsCurrentUser(endpoint: string, clientParams?: Record, options?: CallAPIOptions): Promise; - callAsInternalUser(endpoint: string, clientParams?: Record, options?: CallAPIOptions): Promise; - } - -// @public -export interface SessionCookieValidationResult { - isValid: boolean; - path?: string; -} - -// @public -export interface SessionStorage { - clear(): void; - get(): Promise; - set(sessionValue: T): void; -} - -// @public -export interface SessionStorageCookieOptions { - encryptionKey: string; - isSecure: boolean; - name: string; - validate: (sessionValue: T | T[]) => SessionCookieValidationResult; -} - -// @public -export interface SessionStorageFactory { - // (undocumented) - asScoped: (request: KibanaRequest) => SessionStorage; -} - -// @public (undocumented) -export type SharedGlobalConfig = RecursiveReadonly_2<{ - kibana: Pick; - elasticsearch: Pick; - path: Pick; -}>; - -// @public -export interface UiSettingsParams { - category?: string[]; - description?: string; - name?: string; - optionLabels?: Record; - options?: string[]; - readonly?: boolean; - requiresPageReload?: boolean; - type?: UiSettingsType; - value?: SavedObjectAttribute; -} - -// @public (undocumented) -export interface UiSettingsServiceSetup { - register(settings: Record): void; -} - -// @public (undocumented) -export interface UiSettingsServiceStart { - asScopedToClient(savedObjectsClient: SavedObjectsClientContract): IUiSettingsClient; -} - -// @public -export type UiSettingsType = 'json' | 'markdown' | 'number' | 'select' | 'boolean' | 'string'; - -// @public -export interface UserProvidedValues { - // (undocumented) - isOverridden?: boolean; - // (undocumented) - userValue?: T; -} - -// @public -export interface UuidServiceSetup { - getInstanceUuid(): string; -} - -// @public -export const validBodyOutput: readonly ["data", "stream"]; - - -// Warnings were encountered during analysis: -// -// src/core/server/http/router/response.ts:316:3 - (ae-forgotten-export) The symbol "KibanaResponse" needs to be exported by the entry point index.d.ts -// src/core/server/legacy/types.ts:158:3 - (ae-forgotten-export) The symbol "VarsProvider" needs to be exported by the entry point index.d.ts -// src/core/server/legacy/types.ts:159:3 - (ae-forgotten-export) The symbol "VarsReplacer" needs to be exported by the entry point index.d.ts -// src/core/server/legacy/types.ts:160:3 - (ae-forgotten-export) The symbol "LegacyNavLinkSpec" needs to be exported by the entry point index.d.ts -// src/core/server/legacy/types.ts:161:3 - (ae-forgotten-export) The symbol "LegacyAppSpec" needs to be exported by the entry point index.d.ts -// src/core/server/legacy/types.ts:162:16 - (ae-forgotten-export) The symbol "LegacyPluginSpec" needs to be exported by the entry point index.d.ts -// src/core/server/plugins/plugins_service.ts:43:5 - (ae-forgotten-export) The symbol "InternalPluginInfo" needs to be exported by the entry point index.d.ts -// src/core/server/plugins/types.ts:221:3 - (ae-forgotten-export) The symbol "KibanaConfigType" needs to be exported by the entry point index.d.ts -// src/core/server/plugins/types.ts:221:3 - (ae-forgotten-export) The symbol "SharedGlobalConfigKeys" needs to be exported by the entry point index.d.ts -// src/core/server/plugins/types.ts:222:3 - (ae-forgotten-export) The symbol "ElasticsearchConfigType" needs to be exported by the entry point index.d.ts -// src/core/server/plugins/types.ts:223:3 - (ae-forgotten-export) The symbol "PathConfigType" needs to be exported by the entry point index.d.ts - -``` +## API Report File for "kibana" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import Boom from 'boom'; +import { BulkIndexDocumentsParams } from 'elasticsearch'; +import { CatAliasesParams } from 'elasticsearch'; +import { CatAllocationParams } from 'elasticsearch'; +import { CatCommonParams } from 'elasticsearch'; +import { CatFielddataParams } from 'elasticsearch'; +import { CatHealthParams } from 'elasticsearch'; +import { CatHelpParams } from 'elasticsearch'; +import { CatIndicesParams } from 'elasticsearch'; +import { CatRecoveryParams } from 'elasticsearch'; +import { CatSegmentsParams } from 'elasticsearch'; +import { CatShardsParams } from 'elasticsearch'; +import { CatSnapshotsParams } from 'elasticsearch'; +import { CatTasksParams } from 'elasticsearch'; +import { CatThreadPoolParams } from 'elasticsearch'; +import { ClearScrollParams } from 'elasticsearch'; +import { Client } from 'elasticsearch'; +import { ClusterAllocationExplainParams } from 'elasticsearch'; +import { ClusterGetSettingsParams } from 'elasticsearch'; +import { ClusterHealthParams } from 'elasticsearch'; +import { ClusterPendingTasksParams } from 'elasticsearch'; +import { ClusterPutSettingsParams } from 'elasticsearch'; +import { ClusterRerouteParams } from 'elasticsearch'; +import { ClusterStateParams } from 'elasticsearch'; +import { ClusterStatsParams } from 'elasticsearch'; +import { ConfigOptions } from 'elasticsearch'; +import { CountParams } from 'elasticsearch'; +import { CreateDocumentParams } from 'elasticsearch'; +import { DeleteDocumentByQueryParams } from 'elasticsearch'; +import { DeleteDocumentParams } from 'elasticsearch'; +import { DeleteScriptParams } from 'elasticsearch'; +import { DeleteTemplateParams } from 'elasticsearch'; +import { DetailedPeerCertificate } from 'tls'; +import { Duration } from 'moment'; +import { ExistsParams } from 'elasticsearch'; +import { ExplainParams } from 'elasticsearch'; +import { FieldStatsParams } from 'elasticsearch'; +import { GenericParams } from 'elasticsearch'; +import { GetParams } from 'elasticsearch'; +import { GetResponse } from 'elasticsearch'; +import { GetScriptParams } from 'elasticsearch'; +import { GetSourceParams } from 'elasticsearch'; +import { GetTemplateParams } from 'elasticsearch'; +import { IncomingHttpHeaders } from 'http'; +import { IndexDocumentParams } from 'elasticsearch'; +import { IndicesAnalyzeParams } from 'elasticsearch'; +import { IndicesClearCacheParams } from 'elasticsearch'; +import { IndicesCloseParams } from 'elasticsearch'; +import { IndicesCreateParams } from 'elasticsearch'; +import { IndicesDeleteAliasParams } from 'elasticsearch'; +import { IndicesDeleteParams } from 'elasticsearch'; +import { IndicesDeleteTemplateParams } from 'elasticsearch'; +import { IndicesExistsAliasParams } from 'elasticsearch'; +import { IndicesExistsParams } from 'elasticsearch'; +import { IndicesExistsTemplateParams } from 'elasticsearch'; +import { IndicesExistsTypeParams } from 'elasticsearch'; +import { IndicesFlushParams } from 'elasticsearch'; +import { IndicesFlushSyncedParams } from 'elasticsearch'; +import { IndicesForcemergeParams } from 'elasticsearch'; +import { IndicesGetAliasParams } from 'elasticsearch'; +import { IndicesGetFieldMappingParams } from 'elasticsearch'; +import { IndicesGetMappingParams } from 'elasticsearch'; +import { IndicesGetParams } from 'elasticsearch'; +import { IndicesGetSettingsParams } from 'elasticsearch'; +import { IndicesGetTemplateParams } from 'elasticsearch'; +import { IndicesGetUpgradeParams } from 'elasticsearch'; +import { IndicesOpenParams } from 'elasticsearch'; +import { IndicesPutAliasParams } from 'elasticsearch'; +import { IndicesPutMappingParams } from 'elasticsearch'; +import { IndicesPutSettingsParams } from 'elasticsearch'; +import { IndicesPutTemplateParams } from 'elasticsearch'; +import { IndicesRecoveryParams } from 'elasticsearch'; +import { IndicesRefreshParams } from 'elasticsearch'; +import { IndicesRolloverParams } from 'elasticsearch'; +import { IndicesSegmentsParams } from 'elasticsearch'; +import { IndicesShardStoresParams } from 'elasticsearch'; +import { IndicesShrinkParams } from 'elasticsearch'; +import { IndicesStatsParams } from 'elasticsearch'; +import { IndicesUpdateAliasesParams } from 'elasticsearch'; +import { IndicesUpgradeParams } from 'elasticsearch'; +import { IndicesValidateQueryParams } from 'elasticsearch'; +import { InfoParams } from 'elasticsearch'; +import { IngestDeletePipelineParams } from 'elasticsearch'; +import { IngestGetPipelineParams } from 'elasticsearch'; +import { IngestPutPipelineParams } from 'elasticsearch'; +import { IngestSimulateParams } from 'elasticsearch'; +import { KibanaConfigType } from 'src/core/server/kibana_config'; +import { Logger as Logger_2 } from 'src/core/server/logging'; +import { MGetParams } from 'elasticsearch'; +import { MGetResponse } from 'elasticsearch'; +import { MSearchParams } from 'elasticsearch'; +import { MSearchResponse } from 'elasticsearch'; +import { MSearchTemplateParams } from 'elasticsearch'; +import { MTermVectorsParams } from 'elasticsearch'; +import { NodesHotThreadsParams } from 'elasticsearch'; +import { NodesInfoParams } from 'elasticsearch'; +import { NodesStatsParams } from 'elasticsearch'; +import { ObjectType } from '@kbn/config-schema'; +import { Observable } from 'rxjs'; +import { PeerCertificate } from 'tls'; +import { PingParams } from 'elasticsearch'; +import { PutScriptParams } from 'elasticsearch'; +import { PutTemplateParams } from 'elasticsearch'; +import { Readable } from 'stream'; +import { RecursiveReadonly as RecursiveReadonly_2 } from 'kibana/public'; +import { ReindexParams } from 'elasticsearch'; +import { ReindexRethrottleParams } from 'elasticsearch'; +import { RenderSearchTemplateParams } from 'elasticsearch'; +import { Request } from 'hapi'; +import { ResponseObject } from 'hapi'; +import { ResponseToolkit } from 'hapi'; +import { SchemaTypeError } from '@kbn/config-schema'; +import { ScrollParams } from 'elasticsearch'; +import { SearchParams } from 'elasticsearch'; +import { SearchResponse } from 'elasticsearch'; +import { SearchShardsParams } from 'elasticsearch'; +import { SearchTemplateParams } from 'elasticsearch'; +import { Server } from 'hapi'; +import { ShallowPromise } from '@kbn/utility-types'; +import { SnapshotCreateParams } from 'elasticsearch'; +import { SnapshotCreateRepositoryParams } from 'elasticsearch'; +import { SnapshotDeleteParams } from 'elasticsearch'; +import { SnapshotDeleteRepositoryParams } from 'elasticsearch'; +import { SnapshotGetParams } from 'elasticsearch'; +import { SnapshotGetRepositoryParams } from 'elasticsearch'; +import { SnapshotRestoreParams } from 'elasticsearch'; +import { SnapshotStatusParams } from 'elasticsearch'; +import { SnapshotVerifyRepositoryParams } from 'elasticsearch'; +import { Stream } from 'stream'; +import { SuggestParams } from 'elasticsearch'; +import { TasksCancelParams } from 'elasticsearch'; +import { TasksGetParams } from 'elasticsearch'; +import { TasksListParams } from 'elasticsearch'; +import { TermvectorsParams } from 'elasticsearch'; +import { Type } from '@kbn/config-schema'; +import { TypeOf } from '@kbn/config-schema'; +import { UpdateDocumentByQueryParams } from 'elasticsearch'; +import { UpdateDocumentParams } from 'elasticsearch'; +import { Url } from 'url'; + +// @public (undocumented) +export interface APICaller { + // (undocumented) + (endpoint: 'bulk', params: BulkIndexDocumentsParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'clearScroll', params: ClearScrollParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'count', params: CountParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'create', params: CreateDocumentParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'delete', params: DeleteDocumentParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'deleteByQuery', params: DeleteDocumentByQueryParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'deleteScript', params: DeleteScriptParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'deleteTemplate', params: DeleteTemplateParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'exists', params: ExistsParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'explain', params: ExplainParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'fieldStats', params: FieldStatsParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'get', params: GetParams, options?: CallAPIOptions): Promise>; + // (undocumented) + (endpoint: 'getScript', params: GetScriptParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'getSource', params: GetSourceParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'getTemplate', params: GetTemplateParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'index', params: IndexDocumentParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'info', params: InfoParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'mget', params: MGetParams, options?: CallAPIOptions): Promise>; + // (undocumented) + (endpoint: 'msearch', params: MSearchParams, options?: CallAPIOptions): Promise>; + // (undocumented) + (endpoint: 'msearchTemplate', params: MSearchTemplateParams, options?: CallAPIOptions): Promise>; + // (undocumented) + (endpoint: 'mtermvectors', params: MTermVectorsParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'ping', params: PingParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'putScript', params: PutScriptParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'putTemplate', params: PutTemplateParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'reindex', params: ReindexParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'reindexRethrottle', params: ReindexRethrottleParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'renderSearchTemplate', params: RenderSearchTemplateParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'scroll', params: ScrollParams, options?: CallAPIOptions): Promise>; + // (undocumented) + (endpoint: 'search', params: SearchParams, options?: CallAPIOptions): Promise>; + // (undocumented) + (endpoint: 'searchShards', params: SearchShardsParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'searchTemplate', params: SearchTemplateParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'suggest', params: SuggestParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'termvectors', params: TermvectorsParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'update', params: UpdateDocumentParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'updateByQuery', params: UpdateDocumentByQueryParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.aliases', params: CatAliasesParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.allocation', params: CatAllocationParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.count', params: CatAllocationParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.fielddata', params: CatFielddataParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.health', params: CatHealthParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.help', params: CatHelpParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.indices', params: CatIndicesParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.master', params: CatCommonParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.nodeattrs', params: CatCommonParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.nodes', params: CatCommonParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.pendingTasks', params: CatCommonParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.plugins', params: CatCommonParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.recovery', params: CatRecoveryParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.repositories', params: CatCommonParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.segments', params: CatSegmentsParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.shards', params: CatShardsParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.snapshots', params: CatSnapshotsParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.tasks', params: CatTasksParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.threadPool', params: CatThreadPoolParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cluster.allocationExplain', params: ClusterAllocationExplainParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cluster.getSettings', params: ClusterGetSettingsParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cluster.health', params: ClusterHealthParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cluster.pendingTasks', params: ClusterPendingTasksParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cluster.putSettings', params: ClusterPutSettingsParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cluster.reroute', params: ClusterRerouteParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cluster.state', params: ClusterStateParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cluster.stats', params: ClusterStatsParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.analyze', params: IndicesAnalyzeParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.clearCache', params: IndicesClearCacheParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.close', params: IndicesCloseParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.create', params: IndicesCreateParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.delete', params: IndicesDeleteParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.deleteAlias', params: IndicesDeleteAliasParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.deleteTemplate', params: IndicesDeleteTemplateParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.exists', params: IndicesExistsParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.existsAlias', params: IndicesExistsAliasParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.existsTemplate', params: IndicesExistsTemplateParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.existsType', params: IndicesExistsTypeParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.flush', params: IndicesFlushParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.flushSynced', params: IndicesFlushSyncedParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.forcemerge', params: IndicesForcemergeParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.get', params: IndicesGetParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.getAlias', params: IndicesGetAliasParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.getFieldMapping', params: IndicesGetFieldMappingParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.getMapping', params: IndicesGetMappingParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.getSettings', params: IndicesGetSettingsParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.getTemplate', params: IndicesGetTemplateParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.getUpgrade', params: IndicesGetUpgradeParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.open', params: IndicesOpenParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.putAlias', params: IndicesPutAliasParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.putMapping', params: IndicesPutMappingParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.putSettings', params: IndicesPutSettingsParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.putTemplate', params: IndicesPutTemplateParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.recovery', params: IndicesRecoveryParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.refresh', params: IndicesRefreshParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.rollover', params: IndicesRolloverParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.segments', params: IndicesSegmentsParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.shardStores', params: IndicesShardStoresParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.shrink', params: IndicesShrinkParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.stats', params: IndicesStatsParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.updateAliases', params: IndicesUpdateAliasesParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.upgrade', params: IndicesUpgradeParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.validateQuery', params: IndicesValidateQueryParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'ingest.deletePipeline', params: IngestDeletePipelineParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'ingest.getPipeline', params: IngestGetPipelineParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'ingest.putPipeline', params: IngestPutPipelineParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'ingest.simulate', params: IngestSimulateParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'nodes.hotThreads', params: NodesHotThreadsParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'nodes.info', params: NodesInfoParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'nodes.stats', params: NodesStatsParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'snapshot.create', params: SnapshotCreateParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'snapshot.createRepository', params: SnapshotCreateRepositoryParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'snapshot.delete', params: SnapshotDeleteParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'snapshot.deleteRepository', params: SnapshotDeleteRepositoryParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'snapshot.get', params: SnapshotGetParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'snapshot.getRepository', params: SnapshotGetRepositoryParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'snapshot.restore', params: SnapshotRestoreParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'snapshot.status', params: SnapshotStatusParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'snapshot.verifyRepository', params: SnapshotVerifyRepositoryParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'tasks.cancel', params: TasksCancelParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'tasks.get', params: TasksGetParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'tasks.list', params: TasksListParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'transport.request', clientParams: AssistantAPIClientParams, options?: CallAPIOptions): Promise; + // (undocumented) + (endpoint: 'transport.request', clientParams: DeprecationAPIClientParams, options?: CallAPIOptions): Promise; + // (undocumented) + (endpoint: string, clientParams?: Record, options?: CallAPIOptions): Promise; +} + +// @public (undocumented) +export interface AssistanceAPIResponse { + // (undocumented) + indices: { + [indexName: string]: { + action_required: MIGRATION_ASSISTANCE_INDEX_ACTION; + }; + }; +} + +// @public (undocumented) +export interface AssistantAPIClientParams extends GenericParams { + // (undocumented) + method: 'GET'; + // (undocumented) + path: '/_migration/assistance'; +} + +// @public (undocumented) +export interface Authenticated extends AuthResultParams { + // (undocumented) + type: AuthResultType.authenticated; +} + +// @public +export type AuthenticationHandler = (request: KibanaRequest, response: LifecycleResponseFactory, toolkit: AuthToolkit) => AuthResult | IKibanaResponse | Promise; + +// @public +export type AuthHeaders = Record; + +// @public (undocumented) +export type AuthResult = Authenticated; + +// @public +export interface AuthResultParams { + requestHeaders?: AuthHeaders; + responseHeaders?: AuthHeaders; + state?: Record; +} + +// @public (undocumented) +export enum AuthResultType { + // (undocumented) + authenticated = "authenticated" +} + +// @public +export enum AuthStatus { + authenticated = "authenticated", + unauthenticated = "unauthenticated", + unknown = "unknown" +} + +// @public +export interface AuthToolkit { + authenticated: (data?: AuthResultParams) => AuthResult; +} + +// @public +export class BasePath { + // @internal + constructor(serverBasePath?: string); + get: (request: KibanaRequest | LegacyRequest) => string; + prepend: (path: string) => string; + remove: (path: string) => string; + readonly serverBasePath: string; + set: (request: KibanaRequest | LegacyRequest, requestSpecificBasePath: string) => void; +} + +// Warning: (ae-forgotten-export) The symbol "BootstrapArgs" needs to be exported by the entry point index.d.ts +// +// @internal (undocumented) +export function bootstrap({ configs, cliArgs, applyConfigOverrides, features, }: BootstrapArgs): Promise; + +// @public +export interface CallAPIOptions { + signal?: AbortSignal; + wrap401Errors?: boolean; +} + +// @public +export interface Capabilities { + [key: string]: Record>; + catalogue: Record; + management: { + [sectionId: string]: Record; + }; + navLinks: Record; +} + +// @public +export type CapabilitiesProvider = () => Partial; + +// @public +export interface CapabilitiesSetup { + registerProvider(provider: CapabilitiesProvider): void; + registerSwitcher(switcher: CapabilitiesSwitcher): void; +} + +// @public +export interface CapabilitiesStart { + resolveCapabilities(request: KibanaRequest): Promise; +} + +// @public +export type CapabilitiesSwitcher = (request: KibanaRequest, uiCapabilities: Capabilities) => Partial | Promise>; + +// @public +export class ClusterClient implements IClusterClient { + constructor(config: ElasticsearchClientConfig, log: Logger, getAuthHeaders?: GetAuthHeaders); + asScoped(request?: KibanaRequest | LegacyRequest | FakeRequest): IScopedClusterClient; + callAsInternalUser: APICaller; + close(): void; + } + +// @public +export type ConfigDeprecation = (config: Record, fromPath: string, logger: ConfigDeprecationLogger) => Record; + +// @public +export interface ConfigDeprecationFactory { + rename(oldKey: string, newKey: string): ConfigDeprecation; + renameFromRoot(oldKey: string, newKey: string): ConfigDeprecation; + unused(unusedKey: string): ConfigDeprecation; + unusedFromRoot(unusedKey: string): ConfigDeprecation; +} + +// @public +export type ConfigDeprecationLogger = (message: string) => void; + +// @public +export type ConfigDeprecationProvider = (factory: ConfigDeprecationFactory) => ConfigDeprecation[]; + +// @public (undocumented) +export type ConfigPath = string | string[]; + +// @internal (undocumented) +export class ConfigService { + // Warning: (ae-forgotten-export) The symbol "RawConfigurationProvider" needs to be exported by the entry point index.d.ts + // Warning: (ae-forgotten-export) The symbol "Env" needs to be exported by the entry point index.d.ts + constructor(rawConfigProvider: RawConfigurationProvider, env: Env, logger: LoggerFactory); + addDeprecationProvider(path: ConfigPath, provider: ConfigDeprecationProvider): void; + atPath(path: ConfigPath): Observable; + // Warning: (ae-forgotten-export) The symbol "Config" needs to be exported by the entry point index.d.ts + getConfig$(): Observable; + // (undocumented) + getUnusedPaths(): Promise; + // (undocumented) + getUsedPaths(): Promise; + // (undocumented) + isEnabledAtPath(path: ConfigPath): Promise; + optionalAtPath(path: ConfigPath): Observable; + setSchema(path: ConfigPath, schema: Type): Promise; + validate(): Promise; + } + +// @public +export interface ContextSetup { + createContextContainer>(): IContextContainer; +} + +// @internal (undocumented) +export type CoreId = symbol; + +// @public +export interface CoreSetup { + // (undocumented) + capabilities: CapabilitiesSetup; + // (undocumented) + context: ContextSetup; + // (undocumented) + elasticsearch: ElasticsearchServiceSetup; + // (undocumented) + http: HttpServiceSetup; + // (undocumented) + savedObjects: SavedObjectsServiceSetup; + // (undocumented) + uiSettings: UiSettingsServiceSetup; + // (undocumented) + uuid: UuidServiceSetup; +} + +// @public +export interface CoreStart { + // (undocumented) + capabilities: CapabilitiesStart; + // (undocumented) + savedObjects: SavedObjectsServiceStart; + // (undocumented) + uiSettings: UiSettingsServiceStart; +} + +// @public +export class CspConfig implements ICspConfig { + // @internal + constructor(rawCspConfig?: Partial>); + // (undocumented) + static readonly DEFAULT: CspConfig; + // (undocumented) + readonly header: string; + // (undocumented) + readonly rules: string[]; + // (undocumented) + readonly strict: boolean; + // (undocumented) + readonly warnLegacyBrowsers: boolean; +} + +// @public +export interface CustomHttpResponseOptions { + body?: T; + headers?: ResponseHeaders; + // (undocumented) + statusCode: number; +} + +// @public (undocumented) +export interface DeprecationAPIClientParams extends GenericParams { + // (undocumented) + method: 'GET'; + // (undocumented) + path: '/_migration/deprecations'; +} + +// @public (undocumented) +export interface DeprecationAPIResponse { + // (undocumented) + cluster_settings: DeprecationInfo[]; + // (undocumented) + index_settings: IndexSettingsDeprecationInfo; + // (undocumented) + ml_settings: DeprecationInfo[]; + // (undocumented) + node_settings: DeprecationInfo[]; +} + +// @public (undocumented) +export interface DeprecationInfo { + // (undocumented) + details?: string; + // (undocumented) + level: MIGRATION_DEPRECATION_LEVEL; + // (undocumented) + message: string; + // (undocumented) + url: string; +} + +// @public +export interface DiscoveredPlugin { + readonly configPath: ConfigPath; + readonly id: PluginName; + readonly optionalPlugins: readonly PluginName[]; + readonly requiredPlugins: readonly PluginName[]; +} + +// Warning: (ae-forgotten-export) The symbol "ElasticsearchConfig" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export type ElasticsearchClientConfig = Pick & Pick & { + pingTimeout?: ElasticsearchConfig['pingTimeout'] | ConfigOptions['pingTimeout']; + requestTimeout?: ElasticsearchConfig['requestTimeout'] | ConfigOptions['requestTimeout']; + sniffInterval?: ElasticsearchConfig['sniffInterval'] | ConfigOptions['sniffInterval']; + ssl?: Partial; +}; + +// @public (undocumented) +export interface ElasticsearchError extends Boom { + // (undocumented) + [code]?: string; +} + +// @public +export class ElasticsearchErrorHelpers { + // (undocumented) + static decorateNotAuthorizedError(error: Error, reason?: string): ElasticsearchError; + // (undocumented) + static isNotAuthorizedError(error: any): error is ElasticsearchError; +} + +// @public (undocumented) +export interface ElasticsearchServiceSetup { + readonly adminClient$: Observable; + readonly createClient: (type: string, clientConfig?: Partial) => IClusterClient; + readonly dataClient$: Observable; +} + +// @public (undocumented) +export interface EnvironmentMode { + // (undocumented) + dev: boolean; + // (undocumented) + name: 'development' | 'production'; + // (undocumented) + prod: boolean; +} + +// @public +export interface ErrorHttpResponseOptions { + body?: ResponseError; + headers?: ResponseHeaders; +} + +// @public +export interface FakeRequest { + headers: Headers; +} + +// @public +export type GetAuthHeaders = (request: KibanaRequest | LegacyRequest) => AuthHeaders | undefined; + +// @public +export type GetAuthState = (request: KibanaRequest | LegacyRequest) => { + status: AuthStatus; + state: unknown; +}; + +// @public +export type HandlerContextType> = T extends HandlerFunction ? U : never; + +// @public +export type HandlerFunction = (context: T, ...args: any[]) => any; + +// @public +export type HandlerParameters> = T extends (context: any, ...args: infer U) => any ? U : never; + +// @public +export type Headers = { + [header in KnownHeaders]?: string | string[] | undefined; +} & { + [header: string]: string | string[] | undefined; +}; + +// @public +export interface HttpResponseOptions { + body?: HttpResponsePayload; + headers?: ResponseHeaders; +} + +// @public +export type HttpResponsePayload = undefined | string | Record | Buffer | Stream; + +// @public +export interface HttpServiceSetup { + basePath: IBasePath; + createCookieSessionStorageFactory: (cookieOptions: SessionStorageCookieOptions) => Promise>; + createRouter: () => IRouter; + csp: ICspConfig; + isTlsEnabled: boolean; + registerAuth: (handler: AuthenticationHandler) => void; + registerOnPostAuth: (handler: OnPostAuthHandler) => void; + registerOnPreAuth: (handler: OnPreAuthHandler) => void; + registerOnPreResponse: (handler: OnPreResponseHandler) => void; + registerRouteHandlerContext: (contextName: T, provider: RequestHandlerContextProvider) => RequestHandlerContextContainer; +} + +// @public (undocumented) +export interface HttpServiceStart { + isListening: (port: number) => boolean; +} + +// @public +export type IBasePath = Pick; + +// @public +export type IClusterClient = Pick; + +// @public +export interface IContextContainer> { + createHandler(pluginOpaqueId: PluginOpaqueId, handler: THandler): (...rest: HandlerParameters) => ShallowPromise>; + registerContext>(pluginOpaqueId: PluginOpaqueId, contextName: TContextName, provider: IContextProvider): this; +} + +// @public +export type IContextProvider, TContextName extends keyof HandlerContextType> = (context: Partial>, ...rest: HandlerParameters) => Promise[TContextName]> | HandlerContextType[TContextName]; + +// @public +export interface ICspConfig { + readonly header: string; + readonly rules: string[]; + readonly strict: boolean; + readonly warnLegacyBrowsers: boolean; +} + +// @public +export interface IKibanaResponse { + // (undocumented) + readonly options: HttpResponseOptions; + // (undocumented) + readonly payload?: T; + // (undocumented) + readonly status: number; +} + +// @public +export interface IKibanaSocket { + readonly authorizationError?: Error; + readonly authorized?: boolean; + // (undocumented) + getPeerCertificate(detailed: true): DetailedPeerCertificate | null; + // (undocumented) + getPeerCertificate(detailed: false): PeerCertificate | null; + getPeerCertificate(detailed?: boolean): PeerCertificate | DetailedPeerCertificate | null; +} + +// @public (undocumented) +export interface IndexSettingsDeprecationInfo { + // (undocumented) + [indexName: string]: DeprecationInfo[]; +} + +// @public (undocumented) +export interface IRenderOptions { + includeUserSettings?: boolean; +} + +// @public +export interface IRouter { + delete: RouteRegistrar<'delete'>; + get: RouteRegistrar<'get'>; + // Warning: (ae-forgotten-export) The symbol "RouterRoute" needs to be exported by the entry point index.d.ts + // + // @internal + getRoutes: () => RouterRoute[]; + handleLegacyErrors: (handler: RequestHandler) => RequestHandler; + patch: RouteRegistrar<'patch'>; + post: RouteRegistrar<'post'>; + put: RouteRegistrar<'put'>; + routerPath: string; +} + +// @public +export type IsAuthenticated = (request: KibanaRequest | LegacyRequest) => boolean; + +// @public +export type ISavedObjectsRepository = Pick; + +// @public +export type IScopedClusterClient = Pick; + +// @public (undocumented) +export interface IScopedRenderingClient { + render(options?: IRenderOptions): Promise; +} + +// @public +export interface IUiSettingsClient { + get: (key: string) => Promise; + getAll: () => Promise>; + getRegistered: () => Readonly>; + getUserProvided: () => Promise>>; + isOverridden: (key: string) => boolean; + remove: (key: string) => Promise; + removeMany: (keys: string[]) => Promise; + set: (key: string, value: any) => Promise; + setMany: (changes: Record) => Promise; +} + +// @public +export class KibanaRequest { + // @internal (undocumented) + protected readonly [requestSymbol]: Request; + constructor(request: Request, params: Params, query: Query, body: Body, withoutSecretHeaders: boolean); + // (undocumented) + readonly body: Body; + // Warning: (ae-forgotten-export) The symbol "RouteValidator" needs to be exported by the entry point index.d.ts + // + // @internal + static from(req: Request, routeSchemas?: RouteValidator | RouteValidatorFullConfig, withoutSecretHeaders?: boolean): KibanaRequest; + readonly headers: Headers; + // (undocumented) + readonly params: Params; + // (undocumented) + readonly query: Query; + readonly route: RecursiveReadonly>; + // (undocumented) + readonly socket: IKibanaSocket; + readonly url: Url; + } + +// @public +export interface KibanaRequestRoute { + // (undocumented) + method: Method; + // (undocumented) + options: KibanaRequestRouteOptions; + // (undocumented) + path: string; +} + +// @public +export type KibanaRequestRouteOptions = Method extends 'get' | 'options' ? Required, 'body'>> : Required>; + +// @public +export type KibanaResponseFactory = typeof kibanaResponseFactory; + +// @public +export const kibanaResponseFactory: { + custom: | Buffer | Stream | { + message: string | Error; + attributes?: Record | undefined; + } | undefined>(options: CustomHttpResponseOptions) => KibanaResponse; + badRequest: (options?: ErrorHttpResponseOptions) => KibanaResponse; + unauthorized: (options?: ErrorHttpResponseOptions) => KibanaResponse; + forbidden: (options?: ErrorHttpResponseOptions) => KibanaResponse; + notFound: (options?: ErrorHttpResponseOptions) => KibanaResponse; + conflict: (options?: ErrorHttpResponseOptions) => KibanaResponse; + internalError: (options?: ErrorHttpResponseOptions) => KibanaResponse; + customError: (options: CustomHttpResponseOptions) => KibanaResponse; + redirected: (options: RedirectResponseOptions) => KibanaResponse | Buffer | Stream>; + ok: (options?: HttpResponseOptions) => KibanaResponse | Buffer | Stream>; + accepted: (options?: HttpResponseOptions) => KibanaResponse | Buffer | Stream>; + noContent: (options?: HttpResponseOptions) => KibanaResponse; +}; + +// Warning: (ae-forgotten-export) The symbol "KnownKeys" needs to be exported by the entry point index.d.ts +// +// @public +export type KnownHeaders = KnownKeys; + +// @internal @deprecated +export interface LegacyConfig { + // (undocumented) + get(key?: string): T; + // (undocumented) + has(key: string): boolean; + // (undocumented) + set(key: string, value: any): void; + // Warning: (ae-forgotten-export) The symbol "LegacyVars" needs to be exported by the entry point index.d.ts + // + // (undocumented) + set(config: LegacyVars): void; +} + +// Warning: (ae-forgotten-export) The symbol "ILegacyInternals" needs to be exported by the entry point index.d.ts +// +// @internal @deprecated (undocumented) +export class LegacyInternals implements ILegacyInternals { + constructor(uiExports: LegacyUiExports, config: LegacyConfig, server: Server); + // (undocumented) + getInjectedUiAppVars(id: string): Promise>; + // (undocumented) + getVars(id: string, request: LegacyRequest, injected?: LegacyVars): Promise>; + // Warning: (ae-forgotten-export) The symbol "VarsInjector" needs to be exported by the entry point index.d.ts + // + // (undocumented) + injectUiAppVars(id: string, injector: VarsInjector): void; + } + +// @internal @deprecated (undocumented) +export interface LegacyRenderOptions extends IRenderOptions { + app?: { + getId(): string; + }; + vars?: Record; +} + +// @public @deprecated (undocumented) +export interface LegacyRequest extends Request { +} + +// Warning: (ae-forgotten-export) The symbol "LegacyPlugins" needs to be exported by the entry point index.d.ts +// +// @internal @deprecated (undocumented) +export interface LegacyServiceDiscoverPlugins extends LegacyPlugins { + // (undocumented) + pluginExtendedConfig: LegacyConfig; + // (undocumented) + settings: LegacyVars; +} + +// @public @deprecated (undocumented) +export interface LegacyServiceSetupDeps { + // Warning: (ae-forgotten-export) The symbol "LegacyCoreSetup" needs to be exported by the entry point index.d.ts + // + // (undocumented) + core: LegacyCoreSetup; + // (undocumented) + plugins: Record; +} + +// @public @deprecated (undocumented) +export interface LegacyServiceStartDeps { + // Warning: (ae-forgotten-export) The symbol "LegacyCoreStart" needs to be exported by the entry point index.d.ts + // + // (undocumented) + core: LegacyCoreStart; + // (undocumented) + plugins: Record; +} + +// Warning: (ae-forgotten-export) The symbol "SavedObjectsLegacyUiExports" needs to be exported by the entry point index.d.ts +// +// @internal @deprecated (undocumented) +export type LegacyUiExports = SavedObjectsLegacyUiExports & { + defaultInjectedVarProviders?: VarsProvider[]; + injectedVarsReplacers?: VarsReplacer[]; + navLinkSpecs?: LegacyNavLinkSpec[] | null; + uiAppSpecs?: Array; + unknown?: [{ + pluginSpec: LegacyPluginSpec; + type: unknown; + }]; +}; + +// Warning: (ae-forgotten-export) The symbol "lifecycleResponseFactory" needs to be exported by the entry point index.d.ts +// +// @public +export type LifecycleResponseFactory = typeof lifecycleResponseFactory; + +// @public +export interface Logger { + debug(message: string, meta?: LogMeta): void; + error(errorOrMessage: string | Error, meta?: LogMeta): void; + fatal(errorOrMessage: string | Error, meta?: LogMeta): void; + get(...childContextPaths: string[]): Logger; + info(message: string, meta?: LogMeta): void; + // @internal (undocumented) + log(record: LogRecord): void; + trace(message: string, meta?: LogMeta): void; + warn(errorOrMessage: string | Error, meta?: LogMeta): void; +} + +// @public +export interface LoggerFactory { + get(...contextParts: string[]): Logger; +} + +// @internal +export class LogLevel { + // (undocumented) + static readonly All: LogLevel; + // (undocumented) + static readonly Debug: LogLevel; + // (undocumented) + static readonly Error: LogLevel; + // (undocumented) + static readonly Fatal: LogLevel; + static fromId(level: LogLevelId): LogLevel; + // Warning: (ae-forgotten-export) The symbol "LogLevelId" needs to be exported by the entry point index.d.ts + // + // (undocumented) + readonly id: LogLevelId; + // (undocumented) + static readonly Info: LogLevel; + // (undocumented) + static readonly Off: LogLevel; + supports(level: LogLevel): boolean; + // (undocumented) + static readonly Trace: LogLevel; + // (undocumented) + readonly value: number; + // (undocumented) + static readonly Warn: LogLevel; +} + +// @public +export interface LogMeta { + // (undocumented) + [key: string]: any; +} + +// @internal +export interface LogRecord { + // (undocumented) + context: string; + // (undocumented) + error?: Error; + // (undocumented) + level: LogLevel; + // (undocumented) + message: string; + // (undocumented) + meta?: { + [name: string]: any; + }; + // (undocumented) + timestamp: Date; +} + +// @public (undocumented) +export type MIGRATION_ASSISTANCE_INDEX_ACTION = 'upgrade' | 'reindex'; + +// @public (undocumented) +export type MIGRATION_DEPRECATION_LEVEL = 'none' | 'info' | 'warning' | 'critical'; + +// @public +export type MutatingOperationRefreshSetting = boolean | 'wait_for'; + +// Warning: (ae-forgotten-export) The symbol "OnPostAuthResult" needs to be exported by the entry point index.d.ts +// +// @public +export type OnPostAuthHandler = (request: KibanaRequest, response: LifecycleResponseFactory, toolkit: OnPostAuthToolkit) => OnPostAuthResult | KibanaResponse | Promise; + +// @public +export interface OnPostAuthToolkit { + next: () => OnPostAuthResult; +} + +// Warning: (ae-forgotten-export) The symbol "OnPreAuthResult" needs to be exported by the entry point index.d.ts +// +// @public +export type OnPreAuthHandler = (request: KibanaRequest, response: LifecycleResponseFactory, toolkit: OnPreAuthToolkit) => OnPreAuthResult | KibanaResponse | Promise; + +// @public +export interface OnPreAuthToolkit { + next: () => OnPreAuthResult; + rewriteUrl: (url: string) => OnPreAuthResult; +} + +// @public +export interface OnPreResponseExtensions { + headers?: ResponseHeaders; +} + +// Warning: (ae-forgotten-export) The symbol "OnPreResponseResult" needs to be exported by the entry point index.d.ts +// +// @public +export type OnPreResponseHandler = (request: KibanaRequest, preResponse: OnPreResponseInfo, toolkit: OnPreResponseToolkit) => OnPreResponseResult | Promise; + +// @public +export interface OnPreResponseInfo { + // (undocumented) + statusCode: number; +} + +// @public +export interface OnPreResponseToolkit { + next: (responseExtensions?: OnPreResponseExtensions) => OnPreResponseResult; +} + +// @public (undocumented) +export interface PackageInfo { + // (undocumented) + branch: string; + // (undocumented) + buildNum: number; + // (undocumented) + buildSha: string; + // (undocumented) + dist: boolean; + // (undocumented) + version: string; +} + +// @public +export interface Plugin { + // (undocumented) + setup(core: CoreSetup, plugins: TPluginsSetup): TSetup | Promise; + // (undocumented) + start(core: CoreStart, plugins: TPluginsStart): TStart | Promise; + // (undocumented) + stop?(): void; +} + +// @public +export interface PluginConfigDescriptor { + deprecations?: ConfigDeprecationProvider; + exposeToBrowser?: { + [P in keyof T]?: boolean; + }; + schema: PluginConfigSchema; +} + +// @public +export type PluginConfigSchema = Type; + +// @public +export type PluginInitializer = (core: PluginInitializerContext) => Plugin; + +// @public +export interface PluginInitializerContext { + // (undocumented) + config: { + legacy: { + globalConfig$: Observable; + }; + create: () => Observable; + createIfExists: () => Observable; + }; + // (undocumented) + env: { + mode: EnvironmentMode; + packageInfo: Readonly; + }; + // (undocumented) + logger: LoggerFactory; + // (undocumented) + opaqueId: PluginOpaqueId; +} + +// @public +export interface PluginManifest { + readonly configPath: ConfigPath; + readonly id: PluginName; + readonly kibanaVersion: string; + readonly optionalPlugins: readonly PluginName[]; + readonly requiredPlugins: readonly PluginName[]; + readonly server: boolean; + readonly ui: boolean; + readonly version: string; +} + +// @public +export type PluginName = string; + +// @public (undocumented) +export type PluginOpaqueId = symbol; + +// @public (undocumented) +export interface PluginsServiceSetup { + // (undocumented) + contracts: Map; + // (undocumented) + uiPlugins: { + internal: Map; + public: Map; + browserConfigs: Map>; + }; +} + +// @public (undocumented) +export interface PluginsServiceStart { + // (undocumented) + contracts: Map; +} + +// Warning: (ae-forgotten-export) The symbol "RecursiveReadonlyArray" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export type RecursiveReadonly = T extends (...args: any[]) => any ? T : T extends any[] ? RecursiveReadonlyArray : T extends object ? Readonly<{ + [K in keyof T]: RecursiveReadonly; +}> : T; + +// @public +export type RedirectResponseOptions = HttpResponseOptions & { + headers: { + location: string; + }; +}; + +// @internal (undocumented) +export interface RenderingServiceSetup { + render(request: R, uiSettings: IUiSettingsClient, options?: R extends LegacyRequest ? LegacyRenderOptions : IRenderOptions): Promise; +} + +// @public +export type RequestHandler

= (context: RequestHandlerContext, request: KibanaRequest, response: KibanaResponseFactory) => IKibanaResponse | Promise>; + +// @public +export interface RequestHandlerContext { + // (undocumented) + core: { + rendering: IScopedRenderingClient; + savedObjects: { + client: SavedObjectsClientContract; + }; + elasticsearch: { + dataClient: IScopedClusterClient; + adminClient: IScopedClusterClient; + }; + uiSettings: { + client: IUiSettingsClient; + }; + }; +} + +// @public +export type RequestHandlerContextContainer = IContextContainer>; + +// @public +export type RequestHandlerContextProvider = IContextProvider, TContextName>; + +// @public +export type ResponseError = string | Error | { + message: string | Error; + attributes?: ResponseErrorAttributes; +}; + +// @public +export type ResponseErrorAttributes = Record; + +// @public +export type ResponseHeaders = { + [header in KnownHeaders]?: string | string[]; +} & { + [header: string]: string | string[]; +}; + +// @public +export interface RouteConfig { + options?: RouteConfigOptions; + path: string; + validate: RouteValidatorFullConfig | false; +} + +// @public +export interface RouteConfigOptions { + authRequired?: boolean; + body?: Method extends 'get' | 'options' ? undefined : RouteConfigOptionsBody; + tags?: readonly string[]; +} + +// @public +export interface RouteConfigOptionsBody { + accepts?: RouteContentType | RouteContentType[] | string | string[]; + maxBytes?: number; + output?: typeof validBodyOutput[number]; + parse?: boolean | 'gunzip'; +} + +// @public +export type RouteContentType = 'application/json' | 'application/*+json' | 'application/octet-stream' | 'application/x-www-form-urlencoded' | 'multipart/form-data' | 'text/*'; + +// @public +export type RouteMethod = 'get' | 'post' | 'put' | 'delete' | 'patch' | 'options'; + +// @public +export type RouteRegistrar = (route: RouteConfig, handler: RequestHandler) => void; + +// @public +export class RouteValidationError extends SchemaTypeError { + constructor(error: Error | string, path?: string[]); +} + +// @public +export type RouteValidationFunction = (data: any, validationResult: RouteValidationResultFactory) => { + value: T; + error?: never; +} | { + value?: never; + error: RouteValidationError; +}; + +// @public +export interface RouteValidationResultFactory { + // (undocumented) + badRequest: (error: Error | string, path?: string[]) => { + error: RouteValidationError; + }; + // (undocumented) + ok: (value: T) => { + value: T; + }; +} + +// @public +export type RouteValidationSpec = ObjectType | Type | RouteValidationFunction; + +// @public +export interface RouteValidatorConfig { + body?: RouteValidationSpec; + params?: RouteValidationSpec

; + query?: RouteValidationSpec; +} + +// @public +export type RouteValidatorFullConfig = RouteValidatorConfig & RouteValidatorOptions; + +// @public +export interface RouteValidatorOptions { + unsafe?: { + params?: boolean; + query?: boolean; + body?: boolean; + }; +} + +// @public (undocumented) +export interface SavedObject { + attributes: T; + // (undocumented) + error?: { + message: string; + statusCode: number; + }; + id: string; + migrationVersion?: SavedObjectsMigrationVersion; + references: SavedObjectReference[]; + type: string; + updated_at?: string; + version?: string; +} + +// @public +export type SavedObjectAttribute = SavedObjectAttributeSingle | SavedObjectAttributeSingle[]; + +// @public +export interface SavedObjectAttributes { + // (undocumented) + [key: string]: SavedObjectAttribute; +} + +// @public +export type SavedObjectAttributeSingle = string | number | boolean | null | undefined | SavedObjectAttributes; + +// @public +export interface SavedObjectReference { + // (undocumented) + id: string; + // (undocumented) + name: string; + // (undocumented) + type: string; +} + +// @public (undocumented) +export interface SavedObjectsBaseOptions { + namespace?: string; +} + +// @public (undocumented) +export interface SavedObjectsBulkCreateObject { + // (undocumented) + attributes: T; + // (undocumented) + id?: string; + migrationVersion?: SavedObjectsMigrationVersion; + // (undocumented) + references?: SavedObjectReference[]; + // (undocumented) + type: string; +} + +// @public (undocumented) +export interface SavedObjectsBulkGetObject { + fields?: string[]; + // (undocumented) + id: string; + // (undocumented) + type: string; +} + +// @public (undocumented) +export interface SavedObjectsBulkResponse { + // (undocumented) + saved_objects: Array>; +} + +// @public (undocumented) +export interface SavedObjectsBulkResponse { + // (undocumented) + saved_objects: Array>; +} + +// @public (undocumented) +export interface SavedObjectsBulkUpdateObject extends Pick { + attributes: Partial; + id: string; + type: string; +} + +// @public (undocumented) +export interface SavedObjectsBulkUpdateOptions extends SavedObjectsBaseOptions { + refresh?: MutatingOperationRefreshSetting; +} + +// @public (undocumented) +export interface SavedObjectsBulkUpdateResponse { + // (undocumented) + saved_objects: Array>; +} + +// @public (undocumented) +export class SavedObjectsClient { + // @internal + constructor(repository: ISavedObjectsRepository); + bulkCreate(objects: Array>, options?: SavedObjectsCreateOptions): Promise>; + bulkGet(objects?: SavedObjectsBulkGetObject[], options?: SavedObjectsBaseOptions): Promise>; + bulkUpdate(objects: Array>, options?: SavedObjectsBulkUpdateOptions): Promise>; + create(type: string, attributes: T, options?: SavedObjectsCreateOptions): Promise>; + delete(type: string, id: string, options?: SavedObjectsDeleteOptions): Promise<{}>; + // (undocumented) + static errors: typeof SavedObjectsErrorHelpers; + // (undocumented) + errors: typeof SavedObjectsErrorHelpers; + find(options: SavedObjectsFindOptions): Promise>; + get(type: string, id: string, options?: SavedObjectsBaseOptions): Promise>; + update(type: string, id: string, attributes: Partial, options?: SavedObjectsUpdateOptions): Promise>; +} + +// @public +export type SavedObjectsClientContract = Pick; + +// @public +export type SavedObjectsClientFactory = ({ request, }: { + request: Request; +}) => SavedObjectsClientContract; + +// @public +export interface SavedObjectsClientProviderOptions { + // (undocumented) + excludedWrappers?: string[]; +} + +// @public +export type SavedObjectsClientWrapperFactory = (options: SavedObjectsClientWrapperOptions) => SavedObjectsClientContract; + +// @public +export interface SavedObjectsClientWrapperOptions { + // (undocumented) + client: SavedObjectsClientContract; + // (undocumented) + request: Request; +} + +// @public (undocumented) +export interface SavedObjectsCreateOptions extends SavedObjectsBaseOptions { + id?: string; + migrationVersion?: SavedObjectsMigrationVersion; + overwrite?: boolean; + // (undocumented) + references?: SavedObjectReference[]; + refresh?: MutatingOperationRefreshSetting; +} + +// @public (undocumented) +export interface SavedObjectsDeleteByNamespaceOptions extends SavedObjectsBaseOptions { + refresh?: MutatingOperationRefreshSetting; +} + +// @public (undocumented) +export interface SavedObjectsDeleteOptions extends SavedObjectsBaseOptions { + refresh?: MutatingOperationRefreshSetting; +} + +// @public (undocumented) +export class SavedObjectsErrorHelpers { + // (undocumented) + static createBadRequestError(reason?: string): DecoratedError; + // (undocumented) + static createEsAutoCreateIndexError(): DecoratedError; + // (undocumented) + static createGenericNotFoundError(type?: string | null, id?: string | null): DecoratedError; + // (undocumented) + static createInvalidVersionError(versionInput?: string): DecoratedError; + // (undocumented) + static createUnsupportedTypeError(type: string): DecoratedError; + // (undocumented) + static decorateBadRequestError(error: Error, reason?: string): DecoratedError; + // (undocumented) + static decorateConflictError(error: Error, reason?: string): DecoratedError; + // (undocumented) + static decorateEsUnavailableError(error: Error, reason?: string): DecoratedError; + // (undocumented) + static decorateForbiddenError(error: Error, reason?: string): DecoratedError; + // (undocumented) + static decorateGeneralError(error: Error, reason?: string): DecoratedError; + // (undocumented) + static decorateNotAuthorizedError(error: Error, reason?: string): DecoratedError; + // (undocumented) + static decorateRequestEntityTooLargeError(error: Error, reason?: string): DecoratedError; + // (undocumented) + static isBadRequestError(error: Error | DecoratedError): boolean; + // (undocumented) + static isConflictError(error: Error | DecoratedError): boolean; + // (undocumented) + static isEsAutoCreateIndexError(error: Error | DecoratedError): boolean; + // (undocumented) + static isEsUnavailableError(error: Error | DecoratedError): boolean; + // (undocumented) + static isForbiddenError(error: Error | DecoratedError): boolean; + // (undocumented) + static isInvalidVersionError(error: Error | DecoratedError): boolean; + // (undocumented) + static isNotAuthorizedError(error: Error | DecoratedError): boolean; + // (undocumented) + static isNotFoundError(error: Error | DecoratedError): boolean; + // (undocumented) + static isRequestEntityTooLargeError(error: Error | DecoratedError): boolean; + // Warning: (ae-forgotten-export) The symbol "DecoratedError" needs to be exported by the entry point index.d.ts + // + // (undocumented) + static isSavedObjectsClientError(error: any): error is DecoratedError; +} + +// @public +export interface SavedObjectsExportOptions { + excludeExportDetails?: boolean; + exportSizeLimit: number; + includeReferencesDeep?: boolean; + namespace?: string; + objects?: Array<{ + id: string; + type: string; + }>; + savedObjectsClient: SavedObjectsClientContract; + search?: string; + types?: string[]; +} + +// @public +export interface SavedObjectsExportResultDetails { + exportedCount: number; + missingRefCount: number; + missingReferences: Array<{ + id: string; + type: string; + }>; +} + +// @public (undocumented) +export interface SavedObjectsFindOptions extends SavedObjectsBaseOptions { + // (undocumented) + defaultSearchOperator?: 'AND' | 'OR'; + fields?: string[]; + // (undocumented) + filter?: string; + // (undocumented) + hasReference?: { + type: string; + id: string; + }; + // (undocumented) + page?: number; + // (undocumented) + perPage?: number; + search?: string; + searchFields?: string[]; + // (undocumented) + sortField?: string; + // (undocumented) + sortOrder?: string; + // (undocumented) + type: string | string[]; +} + +// @public +export interface SavedObjectsFindResponse { + // (undocumented) + page: number; + // (undocumented) + per_page: number; + // (undocumented) + saved_objects: Array>; + // (undocumented) + total: number; +} + +// @public +export interface SavedObjectsImportConflictError { + // (undocumented) + type: 'conflict'; +} + +// @public +export interface SavedObjectsImportError { + // (undocumented) + error: SavedObjectsImportConflictError | SavedObjectsImportUnsupportedTypeError | SavedObjectsImportMissingReferencesError | SavedObjectsImportUnknownError; + // (undocumented) + id: string; + // (undocumented) + title?: string; + // (undocumented) + type: string; +} + +// @public +export interface SavedObjectsImportMissingReferencesError { + // (undocumented) + blocking: Array<{ + type: string; + id: string; + }>; + // (undocumented) + references: Array<{ + type: string; + id: string; + }>; + // (undocumented) + type: 'missing_references'; +} + +// @public +export interface SavedObjectsImportOptions { + // (undocumented) + namespace?: string; + // (undocumented) + objectLimit: number; + // (undocumented) + overwrite: boolean; + // (undocumented) + readStream: Readable; + // (undocumented) + savedObjectsClient: SavedObjectsClientContract; + // (undocumented) + supportedTypes: string[]; +} + +// @public +export interface SavedObjectsImportResponse { + // (undocumented) + errors?: SavedObjectsImportError[]; + // (undocumented) + success: boolean; + // (undocumented) + successCount: number; +} + +// @public +export interface SavedObjectsImportRetry { + // (undocumented) + id: string; + // (undocumented) + overwrite: boolean; + // (undocumented) + replaceReferences: Array<{ + type: string; + from: string; + to: string; + }>; + // (undocumented) + type: string; +} + +// @public +export interface SavedObjectsImportUnknownError { + // (undocumented) + message: string; + // (undocumented) + statusCode: number; + // (undocumented) + type: 'unknown'; +} + +// @public +export interface SavedObjectsImportUnsupportedTypeError { + // (undocumented) + type: 'unsupported_type'; +} + +// @public (undocumented) +export interface SavedObjectsIncrementCounterOptions extends SavedObjectsBaseOptions { + // (undocumented) + migrationVersion?: SavedObjectsMigrationVersion; + refresh?: MutatingOperationRefreshSetting; +} + +// @internal @deprecated (undocumented) +export interface SavedObjectsLegacyService { + // Warning: (ae-forgotten-export) The symbol "SavedObjectsClientProvider" needs to be exported by the entry point index.d.ts + // + // (undocumented) + addScopedSavedObjectsClientWrapperFactory: SavedObjectsClientProvider['addClientWrapperFactory']; + // (undocumented) + getSavedObjectsRepository(...rest: any[]): any; + // (undocumented) + getScopedSavedObjectsClient: SavedObjectsClientProvider['getClient']; + // (undocumented) + importExport: { + objectLimit: number; + importSavedObjects(options: SavedObjectsImportOptions): Promise; + resolveImportErrors(options: SavedObjectsResolveImportErrorsOptions): Promise; + getSortedObjectsForExport(options: SavedObjectsExportOptions): Promise; + }; + // (undocumented) + SavedObjectsClient: typeof SavedObjectsClient; + // (undocumented) + schema: SavedObjectsSchema; + // (undocumented) + setScopedSavedObjectsClientFactory: SavedObjectsClientProvider['setClientFactory']; + // (undocumented) + types: string[]; +} + +// @public (undocumented) +export interface SavedObjectsMigrationLogger { + // (undocumented) + debug: (msg: string) => void; + // (undocumented) + info: (msg: string) => void; + // (undocumented) + warning: (msg: string) => void; +} + +// @public +export interface SavedObjectsMigrationVersion { + // (undocumented) + [pluginName: string]: string; +} + +// Warning: (ae-missing-release-tag) "RawDoc" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface SavedObjectsRawDoc { + // (undocumented) + _id: string; + // (undocumented) + _primary_term?: number; + // (undocumented) + _seq_no?: number; + // (undocumented) + _source: any; + // (undocumented) + _type?: string; +} + +// @public (undocumented) +export class SavedObjectsRepository { + bulkCreate(objects: Array>, options?: SavedObjectsCreateOptions): Promise>; + bulkGet(objects?: SavedObjectsBulkGetObject[], options?: SavedObjectsBaseOptions): Promise>; + bulkUpdate(objects: Array>, options?: SavedObjectsBulkUpdateOptions): Promise>; + create(type: string, attributes: T, options?: SavedObjectsCreateOptions): Promise>; + // Warning: (ae-forgotten-export) The symbol "KibanaMigrator" needs to be exported by the entry point index.d.ts + // + // @internal + static createRepository(migrator: KibanaMigrator, schema: SavedObjectsSchema, config: LegacyConfig, indexName: string, callCluster: APICaller, extraTypes?: string[], injectedConstructor?: any): any; + delete(type: string, id: string, options?: SavedObjectsDeleteOptions): Promise<{}>; + deleteByNamespace(namespace: string, options?: SavedObjectsDeleteByNamespaceOptions): Promise; + // (undocumented) + find({ search, defaultSearchOperator, searchFields, hasReference, page, perPage, sortField, sortOrder, fields, namespace, type, filter, }: SavedObjectsFindOptions): Promise>; + get(type: string, id: string, options?: SavedObjectsBaseOptions): Promise>; + incrementCounter(type: string, id: string, counterFieldName: string, options?: SavedObjectsIncrementCounterOptions): Promise<{ + id: string; + type: string; + updated_at: string; + references: any; + version: string; + attributes: any; + }>; + update(type: string, id: string, attributes: Partial, options?: SavedObjectsUpdateOptions): Promise>; + } + +// @public +export interface SavedObjectsResolveImportErrorsOptions { + // (undocumented) + namespace?: string; + // (undocumented) + objectLimit: number; + // (undocumented) + readStream: Readable; + // (undocumented) + retries: SavedObjectsImportRetry[]; + // (undocumented) + savedObjectsClient: SavedObjectsClientContract; + // (undocumented) + supportedTypes: string[]; +} + +// @internal (undocumented) +export class SavedObjectsSchema { + // Warning: (ae-forgotten-export) The symbol "SavedObjectsSchemaDefinition" needs to be exported by the entry point index.d.ts + constructor(schemaDefinition?: SavedObjectsSchemaDefinition); + // (undocumented) + getConvertToAliasScript(type: string): string | undefined; + // (undocumented) + getIndexForType(config: LegacyConfig, type: string): string | undefined; + // (undocumented) + isHiddenType(type: string): boolean; + // (undocumented) + isNamespaceAgnostic(type: string): boolean; +} + +// @internal (undocumented) +export class SavedObjectsSerializer { + constructor(schema: SavedObjectsSchema); + generateRawId(namespace: string | undefined, type: string, id?: string): string; + isRawSavedObject(rawDoc: SavedObjectsRawDoc): any; + // Warning: (ae-forgotten-export) The symbol "SanitizedSavedObjectDoc" needs to be exported by the entry point index.d.ts + rawToSavedObject(doc: SavedObjectsRawDoc): SanitizedSavedObjectDoc; + savedObjectToRaw(savedObj: SanitizedSavedObjectDoc): SavedObjectsRawDoc; + } + +// @public +export interface SavedObjectsServiceSetup { + addClientWrapper: (priority: number, id: string, factory: SavedObjectsClientWrapperFactory) => void; + createInternalRepository: (extraTypes?: string[]) => ISavedObjectsRepository; + createScopedRepository: (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository; + setClientFactory: (customClientFactory: SavedObjectsClientFactory) => void; +} + +// @public +export interface SavedObjectsServiceStart { + getScopedClient: (req: KibanaRequest, options?: SavedObjectsClientProviderOptions) => SavedObjectsClientContract; +} + +// @public (undocumented) +export interface SavedObjectsUpdateOptions extends SavedObjectsBaseOptions { + references?: SavedObjectReference[]; + refresh?: MutatingOperationRefreshSetting; + version?: string; +} + +// @public (undocumented) +export interface SavedObjectsUpdateResponse extends Omit, 'attributes' | 'references'> { + // (undocumented) + attributes: Partial; + // (undocumented) + references: SavedObjectReference[] | undefined; +} + +// @public +export class ScopedClusterClient implements IScopedClusterClient { + constructor(internalAPICaller: APICaller, scopedAPICaller: APICaller, headers?: Headers | undefined); + callAsCurrentUser(endpoint: string, clientParams?: Record, options?: CallAPIOptions): Promise; + callAsInternalUser(endpoint: string, clientParams?: Record, options?: CallAPIOptions): Promise; + } + +// @public +export interface SessionCookieValidationResult { + isValid: boolean; + path?: string; +} + +// @public +export interface SessionStorage { + clear(): void; + get(): Promise; + set(sessionValue: T): void; +} + +// @public +export interface SessionStorageCookieOptions { + encryptionKey: string; + isSecure: boolean; + name: string; + validate: (sessionValue: T | T[]) => SessionCookieValidationResult; +} + +// @public +export interface SessionStorageFactory { + // (undocumented) + asScoped: (request: KibanaRequest) => SessionStorage; +} + +// @public (undocumented) +export type SharedGlobalConfig = RecursiveReadonly_2<{ + kibana: Pick; + elasticsearch: Pick; + path: Pick; +}>; + +// @public +export interface UiSettingsParams { + category?: string[]; + description?: string; + name?: string; + optionLabels?: Record; + options?: string[]; + readonly?: boolean; + requiresPageReload?: boolean; + type?: UiSettingsType; + value?: SavedObjectAttribute; +} + +// @public (undocumented) +export interface UiSettingsServiceSetup { + register(settings: Record): void; +} + +// @public (undocumented) +export interface UiSettingsServiceStart { + asScopedToClient(savedObjectsClient: SavedObjectsClientContract): IUiSettingsClient; +} + +// @public +export type UiSettingsType = 'json' | 'markdown' | 'number' | 'select' | 'boolean' | 'string'; + +// @public +export interface UserProvidedValues { + // (undocumented) + isOverridden?: boolean; + // (undocumented) + userValue?: T; +} + +// @public +export interface UuidServiceSetup { + getInstanceUuid(): string; +} + +// @public +export const validBodyOutput: readonly ["data", "stream"]; + + +// Warnings were encountered during analysis: +// +// src/core/server/http/router/response.ts:316:3 - (ae-forgotten-export) The symbol "KibanaResponse" needs to be exported by the entry point index.d.ts +// src/core/server/legacy/types.ts:158:3 - (ae-forgotten-export) The symbol "VarsProvider" needs to be exported by the entry point index.d.ts +// src/core/server/legacy/types.ts:159:3 - (ae-forgotten-export) The symbol "VarsReplacer" needs to be exported by the entry point index.d.ts +// src/core/server/legacy/types.ts:160:3 - (ae-forgotten-export) The symbol "LegacyNavLinkSpec" needs to be exported by the entry point index.d.ts +// src/core/server/legacy/types.ts:161:3 - (ae-forgotten-export) The symbol "LegacyAppSpec" needs to be exported by the entry point index.d.ts +// src/core/server/legacy/types.ts:162:16 - (ae-forgotten-export) The symbol "LegacyPluginSpec" needs to be exported by the entry point index.d.ts +// src/core/server/plugins/plugins_service.ts:43:5 - (ae-forgotten-export) The symbol "InternalPluginInfo" needs to be exported by the entry point index.d.ts +// src/core/server/plugins/types.ts:221:3 - (ae-forgotten-export) The symbol "KibanaConfigType" needs to be exported by the entry point index.d.ts +// src/core/server/plugins/types.ts:221:3 - (ae-forgotten-export) The symbol "SharedGlobalConfigKeys" needs to be exported by the entry point index.d.ts +// src/core/server/plugins/types.ts:222:3 - (ae-forgotten-export) The symbol "ElasticsearchConfigType" needs to be exported by the entry point index.d.ts +// src/core/server/plugins/types.ts:223:3 - (ae-forgotten-export) The symbol "PathConfigType" needs to be exported by the entry point index.d.ts + +``` diff --git a/src/dev/run_check_core_api_changes.ts b/src/dev/run_check_core_api_changes.ts index ccf92cc432d2b..56664477df491 100644 --- a/src/dev/run_check_core_api_changes.ts +++ b/src/dev/run_check_core_api_changes.ts @@ -35,6 +35,7 @@ import getopts from 'getopts'; const apiExtractorConfig = (folder: string): ExtractorConfig => { const config: IConfigFile = { + newlineKind: 'lf', compiler: { tsconfigFilePath: '/tsconfig.json', }, diff --git a/yarn.lock b/yarn.lock index 58578bf7890e4..9a974926927c1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2246,64 +2246,61 @@ utils-error-reviver "^1.0.0" utils-error-to-json "^1.0.0" -"@microsoft/api-documenter@7.4.3": - version "7.4.3" - resolved "https://registry.yarnpkg.com/@microsoft/api-documenter/-/api-documenter-7.4.3.tgz#af5e69891c4b62e963a697127b69a25396d0123d" - integrity sha512-+RdqNNt9ssUCBAsLvKVPzbRD5RYd0HRgmnhRuEr+eZg2tj2tqfP9AhLWK5SBVn68CX5hyeWHEnQJ4HikDTZN8A== - dependencies: - "@microsoft/api-extractor-model" "7.4.1" - "@microsoft/node-core-library" "3.14.2" - "@microsoft/ts-command-line" "4.2.8" +"@microsoft/api-documenter@7.7.2": + version "7.7.2" + resolved "https://registry.yarnpkg.com/@microsoft/api-documenter/-/api-documenter-7.7.2.tgz#b6897f052ad447d6bb74f806287e8846c64691da" + integrity sha512-4mWE5G3grYd4PX5D6awiKa3B3GOXumkyGspgeTwlOBxrmj0FuVFRNPVZxGU0NqYnaw/bW4cg4ftUnSDzycrW+A== + dependencies: + "@microsoft/api-extractor-model" "7.7.0" + "@microsoft/node-core-library" "3.18.0" + "@microsoft/ts-command-line" "4.3.5" "@microsoft/tsdoc" "0.12.14" colors "~1.2.1" js-yaml "~3.13.1" resolve "1.8.1" -"@microsoft/api-extractor-model@7.4.1": - version "7.4.1" - resolved "https://registry.yarnpkg.com/@microsoft/api-extractor-model/-/api-extractor-model-7.4.1.tgz#3376f72570d336960c9b7b0dd44c8a0dbbe34604" - integrity sha512-rBO/QbrOMCdL8e9qwhIu1aH4C5sKOnUO1YhEh3+kVieFzTjiRnync7ghyQOtCaCVl2VXtp4LuOIv02e82oRqUg== +"@microsoft/api-extractor-model@7.7.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@microsoft/api-extractor-model/-/api-extractor-model-7.7.0.tgz#a5e86a638fa3fea283aeebc4785d8150652f30c6" + integrity sha512-9yrSr9LpdNnx7X8bXVb/YbcQopizsr43McAG7Xno5CMNFzbSkmIr8FJL0L+WGfrSWSTms9Bngfz7d1ScP6zbWQ== dependencies: - "@microsoft/node-core-library" "3.14.2" + "@microsoft/node-core-library" "3.18.0" "@microsoft/tsdoc" "0.12.14" - "@types/node" "8.5.8" -"@microsoft/api-extractor@7.4.2": - version "7.4.2" - resolved "https://registry.yarnpkg.com/@microsoft/api-extractor/-/api-extractor-7.4.2.tgz#440023cf05c69840e054cdb5f85cab9680227a40" - integrity sha512-O8OEaFvsvWEuwkOcVyWegIAFDY6TBZBvSIoOKLsSQYiQZtryGf13e2ym83iewhbUN7RmuOJtyQUKlBvcJbpgQA== +"@microsoft/api-extractor@7.7.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@microsoft/api-extractor/-/api-extractor-7.7.0.tgz#1550a5b88ca927d57e9c9698356a2f9375c5984c" + integrity sha512-1ngy95VA1s7GTE+bkS7QoYTg/TZs54CdJ46uAhl6HlyDJut4p/aH46W70g2XQs9VniIymW1Qe6fqNmcQUx5CVg== dependencies: - "@microsoft/api-extractor-model" "7.4.1" - "@microsoft/node-core-library" "3.14.2" - "@microsoft/ts-command-line" "4.2.8" + "@microsoft/api-extractor-model" "7.7.0" + "@microsoft/node-core-library" "3.18.0" + "@microsoft/ts-command-line" "4.3.5" "@microsoft/tsdoc" "0.12.14" colors "~1.2.1" lodash "~4.17.15" resolve "1.8.1" source-map "~0.6.1" - typescript "~3.5.3" + typescript "~3.7.2" -"@microsoft/node-core-library@3.14.2": - version "3.14.2" - resolved "https://registry.yarnpkg.com/@microsoft/node-core-library/-/node-core-library-3.14.2.tgz#255d421963f2d447a19f935e3c8eb3053e8e381b" - integrity sha512-bd8XhqhIvXsWg/SSNsZJdJxkN8Ucj7XKQkRe4cdYiKqpVdAREvW/shw8AoZIdgvjLI53029I/MO2Wn/AjGD3Jw== +"@microsoft/node-core-library@3.18.0": + version "3.18.0" + resolved "https://registry.yarnpkg.com/@microsoft/node-core-library/-/node-core-library-3.18.0.tgz#9a9123354b3e067bb8a975ba791959ffee1322ed" + integrity sha512-VzzSHtcwgHVW1xbHqpngfn+OS1trAZ1Tw3XXBlMsEKe7Wz7FF2gLr0hZa6x9Pemk5pkd4tu4+GTSOJjCKGjrgg== dependencies: - "@types/fs-extra" "5.0.4" - "@types/jju" "~1.4.0" - "@types/node" "8.5.8" - "@types/z-schema" "3.16.31" + "@types/node" "8.10.54" colors "~1.2.1" fs-extra "~7.0.1" jju "~1.4.0" + semver "~5.3.0" + timsort "~0.3.0" z-schema "~3.18.3" -"@microsoft/ts-command-line@4.2.8": - version "4.2.8" - resolved "https://registry.yarnpkg.com/@microsoft/ts-command-line/-/ts-command-line-4.2.8.tgz#92f4c85d0a4b893090fe6605f255e272b270495e" - integrity sha512-K4sc8/OJ/y5uQPWJFACMExS2UIqF+t3vdQ2A9Mhl9tMsp70CXf0sp6Y9ENYju1K7XWwR5Clh8dkP2jO1Ntlg1g== +"@microsoft/ts-command-line@4.3.5": + version "4.3.5" + resolved "https://registry.yarnpkg.com/@microsoft/ts-command-line/-/ts-command-line-4.3.5.tgz#78026d20244f39978d3397849ac8c40c0c2d4079" + integrity sha512-CN3j86apNOmllUmeJ0AyRfTYA2BP2xlnfgmnyp1HWLqcJmR/zLe/fk/+gohGnNt7o5/qHta3681LQhO2Yy3GFw== dependencies: "@types/argparse" "1.0.33" - "@types/node" "8.5.8" argparse "~1.0.9" colors "~1.2.1" @@ -3498,13 +3495,6 @@ resolved "https://registry.yarnpkg.com/@types/file-saver/-/file-saver-2.0.0.tgz#cbb49815a5e1129d5f23836a98d65d93822409af" integrity sha512-dxdRrUov2HVTbSRFX+7xwUPlbGYVEZK6PrSqClg2QPos3PNe0bCajkDDkDeeC1znjSH03KOEqVbXpnJuWa2wgQ== -"@types/fs-extra@5.0.4": - version "5.0.4" - resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-5.0.4.tgz#b971134d162cc0497d221adde3dbb67502225599" - integrity sha512-DsknoBvD8s+RFfSGjmERJ7ZOP1HI0UZRA3FSI+Zakhrc/Gy26YQsLI+m5V5DHxroHRJqCDLKJp7Hixn8zyaF7g== - dependencies: - "@types/node" "*" - "@types/geojson@*": version "7946.0.7" resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.7.tgz#c8fa532b60a0042219cdf173ca21a975ef0666ad" @@ -3703,11 +3693,6 @@ dependencies: "@types/jest-diff" "*" -"@types/jju@~1.4.0": - version "1.4.1" - resolved "https://registry.yarnpkg.com/@types/jju/-/jju-1.4.1.tgz#0a39f5f8e84fec46150a7b9ca985c3f89ad98e9f" - integrity sha512-LFt+YA7Lv2IZROMwokZKiPNORAV5N3huMs3IKnzlE430HWhWYZ8b+78HiwJXJJP1V2IEjinyJURuRJfGoaFSIA== - "@types/joi@*", "@types/joi@^13.4.2": version "13.6.1" resolved "https://registry.yarnpkg.com/@types/joi/-/joi-13.6.1.tgz#325486a397504f8e22c8c551dc8b0e1d41d5d5ae" @@ -3904,7 +3889,7 @@ dependencies: "@types/node" "*" -"@types/node@*", "@types/node@10.12.27", "@types/node@8.5.8", "@types/node@>=8.9.0", "@types/node@^10.12.27", "@types/node@^12.0.2": +"@types/node@*", "@types/node@10.12.27", "@types/node@8.10.54", "@types/node@>=8.9.0", "@types/node@^10.12.27", "@types/node@^12.0.2": version "10.12.27" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.27.tgz#eb3843f15d0ba0986cc7e4d734d2ee8b50709ef8" integrity sha512-e9wgeY6gaY21on3ve0xAjgBVjGDWq/xUteK0ujsE53bUoxycMkqfnkUgMt6ffZtykZ5X12Mg3T7Pw4TRCObDKg== @@ -4444,11 +4429,6 @@ dependencies: "@types/yargs-parser" "*" -"@types/z-schema@3.16.31": - version "3.16.31" - resolved "https://registry.yarnpkg.com/@types/z-schema/-/z-schema-3.16.31.tgz#2eb1d00a5e4ec3fa58c76afde12e182b66dc5c1c" - integrity sha1-LrHQCl5Ow/pYx2r94S4YK2bcXBw= - "@types/zen-observable@^0.8.0": version "0.8.0" resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d" @@ -5600,14 +5580,7 @@ are-we-there-yet@~1.1.2: delegates "^1.0.0" readable-stream "^2.0.6" -argparse@^1.0.7: - version "1.0.9" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86" - integrity sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY= - dependencies: - sprintf-js "~1.0.2" - -argparse@~1.0.9: +argparse@^1.0.7, argparse@~1.0.9: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== @@ -8553,12 +8526,12 @@ commander@3.0.2: resolved "https://registry.yarnpkg.com/commander/-/commander-3.0.2.tgz#6837c3fb677ad9933d1cfba42dd14d5117d6b39e" integrity sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow== -commander@^2.13.0, commander@^2.15.1, commander@^2.16.0, commander@^2.19.0, commander@^2.20.0, commander@^2.7.1: +commander@^2.13.0, commander@^2.15.1, commander@^2.16.0, commander@^2.19.0, commander@^2.20.0: version "2.20.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ== -commander@^2.5.0: +commander@^2.5.0, commander@^2.7.1: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== @@ -11837,21 +11810,16 @@ esprima@^3.1.3, esprima@~3.1.0: resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" integrity sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM= -esprima@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804" - integrity sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw== +esprima@^4.0.0, esprima@~4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== esprima@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/esprima/-/esprima-1.0.4.tgz#9f557e08fc3b4d26ece9dd34f8fbf476b62585ad" integrity sha1-n1V+CPw7TSbs6d00+Pv0drYlha0= -esprima@~4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - esquery@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.1.tgz#406c51658b1f5991a5f9b62b1dc25b00e3e5c708" @@ -14180,7 +14148,7 @@ got@^8.3.1, got@^8.3.2: url-parse-lax "^3.0.0" url-to-options "^1.0.1" -graceful-fs@4.X, graceful-fs@^4.0.0, graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.4, graceful-fs@^4.1.6: +graceful-fs@4.X, graceful-fs@^4.0.0, graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.4: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" integrity sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg= @@ -14190,6 +14158,11 @@ graceful-fs@^4.1.15, graceful-fs@^4.1.9: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA== +graceful-fs@^4.1.2, graceful-fs@^4.1.6: + version "4.2.3" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" + integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== + graceful-fs@^4.2.0, graceful-fs@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.2.tgz#6f0952605d0140c1cfdb138ed005775b92d67b02" @@ -21785,12 +21758,7 @@ path-key@^3.0.0, path-key@^3.1.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.0.tgz#99a10d870a803bdd5ee6f0470e58dfcd2f9a54d3" integrity sha512-8cChqz0RP6SHJkMt48FW0A7+qUOn+OsnOsVtzI59tZ8m+5bCSk7hzwET0pulwOM2YMn9J1efb07KB9l9f30SGg== -path-parse@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" - integrity sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME= - -path-parse@^1.0.6: +path-parse@^1.0.5, path-parse@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== @@ -27663,6 +27631,11 @@ timm@^1.6.1: resolved "https://registry.yarnpkg.com/timm/-/timm-1.6.1.tgz#5f8aafc932248c76caf2c6af60542a32d3c30701" integrity sha512-hqDTYi/bWuDxL2i6T3v6nrvkAQ/1Bc060GSkVEQZp02zTSTB4CHSKsOkliequCftQaNRcjRqUZmpGWs5FfhrNg== +timsort@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" + integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= + tiny-emitter@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.0.2.tgz#82d27468aca5ade8e5fd1e6d22b57dd43ebdfb7c" @@ -28657,7 +28630,7 @@ typescript-fsa@^2.0.0, typescript-fsa@^2.5.0: resolved "https://registry.yarnpkg.com/typescript-fsa/-/typescript-fsa-2.5.0.tgz#1baec01b5e8f5f34c322679d1327016e9e294faf" integrity sha1-G67AG16PXzTDImedEycBbp4pT68= -typescript@3.5.3, typescript@3.7.2, typescript@^3.0.1, typescript@^3.0.3, typescript@^3.2.2, typescript@^3.3.3333, typescript@^3.4.5, typescript@~3.5.3: +typescript@3.5.3, typescript@3.7.2, typescript@^3.0.1, typescript@^3.0.3, typescript@^3.2.2, typescript@^3.3.3333, typescript@^3.4.5, typescript@~3.7.2: version "3.7.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.2.tgz#27e489b95fa5909445e9fef5ee48d81697ad18fb" integrity sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ== @@ -28956,9 +28929,9 @@ universal-user-agent@^2.0.0, universal-user-agent@^2.0.1: os-name "^3.0.0" universalify@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.1.tgz#fa71badd4437af4c148841e3b3b165f9e9e590b7" - integrity sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc= + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== unlazy-loader@^0.1.3: version "0.1.3" From 9d5603a29821029a986369692ba9bffd144c7a4b Mon Sep 17 00:00:00 2001 From: Marco Vettorello Date: Sat, 4 Jan 2020 13:38:11 +0100 Subject: [PATCH 03/17] Update dependency @elastic/charts to v16.0.2 (#52619) --- package.json | 3 +- .../np_ready/angular/directives/histogram.tsx | 13 +- .../timeseries/__mocks__/@elastic/charts.js | 3 - .../__snapshots__/area_decorator.test.js.snap | 13 +- .../__snapshots__/bar_decorator.test.js.snap | 13 +- .../timeseries/decorators/area_decorator.js | 10 +- .../timeseries/decorators/bar_decorator.js | 10 +- .../visualizations/views/timeseries/index.js | 15 +- .../__snapshots__/series_styles.test.js.snap | 9 - .../views/timeseries/utils/series_styles.js | 9 - .../timeseries/utils/series_styles.test.js | 10 +- test/functional/services/elastic_chart.ts | 2 +- .../components/metrics_explorer/chart.tsx | 14 +- .../metrics_explorer/series_chart.tsx | 25 +- .../sections/anomalies/chart.tsx | 21 +- .../sections/log_rate/bar_chart.tsx | 10 +- .../metrics/components/chart_section_vis.tsx | 14 +- .../pages/metrics/components/series_chart.tsx | 23 +- .../public/indexpattern_plugin/field_item.tsx | 22 +- .../__snapshots__/xy_expression.test.tsx.snap | 56 ++--- .../xy_expression.test.tsx | 5 + .../xy_visualization_plugin/xy_expression.tsx | 8 +- .../document_count_chart.tsx | 8 +- .../metric_distribution_chart.tsx | 13 +- .../charts/anomaly_chart/anomalies.tsx | 12 +- .../components/charts/anomaly_chart/line.tsx | 7 +- .../charts/anomaly_chart/model_bounds.tsx | 7 +- .../charts/anomaly_chart/scatter.tsx | 7 +- .../pages/components/charts/common/axes.tsx | 11 +- .../pages/components/charts/common/utils.ts | 11 - .../event_rate_chart/event_rate_chart.tsx | 9 +- .../public/components/charts/areachart.tsx | 14 +- .../public/components/charts/barchart.tsx | 23 +- .../public/components/charts/common.test.tsx | 22 -- .../siem/public/components/charts/common.tsx | 25 +- .../histogram_signals/index.tsx | 12 +- .../monitor_bar_series.test.tsx.snap | 15 +- .../functional/charts/checks_chart.tsx | 15 +- .../functional/charts/duration_chart.tsx | 6 +- .../charts/duration_line_series_list.tsx | 7 +- .../functional/charts/get_colors_map.ts | 23 -- .../functional/charts/monitor_bar_series.tsx | 11 +- .../functional/charts/snapshot_histogram.tsx | 36 +-- .../watch_visualization.tsx | 16 +- yarn.lock | 226 +++++------------- 45 files changed, 248 insertions(+), 596 deletions(-) delete mode 100644 x-pack/legacy/plugins/uptime/public/components/functional/charts/get_colors_map.ts diff --git a/package.json b/package.json index 5b90b9b08ea29..4f8229333e5a0 100644 --- a/package.json +++ b/package.json @@ -88,6 +88,7 @@ "**/isomorphic-git/**/base64-js": "^1.2.1", "**/image-diff/gm/debug": "^2.6.9", "**/react-dom": "^16.12.0", + "**/react": "^16.12.0", "**/react-test-renderer": "^16.12.0", "**/deepmerge": "^4.2.2", "**/serialize-javascript": "^2.1.1" @@ -113,7 +114,7 @@ "@babel/core": "^7.5.5", "@babel/register": "^7.7.0", "@elastic/apm-rum": "^4.6.0", - "@elastic/charts": "^14.0.0", + "@elastic/charts": "^16.0.2", "@elastic/datemath": "5.0.2", "@elastic/ems-client": "1.0.5", "@elastic/eui": "17.3.1", diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/histogram.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/histogram.tsx index 28ce64c0a5f9c..c2f716ff6c45a 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/histogram.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/histogram.tsx @@ -30,9 +30,6 @@ import { Chart, HistogramBarSeries, GeometryValue, - getAnnotationId, - getAxisId, - getSpecId, LineAnnotation, Position, ScaleType, @@ -237,20 +234,20 @@ export class DiscoverHistogram extends Component `id:${x}`; -export const getGroupId = x => `groupId:${x}`; - export const BarSeries = () => null; export const AreaSeries = () => null; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/__snapshots__/area_decorator.test.js.snap b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/__snapshots__/area_decorator.test.js.snap index 822de4cef0813..56504ca11ca39 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/__snapshots__/area_decorator.test.js.snap +++ b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/__snapshots__/area_decorator.test.js.snap @@ -24,12 +24,9 @@ exports[`src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/ } curve={9} customSeriesColors={ - Map { - Object { - "colorValues": Array [], - "specId": "id:61ca57f1-469d-11e7-af02-69e470af7417:Rome", - } => "rgb(0, 156, 224)", - } + Array [ + "rgb(0, 156, 224)", + ] } data={ Array [ @@ -44,10 +41,10 @@ exports[`src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/ ] } enableHistogramMode={true} - groupId="groupId:yaxis_main_group" + groupId="yaxis_main_group" hideInLegend={false} histogramModeAlignment="center" - id="id:61ca57f1-469d-11e7-af02-69e470af7417:Rome" + id="61ca57f1-469d-11e7-af02-69e470af7417:Rome" name="Rome" stackAsPercentage={false} timeZone="local" diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/__snapshots__/bar_decorator.test.js.snap b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/__snapshots__/bar_decorator.test.js.snap index 78133f2dda7cc..6317973cef536 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/__snapshots__/bar_decorator.test.js.snap +++ b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/__snapshots__/bar_decorator.test.js.snap @@ -16,12 +16,9 @@ exports[`src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/ } } customSeriesColors={ - Map { - Object { - "colorValues": Array [], - "specId": "id:61ca57f1-469d-11e7-af02-69e470af7417:Rome", - } => "rgb(0, 156, 224)", - } + Array [ + "rgb(0, 156, 224)", + ] } data={ Array [ @@ -36,10 +33,10 @@ exports[`src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/ ] } enableHistogramMode={true} - groupId="groupId:yaxis_main_group" + groupId="yaxis_main_group" hideInLegend={false} histogramModeAlignment="center" - id="id:61ca57f1-469d-11e7-af02-69e470af7417:Rome" + id="61ca57f1-469d-11e7-af02-69e470af7417:Rome" name="Rome" stackAsPercentage={false} timeZone="local" diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/area_decorator.js b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/area_decorator.js index 536064139e6ea..411c0813cad7c 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/area_decorator.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/area_decorator.js @@ -18,8 +18,8 @@ */ import React from 'react'; -import { getSpecId, getGroupId, ScaleType, AreaSeries } from '@elastic/charts'; -import { getSeriesColors, getAreaStyles } from '../utils/series_styles'; +import { ScaleType, AreaSeries } from '@elastic/charts'; +import { getAreaStyles } from '../utils/series_styles'; import { ChartsEntities } from '../model/charts'; import { X_ACCESSOR_INDEX, Y_ACCESSOR_INDEXES } from '../../../constants'; @@ -41,9 +41,9 @@ export function AreaSeriesDecorator({ useDefaultGroupDomain, sortIndex, }) { - const id = getSpecId(seriesId); - const groupId = getGroupId(seriesGroupId); - const customSeriesColors = getSeriesColors(color, id); + const id = seriesId; + const groupId = seriesGroupId; + const customSeriesColors = [color]; const areaSeriesStyle = getAreaStyles({ points, lines, color }); const seriesSettings = { diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/bar_decorator.js b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/bar_decorator.js index 3dbe04dca06b8..9cc8931b48d9f 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/bar_decorator.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/bar_decorator.js @@ -18,8 +18,8 @@ */ import React from 'react'; -import { getSpecId, getGroupId, ScaleType, BarSeries } from '@elastic/charts'; -import { getSeriesColors, getBarStyles } from '../utils/series_styles'; +import { ScaleType, BarSeries } from '@elastic/charts'; +import { getBarStyles } from '../utils/series_styles'; import { ChartsEntities } from '../model/charts'; import { X_ACCESSOR_INDEX, Y_ACCESSOR_INDEXES } from '../../../constants'; @@ -40,9 +40,9 @@ export function BarSeriesDecorator({ useDefaultGroupDomain, sortIndex, }) { - const id = getSpecId(seriesId); - const groupId = getGroupId(seriesGroupId); - const customSeriesColors = getSeriesColors(color, id); + const id = seriesId; + const groupId = seriesGroupId; + const customSeriesColors = [color]; const barSeriesStyle = getBarStyles(bars, color); const seriesSettings = { diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/index.js b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/index.js index a02ea83e5104b..bcd0b6314cef1 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/index.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/index.js @@ -25,11 +25,8 @@ import { Chart, Position, Settings, - getAxisId, - getGroupId, DARK_THEME, LIGHT_THEME, - getAnnotationId, AnnotationDomainTypes, LineAnnotation, TooltipType, @@ -75,7 +72,7 @@ export const TimeSeries = ({ const chartRef = useRef(); const updateCursor = (_, cursor) => { if (chartRef.current) { - chartRef.current.dispatchExternalCursorEvent(cursor); + chartRef.current.dispatchExternalPointerEvent(cursor); } }; @@ -99,7 +96,7 @@ export const TimeSeries = ({ legendPosition={legendPosition} onBrushEnd={onBrush} animateData={false} - onCursorUpdate={handleCursorUpdate} + onPointerUpdate={handleCursorUpdate} theme={ hasBarChart ? {} @@ -126,7 +123,7 @@ export const TimeSeries = ({ return ( } @@ -213,8 +210,8 @@ export const TimeSeries = ({ {yAxis.map(({ id, groupId, position, tickFormatter, domain, hide }) => ( "rgb(224, 0, 221)", -} -`; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/series_styles.js b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/series_styles.js index 63be14790c6c5..2891751f121ca 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/series_styles.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/series_styles.js @@ -56,12 +56,3 @@ export const getBarStyles = ({ show = true, lineWidth = 0, fill = 1 }, color) => }, }, }); - -export const getSeriesColors = (color, specId) => { - const map = new Map(); - const seriesColorsValues = { specId, colorValues: [] }; - - map.set(seriesColorsValues, color); - - return map; -}; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/series_styles.test.js b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/series_styles.test.js index ac0a7610f2660..c04ec457d1523 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/series_styles.test.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/series_styles.test.js @@ -17,12 +17,11 @@ * under the License. */ -import { getBarStyles, getSeriesColors, getAreaStyles } from './series_styles'; +import { getBarStyles, getAreaStyles } from './series_styles'; describe('src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/utils/series_styles.js', () => { let bars; let color; - let specId; let points; let lines; @@ -33,7 +32,6 @@ describe('src/legacy/core_plugins/metrics/public/visualizations/views/timeseries show: true, }; color = 'rgb(224, 0, 221)'; - specId = 'IT'; points = { lineWidth: 1, show: true, @@ -60,12 +58,6 @@ describe('src/legacy/core_plugins/metrics/public/visualizations/views/timeseries }); }); - describe('getSeriesColors()', () => { - test('should match a snapshot', () => { - expect(getSeriesColors(color, specId)).toMatchSnapshot(); - }); - }); - describe('getAreaStyles()', () => { test('should match a snapshot', () => { expect(getAreaStyles({ points, lines, color })).toMatchSnapshot(); diff --git a/test/functional/services/elastic_chart.ts b/test/functional/services/elastic_chart.ts index 4f4dbcba5f0b8..afae3f830b3bf 100644 --- a/test/functional/services/elastic_chart.ts +++ b/test/functional/services/elastic_chart.ts @@ -37,7 +37,7 @@ export function ElasticChartProvider({ getService }: FtrProviderContext) { public async getVisualizationRenderingCount(dataTestSubj: string) { const chart = await testSubjects.find(dataTestSubj); - const visContainer = await chart.findByCssSelector('.echChart'); + const visContainer = await chart.findByCssSelector('.echChartStatus'); const renderingCount = await visContainer.getAttribute('data-ech-render-count'); return Number(renderingCount); } diff --git a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/chart.tsx b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/chart.tsx index a700a455cd2bb..9491fe3024f94 100644 --- a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/chart.tsx +++ b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/chart.tsx @@ -7,15 +7,7 @@ import React, { useCallback, useMemo } from 'react'; import { EuiTitle, EuiToolTip, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { - Axis, - Chart, - getAxisId, - niceTimeFormatter, - Position, - Settings, - TooltipValue, -} from '@elastic/charts'; +import { Axis, Chart, niceTimeFormatter, Position, Settings, TooltipValue } from '@elastic/charts'; import { first, last } from 'lodash'; import moment from 'moment'; import { MetricsExplorerSeries } from '../../../server/routes/metrics_explorer/types'; @@ -139,13 +131,13 @@ export const MetricsExplorerChart = ({ /> ))} = { @@ -66,8 +56,8 @@ export const MetricsExplorerAreaChart = ({ metric, id, series, type, stack }: Pr }; return ( ); }; @@ -87,13 +77,6 @@ export const MetricsExplorerBarChart = ({ metric, id, series, stack }: Props) => colorTransformer(MetricsExplorerColor.color0); const yAccessor = `metric_${id}`; - const specId = getSpecId(yAccessor); - const colors: DataSeriesColorsValues = { - colorValues: [], - specId, - }; - const customColors: CustomSeriesColorsMap = new Map(); - customColors.set(colors, color); const chartId = `series-${series.id}-${yAccessor}`; const seriesBarStyle: RecursivePartial = { @@ -108,8 +91,8 @@ export const MetricsExplorerBarChart = ({ metric, id, series, stack }: Props) => }; return ( data={series.rows} stackAccessors={stack ? ['timestamp'] : void 0} barSeriesStyle={seriesBarStyle} - customSeriesColors={customColors} + customSeriesColors={[color]} /> ); }; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/chart.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/chart.tsx index e4100c6d774b1..a75e6c50ab03f 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/chart.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/chart.tsx @@ -9,14 +9,11 @@ import { Axis, BarSeries, Chart, - getAxisId, - getSpecId, niceTimeFormatter, Settings, TooltipValue, LIGHT_THEME, DARK_THEME, - getAnnotationId, RectAnnotation, } from '@elastic/charts'; import numeral from '@elastic/numeral'; @@ -44,7 +41,7 @@ export const AnomaliesChart: React.FunctionComponent<{ [timeRange] ); - const logEntryRateSpecId = getSpecId('averageValues'); + const logEntryRateSpecId = 'averageValues'; const tooltipProps = useMemo( () => ({ @@ -67,13 +64,13 @@ export const AnomaliesChart: React.FunctionComponent<{

numeral(value.toPrecision(3)).format('0[.][00]a')} // https://github.com/adamwdraper/Numeral-js/issues/194 /> @@ -102,7 +99,7 @@ export const AnomaliesChart: React.FunctionComponent<{ }; interface SeverityConfig { - annotationId: AnnotationId; + id: AnnotationId; style: { fill: string; opacity: number; @@ -111,19 +108,19 @@ interface SeverityConfig { const severityConfigs: Record = { warning: { - annotationId: getAnnotationId(`anomalies-warning`), + id: `anomalies-warning`, style: { fill: 'rgb(125, 180, 226)', opacity: 0.7 }, }, minor: { - annotationId: getAnnotationId(`anomalies-minor`), + id: `anomalies-minor`, style: { fill: 'rgb(255, 221, 0)', opacity: 0.7 }, }, major: { - annotationId: getAnnotationId(`anomalies-major`), + id: `anomalies-major`, style: { fill: 'rgb(229, 113, 0)', opacity: 0.7 }, }, critical: { - annotationId: getAnnotationId(`anomalies-critical`), + id: `anomalies-critical`, style: { fill: 'rgb(228, 72, 72)', opacity: 0.7 }, }, }; @@ -138,7 +135,7 @@ const renderAnnotations = ( diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/log_rate/bar_chart.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/log_rate/bar_chart.tsx index 5055e3fc08239..7969c61bc9f34 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/log_rate/bar_chart.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/log_rate/bar_chart.tsx @@ -8,8 +8,6 @@ import { Axis, BarSeries, Chart, - getAxisId, - getSpecId, niceTimeFormatter, Settings, TooltipValue, @@ -37,8 +35,6 @@ export const LogEntryRateBarChart: React.FunctionComponent<{ [timeRange] ); - const logEntryRateSpecId = getSpecId('averageValues'); - const tooltipProps = useMemo( () => ({ headerFormatter: (tooltipData: TooltipValue) => @@ -61,18 +57,18 @@ export const LogEntryRateBarChart: React.FunctionComponent<{
numeral(value.toPrecision(3)).format('0[.][00]a')} // https://github.com/adamwdraper/Numeral-js/issues/194 /> - + {metric && metric.series.map(series => ( { visible: true, }, }; - const colors: DataSeriesColorsValues = { - colorValues: [], - specId: getSpecId(id), - }; - const customColors: CustomSeriesColorsMap = new Map(); - customColors.set(colors, color || '#999'); return ( { yAccessors={['value']} data={series.data} areaSeriesStyle={style} - customSeriesColors={color ? customColors : void 0} + customSeriesColors={color ? [color] : void 0} stackAccessors={stack ? ['timestamp'] : void 0} /> ); @@ -79,15 +70,9 @@ export const BarChart = ({ id, color, series, name, type, stack }: Props) => { opacity: 1, }, }; - const colors: DataSeriesColorsValues = { - colorValues: [], - specId: getSpecId(id), - }; - const customColors: CustomSeriesColorsMap = new Map(); - customColors.set(colors, color || '#999'); return ( { yAccessors={['value']} data={series.data} barSeriesStyle={style} - customSeriesColors={customColors} + customSeriesColors={color ? [color] : void 0} stackAccessors={stack ? ['timestamp'] : void 0} /> ); diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.tsx index 20505107be122..4a58f0354882a 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.tsx @@ -26,9 +26,6 @@ import { Axis, BarSeries, Chart, - DataSeriesColorsValues, - getAxisId, - getSpecId, niceTimeFormatter, Position, ScaleType, @@ -391,18 +388,11 @@ function FieldItemPopoverContents(props: State & FieldItemProps) { } if (histogram && histogram.buckets.length) { - const specId = getSpecId( - i18n.translate('xpack.lens.indexPattern.fieldStatsCountLabel', { - defaultMessage: 'Count', - }) - ); - const colors: DataSeriesColorsValues = { - colorValues: [], - specId, - }; + const specId = i18n.translate('xpack.lens.indexPattern.fieldStatsCountLabel', { + defaultMessage: 'Count', + }); const expectedColor = getColorForDataType(field.type); - - const seriesColors = new Map([[colors, expectedColor]]); + const seriesColors = expectedColor ? [expectedColor] : undefined; if (field.type === 'date') { return wrapInPopover( @@ -422,7 +412,7 @@ function FieldItemPopoverContents(props: State & FieldItemProps) { /> formatter.convert(d)} diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/__snapshots__/xy_expression.test.tsx.snap b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/__snapshots__/xy_expression.test.tsx.snap index 5089a3b8a3a22..8df2d764c0208 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/__snapshots__/xy_expression.test.tsx.snap +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/__snapshots__/xy_expression.test.tsx.snap @@ -4,7 +4,7 @@ exports[`xy_expression XYChart component it renders area 1`] = ` - - - - - - - - - - - - - - - - - - - - - - - - - - - - { .find(Axis) .first() .prop('tickFormat'); + + if (!tickFormatter) { + throw new Error('tickFormatter prop not found'); + } + tickFormatter('I'); expect(convertSpy).toHaveBeenCalledWith('I'); diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx index 3c113eaca7ed1..32c1ace5b1770 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx @@ -12,8 +12,6 @@ import { Settings, Axis, LineSeries, - getAxisId, - getSpecId, AreaSeries, BarSeries, Position, @@ -205,7 +203,7 @@ export function XYChart({ data, args, formatFactory, timeZone }: XYChartRenderPr /> = ({ }} /> - + = ({ width, height, chartData, f tooltip={{ headerFormatter }} /> kibanaFieldFormat(d, fieldFormat)} /> - d.toFixed(3)} - hide={true} - /> + d.toFixed(3)} hide={true} /> = ({ anomalyData }) => { return ( = ({ chartData }) => { return ( = ({ chartData }) => { yScaleToDataExtent={false} curve={CurveType.CURVE_MONOTONE_X} lineSeriesStyle={lineSeriesStyle} - customSeriesColors={getCustomColor(SPEC_ID, LINE_COLOR)} + customSeriesColors={[LINE_COLOR]} /> ); }; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/anomaly_chart/model_bounds.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/anomaly_chart/model_bounds.tsx index 0d76b50b80b97..588bb58135643 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/anomaly_chart/model_bounds.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/anomaly_chart/model_bounds.tsx @@ -5,9 +5,8 @@ */ import React, { FC } from 'react'; -import { getSpecId, ScaleType, AreaSeries, CurveType } from '@elastic/charts'; +import { ScaleType, AreaSeries, CurveType } from '@elastic/charts'; import { ModelItem } from '../../../../common/results_loader'; -import { getCustomColor } from '../common/utils'; import { seriesStyle, MODEL_COLOR } from '../common/settings'; interface Props { @@ -33,7 +32,7 @@ export const ModelBounds: FC = ({ modelData }) => { const model = modelData === undefined ? [] : modelData; return ( = ({ modelData }) => { yScaleToDataExtent={false} curve={CurveType.CURVE_MONOTONE_X} areaSeriesStyle={areaSeriesStyle} - customSeriesColors={getCustomColor(SPEC_ID, MODEL_COLOR)} + customSeriesColors={[MODEL_COLOR]} /> ); }; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/anomaly_chart/scatter.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/anomaly_chart/scatter.tsx index 3a8fb9dbfb4d7..30148be62b835 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/anomaly_chart/scatter.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/anomaly_chart/scatter.tsx @@ -5,8 +5,7 @@ */ import React, { FC } from 'react'; -import { LineSeries, getSpecId, ScaleType, CurveType } from '@elastic/charts'; -import { getCustomColor } from '../common/utils'; +import { LineSeries, ScaleType, CurveType } from '@elastic/charts'; import { seriesStyle, LINE_COLOR } from '../common/settings'; interface Props { @@ -30,7 +29,7 @@ const scatterSeriesStyle = { export const Scatter: FC = ({ chartData }) => { return ( = ({ chartData }) => { yScaleToDataExtent={false} curve={CurveType.CURVE_MONOTONE_X} lineSeriesStyle={scatterSeriesStyle} - customSeriesColors={getCustomColor(SPEC_ID, LINE_COLOR)} + customSeriesColors={[LINE_COLOR]} /> ); }; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/common/axes.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/common/axes.tsx index d068609760da8..02e62a0dfcc96 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/common/axes.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/common/axes.tsx @@ -5,7 +5,7 @@ */ import React, { FC, Fragment } from 'react'; -import { Axis, getAxisId, Position, timeFormatter, niceTimeFormatByDay } from '@elastic/charts'; +import { Axis, Position, timeFormatter, niceTimeFormatByDay } from '@elastic/charts'; import { getYRange } from './utils'; import { LineChartPoint } from '../../../../common/chart_loader'; @@ -26,17 +26,12 @@ export const Axes: FC = ({ chartData }) => { return ( - + ); }; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/common/utils.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/common/utils.ts index 91f4c08c72f7a..4103c1520b5b1 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/common/utils.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/common/utils.ts @@ -3,17 +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 { getSpecId, CustomSeriesColorsMap, DataSeriesColorsValues } from '@elastic/charts'; - -export function getCustomColor(specId: string, color: string): CustomSeriesColorsMap { - const lineDataSeriesColorValues: DataSeriesColorsValues = { - colorValues: [], - specId: getSpecId(specId), - }; - return new Map([[lineDataSeriesColorValues, color]]); -} - export function getYRange(chartData: any[]) { if (chartData.length === 0) { return { min: 0, max: 0 }; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/event_rate_chart/event_rate_chart.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/event_rate_chart/event_rate_chart.tsx index 7130a24cffb7d..5423e80a82f15 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/event_rate_chart/event_rate_chart.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/event_rate_chart/event_rate_chart.tsx @@ -5,9 +5,8 @@ */ import React, { FC } from 'react'; -import { BarSeries, Chart, getSpecId, ScaleType, Settings, TooltipType } from '@elastic/charts'; +import { BarSeries, Chart, ScaleType, Settings, TooltipType } from '@elastic/charts'; import { Axes } from '../common/axes'; -import { getCustomColor } from '../common/utils'; import { LineChartPoint } from '../../../../common/chart_loader'; import { EVENT_RATE_COLOR } from '../common/settings'; import { LoadingWrapper } from '../loading_wrapper'; @@ -20,8 +19,6 @@ interface Props { loading?: boolean; } -const SPEC_ID = 'event_rate'; - export const EventRateChart: FC = ({ eventRateChartData, height, @@ -40,13 +37,13 @@ export const EventRateChart: FC = ({ diff --git a/x-pack/legacy/plugins/siem/public/components/charts/areachart.tsx b/x-pack/legacy/plugins/siem/public/components/charts/areachart.tsx index 297563c8e31cf..ba07a3f3436d9 100644 --- a/x-pack/legacy/plugins/siem/public/components/charts/areachart.tsx +++ b/x-pack/legacy/plugins/siem/public/components/charts/areachart.tsx @@ -9,8 +9,6 @@ import { Axis, AreaSeries, Chart, - getAxisId, - getSpecId, Position, ScaleType, Settings, @@ -26,7 +24,6 @@ import { ChartSeriesData, getChartHeight, getChartWidth, - getSeriesStyle, WrappedByAutoSizer, useTheme, useBrowserTimeZone, @@ -77,8 +74,8 @@ export const AreaChartBaseComponent = ({ const timeZone = useBrowserTimeZone(); const xTickFormatter = get('configs.axis.xTickFormatter', chartConfigs); const yTickFormatter = get('configs.axis.yTickFormatter', chartConfigs); - const xAxisId = getAxisId(`group-${data[0].key}-x`); - const yAxisId = getAxisId(`group-${data[0].key}-y`); + const xAxisId = `group-${data[0].key}-x`; + const yAxisId = `group-${data[0].key}-y`; const settings = { ...chartDefaultSettings, theme, @@ -90,20 +87,19 @@ export const AreaChartBaseComponent = ({ {data.map(series => { const seriesKey = series.key; - const seriesSpecId = getSpecId(seriesKey); return checkIfAllTheDataInTheSeriesAreValid(series) ? ( ) : null; })} diff --git a/x-pack/legacy/plugins/siem/public/components/charts/barchart.tsx b/x-pack/legacy/plugins/siem/public/components/charts/barchart.tsx index ee8b4eaf6b08c..db84d7dbd2c18 100644 --- a/x-pack/legacy/plugins/siem/public/components/charts/barchart.tsx +++ b/x-pack/legacy/plugins/siem/public/components/charts/barchart.tsx @@ -5,16 +5,7 @@ */ import React from 'react'; -import { - Chart, - BarSeries, - Axis, - Position, - getAxisId, - getSpecId, - ScaleType, - Settings, -} from '@elastic/charts'; +import { Chart, BarSeries, Axis, Position, ScaleType, Settings } from '@elastic/charts'; import { getOr, get, isNumber } from 'lodash/fp'; import { AutoSizer } from '../auto_sizer'; import { ChartPlaceHolder } from './chart_place_holder'; @@ -25,8 +16,6 @@ import { checkIfAllValuesAreZero, getChartHeight, getChartWidth, - getSeriesStyle, - SeriesType, WrappedByAutoSizer, useBrowserTimeZone, useTheme, @@ -59,8 +48,8 @@ export const BarChartBaseComponent = ({ const xTickFormatter = get('configs.axis.xTickFormatter', chartConfigs); const yTickFormatter = get('configs.axis.yTickFormatter', chartConfigs); const tickSize = getOr(0, 'configs.axis.tickSize', chartConfigs); - const xAxisId = getAxisId(`stat-items-barchart-${data[0].key}-x`); - const yAxisId = getAxisId(`stat-items-barchart-${data[0].key}-y`); + const xAxisId = `stat-items-barchart-${data[0].key}-x`; + const yAxisId = `stat-items-barchart-${data[0].key}-y`; const settings = { ...chartDefaultSettings, theme, @@ -72,11 +61,9 @@ export const BarChartBaseComponent = ({ {data.map(series => { const barSeriesKey = series.key; - const barSeriesSpecId = getSpecId(barSeriesKey); - const seriesType = SeriesType.BAR; return checkIfAllTheDataInTheSeriesAreValid ? ( ) : null; })} diff --git a/x-pack/legacy/plugins/siem/public/components/charts/common.test.tsx b/x-pack/legacy/plugins/siem/public/components/charts/common.test.tsx index e9df0d3885a18..3748a2505c115 100644 --- a/x-pack/legacy/plugins/siem/public/components/charts/common.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/charts/common.test.tsx @@ -13,8 +13,6 @@ import { defaultChartHeight, getChartHeight, getChartWidth, - getSeriesStyle, - SeriesType, WrappedByAutoSizer, ChartSeriesData, useTheme, @@ -41,26 +39,6 @@ describe('WrappedByAutoSizer', () => { }); }); -describe('getSeriesStyle', () => { - it('should not create style mapping if color is not given', () => { - const mockSeriesKey = 'mockSeriesKey'; - const color = ''; - const customSeriesColors = getSeriesStyle(mockSeriesKey, color, SeriesType.BAR); - expect(customSeriesColors).toBeUndefined(); - }); - - it('should create correct style mapping for series of a chart', () => { - const mockSeriesKey = 'mockSeriesKey'; - const color = 'red'; - const customSeriesColors = getSeriesStyle(mockSeriesKey, color, SeriesType.BAR); - const expectedKey = { colorValues: [mockSeriesKey] }; - customSeriesColors!.forEach((value, key) => { - expect(JSON.stringify(key)).toEqual(JSON.stringify(expectedKey)); - expect(value).toEqual(color); - }); - }); -}); - describe('getChartHeight', () => { it('should render customHeight', () => { const height = getChartHeight(10, 100); diff --git a/x-pack/legacy/plugins/siem/public/components/charts/common.tsx b/x-pack/legacy/plugins/siem/public/components/charts/common.tsx index dfb201fc3d927..a4be390019916 100644 --- a/x-pack/legacy/plugins/siem/public/components/charts/common.tsx +++ b/x-pack/legacy/plugins/siem/public/components/charts/common.tsx @@ -5,17 +5,14 @@ */ import { - CustomSeriesColorsMap, DARK_THEME, - DataSeriesColorsValues, - getSpecId, LIGHT_THEME, mergeWithDefaultTheme, PartialTheme, Rendering, Rotation, ScaleType, - SettingSpecProps, + SettingsSpecProps, TickFormatter, } from '@elastic/charts'; import moment from 'moment-timezone'; @@ -47,7 +44,7 @@ export interface ChartSeriesConfigs { xTickFormatter?: TickFormatter | undefined; yTickFormatter?: TickFormatter | undefined; }; - settings?: Partial; + settings?: Partial; } export interface ChartSeriesData { @@ -76,24 +73,6 @@ export enum SeriesType { LINE = 'line', } -// Customize colors: https://ela.st/custom-colors -export const getSeriesStyle = ( - seriesKey: string, - color: string | undefined, - seriesType?: SeriesType -) => { - if (!color) return undefined; - const customSeriesColors: CustomSeriesColorsMap = new Map(); - const dataSeriesColorValues: DataSeriesColorsValues = { - colorValues: seriesType === SeriesType.BAR ? [seriesKey] : [], - specId: getSpecId(seriesKey), - }; - - customSeriesColors.set(dataSeriesColorValues, color); - - return customSeriesColors; -}; - // Apply margins and paddings: https://ela.st/charts-spacing const theme: PartialTheme = { chartMargins: { diff --git a/x-pack/legacy/plugins/siem/public/components/page/detection_engine/histogram_signals/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/detection_engine/histogram_signals/index.tsx index fa26664930fe5..35fe8a2d90509 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/detection_engine/histogram_signals/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/detection_engine/histogram_signals/index.tsx @@ -9,8 +9,6 @@ import { Chart, HistogramBarSeries, Settings, - getAxisId, - getSpecId, niceTimeFormatByDay, timeFormatter, } from '@elastic/charts'; @@ -62,16 +60,12 @@ export const HistogramSignals = React.memo(() => { theme={npStart.plugins.eui_utils.useChartsTheme()} /> - + - + - - - "A danger color", - } + Array [ + "A danger color", + ] } data={ Array [ diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/charts/checks_chart.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/charts/checks_chart.tsx index c7ac0a59e4434..a88a9668660f7 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/charts/checks_chart.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/charts/checks_chart.tsx @@ -8,8 +8,6 @@ import { AreaSeries, Axis, Chart, - getAxisId, - getSpecId, Position, Settings, ScaleType, @@ -21,7 +19,6 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { StatusData } from '../../../../common/graphql/types'; import { getChartDateLabel } from '../../../lib/helper'; -import { getColorsMap } from './get_colors_map'; import { useUrlParams } from '../../../hooks'; interface ChecksChartProps { @@ -45,8 +42,8 @@ interface ChecksChartProps { * @param props The props values required by this component. */ export const ChecksChart = ({ dangerColor, status, successColor }: ChecksChartProps) => { - const upSeriesSpecId = getSpecId('Up'); - const downSeriesSpecId = getSpecId('Down'); + const upSeriesSpecId = 'Up'; + const downSeriesSpecId = 'Down'; const [getUrlParams] = useUrlParams(); const { absoluteDateRangeStart: min, absoluteDateRangeEnd: max } = getUrlParams(); @@ -74,7 +71,7 @@ export const ChecksChart = ({ dangerColor, status, successColor }: ChecksChartPr Number(d).toFixed(0)} title={i18n.translate('xpack.uptime.monitorChart.checksChart.leftAxis.title', { @@ -93,7 +90,7 @@ export const ChecksChart = ({ dangerColor, status, successColor }: ChecksChartPr })} /> ({ x, [upString]: up || 0, @@ -107,7 +104,7 @@ export const ChecksChart = ({ dangerColor, status, successColor }: ChecksChartPr yScaleType={ScaleType.Linear} /> ({ x, [downString]: down || 0, diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/charts/duration_chart.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/charts/duration_chart.tsx index 012d848610940..b5dcfce032724 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/charts/duration_chart.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/charts/duration_chart.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Axis, Chart, getAxisId, Position, timeFormatter, Settings } from '@elastic/charts'; +import { Axis, Chart, Position, timeFormatter, Settings } from '@elastic/charts'; import { EuiPanel, EuiTitle } from '@elastic/eui'; import React from 'react'; import { i18n } from '@kbn/i18n'; @@ -69,7 +69,7 @@ export const DurationChart = ({ getTickFormat(d)} title={i18n.translate('xpack.uptime.monitorCharts.durationChart.leftAxis.title', { diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/charts/duration_line_series_list.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/charts/duration_line_series_list.tsx index b6a65f193e913..872c79933d85a 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/charts/duration_line_series_list.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/charts/duration_line_series_list.tsx @@ -5,9 +5,8 @@ */ import React from 'react'; -import { LineSeries, CurveType, getSpecId } from '@elastic/charts'; +import { LineSeries, CurveType } from '@elastic/charts'; import { LocationDurationLine } from '../../../../common/graphql/types'; -import { getColorsMap } from './get_colors_map'; import { convertMicrosecondsToMilliseconds as microsToMillis } from '../../../lib/helper'; interface Props { @@ -21,9 +20,9 @@ export const DurationLineSeriesList = ({ lines, meanColor }: Props) => ( [x, microsToMillis(y || null)])} - id={getSpecId(`loc-avg-${name}`)} + id={`loc-avg-${name}`} key={`locline-${name}`} name={name} xAccessor={0} diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/charts/get_colors_map.ts b/x-pack/legacy/plugins/uptime/public/components/functional/charts/get_colors_map.ts deleted file mode 100644 index 83d181d91f8da..0000000000000 --- a/x-pack/legacy/plugins/uptime/public/components/functional/charts/get_colors_map.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { DataSeriesColorsValues, SpecId } from '@elastic/charts'; - -/** - * This is a helper function used to more easily define a basic map - * for color values for use with elastic charts. Support for multiple - * color values can be added in the future if needed. - * @param color a string containing a valid color value - * @param specId an ID generated by the elastic charts library - */ -export const getColorsMap = ( - color: string, - specId: SpecId -): Map => { - const map = new Map(); - map.set({ colorValues: [], specId }, color); - return map; -}; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/charts/monitor_bar_series.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/charts/monitor_bar_series.tsx index 52b41416bd17b..a0cbdc5922123 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/charts/monitor_bar_series.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/charts/monitor_bar_series.tsx @@ -8,10 +8,8 @@ import { Axis, BarSeries, Chart, - getSpecId, ScaleType, Settings, - getAxisId, Position, timeFormatter, } from '@elastic/charts'; @@ -20,7 +18,6 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiText, EuiToolTip } from '@elastic/eui'; import { SummaryHistogramPoint } from '../../../../common/graphql/types'; -import { getColorsMap } from './get_colors_map'; import { getChartDateLabel, seriesHasDownValues } from '../../../lib/helper'; export interface MonitorBarSeriesProps { @@ -53,7 +50,7 @@ export const MonitorBarSeries = ({ dangerColor, histogramSeries, }: MonitorBarSeriesProps) => { - const id = getSpecId('downSeries'); + const id = 'downSeries'; return seriesHasDownValues(histogramSeries) ? (
@@ -61,14 +58,14 @@ export const MonitorBarSeries = ({ [timestamp, down])} id={id} + customSeriesColors={[dangerColor]} + data={(histogramSeries || []).map(({ timestamp, down }) => [timestamp, down])} name={i18n.translate('xpack.uptime.monitorList.downLineSeries.downLabel', { defaultMessage: 'Down checks', })} diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/charts/snapshot_histogram.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/charts/snapshot_histogram.tsx index bacd06dbe162d..52ba482ce7fc1 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/charts/snapshot_histogram.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/charts/snapshot_histogram.tsx @@ -4,22 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - Axis, - BarSeries, - Chart, - getAxisId, - getSpecId, - Position, - timeFormatter, - Settings, -} from '@elastic/charts'; +import { Axis, BarSeries, Chart, Position, timeFormatter, Settings } from '@elastic/charts'; import { EuiEmptyPrompt, EuiTitle, EuiPanel } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useContext } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import moment from 'moment'; -import { getColorsMap } from './get_colors_map'; import { getChartDateLabel } from '../../../lib/helper'; import { withUptimeGraphQL, UptimeGraphQLQueryProps } from '../../higher_order'; import { snapshotHistogramQuery } from '../../../queries/snapshot_histogram_query'; @@ -110,12 +100,12 @@ export const SnapshotHistogramComponent: React.FC = ({ const downMonitorsId = i18n.translate('xpack.uptime.snapshotHistogram.downMonitorsId', { defaultMessage: 'Down Monitors', }); - const downSpecId = getSpecId(downMonitorsId); + const downSpecId = downMonitorsId; const upMonitorsId = i18n.translate('xpack.uptime.snapshotHistogram.series.upLabel', { defaultMessage: 'Up', }); - const upSpecId = getSpecId(upMonitorsId); + const upSpecId = upMonitorsId; return ( <> @@ -148,21 +138,17 @@ export const SnapshotHistogramComponent: React.FC = ({ showLegend={false} /> = ({ })} /> [x, downCount || 0])} id={downSpecId} name={i18n.translate('xpack.uptime.snapshotHistogram.series.downLabel', { @@ -185,7 +171,7 @@ export const SnapshotHistogramComponent: React.FC = ({ yScaleType="linear" /> [x, upCount || 0])} id={upSpecId} name={upMonitorsId} diff --git a/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/watch_visualization.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/watch_visualization.tsx index a3da7d14c8886..3901f84f506ef 100644 --- a/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/watch_visualization.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/watch_visualization.tsx @@ -8,9 +8,6 @@ import React, { Fragment, useContext, useEffect } from 'react'; import { AnnotationDomainTypes, Axis, - getAnnotationId, - getAxisId, - getSpecId, Chart, LineAnnotation, LineSeries, @@ -224,22 +221,17 @@ export const WatchVisualization = () => { legendPosition={Position.Bottom} /> - + {watchVisualizationDataKeys.map((key: string) => { return ( { return ( diff --git a/yarn.lock b/yarn.lock index 9a974926927c1..7e90744195608 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1361,10 +1361,10 @@ dependencies: "@elastic/apm-rum-core" "^4.7.0" -"@elastic/charts@^14.0.0": - version "14.0.0" - resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-14.0.0.tgz#410c87e9ae53df5848aae09a210fa7d08510b376" - integrity sha512-cskrD5Yq6yTTqGOKV2/7dw/eRON1GbWkIgSuWXPIBa/TQMUwiWqxFkxSMUJSbu9xXq07KMblDgXLf73yMc0AyQ== +"@elastic/charts@^16.0.2": + version "16.0.2" + resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-16.0.2.tgz#35068a08a19534da62e9bcad700cc7b2a15bc55a" + integrity sha512-0tVyltAmAPOAfiRU1iKYk3b9++4oTn6IXvyM4SSj7Ukh5Y90XXmOtGEUPnZTiRPmup9MJi4srrm9ra9k/Kq4UQ== dependencies: "@types/d3-shape" "^1.3.1" classnames "^2.2.6" @@ -1372,18 +1372,18 @@ d3-collection "^1.0.7" d3-scale "^1.0.7" d3-shape "^1.3.4" - fp-ts "^1.14.2" - konva "^2.6.0" - mobx "^4.9.2" - mobx-react "^5.4.3" + konva "^4.0.18" newtype-ts "^0.2.4" prop-types "^15.7.2" - react "^16.8.3" - react-dom "^16.8.3" - react-konva "16.8.3" + re-reselect "^3.4.0" + react-konva "16.10.1-0" + react-redux "^7.1.0" react-spring "^8.0.8" + redux "^4.0.4" + reselect "^4.0.0" resize-observer-polyfill "^1.5.1" ts-debounce "^1.0.0" + utility-types "^3.9.0" uuid "^3.3.2" "@elastic/elasticsearch@^7.4.0": @@ -4823,7 +4823,7 @@ acorn-walk@^7.0.0: resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.0.0.tgz#c8ba6f0f1aac4b0a9e32d1f0af12be769528f36b" integrity sha512-7Bv1We7ZGuU79zZbb6rRqcpxo3OY+zrdtloZWoyD8fmGX+FeXRjE+iuGkZjSXLVovLzrsvMGMy0EkwA0E0umxg== -acorn@5.X, acorn@^5.0.0, acorn@^5.0.3, acorn@^5.1.2, acorn@^5.2.1, acorn@^5.5.0: +acorn@5.X, acorn@^5.0.0, acorn@^5.0.3, acorn@^5.1.2, acorn@^5.5.0: version "5.7.3" resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279" integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw== @@ -6663,11 +6663,6 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= -base62@^1.1.0: - version "1.2.8" - resolved "https://registry.yarnpkg.com/base62/-/base62-1.2.8.tgz#1264cb0fb848d875792877479dbe8bae6bae3428" - integrity sha512-V6YHUbjLxN1ymqNLb1DPHoU1CpfdL7d2YTIp5W3U4hhoG4hhxNmsFDs66M9EXxBiSEke5Bt5dwdfMwwZF70iLA== - base64-arraybuffer@0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8" @@ -8531,7 +8526,7 @@ commander@^2.13.0, commander@^2.15.1, commander@^2.16.0, commander@^2.19.0, comm resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ== -commander@^2.5.0, commander@^2.7.1: +commander@^2.7.1: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== @@ -8563,21 +8558,6 @@ commondir@^1.0.1: resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= -commoner@^0.10.1: - version "0.10.8" - resolved "https://registry.yarnpkg.com/commoner/-/commoner-0.10.8.tgz#34fc3672cd24393e8bb47e70caa0293811f4f2c5" - integrity sha1-NPw2cs0kOT6LtH5wyqApOBH08sU= - dependencies: - commander "^2.5.0" - detective "^4.3.1" - glob "^5.0.15" - graceful-fs "^4.1.2" - iconv-lite "^0.4.5" - mkdirp "^0.5.0" - private "^0.1.6" - q "^1.1.2" - recast "^0.11.17" - compare-versions@3.5.1: version "3.5.1" resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.5.1.tgz#26e1f5cf0d48a77eced5046b9f67b6b61075a393" @@ -10147,7 +10127,7 @@ define-property@^2.0.2: is-descriptor "^1.0.2" isobject "^3.0.1" -defined@^1.0.0, defined@~1.0.0: +defined@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM= @@ -10396,14 +10376,6 @@ detective-typescript@^5.1.1: node-source-walk "^4.2.0" typescript "^3.4.5" -detective@^4.3.1: - version "4.7.1" - resolved "https://registry.yarnpkg.com/detective/-/detective-4.7.1.tgz#0eca7314338442febb6d65da54c10bb1c82b246e" - integrity sha512-H6PmeeUcZloWtdt4DAkFyzFL94arpHr3NOwwmVILFiy+9Qd4JTxxXrzfyGk/lmct2qVGBwTSwSXagqu2BxmWig== - dependencies: - acorn "^5.2.1" - defined "^1.0.0" - dezalgo@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.3.tgz#7f742de066fc748bc8db820569dddce49bf0d456" @@ -11110,14 +11082,6 @@ env-variable@0.0.x: resolved "https://registry.yarnpkg.com/env-variable/-/env-variable-0.0.5.tgz#913dd830bef11e96a039c038d4130604eba37f88" integrity sha512-zoB603vQReOFvTg5xMl9I1P2PnHsHQQKTEowsKKD7nseUfJq6UWzK+4YtlWUO1nhiQUxe6XMkk+JleSZD1NZFA== -envify@^3.0.0: - version "3.4.1" - resolved "https://registry.yarnpkg.com/envify/-/envify-3.4.1.tgz#d7122329e8df1688ba771b12501917c9ce5cbce8" - integrity sha1-1xIjKejfFoi6dxsSUBkXyc5cvOg= - dependencies: - jstransform "^11.0.3" - through "~2.3.4" - enzyme-adapter-react-16@^1.15.1: version "1.15.1" resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.1.tgz#8ad55332be7091dc53a25d7d38b3485fc2ba50d5" @@ -11795,11 +11759,6 @@ espree@^6.1.1: acorn-jsx "^5.0.2" eslint-visitor-keys "^1.1.0" -esprima-fb@^15001.1.0-dev-harmony-fb: - version "15001.1.0-dev-harmony-fb" - resolved "https://registry.yarnpkg.com/esprima-fb/-/esprima-fb-15001.1.0-dev-harmony-fb.tgz#30a947303c6b8d5e955bee2b99b1d233206a6901" - integrity sha1-MKlHMDxrjV6VW+4rmbHSMyBqaQE= - esprima@2.7.x, esprima@^2.7.1: version "2.7.3" resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" @@ -12486,17 +12445,6 @@ fb-watchman@^2.0.0: dependencies: bser "^2.0.0" -fbjs@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.6.1.tgz#9636b7705f5ba9684d44b72f78321254afc860f7" - integrity sha1-lja3cF9bqWhNRLcveDISVK/IYPc= - dependencies: - core-js "^1.0.0" - loose-envify "^1.0.0" - promise "^7.0.3" - ua-parser-js "^0.7.9" - whatwg-fetch "^0.9.0" - fbjs@^0.8.0, fbjs@^0.8.1, fbjs@^0.8.16: version "0.8.17" resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd" @@ -13151,11 +13099,6 @@ fp-ts@^1.0.0: resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-1.12.0.tgz#d333310e4ac104cdcb6bea47908e381bb09978e7" integrity sha512-fWwnAgVlTsV26Ruo9nx+fxNHIm6l1puE1VJ/C0XJ3nRQJJJIgRHYw6sigB3MuNFZL1o4fpGlhwFhcbxHK0RsOA== -fp-ts@^1.14.2: - version "1.17.3" - resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-1.17.3.tgz#5064afc4bee8ddcaea567479bfc62d527e015825" - integrity sha512-r4gHfAWaRrYPsmdzRl1U9CkpbdOi8fPg5F5KiazAadENz5DKdWEaCDPl2Tf92fvkZGD/ekZ3EHu3gtXIVcsXtA== - fp-ts@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-2.0.5.tgz#9560d8a6a4f53cbda9f9b31ed8d1458e41939e07" @@ -15050,13 +14993,6 @@ hoist-non-react-statics@^2.3.1, hoist-non-react-statics@^2.5.0, hoist-non-react- resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47" integrity sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw== -hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz#b09178f0122184fb95acf525daaecb4d8f45958b" - integrity sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA== - dependencies: - react-is "^16.7.0" - hoist-non-react-statics@^3.1.0: version "3.3.1" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#101685d3aff3b23ea213163f6e8e12f4f111e19f" @@ -15064,6 +15000,13 @@ hoist-non-react-statics@^3.1.0: dependencies: react-is "^16.7.0" +hoist-non-react-statics@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz#b09178f0122184fb95acf525daaecb4d8f45958b" + integrity sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA== + dependencies: + react-is "^16.7.0" + homedir-polyfill@^1.0.0, homedir-polyfill@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz#4c2bbc8a758998feebf5ed68580f76d46768b4bc" @@ -15367,7 +15310,7 @@ icalendar@0.7.1: resolved "https://registry.yarnpkg.com/icalendar/-/icalendar-0.7.1.tgz#d0d3486795f8f1c5cf4f8cafac081b4b4e7a32ae" integrity sha1-0NNIZ5X48cXPT4yvrAgbS056Mq4= -iconv-lite@0.4, iconv-lite@0.4.24, iconv-lite@^0.4.17, iconv-lite@^0.4.22, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@^0.4.5, iconv-lite@~0.4.13: +iconv-lite@0.4, iconv-lite@0.4.24, iconv-lite@^0.4.17, iconv-lite@^0.4.22, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -17627,17 +17570,6 @@ jssha@^2.1.0: resolved "https://registry.yarnpkg.com/jssha/-/jssha-2.3.1.tgz#147b2125369035ca4b2f7d210dc539f009b3de9a" integrity sha1-FHshJTaQNcpLL30hDcU58Amz3po= -jstransform@^11.0.3: - version "11.0.3" - resolved "https://registry.yarnpkg.com/jstransform/-/jstransform-11.0.3.tgz#09a78993e0ae4d4ef4487f6155a91f6190cb4223" - integrity sha1-CaeJk+CuTU70SH9hVakfYZDLQiM= - dependencies: - base62 "^1.1.0" - commoner "^0.10.1" - esprima-fb "^15001.1.0-dev-harmony-fb" - object-assign "^2.0.0" - source-map "^0.4.2" - jstransformer-ejs@^0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/jstransformer-ejs/-/jstransformer-ejs-0.0.3.tgz#04d9201469274fcf260f1e7efd732d487fa234b6" @@ -17906,10 +17838,10 @@ known-css-properties@^0.3.0: resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.3.0.tgz#a3d135bbfc60ee8c6eacf2f7e7e6f2d4755e49a4" integrity sha512-QMQcnKAiQccfQTqtBh/qwquGZ2XK/DXND1jrcN9M8gMMy99Gwla7GQjndVUsEqIaRyP6bsFRuhwRj5poafBGJQ== -konva@^2.6.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/konva/-/konva-2.6.0.tgz#43165b95e32a4378ce532d9113c914f4998409c3" - integrity sha512-LCOoavICTD9PYoAqtWo8sbxYtCiXdgEeY7vj/Sq8b2bwFmrQr9Ak0RkD4/jxAf5fcUQRL5e1zPLyfRpVndp20A== +konva@^4.0.18: + version "4.0.18" + resolved "https://registry.yarnpkg.com/konva/-/konva-4.0.18.tgz#43e614c9b22827183506d4a6b3b474f90187b469" + integrity sha512-Tlq0v7QHr8q73xr1cKjHdQl41oHC06IOldPO+ukjt99G74NgoU0TVouvPIFpW2whA9t3xNk/+/VJcc3XPcboOw== kopy@^8.2.0: version "8.2.5" @@ -19757,19 +19689,6 @@ mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdi dependencies: minimist "0.0.8" -mobx-react@^5.4.3: - version "5.4.3" - resolved "https://registry.yarnpkg.com/mobx-react/-/mobx-react-5.4.3.tgz#6709b7dd89670c40e9815914ac2ca49cc02bfb47" - integrity sha512-WC8yFlwvJ91hy8j6CrydAuFteUafcuvdITFQeHl3LRIf5ayfT/4W3M/byhEYD2BcJWejeXr8y4Rh2H26RunCRQ== - dependencies: - hoist-non-react-statics "^3.0.0" - react-lifecycles-compat "^3.0.2" - -mobx@^4.9.2: - version "4.9.4" - resolved "https://registry.yarnpkg.com/mobx/-/mobx-4.9.4.tgz#bb37a0e4e05f0b02be89ced9d23445cad73377ad" - integrity sha512-RaEpydw7D1ebp1pdFHrEMZcLk4nALAZyHAroCPQpqLzuIXIxJpLmMIe5PUZwYHqvlcWL6DVqDYCANZpPOi9iXA== - mocha-junit-reporter@^1.23.1: version "1.23.1" resolved "https://registry.yarnpkg.com/mocha-junit-reporter/-/mocha-junit-reporter-1.23.1.tgz#ba11519c0b967f404e4123dd69bc4ba022ab0f12" @@ -22437,7 +22356,7 @@ promise.prototype.finally@^3.1.0: es-abstract "^1.9.0" function-bind "^1.1.1" -promise@^7.0.1, promise@^7.0.3, promise@^7.1.1: +promise@^7.0.1, promise@^7.1.1: version "7.3.1" resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== @@ -23031,6 +22950,11 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" +re-reselect@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/re-reselect/-/re-reselect-3.4.0.tgz#0f2303f3c84394f57f0cd31fea08a1ca4840a7cd" + integrity sha512-JsecfN+JlckncVXTWFWjn0Vk6uInl8GSf4eEd9tTk5qXHlgqkPdILpnYpgZcISXNYAzvfvsCZviaDk8AxyS5sg== + react-ace@^5.5.0: version "5.10.0" resolved "https://registry.yarnpkg.com/react-ace/-/react-ace-5.10.0.tgz#e328b37ac52759f700be5afdb86ada2f5ec84c5e" @@ -23401,20 +23325,20 @@ react-is@~16.3.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.3.2.tgz#f4d3d0e2f5fbb6ac46450641eb2e25bf05d36b22" integrity sha512-ybEM7YOr4yBgFd6w8dJqwxegqZGJNBZl6U27HnGKuTZmDvVrD5quWOK/wAnMywiZzW+Qsk+l4X2c70+thp/A8Q== -react-konva@16.8.3: - version "16.8.3" - resolved "https://registry.yarnpkg.com/react-konva/-/react-konva-16.8.3.tgz#e55390040ea54675a0ef0d40b4fa93731e6d7b03" - integrity sha512-gU36TBxcPZANQOV5prAFnpRSNp2ikAT7zCICHTBJvOzAfa8Yhcyaey6EIrD+NTT/4y0PyGFBIkmWq6zdrlNrQg== +react-konva@16.10.1-0: + version "16.10.1-0" + resolved "https://registry.yarnpkg.com/react-konva/-/react-konva-16.10.1-0.tgz#f8cc2c95374933069e891a6c714c70d0fdc77e68" + integrity sha512-N0Zi3TcWmUxb2d7y1DUDQhRA+WIcqk54DQmmUmJSadj+fS0bg6iZDebQSEQC8dMbjnLHc/338xRT4a4718PEiw== dependencies: - react-reconciler "^0.20.1" - scheduler "^0.13.3" + react-reconciler "^0.22.1" + scheduler "^0.16.1" react-lib-adler32@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/react-lib-adler32/-/react-lib-adler32-1.0.3.tgz#63df1aed274eabcc1c5067077ea281ec30888ba7" integrity sha512-AqFqdt4cP0RPffHNjVHZ7tyIgnoSzNxgFhG8XKMXCtA1dZ72gTPO4iYFwWDKHqvE8sHS14rhltQTdbXU5G4BFA== -react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.2, react-lifecycles-compat@^3.0.4: +react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== @@ -23523,15 +23447,15 @@ react-portal@^3.2.0: dependencies: prop-types "^15.5.8" -react-reconciler@^0.20.1: - version "0.20.4" - resolved "https://registry.yarnpkg.com/react-reconciler/-/react-reconciler-0.20.4.tgz#3da6a95841592f849cb4edd3d38676c86fd920b2" - integrity sha512-kxERc4H32zV2lXMg/iMiwQHOtyqf15qojvkcZ5Ja2CPkjVohHw9k70pdDBwrnQhLVetUJBSYyqU3yqrlVTOajA== +react-reconciler@^0.22.1: + version "0.22.2" + resolved "https://registry.yarnpkg.com/react-reconciler/-/react-reconciler-0.22.2.tgz#e8a10374fec8fee7c5cd0cf3cd05626f1b134d3e" + integrity sha512-MLX5Y2pNLsdXzWz/GLNhhYkdLOvxEtw2IGqVCzkiRdSFSHRjujI9gfTOQ3rV5z8toTBxSZ2qrRkRUo97mmEdhA== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" prop-types "^15.6.2" - scheduler "^0.13.6" + scheduler "^0.16.2" react-redux@^5.0.7: version "5.0.7" @@ -23558,7 +23482,7 @@ react-redux@^5.1.2: react-is "^16.6.0" react-lifecycles-compat "^3.0.0" -react-redux@^7.1.1: +react-redux@^7.1.0, react-redux@^7.1.1: version "7.1.3" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.1.3.tgz#717a3d7bbe3a1b2d535c94885ce04cdc5a33fc79" integrity sha512-uI1wca+ECG9RoVkWQFF4jDMqmaw0/qnvaSvOoL/GA4dNxf6LoV8sUAcNDvE5NWKs4hFpn0t6wswNQnY3f7HT3w== @@ -23820,15 +23744,7 @@ react-visibility-sensor@^5.1.1: dependencies: prop-types "^15.7.2" -react@^0.14.0: - version "0.14.9" - resolved "https://registry.yarnpkg.com/react/-/react-0.14.9.tgz#9110a6497c49d44ba1c0edd317aec29c2e0d91d1" - integrity sha1-kRCmSXxJ1EuhwO3TF67CnC4NkdE= - dependencies: - envify "^3.0.0" - fbjs "^0.6.1" - -react@^16.12.0: +react@^0.14.0, react@^16.12.0, react@^16.8.3, react@^16.8.5: version "16.12.0" resolved "https://registry.yarnpkg.com/react/-/react-16.12.0.tgz#0c0a9c6a142429e3614834d5a778e18aa78a0b83" integrity sha512-fglqy3k5E+81pA8s+7K0/T3DBCF0ZDOher1elBFzF7O6arXJgzyu/FW+COxFvAWXJoJN9KIZbT2LXlukwphYTA== @@ -23837,16 +23753,6 @@ react@^16.12.0: object-assign "^4.1.1" prop-types "^15.6.2" -react@^16.8.3, react@^16.8.5: - version "16.8.6" - resolved "https://registry.yarnpkg.com/react/-/react-16.8.6.tgz#ad6c3a9614fd3a4e9ef51117f54d888da01f2bbe" - integrity sha512-pC0uMkhLaHm11ZSJULfOBqV4tIZkx87ZLvbbQYunNixAAvjnC+snJCg0XQXn9VIsttVsbZP/H/ewzgsd5fxKXw== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.2" - scheduler "^0.13.6" - reactcss@1.2.3, reactcss@^1.2.0: version "1.2.3" resolved "https://registry.yarnpkg.com/reactcss/-/reactcss-1.2.3.tgz#c00013875e557b1cf0dfd9a368a1c3dab3b548dd" @@ -24079,16 +23985,6 @@ realpath-native@^1.1.0: dependencies: util.promisify "^1.0.0" -recast@^0.11.17, recast@~0.11.12: - version "0.11.23" - resolved "https://registry.yarnpkg.com/recast/-/recast-0.11.23.tgz#451fd3004ab1e4df9b4e4b66376b2a21912462d3" - integrity sha1-RR/TAEqx5N+bTktmN2sqIZEkYtM= - dependencies: - ast-types "0.9.6" - esprima "~3.1.0" - private "~0.1.5" - source-map "~0.5.0" - recast@^0.14.7: version "0.14.7" resolved "https://registry.yarnpkg.com/recast/-/recast-0.14.7.tgz#4f1497c2b5826d42a66e8e3c9d80c512983ff61d" @@ -24109,6 +24005,16 @@ recast@^0.17.3: private "^0.1.8" source-map "~0.6.1" +recast@~0.11.12: + version "0.11.23" + resolved "https://registry.yarnpkg.com/recast/-/recast-0.11.23.tgz#451fd3004ab1e4df9b4e4b66376b2a21912462d3" + integrity sha1-RR/TAEqx5N+bTktmN2sqIZEkYtM= + dependencies: + ast-types "0.9.6" + esprima "~3.1.0" + private "~0.1.5" + source-map "~0.5.0" + rechoir@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" @@ -24799,6 +24705,11 @@ reselect@3.0.1, reselect@^3.0.1: resolved "https://registry.yarnpkg.com/reselect/-/reselect-3.0.1.tgz#efdaa98ea7451324d092b2b2163a6a1d7a9a2147" integrity sha1-79qpjqdFEyTQkrKyFjpqHXqaIUc= +reselect@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.0.0.tgz#f2529830e5d3d0e021408b246a206ef4ea4437f7" + integrity sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA== + resize-observer-polyfill@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.0.tgz#660ff1d9712a2382baa2cad450a4716209f9ca69" @@ -25367,10 +25278,10 @@ saxes@^3.1.9: dependencies: xmlchars "^2.1.1" -scheduler@^0.13.3, scheduler@^0.13.6: - version "0.13.6" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.13.6.tgz#466a4ec332467b31a91b9bf74e5347072e4cd889" - integrity sha512-IWnObHt413ucAYKsD9J1QShUKkbKLQQHdxRyw73sw4FN26iWr3DY/H34xGPe4nmL1DwXyWmSWmMrA9TfQbE/XQ== +scheduler@^0.16.1, scheduler@^0.16.2: + version "0.16.2" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.16.2.tgz#f74cd9d33eff6fc554edfb79864868e4819132c1" + integrity sha512-BqYVWqwz6s1wZMhjFvLfVR5WXP7ZY32M/wYPo04CcuPM7XZEbV2TBNW7Z0UkguPTl0dWMA59VbNXxK6q+pHItg== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" @@ -29200,7 +29111,7 @@ utila@^0.4.0, utila@~0.4: resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" integrity sha1-ihagXURWV6Oupe7MWxKk+lN5dyw= -utility-types@^3.10.0: +utility-types@^3.10.0, utility-types@^3.9.0: version "3.10.0" resolved "https://registry.yarnpkg.com/utility-types/-/utility-types-3.10.0.tgz#ea4148f9a741015f05ed74fd615e1d20e6bed82b" integrity sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg== @@ -30297,11 +30208,6 @@ whatwg-fetch@>=0.10.0, whatwg-fetch@^3.0.0: resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb" integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q== -whatwg-fetch@^0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-0.9.0.tgz#0e3684c6cb9995b43efc9df03e4c365d95fd9cc0" - integrity sha1-DjaExsuZlbQ+/J3wPkw2XZX9nMA= - whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" From a73ad23a64df2002775202cf922f93442dda1c99 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Sat, 4 Jan 2020 20:20:41 -0500 Subject: [PATCH 04/17] [SIEM] [Detection engine] Add user permission to detection engine (#53778) * add logic to see if we can show signals or create signal index for user * fix unit test * fix spelling set up * Update msg from review * review II * fix type * review III * fix bug found by Garrett * fix snapshot --- .../__snapshots__/index.test.tsx.snap | 8 +- .../public/components/empty_page/index.tsx | 6 +- .../components/ml/api/throw_if_not_ok.ts | 15 +- .../detection_engine/signals/api.ts | 104 +++++++++++++- .../signals/errors_types/get_index_error.ts | 24 ++++ .../signals/errors_types/index.ts | 9 ++ .../signals/errors_types/post_index_error.ts | 24 ++++ .../errors_types/privilege_user_error.ts | 24 ++++ .../detection_engine/signals/types.ts | 65 ++++++++- .../signals/use_privilege_user.tsx | 63 ++++++++ .../detection_engine/signals/use_query.tsx | 5 - .../signals/use_signal_index.tsx | 99 +++++++++++++ .../components/signals/index.tsx | 14 +- .../components/signals_info/index.tsx | 9 +- .../detection_engine/detection_engine.tsx | 134 ++++++++++++------ .../detection_engine_no_signal_index.tsx | 25 ++++ .../detection_engine_user_unauthenticated.tsx | 25 ++++ .../public/pages/detection_engine/index.tsx | 85 +++++++---- .../detection_engine/rules/details/index.tsx | 14 +- .../pages/detection_engine/translations.ts | 31 ++++ .../plugins/siem/public/utils/api/index.ts | 20 +++ .../routes/__mocks__/request_responses.ts | 1 + .../privileges/read_privileges_route.ts | 5 +- 23 files changed, 696 insertions(+), 113 deletions(-) create mode 100644 x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/errors_types/get_index_error.ts create mode 100644 x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/errors_types/index.ts create mode 100644 x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/errors_types/post_index_error.ts create mode 100644 x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/errors_types/privilege_user_error.ts create mode 100644 x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_privilege_user.tsx create mode 100644 x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_signal_index.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_no_signal_index.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_user_unauthenticated.tsx create mode 100644 x-pack/legacy/plugins/siem/public/utils/api/index.ts diff --git a/x-pack/legacy/plugins/siem/public/components/empty_page/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/empty_page/__snapshots__/index.test.tsx.snap index 9b6bfb1752a20..87409c5fdebe0 100644 --- a/x-pack/legacy/plugins/siem/public/components/empty_page/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/empty_page/__snapshots__/index.test.tsx.snap @@ -3,8 +3,12 @@ exports[`renders correctly 1`] = ` - + + ( title={

{title}

} body={message &&

{message}

} actions={ - - + + ( {actionSecondaryLabel && actionSecondaryUrl && ( - + => { - try { - const text = await response.text(); - return JSON.parse(text); - } catch (error) { - return null; - } -}; - export const tryParseResponse = (response: string): string => { try { return JSON.stringify(JSON.parse(response), null, 2); diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/api.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/api.ts index f6274db4a9dae..e69bbfe1925fb 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/api.ts +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/api.ts @@ -10,14 +10,27 @@ import { throwIfNotOk } from '../../../hooks/api/api'; import { DETECTION_ENGINE_QUERY_SIGNALS_URL, DETECTION_ENGINE_SIGNALS_STATUS_URL, + DETECTION_ENGINE_INDEX_URL, + DETECTION_ENGINE_PRIVILEGES_URL, } from '../../../../common/constants'; -import { QuerySignals, SignalSearchResponse, UpdateSignalStatusProps } from './types'; +import { + QuerySignals, + SignalSearchResponse, + UpdateSignalStatusProps, + SignalsIndex, + SignalIndexError, + Privilege, + PostSignalError, + BasicSignals, +} from './types'; +import { parseJsonFromBody } from '../../../utils/api'; /** * Fetch Signals by providing a query * * @param query String to match a dsl * @param kbnVersion current Kibana Version to use for headers + * @param signal AbortSignal for cancelling request */ export const fetchQuerySignals = async ({ query, @@ -46,7 +59,7 @@ export const fetchQuerySignals = async ({ * @param query of signals to update * @param status to update to('open' / 'closed') * @param kbnVersion current Kibana Version to use for headers - * @param signal to cancel request + * @param signal AbortSignal for cancelling request */ export const updateSignalStatus = async ({ query, @@ -69,3 +82,90 @@ export const updateSignalStatus = async ({ await throwIfNotOk(response); return response.json(); }; + +/** + * Fetch Signal Index + * + * @param kbnVersion current Kibana Version to use for headers + * @param signal AbortSignal for cancelling request + */ +export const getSignalIndex = async ({ + kbnVersion, + signal, +}: BasicSignals): Promise => { + const response = await fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_INDEX_URL}`, { + method: 'GET', + credentials: 'same-origin', + headers: { + 'content-type': 'application/json', + 'kbn-version': kbnVersion, + 'kbn-xsrf': kbnVersion, + }, + signal, + }); + if (response.ok) { + const signalIndex = await response.json(); + return signalIndex; + } + const error = await parseJsonFromBody(response); + if (error != null) { + throw new SignalIndexError(error); + } + return null; +}; + +/** + * Get User Privileges + * + * @param kbnVersion current Kibana Version to use for headers + * @param signal AbortSignal for cancelling request + */ +export const getUserPrivilege = async ({ + kbnVersion, + signal, +}: BasicSignals): Promise => { + const response = await fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_PRIVILEGES_URL}`, { + method: 'GET', + credentials: 'same-origin', + headers: { + 'content-type': 'application/json', + 'kbn-version': kbnVersion, + 'kbn-xsrf': kbnVersion, + }, + signal, + }); + + await throwIfNotOk(response); + return response.json(); +}; + +/** + * Create Signal Index if needed it + * + * @param kbnVersion current Kibana Version to use for headers + * @param signal AbortSignal for cancelling request + */ +export const createSignalIndex = async ({ + kbnVersion, + signal, +}: BasicSignals): Promise => { + const response = await fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_INDEX_URL}`, { + method: 'POST', + credentials: 'same-origin', + headers: { + 'content-type': 'application/json', + 'kbn-version': kbnVersion, + 'kbn-xsrf': kbnVersion, + }, + signal, + }); + if (response.ok) { + const signalIndex = await response.json(); + return signalIndex; + } + const error = await parseJsonFromBody(response); + if (error != null) { + throw new PostSignalError(error); + } + return null; +}; diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/errors_types/get_index_error.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/errors_types/get_index_error.ts new file mode 100644 index 0000000000000..e4f2a658b7362 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/errors_types/get_index_error.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { MessageBody } from '../../../../components/ml/api/throw_if_not_ok'; + +export class SignalIndexError extends Error { + message: string = ''; + statusCode: number = -1; + error: string = ''; + + constructor(errObj: MessageBody) { + super(errObj.message); + this.message = errObj.message ?? ''; + this.statusCode = errObj.statusCode ?? -1; + this.error = errObj.error ?? ''; + this.name = 'SignalIndexError'; + + // Set the prototype explicitly. + Object.setPrototypeOf(this, SignalIndexError.prototype); + } +} diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/errors_types/index.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/errors_types/index.ts new file mode 100644 index 0000000000000..4ce8e6ba89183 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/errors_types/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './get_index_error'; +export * from './post_index_error'; +export * from './privilege_user_error'; diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/errors_types/post_index_error.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/errors_types/post_index_error.ts new file mode 100644 index 0000000000000..d6d8cccfb4540 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/errors_types/post_index_error.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { MessageBody } from '../../../../components/ml/api/throw_if_not_ok'; + +export class PostSignalError extends Error { + message: string = ''; + statusCode: number = -1; + error: string = ''; + + constructor(errObj: MessageBody) { + super(errObj.message); + this.message = errObj.message ?? ''; + this.statusCode = errObj.statusCode ?? -1; + this.error = errObj.error ?? ''; + this.name = 'PostSignalError'; + + // Set the prototype explicitly. + Object.setPrototypeOf(this, PostSignalError.prototype); + } +} diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/errors_types/privilege_user_error.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/errors_types/privilege_user_error.ts new file mode 100644 index 0000000000000..5cd458a7fe9aa --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/errors_types/privilege_user_error.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { MessageBody } from '../../../../components/ml/api/throw_if_not_ok'; + +export class PrivilegeUserError extends Error { + message: string = ''; + statusCode: number = -1; + error: string = ''; + + constructor(errObj: MessageBody) { + super(errObj.message); + this.message = errObj.message ?? ''; + this.statusCode = errObj.statusCode ?? -1; + this.error = errObj.error ?? ''; + this.name = 'PrivilegeUserError'; + + // Set the prototype explicitly. + Object.setPrototypeOf(this, PrivilegeUserError.prototype); + } +} diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/types.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/types.ts index 5846f3275c0fd..118c2b367ca5b 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/types.ts +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/types.ts @@ -4,11 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -export interface QuerySignals { - query: string; +export * from './errors_types'; + +export interface BasicSignals { kbnVersion: string; signal: AbortSignal; } +export interface QuerySignals extends BasicSignals { + query: string; +} export interface SignalsResponse { took: number; @@ -38,3 +42,60 @@ export interface UpdateSignalStatusProps { kbnVersion: string; signal?: AbortSignal; // TODO: implement cancelling } + +export interface SignalsIndex { + name: string; +} + +export interface Privilege { + username: string; + has_all_requested: boolean; + cluster: { + monitor_ml: boolean; + manage_ccr: boolean; + manage_index_templates: boolean; + monitor_watcher: boolean; + monitor_transform: boolean; + read_ilm: boolean; + manage_api_key: boolean; + manage_security: boolean; + manage_own_api_key: boolean; + manage_saml: boolean; + all: boolean; + manage_ilm: boolean; + manage_ingest_pipelines: boolean; + read_ccr: boolean; + manage_rollup: boolean; + monitor: boolean; + manage_watcher: boolean; + manage: boolean; + manage_transform: boolean; + manage_token: boolean; + manage_ml: boolean; + manage_pipeline: boolean; + monitor_rollup: boolean; + transport_client: boolean; + create_snapshot: boolean; + }; + index: { + [indexName: string]: { + all: boolean; + manage_ilm: boolean; + read: boolean; + create_index: boolean; + read_cross_cluster: boolean; + index: boolean; + monitor: boolean; + delete: boolean; + manage: boolean; + delete_index: boolean; + create_doc: boolean; + view_index_metadata: boolean; + create: boolean; + manage_follow_index: boolean; + manage_leader_index: boolean; + write: boolean; + }; + }; + isAuthenticated: boolean; +} diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_privilege_user.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_privilege_user.tsx new file mode 100644 index 0000000000000..6f897703059f7 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_privilege_user.tsx @@ -0,0 +1,63 @@ +/* + * 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 { useEffect, useState } from 'react'; + +import { DEFAULT_KBN_VERSION } from '../../../../common/constants'; +import { useUiSetting$ } from '../../../lib/kibana'; +import { getUserPrivilege } from './api'; + +type Return = [boolean, boolean | null, boolean | null]; + +/** + * Hook to get user privilege from + * + */ +export const usePrivilegeUser = (): Return => { + const [loading, setLoading] = useState(true); + const [isAuthenticated, setAuthenticated] = useState(null); + const [hasWrite, setHasWrite] = useState(null); + const [kbnVersion] = useUiSetting$(DEFAULT_KBN_VERSION); + + useEffect(() => { + let isSubscribed = true; + const abortCtrl = new AbortController(); + setLoading(true); + + async function fetchData() { + try { + const privilege = await getUserPrivilege({ + kbnVersion, + signal: abortCtrl.signal, + }); + + if (isSubscribed && privilege != null) { + setAuthenticated(privilege.isAuthenticated); + if (privilege.index != null && Object.keys(privilege.index).length > 0) { + const indexName = Object.keys(privilege.index)[0]; + setHasWrite(privilege.index[indexName].create_index); + } + } + } catch (error) { + if (isSubscribed) { + setAuthenticated(false); + setHasWrite(false); + } + } + if (isSubscribed) { + setLoading(false); + } + } + + fetchData(); + return () => { + isSubscribed = false; + abortCtrl.abort(); + }; + }, []); + + return [loading, isAuthenticated, hasWrite]; +}; diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_query.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_query.tsx index d865625288550..9501f1189a483 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_query.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_query.tsx @@ -8,11 +8,8 @@ import { useEffect, useState } from 'react'; import { useUiSetting$ } from '../../../lib/kibana'; import { DEFAULT_KBN_VERSION } from '../../../../common/constants'; -import { errorToToaster } from '../../../components/ml/api/error_to_toaster'; -import { useStateToaster } from '../../../components/toasters'; import { fetchQuerySignals } from './api'; -import * as i18n from './translations'; import { SignalSearchResponse } from './types'; type Return = [boolean, SignalSearchResponse | null]; @@ -27,7 +24,6 @@ export const useQuerySignals = (query: string): Return => const [signals, setSignals] = useState | null>(null); const [loading, setLoading] = useState(true); const [kbnVersion] = useUiSetting$(DEFAULT_KBN_VERSION); - const [, dispatchToaster] = useStateToaster(); useEffect(() => { let isSubscribed = true; @@ -48,7 +44,6 @@ export const useQuerySignals = (query: string): Return => } catch (error) { if (isSubscribed) { setSignals(null); - errorToToaster({ title: i18n.SIGNAL_FETCH_FAILURE, error, dispatchToaster }); } } if (isSubscribed) { diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_signal_index.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_signal_index.tsx new file mode 100644 index 0000000000000..347c90fa3b411 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_signal_index.tsx @@ -0,0 +1,99 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useEffect, useState, useRef } from 'react'; + +import { DEFAULT_KBN_VERSION } from '../../../../common/constants'; +import { errorToToaster } from '../../../components/ml/api/error_to_toaster'; +import { useStateToaster } from '../../../components/toasters'; +import { useUiSetting$ } from '../../../lib/kibana'; +import { createSignalIndex, getSignalIndex } from './api'; +import * as i18n from './translations'; +import { PostSignalError } from './types'; + +type Func = () => void; + +type Return = [boolean, boolean | null, string | null, Func | null]; + +/** + * Hook for managing signal index + * + * + */ +export const useSignalIndex = (): Return => { + const [loading, setLoading] = useState(true); + const [signalIndexName, setSignalIndexName] = useState(null); + const [signalIndexExists, setSignalIndexExists] = useState(null); + const createDeSignalIndex = useRef(null); + const [kbnVersion] = useUiSetting$(DEFAULT_KBN_VERSION); + const [, dispatchToaster] = useStateToaster(); + + useEffect(() => { + let isSubscribed = true; + const abortCtrl = new AbortController(); + + const fetchData = async () => { + try { + setLoading(true); + const signal = await getSignalIndex({ + kbnVersion, + signal: abortCtrl.signal, + }); + + if (isSubscribed && signal != null) { + setSignalIndexName(signal.name); + setSignalIndexExists(true); + } + } catch (error) { + if (isSubscribed) { + setSignalIndexName(null); + setSignalIndexExists(false); + } + } + if (isSubscribed) { + setLoading(false); + } + }; + + const createIndex = async () => { + let isFetchingData = false; + try { + setLoading(true); + await createSignalIndex({ + kbnVersion, + signal: abortCtrl.signal, + }); + + if (isSubscribed) { + isFetchingData = true; + fetchData(); + } + } catch (error) { + if (isSubscribed) { + if (error instanceof PostSignalError && error.statusCode === 409) { + fetchData(); + } else { + setSignalIndexName(null); + setSignalIndexExists(false); + errorToToaster({ title: i18n.SIGNAL_FETCH_FAILURE, error, dispatchToaster }); + } + } + } + if (isSubscribed && !isFetchingData) { + setLoading(false); + } + }; + + fetchData(); + createDeSignalIndex.current = createIndex; + return () => { + isSubscribed = false; + abortCtrl.abort(); + }; + }, []); + + return [loading, signalIndexExists, signalIndexName, createDeSignalIndex.current]; +}; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/index.tsx index aeb5e677374fc..f9e80334a8882 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/index.tsx @@ -26,7 +26,7 @@ import { SignalsTableFilterGroup, } from './signals_filter_group'; import { useKibana, useUiSetting$ } from '../../../../lib/kibana'; -import { DEFAULT_KBN_VERSION, DEFAULT_SIGNALS_INDEX } from '../../../../../common/constants'; +import { DEFAULT_KBN_VERSION } from '../../../../../common/constants'; import { defaultHeaders } from '../../../../components/timeline/body/column_headers/default_headers'; import { ColumnHeader } from '../../../../components/timeline/body/column_headers/column_header'; import { esFilters, esQuery } from '../../../../../../../../../src/plugins/data/common/es_query'; @@ -91,6 +91,7 @@ interface DispatchProps { interface OwnProps { defaultFilters?: esFilters.Filter[]; from: number; + signalsIndex: string; to: number; } @@ -112,15 +113,14 @@ export const SignalsTableComponent = React.memo( selectedEventIds, setEventsDeleted, setEventsLoading, + signalsIndex, to, }) => { const [selectAll, setSelectAll] = useState(false); const [showClearSelectionAction, setShowClearSelectionAction] = useState(false); const [filterGroup, setFilterGroup] = useState(FILTER_OPEN); - const [{ browserFields, indexPatterns }] = useFetchIndexPatterns([ - `${DEFAULT_SIGNALS_INDEX}-default`, - ]); // TODO Get from new FrankInspired XavierHook + const [{ browserFields, indexPatterns }] = useFetchIndexPatterns([signalsIndex]); const [kbnVersion] = useUiSetting$(DEFAULT_KBN_VERSION); const kibana = useKibana(); @@ -266,9 +266,7 @@ export const SignalsTableComponent = React.memo( [createTimelineCallback, filterGroup, kbnVersion] ); - const defaultIndices = useMemo(() => [`${DEFAULT_SIGNALS_INDEX}-default`], [ - `${DEFAULT_SIGNALS_INDEX}-default`, - ]); + const defaultIndices = useMemo(() => [signalsIndex], [signalsIndex]); const defaultFiltersMemo = useMemo( () => [ ...defaultFilters, @@ -292,7 +290,7 @@ export const SignalsTableComponent = React.memo( return ( { const [lastSignals, setLastSignals] = useState( ); - const [totalSignals, setTotalSignals] = useState( + const [totalSignals, setTotalSignals] = useState( ); @@ -33,7 +33,7 @@ export const useSignalInfo = ({ ruleId = null }: SignalInfo): Return => { query = ''; } - const [, signals] = useQuerySignals(query); + const [loading, signals] = useQuerySignals(query); useEffect(() => { if (signals != null) { @@ -46,8 +46,11 @@ export const useSignalInfo = ({ ruleId = null }: SignalInfo): Return => { ) : null ); setTotalSignals(<>{mySignals.hits.total.value}); + } else { + setLastSignals(null); + setTotalSignals(null); } - }, [signals]); + }, [loading, signals]); return [lastSignals, totalSignals]; }; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx index 6e6b71729b07e..8e5c3e9f13118 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiButton, EuiSpacer } from '@elastic/eui'; +import { EuiButton, EuiSpacer, EuiPanel, EuiLoadingContent } from '@elastic/eui'; import React from 'react'; import { StickyContainer } from 'react-sticky'; @@ -17,59 +17,101 @@ import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../cont import { SpyRoute } from '../../utils/route/spy_routes'; import { SignalsTable } from './components/signals'; +import * as signalsI18n from './components/signals/translations'; import { SignalsCharts } from './components/signals_chart'; import { useSignalInfo } from './components/signals_info'; import { DetectionEngineEmptyPage } from './detection_engine_empty_page'; +import { DetectionEngineNoIndex } from './detection_engine_no_signal_index'; +import { DetectionEngineUserUnauthenticated } from './detection_engine_user_unauthenticated'; import * as i18n from './translations'; +import { HeaderSection } from '../../components/header_section'; -export const DetectionEngineComponent = React.memo(() => { - const [lastSignals] = useSignalInfo({}); - return ( - <> - - {({ indicesExist, indexPattern }) => { - return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( - - - - +interface DetectionEngineComponentProps { + loading: boolean; + isSignalIndexExists: boolean | null; + isUserAuthenticated: boolean | null; + signalsIndex: string | null; +} - - - {i18n.LAST_SIGNAL} - {': '} - {lastSignals} - - ) - } - title={i18n.PAGE_TITLE} - > - - {i18n.BUTTON_MANAGE_RULES} - - +export const DetectionEngineComponent = React.memo( + ({ loading, isSignalIndexExists, isUserAuthenticated, signalsIndex }) => { + const [lastSignals] = useSignalInfo({}); + if (isUserAuthenticated != null && !isUserAuthenticated && !loading) { + return ( + + + + + ); + } + if (isSignalIndexExists != null && !isSignalIndexExists && !loading) { + return ( + + + + + ); + } + return ( + <> + + {({ indicesExist, indexPattern }) => { + return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( + + + + + + + + {i18n.LAST_SIGNAL} + {': '} + {lastSignals} + + ) + } + title={i18n.PAGE_TITLE} + > + + {i18n.BUTTON_MANAGE_RULES} + + - + - - {({ to, from }) => } + + + {({ to, from }) => + !loading ? ( + isSignalIndexExists && ( + + ) + ) : ( + + + + + ) + } + + + + ) : ( + + + - - ) : ( - - - - - ); - }} - + ); + }} + - - - ); -}); + + + ); + } +); DetectionEngineComponent.displayName = 'DetectionEngineComponent'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_no_signal_index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_no_signal_index.tsx new file mode 100644 index 0000000000000..713bd6239d80e --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_no_signal_index.tsx @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { documentationLinks } from 'ui/documentation_links'; + +import { EmptyPage } from '../../components/empty_page'; +import * as i18n from './translations'; + +export const DetectionEngineNoIndex = React.memo(() => ( + +)); + +DetectionEngineNoIndex.displayName = 'DetectionEngineNoIndex'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_user_unauthenticated.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_user_unauthenticated.tsx new file mode 100644 index 0000000000000..bd3876b810a73 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_user_unauthenticated.tsx @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { documentationLinks } from 'ui/documentation_links'; + +import { EmptyPage } from '../../components/empty_page'; +import * as i18n from './translations'; + +export const DetectionEngineUserUnauthenticated = React.memo(() => ( + +)); + +DetectionEngineUserUnauthenticated.displayName = 'DetectionEngineUserUnauthenticated'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx index 21ebac2b4d337..e8a2c98a94a56 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx @@ -4,9 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useEffect } from 'react'; import { Redirect, Route, Switch, RouteComponentProps } from 'react-router-dom'; +import { useSignalIndex } from '../../containers/detection_engine/signals/use_signal_index'; +import { usePrivilegeUser } from '../../containers/detection_engine/signals/use_privilege_user'; + import { CreateRuleComponent } from './rules/create'; import { DetectionEngineComponent } from './detection_engine'; import { EditRuleComponent } from './rules/edit'; @@ -17,29 +20,61 @@ const detectionEnginePath = `/:pageName(detection-engine)`; type Props = Partial> & { url: string }; -export const DetectionEngineContainer = React.memo(() => ( - - - - - - - - - - - - - - - - - ( - +export const DetectionEngineContainer = React.memo(() => { + const [privilegeLoading, isAuthenticated, hasWrite] = usePrivilegeUser(); + const [ + indexNameLoading, + isSignalIndexExists, + signalIndexName, + createSignalIndex, + ] = useSignalIndex(); + + useEffect(() => { + if ( + isAuthenticated && + hasWrite && + isSignalIndexExists != null && + !isSignalIndexExists && + createSignalIndex != null + ) { + createSignalIndex(); + } + }, [createSignalIndex, isAuthenticated, isSignalIndexExists, hasWrite]); + + return ( + + + + + {isSignalIndexExists && isAuthenticated && ( + <> + + + + + + + + + + + + + )} - /> - -)); + + ( + + )} + /> + + ); +}); DetectionEngineContainer.displayName = 'DetectionEngineContainer'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx index b0cf183949dd9..1bc2bc24517e3 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx @@ -40,7 +40,11 @@ import * as ruleI18n from '../translations'; import * as i18n from './translations'; import { GlobalTime } from '../../../../containers/global_time'; -export const RuleDetailsComponent = memo(() => { +interface RuleDetailsComponentProps { + signalsIndex: string | null; +} + +export const RuleDetailsComponent = memo(({ signalsIndex }) => { const { ruleId } = useParams(); const [loading, rule] = useRule(ruleId); const { aboutRuleData, defineRuleData, scheduleRuleData } = getStepsData({ @@ -200,7 +204,12 @@ export const RuleDetailsComponent = memo(() => { {ruleId != null && ( - + )} @@ -220,4 +229,5 @@ export const RuleDetailsComponent = memo(() => { ); }); + RuleDetailsComponent.displayName = 'RuleDetailsComponent'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts index c7a71bf48d7dd..e5f830d3a49b0 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts @@ -51,3 +51,34 @@ export const EMPTY_ACTION_SECONDARY = i18n.translate( defaultMessage: 'Go to documentation', } ); + +export const NO_INDEX_TITLE = i18n.translate('xpack.siem.detectionEngine.noIndexTitle', { + defaultMessage: 'Let’s set up your detection engine', +}); + +export const NO_INDEX_MSG_BODY = i18n.translate('xpack.siem.detectionEngine.noIndexMsgBody', { + defaultMessage: + 'To use the detection engine, a user with the required cluster and index privileges must first access this page. For more help, contact your administrator.', +}); + +export const GO_TO_DOCUMENTATION = i18n.translate( + 'xpack.siem.detectionEngine.goToDocumentationButton', + { + defaultMessage: 'View documentation', + } +); + +export const USER_UNAUTHENTICATED_TITLE = i18n.translate( + 'xpack.siem.detectionEngine.userUnauthenticatedTitle', + { + defaultMessage: 'Detection engine permissions required', + } +); + +export const USER_UNAUTHENTICATED_MSG_BODY = i18n.translate( + 'xpack.siem.detectionEngine.userUnauthenticatedMsgBody', + { + defaultMessage: + 'You do not have the required permissions for viewing the detection engine. For more help, contact your administrator.', + } +); diff --git a/x-pack/legacy/plugins/siem/public/utils/api/index.ts b/x-pack/legacy/plugins/siem/public/utils/api/index.ts new file mode 100644 index 0000000000000..1dc14413b04d2 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/utils/api/index.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. + */ + +export interface MessageBody { + error?: string; + message?: string; + statusCode?: number; +} + +export const parseJsonFromBody = async (response: Response): Promise => { + try { + const text = await response.text(); + return JSON.parse(text); + } catch (error) { + return null; + } +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts index 66e1d29b80852..2e16f209acfb1 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -381,4 +381,5 @@ export const getMockPrivileges = () => ({ }, }, application: {}, + isAuthenticated: false, }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.ts index 457de05674f66..240200af8b585 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.ts @@ -5,6 +5,7 @@ */ import Hapi from 'hapi'; +import { merge } from 'lodash/fp'; import { DETECTION_ENGINE_PRIVILEGES_URL } from '../../../../../common/constants'; import { RulesRequest } from '../../rules/types'; import { ServerFacade } from '../../../../types'; @@ -28,7 +29,9 @@ export const createReadPrivilegesRulesRoute = (server: ServerFacade): Hapi.Serve const callWithRequest = callWithRequestFactory(request, server); const index = getIndex(request, server); const permissions = await readPrivileges(callWithRequest, index); - return permissions; + return merge(permissions, { + isAuthenticated: request?.auth?.isAuthenticated ?? false, + }); } catch (err) { return transformError(err); } From 58da936968b552554e125a3c47b0a6b330339d0f Mon Sep 17 00:00:00 2001 From: Maja Grubic Date: Sun, 5 Jan 2020 09:16:07 +0000 Subject: [PATCH 05/17] [Lens] Expression type on document can be null (#53883) Co-authored-by: Elastic Machine --- x-pack/legacy/plugins/lens/public/app_plugin/app.tsx | 1 + .../editor_frame_plugin/embeddable/expression_wrapper.tsx | 4 ++-- .../plugins/lens/public/persistence/saved_object_store.ts | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx b/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx index 443bcf99a4f09..cb57f2c884e38 100644 --- a/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx @@ -152,6 +152,7 @@ export function App({ const isSaveable = lastKnownDoc && + lastKnownDoc.expression && lastKnownDoc.expression.length > 0 && core.application.capabilities.visualize.save; diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/embeddable/expression_wrapper.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/embeddable/expression_wrapper.tsx index 3dd4373347129..9b85e89cd80eb 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/embeddable/expression_wrapper.tsx +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/embeddable/expression_wrapper.tsx @@ -13,7 +13,7 @@ import { ExpressionRenderer } from 'src/plugins/expressions/public'; export interface ExpressionWrapperProps { ExpressionRenderer: ExpressionRenderer; - expression: string; + expression: string | null; context: { timeRange?: TimeRange; query?: Query; @@ -29,7 +29,7 @@ export function ExpressionWrapper({ }: ExpressionWrapperProps) { return ( - {expression === '' ? ( + {expression === null || expression === '' ? ( diff --git a/x-pack/legacy/plugins/lens/public/persistence/saved_object_store.ts b/x-pack/legacy/plugins/lens/public/persistence/saved_object_store.ts index 4337482663a9c..1d36e18c726ec 100644 --- a/x-pack/legacy/plugins/lens/public/persistence/saved_object_store.ts +++ b/x-pack/legacy/plugins/lens/public/persistence/saved_object_store.ts @@ -13,7 +13,7 @@ export interface Document { type?: string; visualizationType: string | null; title: string; - expression: string; + expression: string | null; state: { datasourceMetaData: { filterableIndexPatterns: Array<{ id: string; title: string }>; From c19151d474d04a66073034b98268e30dee37cb02 Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Sun, 5 Jan 2020 20:05:47 -0500 Subject: [PATCH 06/17] [Maps] show custom color ramps in legend (#53780) --- .../vector/components/color/color_stops.js | 2 +- .../extract_color_from_style_property.js | 39 +++++++ .../vector/components/legend/vector_icon.js | 49 +------- .../components/legend/vector_icon.test.js | 34 ++++-- .../components/legend/vector_style_legend.js | 10 +- .../components/static_dynamic_style_row.js | 2 +- .../dynamic_color_property.test.js.snap | 105 ++++++++++++++++++ .../components/categorical_legend.js | 56 ++++++++++ ...ynamic_legend_row.js => ordinal_legend.js} | 23 ++-- .../properties/dynamic_color_property.js | 97 +++++++++++++++- .../properties/dynamic_color_property.test.js | 103 +++++++++++++++++ .../properties/dynamic_size_property.js | 4 +- .../properties/dynamic_style_property.js | 58 ++++++++-- .../properties/dynamic_text_property.js | 4 - .../vector/properties/style_property.js | 7 +- .../layers/styles/vector/vector_style.js | 37 +++++- 16 files changed, 541 insertions(+), 89 deletions(-) create mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/extract_color_from_style_property.js create mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/__snapshots__/dynamic_color_property.test.js.snap create mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/components/categorical_legend.js rename x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/components/{dynamic_legend_row.js => ordinal_legend.js} (87%) create mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops.js index df8464d141b5a..d523cf5870912 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops.js @@ -113,7 +113,7 @@ export const ColorStops = ({ colorStops = [{ stop: 0, color: DEFAULT_COLOR }], o display="rowCompressed" >
- + {stopInput} {colorInput} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/extract_color_from_style_property.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/extract_color_from_style_property.js new file mode 100644 index 0000000000000..157b863ac4986 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/extract_color_from_style_property.js @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { VectorStyle } from '../../vector_style'; +import { getColorRampCenterColor } from '../../../color_utils'; + +export function extractColorFromStyleProperty(colorStyleProperty, defaultColor) { + if (!colorStyleProperty) { + return defaultColor; + } + + if (colorStyleProperty.type === VectorStyle.STYLE_TYPE.STATIC) { + return colorStyleProperty.options.color; + } + + // Do not use dynamic color unless configuration is complete + if (!colorStyleProperty.options.field || !colorStyleProperty.options.field.name) { + return defaultColor; + } + + // return middle of gradient for dynamic style property + + if (colorStyleProperty.options.useCustomColorRamp) { + if ( + !colorStyleProperty.options.customColorRamp || + !colorStyleProperty.options.customColorRamp.length + ) { + return defaultColor; + } + // favor the lowest color in even arrays + const middleIndex = Math.floor((colorStyleProperty.options.customColorRamp.length - 1) / 2); + return colorStyleProperty.options.customColorRamp[middleIndex].color; + } + + return getColorRampCenterColor(colorStyleProperty.options.color); +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_icon.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_icon.js index c9d31b7c22c19..1ce12a5f75bb6 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_icon.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_icon.js @@ -7,13 +7,11 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { dynamicColorShape, staticColorShape } from '../style_option_shapes'; import { CircleIcon } from './circle_icon'; import { LineIcon } from './line_icon'; import { PolygonIcon } from './polygon_icon'; import { SymbolIcon } from './symbol_icon'; -import { VectorStyle } from '../../vector_style'; -import { getColorRampCenterColor } from '../../../color_utils'; +import { VECTOR_STYLES } from '../../vector_style_defaults'; export class VectorIcon extends Component { state = { @@ -48,16 +46,16 @@ export class VectorIcon extends Component { if (this.state.isLinesOnly) { const style = { - stroke: extractColorFromStyleProperty(this.props.lineColor, 'grey'), + stroke: this.props.getColorForProperty(VECTOR_STYLES.LINE_COLOR, true), strokeWidth: '4px', }; return ; } const style = { - stroke: extractColorFromStyleProperty(this.props.lineColor, 'none'), + stroke: this.props.getColorForProperty(VECTOR_STYLES.LINE_COLOR, false), strokeWidth: '1px', - fill: extractColorFromStyleProperty(this.props.fillColor, 'grey'), + fill: this.props.getColorForProperty(VECTOR_STYLES.FILL_COLOR, false), }; if (!this.state.isPointsOnly) { @@ -79,45 +77,8 @@ export class VectorIcon extends Component { } } -function extractColorFromStyleProperty(colorStyleProperty, defaultColor) { - if (!colorStyleProperty) { - return defaultColor; - } - - if (colorStyleProperty.type === VectorStyle.STYLE_TYPE.STATIC) { - return colorStyleProperty.options.color; - } - - // Do not use dynamic color unless configuration is complete - if (!colorStyleProperty.options.field || !colorStyleProperty.options.field.name) { - return defaultColor; - } - - // return middle of gradient for dynamic style property - - if (colorStyleProperty.options.useCustomColorRamp) { - if ( - !colorStyleProperty.options.customColorRamp || - !colorStyleProperty.options.customColorRamp.length - ) { - return defaultColor; - } - // favor the lowest color in even arrays - const middleIndex = Math.floor((colorStyleProperty.options.customColorRamp.length - 1) / 2); - return colorStyleProperty.options.customColorRamp[middleIndex].color; - } - - return getColorRampCenterColor(colorStyleProperty.options.color); -} - -const colorStylePropertyShape = PropTypes.shape({ - type: PropTypes.string.isRequired, - options: PropTypes.oneOfType([dynamicColorShape, staticColorShape]).isRequired, -}); - VectorIcon.propTypes = { - fillColor: colorStylePropertyShape, - lineColor: colorStylePropertyShape, + getColorForProperty: PropTypes.func.isRequired, symbolId: PropTypes.string, loadIsPointsOnly: PropTypes.func.isRequired, loadIsLinesOnly: PropTypes.func.isRequired, diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_icon.test.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_icon.test.js index 0341f1f22ca0a..ee0058a6ef1aa 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_icon.test.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_icon.test.js @@ -9,16 +9,12 @@ import { shallow } from 'enzyme'; import { VectorIcon } from './vector_icon'; import { VectorStyle } from '../../vector_style'; +import { extractColorFromStyleProperty } from './extract_color_from_style_property'; +import { VECTOR_STYLES } from '../../vector_style_defaults'; let isPointsOnly = false; let isLinesOnly = false; -const defaultProps = { - loadIsPointsOnly: () => { - return isPointsOnly; - }, - loadIsLinesOnly: () => { - return isLinesOnly; - }, +const styles = { fillColor: { type: VectorStyle.STYLE_TYPE.STATIC, options: { @@ -36,6 +32,30 @@ const defaultProps = { }, }; +const defaultProps = { + getColorForProperty: (styleProperty, isLinesOnly) => { + if (isLinesOnly) { + return extractColorFromStyleProperty(styles[VECTOR_STYLES.LINE_COLOR], 'grey'); + } + + if (styleProperty === VECTOR_STYLES.LINE_COLOR) { + return extractColorFromStyleProperty(styles[VECTOR_STYLES.LINE_COLOR], 'none'); + } else if (styleProperty === VECTOR_STYLES.FILL_COLOR) { + return extractColorFromStyleProperty(styles[VECTOR_STYLES.FILL_COLOR], 'grey'); + } else { + //unexpected + console.error('Cannot return color for properties other then line or fill color'); + } + }, + + loadIsPointsOnly: () => { + return isPointsOnly; + }, + loadIsLinesOnly: () => { + return isLinesOnly; + }, +}; + function configureIsLinesOnly() { isLinesOnly = true; isPointsOnly = false; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_style_legend.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_style_legend.js index 9e7afb0aa8873..df302c42d48ed 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_style_legend.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_style_legend.js @@ -46,7 +46,15 @@ export class VectorStyleLegend extends Component { render() { return this.state.styles.map(style => { - return {style.renderLegendDetailRow()}; + return ( + + {style.renderLegendDetailRow({ + loadIsLinesOnly: this.props.loadIsLinesOnly, + loadIsPointsOnly: this.props.loadIsPointsOnly, + symbolId: this.props.symbolId, + })} + + ); }); } } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/static_dynamic_style_row.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/static_dynamic_style_row.js index 625f63a77374d..311406731801a 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/static_dynamic_style_row.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/static_dynamic_style_row.js @@ -111,7 +111,7 @@ export class StaticDynamicStyleRow extends Component { }); return ( - + diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/__snapshots__/dynamic_color_property.test.js.snap b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/__snapshots__/dynamic_color_property.test.js.snap new file mode 100644 index 0000000000000..26e36cb97a791 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/__snapshots__/dynamic_color_property.test.js.snap @@ -0,0 +1,105 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Should render categorical legend 1`] = ` +
+ + + + + + + 0_format + + + + + + + + + + + + 10_format + + + + + + + + + + + + + + + foobar_label + + + + + + +
+`; + +exports[`Should render ranged legend 1`] = ` + + } + maxLabel="100_format" + minLabel="0_format" + propertyLabel="Border color" +/> +`; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/components/categorical_legend.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/components/categorical_legend.js new file mode 100644 index 0000000000000..8f8ad4d24e715 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/components/categorical_legend.js @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import _ from 'lodash'; +const EMPTY_VALUE = ''; + +export class CategoricalLegend extends React.Component { + constructor() { + super(); + this._isMounted = false; + this.state = { + label: EMPTY_VALUE, + isPointsOnly: null, + isLinesOnly: null, + }; + } + + async _loadParams() { + const label = await this.props.style.getField().getLabel(); + const isLinesOnly = await this.props.loadIsLinesOnly(); + const isPointsOnly = await this.props.loadIsPointsOnly(); + const newState = { label, isLinesOnly, isPointsOnly }; + if (this._isMounted && !_.isEqual(this.state, newState)) { + this.setState(newState); + } + } + + componentDidUpdate() { + this._loadParams(); + } + + componentWillUnmount() { + this._isMounted = false; + } + + componentDidMount() { + this._isMounted = true; + this._loadParams(); + } + + render() { + if (this.state.label === EMPTY_VALUE) { + return null; + } + return this.props.style.renderBreakedLegend({ + fieldLabel: this.state.label, + isLinesOnly: this.state.isLinesOnly, + isPointsOnly: this.state.isPointsOnly, + symbolId: this.props.symbolId, + }); + } +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/components/dynamic_legend_row.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/components/ordinal_legend.js similarity index 87% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/components/dynamic_legend_row.js rename to x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/components/ordinal_legend.js index 5933bd1575b2a..564bae3ef3f72 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/components/dynamic_legend_row.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/components/ordinal_legend.js @@ -7,11 +7,9 @@ import React from 'react'; import _ from 'lodash'; import { RangedStyleLegendRow } from '../../../components/ranged_style_legend_row'; -import { getVectorStyleLabel } from '../../components/get_vector_style_label'; - const EMPTY_VALUE = ''; -export class DynamicLegendRow extends React.Component { +export class OrdinalLegend extends React.Component { constructor() { super(); this._isMounted = false; @@ -28,6 +26,13 @@ export class DynamicLegendRow extends React.Component { } } + _formatValue(value) { + if (value === EMPTY_VALUE) { + return value; + } + return this.props.style.formatField(value); + } + componentDidUpdate() { this._loadParams(); } @@ -40,14 +45,6 @@ export class DynamicLegendRow extends React.Component { this._isMounted = true; this._loadParams(); } - - _formatValue(value) { - if (value === EMPTY_VALUE) { - return value; - } - return this.props.style.formatField(value); - } - render() { const fieldMeta = this.props.style.getFieldMeta(); @@ -70,10 +67,10 @@ export class DynamicLegendRow extends React.Component { return ( ); diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.js index 3961325c3bd59..200df9e5cc33d 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.js @@ -10,6 +10,9 @@ import { getComputedFieldName } from '../style_util'; import { getColorRampStops } from '../../color_utils'; import { ColorGradient } from '../../components/color_gradient'; import React from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText, EuiToolTip } from '@elastic/eui'; +import { VectorIcon } from '../components/legend/vector_icon'; +import { VECTOR_STYLES } from '../vector_style_defaults'; export class DynamicColorProperty extends DynamicStyleProperty { syncCircleColorWithMb(mbLayerId, mbMap, alpha) { @@ -64,6 +67,14 @@ export class DynamicColorProperty extends DynamicStyleProperty { return !this.isCustomColorRamp(); } + isRanged() { + return !this.isCustomColorRamp(); + } + + hasBreaks() { + return this.isCustomColorRamp(); + } + _getMbColor() { const isDynamicConfigComplete = _.has(this._options, 'field.name') && _.has(this._options, 'color'); @@ -117,11 +128,95 @@ export class DynamicColorProperty extends DynamicStyleProperty { return getColorRampStops(this._options.color); } - renderLegendHeader() { + renderRangeLegendHeader() { if (this._options.color) { return ; } else { return null; } } + + _renderStopIcon(color, isLinesOnly, isPointsOnly, symbolId) { + if (this.getStyleName() === VECTOR_STYLES.LABEL_COLOR) { + const style = { color: color }; + return ( + + Tx + + ); + } + + const loadIsLinesOnly = () => { + return isLinesOnly; + }; + + const loadIsPointsOnly = () => { + return isPointsOnly; + }; + + const getColorForProperty = (styleProperty, isLinesOnly) => { + if (isLinesOnly) { + return color; + } + + return this.getStyleName() === styleProperty ? color : 'none'; + }; + + return ( + + ); + } + + _renderColorbreaks({ isLinesOnly, isPointsOnly, symbolId }) { + if (!this._options.customColorRamp) { + return null; + } + + return this._options.customColorRamp.map((config, index) => { + const value = this.formatField(config.stop); + return ( + + + + {value} + + + {this._renderStopIcon(config.color, isLinesOnly, isPointsOnly, symbolId)} + + + + ); + }); + } + + renderBreakedLegend({ fieldLabel, isPointsOnly, isLinesOnly, symbolId }) { + return ( +
+ + + {this._renderColorbreaks({ + isPointsOnly, + isLinesOnly, + symbolId, + })} + + + + + + + {fieldLabel} + + + + + +
+ ); + } } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.test.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.test.js new file mode 100644 index 0000000000000..dbf704c9cbe4c --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.test.js @@ -0,0 +1,103 @@ +/* + * 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. + */ + +// eslint-disable-next-line no-unused-vars +import React from 'react'; +import { shallow } from 'enzyme'; + +import { VECTOR_STYLES } from '../vector_style_defaults'; +import { DynamicColorProperty } from './dynamic_color_property'; + +const mockField = { + async getLabel() { + return 'foobar_label'; + }, + + getName() { + return 'foobar'; + }, + supportsFieldMeta() { + return true; + }, +}; + +test('Should render ranged legend', async () => { + const colorStyle = new DynamicColorProperty( + { + color: 'Blues', + }, + VECTOR_STYLES.LINE_COLOR, + mockField, + () => { + return { min: 0, max: 100 }; + }, + () => { + return x => x + '_format'; + } + ); + + const legendRow = colorStyle.renderLegendDetailRow({ + loadIsPointsOnly: () => { + return true; + }, + loadIsLinesOnly: () => { + return false; + }, + }); + + const component = shallow(legendRow); + + // Ensure all promises resolve + await new Promise(resolve => process.nextTick(resolve)); + // Ensure the state changes are reflected + component.update(); + + expect(component).toMatchSnapshot(); +}); + +test('Should render categorical legend', async () => { + const colorStyle = new DynamicColorProperty( + { + useCustomColorRamp: true, + customColorRamp: [ + { + stop: 0, + color: '#FF0000', + }, + { + stop: 10, + color: '#00FF00', + }, + ], + }, + VECTOR_STYLES.LINE_COLOR, + mockField, + () => { + return { min: 0, max: 100 }; + }, + () => { + return x => x + '_format'; + } + ); + + const legendRow = colorStyle.renderLegendDetailRow({ + loadIsPointsOnly: () => { + return true; + }, + loadIsLinesOnly: () => { + return false; + }, + }); + + const component = shallow(legendRow); + + // Ensure all promises resolve + await new Promise(resolve => process.nextTick(resolve)); + // Ensure the state changes are reflected + component.update(); + + expect(component).toMatchSnapshot(); +}); diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js index 05e235f6926a6..f2e5672226814 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js @@ -146,7 +146,7 @@ export class DynamicSizeProperty extends DynamicStyleProperty { ); } - renderLegendHeader() { + renderRangeLegendHeader() { let icons; if (this.getStyleName() === VECTOR_STYLES.LINE_WIDTH) { icons = getLineWidthIcons(); @@ -157,7 +157,7 @@ export class DynamicSizeProperty extends DynamicStyleProperty { } return ( - + {icons.map((icon, index) => { const isLast = index === icons.length - 1; let spacer; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.js index e6448f365b44a..bac3c96581967 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.js @@ -8,9 +8,10 @@ import _ from 'lodash'; import { AbstractStyleProperty } from './style_property'; import { DEFAULT_SIGMA } from '../vector_style_defaults'; import { STYLE_TYPE } from '../../../../../common/constants'; -import { DynamicLegendRow } from './components/dynamic_legend_row'; import { scaleValue } from '../style_util'; import React from 'react'; +import { OrdinalLegend } from './components/ordinal_legend'; +import { CategoricalLegend } from './components/categorical_legend'; export class DynamicStyleProperty extends AbstractStyleProperty { static type = STYLE_TYPE.DYNAMIC; @@ -38,6 +39,14 @@ export class DynamicStyleProperty extends AbstractStyleProperty { return true; } + hasBreaks() { + return false; + } + + isRanged() { + return true; + } + isComplete() { return !!this._field; } @@ -75,6 +84,10 @@ export class DynamicStyleProperty extends AbstractStyleProperty { } pluckStyleMetaFromFeatures(features) { + if (!this.isOrdinal()) { + return null; + } + const name = this.getField().getName(); let min = Infinity; let max = -Infinity; @@ -97,6 +110,10 @@ export class DynamicStyleProperty extends AbstractStyleProperty { } pluckStyleMetaFromFieldMetaData(fieldMetaData) { + if (!this.isOrdinal()) { + return null; + } + const realFieldName = this._field.getESDocFieldName ? this._field.getESDocFieldName() : this._field.getName(); @@ -120,13 +137,13 @@ export class DynamicStyleProperty extends AbstractStyleProperty { } formatField(value) { - if (!this.getField()) { + if (this.getField()) { + const fieldName = this.getField().getName(); + const fieldFormatter = this._getFieldFormatter(fieldName); + return fieldFormatter ? fieldFormatter(value) : value; + } else { return value; } - - const fieldName = this.getField().getName(); - const fieldFormatter = this._getFieldFormatter(fieldName); - return fieldFormatter ? fieldFormatter(value) : value; } getMbValue(value) { @@ -144,7 +161,32 @@ export class DynamicStyleProperty extends AbstractStyleProperty { return valueAsFloat; } - renderLegendDetailRow() { - return ; + renderBreakedLegend() { + return null; + } + + _renderCategoricalLegend({ loadIsPointsOnly, loadIsLinesOnly, symbolId }) { + return ( + + ); + } + + _renderRangeLegend() { + return ; + } + + renderLegendDetailRow({ loadIsPointsOnly, loadIsLinesOnly, symbolId }) { + if (this.isRanged()) { + return this._renderRangeLegend(); + } else if (this.hasBreaks()) { + return this._renderCategoricalLegend({ loadIsPointsOnly, loadIsLinesOnly, symbolId }); + } else { + return null; + } } } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_text_property.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_text_property.js index b716030d2f263..fbc4c3af78f98 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_text_property.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_text_property.js @@ -32,8 +32,4 @@ export class DynamicTextProperty extends DynamicStyleProperty { isScaled() { return false; } - - renderHeader() { - return null; - } } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/style_property.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/style_property.js index 8da76a775229e..52e1a46a18e94 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/style_property.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/style_property.js @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { getVectorStyleLabel } from '../components/get_vector_style_label'; export class AbstractStyleProperty { constructor(options, styleName) { this._options = options; @@ -36,11 +37,15 @@ export class AbstractStyleProperty { return this._options || {}; } - renderLegendHeader() { + renderRangeLegendHeader() { return null; } renderLegendDetailRow() { return null; } + + getDisplayStyleName() { + return getVectorStyleLabel(this.getStyleName()); + } } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js index ae434f5002800..ea80b188e1646 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js @@ -38,6 +38,7 @@ import { StaticOrientationProperty } from './properties/static_orientation_prope import { DynamicOrientationProperty } from './properties/dynamic_orientation_property'; import { StaticTextProperty } from './properties/static_text_property'; import { DynamicTextProperty } from './properties/dynamic_text_property'; +import { extractColorFromStyleProperty } from './components/legend/extract_color_from_style_property'; const POINTS = [GEO_JSON_TYPE.POINT, GEO_JSON_TYPE.MULTI_POINT]; const LINES = [GEO_JSON_TYPE.LINE_STRING, GEO_JSON_TYPE.MULTI_LINE_STRING]; @@ -386,18 +387,37 @@ export class VectorStyle extends AbstractStyle { return _.get(this._descriptor, '__styleMeta', {}); }; - getIcon = () => { - const styles = this.getRawProperties(); - const symbolId = this.arePointsSymbolizedAsCircles() + _getSymbolId() { + return this.arePointsSymbolizedAsCircles() ? undefined : this._descriptor.properties.symbol.options.symbolId; + } + + _getColorForProperty = (styleProperty, isLinesOnly) => { + const styles = this.getRawProperties(); + if (isLinesOnly) { + return extractColorFromStyleProperty(styles[VECTOR_STYLES.LINE_COLOR], 'grey'); + } + + if (styleProperty === VECTOR_STYLES.LINE_COLOR) { + return extractColorFromStyleProperty(styles[VECTOR_STYLES.LINE_COLOR], 'none'); + } else if (styleProperty === VECTOR_STYLES.FILL_COLOR) { + return extractColorFromStyleProperty(styles[VECTOR_STYLES.FILL_COLOR], 'grey'); + } else { + //unexpected + console.error('Cannot return color for properties other then line or fill color'); + } + }; + + getIcon = () => { + const symbolId = this._getSymbolId(); + return ( ); }; @@ -431,7 +451,12 @@ export class VectorStyle extends AbstractStyle { renderLegendDetails() { return ( - + ); } From 24542c3ad747ff6bb024e315e5475fb6cdb04a77 Mon Sep 17 00:00:00 2001 From: Pierre Gayvallet Date: Mon, 6 Jan 2020 09:24:15 +0100 Subject: [PATCH 07/17] Allow custom NP plugin paths in production (#53562) * allow NP plugins --plugin-path in production, logs a warning * remove env.staticFilesDir (unused) * only show warning in development * update rendering tests snapshots * fix typo --- src/cli/serve/serve.js | 12 +-- src/core/server/config/__mocks__/env.ts | 1 + .../config/__snapshots__/env.test.ts.snap | 12 +-- src/core/server/config/env.test.ts | 22 +++++ src/core/server/config/env.ts | 9 +- src/core/server/http/http_config.test.ts | 11 +-- src/core/server/http/http_config.ts | 5 +- src/core/server/http/http_service.ts | 4 +- src/core/server/http/http_tools.test.ts | 8 +- src/core/server/legacy/legacy_service.ts | 4 +- .../discovery/plugins_discovery.test.ts | 92 +++++++++++++++++-- .../plugins/discovery/plugins_discovery.ts | 4 +- .../server/plugins/plugins_config.test.ts | 44 +++++++++ src/core/server/plugins/plugins_config.ts | 4 +- .../rendering_service.test.ts.snap | 20 ++-- .../rendering/rendering_service.test.ts | 2 +- src/test_utils/kbn_server.ts | 1 + 17 files changed, 186 insertions(+), 69 deletions(-) create mode 100644 src/core/server/plugins/plugins_config.test.ts diff --git a/src/cli/serve/serve.js b/src/cli/serve/serve.js index 976eac7f95da6..6b13d0dc32d3f 100644 --- a/src/cli/serve/serve.js +++ b/src/cli/serve/serve.js @@ -140,23 +140,12 @@ function applyConfigOverrides(rawConfig, opts, extraCliOptions) { } set('plugins.scanDirs', _.compact([].concat(get('plugins.scanDirs'), opts.pluginDir))); - set( 'plugins.paths', _.compact( [].concat( get('plugins.paths'), opts.pluginPath, - opts.runExamples - ? [ - // Ideally this would automatically include all plugins in the examples dir - fromRoot('examples/demo_search'), - fromRoot('examples/search_explorer'), - fromRoot('examples/embeddable_examples'), - fromRoot('examples/embeddable_explorer'), - ] - : [], - XPACK_INSTALLED && !opts.oss ? [XPACK_DIR] : [] ) ) @@ -253,6 +242,7 @@ export default function(program) { silent: !!opts.silent, watch: !!opts.watch, repl: !!opts.repl, + runExamples: !!opts.runExamples, // We want to run without base path when the `--run-examples` flag is given so that we can use local // links in other documentation sources, like "View this tutorial [here](http://localhost:5601/app/tutorial/xyz)". // We can tell users they only have to run with `yarn start --run-examples` to get those diff --git a/src/core/server/config/__mocks__/env.ts b/src/core/server/config/__mocks__/env.ts index 644b499ff56d8..80cfab81fb557 100644 --- a/src/core/server/config/__mocks__/env.ts +++ b/src/core/server/config/__mocks__/env.ts @@ -38,6 +38,7 @@ export function getEnvOptions(options: DeepPartial = {}): EnvOptions basePath: false, optimize: false, oss: false, + runExamples: false, ...(options.cliArgs || {}), }, isDevClusterMaster: diff --git a/src/core/server/config/__snapshots__/env.test.ts.snap b/src/core/server/config/__snapshots__/env.test.ts.snap index 1f4661283de6e..204b8a70aa877 100644 --- a/src/core/server/config/__snapshots__/env.test.ts.snap +++ b/src/core/server/config/__snapshots__/env.test.ts.snap @@ -12,6 +12,7 @@ Env { "oss": false, "quiet": false, "repl": false, + "runExamples": false, "silent": false, "watch": false, }, @@ -40,7 +41,6 @@ Env { "/test/kibanaRoot/plugins", "/test/kibanaRoot/../kibana-extra", ], - "staticFilesDir": "/test/kibanaRoot/ui", } `; @@ -56,6 +56,7 @@ Env { "oss": false, "quiet": false, "repl": false, + "runExamples": false, "silent": false, "watch": false, }, @@ -84,7 +85,6 @@ Env { "/test/kibanaRoot/plugins", "/test/kibanaRoot/../kibana-extra", ], - "staticFilesDir": "/test/kibanaRoot/ui", } `; @@ -99,6 +99,7 @@ Env { "oss": false, "quiet": false, "repl": false, + "runExamples": false, "silent": false, "watch": false, }, @@ -127,7 +128,6 @@ Env { "/test/kibanaRoot/plugins", "/test/kibanaRoot/../kibana-extra", ], - "staticFilesDir": "/test/kibanaRoot/ui", } `; @@ -142,6 +142,7 @@ Env { "oss": false, "quiet": false, "repl": false, + "runExamples": false, "silent": false, "watch": false, }, @@ -170,7 +171,6 @@ Env { "/test/kibanaRoot/plugins", "/test/kibanaRoot/../kibana-extra", ], - "staticFilesDir": "/test/kibanaRoot/ui", } `; @@ -185,6 +185,7 @@ Env { "oss": false, "quiet": false, "repl": false, + "runExamples": false, "silent": false, "watch": false, }, @@ -213,7 +214,6 @@ Env { "/test/kibanaRoot/plugins", "/test/kibanaRoot/../kibana-extra", ], - "staticFilesDir": "/test/kibanaRoot/ui", } `; @@ -228,6 +228,7 @@ Env { "oss": false, "quiet": false, "repl": false, + "runExamples": false, "silent": false, "watch": false, }, @@ -256,6 +257,5 @@ Env { "/some/home/dir/plugins", "/some/home/dir/../kibana-extra", ], - "staticFilesDir": "/some/home/dir/ui", } `; diff --git a/src/core/server/config/env.test.ts b/src/core/server/config/env.test.ts index 5812fa93cf18f..c244012e34469 100644 --- a/src/core/server/config/env.test.ts +++ b/src/core/server/config/env.test.ts @@ -152,3 +152,25 @@ test('pluginSearchPaths does not contains x-pack plugins path if --oss flag is t expect(env.pluginSearchPaths).not.toContain('/some/home/dir/x-pack/plugins'); }); + +test('pluginSearchPaths contains examples plugins path if --run-examples flag is true', () => { + const env = new Env( + '/some/home/dir', + getEnvOptions({ + cliArgs: { runExamples: true }, + }) + ); + + expect(env.pluginSearchPaths).toContain('/some/home/dir/examples'); +}); + +test('pluginSearchPaths does not contains examples plugins path if --run-examples flag is false', () => { + const env = new Env( + '/some/home/dir', + getEnvOptions({ + cliArgs: { runExamples: false }, + }) + ); + + expect(env.pluginSearchPaths).not.toContain('/some/home/dir/examples'); +}); diff --git a/src/core/server/config/env.ts b/src/core/server/config/env.ts index 460773d89db85..db363fcd4d751 100644 --- a/src/core/server/config/env.ts +++ b/src/core/server/config/env.ts @@ -43,6 +43,7 @@ export interface CliArgs { optimize: boolean; open: boolean; oss: boolean; + runExamples: boolean; } export class Env { @@ -61,8 +62,6 @@ export class Env { /** @internal */ public readonly logDir: string; /** @internal */ - public readonly staticFilesDir: string; - /** @internal */ public readonly pluginSearchPaths: readonly string[]; /** @@ -100,14 +99,14 @@ export class Env { this.configDir = resolve(this.homeDir, 'config'); this.binDir = resolve(this.homeDir, 'bin'); this.logDir = resolve(this.homeDir, 'log'); - this.staticFilesDir = resolve(this.homeDir, 'ui'); this.pluginSearchPaths = [ resolve(this.homeDir, 'src', 'plugins'), - options.cliArgs.oss ? '' : resolve(this.homeDir, 'x-pack', 'plugins'), + ...(options.cliArgs.oss ? [] : [resolve(this.homeDir, 'x-pack', 'plugins')]), resolve(this.homeDir, 'plugins'), + ...(options.cliArgs.runExamples ? [resolve(this.homeDir, 'examples')] : []), resolve(this.homeDir, '..', 'kibana-extra'), - ].filter(Boolean); + ]; this.cliArgs = Object.freeze(options.cliArgs); this.configs = Object.freeze(options.configs); diff --git a/src/core/server/http/http_config.test.ts b/src/core/server/http/http_config.test.ts index 9b6fab8f3daec..082b85ad68add 100644 --- a/src/core/server/http/http_config.test.ts +++ b/src/core/server/http/http_config.test.ts @@ -19,8 +19,6 @@ import uuid from 'uuid'; import { config, HttpConfig } from '.'; -import { Env } from '../config'; -import { getEnvOptions } from '../config/__mocks__/env'; const validHostnames = ['www.example.com', '8.8.8.8', '::1', 'localhost']; const invalidHostname = 'asdf$%^'; @@ -265,8 +263,7 @@ describe('with TLS', () => { clientAuthentication: 'none', }, }), - {} as any, - Env.createDefault(getEnvOptions()) + {} as any ); expect(httpConfig.ssl.requestCert).toBe(false); @@ -283,8 +280,7 @@ describe('with TLS', () => { clientAuthentication: 'optional', }, }), - {} as any, - Env.createDefault(getEnvOptions()) + {} as any ); expect(httpConfig.ssl.requestCert).toBe(true); @@ -301,8 +297,7 @@ describe('with TLS', () => { clientAuthentication: 'required', }, }), - {} as any, - Env.createDefault(getEnvOptions()) + {} as any ); expect(httpConfig.ssl.requestCert).toBe(true); diff --git a/src/core/server/http/http_config.ts b/src/core/server/http/http_config.ts index ef6a9c0a5f1a5..5749eb383f8b9 100644 --- a/src/core/server/http/http_config.ts +++ b/src/core/server/http/http_config.ts @@ -18,7 +18,6 @@ */ import { ByteSizeValue, schema, TypeOf } from '@kbn/config-schema'; -import { Env } from '../config'; import { CspConfigType, CspConfig, ICspConfig } from '../csp'; import { SslConfig, sslSchema } from './ssl_config'; @@ -135,7 +134,6 @@ export class HttpConfig { public maxPayload: ByteSizeValue; public basePath?: string; public rewriteBasePath: boolean; - public publicDir: string; public defaultRoute?: string; public ssl: SslConfig; public compression: { enabled: boolean; referrerWhitelist?: string[] }; @@ -144,7 +142,7 @@ export class HttpConfig { /** * @internal */ - constructor(rawHttpConfig: HttpConfigType, rawCspConfig: CspConfigType, env: Env) { + constructor(rawHttpConfig: HttpConfigType, rawCspConfig: CspConfigType) { this.autoListen = rawHttpConfig.autoListen; this.host = rawHttpConfig.host; this.port = rawHttpConfig.port; @@ -154,7 +152,6 @@ export class HttpConfig { this.keepaliveTimeout = rawHttpConfig.keepaliveTimeout; this.socketTimeout = rawHttpConfig.socketTimeout; this.rewriteBasePath = rawHttpConfig.rewriteBasePath; - this.publicDir = env.staticFilesDir; this.ssl = new SslConfig(rawHttpConfig.ssl || {}); this.defaultRoute = rawHttpConfig.defaultRoute; this.compression = rawHttpConfig.compression; diff --git a/src/core/server/http/http_service.ts b/src/core/server/http/http_service.ts index faeae0b559b6b..e038443d5c83f 100644 --- a/src/core/server/http/http_service.ts +++ b/src/core/server/http/http_service.ts @@ -61,14 +61,14 @@ export class HttpService implements CoreService(httpConfig.path), configService.atPath(cspConfig.path) - ).pipe(map(([http, csp]) => new HttpConfig(http, csp, env))); + ).pipe(map(([http, csp]) => new HttpConfig(http, csp))); this.httpServer = new HttpServer(logger, 'Kibana'); this.httpsRedirectServer = new HttpsRedirectServer(logger.get('http', 'redirect', 'server')); } diff --git a/src/core/server/http/http_tools.test.ts b/src/core/server/http/http_tools.test.ts index b889ebd64971f..c1322a5aa94db 100644 --- a/src/core/server/http/http_tools.test.ts +++ b/src/core/server/http/http_tools.test.ts @@ -31,8 +31,6 @@ import { HttpConfig, config } from './http_config'; import { Router } from './router'; import { loggingServiceMock } from '../logging/logging_service.mock'; import { ByteSizeValue } from '@kbn/config-schema'; -import { Env } from '../config'; -import { getEnvOptions } from '../config/__mocks__/env'; const emptyOutput = { statusCode: 400, @@ -122,8 +120,7 @@ describe('getServerOptions', () => { certificate: 'some-certificate-path', }, }), - {} as any, - Env.createDefault(getEnvOptions()) + {} as any ); expect(getServerOptions(httpConfig).tls).toMatchInlineSnapshot(` @@ -152,8 +149,7 @@ describe('getServerOptions', () => { clientAuthentication: 'required', }, }), - {} as any, - Env.createDefault(getEnvOptions()) + {} as any ); expect(getServerOptions(httpConfig).tls).toMatchInlineSnapshot(` diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index 2ed87f4c6d488..e17de7364ce59 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -84,7 +84,7 @@ export class LegacyService implements CoreService { private settings?: LegacyVars; constructor(private readonly coreContext: CoreContext) { - const { logger, configService, env } = coreContext; + const { logger, configService } = coreContext; this.log = logger.get('legacy-service'); this.devConfig$ = configService @@ -93,7 +93,7 @@ export class LegacyService implements CoreService { this.httpConfig$ = combineLatest( configService.atPath(httpConfig.path), configService.atPath(cspConfig.path) - ).pipe(map(([http, csp]) => new HttpConfig(http, csp, env))); + ).pipe(map(([http, csp]) => new HttpConfig(http, csp))); } public async discoverPlugins(): Promise { diff --git a/src/core/server/plugins/discovery/plugins_discovery.test.ts b/src/core/server/plugins/discovery/plugins_discovery.test.ts index bf55fc7caae4c..2902aafdbf146 100644 --- a/src/core/server/plugins/discovery/plugins_discovery.test.ts +++ b/src/core/server/plugins/discovery/plugins_discovery.test.ts @@ -18,13 +18,14 @@ */ import { mockPackage, mockReaddir, mockReadFile, mockStat } from './plugins_discovery.test.mocks'; +import { rawConfigServiceMock } from '../../config/raw_config_service.mock'; +import { loggingServiceMock } from '../../logging/logging_service.mock'; import { resolve } from 'path'; import { first, map, toArray } from 'rxjs/operators'; + import { ConfigService, Env } from '../../config'; -import { rawConfigServiceMock } from '../../config/raw_config_service.mock'; import { getEnvOptions } from '../../config/__mocks__/env'; -import { loggingServiceMock } from '../../logging/logging_service.mock'; import { PluginWrapper } from '../plugin'; import { PluginsConfig, PluginsConfigType, config } from '../plugins_config'; import { discover } from './plugins_discovery'; @@ -37,6 +38,7 @@ const TEST_PLUGIN_SEARCH_PATHS = { const TEST_EXTRA_PLUGIN_PATH = resolve(process.cwd(), 'my-extra-plugin'); const logger = loggingServiceMock.create(); + beforeEach(() => { mockReaddir.mockImplementation((path, cb) => { if (path === TEST_PLUGIN_SEARCH_PATHS.nonEmptySrcPlugins) { @@ -182,12 +184,84 @@ test('properly iterates through plugin search locations', async () => { 'kibana.json' )})`, ]); +}); + +test('logs a warning about --plugin-path when used in development', async () => { + mockPackage.raw = { + branch: 'master', + version: '1.2.3', + build: { + distributable: true, + number: 1, + sha: '', + }, + }; + + const env = Env.createDefault( + getEnvOptions({ + cliArgs: { dev: false, envName: 'development' }, + }) + ); + const configService = new ConfigService( + rawConfigServiceMock.create({ rawConfig: { plugins: { paths: [TEST_EXTRA_PLUGIN_PATH] } } }), + env, + logger + ); + await configService.setSchema(config.path, config.schema); + + const rawConfig = await configService + .atPath('plugins') + .pipe(first()) + .toPromise(); + + discover(new PluginsConfig(rawConfig, env), { + coreId: Symbol(), + configService, + env, + logger, + }); + + expect(loggingServiceMock.collect(logger).warn).toEqual([ + [ + `Explicit plugin paths [${TEST_EXTRA_PLUGIN_PATH}] should only be used in development. Relative imports may not work properly in production.`, + ], + ]); +}); + +test('does not log a warning about --plugin-path when used in production', async () => { + mockPackage.raw = { + branch: 'master', + version: '1.2.3', + build: { + distributable: true, + number: 1, + sha: '', + }, + }; + + const env = Env.createDefault( + getEnvOptions({ + cliArgs: { dev: false, envName: 'production' }, + }) + ); + const configService = new ConfigService( + rawConfigServiceMock.create({ rawConfig: { plugins: { paths: [TEST_EXTRA_PLUGIN_PATH] } } }), + env, + logger + ); + await configService.setSchema(config.path, config.schema); + + const rawConfig = await configService + .atPath('plugins') + .pipe(first()) + .toPromise(); + + discover(new PluginsConfig(rawConfig, env), { + coreId: Symbol(), + configService, + env, + logger, + }); - expect(loggingServiceMock.collect(logger).warn).toMatchInlineSnapshot(` -Array [ - Array [ - "Explicit plugin paths [${TEST_EXTRA_PLUGIN_PATH}] are only supported in development. Relative imports will not work in production.", - ], -] -`); + expect(loggingServiceMock.collect(logger).warn).toEqual([]); }); diff --git a/src/core/server/plugins/discovery/plugins_discovery.ts b/src/core/server/plugins/discovery/plugins_discovery.ts index 521d02e487df6..79238afdf5c81 100644 --- a/src/core/server/plugins/discovery/plugins_discovery.ts +++ b/src/core/server/plugins/discovery/plugins_discovery.ts @@ -46,9 +46,9 @@ export function discover(config: PluginsConfig, coreContext: CoreContext) { const log = coreContext.logger.get('plugins-discovery'); log.debug('Discovering plugins...'); - if (config.additionalPluginPaths.length) { + if (config.additionalPluginPaths.length && coreContext.env.mode.dev) { log.warn( - `Explicit plugin paths [${config.additionalPluginPaths}] are only supported in development. Relative imports will not work in production.` + `Explicit plugin paths [${config.additionalPluginPaths}] should only be used in development. Relative imports may not work properly in production.` ); } diff --git a/src/core/server/plugins/plugins_config.test.ts b/src/core/server/plugins/plugins_config.test.ts new file mode 100644 index 0000000000000..180d6093e0404 --- /dev/null +++ b/src/core/server/plugins/plugins_config.test.ts @@ -0,0 +1,44 @@ +/* + * 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 { PluginsConfig, PluginsConfigType } from './plugins_config'; +import { Env } from '../config'; +import { getEnvOptions } from '../config/__mocks__/env'; + +describe('PluginsConfig', () => { + it('retrieves additionalPluginPaths from config.paths when in production mode', () => { + const env = Env.createDefault(getEnvOptions({ cliArgs: { dev: false } })); + const rawConfig: PluginsConfigType = { + initialize: true, + paths: ['some-path', 'another-path'], + }; + const config = new PluginsConfig(rawConfig, env); + expect(config.additionalPluginPaths).toEqual(['some-path', 'another-path']); + }); + + it('retrieves additionalPluginPaths from config.paths when in development mode', () => { + const env = Env.createDefault(getEnvOptions({ cliArgs: { dev: true } })); + const rawConfig: PluginsConfigType = { + initialize: true, + paths: ['some-path', 'another-path'], + }; + const config = new PluginsConfig(rawConfig, env); + expect(config.additionalPluginPaths).toEqual(['some-path', 'another-path']); + }); +}); diff --git a/src/core/server/plugins/plugins_config.ts b/src/core/server/plugins/plugins_config.ts index 93ae229737fae..8ebcc92672dde 100644 --- a/src/core/server/plugins/plugins_config.ts +++ b/src/core/server/plugins/plugins_config.ts @@ -29,7 +29,6 @@ export const config = { /** * Defines an array of directories where another plugin should be loaded from. - * Should only be used in a development environment. */ paths: schema.arrayOf(schema.string(), { defaultValue: [] }), }), @@ -55,7 +54,6 @@ export class PluginsConfig { constructor(rawConfig: PluginsConfigType, env: Env) { this.initialize = rawConfig.initialize; this.pluginSearchPaths = env.pluginSearchPaths; - // Only allow custom plugin paths in dev. - this.additionalPluginPaths = env.mode.dev ? rawConfig.paths : []; + this.additionalPluginPaths = rawConfig.paths; } } diff --git a/src/core/server/rendering/__snapshots__/rendering_service.test.ts.snap b/src/core/server/rendering/__snapshots__/rendering_service.test.ts.snap index edde1dee85f4f..5e6e977663bc4 100644 --- a/src/core/server/rendering/__snapshots__/rendering_service.test.ts.snap +++ b/src/core/server/rendering/__snapshots__/rendering_service.test.ts.snap @@ -18,6 +18,7 @@ Object { "oss": false, "quiet": false, "repl": false, + "runExamples": false, "silent": false, "watch": false, }, @@ -39,7 +40,6 @@ Object { "version": Any, }, "pluginSearchPaths": Any, - "staticFilesDir": Any, }, "i18n": Object { "translationsUrl": "/mock-server-basepath/translations/en.json", @@ -89,6 +89,7 @@ Object { "oss": false, "quiet": false, "repl": false, + "runExamples": false, "silent": false, "watch": false, }, @@ -110,7 +111,6 @@ Object { "version": Any, }, "pluginSearchPaths": Any, - "staticFilesDir": Any, }, "i18n": Object { "translationsUrl": "/mock-server-basepath/translations/en.json", @@ -160,6 +160,7 @@ Object { "oss": false, "quiet": false, "repl": false, + "runExamples": false, "silent": false, "watch": false, }, @@ -181,7 +182,6 @@ Object { "version": Any, }, "pluginSearchPaths": Any, - "staticFilesDir": Any, }, "i18n": Object { "translationsUrl": "/mock-server-basepath/translations/en.json", @@ -235,6 +235,7 @@ Object { "oss": false, "quiet": false, "repl": false, + "runExamples": false, "silent": false, "watch": false, }, @@ -256,7 +257,6 @@ Object { "version": Any, }, "pluginSearchPaths": Any, - "staticFilesDir": Any, }, "i18n": Object { "translationsUrl": "/translations/en.json", @@ -306,6 +306,7 @@ Object { "oss": false, "quiet": false, "repl": false, + "runExamples": false, "silent": false, "watch": false, }, @@ -327,7 +328,6 @@ Object { "version": Any, }, "pluginSearchPaths": Any, - "staticFilesDir": Any, }, "i18n": Object { "translationsUrl": "/mock-server-basepath/translations/en.json", @@ -377,6 +377,7 @@ Object { "oss": false, "quiet": false, "repl": false, + "runExamples": false, "silent": false, "watch": false, }, @@ -398,7 +399,6 @@ Object { "version": Any, }, "pluginSearchPaths": Any, - "staticFilesDir": Any, }, "i18n": Object { "translationsUrl": "/mock-server-basepath/translations/en.json", @@ -448,6 +448,7 @@ Object { "oss": false, "quiet": false, "repl": false, + "runExamples": false, "silent": false, "watch": false, }, @@ -469,7 +470,6 @@ Object { "version": Any, }, "pluginSearchPaths": Any, - "staticFilesDir": Any, }, "i18n": Object { "translationsUrl": "/translations/en.json", @@ -519,6 +519,7 @@ Object { "oss": false, "quiet": false, "repl": false, + "runExamples": false, "silent": false, "watch": false, }, @@ -540,7 +541,6 @@ Object { "version": Any, }, "pluginSearchPaths": Any, - "staticFilesDir": Any, }, "i18n": Object { "translationsUrl": "/mock-server-basepath/translations/en.json", @@ -592,6 +592,7 @@ Object { "oss": false, "quiet": false, "repl": false, + "runExamples": false, "silent": false, "watch": false, }, @@ -613,7 +614,6 @@ Object { "version": Any, }, "pluginSearchPaths": Any, - "staticFilesDir": Any, }, "i18n": Object { "translationsUrl": "/mock-server-basepath/translations/en.json", @@ -663,6 +663,7 @@ Object { "oss": false, "quiet": false, "repl": false, + "runExamples": false, "silent": false, "watch": false, }, @@ -684,7 +685,6 @@ Object { "version": Any, }, "pluginSearchPaths": Any, - "staticFilesDir": Any, }, "i18n": Object { "translationsUrl": "/mock-server-basepath/translations/en.json", diff --git a/src/core/server/rendering/rendering_service.test.ts b/src/core/server/rendering/rendering_service.test.ts index 63145f2b30573..43ff4f633085c 100644 --- a/src/core/server/rendering/rendering_service.test.ts +++ b/src/core/server/rendering/rendering_service.test.ts @@ -41,7 +41,6 @@ const INJECTED_METADATA = { version: expect.any(String), }, pluginSearchPaths: expect.any(Array), - staticFilesDir: expect.any(String), }, legacyMetadata: { branch: expect.any(String), @@ -50,6 +49,7 @@ const INJECTED_METADATA = { version: expect.any(String), }, }; + const { createKibanaRequest, createRawRequest } = httpServerMock; const legacyApp = { getId: () => 'legacy' }; diff --git a/src/test_utils/kbn_server.ts b/src/test_utils/kbn_server.ts index 40700e05bcde8..43c6b4378ed27 100644 --- a/src/test_utils/kbn_server.ts +++ b/src/test_utils/kbn_server.ts @@ -76,6 +76,7 @@ export function createRootWithSettings( repl: false, basePath: false, optimize: false, + runExamples: false, oss: true, ...cliArgs, }, From d64c4cb5fe6bb7df9c1e34c56ae14f3ff14518da Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Mon, 6 Jan 2020 12:24:25 +0300 Subject: [PATCH 08/17] increase delay to make sure license refetched (#53882) Co-authored-by: Elastic Machine --- x-pack/test/licensing_plugin/public/updates.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/test/licensing_plugin/public/updates.ts b/x-pack/test/licensing_plugin/public/updates.ts index 6d2253fe83868..2e750dcea1a59 100644 --- a/x-pack/test/licensing_plugin/public/updates.ts +++ b/x-pack/test/licensing_plugin/public/updates.ts @@ -32,7 +32,7 @@ export default function(ftrContext: FtrProviderContext) { // this call enforces signature check to detect license update // and causes license re-fetch await setup.core.http.get('/'); - await testUtils.delay(100); + await testUtils.delay(500); const licensing: LicensingPluginSetup = setup.plugins.licensing; licensing.license$.subscribe(license => cb(license.type)); @@ -48,7 +48,7 @@ export default function(ftrContext: FtrProviderContext) { // this call enforces signature check to detect license update // and causes license re-fetch await setup.core.http.get('/'); - await testUtils.delay(100); + await testUtils.delay(500); const licensing: LicensingPluginSetup = setup.plugins.licensing; licensing.license$.subscribe(license => cb(license.type)); @@ -64,7 +64,7 @@ export default function(ftrContext: FtrProviderContext) { // this call enforces signature check to detect license update // and causes license re-fetch await setup.core.http.get('/'); - await testUtils.delay(100); + await testUtils.delay(500); const licensing: LicensingPluginSetup = setup.plugins.licensing; licensing.license$.subscribe(license => cb(license.type)); @@ -80,7 +80,7 @@ export default function(ftrContext: FtrProviderContext) { // this call enforces signature check to detect license update // and causes license re-fetch await setup.core.http.get('/'); - await testUtils.delay(100); + await testUtils.delay(500); const licensing: LicensingPluginSetup = setup.plugins.licensing; licensing.license$.subscribe(license => cb(license.type)); From aa38fb68a7473aec1c341e759a0d9e622d4c346a Mon Sep 17 00:00:00 2001 From: Aleh Zasypkin Date: Mon, 6 Jan 2020 11:43:15 +0100 Subject: [PATCH 09/17] Migrate config deprecations and `ShieldUser` functionality to the New Platform (#53768) --- .../framework/kibana_framework_adapter.ts | 8 ++- .../components/pipeline_edit/pipeline_edit.js | 4 +- x-pack/legacy/plugins/security/index.js | 18 +---- .../legacy/plugins/security/public/lib/api.ts | 6 +- .../security/public/services/shield_user.js | 33 --------- .../security/public/views/account/account.js | 26 +++---- .../account_management_page.test.tsx | 71 ++++++++++++++++--- .../components/account_management_page.tsx | 44 +++++++----- .../views/management/edit_role/index.js | 7 +- .../components/edit_user_page.test.tsx | 71 ++++++++++++------- .../edit_user/components/edit_user_page.tsx | 6 +- .../views/management/edit_user/edit_user.js | 9 ++- .../public/views/management/management.js | 15 ++-- .../views/management/users_grid/users.js | 1 - .../overwritten_session.tsx | 52 +++++++------- .../authentication/authentication_service.ts | 31 ++++++++ .../public/authentication/index.mock.ts | 13 ++++ .../security/public/authentication/index.ts | 7 ++ x-pack/plugins/security/public/index.ts | 3 + x-pack/plugins/security/public/mocks.ts | 19 +++++ .../nav_control/nav_control_service.test.ts | 12 +++- .../nav_control/nav_control_service.tsx | 21 +++--- x-pack/plugins/security/public/plugin.ts | 9 ++- .../plugins/security/public/session/index.ts | 2 +- .../public/session/session_timeout.tsx | 2 +- x-pack/plugins/security/server/config.ts | 15 +--- x-pack/plugins/security/server/index.ts | 27 +++++-- x-pack/plugins/security/server/plugin.ts | 7 +- 28 files changed, 326 insertions(+), 213 deletions(-) delete mode 100644 x-pack/legacy/plugins/security/public/services/shield_user.js create mode 100644 x-pack/plugins/security/public/authentication/authentication_service.ts create mode 100644 x-pack/plugins/security/public/authentication/index.mock.ts create mode 100644 x-pack/plugins/security/public/authentication/index.ts create mode 100644 x-pack/plugins/security/public/mocks.ts diff --git a/x-pack/legacy/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/legacy/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts index 79ffe58d419bd..b2cfd826e6207 100644 --- a/x-pack/legacy/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts +++ b/x-pack/legacy/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts @@ -11,6 +11,8 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { UIRoutes } from 'ui/routes'; import { isLeft } from 'fp-ts/lib/Either'; +import { npSetup } from 'ui/new_platform'; +import { SecurityPluginSetup } from '../../../../../../../plugins/security/public'; import { BufferedKibanaServiceCall, KibanaAdapterServiceRefs, KibanaUIConfig } from '../../types'; import { FrameworkAdapter, @@ -58,7 +60,7 @@ export class KibanaFrameworkAdapter implements FrameworkAdapter { }; public async waitUntilFrameworkReady(): Promise { - const $injector = await this.onKibanaReady(); + await this.onKibanaReady(); const xpackInfo: any = this.xpackInfoService; let xpackInfoUnpacked: FrameworkInfo; @@ -95,8 +97,10 @@ export class KibanaFrameworkAdapter implements FrameworkAdapter { } this.xpackInfo = xpackInfoUnpacked; + const securitySetup = ((npSetup.plugins as unknown) as { security?: SecurityPluginSetup }) + .security; try { - this.shieldUser = await $injector.get('ShieldUser').getCurrent().$promise; + this.shieldUser = (await securitySetup?.authc.getCurrentUser()) || null; const assertUser = RuntimeFrameworkUser.decode(this.shieldUser); if (isLeft(assertUser)) { diff --git a/x-pack/legacy/plugins/logstash/public/sections/pipeline_edit/components/pipeline_edit/pipeline_edit.js b/x-pack/legacy/plugins/logstash/public/sections/pipeline_edit/components/pipeline_edit/pipeline_edit.js index aa7f88a62397c..83446278fdeca 100755 --- a/x-pack/legacy/plugins/logstash/public/sections/pipeline_edit/components/pipeline_edit/pipeline_edit.js +++ b/x-pack/legacy/plugins/logstash/public/sections/pipeline_edit/components/pipeline_edit/pipeline_edit.js @@ -8,6 +8,7 @@ import React from 'react'; import { render } from 'react-dom'; import { isEmpty } from 'lodash'; import { uiModules } from 'ui/modules'; +import { npSetup } from 'ui/new_platform'; import { toastNotifications } from 'ui/notify'; import { I18nContext } from 'ui/i18n'; import { PipelineEditor } from '../../../../components/pipeline_editor'; @@ -21,7 +22,6 @@ app.directive('pipelineEdit', function($injector) { const pipelineService = $injector.get('pipelineService'); const licenseService = $injector.get('logstashLicenseService'); const kbnUrl = $injector.get('kbnUrl'); - const shieldUser = $injector.get('ShieldUser'); const $route = $injector.get('$route'); return { @@ -32,7 +32,7 @@ app.directive('pipelineEdit', function($injector) { scope.$evalAsync(kbnUrl.change(`/management/logstash/pipelines/${id}/edit`)); const userResource = logstashSecurity.isSecurityEnabled() - ? await shieldUser.getCurrent().$promise + ? await npSetup.plugins.security.authc.getCurrentUser() : null; render( diff --git a/x-pack/legacy/plugins/security/index.js b/x-pack/legacy/plugins/security/index.js index 6ee8b5f8b2b10..bc403b803b8df 100644 --- a/x-pack/legacy/plugins/security/index.js +++ b/x-pack/legacy/plugins/security/index.js @@ -28,17 +28,10 @@ export const security = kibana => enabled: Joi.boolean().default(true), cookieName: HANDLED_IN_NEW_PLATFORM, encryptionKey: HANDLED_IN_NEW_PLATFORM, - session: Joi.object({ - idleTimeout: HANDLED_IN_NEW_PLATFORM, - lifespan: HANDLED_IN_NEW_PLATFORM, - }).default(), + session: HANDLED_IN_NEW_PLATFORM, secureCookies: HANDLED_IN_NEW_PLATFORM, loginAssistanceMessage: HANDLED_IN_NEW_PLATFORM, - authorization: Joi.object({ - legacyFallback: Joi.object({ - enabled: Joi.boolean().default(true), // deprecated - }).default(), - }).default(), + authorization: HANDLED_IN_NEW_PLATFORM, audit: Joi.object({ enabled: Joi.boolean().default(false), }).default(), @@ -46,13 +39,6 @@ export const security = kibana => }).default(); }, - deprecations: function({ rename, unused }) { - return [ - unused('authorization.legacyFallback.enabled'), - rename('sessionTimeout', 'session.idleTimeout'), - ]; - }, - uiExports: { chromeNavControls: [], managementSections: ['plugins/security/views/management'], diff --git a/x-pack/legacy/plugins/security/public/lib/api.ts b/x-pack/legacy/plugins/security/public/lib/api.ts index ffa08ca44f376..c5c6994bf4be3 100644 --- a/x-pack/legacy/plugins/security/public/lib/api.ts +++ b/x-pack/legacy/plugins/security/public/lib/api.ts @@ -5,16 +5,12 @@ */ import { kfetch } from 'ui/kfetch'; -import { AuthenticatedUser, Role, User, EditUser } from '../../common/model'; +import { Role, User, EditUser } from '../../common/model'; const usersUrl = '/internal/security/users'; const rolesUrl = '/api/security/role'; export class UserAPIClient { - public async getCurrentUser(): Promise { - return await kfetch({ pathname: `/internal/security/me` }); - } - public async getUsers(): Promise { return await kfetch({ pathname: usersUrl }); } diff --git a/x-pack/legacy/plugins/security/public/services/shield_user.js b/x-pack/legacy/plugins/security/public/services/shield_user.js deleted file mode 100644 index 14a79f267ca75..0000000000000 --- a/x-pack/legacy/plugins/security/public/services/shield_user.js +++ /dev/null @@ -1,33 +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 'angular-resource'; -import angular from 'angular'; -import { uiModules } from 'ui/modules'; - -const module = uiModules.get('security', ['ngResource']); -module.service('ShieldUser', ($resource, chrome) => { - const baseUrl = chrome.addBasePath('/internal/security/users/:username'); - const ShieldUser = $resource( - baseUrl, - { - username: '@username', - }, - { - changePassword: { - method: 'POST', - url: `${baseUrl}/password`, - transformRequest: ({ password, newPassword }) => angular.toJson({ password, newPassword }), - }, - getCurrent: { - method: 'GET', - url: chrome.addBasePath('/internal/security/me'), - }, - } - ); - - return ShieldUser; -}); diff --git a/x-pack/legacy/plugins/security/public/views/account/account.js b/x-pack/legacy/plugins/security/public/views/account/account.js index db971bd97eab7..70a7b8dce727e 100644 --- a/x-pack/legacy/plugins/security/public/views/account/account.js +++ b/x-pack/legacy/plugins/security/public/views/account/account.js @@ -6,22 +6,13 @@ import routes from 'ui/routes'; import template from './account.html'; -import '../../services/shield_user'; import { i18n } from '@kbn/i18n'; import { I18nContext } from 'ui/i18n'; +import { npSetup } from 'ui/new_platform'; import { AccountManagementPage } from './components'; import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -const renderReact = (elem, user) => { - render( - - - , - elem - ); -}; - routes.when('/account', { template, k7Breadcrumbs: () => [ @@ -31,13 +22,8 @@ routes.when('/account', { }), }, ], - resolve: { - user(ShieldUser) { - return ShieldUser.getCurrent().$promise; - }, - }, controllerAs: 'accountController', - controller($scope, $route) { + controller($scope) { $scope.$on('$destroy', () => { const elem = document.getElementById('userProfileReactRoot'); if (elem) { @@ -45,8 +31,12 @@ routes.when('/account', { } }); $scope.$$postDigest(() => { - const elem = document.getElementById('userProfileReactRoot'); - renderReact(elem, $route.current.locals.user); + render( + + + , + document.getElementById('userProfileReactRoot') + ); }); }, }); diff --git a/x-pack/legacy/plugins/security/public/views/account/components/account_management_page.test.tsx b/x-pack/legacy/plugins/security/public/views/account/components/account_management_page.test.tsx index 176b05f455439..366842e58e9e4 100644 --- a/x-pack/legacy/plugins/security/public/views/account/components/account_management_page.test.tsx +++ b/x-pack/legacy/plugins/security/public/views/account/components/account_management_page.test.tsx @@ -4,8 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ import React from 'react'; -import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { act } from '@testing-library/react'; +import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; +import { securityMock } from '../../../../../../../plugins/security/public/mocks'; import { AccountManagementPage } from './account_management_page'; +import { AuthenticatedUser } from '../../../../common/model'; jest.mock('ui/kfetch'); @@ -32,10 +35,24 @@ const createUser = ({ withFullName = true, withEmail = true, realm = 'native' }: }; }; +function getSecuritySetupMock({ currentUser }: { currentUser: AuthenticatedUser }) { + const securitySetupMock = securityMock.createSetup(); + securitySetupMock.authc.getCurrentUser.mockResolvedValue(currentUser); + return securitySetupMock; +} + describe('', () => { - it(`displays users full name, username, and email address`, () => { + it(`displays users full name, username, and email address`, async () => { const user = createUser(); - const wrapper = mountWithIntl(); + const wrapper = mountWithIntl( + + ); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + expect(wrapper.find('EuiText[data-test-subj="userDisplayName"]').text()).toEqual( user.full_name ); @@ -43,28 +60,60 @@ describe('', () => { expect(wrapper.find('[data-test-subj="email"]').text()).toEqual(user.email); }); - it(`displays username when full_name is not provided`, () => { + it(`displays username when full_name is not provided`, async () => { const user = createUser({ withFullName: false }); - const wrapper = mountWithIntl(); + const wrapper = mountWithIntl( + + ); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + expect(wrapper.find('EuiText[data-test-subj="userDisplayName"]').text()).toEqual(user.username); }); - it(`displays a placeholder when no email address is provided`, () => { + it(`displays a placeholder when no email address is provided`, async () => { const user = createUser({ withEmail: false }); - const wrapper = mountWithIntl(); + const wrapper = mountWithIntl( + + ); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + expect(wrapper.find('[data-test-subj="email"]').text()).toEqual('no email address'); }); - it(`displays change password form for users in the native realm`, () => { + it(`displays change password form for users in the native realm`, async () => { const user = createUser(); - const wrapper = mountWithIntl(); + const wrapper = mountWithIntl( + + ); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + expect(wrapper.find('EuiFieldText[data-test-subj="currentPassword"]')).toHaveLength(1); expect(wrapper.find('EuiFieldText[data-test-subj="newPassword"]')).toHaveLength(1); }); - it(`does not display change password form for users in the saml realm`, () => { + it(`does not display change password form for users in the saml realm`, async () => { const user = createUser({ realm: 'saml' }); - const wrapper = mountWithIntl(); + const wrapper = mountWithIntl( + + ); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + expect(wrapper.find('EuiFieldText[data-test-subj="currentPassword"]')).toHaveLength(0); expect(wrapper.find('EuiFieldText[data-test-subj="newPassword"]')).toHaveLength(0); }); diff --git a/x-pack/legacy/plugins/security/public/views/account/components/account_management_page.tsx b/x-pack/legacy/plugins/security/public/views/account/components/account_management_page.tsx index 2ed057ad73a12..6abee73e0b353 100644 --- a/x-pack/legacy/plugins/security/public/views/account/components/account_management_page.tsx +++ b/x-pack/legacy/plugins/security/public/views/account/components/account_management_page.tsx @@ -4,29 +4,41 @@ * you may not use this file except in compliance with the Elastic License. */ import { EuiPage, EuiPageBody, EuiPanel, EuiSpacer, EuiText } from '@elastic/eui'; -import React from 'react'; +import React, { useEffect, useState } from 'react'; +import { SecurityPluginSetup } from '../../../../../../../plugins/security/public'; import { getUserDisplayName, AuthenticatedUser } from '../../../../common/model'; import { ChangePassword } from './change_password'; import { PersonalInfo } from './personal_info'; interface Props { - user: AuthenticatedUser; + securitySetup: SecurityPluginSetup; } -export const AccountManagementPage: React.FC = props => ( - - - - -

{getUserDisplayName(props.user)}

-
+export const AccountManagementPage = (props: Props) => { + const [currentUser, setCurrentUser] = useState(null); + useEffect(() => { + props.securitySetup.authc.getCurrentUser().then(setCurrentUser); + }, [props]); - + if (!currentUser) { + return null; + } - + return ( + + + + +

{getUserDisplayName(currentUser)}

+
- -
-
-
-); + + + + + +
+
+
+ ); +}; diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/index.js b/x-pack/legacy/plugins/security/public/views/management/edit_role/index.js index 09c612526918f..27c9beb4ba828 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/index.js +++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/index.js @@ -11,10 +11,10 @@ import { kfetch } from 'ui/kfetch'; import { fatalError, toastNotifications } from 'ui/notify'; import { npStart } from 'ui/new_platform'; import template from 'plugins/security/views/management/edit_role/edit_role.html'; -import 'plugins/security/services/shield_user'; import 'plugins/security/services/shield_role'; import 'plugins/security/services/shield_indices'; import { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; +import { UserAPIClient } from '../../../lib/api'; import { ROLES_PATH, CLONE_ROLES_PATH, EDIT_ROLES_PATH } from '../management_urls'; import { getEditRoleBreadcrumbs, getCreateRoleBreadcrumbs } from '../breadcrumbs'; @@ -69,9 +69,8 @@ const routeDefinition = action => ({ return role.then(res => res.toJSON()); }, - users(ShieldUser) { - // $promise is used here because the result is an ngResource, not a promise itself - return ShieldUser.query().$promise.then(users => _.map(users, 'username')); + users() { + return new UserAPIClient().getUsers().then(users => _.map(users, 'username')); }, indexPatterns() { return npStart.plugins.data.indexPatterns.getTitles(); diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_user/components/edit_user_page.test.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_user/components/edit_user_page.test.tsx index 5c71d0da3954a..639646ce48e22 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_user/components/edit_user_page.test.tsx +++ b/x-pack/legacy/plugins/security/public/views/management/edit_user/components/edit_user_page.test.tsx @@ -4,38 +4,42 @@ * you may not use this file except in compliance with the Elastic License. */ -import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { act } from '@testing-library/react'; +import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; import { EditUserPage } from './edit_user_page'; import React from 'react'; +import { securityMock } from '../../../../../../../../plugins/security/public/mocks'; import { UserAPIClient } from '../../../../lib/api'; import { User, Role } from '../../../../../common/model'; import { ReactWrapper } from 'enzyme'; +import { mockAuthenticatedUser } from '../../../../../../../../plugins/security/common/model/authenticated_user.mock'; jest.mock('ui/kfetch'); -const buildClient = () => { - const apiClient = new UserAPIClient(); +const createUser = (username: string) => { + const user: User = { + username, + full_name: 'my full name', + email: 'foo@bar.com', + roles: ['idk', 'something'], + enabled: true, + }; - const createUser = (username: string) => { - const user: User = { - username, - full_name: 'my full name', - email: 'foo@bar.com', - roles: ['idk', 'something'], - enabled: true, + if (username === 'reserved_user') { + user.metadata = { + _reserved: true, }; + } - if (username === 'reserved_user') { - user.metadata = { - _reserved: true, - }; - } + return user; +}; - return Promise.resolve(user); - }; +const buildClient = () => { + const apiClient = new UserAPIClient(); - apiClient.getUser = jest.fn().mockImplementation(createUser); - apiClient.getCurrentUser = jest.fn().mockImplementation(() => createUser('current_user')); + apiClient.getUser = jest + .fn() + .mockImplementation(async (username: string) => createUser(username)); apiClient.getRoles = jest.fn().mockImplementation(() => { return Promise.resolve([ @@ -63,6 +67,14 @@ const buildClient = () => { return apiClient; }; +function buildSecuritySetup() { + const securitySetupMock = securityMock.createSetup(); + securitySetupMock.authc.getCurrentUser.mockResolvedValue( + mockAuthenticatedUser(createUser('current_user')) + ); + return securitySetupMock; +} + function expectSaveButton(wrapper: ReactWrapper) { expect(wrapper.find('EuiButton[data-test-subj="userFormSaveButton"]')).toHaveLength(1); } @@ -74,10 +86,12 @@ function expectMissingSaveButton(wrapper: ReactWrapper) { describe('EditUserPage', () => { it('allows reserved users to be viewed', async () => { const apiClient = buildClient(); + const securitySetup = buildSecuritySetup(); const wrapper = mountWithIntl( path} intl={null as any} /> @@ -86,17 +100,19 @@ describe('EditUserPage', () => { await waitForRender(wrapper); expect(apiClient.getUser).toBeCalledTimes(1); - expect(apiClient.getCurrentUser).toBeCalledTimes(1); + expect(securitySetup.authc.getCurrentUser).toBeCalledTimes(1); expectMissingSaveButton(wrapper); }); it('allows new users to be created', async () => { const apiClient = buildClient(); + const securitySetup = buildSecuritySetup(); const wrapper = mountWithIntl( path} intl={null as any} /> @@ -105,17 +121,19 @@ describe('EditUserPage', () => { await waitForRender(wrapper); expect(apiClient.getUser).toBeCalledTimes(0); - expect(apiClient.getCurrentUser).toBeCalledTimes(0); + expect(securitySetup.authc.getCurrentUser).toBeCalledTimes(0); expectSaveButton(wrapper); }); it('allows existing users to be edited', async () => { const apiClient = buildClient(); + const securitySetup = buildSecuritySetup(); const wrapper = mountWithIntl( path} intl={null as any} /> @@ -124,16 +142,15 @@ describe('EditUserPage', () => { await waitForRender(wrapper); expect(apiClient.getUser).toBeCalledTimes(1); - expect(apiClient.getCurrentUser).toBeCalledTimes(1); + expect(securitySetup.authc.getCurrentUser).toBeCalledTimes(1); expectSaveButton(wrapper); }); }); async function waitForRender(wrapper: ReactWrapper) { - await Promise.resolve(); - await Promise.resolve(); - await Promise.resolve(); - await Promise.resolve(); - wrapper.update(); + await act(async () => { + await nextTick(); + wrapper.update(); + }); } diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_user/components/edit_user_page.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_user/components/edit_user_page.tsx index 91f5f048adc6d..bbffe07485f8d 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_user/components/edit_user_page.tsx +++ b/x-pack/legacy/plugins/security/public/views/management/edit_user/components/edit_user_page.tsx @@ -28,6 +28,7 @@ import { } from '@elastic/eui'; import { toastNotifications } from 'ui/notify'; import { FormattedMessage, injectI18n, InjectedIntl } from '@kbn/i18n/react'; +import { SecurityPluginSetup } from '../../../../../../../../plugins/security/public'; import { UserValidator, UserValidationResult } from '../../../../lib/validate_user'; import { User, EditUser, Role } from '../../../../../common/model'; import { USERS_PATH } from '../../../../views/management/management_urls'; @@ -40,6 +41,7 @@ interface Props { intl: InjectedIntl; changeUrl: (path: string) => void; apiClient: UserAPIClient; + securitySetup: SecurityPluginSetup; } interface State { @@ -82,7 +84,7 @@ class EditUserPageUI extends Component { } public async componentDidMount() { - const { username, apiClient } = this.props; + const { username, apiClient, securitySetup } = this.props; let { user, currentUser } = this.state; if (username) { try { @@ -91,7 +93,7 @@ class EditUserPageUI extends Component { password: '', confirmPassword: '', }; - currentUser = await apiClient.getCurrentUser(); + currentUser = await securitySetup.authc.getCurrentUser(); } catch (err) { toastNotifications.addDanger({ title: this.props.intl.formatMessage({ diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_user/edit_user.js b/x-pack/legacy/plugins/security/public/views/management/edit_user/edit_user.js index bd9d6f2b1ca35..ab218022c6ee6 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_user/edit_user.js +++ b/x-pack/legacy/plugins/security/public/views/management/edit_user/edit_user.js @@ -7,7 +7,6 @@ import routes from 'ui/routes'; import template from 'plugins/security/views/management/edit_user/edit_user.html'; import 'angular-resource'; import 'ui/angular_ui_select'; -import 'plugins/security/services/shield_user'; import 'plugins/security/services/shield_role'; import { EDIT_USERS_PATH } from '../management_urls'; import { EditUserPage } from './components'; @@ -15,12 +14,18 @@ import { UserAPIClient } from '../../../lib/api'; import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { I18nContext } from 'ui/i18n'; +import { npSetup } from 'ui/new_platform'; import { getEditUserBreadcrumbs, getCreateUserBreadcrumbs } from '../breadcrumbs'; const renderReact = (elem, changeUrl, username) => { render( - + , elem ); diff --git a/x-pack/legacy/plugins/security/public/views/management/management.js b/x-pack/legacy/plugins/security/public/views/management/management.js index db2175e91c5de..59da63abbb83f 100644 --- a/x-pack/legacy/plugins/security/public/views/management/management.js +++ b/x-pack/legacy/plugins/security/public/views/management/management.js @@ -13,10 +13,10 @@ import 'plugins/security/views/management/edit_user/edit_user'; import 'plugins/security/views/management/edit_role/index'; import routes from 'ui/routes'; import { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; -import '../../services/shield_user'; import { ROLES_PATH, USERS_PATH, API_KEYS_PATH } from './management_urls'; import { management } from 'ui/management'; +import { npSetup } from 'ui/new_platform'; import { i18n } from '@kbn/i18n'; import { toastNotifications } from 'ui/notify'; @@ -36,7 +36,7 @@ routes }) .defaults(/\/management/, { resolve: { - securityManagementSection: function(ShieldUser) { + securityManagementSection: function() { const showSecurityLinks = xpackInfo.get('features.security.showLinks'); function deregisterSecurity() { @@ -93,12 +93,11 @@ routes if (!showSecurityLinks) { deregisterSecurity(); } else { - // getCurrent will reject if there is no authenticated user, so we prevent them from seeing the security - // management screens - // - // $promise is used here because the result is an ngResource, not a promise itself - return ShieldUser.getCurrent() - .$promise.then(ensureSecurityRegistered) + // getCurrentUser will reject if there is no authenticated user, so we prevent them from + // seeing the security management screens. + return npSetup.plugins.security.authc + .getCurrentUser() + .then(ensureSecurityRegistered) .catch(deregisterSecurity); } }, diff --git a/x-pack/legacy/plugins/security/public/views/management/users_grid/users.js b/x-pack/legacy/plugins/security/public/views/management/users_grid/users.js index a7115f449ebfd..8d4e0526251d7 100644 --- a/x-pack/legacy/plugins/security/public/views/management/users_grid/users.js +++ b/x-pack/legacy/plugins/security/public/views/management/users_grid/users.js @@ -8,7 +8,6 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import routes from 'ui/routes'; import template from 'plugins/security/views/management/users_grid/users.html'; -import 'plugins/security/services/shield_user'; import { SECURITY_PATH, USERS_PATH } from '../management_urls'; import { UsersListPage } from './components'; import { UserAPIClient } from '../../../lib/api'; diff --git a/x-pack/legacy/plugins/security/public/views/overwritten_session/overwritten_session.tsx b/x-pack/legacy/plugins/security/public/views/overwritten_session/overwritten_session.tsx index 76088443212b2..fb39c517e1c2c 100644 --- a/x-pack/legacy/plugins/security/public/views/overwritten_session/overwritten_session.tsx +++ b/x-pack/legacy/plugins/security/public/views/overwritten_session/overwritten_session.tsx @@ -10,36 +10,40 @@ import React from 'react'; import { render } from 'react-dom'; import chrome from 'ui/chrome'; import { I18nContext } from 'ui/i18n'; +import { npSetup } from 'ui/new_platform'; +import { SecurityPluginSetup } from '../../../../../../plugins/security/public'; import { AuthenticatedUser } from '../../../common/model'; import { AuthenticationStatePage } from '../../components/authentication_state_page'; chrome .setVisible(false) .setRootTemplate('
') - .setRootController('overwritten_session', ($scope: any, ShieldUser: any) => { + .setRootController('overwritten_session', ($scope: any) => { $scope.$$postDigest(() => { - ShieldUser.getCurrent().$promise.then((user: AuthenticatedUser) => { - const overwrittenSessionPage = ( - - - } - > - - - - - - ); - render(overwrittenSessionPage, document.getElementById('reactOverwrittenSessionRoot')); - }); + ((npSetup.plugins as unknown) as { security: SecurityPluginSetup }).security.authc + .getCurrentUser() + .then((user: AuthenticatedUser) => { + const overwrittenSessionPage = ( + + + } + > + + + + + + ); + render(overwrittenSessionPage, document.getElementById('reactOverwrittenSessionRoot')); + }); }); }); diff --git a/x-pack/plugins/security/public/authentication/authentication_service.ts b/x-pack/plugins/security/public/authentication/authentication_service.ts new file mode 100644 index 0000000000000..23c45c88e563a --- /dev/null +++ b/x-pack/plugins/security/public/authentication/authentication_service.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { HttpSetup } from 'src/core/public'; +import { AuthenticatedUser } from '../../common/model'; + +interface SetupParams { + http: HttpSetup; +} + +export interface AuthenticationServiceSetup { + /** + * Returns currently authenticated user and throws if current user isn't authenticated. + */ + getCurrentUser: () => Promise; +} + +export class AuthenticationService { + public setup({ http }: SetupParams): AuthenticationServiceSetup { + return { + async getCurrentUser() { + return (await http.get('/internal/security/me', { + headers: { 'kbn-system-api': true }, + })) as AuthenticatedUser; + }, + }; + } +} diff --git a/x-pack/plugins/security/public/authentication/index.mock.ts b/x-pack/plugins/security/public/authentication/index.mock.ts new file mode 100644 index 0000000000000..c8d77a5b62c6f --- /dev/null +++ b/x-pack/plugins/security/public/authentication/index.mock.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AuthenticationServiceSetup } from './authentication_service'; + +export const authenticationMock = { + createSetup: (): jest.Mocked => ({ + getCurrentUser: jest.fn(), + }), +}; diff --git a/x-pack/plugins/security/public/authentication/index.ts b/x-pack/plugins/security/public/authentication/index.ts new file mode 100644 index 0000000000000..a55f4d7bb95b3 --- /dev/null +++ b/x-pack/plugins/security/public/authentication/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 { AuthenticationService, AuthenticationServiceSetup } from './authentication_service'; diff --git a/x-pack/plugins/security/public/index.ts b/x-pack/plugins/security/public/index.ts index dc34fcbbe7d1e..336ec37d76a1b 100644 --- a/x-pack/plugins/security/public/index.ts +++ b/x-pack/plugins/security/public/index.ts @@ -6,7 +6,10 @@ import { PluginInitializer } from 'src/core/public'; import { SecurityPlugin, SecurityPluginSetup, SecurityPluginStart } from './plugin'; + +export { SecurityPluginSetup, SecurityPluginStart }; export { SessionInfo } from './types'; +export { AuthenticatedUser } from '../common/model'; export const plugin: PluginInitializer = () => new SecurityPlugin(); diff --git a/x-pack/plugins/security/public/mocks.ts b/x-pack/plugins/security/public/mocks.ts new file mode 100644 index 0000000000000..3c0c59d10abd1 --- /dev/null +++ b/x-pack/plugins/security/public/mocks.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { authenticationMock } from './authentication/index.mock'; +import { createSessionTimeoutMock } from './session/session_timeout.mock'; + +function createSetupMock() { + return { + authc: authenticationMock.createSetup(), + sessionTimeout: createSessionTimeoutMock(), + }; +} + +export const securityMock = { + createSetup: createSetupMock, +}; diff --git a/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts b/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts index 3879d611d46eb..a9a89ee05f561 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts +++ b/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts @@ -10,6 +10,8 @@ import { ILicense } from '../../../licensing/public'; import { SecurityNavControlService } from '.'; import { SecurityLicenseService } from '../../common/licensing'; import { nextTick } from 'test_utils/enzyme_helpers'; +import { securityMock } from '../mocks'; +import { mockAuthenticatedUser } from '../../common/model/authenticated_user.mock'; const validLicense = { isAvailable: true, @@ -29,13 +31,17 @@ describe('SecurityNavControlService', () => { const license$ = new BehaviorSubject(validLicense); const navControlService = new SecurityNavControlService(); + const mockSecuritySetup = securityMock.createSetup(); + mockSecuritySetup.authc.getCurrentUser.mockResolvedValue( + mockAuthenticatedUser({ username: 'some-user', full_name: undefined }) + ); navControlService.setup({ securityLicense: new SecurityLicenseService().setup({ license$ }).license, + authc: mockSecuritySetup.authc, }); const coreStart = coreMock.createStart(); coreStart.chrome.navControls.registerRight = jest.fn(); - coreStart.http.get.mockResolvedValue({ username: 'some-user' }); navControlService.start({ core: coreStart }); expect(coreStart.chrome.navControls.registerRight).toHaveBeenCalledTimes(1); @@ -93,6 +99,7 @@ describe('SecurityNavControlService', () => { const navControlService = new SecurityNavControlService(); navControlService.setup({ securityLicense: new SecurityLicenseService().setup({ license$ }).license, + authc: securityMock.createSetup().authc, }); const coreStart = coreMock.createStart(); @@ -111,6 +118,7 @@ describe('SecurityNavControlService', () => { const navControlService = new SecurityNavControlService(); navControlService.setup({ securityLicense: new SecurityLicenseService().setup({ license$ }).license, + authc: securityMock.createSetup().authc, }); const coreStart = coreMock.createStart(); @@ -126,6 +134,7 @@ describe('SecurityNavControlService', () => { const navControlService = new SecurityNavControlService(); navControlService.setup({ securityLicense: new SecurityLicenseService().setup({ license$ }).license, + authc: securityMock.createSetup().authc, }); const coreStart = coreMock.createStart(); @@ -146,6 +155,7 @@ describe('SecurityNavControlService', () => { const navControlService = new SecurityNavControlService(); navControlService.setup({ securityLicense: new SecurityLicenseService().setup({ license$ }).license, + authc: securityMock.createSetup().authc, }); const coreStart = coreMock.createStart(); diff --git a/x-pack/plugins/security/public/nav_control/nav_control_service.tsx b/x-pack/plugins/security/public/nav_control/nav_control_service.tsx index aeeb84219c937..153e7112dc95b 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_service.tsx +++ b/x-pack/plugins/security/public/nav_control/nav_control_service.tsx @@ -9,11 +9,12 @@ import { CoreStart } from 'src/core/public'; import ReactDOM from 'react-dom'; import React from 'react'; import { SecurityLicense } from '../../common/licensing'; -import { AuthenticatedUser } from '../../common/model'; import { SecurityNavControl } from './nav_control_component'; +import { AuthenticationServiceSetup } from '../authentication'; interface SetupDeps { securityLicense: SecurityLicense; + authc: AuthenticationServiceSetup; } interface StartDeps { @@ -22,13 +23,15 @@ interface StartDeps { export class SecurityNavControlService { private securityLicense!: SecurityLicense; + private authc!: AuthenticationServiceSetup; private navControlRegistered!: boolean; private securityFeaturesSubscription?: Subscription; - public setup({ securityLicense }: SetupDeps) { + public setup({ securityLicense, authc }: SetupDeps) { this.securityLicense = securityLicense; + this.authc = authc; } public start({ core }: StartDeps) { @@ -38,14 +41,8 @@ export class SecurityNavControlService { const shouldRegisterNavControl = !isAnonymousPath && showLinks && !this.navControlRegistered; - if (shouldRegisterNavControl) { - const user = core.http.get('/internal/security/me', { - headers: { - 'kbn-system-api': true, - }, - }) as Promise; - this.registerSecurityNavControl(core, user); + this.registerSecurityNavControl(core); } } ); @@ -60,16 +57,16 @@ export class SecurityNavControlService { } private registerSecurityNavControl( - core: Pick, - user: Promise + core: Pick ) { + const currentUserPromise = this.authc.getCurrentUser(); core.chrome.navControls.registerRight({ order: 2000, mount: (el: HTMLElement) => { const I18nContext = core.i18n.Context; const props = { - user, + user: currentUserPromise, editProfileUrl: core.http.basePath.prepend('/app/kibana#/account'), logoutUrl: core.http.basePath.prepend(`/logout`), }; diff --git a/x-pack/plugins/security/public/plugin.ts b/x-pack/plugins/security/public/plugin.ts index 0f10f9d89f25a..50e0b838c750f 100644 --- a/x-pack/plugins/security/public/plugin.ts +++ b/x-pack/plugins/security/public/plugin.ts @@ -9,18 +9,20 @@ import { LicensingPluginSetup } from '../../licensing/public'; import { SessionExpired, SessionTimeout, + ISessionTimeout, SessionTimeoutHttpInterceptor, UnauthorizedResponseHttpInterceptor, } from './session'; import { SecurityLicenseService } from '../common/licensing'; import { SecurityNavControlService } from './nav_control'; +import { AuthenticationService } from './authentication'; export interface PluginSetupDependencies { licensing: LicensingPluginSetup; } export class SecurityPlugin implements Plugin { - private sessionTimeout!: SessionTimeout; + private sessionTimeout!: ISessionTimeout; private navControlService!: SecurityNavControlService; @@ -43,12 +45,15 @@ export class SecurityPlugin implements Plugin; private sessionInfo?: SessionInfo; private fetchTimer?: number; diff --git a/x-pack/plugins/security/server/config.ts b/x-pack/plugins/security/server/config.ts index b3f96497b0538..4f1c25702ae97 100644 --- a/x-pack/plugins/security/server/config.ts +++ b/x-pack/plugins/security/server/config.ts @@ -8,7 +8,6 @@ import crypto from 'crypto'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { schema, Type, TypeOf } from '@kbn/config-schema'; -import { duration } from 'moment'; import { PluginInitializerContext } from '../../../../src/core/server'; export type ConfigType = ReturnType extends Observable @@ -35,7 +34,6 @@ export const ConfigSchema = schema.object( schema.maybe(schema.string({ minLength: 32 })), schema.string({ minLength: 32, defaultValue: 'a'.repeat(32) }) ), - sessionTimeout: schema.maybe(schema.nullable(schema.number())), // DEPRECATED session: schema.object({ idleTimeout: schema.nullable(schema.duration()), lifespan: schema.nullable(schema.duration()), @@ -88,22 +86,11 @@ export function createConfig$(context: PluginInitializerContext, isTLSEnabled: b secureCookies = true; } - // "sessionTimeout" is deprecated and replaced with "session.idleTimeout" - // however, NP does not yet have a mechanism to automatically rename deprecated keys - // for the time being, we'll do it manually: - const deprecatedSessionTimeout = - typeof config.sessionTimeout === 'number' ? duration(config.sessionTimeout) : null; - const val = { + return { ...config, encryptionKey, secureCookies, - session: { - ...config.session, - idleTimeout: config.session.idleTimeout || deprecatedSessionTimeout, - }, }; - delete val.sessionTimeout; // DEPRECATED - return val; }) ); } diff --git a/x-pack/plugins/security/server/index.ts b/x-pack/plugins/security/server/index.ts index e189b71345ffc..33f554be5caa3 100644 --- a/x-pack/plugins/security/server/index.ts +++ b/x-pack/plugins/security/server/index.ts @@ -4,9 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PluginInitializerContext } from '../../../../src/core/server'; +import { TypeOf } from '@kbn/config-schema'; +import { + PluginConfigDescriptor, + PluginInitializer, + PluginInitializerContext, + RecursiveReadonly, +} from '../../../../src/core/server'; import { ConfigSchema } from './config'; -import { Plugin } from './plugin'; +import { Plugin, PluginSetupContract, PluginSetupDependencies } from './plugin'; // These exports are part of public Security plugin contract, any change in signature of exported // functions or removal of exports should be considered as a breaking change. @@ -17,8 +23,17 @@ export { InvalidateAPIKeyParams, InvalidateAPIKeyResult, } from './authentication'; -export { PluginSetupContract } from './plugin'; +export { PluginSetupContract }; -export const config = { schema: ConfigSchema }; -export const plugin = (initializerContext: PluginInitializerContext) => - new Plugin(initializerContext); +export const config: PluginConfigDescriptor> = { + schema: ConfigSchema, + deprecations: ({ rename, unused }) => [ + rename('sessionTimeout', 'session.idleTimeout'), + unused('authorization.legacyFallback.enabled'), + ], +}; +export const plugin: PluginInitializer< + RecursiveReadonly, + void, + PluginSetupDependencies +> = (initializerContext: PluginInitializerContext) => new Plugin(initializerContext); diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts index cdd2a024310bb..9c4b01f94ef4d 100644 --- a/x-pack/plugins/security/server/plugin.ts +++ b/x-pack/plugins/security/server/plugin.ts @@ -110,10 +110,7 @@ export class Plugin { this.logger = this.initializerContext.logger.get(); } - public async setup( - core: CoreSetup, - { features, licensing }: PluginSetupDependencies - ): Promise> { + public async setup(core: CoreSetup, { features, licensing }: PluginSetupDependencies) { const [config, legacyConfig] = await combineLatest([ createConfig$(this.initializerContext, core.http.isTlsEnabled), this.initializerContext.config.legacy.globalConfig$, @@ -169,7 +166,7 @@ export class Plugin { csp: core.http.csp, }); - return deepFreeze({ + return deepFreeze({ authc, authz: { From 5b2a188c4362211798df998e990e2047575ee309 Mon Sep 17 00:00:00 2001 From: Maja Grubic Date: Mon, 6 Jan 2020 10:55:15 +0000 Subject: [PATCH 10/17] [Dashboard] Empty screen redesign (#53681) * Edit screen redesign * Edit screen redesign * Redesign view screen * Redesign view screen * Fixing type failure, and functional test * Updating failing functional tests * update dashboard empty styles * i18n fix * Updating failing snapshot Co-authored-by: Ryan Keairns Co-authored-by: Elastic Machine --- .../dashboard_empty_screen.test.tsx.snap | 512 ++++++++++-------- .../__tests__/dashboard_empty_screen.test.tsx | 5 + .../public/dashboard/_dashboard_app.scss | 28 +- .../np_ready/dashboard_app_controller.tsx | 4 +- .../np_ready/dashboard_empty_screen.tsx | 80 +-- .../dashboard_empty_screen_constants.tsx | 60 +- .../home/assets/welcome_graphic_dark_2x.png | Bin 0 -> 53603 bytes .../home/assets/welcome_graphic_light_2x.png | Bin 0 -> 53122 bytes .../apps/dashboard/empty_dashboard.js | 8 +- .../translations/translations/ja-JP.json | 3 - .../translations/translations/zh-CN.json | 3 - .../feature_controls/dashboard_security.ts | 2 +- .../feature_controls/dashboard_spaces.ts | 2 +- 13 files changed, 402 insertions(+), 305 deletions(-) create mode 100644 src/legacy/core_plugins/kibana/public/home/assets/welcome_graphic_dark_2x.png create mode 100644 src/legacy/core_plugins/kibana/public/home/assets/welcome_graphic_light_2x.png diff --git a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/__snapshots__/dashboard_empty_screen.test.tsx.snap b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/__snapshots__/dashboard_empty_screen.test.tsx.snap index d48e34b2e4837..f611ec978b6b3 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/__snapshots__/dashboard_empty_screen.test.tsx.snap +++ b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/__snapshots__/dashboard_empty_screen.test.tsx.snap @@ -2,6 +2,31 @@ exports[`DashboardEmptyScreen renders correctly with visualize paragraph 1`] = ` - -
- -
+ - - -
+ - - - - - - -
- - -
-

- This dashboard is empty. Let’s fill it up! -

-
-
- -
- - -
-

- Click the - - - - button in the menu bar above to add a visualization to the dashboard. -

-
-
- -
- -

- - - -

-
- - -
-
-
-
+ Add an existing + + + +   + + or new object to this dashboard +

+
+ +
+ + +
+ +

+ + + +

+
@@ -373,6 +361,31 @@ exports[`DashboardEmptyScreen renders correctly with visualize paragraph 1`] = ` exports[`DashboardEmptyScreen renders correctly without visualize paragraph 1`] = `
@@ -581,59 +630,48 @@ exports[`DashboardEmptyScreen renders correctly without visualize paragraph 1`] className="euiPageBody" >
- - - - - - -
- + +
-

This dashboard is empty. Let’s fill it up! -

+

- -
-

- Click the - + - - - button in the menu bar above to start working on your new dashboard. -

-
-
+

+ Click + +   + + + + + +   + + in the menu bar above to start adding panels. +

+
+ +
+ +
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/dashboard_empty_screen.test.tsx b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/dashboard_empty_screen.test.tsx index 1c450879ee553..381ced2efd8e3 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/dashboard_empty_screen.test.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/dashboard_empty_screen.test.tsx @@ -24,11 +24,16 @@ import { } from '../np_ready/dashboard_empty_screen'; // @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; +import { coreMock } from '../../../../../../core/public/mocks'; describe('DashboardEmptyScreen', () => { + const setupMock = coreMock.createSetup(); + const defaultProps = { showLinkToVisualize: true, onLinkClick: jest.fn(), + uiSettings: setupMock.uiSettings, + http: setupMock.http, }; function mountComponent(props?: DashboardEmptyScreenProps) { diff --git a/src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss b/src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss index d9eadf6c0e37d..03a8a07d6b17d 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss +++ b/src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss @@ -6,5 +6,31 @@ .dshStartScreen { text-align: center; - padding: $euiSizeS; +} + +.dshStartScreen__pageContent { + padding: $euiSizeXXL; +} + +.dshStartScreen__panelDesc { + max-width: 260px; + margin: 0 auto; +} + +.dshEmptyWidget { + border: $euiBorderThin; + border-style: dashed; + border-radius: $euiBorderRadius; + padding: $euiSizeXXL * 2; + max-width: 400px; + margin-left: $euiSizeS; + text-align: center; +} + +.dshEmptyWidget { + border: 2px dashed $euiColorLightShade; + padding: 4 * $euiSize; + max-width: 20em; + margin-left: 10px; + text-align: center; } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx index 08637174c8cec..8fcc7e4c26321 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx @@ -123,7 +123,7 @@ export class DashboardAppController { timefilter: { timefilter }, }, }, - core: { notifications, overlays, chrome, injectedMetadata, uiSettings, savedObjects }, + core: { notifications, overlays, chrome, injectedMetadata, uiSettings, savedObjects, http }, }: DashboardAppControllerDependencies) { new FilterStateManager(globalState, getAppState, filterManager); const queryFilter = filterManager; @@ -197,6 +197,8 @@ export class DashboardAppController { const emptyScreenProps: DashboardEmptyScreenProps = { onLinkClick: shouldShowEditHelp ? $scope.showAddPanel : $scope.enterEditMode, showLinkToVisualize: shouldShowEditHelp, + uiSettings, + http, }; if (shouldShowEditHelp) { emptyScreenProps.onVisualizeClick = addVisualization; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_empty_screen.tsx b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_empty_screen.tsx index 2fc78d64d0a0c..ae5319c560ab9 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_empty_screen.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_empty_screen.tsx @@ -19,94 +19,110 @@ import React from 'react'; import { I18nProvider } from '@kbn/i18n/react'; import { - EuiIcon, EuiLink, EuiSpacer, EuiPageContent, EuiPageBody, EuiPage, + EuiImage, EuiText, EuiButton, } from '@elastic/eui'; +import { IUiSettingsClient, HttpStart } from 'kibana/public'; import * as constants from './dashboard_empty_screen_constants'; export interface DashboardEmptyScreenProps { showLinkToVisualize: boolean; onLinkClick: () => void; onVisualizeClick?: () => void; + uiSettings: IUiSettingsClient; + http: HttpStart; } export function DashboardEmptyScreen({ showLinkToVisualize, onLinkClick, onVisualizeClick, + uiSettings, + http, }: DashboardEmptyScreenProps) { + const IS_DARK_THEME = uiSettings.get('theme:darkMode'); + const emptyStateGraphicURL = IS_DARK_THEME + ? '/plugins/kibana/home/assets/welcome_graphic_dark_2x.png' + : '/plugins/kibana/home/assets/welcome_graphic_light_2x.png'; const linkToVisualizeParagraph = (

{constants.createNewVisualizationButton}

); const paragraph = ( - description1: string, + description1: string | null, description2: string, linkText: string, ariaLabel: string, dataTestSubj?: string ) => { return ( - +

{description1} + {description1 &&  } {linkText} +   {description2}

); }; - const addVisualizationParagraph = ( - - {paragraph( - constants.addVisualizationDescription1, - constants.addVisualizationDescription2, - constants.addVisualizationLinkText, - constants.addVisualizationLinkAriaLabel, - 'emptyDashboardAddPanelButton' - )} - - {linkToVisualizeParagraph} - - ); const enterEditModeParagraph = paragraph( constants.howToStartWorkingOnNewDashboardDescription1, constants.howToStartWorkingOnNewDashboardDescription2, constants.howToStartWorkingOnNewDashboardEditLinkText, constants.howToStartWorkingOnNewDashboardEditLinkAriaLabel ); - return ( - - - - - - - -

{constants.fillDashboardTitle}

-
- - {showLinkToVisualize ? addVisualizationParagraph : enterEditModeParagraph} -
-
-
-
+ const enterViewModeParagraph = paragraph( + null, + constants.addNewVisualizationDescription, + constants.addExistingVisualizationLinkText, + constants.addExistingVisualizationLinkAriaLabel + ); + const viewMode = ( + + + + + +

{constants.fillDashboardTitle}

+
+ +
{enterEditModeParagraph}
+
+
+
+ ); + const editMode = ( +
+ {enterViewModeParagraph} + + {linkToVisualizeParagraph} +
); + return {showLinkToVisualize ? editMode : viewMode}; } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_empty_screen_constants.tsx b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_empty_screen_constants.tsx index 03004f6270fef..513e6cb685a7a 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_empty_screen_constants.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_empty_screen_constants.tsx @@ -19,40 +19,20 @@ import { i18n } from '@kbn/i18n'; -export const addVisualizationDescription1: string = i18n.translate( - 'kbn.dashboard.addVisualizationDescription1', - { - defaultMessage: 'Click the ', - } -); -export const addVisualizationDescription2: string = i18n.translate( - 'kbn.dashboard.addVisualizationDescription2', - { - defaultMessage: ' button in the menu bar above to add a visualization to the dashboard.', - } -); -export const addVisualizationLinkText: string = i18n.translate( - 'kbn.dashboard.addVisualizationLinkText', - { - defaultMessage: 'Add', - } -); -export const addVisualizationLinkAriaLabel: string = i18n.translate( - 'kbn.dashboard.addVisualizationLinkAriaLabel', - { - defaultMessage: 'Add visualization', - } -); +/** VIEW MODE CONSTANTS **/ +export const fillDashboardTitle: string = i18n.translate('kbn.dashboard.fillDashboardTitle', { + defaultMessage: 'This dashboard is empty. Let\u2019s fill it up!', +}); export const howToStartWorkingOnNewDashboardDescription1: string = i18n.translate( 'kbn.dashboard.howToStartWorkingOnNewDashboardDescription1', { - defaultMessage: 'Click the ', + defaultMessage: 'Click', } ); export const howToStartWorkingOnNewDashboardDescription2: string = i18n.translate( 'kbn.dashboard.howToStartWorkingOnNewDashboardDescription2', { - defaultMessage: ' button in the menu bar above to start working on your new dashboard.', + defaultMessage: 'in the menu bar above to start adding panels.', } ); export const howToStartWorkingOnNewDashboardEditLinkText: string = i18n.translate( @@ -67,13 +47,23 @@ export const howToStartWorkingOnNewDashboardEditLinkAriaLabel: string = i18n.tra defaultMessage: 'Edit dashboard', } ); -export const fillDashboardTitle: string = i18n.translate('kbn.dashboard.fillDashboardTitle', { - defaultMessage: 'This dashboard is empty. Let\u2019s fill it up!', -}); -export const visualizeAppLinkTest: string = i18n.translate( - 'kbn.dashboard.visitVisualizeAppLinkText', +/** EDIT MODE CONSTANTS **/ +export const addExistingVisualizationLinkText: string = i18n.translate( + 'kbn.dashboard.addExistingVisualizationLinkText', + { + defaultMessage: 'Add an existing', + } +); +export const addExistingVisualizationLinkAriaLabel: string = i18n.translate( + 'kbn.dashboard.addVisualizationLinkAriaLabel', + { + defaultMessage: 'Add an existing visualization', + } +); +export const addNewVisualizationDescription: string = i18n.translate( + 'kbn.dashboard.addNewVisualizationText', { - defaultMessage: 'visit the Visualize app', + defaultMessage: 'or new object to this dashboard', } ); export const createNewVisualizationButton: string = i18n.translate( @@ -82,3 +72,9 @@ export const createNewVisualizationButton: string = i18n.translate( defaultMessage: 'Create new', } ); +export const createNewVisualizationButtonAriaLabel: string = i18n.translate( + 'kbn.dashboard.createNewVisualizationButtonAriaLabel', + { + defaultMessage: 'Create new visualization button', + } +); diff --git a/src/legacy/core_plugins/kibana/public/home/assets/welcome_graphic_dark_2x.png b/src/legacy/core_plugins/kibana/public/home/assets/welcome_graphic_dark_2x.png new file mode 100644 index 0000000000000000000000000000000000000000..8f551c54bd5527bcd9f3c6848ec550b4324f474b GIT binary patch literal 53603 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 IBbT_p0jSuq(EtDd literal 0 HcmV?d00001 diff --git a/src/legacy/core_plugins/kibana/public/home/assets/welcome_graphic_light_2x.png b/src/legacy/core_plugins/kibana/public/home/assets/welcome_graphic_light_2x.png new file mode 100644 index 0000000000000000000000000000000000000000..034e8b87ff0f5bdafccb49bbd68d988b342655b6 GIT binary patch literal 53122 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 a#-9q>E7DL(mf6?_JP6m5uySX= { - const addButtonExists = await testSubjects.exists('emptyDashboardAddPanelButton'); - expect(addButtonExists).to.be(true); + it('should display empty widget', async () => { + const emptyWidgetExists = await testSubjects.exists('emptyDashboardWidget'); + expect(emptyWidgetExists).to.be(true); }); it.skip('should open add panel when add button is clicked', async () => { - await testSubjects.click('emptyDashboardAddPanelButton'); + await testSubjects.click('dashboardAddPanelButton'); const isAddPanelOpen = await dashboardAddPanel.isAddPanelOpen(); expect(isAddPanelOpen).to.be(true); }); diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index d957e451fdb74..83ef497e50649 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -1773,10 +1773,7 @@ "kbn.context.reloadPageDescription.selectValidAnchorDocumentTextMessage": "にアクセスして有効な別のドキュメントを選択してください。", "kbn.context.unableToLoadAnchorDocumentDescription": "別のドキュメントが読み込めません", "kbn.context.unableToLoadDocumentDescription": "ドキュメントが読み込めません", - "kbn.dashboard.addVisualizationDescription1": "上のメニューバーの ", - "kbn.dashboard.addVisualizationDescription2": " ボタンをクリックして、ダッシュボードにビジュアライゼーションを追加します。", "kbn.dashboard.addVisualizationLinkAriaLabel": "ビジュアライゼーションを追加", - "kbn.dashboard.addVisualizationLinkText": "追加", "kbn.dashboard.badge.readOnly.text": "読み込み専用", "kbn.dashboard.badge.readOnly.tooltip": "ダッシュボードを保存できません", "kbn.dashboard.changeViewModeConfirmModal.cancelButtonLabel": "編集を続行", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 2e47c7a615e36..87c11adcb5e77 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -1774,10 +1774,7 @@ "kbn.context.reloadPageDescription.selectValidAnchorDocumentTextMessage": "以选择有效地定位点文档。", "kbn.context.unableToLoadAnchorDocumentDescription": "无法加载该定位点文档", "kbn.context.unableToLoadDocumentDescription": "无法加载文档", - "kbn.dashboard.addVisualizationDescription1": "单击上述菜单栏中的 ", - "kbn.dashboard.addVisualizationDescription2": " 按钮,以将可视化添加到仪表板。", "kbn.dashboard.addVisualizationLinkAriaLabel": "添加可视化", - "kbn.dashboard.addVisualizationLinkText": "添加", "kbn.dashboard.badge.readOnly.text": "只读", "kbn.dashboard.badge.readOnly.tooltip": "无法保存仪表板", "kbn.dashboard.changeViewModeConfirmModal.cancelButtonLabel": "继续编辑", diff --git a/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_security.ts b/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_security.ts index c7a9764c6fb58..aa6860b35763f 100644 --- a/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_security.ts +++ b/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_security.ts @@ -107,7 +107,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { shouldLoginIfPrompted: false, } ); - await testSubjects.existOrFail('emptyDashboardAddPanelButton', { timeout: 10000 }); + await testSubjects.existOrFail('emptyDashboardWidget', { timeout: 10000 }); }); it(`can view existing Dashboard`, async () => { diff --git a/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_spaces.ts b/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_spaces.ts index 127141b156cd8..c1197fa7023c5 100644 --- a/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_spaces.ts +++ b/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_spaces.ts @@ -73,7 +73,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { shouldLoginIfPrompted: false, } ); - await testSubjects.existOrFail('emptyDashboardAddPanelButton', { timeout: 10000 }); + await testSubjects.existOrFail('emptyDashboardWidget', { timeout: 10000 }); }); it(`can view existing Dashboard`, async () => { From 8992a43c6e4a8d3f1a55b20c520926e817c14a2e Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Mon, 6 Jan 2020 11:11:18 +0000 Subject: [PATCH 11/17] adds strict types to Alerting Client (#53821) The AlertsClient API currently returns mixed inferred types instead of a clear strict type, making it harder to work with the client's type signatures. The root causes for this difficulty is that we have to support the SavedObjects API which allows partial updates of types, and the implementation of code that converts the SavedObject from a RawAlert to an Alert in a non type-strict manner. To address this we've added concrete types on the AlertsClient APIs, using Partial on update due to the SavedObjects API, and a strict Alert on the other APIs. --- .../plugins/alerting/server/alerts_client.ts | 88 +++++++++++-------- .../alerting/server/routes/create.test.ts | 18 +++- .../alerting/server/routes/get.test.ts | 11 +++ .../legacy/plugins/alerting/server/types.ts | 3 + .../routes/__mocks__/request_responses.ts | 2 - .../lib/detection_engine/rules/find_rules.ts | 9 +- .../lib/detection_engine/rules/types.ts | 5 +- 7 files changed, 90 insertions(+), 46 deletions(-) diff --git a/x-pack/legacy/plugins/alerting/server/alerts_client.ts b/x-pack/legacy/plugins/alerting/server/alerts_client.ts index fc0252c86fe50..33a6b716e9b8a 100644 --- a/x-pack/legacy/plugins/alerting/server/alerts_client.ts +++ b/x-pack/legacy/plugins/alerting/server/alerts_client.ts @@ -15,6 +15,7 @@ import { } from 'src/core/server'; import { Alert, + PartialAlert, RawAlert, AlertTypeRegistry, AlertAction, @@ -69,28 +70,26 @@ export interface FindOptions { }; } -interface FindResult { +export interface FindResult { page: number; perPage: number; total: number; - data: object[]; + data: Alert[]; } interface CreateOptions { - data: Pick< + data: Omit< Alert, - Exclude< - keyof Alert, - | 'createdBy' - | 'updatedBy' - | 'createdAt' - | 'updatedAt' - | 'apiKey' - | 'apiKeyOwner' - | 'muteAll' - | 'mutedInstanceIds' - | 'actions' - > + | 'id' + | 'createdBy' + | 'updatedBy' + | 'createdAt' + | 'updatedAt' + | 'apiKey' + | 'apiKeyOwner' + | 'muteAll' + | 'mutedInstanceIds' + | 'actions' > & { actions: NormalizedAlertAction[] }; options?: { migrationVersion?: Record; @@ -146,7 +145,7 @@ export class AlertsClient { this.encryptedSavedObjectsPlugin = encryptedSavedObjectsPlugin; } - public async create({ data, options }: CreateOptions) { + public async create({ data, options }: CreateOptions): Promise { // Throws an error if alert type isn't registered const alertType = this.alertTypeRegistry.get(data.alertTypeId); const validatedAlertTypeParams = validateAlertTypeParams(alertType, data.params); @@ -199,26 +198,29 @@ export class AlertsClient { ); } - public async get({ id }: { id: string }) { + public async get({ id }: { id: string }): Promise { const result = await this.savedObjectsClient.get('alert', id); return this.getAlertFromRaw(result.id, result.attributes, result.updated_at, result.references); } public async find({ options = {} }: FindOptions = {}): Promise { - const results = await this.savedObjectsClient.find({ + const { + page, + per_page: perPage, + total, + saved_objects: data, + } = await this.savedObjectsClient.find({ ...options, type: 'alert', }); - const data = results.saved_objects.map(result => - this.getAlertFromRaw(result.id, result.attributes, result.updated_at, result.references) - ); - return { - page: results.page, - perPage: results.per_page, - total: results.total, - data, + page, + perPage, + total, + data: data.map(({ id, attributes, updated_at, references }) => + this.getAlertFromRaw(id, attributes, updated_at, references) + ), }; } @@ -234,7 +236,7 @@ export class AlertsClient { return removeResult; } - public async update({ id, data }: UpdateOptions) { + public async update({ id, data }: UpdateOptions): Promise { const decryptedAlertSavedObject = await this.encryptedSavedObjectsPlugin.getDecryptedAsInternalUser< RawAlert >('alert', id, { namespace: this.namespace }); @@ -257,7 +259,7 @@ export class AlertsClient { private async updateAlert( { id, data }: UpdateOptions, { attributes, version }: SavedObject - ) { + ): Promise { const alertType = this.alertTypeRegistry.get(attributes.alertTypeId); // Validate @@ -287,7 +289,7 @@ export class AlertsClient { await this.invalidateApiKey({ apiKey: attributes.apiKey }); - return this.getAlertFromRaw( + return this.getPartialAlertFromRaw( id, updatedObject.attributes, updatedObject.updated_at, @@ -494,24 +496,34 @@ export class AlertsClient { } private getAlertFromRaw( + id: string, + rawAlert: RawAlert, + updatedAt: SavedObject['updated_at'], + references: SavedObjectReference[] | undefined + ): Alert { + // In order to support the partial update API of Saved Objects we have to support + // partial updates of an Alert, but when we receive an actual RawAlert, it is safe + // to cast the result to an Alert + return this.getPartialAlertFromRaw(id, rawAlert, updatedAt, references) as Alert; + } + + private getPartialAlertFromRaw( id: string, rawAlert: Partial, updatedAt: SavedObject['updated_at'], references: SavedObjectReference[] | undefined - ) { - if (!rawAlert.actions) { - return { - id, - ...rawAlert, - }; - } - const actions = this.injectReferencesIntoActions(rawAlert.actions, references || []); + ): PartialAlert { return { id, ...rawAlert, + // we currently only support the Interval Schedule type + // Once we support additional types, this type signature will likely change + schedule: rawAlert.schedule as IntervalSchedule, updatedAt: updatedAt ? new Date(updatedAt) : new Date(rawAlert.createdAt!), createdAt: new Date(rawAlert.createdAt!), - actions, + actions: rawAlert.actions + ? this.injectReferencesIntoActions(rawAlert.actions, references || []) + : [], }; } diff --git a/x-pack/legacy/plugins/alerting/server/routes/create.test.ts b/x-pack/legacy/plugins/alerting/server/routes/create.test.ts index 03b33b0bd40b0..2a0ae78fd78b2 100644 --- a/x-pack/legacy/plugins/alerting/server/routes/create.test.ts +++ b/x-pack/legacy/plugins/alerting/server/routes/create.test.ts @@ -20,6 +20,7 @@ const mockedAlert = { params: { bar: true, }, + throttle: '30s', actions: [ { group: 'default', @@ -44,6 +45,13 @@ test('creates an alert with proper parameters', async () => { const updatedAt = new Date(); alertsClient.create.mockResolvedValueOnce({ ...mockedAlert, + enabled: true, + muteAll: false, + createdBy: '', + updatedBy: '', + apiKey: '', + apiKeyOwner: '', + mutedInstanceIds: [], createdAt, updatedAt, id: '123', @@ -71,8 +79,14 @@ test('creates an alert with proper parameters', async () => { }, ], "alertTypeId": "1", + "apiKey": "", + "apiKeyOwner": "", "consumer": "bar", + "createdBy": "", + "enabled": true, "id": "123", + "muteAll": false, + "mutedInstanceIds": Array [], "name": "abc", "params": Object { "bar": true, @@ -83,6 +97,8 @@ test('creates an alert with proper parameters', async () => { "tags": Array [ "foo", ], + "throttle": "30s", + "updatedBy": "", } `); expect(alertsClient.create).toHaveBeenCalledTimes(1); @@ -112,7 +128,7 @@ test('creates an alert with proper parameters', async () => { "tags": Array [ "foo", ], - "throttle": null, + "throttle": "30s", }, }, ] diff --git a/x-pack/legacy/plugins/alerting/server/routes/get.test.ts b/x-pack/legacy/plugins/alerting/server/routes/get.test.ts index 5b1bdc7f69708..320e9042d87c5 100644 --- a/x-pack/legacy/plugins/alerting/server/routes/get.test.ts +++ b/x-pack/legacy/plugins/alerting/server/routes/get.test.ts @@ -29,6 +29,17 @@ const mockedAlert = { }, }, ], + consumer: 'bar', + name: 'abc', + tags: ['foo'], + enabled: true, + muteAll: false, + createdBy: '', + updatedBy: '', + apiKey: '', + apiKeyOwner: '', + throttle: '30s', + mutedInstanceIds: [], }; beforeEach(() => jest.resetAllMocks()); diff --git a/x-pack/legacy/plugins/alerting/server/types.ts b/x-pack/legacy/plugins/alerting/server/types.ts index a2390bf93d005..62dcf07abb7bd 100644 --- a/x-pack/legacy/plugins/alerting/server/types.ts +++ b/x-pack/legacy/plugins/alerting/server/types.ts @@ -65,6 +65,7 @@ export interface IntervalSchedule extends SavedObjectAttributes { } export interface Alert { + id: string; enabled: boolean; name: string; tags: string[]; @@ -85,6 +86,8 @@ export interface Alert { mutedInstanceIds: string[]; } +export type PartialAlert = Pick & Partial>; + export interface RawAlert extends SavedObjectAttributes { enabled: boolean; name: string; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts index 2e16f209acfb1..edf196b96f5d0 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -283,9 +283,7 @@ export const getResult = (): RuleAlertType => ({ ], riskScore: 50, maxSignals: 100, - size: 1, severity: 'high', - tags: [], to: 'now', type: 'query', threats: [ diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/find_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/find_rules.ts index c1058bd353e8c..5f69082e3fc71 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/find_rules.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/find_rules.ts @@ -5,7 +5,7 @@ */ import { SIGNALS_ID } from '../../../../common/constants'; -import { FindRuleParams } from './types'; +import { FindRuleParams, RuleAlertType } from './types'; export const getFilter = (filter: string | null | undefined) => { if (filter == null) { @@ -33,5 +33,10 @@ export const findRules = async ({ sortOrder, sortField, }, - }); + }) as Promise<{ + page: number; + perPage: number; + total: number; + data: RuleAlertType[]; + }>; }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts index b0578174e1f65..4f4c0da7127cd 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts @@ -35,10 +35,9 @@ export interface BulkUpdateRulesRequest extends RequestFacade { payload: UpdateRuleAlertParamsRest[]; } -export type RuleAlertType = Alert & { - id: string; +export interface RuleAlertType extends Alert { params: RuleTypeParams; -}; +} export interface RulesRequest extends RequestFacade { payload: RuleAlertParamsRest; From fd4bb8e8a4ff279421aa58b5ea076fc48f55e21d Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Mon, 6 Jan 2020 15:26:51 +0300 Subject: [PATCH 12/17] use NP deprecations in uiSettings (#53755) * use NP deprecation iunstead of manual one in uiSettings * add ServiceConfigDescriptor type Co-authored-by: Elastic Machine --- .../config/deprecation/core_deprecations.ts | 1 - src/core/server/http/http_config.ts | 11 ------ src/core/server/http/http_service.mock.ts | 1 - src/core/server/http/http_service.ts | 4 --- src/core/server/http/types.ts | 10 ------ src/core/server/internal_types.ts | 18 ++++++++++ ...gacy_object_to_config_adapter.test.ts.snap | 2 -- .../config/legacy_object_to_config_adapter.ts | 1 - src/core/server/server.ts | 4 +++ .../server/ui_settings/ui_settings_config.ts | 36 ++++++++++++++----- .../ui_settings/ui_settings_service.test.ts | 22 ------------ .../server/ui_settings/ui_settings_service.ts | 18 ++-------- src/legacy/server/config/schema.js | 1 - .../http/setup_default_route_provider.ts | 2 +- 14 files changed, 54 insertions(+), 77 deletions(-) diff --git a/src/core/server/config/deprecation/core_deprecations.ts b/src/core/server/config/deprecation/core_deprecations.ts index 6a401ec6625a2..36fe95e05cb53 100644 --- a/src/core/server/config/deprecation/core_deprecations.ts +++ b/src/core/server/config/deprecation/core_deprecations.ts @@ -97,7 +97,6 @@ export const coreDeprecationProvider: ConfigDeprecationProvider = ({ }) => [ unusedFromRoot('savedObjects.indexCheckTimeout'), unusedFromRoot('server.xsrf.token'), - unusedFromRoot('uiSettings.enabled'), renameFromRoot('optimize.lazy', 'optimize.watch'), renameFromRoot('optimize.lazyPort', 'optimize.watchPort'), renameFromRoot('optimize.lazyHost', 'optimize.watchHost'), diff --git a/src/core/server/http/http_config.ts b/src/core/server/http/http_config.ts index 5749eb383f8b9..92a8d6a95b854 100644 --- a/src/core/server/http/http_config.ts +++ b/src/core/server/http/http_config.ts @@ -39,15 +39,6 @@ export const config = { validate: match(validBasePathRegex, "must start with a slash, don't end with one"), }) ), - defaultRoute: schema.maybe( - schema.string({ - validate(value) { - if (!value.startsWith('/')) { - return 'must start with a slash'; - } - }, - }) - ), cors: schema.conditional( schema.contextRef('dev'), true, @@ -134,7 +125,6 @@ export class HttpConfig { public maxPayload: ByteSizeValue; public basePath?: string; public rewriteBasePath: boolean; - public defaultRoute?: string; public ssl: SslConfig; public compression: { enabled: boolean; referrerWhitelist?: string[] }; public csp: ICspConfig; @@ -153,7 +143,6 @@ export class HttpConfig { this.socketTimeout = rawHttpConfig.socketTimeout; this.rewriteBasePath = rawHttpConfig.rewriteBasePath; this.ssl = new SslConfig(rawHttpConfig.ssl || {}); - this.defaultRoute = rawHttpConfig.defaultRoute; this.compression = rawHttpConfig.compression; this.csp = new CspConfig(rawCspConfig); } diff --git a/src/core/server/http/http_service.mock.ts b/src/core/server/http/http_service.mock.ts index 700ae04f00d47..6db1ca80ab437 100644 --- a/src/core/server/http/http_service.mock.ts +++ b/src/core/server/http/http_service.mock.ts @@ -68,7 +68,6 @@ const createSetupContractMock = () => { getAuthHeaders: jest.fn(), }, isTlsEnabled: false, - config: {}, }; setupContract.createCookieSessionStorageFactory.mockResolvedValue( sessionStorageMock.createFactory() diff --git a/src/core/server/http/http_service.ts b/src/core/server/http/http_service.ts index e038443d5c83f..09982cf164a19 100644 --- a/src/core/server/http/http_service.ts +++ b/src/core/server/http/http_service.ts @@ -107,10 +107,6 @@ export class HttpService implements CoreService ) => this.requestHandlerContext!.registerContext(pluginOpaqueId, contextName, provider), - - config: { - defaultRoute: config.defaultRoute, - }, }; return contract; diff --git a/src/core/server/http/types.ts b/src/core/server/http/types.ts index 92217515a22a1..9c8bfc073a524 100644 --- a/src/core/server/http/types.ts +++ b/src/core/server/http/types.ts @@ -250,16 +250,6 @@ export interface InternalHttpServiceSetup contextName: T, provider: RequestHandlerContextProvider ) => RequestHandlerContextContainer; - config: { - /** - * @internalRemarks - * Deprecated part of the server config, provided until - * https://github.com/elastic/kibana/issues/40255 - * - * @deprecated - * */ - defaultRoute?: string; - }; } /** @public */ diff --git a/src/core/server/internal_types.ts b/src/core/server/internal_types.ts index be4d830c55eab..ff68d1544d119 100644 --- a/src/core/server/internal_types.ts +++ b/src/core/server/internal_types.ts @@ -17,7 +17,10 @@ * under the License. */ +import { Type } from '@kbn/config-schema'; + import { CapabilitiesSetup, CapabilitiesStart } from './capabilities'; +import { ConfigDeprecationProvider } from './config'; import { ContextSetup } from './context'; import { InternalElasticsearchServiceSetup } from './elasticsearch'; import { InternalHttpServiceSetup } from './http'; @@ -47,3 +50,18 @@ export interface InternalCoreStart { savedObjects: InternalSavedObjectsServiceStart; uiSettings: InternalUiSettingsServiceStart; } + +/** + * @internal + */ +export interface ServiceConfigDescriptor { + path: string; + /** + * Schema to use to validate the configuration. + */ + schema: Type; + /** + * Provider for the {@link ConfigDeprecation} to apply to the plugin configuration. + */ + deprecations?: ConfigDeprecationProvider; +} diff --git a/src/core/server/legacy/config/__snapshots__/legacy_object_to_config_adapter.test.ts.snap b/src/core/server/legacy/config/__snapshots__/legacy_object_to_config_adapter.test.ts.snap index 0ebd8b8371628..3161dd06cf3b6 100644 --- a/src/core/server/legacy/config/__snapshots__/legacy_object_to_config_adapter.test.ts.snap +++ b/src/core/server/legacy/config/__snapshots__/legacy_object_to_config_adapter.test.ts.snap @@ -8,7 +8,6 @@ Object { "enabled": true, }, "cors": false, - "defaultRoute": undefined, "host": "host", "keepaliveTimeout": 5000, "maxPayload": 1000, @@ -32,7 +31,6 @@ Object { "enabled": true, }, "cors": false, - "defaultRoute": undefined, "host": "host", "keepaliveTimeout": 5000, "maxPayload": 1000, diff --git a/src/core/server/legacy/config/legacy_object_to_config_adapter.ts b/src/core/server/legacy/config/legacy_object_to_config_adapter.ts index bdcde8262ef98..458c1f1f119ee 100644 --- a/src/core/server/legacy/config/legacy_object_to_config_adapter.ts +++ b/src/core/server/legacy/config/legacy_object_to_config_adapter.ts @@ -64,7 +64,6 @@ export class LegacyObjectToConfigAdapter extends ObjectToConfigAdapter { return { autoListen: configValue.autoListen, basePath: configValue.basePath, - defaultRoute: configValue.defaultRoute, cors: configValue.cors, host: configValue.host, maxPayload: configValue.maxPayloadBytes, diff --git a/src/core/server/server.ts b/src/core/server/server.ts index 6ae9fd07f3d5f..4239e7e6c4254 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -257,6 +257,10 @@ export class Server { ]; this.configService.addDeprecationProvider(rootConfigPath, coreDeprecationProvider); + this.configService.addDeprecationProvider( + uiSettingsConfig.path, + uiSettingsConfig.deprecations! + ); for (const [path, schema] of schemas) { await this.configService.setSchema(path, schema); diff --git a/src/core/server/ui_settings/ui_settings_config.ts b/src/core/server/ui_settings/ui_settings_config.ts index 702286f953ef1..a54d482a0296a 100644 --- a/src/core/server/ui_settings/ui_settings_config.ts +++ b/src/core/server/ui_settings/ui_settings_config.ts @@ -18,15 +18,35 @@ */ import { schema, TypeOf } from '@kbn/config-schema'; +import { ConfigDeprecationProvider } from 'src/core/server'; +import { ServiceConfigDescriptor } from '../internal_types'; -export type UiSettingsConfigType = TypeOf; +const deprecations: ConfigDeprecationProvider = ({ unused, renameFromRoot }) => [ + unused('enabled'), + renameFromRoot('server.defaultRoute', 'uiSettings.overrides.defaultRoute'), +]; -export const config = { +const configSchema = schema.object({ + overrides: schema.object( + { + defaultRoute: schema.maybe( + schema.string({ + validate(value) { + if (!value.startsWith('/')) { + return 'must start with a slash'; + } + }, + }) + ), + }, + { allowUnknowns: true } + ), +}); + +export type UiSettingsConfigType = TypeOf; + +export const config: ServiceConfigDescriptor = { path: 'uiSettings', - schema: schema.object({ - overrides: schema.object({}, { allowUnknowns: true }), - // Deprecation is implemented in LP. - // We define schema here not to fail on the validation step. - enabled: schema.maybe(schema.boolean()), - }), + schema: configSchema, + deprecations, }; diff --git a/src/core/server/ui_settings/ui_settings_service.test.ts b/src/core/server/ui_settings/ui_settings_service.test.ts index d908a91a39c70..9b6a5ba0a4884 100644 --- a/src/core/server/ui_settings/ui_settings_service.test.ts +++ b/src/core/server/ui_settings/ui_settings_service.test.ts @@ -22,7 +22,6 @@ import { MockUiSettingsClientConstructor } from './ui_settings_service.test.mock import { UiSettingsService } from './ui_settings_service'; import { httpServiceMock } from '../http/http_service.mock'; -import { loggingServiceMock } from '../logging/logging_service.mock'; import { savedObjectsClientMock } from '../mocks'; import { mockCoreContext } from '../core_context.mock'; @@ -69,27 +68,6 @@ describe('uiSettings', () => { expect(MockUiSettingsClientConstructor.mock.calls[0][0].overrides).toEqual(overrides); }); - it('passes overrides with deprecated "server.defaultRoute"', async () => { - const service = new UiSettingsService(coreContext); - const httpSetupWithDefaultRoute = httpServiceMock.createSetupContract(); - httpSetupWithDefaultRoute.config.defaultRoute = '/defaultRoute'; - const setup = await service.setup({ http: httpSetupWithDefaultRoute }); - setup.asScopedToClient(savedObjectsClient); - - expect(MockUiSettingsClientConstructor.mock.calls[0][0].overrides).toEqual({ - ...overrides, - defaultRoute: '/defaultRoute', - }); - - expect(loggingServiceMock.collect(coreContext.logger).warn).toMatchInlineSnapshot(` - Array [ - Array [ - "Config key \\"server.defaultRoute\\" is deprecated. It has been replaced with \\"uiSettings.overrides.defaultRoute\\"", - ], - ] - `); - }); - it('passes a copy of set defaults to UiSettingsClient', async () => { const service = new UiSettingsService(coreContext); const setup = await service.setup(setupDeps); diff --git a/src/core/server/ui_settings/ui_settings_service.ts b/src/core/server/ui_settings/ui_settings_service.ts index db08c3cad85a2..942c2625ac8e7 100644 --- a/src/core/server/ui_settings/ui_settings_service.ts +++ b/src/core/server/ui_settings/ui_settings_service.ts @@ -56,7 +56,9 @@ export class UiSettingsService public async setup(deps: SetupDeps): Promise { registerRoutes(deps.http.createRouter('')); this.log.debug('Setting up ui settings service'); - this.overrides = await this.getOverrides(deps); + const config = await this.config$.pipe(first()).toPromise(); + this.overrides = config.overrides; + return { register: this.register.bind(this), asScopedToClient: this.getScopedClientFactory(), @@ -95,18 +97,4 @@ export class UiSettingsService this.uiSettingsDefaults.set(key, value); }); } - - private async getOverrides(deps: SetupDeps) { - const config = await this.config$.pipe(first()).toPromise(); - const overrides: Record = config.overrides; - // manually implemented deprecation until New platform Config service - // supports them https://github.com/elastic/kibana/issues/40255 - if (typeof deps.http.config.defaultRoute !== 'undefined') { - overrides.defaultRoute = deps.http.config.defaultRoute; - this.log.warn( - 'Config key "server.defaultRoute" is deprecated. It has been replaced with "uiSettings.overrides.defaultRoute"' - ); - } - return overrides; - } } diff --git a/src/legacy/server/config/schema.js b/src/legacy/server/config/schema.js index f886fd598f5c9..183904ff35985 100644 --- a/src/legacy/server/config/schema.js +++ b/src/legacy/server/config/schema.js @@ -70,7 +70,6 @@ export default () => server: Joi.object({ name: Joi.string().default(os.hostname()), - defaultRoute: Joi.string().regex(/^\//, `start with a slash`), customResponseHeaders: Joi.object() .unknown(true) .default({}), diff --git a/src/legacy/server/http/setup_default_route_provider.ts b/src/legacy/server/http/setup_default_route_provider.ts index 0e7bcf1f56f6f..9a580dd1c59bd 100644 --- a/src/legacy/server/http/setup_default_route_provider.ts +++ b/src/legacy/server/http/setup_default_route_provider.ts @@ -29,7 +29,7 @@ export function setupDefaultRouteProvider(server: Legacy.Server) { const uiSettings = request.getUiSettingsService(); - const defaultRoute = await uiSettings.get('defaultRoute'); + const defaultRoute = await uiSettings.get('defaultRoute'); const qualifiedDefaultRoute = `${request.getBasePath()}${defaultRoute}`; if (isRelativePath(qualifiedDefaultRoute, serverBasePath)) { From 205fbce657c3af3834d5ad33586d04e17c906298 Mon Sep 17 00:00:00 2001 From: Pierre Gayvallet Date: Mon, 6 Jan 2020 15:21:21 +0100 Subject: [PATCH 13/17] migrate xsrf / version-check / custom-headers handlers to NP (#53684) * migrate xsrf / version-check / custom-headers handlers to NP * export lifecycleMock to be used by plugins * move toolkit mock to http_server mock * remove legacy config tests on xsrf * fix integration_test http configuration * remove direct HAPI usages from integration tests * nits and comments * add custom headers test in case of server returning error * resolve merge conflicts * restore `server.name` to legacy config --- .../__snapshots__/http_config.test.ts.snap | 6 + .../http/cookie_session_storage.test.ts | 4 + src/core/server/http/http_config.test.ts | 23 ++ src/core/server/http/http_config.ts | 19 ++ src/core/server/http/http_server.mocks.ts | 13 + src/core/server/http/http_server.ts | 6 + .../server/http/http_service.test.mocks.ts | 4 + src/core/server/http/http_service.ts | 19 +- .../lifecycle_handlers.test.ts | 241 ++++++++++++++++ .../server/http/lifecycle_handlers.test.ts | 269 ++++++++++++++++++ src/core/server/http/lifecycle_handlers.ts | 93 ++++++ src/core/server/http/test_utils.ts | 4 + ...gacy_object_to_config_adapter.test.ts.snap | 16 ++ .../legacy_object_to_config_adapter.test.ts | 12 + .../config/legacy_object_to_config_adapter.ts | 5 +- src/legacy/server/config/schema.js | 15 +- src/legacy/server/config/schema.test.js | 56 ---- src/legacy/server/http/index.js | 28 -- .../integration_tests/version_check.test.js | 64 ----- .../http/integration_tests/xsrf.test.js | 145 ---------- src/legacy/server/http/version_check.js | 39 --- src/legacy/server/http/xsrf.js | 47 --- src/test_utils/kbn_server.ts | 4 +- 23 files changed, 730 insertions(+), 402 deletions(-) create mode 100644 src/core/server/http/integration_tests/lifecycle_handlers.test.ts create mode 100644 src/core/server/http/lifecycle_handlers.test.ts create mode 100644 src/core/server/http/lifecycle_handlers.ts delete mode 100644 src/legacy/server/http/integration_tests/version_check.test.js delete mode 100644 src/legacy/server/http/integration_tests/xsrf.test.js delete mode 100644 src/legacy/server/http/version_check.js delete mode 100644 src/legacy/server/http/xsrf.js diff --git a/src/core/server/http/__snapshots__/http_config.test.ts.snap b/src/core/server/http/__snapshots__/http_config.test.ts.snap index 6c690f9da70c3..8856eb95ba722 100644 --- a/src/core/server/http/__snapshots__/http_config.test.ts.snap +++ b/src/core/server/http/__snapshots__/http_config.test.ts.snap @@ -31,11 +31,13 @@ Object { "enabled": true, }, "cors": false, + "customResponseHeaders": Object {}, "host": "localhost", "keepaliveTimeout": 120000, "maxPayload": ByteSizeValue { "valueInBytes": 1048576, }, + "name": "kibana-hostname", "port": 5601, "rewriteBasePath": false, "socketTimeout": 120000, @@ -70,6 +72,10 @@ Object { "TLSv1.2", ], }, + "xsrf": Object { + "disableProtection": false, + "whitelist": Array [], + }, } `; diff --git a/src/core/server/http/cookie_session_storage.test.ts b/src/core/server/http/cookie_session_storage.test.ts index 0e4f3972fe9dc..4ce422e1f65c4 100644 --- a/src/core/server/http/cookie_session_storage.test.ts +++ b/src/core/server/http/cookie_session_storage.test.ts @@ -58,6 +58,10 @@ configService.atPath.mockReturnValue( verificationMode: 'none', }, compression: { enabled: true }, + xsrf: { + disableProtection: true, + whitelist: [], + }, } as any) ); diff --git a/src/core/server/http/http_config.test.ts b/src/core/server/http/http_config.test.ts index 082b85ad68add..3dc5fa48bc366 100644 --- a/src/core/server/http/http_config.test.ts +++ b/src/core/server/http/http_config.test.ts @@ -23,6 +23,11 @@ import { config, HttpConfig } from '.'; const validHostnames = ['www.example.com', '8.8.8.8', '::1', 'localhost']; const invalidHostname = 'asdf$%^'; +jest.mock('os', () => ({ + ...jest.requireActual('os'), + hostname: () => 'kibana-hostname', +})); + test('has defaults for config', () => { const httpSchema = config.schema; const obj = {}; @@ -84,6 +89,24 @@ test('accepts only valid uuids for server.uuid', () => { ); }); +test('uses os.hostname() as default for server.name', () => { + const httpSchema = config.schema; + const validated = httpSchema.validate({}); + expect(validated.name).toEqual('kibana-hostname'); +}); + +test('throws if xsrf.whitelist element does not start with a slash', () => { + const httpSchema = config.schema; + const obj = { + xsrf: { + whitelist: ['/valid-path', 'invalid-path'], + }, + }; + expect(() => httpSchema.validate(obj)).toThrowErrorMatchingInlineSnapshot( + `"[xsrf.whitelist.1]: must start with a slash"` + ); +}); + describe('with TLS', () => { test('throws if TLS is enabled but `key` is not specified', () => { const httpSchema = config.schema; diff --git a/src/core/server/http/http_config.ts b/src/core/server/http/http_config.ts index 92a8d6a95b854..73f44f3c5ab5c 100644 --- a/src/core/server/http/http_config.ts +++ b/src/core/server/http/http_config.ts @@ -18,6 +18,8 @@ */ import { ByteSizeValue, schema, TypeOf } from '@kbn/config-schema'; +import { hostname } from 'os'; + import { CspConfigType, CspConfig, ICspConfig } from '../csp'; import { SslConfig, sslSchema } from './ssl_config'; @@ -33,6 +35,7 @@ export const config = { path: 'server', schema: schema.object( { + name: schema.string({ defaultValue: () => hostname() }), autoListen: schema.boolean({ defaultValue: true }), basePath: schema.maybe( schema.string({ @@ -54,6 +57,9 @@ export const config = { ), schema.boolean({ defaultValue: false }) ), + customResponseHeaders: schema.recordOf(schema.string(), schema.string(), { + defaultValue: {}, + }), host: schema.string({ defaultValue: 'localhost', hostname: true, @@ -88,6 +94,13 @@ export const config = { validate: match(uuidRegexp, 'must be a valid uuid'), }) ), + xsrf: schema.object({ + disableProtection: schema.boolean({ defaultValue: false }), + whitelist: schema.arrayOf( + schema.string({ validate: match(/^\//, 'must start with a slash') }), + { defaultValue: [] } + ), + }), }, { validate: rawConfig => { @@ -116,18 +129,21 @@ export const config = { export type HttpConfigType = TypeOf; export class HttpConfig { + public name: string; public autoListen: boolean; public host: string; public keepaliveTimeout: number; public socketTimeout: number; public port: number; public cors: boolean | { origin: string[] }; + public customResponseHeaders: Record; public maxPayload: ByteSizeValue; public basePath?: string; public rewriteBasePath: boolean; public ssl: SslConfig; public compression: { enabled: boolean; referrerWhitelist?: string[] }; public csp: ICspConfig; + public xsrf: { disableProtection: boolean; whitelist: string[] }; /** * @internal @@ -137,7 +153,9 @@ export class HttpConfig { this.host = rawHttpConfig.host; this.port = rawHttpConfig.port; this.cors = rawHttpConfig.cors; + this.customResponseHeaders = rawHttpConfig.customResponseHeaders; this.maxPayload = rawHttpConfig.maxPayload; + this.name = rawHttpConfig.name; this.basePath = rawHttpConfig.basePath; this.keepaliveTimeout = rawHttpConfig.keepaliveTimeout; this.socketTimeout = rawHttpConfig.socketTimeout; @@ -145,5 +163,6 @@ export class HttpConfig { this.ssl = new SslConfig(rawHttpConfig.ssl || {}); this.compression = rawHttpConfig.compression; this.csp = new CspConfig(rawCspConfig); + this.xsrf = rawHttpConfig.xsrf; } } diff --git a/src/core/server/http/http_server.mocks.ts b/src/core/server/http/http_server.mocks.ts index ba742292e9e83..230a229b36888 100644 --- a/src/core/server/http/http_server.mocks.ts +++ b/src/core/server/http/http_server.mocks.ts @@ -30,6 +30,9 @@ import { RouteMethod, KibanaResponseFactory, } from './router'; +import { OnPreResponseToolkit } from './lifecycle/on_pre_response'; +import { OnPostAuthToolkit } from './lifecycle/on_post_auth'; +import { OnPreAuthToolkit } from './lifecycle/on_pre_auth'; interface RequestFixtureOptions { headers?: Record; @@ -137,9 +140,19 @@ const createLifecycleResponseFactoryMock = (): jest.Mocked; + +const createToolkitMock = (): ToolkitMock => { + return { + next: jest.fn(), + rewriteUrl: jest.fn(), + }; +}; + export const httpServerMock = { createKibanaRequest: createKibanaRequestMock, createRawRequest: createRawRequestMock, createResponseFactory: createResponseFactoryMock, createLifecycleResponseFactory: createLifecycleResponseFactoryMock, + createToolkit: createToolkitMock, }; diff --git a/src/core/server/http/http_server.ts b/src/core/server/http/http_server.ts index 994a6cced8914..6b978b71c6f2b 100644 --- a/src/core/server/http/http_server.ts +++ b/src/core/server/http/http_server.ts @@ -60,6 +60,12 @@ export interface HttpServerSetup { }; } +/** @internal */ +export type LifecycleRegistrar = Pick< + HttpServerSetup, + 'registerAuth' | 'registerOnPreAuth' | 'registerOnPostAuth' | 'registerOnPreResponse' +>; + export class HttpServer { private server?: Server; private config?: HttpConfig; diff --git a/src/core/server/http/http_service.test.mocks.ts b/src/core/server/http/http_service.test.mocks.ts index c147944f2b7d8..e18008d3b405d 100644 --- a/src/core/server/http/http_service.test.mocks.ts +++ b/src/core/server/http/http_service.test.mocks.ts @@ -27,3 +27,7 @@ jest.mock('./http_server', () => { HttpServer: mockHttpServer, }; }); + +jest.mock('./lifecycle_handlers', () => ({ + registerCoreHandlers: jest.fn(), +})); diff --git a/src/core/server/http/http_service.ts b/src/core/server/http/http_service.ts index 09982cf164a19..ae9d53f9fd3db 100644 --- a/src/core/server/http/http_service.ts +++ b/src/core/server/http/http_service.ts @@ -21,11 +21,10 @@ import { Observable, Subscription, combineLatest } from 'rxjs'; import { first, map } from 'rxjs/operators'; import { Server } from 'hapi'; -import { LoggerFactory } from '../logging'; import { CoreService } from '../../types'; - -import { Logger } from '../logging'; +import { Logger, LoggerFactory } from '../logging'; import { ContextSetup } from '../context'; +import { Env } from '../config'; import { CoreContext } from '../core_context'; import { PluginOpaqueId } from '../plugins'; import { CspConfigType, config as cspConfig } from '../csp'; @@ -43,6 +42,7 @@ import { } from './types'; import { RequestHandlerContext } from '../../server'; +import { registerCoreHandlers } from './lifecycle_handlers'; interface SetupDeps { context: ContextSetup; @@ -57,18 +57,20 @@ export class HttpService implements CoreService(httpConfig.path), - configService.atPath(cspConfig.path) - ).pipe(map(([http, csp]) => new HttpConfig(http, csp))); + configService.atPath(cspConfig.path), + ]).pipe(map(([http, csp]) => new HttpConfig(http, csp))); this.httpServer = new HttpServer(logger, 'Kibana'); this.httpsRedirectServer = new HttpsRedirectServer(logger.get('http', 'redirect', 'server')); } @@ -92,6 +94,9 @@ export class HttpService implements CoreService { + let server: HttpService; + let innerServer: HttpServerSetup['server']; + let router: IRouter; + + beforeEach(async () => { + const configService = configServiceMock.create(); + configService.atPath.mockReturnValue( + new BehaviorSubject({ + hosts: ['localhost'], + maxPayload: new ByteSizeValue(1024), + autoListen: true, + ssl: { + enabled: false, + }, + compression: { enabled: true }, + name: kibanaName, + customResponseHeaders: { + 'some-header': 'some-value', + }, + xsrf: { disableProtection: false, whitelist: [whitelistedTestPath] }, + } as any) + ); + server = createHttpServer({ configService }); + + const serverSetup = await server.setup(setupDeps); + router = serverSetup.createRouter('/'); + innerServer = serverSetup.server; + }, 30000); + + afterEach(async () => { + await server.stop(); + }); + + describe('versionCheck post-auth handler', () => { + const testRoute = '/version_check/test/route'; + + beforeEach(async () => { + router.get({ path: testRoute, validate: false }, (context, req, res) => { + return res.ok({ body: 'ok' }); + }); + await server.start(); + }); + + it('accepts requests with the correct version passed in the version header', async () => { + await supertest(innerServer.listener) + .get(testRoute) + .set(versionHeader, actualVersion) + .expect(200, 'ok'); + }); + + it('accepts requests that do not include a version header', async () => { + await supertest(innerServer.listener) + .get(testRoute) + .expect(200, 'ok'); + }); + + it('rejects requests with an incorrect version passed in the version header', async () => { + await supertest(innerServer.listener) + .get(testRoute) + .set(versionHeader, 'invalid-version') + .expect(400, /Browser client is out of date/); + }); + }); + + describe('customHeaders pre-response handler', () => { + const testRoute = '/custom_headers/test/route'; + const testErrorRoute = '/custom_headers/test/error_route'; + + beforeEach(async () => { + router.get({ path: testRoute, validate: false }, (context, req, res) => { + return res.ok({ body: 'ok' }); + }); + router.get({ path: testErrorRoute, validate: false }, (context, req, res) => { + return res.badRequest({ body: 'bad request' }); + }); + await server.start(); + }); + + it('adds the kbn-name header', async () => { + const result = await supertest(innerServer.listener) + .get(testRoute) + .expect(200, 'ok'); + const headers = result.header as Record; + expect(headers).toEqual( + expect.objectContaining({ + [nameHeader]: kibanaName, + }) + ); + }); + + it('adds the kbn-name header in case of error', async () => { + const result = await supertest(innerServer.listener) + .get(testErrorRoute) + .expect(400); + const headers = result.header as Record; + expect(headers).toEqual( + expect.objectContaining({ + [nameHeader]: kibanaName, + }) + ); + }); + + it('adds the custom headers', async () => { + const result = await supertest(innerServer.listener) + .get(testRoute) + .expect(200, 'ok'); + const headers = result.header as Record; + expect(headers).toEqual(expect.objectContaining({ 'some-header': 'some-value' })); + }); + + it('adds the custom headers in case of error', async () => { + const result = await supertest(innerServer.listener) + .get(testErrorRoute) + .expect(400); + const headers = result.header as Record; + expect(headers).toEqual(expect.objectContaining({ 'some-header': 'some-value' })); + }); + }); + + describe('xsrf post-auth handler', () => { + const testPath = '/xsrf/test/route'; + const destructiveMethods = ['POST', 'PUT', 'DELETE']; + const nonDestructiveMethods = ['GET', 'HEAD']; + + const getSupertest = (method: string, path: string): supertest.Test => { + return (supertest(innerServer.listener) as any)[method.toLowerCase()](path) as supertest.Test; + }; + + beforeEach(async () => { + router.get({ path: testPath, validate: false }, (context, req, res) => { + return res.ok({ body: 'ok' }); + }); + + destructiveMethods.forEach(method => { + ((router as any)[method.toLowerCase()] as RouteRegistrar)( + { path: testPath, validate: false }, + (context, req, res) => { + return res.ok({ body: 'ok' }); + } + ); + ((router as any)[method.toLowerCase()] as RouteRegistrar)( + { path: whitelistedTestPath, validate: false }, + (context, req, res) => { + return res.ok({ body: 'ok' }); + } + ); + }); + + await server.start(); + }); + + nonDestructiveMethods.forEach(method => { + describe(`When using non-destructive ${method} method`, () => { + it('accepts requests without a token', async () => { + await getSupertest(method.toLowerCase(), testPath).expect( + 200, + method === 'HEAD' ? undefined : 'ok' + ); + }); + + it('accepts requests with the xsrf header', async () => { + await getSupertest(method.toLowerCase(), testPath) + .set(xsrfHeader, 'anything') + .expect(200, method === 'HEAD' ? undefined : 'ok'); + }); + }); + }); + + destructiveMethods.forEach(method => { + describe(`When using destructive ${method} method`, () => { + it('accepts requests with the xsrf header', async () => { + await getSupertest(method.toLowerCase(), testPath) + .set(xsrfHeader, 'anything') + .expect(200, 'ok'); + }); + + it('accepts requests with the version header', async () => { + await getSupertest(method.toLowerCase(), testPath) + .set(versionHeader, actualVersion) + .expect(200, 'ok'); + }); + + it('rejects requests without either an xsrf or version header', async () => { + await getSupertest(method.toLowerCase(), testPath).expect(400, { + statusCode: 400, + error: 'Bad Request', + message: 'Request must contain a kbn-xsrf header.', + }); + }); + + it('accepts whitelisted requests without either an xsrf or version header', async () => { + await getSupertest(method.toLowerCase(), whitelistedTestPath).expect(200, 'ok'); + }); + }); + }); + }); +}); diff --git a/src/core/server/http/lifecycle_handlers.test.ts b/src/core/server/http/lifecycle_handlers.test.ts new file mode 100644 index 0000000000000..48a6973b741ba --- /dev/null +++ b/src/core/server/http/lifecycle_handlers.test.ts @@ -0,0 +1,269 @@ +/* + * 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 { + createCustomHeadersPreResponseHandler, + createVersionCheckPostAuthHandler, + createXsrfPostAuthHandler, +} from './lifecycle_handlers'; +import { httpServerMock } from './http_server.mocks'; +import { HttpConfig } from './http_config'; +import { KibanaRequest, RouteMethod } from './router'; + +const createConfig = (partial: Partial): HttpConfig => partial as HttpConfig; + +const forgeRequest = ({ + headers = {}, + path = '/', + method = 'get', +}: Partial<{ + headers: Record; + path: string; + method: RouteMethod; +}>): KibanaRequest => { + return httpServerMock.createKibanaRequest({ headers, path, method }); +}; + +describe('xsrf post-auth handler', () => { + let toolkit: ReturnType; + let responseFactory: ReturnType; + + beforeEach(() => { + toolkit = httpServerMock.createToolkit(); + responseFactory = httpServerMock.createLifecycleResponseFactory(); + }); + + describe('non destructive methods', () => { + it('accepts requests without version or xsrf header', () => { + const config = createConfig({ xsrf: { whitelist: [], disableProtection: false } }); + const handler = createXsrfPostAuthHandler(config); + const request = forgeRequest({ method: 'get', headers: {} }); + + toolkit.next.mockReturnValue('next' as any); + + const result = handler(request, responseFactory, toolkit); + + expect(responseFactory.badRequest).not.toHaveBeenCalled(); + expect(toolkit.next).toHaveBeenCalledTimes(1); + expect(result).toEqual('next'); + }); + }); + + describe('destructive methods', () => { + it('accepts requests with xsrf header', () => { + const config = createConfig({ xsrf: { whitelist: [], disableProtection: false } }); + const handler = createXsrfPostAuthHandler(config); + const request = forgeRequest({ method: 'post', headers: { 'kbn-xsrf': 'xsrf' } }); + + toolkit.next.mockReturnValue('next' as any); + + const result = handler(request, responseFactory, toolkit); + + expect(responseFactory.badRequest).not.toHaveBeenCalled(); + expect(toolkit.next).toHaveBeenCalledTimes(1); + expect(result).toEqual('next'); + }); + + it('accepts requests with version header', () => { + const config = createConfig({ xsrf: { whitelist: [], disableProtection: false } }); + const handler = createXsrfPostAuthHandler(config); + const request = forgeRequest({ method: 'post', headers: { 'kbn-version': 'some-version' } }); + + toolkit.next.mockReturnValue('next' as any); + + const result = handler(request, responseFactory, toolkit); + + expect(responseFactory.badRequest).not.toHaveBeenCalled(); + expect(toolkit.next).toHaveBeenCalledTimes(1); + expect(result).toEqual('next'); + }); + + it('returns a bad request if called without xsrf or version header', () => { + const config = createConfig({ xsrf: { whitelist: [], disableProtection: false } }); + const handler = createXsrfPostAuthHandler(config); + const request = forgeRequest({ method: 'post' }); + + responseFactory.badRequest.mockReturnValue('badRequest' as any); + + const result = handler(request, responseFactory, toolkit); + + expect(toolkit.next).not.toHaveBeenCalled(); + expect(responseFactory.badRequest).toHaveBeenCalledTimes(1); + expect(responseFactory.badRequest.mock.calls[0][0]).toMatchInlineSnapshot(` + Object { + "body": "Request must contain a kbn-xsrf header.", + } + `); + expect(result).toEqual('badRequest'); + }); + + it('accepts requests if protection is disabled', () => { + const config = createConfig({ xsrf: { whitelist: [], disableProtection: true } }); + const handler = createXsrfPostAuthHandler(config); + const request = forgeRequest({ method: 'post', headers: {} }); + + toolkit.next.mockReturnValue('next' as any); + + const result = handler(request, responseFactory, toolkit); + + expect(responseFactory.badRequest).not.toHaveBeenCalled(); + expect(toolkit.next).toHaveBeenCalledTimes(1); + expect(result).toEqual('next'); + }); + + it('accepts requests if path is whitelisted', () => { + const config = createConfig({ + xsrf: { whitelist: ['/some-path'], disableProtection: false }, + }); + const handler = createXsrfPostAuthHandler(config); + const request = forgeRequest({ method: 'post', headers: {}, path: '/some-path' }); + + toolkit.next.mockReturnValue('next' as any); + + const result = handler(request, responseFactory, toolkit); + + expect(responseFactory.badRequest).not.toHaveBeenCalled(); + expect(toolkit.next).toHaveBeenCalledTimes(1); + expect(result).toEqual('next'); + }); + }); +}); + +describe('versionCheck post-auth handler', () => { + let toolkit: ReturnType; + let responseFactory: ReturnType; + + beforeEach(() => { + toolkit = httpServerMock.createToolkit(); + responseFactory = httpServerMock.createLifecycleResponseFactory(); + }); + + it('forward the request to the next interceptor if header matches', () => { + const handler = createVersionCheckPostAuthHandler('actual-version'); + const request = forgeRequest({ headers: { 'kbn-version': 'actual-version' } }); + + toolkit.next.mockReturnValue('next' as any); + + const result = handler(request, responseFactory, toolkit); + + expect(toolkit.next).toHaveBeenCalledTimes(1); + expect(responseFactory.badRequest).not.toHaveBeenCalled(); + expect(result).toBe('next'); + }); + + it('returns a badRequest error if header does not match', () => { + const handler = createVersionCheckPostAuthHandler('actual-version'); + const request = forgeRequest({ headers: { 'kbn-version': 'another-version' } }); + + responseFactory.badRequest.mockReturnValue('badRequest' as any); + + const result = handler(request, responseFactory, toolkit); + + expect(toolkit.next).not.toHaveBeenCalled(); + expect(responseFactory.badRequest).toHaveBeenCalledTimes(1); + expect(responseFactory.badRequest.mock.calls[0][0]).toMatchInlineSnapshot(` + Object { + "body": Object { + "attributes": Object { + "expected": "actual-version", + "got": "another-version", + }, + "message": "Browser client is out of date, please refresh the page (\\"kbn-version\\" header was \\"another-version\\" but should be \\"actual-version\\")", + }, + } + `); + expect(result).toBe('badRequest'); + }); + + it('forward the request to the next interceptor if header is not present', () => { + const handler = createVersionCheckPostAuthHandler('actual-version'); + const request = forgeRequest({ headers: {} }); + + toolkit.next.mockReturnValue('next' as any); + + const result = handler(request, responseFactory, toolkit); + + expect(toolkit.next).toHaveBeenCalledTimes(1); + expect(responseFactory.badRequest).not.toHaveBeenCalled(); + expect(result).toBe('next'); + }); +}); + +describe('customHeaders pre-response handler', () => { + let toolkit: ReturnType; + + beforeEach(() => { + toolkit = httpServerMock.createToolkit(); + }); + + it('adds the kbn-name header to the response', () => { + const config = createConfig({ name: 'my-server-name' }); + const handler = createCustomHeadersPreResponseHandler(config as HttpConfig); + + handler({} as any, {} as any, toolkit); + + expect(toolkit.next).toHaveBeenCalledTimes(1); + expect(toolkit.next).toHaveBeenCalledWith({ headers: { 'kbn-name': 'my-server-name' } }); + }); + + it('adds the custom headers defined in the configuration', () => { + const config = createConfig({ + name: 'my-server-name', + customResponseHeaders: { + headerA: 'value-A', + headerB: 'value-B', + }, + }); + const handler = createCustomHeadersPreResponseHandler(config as HttpConfig); + + handler({} as any, {} as any, toolkit); + + expect(toolkit.next).toHaveBeenCalledTimes(1); + expect(toolkit.next).toHaveBeenCalledWith({ + headers: { + 'kbn-name': 'my-server-name', + headerA: 'value-A', + headerB: 'value-B', + }, + }); + }); + + it('preserve the kbn-name value from server.name if definied in custom headders ', () => { + const config = createConfig({ + name: 'my-server-name', + customResponseHeaders: { + 'kbn-name': 'custom-name', + headerA: 'value-A', + headerB: 'value-B', + }, + }); + const handler = createCustomHeadersPreResponseHandler(config as HttpConfig); + + handler({} as any, {} as any, toolkit); + + expect(toolkit.next).toHaveBeenCalledTimes(1); + expect(toolkit.next).toHaveBeenCalledWith({ + headers: { + 'kbn-name': 'my-server-name', + headerA: 'value-A', + headerB: 'value-B', + }, + }); + }); +}); diff --git a/src/core/server/http/lifecycle_handlers.ts b/src/core/server/http/lifecycle_handlers.ts new file mode 100644 index 0000000000000..ee877ee031a2b --- /dev/null +++ b/src/core/server/http/lifecycle_handlers.ts @@ -0,0 +1,93 @@ +/* + * 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 { OnPostAuthHandler } from './lifecycle/on_post_auth'; +import { OnPreResponseHandler } from './lifecycle/on_pre_response'; +import { HttpConfig } from './http_config'; +import { Env } from '../config'; +import { LifecycleRegistrar } from './http_server'; + +const VERSION_HEADER = 'kbn-version'; +const XSRF_HEADER = 'kbn-xsrf'; +const KIBANA_NAME_HEADER = 'kbn-name'; + +export const createXsrfPostAuthHandler = (config: HttpConfig): OnPostAuthHandler => { + const { whitelist, disableProtection } = config.xsrf; + + return (request, response, toolkit) => { + if (disableProtection || whitelist.includes(request.route.path)) { + return toolkit.next(); + } + + const isSafeMethod = request.route.method === 'get' || request.route.method === 'head'; + const hasVersionHeader = VERSION_HEADER in request.headers; + const hasXsrfHeader = XSRF_HEADER in request.headers; + + if (!isSafeMethod && !hasVersionHeader && !hasXsrfHeader) { + return response.badRequest({ body: `Request must contain a ${XSRF_HEADER} header.` }); + } + + return toolkit.next(); + }; +}; + +export const createVersionCheckPostAuthHandler = (kibanaVersion: string): OnPostAuthHandler => { + return (request, response, toolkit) => { + const requestVersion = request.headers[VERSION_HEADER]; + if (requestVersion && requestVersion !== kibanaVersion) { + return response.badRequest({ + body: { + message: + `Browser client is out of date, please refresh the page ` + + `("${VERSION_HEADER}" header was "${requestVersion}" but should be "${kibanaVersion}")`, + attributes: { + expected: kibanaVersion, + got: requestVersion, + }, + }, + }); + } + + return toolkit.next(); + }; +}; + +export const createCustomHeadersPreResponseHandler = (config: HttpConfig): OnPreResponseHandler => { + const serverName = config.name; + const customHeaders = config.customResponseHeaders; + + return (request, response, toolkit) => { + const additionalHeaders = { + ...customHeaders, + [KIBANA_NAME_HEADER]: serverName, + }; + + return toolkit.next({ headers: additionalHeaders }); + }; +}; + +export const registerCoreHandlers = ( + registrar: LifecycleRegistrar, + config: HttpConfig, + env: Env +) => { + registrar.registerOnPreResponse(createCustomHeadersPreResponseHandler(config)); + registrar.registerOnPostAuth(createXsrfPostAuthHandler(config)); + registrar.registerOnPostAuth(createVersionCheckPostAuthHandler(env.packageInfo.version)); +}; diff --git a/src/core/server/http/test_utils.ts b/src/core/server/http/test_utils.ts index e0a15cdc6e839..ffdc04d156ca0 100644 --- a/src/core/server/http/test_utils.ts +++ b/src/core/server/http/test_utils.ts @@ -41,6 +41,10 @@ configService.atPath.mockReturnValue( enabled: false, }, compression: { enabled: true }, + xsrf: { + disableProtection: true, + whitelist: [], + }, } as any) ); diff --git a/src/core/server/legacy/config/__snapshots__/legacy_object_to_config_adapter.test.ts.snap b/src/core/server/legacy/config/__snapshots__/legacy_object_to_config_adapter.test.ts.snap index 3161dd06cf3b6..74ecaa9f09c0e 100644 --- a/src/core/server/legacy/config/__snapshots__/legacy_object_to_config_adapter.test.ts.snap +++ b/src/core/server/legacy/config/__snapshots__/legacy_object_to_config_adapter.test.ts.snap @@ -8,9 +8,13 @@ Object { "enabled": true, }, "cors": false, + "customResponseHeaders": Object { + "custom-header": "custom-value", + }, "host": "host", "keepaliveTimeout": 5000, "maxPayload": 1000, + "name": "kibana-hostname", "port": 1234, "rewriteBasePath": false, "socketTimeout": 2000, @@ -20,6 +24,10 @@ Object { "someNewValue": "new", }, "uuid": undefined, + "xsrf": Object { + "disableProtection": false, + "whitelist": Array [], + }, } `; @@ -31,9 +39,13 @@ Object { "enabled": true, }, "cors": false, + "customResponseHeaders": Object { + "custom-header": "custom-value", + }, "host": "host", "keepaliveTimeout": 5000, "maxPayload": 1000, + "name": "kibana-hostname", "port": 1234, "rewriteBasePath": false, "socketTimeout": 2000, @@ -43,6 +55,10 @@ Object { "key": "key", }, "uuid": undefined, + "xsrf": Object { + "disableProtection": false, + "whitelist": Array [], + }, } `; diff --git a/src/core/server/legacy/config/legacy_object_to_config_adapter.test.ts b/src/core/server/legacy/config/legacy_object_to_config_adapter.test.ts index db2bc117280ca..1c51564187442 100644 --- a/src/core/server/legacy/config/legacy_object_to_config_adapter.test.ts +++ b/src/core/server/legacy/config/legacy_object_to_config_adapter.test.ts @@ -80,9 +80,11 @@ describe('#get', () => { test('correctly handles server config.', () => { const configAdapter = new LegacyObjectToConfigAdapter({ server: { + name: 'kibana-hostname', autoListen: true, basePath: '/abc', cors: false, + customResponseHeaders: { 'custom-header': 'custom-value' }, host: 'host', maxPayloadBytes: 1000, keepaliveTimeout: 5000, @@ -92,14 +94,20 @@ describe('#get', () => { ssl: { enabled: true, keyPassphrase: 'some-phrase', someNewValue: 'new' }, compression: { enabled: true }, someNotSupportedValue: 'val', + xsrf: { + disableProtection: false, + whitelist: [], + }, }, }); const configAdapterWithDisabledSSL = new LegacyObjectToConfigAdapter({ server: { + name: 'kibana-hostname', autoListen: true, basePath: '/abc', cors: false, + customResponseHeaders: { 'custom-header': 'custom-value' }, host: 'host', maxPayloadBytes: 1000, keepaliveTimeout: 5000, @@ -109,6 +117,10 @@ describe('#get', () => { ssl: { enabled: false, certificate: 'cert', key: 'key' }, compression: { enabled: true }, someNotSupportedValue: 'val', + xsrf: { + disableProtection: false, + whitelist: [], + }, }, }); diff --git a/src/core/server/legacy/config/legacy_object_to_config_adapter.ts b/src/core/server/legacy/config/legacy_object_to_config_adapter.ts index 458c1f1f119ee..30bb150e6c15a 100644 --- a/src/core/server/legacy/config/legacy_object_to_config_adapter.ts +++ b/src/core/server/legacy/config/legacy_object_to_config_adapter.ts @@ -60,13 +60,15 @@ export class LegacyObjectToConfigAdapter extends ObjectToConfigAdapter { private static transformServer(configValue: any = {}) { // TODO: New platform uses just a subset of `server` config from the legacy platform, - // new values will be exposed once we need them (eg. customResponseHeaders or xsrf). + // new values will be exposed once we need them return { autoListen: configValue.autoListen, basePath: configValue.basePath, cors: configValue.cors, + customResponseHeaders: configValue.customResponseHeaders, host: configValue.host, maxPayload: configValue.maxPayloadBytes, + name: configValue.name, port: configValue.port, rewriteBasePath: configValue.rewriteBasePath, ssl: configValue.ssl, @@ -74,6 +76,7 @@ export class LegacyObjectToConfigAdapter extends ObjectToConfigAdapter { socketTimeout: configValue.socketTimeout, compression: configValue.compression, uuid: configValue.uuid, + xsrf: configValue.xsrf, }; } diff --git a/src/legacy/server/config/schema.js b/src/legacy/server/config/schema.js index 183904ff35985..a18cb7de5a61b 100644 --- a/src/legacy/server/config/schema.js +++ b/src/legacy/server/config/schema.js @@ -70,19 +70,6 @@ export default () => server: Joi.object({ name: Joi.string().default(os.hostname()), - customResponseHeaders: Joi.object() - .unknown(true) - .default({}), - xsrf: Joi.object({ - disableProtection: Joi.boolean().default(false), - whitelist: Joi.array() - .items(Joi.string().regex(/^\//, 'start with a slash')) - .default([]), - token: Joi.string() - .optional() - .notes('Deprecated'), - }).default(), - // keep them for BWC, remove when not used in Legacy. // validation should be in sync with one in New platform. // https://github.com/elastic/kibana/blob/master/src/core/server/http/http_config.ts @@ -102,12 +89,14 @@ export default () => autoListen: HANDLED_IN_NEW_PLATFORM, cors: HANDLED_IN_NEW_PLATFORM, + customResponseHeaders: HANDLED_IN_NEW_PLATFORM, keepaliveTimeout: HANDLED_IN_NEW_PLATFORM, maxPayloadBytes: HANDLED_IN_NEW_PLATFORM, socketTimeout: HANDLED_IN_NEW_PLATFORM, ssl: HANDLED_IN_NEW_PLATFORM, compression: HANDLED_IN_NEW_PLATFORM, uuid: HANDLED_IN_NEW_PLATFORM, + xsrf: HANDLED_IN_NEW_PLATFORM, }).default(), uiSettings: HANDLED_IN_NEW_PLATFORM, diff --git a/src/legacy/server/config/schema.test.js b/src/legacy/server/config/schema.test.js index 1207a05a47497..03d2fe53c2ce7 100644 --- a/src/legacy/server/config/schema.test.js +++ b/src/legacy/server/config/schema.test.js @@ -19,7 +19,6 @@ import schemaProvider from './schema'; import Joi from 'joi'; -import { set } from 'lodash'; describe('Config schema', function() { let schema; @@ -100,60 +99,5 @@ describe('Config schema', function() { expect(error.details[0]).toHaveProperty('path', ['server', 'rewriteBasePath']); }); }); - - describe('xsrf', () => { - it('disableProtection is `false` by default.', () => { - const { - error, - value: { - server: { - xsrf: { disableProtection }, - }, - }, - } = validate({}); - expect(error).toBe(null); - expect(disableProtection).toBe(false); - }); - - it('whitelist is empty by default.', () => { - const { - value: { - server: { - xsrf: { whitelist }, - }, - }, - } = validate({}); - expect(whitelist).toBeInstanceOf(Array); - expect(whitelist).toHaveLength(0); - }); - - it('whitelist rejects paths that do not start with a slash.', () => { - const config = {}; - set(config, 'server.xsrf.whitelist', ['path/to']); - - const { error } = validate(config); - expect(error).toBeInstanceOf(Object); - expect(error).toHaveProperty('details'); - expect(error.details[0]).toHaveProperty('path', ['server', 'xsrf', 'whitelist', 0]); - }); - - it('whitelist accepts paths that start with a slash.', () => { - const config = {}; - set(config, 'server.xsrf.whitelist', ['/path/to']); - - const { - error, - value: { - server: { - xsrf: { whitelist }, - }, - }, - } = validate(config); - expect(error).toBe(null); - expect(whitelist).toBeInstanceOf(Array); - expect(whitelist).toHaveLength(1); - expect(whitelist).toContain('/path/to'); - }); - }); }); }); diff --git a/src/legacy/server/http/index.js b/src/legacy/server/http/index.js index 9b5ce2046c5d3..265d71e95b301 100644 --- a/src/legacy/server/http/index.js +++ b/src/legacy/server/http/index.js @@ -22,11 +22,9 @@ import { resolve } from 'path'; import _ from 'lodash'; import Boom from 'boom'; -import { setupVersionCheck } from './version_check'; import { registerHapiPlugins } from './register_hapi_plugins'; import { setupBasePathProvider } from './setup_base_path_provider'; import { setupDefaultRouteProvider } from './setup_default_route_provider'; -import { setupXsrf } from './xsrf'; export default async function(kbnServer, server, config) { server = kbnServer.server; @@ -62,29 +60,6 @@ export default async function(kbnServer, server, config) { }); }); - // attach the app name to the server, so we can be sure we are actually talking to kibana - server.ext('onPreResponse', function onPreResponse(req, h) { - const response = req.response; - - const customHeaders = { - ...config.get('server.customResponseHeaders'), - 'kbn-name': kbnServer.name, - }; - - if (response.isBoom) { - response.output.headers = { - ...response.output.headers, - ...customHeaders, - }; - } else { - Object.keys(customHeaders).forEach(name => { - response.header(name, customHeaders[name]); - }); - } - - return h.continue; - }); - server.route({ path: '/', method: 'GET', @@ -116,7 +91,4 @@ export default async function(kbnServer, server, config) { // Expose static assets server.exposeStaticDir('/ui/{path*}', resolve(__dirname, '../../ui/public/assets')); - - setupVersionCheck(server, config); - setupXsrf(server, config); } diff --git a/src/legacy/server/http/integration_tests/version_check.test.js b/src/legacy/server/http/integration_tests/version_check.test.js deleted file mode 100644 index 8d71c98d64969..0000000000000 --- a/src/legacy/server/http/integration_tests/version_check.test.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 { resolve } from 'path'; -import * as kbnTestServer from '../../../../test_utils/kbn_server'; - -const src = resolve.bind(null, __dirname, '../../../../../src'); - -const versionHeader = 'kbn-version'; -const version = require(src('../package.json')).version; // eslint-disable-line import/no-dynamic-require - -describe('version_check request filter', function() { - let root; - beforeAll(async () => { - root = kbnTestServer.createRoot(); - - await root.setup(); - await root.start(); - - kbnTestServer.getKbnServer(root).server.route({ - path: '/version_check/test/route', - method: 'GET', - handler: function() { - return 'ok'; - }, - }); - }, 30000); - - afterAll(async () => await root.shutdown()); - - it('accepts requests with the correct version passed in the version header', async function() { - await kbnTestServer.request - .get(root, '/version_check/test/route') - .set(versionHeader, version) - .expect(200, 'ok'); - }); - - it('rejects requests with an incorrect version passed in the version header', async function() { - await kbnTestServer.request - .get(root, '/version_check/test/route') - .set(versionHeader, `invalid:${version}`) - .expect(400, /"Browser client is out of date/); - }); - - it('accepts requests that do not include a version header', async function() { - await kbnTestServer.request.get(root, '/version_check/test/route').expect(200, 'ok'); - }); -}); diff --git a/src/legacy/server/http/integration_tests/xsrf.test.js b/src/legacy/server/http/integration_tests/xsrf.test.js deleted file mode 100644 index a06f4eec4fd5c..0000000000000 --- a/src/legacy/server/http/integration_tests/xsrf.test.js +++ /dev/null @@ -1,145 +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 { resolve } from 'path'; -import * as kbnTestServer from '../../../../test_utils/kbn_server'; - -const destructiveMethods = ['POST', 'PUT', 'DELETE']; -const src = resolve.bind(null, __dirname, '../../../../../src'); - -const xsrfHeader = 'kbn-xsrf'; -const versionHeader = 'kbn-version'; -const testPath = '/xsrf/test/route'; -const whitelistedTestPath = '/xsrf/test/route/whitelisted'; -const actualVersion = require(src('../package.json')).version; // eslint-disable-line import/no-dynamic-require - -describe('xsrf request filter', () => { - let root; - beforeAll(async () => { - root = kbnTestServer.createRoot({ - server: { - xsrf: { disableProtection: false, whitelist: [whitelistedTestPath] }, - }, - }); - - await root.setup(); - await root.start(); - - const kbnServer = kbnTestServer.getKbnServer(root); - kbnServer.server.route({ - path: testPath, - method: 'GET', - handler: async function() { - return 'ok'; - }, - }); - - kbnServer.server.route({ - path: testPath, - method: destructiveMethods, - config: { - // Disable payload parsing to make HapiJS server accept any content-type header. - payload: { - parse: false, - }, - validate: { payload: null }, - }, - handler: async function() { - return 'ok'; - }, - }); - - kbnServer.server.route({ - path: whitelistedTestPath, - method: destructiveMethods, - config: { - // Disable payload parsing to make HapiJS server accept any content-type header. - payload: { - parse: false, - }, - validate: { payload: null }, - }, - handler: async function() { - return 'ok'; - }, - }); - }, 30000); - - afterAll(async () => await root.shutdown()); - - describe(`nonDestructiveMethod: GET`, function() { - it('accepts requests without a token', async function() { - await kbnTestServer.request.get(root, testPath).expect(200, 'ok'); - }); - - it('accepts requests with the xsrf header', async function() { - await kbnTestServer.request - .get(root, testPath) - .set(xsrfHeader, 'anything') - .expect(200, 'ok'); - }); - }); - - describe(`nonDestructiveMethod: HEAD`, function() { - it('accepts requests without a token', async function() { - await kbnTestServer.request.head(root, testPath).expect(200, undefined); - }); - - it('accepts requests with the xsrf header', async function() { - await kbnTestServer.request - .head(root, testPath) - .set(xsrfHeader, 'anything') - .expect(200, undefined); - }); - }); - - for (const method of destructiveMethods) { - // eslint-disable-next-line no-loop-func - describe(`destructiveMethod: ${method}`, function() { - it('accepts requests with the xsrf header', async function() { - await kbnTestServer.request[method.toLowerCase()](root, testPath) - .set(xsrfHeader, 'anything') - .expect(200, 'ok'); - }); - - // this is still valid for existing csrf protection support - // it does not actually do any validation on the version value itself - it('accepts requests with the version header', async function() { - await kbnTestServer.request[method.toLowerCase()](root, testPath) - .set(versionHeader, actualVersion) - .expect(200, 'ok'); - }); - - it('rejects requests without either an xsrf or version header', async function() { - await kbnTestServer.request[method.toLowerCase()](root, testPath).expect(400, { - statusCode: 400, - error: 'Bad Request', - message: 'Request must contain a kbn-xsrf header.', - }); - }); - - it('accepts whitelisted requests without either an xsrf or version header', async function() { - await kbnTestServer.request[method.toLowerCase()](root, whitelistedTestPath).expect( - 200, - 'ok' - ); - }); - }); - } -}); diff --git a/src/legacy/server/http/version_check.js b/src/legacy/server/http/version_check.js deleted file mode 100644 index 12666c9a0f3f6..0000000000000 --- a/src/legacy/server/http/version_check.js +++ /dev/null @@ -1,39 +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 { badRequest } from 'boom'; - -export function setupVersionCheck(server, config) { - const versionHeader = 'kbn-version'; - const actualVersion = config.get('pkg.version'); - - server.ext('onPostAuth', function onPostAuthVersionCheck(req, h) { - const versionRequested = req.headers[versionHeader]; - - if (versionRequested && versionRequested !== actualVersion) { - throw badRequest( - `Browser client is out of date, \ - please refresh the page ("${versionHeader}" header was "${versionRequested}" but should be "${actualVersion}")`, - { expected: actualVersion, got: versionRequested } - ); - } - - return h.continue; - }); -} diff --git a/src/legacy/server/http/xsrf.js b/src/legacy/server/http/xsrf.js deleted file mode 100644 index 79ac3af6d9f90..0000000000000 --- a/src/legacy/server/http/xsrf.js +++ /dev/null @@ -1,47 +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 { badRequest } from 'boom'; - -export function setupXsrf(server, config) { - const disabled = config.get('server.xsrf.disableProtection'); - const whitelist = config.get('server.xsrf.whitelist'); - const versionHeader = 'kbn-version'; - const xsrfHeader = 'kbn-xsrf'; - - server.ext('onPostAuth', function onPostAuthXsrf(req, h) { - if (disabled) { - return h.continue; - } - - if (whitelist.includes(req.path)) { - return h.continue; - } - - const isSafeMethod = req.method === 'get' || req.method === 'head'; - const hasVersionHeader = versionHeader in req.headers; - const hasXsrfHeader = xsrfHeader in req.headers; - - if (!isSafeMethod && !hasVersionHeader && !hasXsrfHeader) { - throw badRequest(`Request must contain a ${xsrfHeader} header.`); - } - - return h.continue; - }); -} diff --git a/src/test_utils/kbn_server.ts b/src/test_utils/kbn_server.ts index 43c6b4378ed27..e1b4a823e7e87 100644 --- a/src/test_utils/kbn_server.ts +++ b/src/test_utils/kbn_server.ts @@ -37,7 +37,7 @@ import { Root } from '../core/server/root'; import KbnServer from '../legacy/server/kbn_server'; import { CallCluster } from '../legacy/core_plugins/elasticsearch'; -type HttpMethod = 'delete' | 'get' | 'head' | 'post' | 'put'; +export type HttpMethod = 'delete' | 'get' | 'head' | 'post' | 'put'; const DEFAULTS_SETTINGS = { server: { @@ -97,7 +97,7 @@ export function createRootWithSettings( * @param method * @param path */ -function getSupertest(root: Root, method: HttpMethod, path: string) { +export function getSupertest(root: Root, method: HttpMethod, path: string) { const testUserCredentials = Buffer.from(`${kibanaTestUser.username}:${kibanaTestUser.password}`); return supertest((root as any).server.http.httpServer.server.listener) [method](path) From 6ce2818f88c3c1f56c051c42ab0973b845c9e9cb Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Mon, 6 Jan 2020 15:21:44 +0100 Subject: [PATCH 14/17] [Console] Fix OSS build (#53885) * Move fp-ts and immer to "." package.json * Revert "Move fp-ts and immer to "." package.json" This reverts commit b876df0d543d7537b84590c7759cdea36014756c. * Second attempt, fp-ts and immer -> root * fp-ts -> 2.3.1 * Revert x-pack/package.json * Update fp-ts in x-pack/package.json Co-authored-by: Elastic Machine --- package.json | 2 ++ x-pack/package.json | 2 +- yarn.lock | 8 ++++---- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 4f8229333e5a0..99151f33962c4 100644 --- a/package.json +++ b/package.json @@ -174,6 +174,7 @@ "fast-deep-equal": "^3.1.1", "file-loader": "4.2.0", "font-awesome": "4.7.0", + "fp-ts": "^2.3.1", "getos": "^3.1.0", "glob": "^7.1.2", "glob-all": "^3.1.0", @@ -188,6 +189,7 @@ "hoek": "^5.0.4", "http-proxy-agent": "^2.1.0", "https-proxy-agent": "^2.2.2", + "immer": "^1.5.0", "inert": "^5.1.0", "inline-style": "^2.0.0", "joi": "^13.5.2", diff --git a/x-pack/package.json b/x-pack/package.json index c0e7a7c86cf0b..110db56c5d4ed 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -230,7 +230,7 @@ "file-type": "^10.9.0", "font-awesome": "4.7.0", "formsy-react": "^1.1.5", - "fp-ts": "^2.0.5", + "fp-ts": "^2.3.1", "geojson-rewind": "^0.3.1", "get-port": "4.2.0", "getos": "^3.1.0", diff --git a/yarn.lock b/yarn.lock index 7e90744195608..0026370927fe1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13099,10 +13099,10 @@ fp-ts@^1.0.0: resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-1.12.0.tgz#d333310e4ac104cdcb6bea47908e381bb09978e7" integrity sha512-fWwnAgVlTsV26Ruo9nx+fxNHIm6l1puE1VJ/C0XJ3nRQJJJIgRHYw6sigB3MuNFZL1o4fpGlhwFhcbxHK0RsOA== -fp-ts@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-2.0.5.tgz#9560d8a6a4f53cbda9f9b31ed8d1458e41939e07" - integrity sha512-opI5r+rVlpZE7Rhk0YtqsrmxGkbIw0dRNqGca8FEAMMnjomXotG+R9QkLQg20onx7R8qhepAn4CCOP8usma/Xw== +fp-ts@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-2.3.1.tgz#8068bfcca118227932941101e062134d7ecd9119" + integrity sha512-KevPBnYt0aaJiuUzmU9YIxjrhC9AgJ8CLtLlXmwArovlNTeYM5NtEoKd86B0wHd7FIbzeE8sNXzCoYIOr7e6Iw== fragment-cache@^0.2.1: version "0.2.1" From 785b9169174ed5793eb4f5c79fa3b32dc9c6c584 Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Mon, 6 Jan 2020 14:52:06 +0000 Subject: [PATCH 15/17] allows Alerts to recover gracefully from Executor errors (#53688) Prevents errors in Alert Executors from forcing their underlying tasks into a zombie state. --- .../alert_instance.test.ts | 0 .../{lib => alert_instance}/alert_instance.ts | 2 +- .../create_alert_instance_factory.test.ts | 0 .../create_alert_instance_factory.ts | 0 .../alerting/server/alert_instance/index.ts | 8 + .../server/alert_type_registry.test.ts | 2 +- .../alerting/server/alert_type_registry.ts | 4 +- .../{lib => }/alerts_client_factory.test.ts | 26 +- .../server/{lib => }/alerts_client_factory.ts | 12 +- .../plugins/alerting/server/lib/index.ts | 6 +- .../alerting/server/lib/result_type.ts | 54 +++ .../server/lib/task_runner_factory.test.ts | 345 --------------- .../server/lib/task_runner_factory.ts | 190 --------- .../legacy/plugins/alerting/server/plugin.ts | 3 +- .../create_execution_handler.test.ts | 0 .../create_execution_handler.ts | 0 .../get_next_run_at.test.ts | 0 .../{lib => task_runner}/get_next_run_at.ts | 2 +- .../alerting/server/task_runner/index.ts | 7 + .../server/task_runner/task_runner.test.ts | 400 ++++++++++++++++++ .../server/task_runner/task_runner.ts | 241 +++++++++++ .../task_runner/task_runner_factory.test.ts | 87 ++++ .../server/task_runner/task_runner_factory.ts | 46 ++ .../transform_action_params.test.ts | 0 .../transform_action_params.ts | 0 .../legacy/plugins/alerting/server/types.ts | 2 +- .../common/lib/alert_utils.ts | 47 +- .../common/lib/index.ts | 1 + .../common/lib/test_assertions.ts | 17 + .../tests/alerting/update.ts | 15 +- .../spaces_only/tests/alerting/alerts.ts | 40 ++ 31 files changed, 982 insertions(+), 575 deletions(-) rename x-pack/legacy/plugins/alerting/server/{lib => alert_instance}/alert_instance.test.ts (100%) rename x-pack/legacy/plugins/alerting/server/{lib => alert_instance}/alert_instance.ts (97%) rename x-pack/legacy/plugins/alerting/server/{lib => alert_instance}/create_alert_instance_factory.test.ts (100%) rename x-pack/legacy/plugins/alerting/server/{lib => alert_instance}/create_alert_instance_factory.ts (100%) create mode 100644 x-pack/legacy/plugins/alerting/server/alert_instance/index.ts rename x-pack/legacy/plugins/alerting/server/{lib => }/alerts_client_factory.test.ts (81%) rename x-pack/legacy/plugins/alerting/server/{lib => }/alerts_client_factory.ts (92%) create mode 100644 x-pack/legacy/plugins/alerting/server/lib/result_type.ts delete mode 100644 x-pack/legacy/plugins/alerting/server/lib/task_runner_factory.test.ts delete mode 100644 x-pack/legacy/plugins/alerting/server/lib/task_runner_factory.ts rename x-pack/legacy/plugins/alerting/server/{lib => task_runner}/create_execution_handler.test.ts (100%) rename x-pack/legacy/plugins/alerting/server/{lib => task_runner}/create_execution_handler.ts (100%) rename x-pack/legacy/plugins/alerting/server/{lib => task_runner}/get_next_run_at.test.ts (100%) rename x-pack/legacy/plugins/alerting/server/{lib => task_runner}/get_next_run_at.ts (92%) create mode 100644 x-pack/legacy/plugins/alerting/server/task_runner/index.ts create mode 100644 x-pack/legacy/plugins/alerting/server/task_runner/task_runner.test.ts create mode 100644 x-pack/legacy/plugins/alerting/server/task_runner/task_runner.ts create mode 100644 x-pack/legacy/plugins/alerting/server/task_runner/task_runner_factory.test.ts create mode 100644 x-pack/legacy/plugins/alerting/server/task_runner/task_runner_factory.ts rename x-pack/legacy/plugins/alerting/server/{lib => task_runner}/transform_action_params.test.ts (100%) rename x-pack/legacy/plugins/alerting/server/{lib => task_runner}/transform_action_params.ts (100%) create mode 100644 x-pack/test/alerting_api_integration/common/lib/test_assertions.ts diff --git a/x-pack/legacy/plugins/alerting/server/lib/alert_instance.test.ts b/x-pack/legacy/plugins/alerting/server/alert_instance/alert_instance.test.ts similarity index 100% rename from x-pack/legacy/plugins/alerting/server/lib/alert_instance.test.ts rename to x-pack/legacy/plugins/alerting/server/alert_instance/alert_instance.test.ts diff --git a/x-pack/legacy/plugins/alerting/server/lib/alert_instance.ts b/x-pack/legacy/plugins/alerting/server/alert_instance/alert_instance.ts similarity index 97% rename from x-pack/legacy/plugins/alerting/server/lib/alert_instance.ts rename to x-pack/legacy/plugins/alerting/server/alert_instance/alert_instance.ts index 1e2cc26f364ad..a56e2077cdfd8 100644 --- a/x-pack/legacy/plugins/alerting/server/lib/alert_instance.ts +++ b/x-pack/legacy/plugins/alerting/server/alert_instance/alert_instance.ts @@ -5,7 +5,7 @@ */ import { State, Context } from '../types'; -import { parseDuration } from './parse_duration'; +import { parseDuration } from '../lib'; interface Meta { lastScheduledActions?: { diff --git a/x-pack/legacy/plugins/alerting/server/lib/create_alert_instance_factory.test.ts b/x-pack/legacy/plugins/alerting/server/alert_instance/create_alert_instance_factory.test.ts similarity index 100% rename from x-pack/legacy/plugins/alerting/server/lib/create_alert_instance_factory.test.ts rename to x-pack/legacy/plugins/alerting/server/alert_instance/create_alert_instance_factory.test.ts diff --git a/x-pack/legacy/plugins/alerting/server/lib/create_alert_instance_factory.ts b/x-pack/legacy/plugins/alerting/server/alert_instance/create_alert_instance_factory.ts similarity index 100% rename from x-pack/legacy/plugins/alerting/server/lib/create_alert_instance_factory.ts rename to x-pack/legacy/plugins/alerting/server/alert_instance/create_alert_instance_factory.ts diff --git a/x-pack/legacy/plugins/alerting/server/alert_instance/index.ts b/x-pack/legacy/plugins/alerting/server/alert_instance/index.ts new file mode 100644 index 0000000000000..40ee0874e805c --- /dev/null +++ b/x-pack/legacy/plugins/alerting/server/alert_instance/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { AlertInstance } from './alert_instance'; +export { createAlertInstanceFactory } from './create_alert_instance_factory'; diff --git a/x-pack/legacy/plugins/alerting/server/alert_type_registry.test.ts b/x-pack/legacy/plugins/alerting/server/alert_type_registry.test.ts index 57e1b965960e8..8e96ad8dae31c 100644 --- a/x-pack/legacy/plugins/alerting/server/alert_type_registry.test.ts +++ b/x-pack/legacy/plugins/alerting/server/alert_type_registry.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { TaskRunnerFactory } from './lib'; +import { TaskRunnerFactory } from './task_runner'; import { AlertTypeRegistry } from './alert_type_registry'; import { taskManagerMock } from '../../task_manager/server/task_manager.mock'; diff --git a/x-pack/legacy/plugins/alerting/server/alert_type_registry.ts b/x-pack/legacy/plugins/alerting/server/alert_type_registry.ts index b7512864c2a98..2003e810a05b5 100644 --- a/x-pack/legacy/plugins/alerting/server/alert_type_registry.ts +++ b/x-pack/legacy/plugins/alerting/server/alert_type_registry.ts @@ -6,8 +6,8 @@ import Boom from 'boom'; import { i18n } from '@kbn/i18n'; -import { TaskRunnerFactory } from './lib'; -import { RunContext } from '../../task_manager/server'; +import { TaskRunnerFactory } from './task_runner'; +import { RunContext } from '../../task_manager'; import { TaskManagerSetupContract } from './shim'; import { AlertType } from './types'; diff --git a/x-pack/legacy/plugins/alerting/server/lib/alerts_client_factory.test.ts b/x-pack/legacy/plugins/alerting/server/alerts_client_factory.test.ts similarity index 81% rename from x-pack/legacy/plugins/alerting/server/lib/alerts_client_factory.test.ts rename to x-pack/legacy/plugins/alerting/server/alerts_client_factory.test.ts index 838c567fb2878..519001d07e089 100644 --- a/x-pack/legacy/plugins/alerting/server/lib/alerts_client_factory.test.ts +++ b/x-pack/legacy/plugins/alerting/server/alerts_client_factory.test.ts @@ -6,13 +6,13 @@ import { Request } from 'hapi'; import { AlertsClientFactory, ConstructorOpts } from './alerts_client_factory'; -import { alertTypeRegistryMock } from '../alert_type_registry.mock'; -import { taskManagerMock } from '../../../task_manager/server/task_manager.mock'; -import { KibanaRequest } from '../../../../../../src/core/server'; -import { loggingServiceMock } from '../../../../../../src/core/server/mocks'; -import { encryptedSavedObjectsMock } from '../../../../../plugins/encrypted_saved_objects/server/mocks'; +import { alertTypeRegistryMock } from './alert_type_registry.mock'; +import { taskManagerMock } from '../../task_manager/server/task_manager.mock'; +import { KibanaRequest } from '../../../../../src/core/server'; +import { loggingServiceMock } from '../../../../../src/core/server/mocks'; +import { encryptedSavedObjectsMock } from '../../../../plugins/encrypted_saved_objects/server/mocks'; -jest.mock('../alerts_client'); +jest.mock('./alerts_client'); const savedObjectsClient = jest.fn(); const securityPluginSetup = { @@ -55,7 +55,7 @@ test('creates an alerts client with proper constructor arguments', async () => { const factory = new AlertsClientFactory(alertsClientFactoryParams); factory.create(KibanaRequest.from(fakeRequest), fakeRequest); - expect(jest.requireMock('../alerts_client').AlertsClient).toHaveBeenCalledWith({ + expect(jest.requireMock('./alerts_client').AlertsClient).toHaveBeenCalledWith({ savedObjectsClient, logger: alertsClientFactoryParams.logger, taskManager: alertsClientFactoryParams.taskManager, @@ -72,7 +72,7 @@ test('creates an alerts client with proper constructor arguments', async () => { test('getUserName() returns null when security is disabled', async () => { const factory = new AlertsClientFactory(alertsClientFactoryParams); factory.create(KibanaRequest.from(fakeRequest), fakeRequest); - const constructorCall = jest.requireMock('../alerts_client').AlertsClient.mock.calls[0][0]; + const constructorCall = jest.requireMock('./alerts_client').AlertsClient.mock.calls[0][0]; const userNameResult = await constructorCall.getUserName(); expect(userNameResult).toEqual(null); @@ -84,7 +84,7 @@ test('getUserName() returns a name when security is enabled', async () => { securityPluginSetup: securityPluginSetup as any, }); factory.create(KibanaRequest.from(fakeRequest), fakeRequest); - const constructorCall = jest.requireMock('../alerts_client').AlertsClient.mock.calls[0][0]; + const constructorCall = jest.requireMock('./alerts_client').AlertsClient.mock.calls[0][0]; securityPluginSetup.authc.getCurrentUser.mockResolvedValueOnce({ username: 'bob' }); const userNameResult = await constructorCall.getUserName(); @@ -94,7 +94,7 @@ test('getUserName() returns a name when security is enabled', async () => { test('createAPIKey() returns { apiKeysEnabled: false } when security is disabled', async () => { const factory = new AlertsClientFactory(alertsClientFactoryParams); factory.create(KibanaRequest.from(fakeRequest), fakeRequest); - const constructorCall = jest.requireMock('../alerts_client').AlertsClient.mock.calls[0][0]; + const constructorCall = jest.requireMock('./alerts_client').AlertsClient.mock.calls[0][0]; const createAPIKeyResult = await constructorCall.createAPIKey(); expect(createAPIKeyResult).toEqual({ apiKeysEnabled: false }); @@ -103,7 +103,7 @@ test('createAPIKey() returns { apiKeysEnabled: false } when security is disabled test('createAPIKey() returns { apiKeysEnabled: false } when security is enabled but ES security is disabled', async () => { const factory = new AlertsClientFactory(alertsClientFactoryParams); factory.create(KibanaRequest.from(fakeRequest), fakeRequest); - const constructorCall = jest.requireMock('../alerts_client').AlertsClient.mock.calls[0][0]; + const constructorCall = jest.requireMock('./alerts_client').AlertsClient.mock.calls[0][0]; securityPluginSetup.authc.createAPIKey.mockResolvedValueOnce(null); const createAPIKeyResult = await constructorCall.createAPIKey(); @@ -116,7 +116,7 @@ test('createAPIKey() returns an API key when security is enabled', async () => { securityPluginSetup: securityPluginSetup as any, }); factory.create(KibanaRequest.from(fakeRequest), fakeRequest); - const constructorCall = jest.requireMock('../alerts_client').AlertsClient.mock.calls[0][0]; + const constructorCall = jest.requireMock('./alerts_client').AlertsClient.mock.calls[0][0]; securityPluginSetup.authc.createAPIKey.mockResolvedValueOnce({ api_key: '123', id: 'abc' }); const createAPIKeyResult = await constructorCall.createAPIKey(); @@ -132,7 +132,7 @@ test('createAPIKey() throws when security plugin createAPIKey throws an error', securityPluginSetup: securityPluginSetup as any, }); factory.create(KibanaRequest.from(fakeRequest), fakeRequest); - const constructorCall = jest.requireMock('../alerts_client').AlertsClient.mock.calls[0][0]; + const constructorCall = jest.requireMock('./alerts_client').AlertsClient.mock.calls[0][0]; securityPluginSetup.authc.createAPIKey.mockRejectedValueOnce(new Error('TLS disabled')); await expect(constructorCall.createAPIKey()).rejects.toThrowErrorMatchingInlineSnapshot( diff --git a/x-pack/legacy/plugins/alerting/server/lib/alerts_client_factory.ts b/x-pack/legacy/plugins/alerting/server/alerts_client_factory.ts similarity index 92% rename from x-pack/legacy/plugins/alerting/server/lib/alerts_client_factory.ts rename to x-pack/legacy/plugins/alerting/server/alerts_client_factory.ts index 026d6c92b0d75..94a396fbaa806 100644 --- a/x-pack/legacy/plugins/alerting/server/lib/alerts_client_factory.ts +++ b/x-pack/legacy/plugins/alerting/server/alerts_client_factory.ts @@ -6,12 +6,12 @@ import Hapi from 'hapi'; import uuid from 'uuid'; -import { AlertsClient } from '../alerts_client'; -import { AlertTypeRegistry, SpaceIdToNamespaceFunction } from '../types'; -import { SecurityPluginStartContract, TaskManagerStartContract } from '../shim'; -import { KibanaRequest, Logger } from '../../../../../../src/core/server'; -import { InvalidateAPIKeyParams } from '../../../../../plugins/security/server'; -import { PluginStartContract as EncryptedSavedObjectsStartContract } from '../../../../../plugins/encrypted_saved_objects/server'; +import { AlertsClient } from './alerts_client'; +import { AlertTypeRegistry, SpaceIdToNamespaceFunction } from './types'; +import { SecurityPluginStartContract, TaskManagerStartContract } from './shim'; +import { KibanaRequest, Logger } from '../../../../../src/core/server'; +import { InvalidateAPIKeyParams } from '../../../../plugins/security/server'; +import { PluginStartContract as EncryptedSavedObjectsStartContract } from '../../../../plugins/encrypted_saved_objects/server'; export interface ConstructorOpts { logger: Logger; diff --git a/x-pack/legacy/plugins/alerting/server/lib/index.ts b/x-pack/legacy/plugins/alerting/server/lib/index.ts index ca4ddf9e11ad2..c41ea4a5998ff 100644 --- a/x-pack/legacy/plugins/alerting/server/lib/index.ts +++ b/x-pack/legacy/plugins/alerting/server/lib/index.ts @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export { AlertInstance } from './alert_instance'; -export { validateAlertTypeParams } from './validate_alert_type_params'; export { parseDuration, getDurationSchema } from './parse_duration'; -export { AlertsClientFactory } from './alerts_client_factory'; -export { TaskRunnerFactory } from './task_runner_factory'; +export { LicenseState } from './license_state'; +export { validateAlertTypeParams } from './validate_alert_type_params'; diff --git a/x-pack/legacy/plugins/alerting/server/lib/result_type.ts b/x-pack/legacy/plugins/alerting/server/lib/result_type.ts new file mode 100644 index 0000000000000..644ae51292249 --- /dev/null +++ b/x-pack/legacy/plugins/alerting/server/lib/result_type.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface Ok { + tag: 'ok'; + value: T; +} + +export interface Err { + tag: 'err'; + error: E; +} +export type Result = Ok | Err; + +export function asOk(value: T): Ok { + return { + tag: 'ok', + value, + }; +} + +export function asErr(error: T): Err { + return { + tag: 'err', + error, + }; +} + +export function isOk(result: Result): result is Ok { + return result.tag === 'ok'; +} + +export function isErr(result: Result): result is Err { + return !isOk(result); +} + +export async function promiseResult(future: Promise): Promise> { + try { + return asOk(await future); + } catch (e) { + return asErr(e); + } +} + +export function map( + result: Result, + onOk: (value: T) => Resolution, + onErr: (error: E) => Resolution +): Resolution { + return isOk(result) ? onOk(result.value) : onErr(result.error); +} diff --git a/x-pack/legacy/plugins/alerting/server/lib/task_runner_factory.test.ts b/x-pack/legacy/plugins/alerting/server/lib/task_runner_factory.test.ts deleted file mode 100644 index fd13452e04535..0000000000000 --- a/x-pack/legacy/plugins/alerting/server/lib/task_runner_factory.test.ts +++ /dev/null @@ -1,345 +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 sinon from 'sinon'; -import { schema } from '@kbn/config-schema'; -import { AlertExecutorOptions } from '../types'; -import { ConcreteTaskInstance, TaskStatus } from '../../../task_manager/server'; -import { TaskRunnerContext, TaskRunnerFactory } from './task_runner_factory'; -import { encryptedSavedObjectsMock } from '../../../../../plugins/encrypted_saved_objects/server/mocks'; -import { - savedObjectsClientMock, - loggingServiceMock, -} from '../../../../../../src/core/server/mocks'; - -const alertType = { - id: 'test', - name: 'My test alert', - actionGroups: ['default'], - executor: jest.fn(), -}; -let fakeTimer: sinon.SinonFakeTimers; -let taskRunnerFactory: TaskRunnerFactory; -let mockedTaskInstance: ConcreteTaskInstance; - -beforeAll(() => { - fakeTimer = sinon.useFakeTimers(); - mockedTaskInstance = { - id: '', - attempts: 0, - status: TaskStatus.Running, - version: '123', - runAt: new Date(), - scheduledAt: new Date(), - startedAt: new Date(), - retryAt: new Date(Date.now() + 5 * 60 * 1000), - state: { - startedAt: new Date(Date.now() - 5 * 60 * 1000), - }, - taskType: 'alerting:test', - params: { - alertId: '1', - }, - ownerId: null, - }; - taskRunnerFactory = new TaskRunnerFactory(); - taskRunnerFactory.initialize(taskRunnerFactoryInitializerParams); -}); - -afterAll(() => fakeTimer.restore()); - -const savedObjectsClient = savedObjectsClientMock.create(); -const encryptedSavedObjectsPlugin = encryptedSavedObjectsMock.createStart(); -const services = { - log: jest.fn(), - callCluster: jest.fn(), - savedObjectsClient, -}; - -const taskRunnerFactoryInitializerParams: jest.Mocked = { - getServices: jest.fn().mockReturnValue(services), - executeAction: jest.fn(), - encryptedSavedObjectsPlugin, - logger: loggingServiceMock.create().get(), - spaceIdToNamespace: jest.fn().mockReturnValue(undefined), - getBasePath: jest.fn().mockReturnValue(undefined), -}; - -const mockedAlertTypeSavedObject = { - id: '1', - type: 'alert', - attributes: { - enabled: true, - alertTypeId: '123', - schedule: { interval: '10s' }, - mutedInstanceIds: [], - params: { - bar: true, - }, - actions: [ - { - group: 'default', - actionRef: 'action_0', - params: { - foo: true, - }, - }, - ], - }, - references: [ - { - name: 'action_0', - type: 'action', - id: '1', - }, - ], -}; - -beforeEach(() => { - jest.resetAllMocks(); - taskRunnerFactoryInitializerParams.getServices.mockReturnValue(services); -}); - -test(`throws an error if factory isn't initialized`, () => { - const factory = new TaskRunnerFactory(); - expect(() => - factory.create(alertType, { taskInstance: mockedTaskInstance }) - ).toThrowErrorMatchingInlineSnapshot(`"TaskRunnerFactory not initialized"`); -}); - -test(`throws an error if factory is already initialized`, () => { - const factory = new TaskRunnerFactory(); - factory.initialize(taskRunnerFactoryInitializerParams); - expect(() => - factory.initialize(taskRunnerFactoryInitializerParams) - ).toThrowErrorMatchingInlineSnapshot(`"TaskRunnerFactory already initialized"`); -}); - -test('successfully executes the task', async () => { - const taskRunner = taskRunnerFactory.create(alertType, { - taskInstance: mockedTaskInstance, - }); - savedObjectsClient.get.mockResolvedValueOnce(mockedAlertTypeSavedObject); - encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ - id: '1', - type: 'alert', - attributes: { - apiKey: Buffer.from('123:abc').toString('base64'), - }, - references: [], - }); - const runnerResult = await taskRunner.run(); - expect(runnerResult).toMatchInlineSnapshot(` - Object { - "runAt": 1970-01-01T00:00:10.000Z, - "state": Object { - "alertInstances": Object {}, - "alertTypeState": undefined, - "previousStartedAt": 1970-01-01T00:00:00.000Z, - }, - } - `); - expect(alertType.executor).toHaveBeenCalledTimes(1); - const call = alertType.executor.mock.calls[0][0]; - expect(call.params).toMatchInlineSnapshot(` - Object { - "bar": true, - } - `); - expect(call.startedAt).toMatchInlineSnapshot(`1970-01-01T00:00:00.000Z`); - expect(call.state).toMatchInlineSnapshot(`Object {}`); - expect(call.services.alertInstanceFactory).toBeTruthy(); - expect(call.services.callCluster).toBeTruthy(); - expect(call.services).toBeTruthy(); -}); - -test('executeAction is called per alert instance that is scheduled', async () => { - alertType.executor.mockImplementation(({ services: executorServices }: AlertExecutorOptions) => { - executorServices.alertInstanceFactory('1').scheduleActions('default'); - }); - const taskRunner = taskRunnerFactory.create(alertType, { - taskInstance: mockedTaskInstance, - }); - savedObjectsClient.get.mockResolvedValueOnce(mockedAlertTypeSavedObject); - encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ - id: '1', - type: 'alert', - attributes: { - apiKey: Buffer.from('123:abc').toString('base64'), - }, - references: [], - }); - await taskRunner.run(); - expect(taskRunnerFactoryInitializerParams.executeAction).toHaveBeenCalledTimes(1); - expect(taskRunnerFactoryInitializerParams.executeAction.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - Object { - "apiKey": "MTIzOmFiYw==", - "id": "1", - "params": Object { - "foo": true, - }, - "spaceId": undefined, - }, - ] - `); -}); - -test('persists alertInstances passed in from state, only if they are scheduled for execution', async () => { - alertType.executor.mockImplementation(({ services: executorServices }: AlertExecutorOptions) => { - executorServices.alertInstanceFactory('1').scheduleActions('default'); - }); - const taskRunner = taskRunnerFactory.create(alertType, { - taskInstance: { - ...mockedTaskInstance, - state: { - ...mockedTaskInstance.state, - alertInstances: { - '1': { meta: {}, state: { bar: false } }, - '2': { meta: {}, state: { bar: false } }, - }, - }, - }, - }); - savedObjectsClient.get.mockResolvedValueOnce(mockedAlertTypeSavedObject); - encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ - id: '1', - type: 'alert', - attributes: { - apiKey: Buffer.from('123:abc').toString('base64'), - }, - references: [], - }); - const runnerResult = await taskRunner.run(); - expect(runnerResult.state.alertInstances).toMatchInlineSnapshot(` - Object { - "1": Object { - "meta": Object { - "lastScheduledActions": Object { - "date": 1970-01-01T00:00:00.000Z, - "group": "default", - }, - }, - "state": Object { - "bar": false, - }, - }, - } - `); -}); - -test('validates params before executing the alert type', async () => { - const taskRunner = taskRunnerFactory.create( - { - ...alertType, - validate: { - params: schema.object({ - param1: schema.string(), - }), - }, - }, - { taskInstance: mockedTaskInstance } - ); - savedObjectsClient.get.mockResolvedValueOnce(mockedAlertTypeSavedObject); - encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ - id: '1', - type: 'alert', - attributes: { - apiKey: Buffer.from('123:abc').toString('base64'), - }, - references: [], - }); - await expect(taskRunner.run()).rejects.toThrowErrorMatchingInlineSnapshot( - `"params invalid: [param1]: expected value of type [string] but got [undefined]"` - ); -}); - -test('throws error if reference not found', async () => { - const taskRunner = taskRunnerFactory.create(alertType, { - taskInstance: mockedTaskInstance, - }); - savedObjectsClient.get.mockResolvedValueOnce({ - ...mockedAlertTypeSavedObject, - references: [], - }); - encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ - id: '1', - type: 'alert', - attributes: { - apiKey: Buffer.from('123:abc').toString('base64'), - }, - references: [], - }); - await expect(taskRunner.run()).rejects.toThrowErrorMatchingInlineSnapshot( - `"Action reference \\"action_0\\" not found in alert id: 1"` - ); -}); - -test('uses API key when provided', async () => { - const taskRunner = taskRunnerFactory.create(alertType, { - taskInstance: mockedTaskInstance, - }); - savedObjectsClient.get.mockResolvedValueOnce(mockedAlertTypeSavedObject); - encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ - id: '1', - type: 'alert', - attributes: { - apiKey: Buffer.from('123:abc').toString('base64'), - }, - references: [], - }); - - await taskRunner.run(); - expect(taskRunnerFactoryInitializerParams.getServices).toHaveBeenCalledWith({ - getBasePath: expect.anything(), - headers: { - // base64 encoded "123:abc" - authorization: 'ApiKey MTIzOmFiYw==', - }, - path: '/', - route: { settings: {} }, - url: { - href: '/', - }, - raw: { - req: { - url: '/', - }, - }, - }); -}); - -test(`doesn't use API key when not provided`, async () => { - const factory = new TaskRunnerFactory(); - factory.initialize(taskRunnerFactoryInitializerParams); - const taskRunner = factory.create(alertType, { - taskInstance: mockedTaskInstance, - }); - savedObjectsClient.get.mockResolvedValueOnce(mockedAlertTypeSavedObject); - encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ - id: '1', - type: 'alert', - attributes: {}, - references: [], - }); - - await taskRunner.run(); - - expect(taskRunnerFactoryInitializerParams.getServices).toHaveBeenCalledWith({ - getBasePath: expect.anything(), - headers: {}, - path: '/', - route: { settings: {} }, - url: { - href: '/', - }, - raw: { - req: { - url: '/', - }, - }, - }); -}); diff --git a/x-pack/legacy/plugins/alerting/server/lib/task_runner_factory.ts b/x-pack/legacy/plugins/alerting/server/lib/task_runner_factory.ts deleted file mode 100644 index 5614188795ded..0000000000000 --- a/x-pack/legacy/plugins/alerting/server/lib/task_runner_factory.ts +++ /dev/null @@ -1,190 +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 { Logger } from '../../../../../../src/core/server'; -import { RunContext } from '../../../task_manager/server'; -import { createExecutionHandler } from './create_execution_handler'; -import { createAlertInstanceFactory } from './create_alert_instance_factory'; -import { AlertInstance } from './alert_instance'; -import { getNextRunAt } from './get_next_run_at'; -import { validateAlertTypeParams } from './validate_alert_type_params'; -import { PluginStartContract as EncryptedSavedObjectsStartContract } from '../../../../../plugins/encrypted_saved_objects/server'; -import { PluginStartContract as ActionsPluginStartContract } from '../../../actions'; -import { - AlertType, - AlertServices, - GetBasePathFunction, - GetServicesFunction, - RawAlert, - SpaceIdToNamespaceFunction, - IntervalSchedule, -} from '../types'; - -export interface TaskRunnerContext { - logger: Logger; - getServices: GetServicesFunction; - executeAction: ActionsPluginStartContract['execute']; - encryptedSavedObjectsPlugin: EncryptedSavedObjectsStartContract; - spaceIdToNamespace: SpaceIdToNamespaceFunction; - getBasePath: GetBasePathFunction; -} - -export class TaskRunnerFactory { - private isInitialized = false; - private taskRunnerContext?: TaskRunnerContext; - - public initialize(taskRunnerContext: TaskRunnerContext) { - if (this.isInitialized) { - throw new Error('TaskRunnerFactory already initialized'); - } - this.isInitialized = true; - this.taskRunnerContext = taskRunnerContext; - } - - public create(alertType: AlertType, { taskInstance }: RunContext) { - if (!this.isInitialized) { - throw new Error('TaskRunnerFactory not initialized'); - } - - const { - logger, - getServices, - executeAction, - encryptedSavedObjectsPlugin, - spaceIdToNamespace, - getBasePath, - } = this.taskRunnerContext!; - - return { - async run() { - const { alertId, spaceId } = taskInstance.params; - const requestHeaders: Record = {}; - const namespace = spaceIdToNamespace(spaceId); - // Only fetch encrypted attributes here, we'll create a saved objects client - // scoped with the API key to fetch the remaining data. - const { - attributes: { apiKey }, - } = await encryptedSavedObjectsPlugin.getDecryptedAsInternalUser( - 'alert', - alertId, - { namespace } - ); - - if (apiKey) { - requestHeaders.authorization = `ApiKey ${apiKey}`; - } - - const fakeRequest = { - headers: requestHeaders, - getBasePath: () => getBasePath(spaceId), - path: '/', - route: { settings: {} }, - url: { - href: '/', - }, - raw: { - req: { - url: '/', - }, - }, - }; - - const services = getServices(fakeRequest); - // Ensure API key is still valid and user has access - const { - attributes: { params, actions, schedule, throttle, muteAll, mutedInstanceIds }, - references, - } = await services.savedObjectsClient.get('alert', alertId); - - // Validate - const validatedAlertTypeParams = validateAlertTypeParams(alertType, params); - - // Inject ids into actions - const actionsWithIds = actions.map(action => { - const actionReference = references.find(obj => obj.name === action.actionRef); - if (!actionReference) { - throw new Error( - `Action reference "${action.actionRef}" not found in alert id: ${alertId}` - ); - } - return { - ...action, - id: actionReference.id, - }; - }); - - const executionHandler = createExecutionHandler({ - alertId, - logger, - executeAction, - apiKey, - actions: actionsWithIds, - spaceId, - alertType, - }); - const alertInstances: Record = {}; - const alertInstancesData = taskInstance.state.alertInstances || {}; - for (const id of Object.keys(alertInstancesData)) { - alertInstances[id] = new AlertInstance(alertInstancesData[id]); - } - const alertInstanceFactory = createAlertInstanceFactory(alertInstances); - - const alertTypeServices: AlertServices = { - ...services, - alertInstanceFactory, - }; - - const alertTypeState = await alertType.executor({ - alertId, - services: alertTypeServices, - params: validatedAlertTypeParams, - state: taskInstance.state.alertTypeState || {}, - startedAt: taskInstance.startedAt!, - previousStartedAt: taskInstance.state.previousStartedAt, - }); - - await Promise.all( - Object.keys(alertInstances).map(alertInstanceId => { - const alertInstance = alertInstances[alertInstanceId]; - if (alertInstance.hasScheduledActions()) { - if ( - alertInstance.isThrottled(throttle) || - muteAll || - mutedInstanceIds.includes(alertInstanceId) - ) { - return; - } - const { actionGroup, context, state } = alertInstance.getScheduledActionOptions()!; - alertInstance.updateLastScheduledActions(actionGroup); - alertInstance.unscheduleActions(); - return executionHandler({ actionGroup, context, state, alertInstanceId }); - } else { - // Cleanup alert instances that are no longer scheduling actions to avoid over populating the alertInstances object - delete alertInstances[alertInstanceId]; - } - }) - ); - - const nextRunAt = getNextRunAt( - new Date(taskInstance.startedAt!), - // we do not currently have a good way of returning the type - // from SavedObjectsClient, and as we currenrtly require a schedule - // and we only support `interval`, we can cast this safely - schedule as IntervalSchedule - ); - - return { - state: { - alertTypeState, - alertInstances, - previousStartedAt: taskInstance.startedAt!, - }, - runAt: nextRunAt, - }; - }, - }; - } -} diff --git a/x-pack/legacy/plugins/alerting/server/plugin.ts b/x-pack/legacy/plugins/alerting/server/plugin.ts index 24d4467dbd807..ede95f76bf811 100644 --- a/x-pack/legacy/plugins/alerting/server/plugin.ts +++ b/x-pack/legacy/plugins/alerting/server/plugin.ts @@ -9,7 +9,8 @@ import { first } from 'rxjs/operators'; import { Services } from './types'; import { AlertsClient } from './alerts_client'; import { AlertTypeRegistry } from './alert_type_registry'; -import { AlertsClientFactory, TaskRunnerFactory } from './lib'; +import { TaskRunnerFactory } from './task_runner'; +import { AlertsClientFactory } from './alerts_client_factory'; import { LicenseState } from './lib/license_state'; import { IClusterClient, KibanaRequest, Logger } from '../../../../../src/core/server'; import { diff --git a/x-pack/legacy/plugins/alerting/server/lib/create_execution_handler.test.ts b/x-pack/legacy/plugins/alerting/server/task_runner/create_execution_handler.test.ts similarity index 100% rename from x-pack/legacy/plugins/alerting/server/lib/create_execution_handler.test.ts rename to x-pack/legacy/plugins/alerting/server/task_runner/create_execution_handler.test.ts diff --git a/x-pack/legacy/plugins/alerting/server/lib/create_execution_handler.ts b/x-pack/legacy/plugins/alerting/server/task_runner/create_execution_handler.ts similarity index 100% rename from x-pack/legacy/plugins/alerting/server/lib/create_execution_handler.ts rename to x-pack/legacy/plugins/alerting/server/task_runner/create_execution_handler.ts diff --git a/x-pack/legacy/plugins/alerting/server/lib/get_next_run_at.test.ts b/x-pack/legacy/plugins/alerting/server/task_runner/get_next_run_at.test.ts similarity index 100% rename from x-pack/legacy/plugins/alerting/server/lib/get_next_run_at.test.ts rename to x-pack/legacy/plugins/alerting/server/task_runner/get_next_run_at.test.ts diff --git a/x-pack/legacy/plugins/alerting/server/lib/get_next_run_at.ts b/x-pack/legacy/plugins/alerting/server/task_runner/get_next_run_at.ts similarity index 92% rename from x-pack/legacy/plugins/alerting/server/lib/get_next_run_at.ts rename to x-pack/legacy/plugins/alerting/server/task_runner/get_next_run_at.ts index f9867b5372908..cea4584e1f713 100644 --- a/x-pack/legacy/plugins/alerting/server/lib/get_next_run_at.ts +++ b/x-pack/legacy/plugins/alerting/server/task_runner/get_next_run_at.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { parseDuration } from './parse_duration'; +import { parseDuration } from '../lib'; import { IntervalSchedule } from '../types'; export function getNextRunAt(currentRunAt: Date, schedule: IntervalSchedule) { diff --git a/x-pack/legacy/plugins/alerting/server/task_runner/index.ts b/x-pack/legacy/plugins/alerting/server/task_runner/index.ts new file mode 100644 index 0000000000000..f5401fbd9cd74 --- /dev/null +++ b/x-pack/legacy/plugins/alerting/server/task_runner/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 { TaskRunnerFactory } from './task_runner_factory'; diff --git a/x-pack/legacy/plugins/alerting/server/task_runner/task_runner.test.ts b/x-pack/legacy/plugins/alerting/server/task_runner/task_runner.test.ts new file mode 100644 index 0000000000000..10627c655eca8 --- /dev/null +++ b/x-pack/legacy/plugins/alerting/server/task_runner/task_runner.test.ts @@ -0,0 +1,400 @@ +/* + * 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 sinon from 'sinon'; +import { schema } from '@kbn/config-schema'; +import { AlertExecutorOptions } from '../types'; +import { ConcreteTaskInstance, TaskStatus } from '../../../task_manager'; +import { TaskRunnerContext } from './task_runner_factory'; +import { TaskRunner } from './task_runner'; +import { encryptedSavedObjectsMock } from '../../../../../plugins/encrypted_saved_objects/server/mocks'; +import { + savedObjectsClientMock, + loggingServiceMock, +} from '../../../../../../src/core/server/mocks'; + +const alertType = { + id: 'test', + name: 'My test alert', + actionGroups: ['default'], + executor: jest.fn(), +}; +let fakeTimer: sinon.SinonFakeTimers; + +describe('Task Runner', () => { + let mockedTaskInstance: ConcreteTaskInstance; + + beforeAll(() => { + fakeTimer = sinon.useFakeTimers(); + mockedTaskInstance = { + id: '', + attempts: 0, + status: TaskStatus.Running, + version: '123', + runAt: new Date(), + scheduledAt: new Date(), + startedAt: new Date(), + retryAt: new Date(Date.now() + 5 * 60 * 1000), + state: { + startedAt: new Date(Date.now() - 5 * 60 * 1000), + }, + taskType: 'alerting:test', + params: { + alertId: '1', + }, + ownerId: null, + }; + }); + + afterAll(() => fakeTimer.restore()); + + const savedObjectsClient = savedObjectsClientMock.create(); + const encryptedSavedObjectsPlugin = encryptedSavedObjectsMock.createStart(); + const services = { + log: jest.fn(), + callCluster: jest.fn(), + savedObjectsClient, + }; + + const taskRunnerFactoryInitializerParams: jest.Mocked = { + getServices: jest.fn().mockReturnValue(services), + executeAction: jest.fn(), + encryptedSavedObjectsPlugin, + logger: loggingServiceMock.create().get(), + spaceIdToNamespace: jest.fn().mockReturnValue(undefined), + getBasePath: jest.fn().mockReturnValue(undefined), + }; + + const mockedAlertTypeSavedObject = { + id: '1', + type: 'alert', + attributes: { + enabled: true, + alertTypeId: '123', + schedule: { interval: '10s' }, + mutedInstanceIds: [], + params: { + bar: true, + }, + actions: [ + { + group: 'default', + actionRef: 'action_0', + params: { + foo: true, + }, + }, + ], + }, + references: [ + { + name: 'action_0', + type: 'action', + id: '1', + }, + ], + }; + + beforeEach(() => { + jest.resetAllMocks(); + taskRunnerFactoryInitializerParams.getServices.mockReturnValue(services); + }); + + test('successfully executes the task', async () => { + const taskRunner = new TaskRunner( + alertType, + mockedTaskInstance, + taskRunnerFactoryInitializerParams + ); + savedObjectsClient.get.mockResolvedValueOnce(mockedAlertTypeSavedObject); + encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + apiKey: Buffer.from('123:abc').toString('base64'), + }, + references: [], + }); + const runnerResult = await taskRunner.run(); + expect(runnerResult).toMatchInlineSnapshot(` + Object { + "runAt": 1970-01-01T00:00:10.000Z, + "state": Object { + "alertInstances": Object {}, + "alertTypeState": undefined, + "previousStartedAt": 1970-01-01T00:00:00.000Z, + }, + } + `); + expect(alertType.executor).toHaveBeenCalledTimes(1); + const call = alertType.executor.mock.calls[0][0]; + expect(call.params).toMatchInlineSnapshot(` + Object { + "bar": true, + } + `); + expect(call.startedAt).toMatchInlineSnapshot(`1970-01-01T00:00:00.000Z`); + expect(call.state).toMatchInlineSnapshot(`Object {}`); + expect(call.services.alertInstanceFactory).toBeTruthy(); + expect(call.services.callCluster).toBeTruthy(); + expect(call.services).toBeTruthy(); + }); + + test('executeAction is called per alert instance that is scheduled', async () => { + alertType.executor.mockImplementation( + ({ services: executorServices }: AlertExecutorOptions) => { + executorServices.alertInstanceFactory('1').scheduleActions('default'); + } + ); + const taskRunner = new TaskRunner( + alertType, + mockedTaskInstance, + taskRunnerFactoryInitializerParams + ); + savedObjectsClient.get.mockResolvedValueOnce(mockedAlertTypeSavedObject); + encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + apiKey: Buffer.from('123:abc').toString('base64'), + }, + references: [], + }); + await taskRunner.run(); + expect(taskRunnerFactoryInitializerParams.executeAction).toHaveBeenCalledTimes(1); + expect(taskRunnerFactoryInitializerParams.executeAction.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "apiKey": "MTIzOmFiYw==", + "id": "1", + "params": Object { + "foo": true, + }, + "spaceId": undefined, + }, + ] + `); + }); + + test('persists alertInstances passed in from state, only if they are scheduled for execution', async () => { + alertType.executor.mockImplementation( + ({ services: executorServices }: AlertExecutorOptions) => { + executorServices.alertInstanceFactory('1').scheduleActions('default'); + } + ); + const taskRunner = new TaskRunner( + alertType, + { + ...mockedTaskInstance, + state: { + ...mockedTaskInstance.state, + alertInstances: { + '1': { meta: {}, state: { bar: false } }, + '2': { meta: {}, state: { bar: false } }, + }, + }, + }, + taskRunnerFactoryInitializerParams + ); + savedObjectsClient.get.mockResolvedValueOnce(mockedAlertTypeSavedObject); + encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + apiKey: Buffer.from('123:abc').toString('base64'), + }, + references: [], + }); + const runnerResult = await taskRunner.run(); + expect(runnerResult.state.alertInstances).toMatchInlineSnapshot(` + Object { + "1": Object { + "meta": Object { + "lastScheduledActions": Object { + "date": 1970-01-01T00:00:00.000Z, + "group": "default", + }, + }, + "state": Object { + "bar": false, + }, + }, + } + `); + }); + + test('validates params before executing the alert type', async () => { + const taskRunner = new TaskRunner( + { + ...alertType, + validate: { + params: schema.object({ + param1: schema.string(), + }), + }, + }, + mockedTaskInstance, + taskRunnerFactoryInitializerParams + ); + savedObjectsClient.get.mockResolvedValueOnce(mockedAlertTypeSavedObject); + encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + apiKey: Buffer.from('123:abc').toString('base64'), + }, + references: [], + }); + expect(await taskRunner.run()).toMatchInlineSnapshot(` + Object { + "runAt": 1970-01-01T00:00:10.000Z, + "state": Object { + "previousStartedAt": 1970-01-01T00:00:00.000Z, + "startedAt": 1969-12-31T23:55:00.000Z, + }, + } + `); + expect(taskRunnerFactoryInitializerParams.logger.error).toHaveBeenCalledWith( + `Executing Alert \"1\" has resulted in Error: params invalid: [param1]: expected value of type [string] but got [undefined]` + ); + }); + + test('throws error if reference not found', async () => { + const taskRunner = new TaskRunner( + alertType, + mockedTaskInstance, + taskRunnerFactoryInitializerParams + ); + savedObjectsClient.get.mockResolvedValueOnce({ + ...mockedAlertTypeSavedObject, + references: [], + }); + encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + apiKey: Buffer.from('123:abc').toString('base64'), + }, + references: [], + }); + expect(await taskRunner.run()).toMatchInlineSnapshot(` + Object { + "runAt": 1970-01-01T00:00:10.000Z, + "state": Object { + "previousStartedAt": 1970-01-01T00:00:00.000Z, + "startedAt": 1969-12-31T23:55:00.000Z, + }, + } + `); + expect(taskRunnerFactoryInitializerParams.logger.error).toHaveBeenCalledWith( + `Executing Alert \"1\" has resulted in Error: Action reference \"action_0\" not found in alert id: 1` + ); + }); + + test('uses API key when provided', async () => { + const taskRunner = new TaskRunner( + alertType, + mockedTaskInstance, + taskRunnerFactoryInitializerParams + ); + savedObjectsClient.get.mockResolvedValueOnce(mockedAlertTypeSavedObject); + encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + apiKey: Buffer.from('123:abc').toString('base64'), + }, + references: [], + }); + + await taskRunner.run(); + expect(taskRunnerFactoryInitializerParams.getServices).toHaveBeenCalledWith({ + getBasePath: expect.anything(), + headers: { + // base64 encoded "123:abc" + authorization: 'ApiKey MTIzOmFiYw==', + }, + path: '/', + route: { settings: {} }, + url: { + href: '/', + }, + raw: { + req: { + url: '/', + }, + }, + }); + }); + + test(`doesn't use API key when not provided`, async () => { + const taskRunner = new TaskRunner( + alertType, + mockedTaskInstance, + taskRunnerFactoryInitializerParams + ); + savedObjectsClient.get.mockResolvedValueOnce(mockedAlertTypeSavedObject); + encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: {}, + references: [], + }); + + await taskRunner.run(); + + expect(taskRunnerFactoryInitializerParams.getServices).toHaveBeenCalledWith({ + getBasePath: expect.anything(), + headers: {}, + path: '/', + route: { settings: {} }, + url: { + href: '/', + }, + raw: { + req: { + url: '/', + }, + }, + }); + }); + + test('recovers gracefully when the AlertType executor throws an exception', async () => { + alertType.executor.mockImplementation( + ({ services: executorServices }: AlertExecutorOptions) => { + throw new Error('OMG'); + } + ); + + const taskRunner = new TaskRunner( + alertType, + mockedTaskInstance, + taskRunnerFactoryInitializerParams + ); + + savedObjectsClient.get.mockResolvedValueOnce(mockedAlertTypeSavedObject); + encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + apiKey: Buffer.from('123:abc').toString('base64'), + }, + references: [], + }); + + const runnerResult = await taskRunner.run(); + + expect(runnerResult).toMatchInlineSnapshot(` + Object { + "runAt": 1970-01-01T00:00:10.000Z, + "state": Object { + "previousStartedAt": 1970-01-01T00:00:00.000Z, + "startedAt": 1969-12-31T23:55:00.000Z, + }, + } + `); + }); +}); diff --git a/x-pack/legacy/plugins/alerting/server/task_runner/task_runner.ts b/x-pack/legacy/plugins/alerting/server/task_runner/task_runner.ts new file mode 100644 index 0000000000000..2347e9e608ed9 --- /dev/null +++ b/x-pack/legacy/plugins/alerting/server/task_runner/task_runner.ts @@ -0,0 +1,241 @@ +/* + * 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 { pick, mapValues, omit } from 'lodash'; +import { Logger } from '../../../../../../src/core/server'; +import { SavedObject } from '../../../../../../src/core/server'; +import { TaskRunnerContext } from './task_runner_factory'; +import { ConcreteTaskInstance } from '../../../task_manager'; +import { createExecutionHandler } from './create_execution_handler'; +import { AlertInstance, createAlertInstanceFactory } from '../alert_instance'; +import { getNextRunAt } from './get_next_run_at'; +import { validateAlertTypeParams } from '../lib'; +import { AlertType, RawAlert, IntervalSchedule, Services, State } from '../types'; +import { promiseResult, map } from '../lib/result_type'; + +type AlertInstances = Record; + +export class TaskRunner { + private context: TaskRunnerContext; + private logger: Logger; + private taskInstance: ConcreteTaskInstance; + private alertType: AlertType; + + constructor( + alertType: AlertType, + taskInstance: ConcreteTaskInstance, + context: TaskRunnerContext + ) { + this.context = context; + this.logger = context.logger; + this.alertType = alertType; + this.taskInstance = taskInstance; + } + + async getApiKeyForAlertPermissions(alertId: string, spaceId: string) { + const namespace = this.context.spaceIdToNamespace(spaceId); + // Only fetch encrypted attributes here, we'll create a saved objects client + // scoped with the API key to fetch the remaining data. + const { + attributes: { apiKey }, + } = await this.context.encryptedSavedObjectsPlugin.getDecryptedAsInternalUser( + 'alert', + alertId, + { namespace } + ); + + return apiKey; + } + + async getServicesWithSpaceLevelPermissions(spaceId: string, apiKey: string | null) { + const requestHeaders: Record = {}; + + if (apiKey) { + requestHeaders.authorization = `ApiKey ${apiKey}`; + } + + const fakeRequest = { + headers: requestHeaders, + getBasePath: () => this.context.getBasePath(spaceId), + path: '/', + route: { settings: {} }, + url: { + href: '/', + }, + raw: { + req: { + url: '/', + }, + }, + }; + + return this.context.getServices(fakeRequest); + } + + getExecutionHandler( + alertId: string, + spaceId: string, + apiKey: string | null, + actions: RawAlert['actions'], + references: SavedObject['references'] + ) { + // Inject ids into actions + const actionsWithIds = actions.map(action => { + const actionReference = references.find(obj => obj.name === action.actionRef); + if (!actionReference) { + throw new Error(`Action reference "${action.actionRef}" not found in alert id: ${alertId}`); + } + return { + ...action, + id: actionReference.id, + }; + }); + + return createExecutionHandler({ + alertId, + logger: this.logger, + executeAction: this.context.executeAction, + apiKey, + actions: actionsWithIds, + spaceId, + alertType: this.alertType, + }); + } + + async executeAlertInstance( + alertInstanceId: string, + alertInstance: AlertInstance, + executionHandler: ReturnType + ) { + const { actionGroup, context, state } = alertInstance.getScheduledActionOptions()!; + alertInstance.updateLastScheduledActions(actionGroup); + alertInstance.unscheduleActions(); + return executionHandler({ actionGroup, context, state, alertInstanceId }); + } + + async executeAlertInstances( + services: Services, + { params, throttle, muteAll, mutedInstanceIds }: SavedObject['attributes'], + executionHandler: ReturnType + ): Promise { + const { + params: { alertId }, + state: { alertInstances: alertRawInstances = {}, alertTypeState = {}, previousStartedAt }, + } = this.taskInstance; + + const alertInstances = mapValues( + alertRawInstances, + alert => new AlertInstance(alert) + ); + + const updatedAlertTypeState = await this.alertType.executor({ + alertId, + services: { + ...services, + alertInstanceFactory: createAlertInstanceFactory(alertInstances), + }, + params, + state: alertTypeState, + startedAt: this.taskInstance.startedAt!, + previousStartedAt, + }); + + // Cleanup alert instances that are no longer scheduling actions to avoid over populating the alertInstances object + const instancesWithScheduledActions = pick( + alertInstances, + alertInstance => alertInstance.hasScheduledActions() + ); + + if (!muteAll) { + const enabledAlertInstances = omit( + instancesWithScheduledActions, + ...mutedInstanceIds + ); + + await Promise.all( + Object.entries(enabledAlertInstances) + .filter( + ([, alertInstance]: [string, AlertInstance]) => !alertInstance.isThrottled(throttle) + ) + .map(([id, alertInstance]: [string, AlertInstance]) => + this.executeAlertInstance(id, alertInstance, executionHandler) + ) + ); + } + + return { + alertTypeState: updatedAlertTypeState, + alertInstances: instancesWithScheduledActions, + }; + } + + async validateAndRunAlert( + services: Services, + apiKey: string | null, + attributes: SavedObject['attributes'], + references: SavedObject['references'] + ) { + const { + params: { alertId, spaceId }, + } = this.taskInstance; + + // Validate + const params = validateAlertTypeParams(this.alertType, attributes.params); + const executionHandler = this.getExecutionHandler( + alertId, + spaceId, + apiKey, + attributes.actions, + references + ); + return this.executeAlertInstances(services, { ...attributes, params }, executionHandler); + } + + async run() { + const { + params: { alertId, spaceId }, + startedAt: previousStartedAt, + state: originalState, + } = this.taskInstance; + + const apiKey = await this.getApiKeyForAlertPermissions(alertId, spaceId); + const services = await this.getServicesWithSpaceLevelPermissions(spaceId, apiKey); + + // Ensure API key is still valid and user has access + const { attributes, references } = await services.savedObjectsClient.get( + 'alert', + alertId + ); + + return { + state: map( + await promiseResult( + this.validateAndRunAlert(services, apiKey, attributes, references) + ), + (stateUpdates: State) => { + return { + ...stateUpdates, + previousStartedAt, + }; + }, + (err: Error) => { + this.logger.error(`Executing Alert "${alertId}" has resulted in Error: ${err.message}`); + return { + ...originalState, + previousStartedAt, + }; + } + ), + runAt: getNextRunAt( + new Date(this.taskInstance.startedAt!), + // we do not currently have a good way of returning the type + // from SavedObjectsClient, and as we currenrtly require a schedule + // and we only support `interval`, we can cast this safely + attributes.schedule as IntervalSchedule + ), + }; + } +} diff --git a/x-pack/legacy/plugins/alerting/server/task_runner/task_runner_factory.test.ts b/x-pack/legacy/plugins/alerting/server/task_runner/task_runner_factory.test.ts new file mode 100644 index 0000000000000..2ea1256352bec --- /dev/null +++ b/x-pack/legacy/plugins/alerting/server/task_runner/task_runner_factory.test.ts @@ -0,0 +1,87 @@ +/* + * 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 sinon from 'sinon'; +import { ConcreteTaskInstance, TaskStatus } from '../../../task_manager'; +import { TaskRunnerContext, TaskRunnerFactory } from './task_runner_factory'; +import { encryptedSavedObjectsMock } from '../../../../../plugins/encrypted_saved_objects/server/mocks'; +import { + savedObjectsClientMock, + loggingServiceMock, +} from '../../../../../../src/core/server/mocks'; + +const alertType = { + id: 'test', + name: 'My test alert', + actionGroups: ['default'], + executor: jest.fn(), +}; +let fakeTimer: sinon.SinonFakeTimers; + +describe('Task Runner Factory', () => { + let mockedTaskInstance: ConcreteTaskInstance; + + beforeAll(() => { + fakeTimer = sinon.useFakeTimers(); + mockedTaskInstance = { + id: '', + attempts: 0, + status: TaskStatus.Running, + version: '123', + runAt: new Date(), + scheduledAt: new Date(), + startedAt: new Date(), + retryAt: new Date(Date.now() + 5 * 60 * 1000), + state: { + startedAt: new Date(Date.now() - 5 * 60 * 1000), + }, + taskType: 'alerting:test', + params: { + alertId: '1', + }, + ownerId: null, + }; + }); + + afterAll(() => fakeTimer.restore()); + + const savedObjectsClient = savedObjectsClientMock.create(); + const encryptedSavedObjectsPlugin = encryptedSavedObjectsMock.createStart(); + const services = { + log: jest.fn(), + callCluster: jest.fn(), + savedObjectsClient, + }; + + const taskRunnerFactoryInitializerParams: jest.Mocked = { + getServices: jest.fn().mockReturnValue(services), + executeAction: jest.fn(), + encryptedSavedObjectsPlugin, + logger: loggingServiceMock.create().get(), + spaceIdToNamespace: jest.fn().mockReturnValue(undefined), + getBasePath: jest.fn().mockReturnValue(undefined), + }; + + beforeEach(() => { + jest.resetAllMocks(); + taskRunnerFactoryInitializerParams.getServices.mockReturnValue(services); + }); + + test(`throws an error if factory isn't initialized`, () => { + const factory = new TaskRunnerFactory(); + expect(() => + factory.create(alertType, { taskInstance: mockedTaskInstance }) + ).toThrowErrorMatchingInlineSnapshot(`"TaskRunnerFactory not initialized"`); + }); + + test(`throws an error if factory is already initialized`, () => { + const factory = new TaskRunnerFactory(); + factory.initialize(taskRunnerFactoryInitializerParams); + expect(() => + factory.initialize(taskRunnerFactoryInitializerParams) + ).toThrowErrorMatchingInlineSnapshot(`"TaskRunnerFactory already initialized"`); + }); +}); diff --git a/x-pack/legacy/plugins/alerting/server/task_runner/task_runner_factory.ts b/x-pack/legacy/plugins/alerting/server/task_runner/task_runner_factory.ts new file mode 100644 index 0000000000000..7186e1e729bda --- /dev/null +++ b/x-pack/legacy/plugins/alerting/server/task_runner/task_runner_factory.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { Logger } from '../../../../../../src/core/server'; +import { RunContext } from '../../../task_manager'; +import { PluginStartContract as EncryptedSavedObjectsStartContract } from '../../../../../plugins/encrypted_saved_objects/server'; +import { PluginStartContract as ActionsPluginStartContract } from '../../../actions'; +import { + AlertType, + GetBasePathFunction, + GetServicesFunction, + SpaceIdToNamespaceFunction, +} from '../types'; +import { TaskRunner } from './task_runner'; + +export interface TaskRunnerContext { + logger: Logger; + getServices: GetServicesFunction; + executeAction: ActionsPluginStartContract['execute']; + encryptedSavedObjectsPlugin: EncryptedSavedObjectsStartContract; + spaceIdToNamespace: SpaceIdToNamespaceFunction; + getBasePath: GetBasePathFunction; +} + +export class TaskRunnerFactory { + private isInitialized = false; + private taskRunnerContext?: TaskRunnerContext; + + public initialize(taskRunnerContext: TaskRunnerContext) { + if (this.isInitialized) { + throw new Error('TaskRunnerFactory already initialized'); + } + this.isInitialized = true; + this.taskRunnerContext = taskRunnerContext; + } + + public create(alertType: AlertType, { taskInstance }: RunContext) { + if (!this.isInitialized) { + throw new Error('TaskRunnerFactory not initialized'); + } + + return new TaskRunner(alertType, taskInstance, this.taskRunnerContext!); + } +} diff --git a/x-pack/legacy/plugins/alerting/server/lib/transform_action_params.test.ts b/x-pack/legacy/plugins/alerting/server/task_runner/transform_action_params.test.ts similarity index 100% rename from x-pack/legacy/plugins/alerting/server/lib/transform_action_params.test.ts rename to x-pack/legacy/plugins/alerting/server/task_runner/transform_action_params.test.ts diff --git a/x-pack/legacy/plugins/alerting/server/lib/transform_action_params.ts b/x-pack/legacy/plugins/alerting/server/task_runner/transform_action_params.ts similarity index 100% rename from x-pack/legacy/plugins/alerting/server/lib/transform_action_params.ts rename to x-pack/legacy/plugins/alerting/server/task_runner/transform_action_params.ts diff --git a/x-pack/legacy/plugins/alerting/server/types.ts b/x-pack/legacy/plugins/alerting/server/types.ts index 62dcf07abb7bd..9b03f9b02aa0a 100644 --- a/x-pack/legacy/plugins/alerting/server/types.ts +++ b/x-pack/legacy/plugins/alerting/server/types.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AlertInstance } from './lib'; +import { AlertInstance } from './alert_instance'; import { AlertTypeRegistry as OrigAlertTypeRegistry } from './alert_type_registry'; import { PluginSetupContract, PluginStartContract } from './plugin'; import { SavedObjectAttributes, SavedObjectsClientContract } from '../../../../../src/core/server'; diff --git a/x-pack/test/alerting_api_integration/common/lib/alert_utils.ts b/x-pack/test/alerting_api_integration/common/lib/alert_utils.ts index 487f396d7a3dc..c47649544f9a7 100644 --- a/x-pack/test/alerting_api_integration/common/lib/alert_utils.ts +++ b/x-pack/test/alerting_api_integration/common/lib/alert_utils.ts @@ -17,7 +17,7 @@ export interface AlertUtilsOpts { objectRemover?: ObjectRemover; } -export interface CreateAlwaysFiringActionOpts { +export interface CreateAlertWithActionOpts { indexRecordActionId?: string; objectRemover?: ObjectRemover; overwrites?: Record; @@ -159,7 +159,7 @@ export class AlertUtils { overwrites = {}, indexRecordActionId, reference, - }: CreateAlwaysFiringActionOpts) { + }: CreateAlertWithActionOpts) { const objRemover = objectRemover || this.objectRemover; const actionId = indexRecordActionId || this.indexRecordActionId; @@ -207,4 +207,47 @@ export class AlertUtils { } return response; } + + public async createAlwaysFailingAction({ + objectRemover, + overwrites = {}, + indexRecordActionId, + reference, + }: CreateAlertWithActionOpts) { + const objRemover = objectRemover || this.objectRemover; + const actionId = indexRecordActionId || this.indexRecordActionId; + + if (!objRemover) { + throw new Error('objectRemover is required'); + } + if (!actionId) { + throw new Error('indexRecordActionId is required '); + } + + let request = this.supertestWithoutAuth + .post(`${getUrlPrefix(this.space.id)}/api/alert`) + .set('kbn-xsrf', 'foo'); + if (this.user) { + request = request.auth(this.user.username, this.user.password); + } + const response = await request.send({ + enabled: true, + name: 'fail', + schedule: { interval: '30s' }, + throttle: '30s', + tags: [], + alertTypeId: 'test.failing', + consumer: 'bar', + params: { + index: ES_TEST_INDEX_NAME, + reference, + }, + actions: [], + ...overwrites, + }); + if (response.statusCode === 200) { + objRemover.add(this.space.id, response.body.id, 'alert'); + } + return response; + } } diff --git a/x-pack/test/alerting_api_integration/common/lib/index.ts b/x-pack/test/alerting_api_integration/common/lib/index.ts index a2f21264634f8..c1e59664f9ce2 100644 --- a/x-pack/test/alerting_api_integration/common/lib/index.ts +++ b/x-pack/test/alerting_api_integration/common/lib/index.ts @@ -10,4 +10,5 @@ export { ES_TEST_INDEX_NAME, ESTestIndexTool } from './es_test_index_tool'; export { getTestAlertData } from './get_test_alert_data'; export { AlertUtils } from './alert_utils'; export { TaskManagerUtils } from './task_manager_utils'; +export * from './test_assertions'; export { checkAAD } from './check_aad'; diff --git a/x-pack/test/alerting_api_integration/common/lib/test_assertions.ts b/x-pack/test/alerting_api_integration/common/lib/test_assertions.ts new file mode 100644 index 0000000000000..9495dd4cfae82 --- /dev/null +++ b/x-pack/test/alerting_api_integration/common/lib/test_assertions.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; + +export function ensureDatetimeIsWithinRange( + date: number, + expectedDiff: number, + buffer: number = 10000 +) { + const diff = date - Date.now(); + expect(diff).to.be.greaterThan(expectedDiff - buffer); + expect(diff).to.be.lessThan(expectedDiff + buffer); +} diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts index e89b54b1caa55..2a7e0b2203824 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts @@ -7,7 +7,13 @@ import expect from '@kbn/expect'; import { Response as SupertestResponse } from 'supertest'; import { UserAtSpaceScenarios } from '../../scenarios'; -import { checkAAD, getUrlPrefix, getTestAlertData, ObjectRemover } from '../../../common/lib'; +import { + checkAAD, + getUrlPrefix, + getTestAlertData, + ObjectRemover, + ensureDatetimeIsWithinRange, +} from '../../../common/lib'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export @@ -406,10 +412,3 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { } }); } - -function ensureDatetimeIsWithinRange(scheduledRunTime: number, expectedDiff: number) { - const buffer = 10000; - const diff = scheduledRunTime - Date.now(); - expect(diff).to.be.greaterThan(expectedDiff - buffer); - expect(diff).to.be.lessThan(expectedDiff + buffer); -} diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts.ts index 03e973194b4e2..032fee15882cf 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts.ts @@ -5,6 +5,7 @@ */ import expect from '@kbn/expect'; +import { Response as SupertestResponse } from 'supertest'; import { Spaces } from '../../scenarios'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; import { @@ -14,6 +15,7 @@ import { getTestAlertData, ObjectRemover, AlertUtils, + ensureDatetimeIsWithinRange, } from '../../../common/lib'; // eslint-disable-next-line import/no-default-export @@ -23,6 +25,13 @@ export default function alertTests({ getService }: FtrProviderContext) { const retry = getService('retry'); const esTestIndexTool = new ESTestIndexTool(es, retry); + function getAlertingTaskById(taskId: string) { + return supertestWithoutAuth + .get(`/api/alerting_tasks/${taskId}`) + .expect(200) + .then((response: SupertestResponse) => response.body); + } + describe('alerts', () => { let alertUtils: AlertUtils; let indexRecordActionId: string; @@ -100,6 +109,37 @@ export default function alertTests({ getService }: FtrProviderContext) { }); }); + it('should reschedule failing alerts using the alerting interval and not the Task Manager retry logic', async () => { + /* + Alerting does not use the Task Manager schedule and instead implements its own due to a current limitation + in TaskManager's ability to update an existing Task. + For this reason we need to handle the retry when Alert executors fail, as TaskManager doesn't understand that + alerting tasks are recurring tasks. + */ + const alertIntervalInSeconds = 30; + const reference = alertUtils.generateReference(); + const response = await alertUtils.createAlwaysFailingAction({ + reference, + overwrites: { schedule: { interval: `${alertIntervalInSeconds}s` } }, + }); + + expect(response.statusCode).to.eql(200); + + // wait for executor Alert Executor to be run, which means the underlying task is running + await esTestIndexTool.waitForDocs('alert:test.failing', reference); + + await retry.try(async () => { + const alertTask = (await getAlertingTaskById(response.body.scheduledTaskId)).docs[0]; + expect(alertTask.status).to.eql('idle'); + // ensure the alert is rescheduled to a minute from now + ensureDatetimeIsWithinRange( + Date.parse(alertTask.runAt), + alertIntervalInSeconds * 1000, + 5000 + ); + }); + }); + it('should handle custom retry logic', async () => { // We'll use this start time to query tasks created after this point const testStart = new Date(); From e4ccf19f758ce03646ca2d0fb91c7de60d5fc52a Mon Sep 17 00:00:00 2001 From: Shahzad Date: Mon, 6 Jan 2020 16:54:37 +0100 Subject: [PATCH 16/17] [Uptime] Fix/location map hide layer view control (#53568) * hide layer control and added loc tags * update test * remove unused comment * remove capitalization * style fix * update types Co-authored-by: Elastic Machine --- .../{ => __tests__}/__mocks__/mock.ts | 2 +- .../{ => __tests__}/map_config.test.ts | 6 +- .../location_map/embeddables/embedded_map.tsx | 14 ++-- .../location_map/embeddables/map_config.ts | 12 ++- .../functional/location_map/location_map.tsx | 21 ++++-- .../location_map/location_status_tags.tsx | 73 +++++++++++++++++++ 6 files changed, 110 insertions(+), 18 deletions(-) rename x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/{ => __tests__}/__mocks__/mock.ts (98%) rename x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/{ => __tests__}/map_config.test.ts (83%) create mode 100644 x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_status_tags.tsx diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/__mocks__/mock.ts b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/__tests__/__mocks__/mock.ts similarity index 98% rename from x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/__mocks__/mock.ts rename to x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/__tests__/__mocks__/mock.ts index 9b902651690bf..291ab555fbdc6 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/__mocks__/mock.ts +++ b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/__tests__/__mocks__/mock.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import lowPolyLayerFeatures from '../low_poly_layer.json'; +import lowPolyLayerFeatures from '../../low_poly_layer.json'; export const mockDownPointsLayer = { id: 'down_points', diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/map_config.test.ts b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/__tests__/map_config.test.ts similarity index 83% rename from x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/map_config.test.ts rename to x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/__tests__/map_config.test.ts index 1e8e5b6012a79..263c3fc787da9 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/map_config.test.ts +++ b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/__tests__/map_config.test.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getLayerList } from './map_config'; +import { getLayerList } from '../map_config'; import { mockLayerList } from './__mocks__/mock'; -import { LocationPoint } from './embedded_map'; +import { LocationPoint } from '../embedded_map'; jest.mock('uuid', () => { return { @@ -33,7 +33,7 @@ describe('map_config', () => { describe('#getLayerList', () => { test('it returns the low poly layer', () => { - const layerList = getLayerList(upPoints, downPoints); + const layerList = getLayerList(upPoints, downPoints, { danger: '#BC261E', gray: '#000' }); expect(layerList).toStrictEqual(mockLayerList); }); }); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/embedded_map.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/embedded_map.tsx index 93de1d478fb83..fe8a1a0bad7ec 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/embedded_map.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/embedded_map.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, useContext } from 'react'; import uuid from 'uuid'; import styled from 'styled-components'; @@ -15,6 +15,7 @@ import { MAP_SAVED_OBJECT_TYPE } from '../../../../../../maps/common/constants'; import { MapEmbeddable } from './types'; import { getLayerList } from './map_config'; +import { UptimeSettingsContext } from '../../../../contexts'; export interface EmbeddedMapProps { upPoints: LocationPoint[]; @@ -45,6 +46,7 @@ const EmbeddedPanel = styled.div` `; export const EmbeddedMap = ({ upPoints, downPoints }: EmbeddedMapProps) => { + const { colors } = useContext(UptimeSettingsContext); const [embeddable, setEmbeddable] = useState(); const embeddableRoot: React.RefObject = React.createRef(); const factory = start.getEmbeddableFactory(MAP_SAVED_OBJECT_TYPE); @@ -58,16 +60,18 @@ export const EmbeddedMap = ({ upPoints, downPoints }: EmbeddedMapProps) => { viewMode: 'view', isLayerTOCOpen: false, hideFilterActions: true, - mapCenter: { lon: 11, lat: 47, zoom: 0 }, + mapCenter: { lon: 11, lat: 20, zoom: 0 }, disableInteractive: true, disableTooltipControl: true, hideToolbarOverlay: true, + hideLayerControl: true, + hideViewControl: true, }; useEffect(() => { async function setupEmbeddable() { const mapState = { - layerList: getLayerList(upPoints, downPoints), + layerList: getLayerList(upPoints, downPoints, colors), title: i18n.MAP_TITLE, }; // @ts-ignore @@ -82,9 +86,9 @@ export const EmbeddedMap = ({ upPoints, downPoints }: EmbeddedMapProps) => { useEffect(() => { if (embeddable) { - embeddable.setLayerList(getLayerList(upPoints, downPoints)); + embeddable.setLayerList(getLayerList(upPoints, downPoints, colors)); } - }, [upPoints, downPoints, embeddable]); + }, [upPoints, downPoints, embeddable, colors]); useEffect(() => { if (embeddableRoot.current && embeddable) { diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/map_config.ts b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/map_config.ts index 608df8b235f00..b423b8baf41bf 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/map_config.ts +++ b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/map_config.ts @@ -12,8 +12,12 @@ import { LocationPoint } from './embedded_map'; * destination, and line layer for each of the provided indexPatterns * */ -export const getLayerList = (upPoints: LocationPoint[], downPoints: LocationPoint[]) => { - return [getLowPolyLayer(), getDownPointsLayer(downPoints), getUpPointsLayer(upPoints)]; +export const getLayerList = ( + upPoints: LocationPoint[], + downPoints: LocationPoint[], + { gray, danger }: { gray: string; danger: string } +) => { + return [getLowPolyLayer(), getDownPointsLayer(downPoints, danger), getUpPointsLayer(upPoints)]; }; export const getLowPolyLayer = () => { @@ -62,7 +66,7 @@ export const getLowPolyLayer = () => { }; }; -export const getDownPointsLayer = (downPoints: LocationPoint[]) => { +export const getDownPointsLayer = (downPoints: LocationPoint[], dangerColor: string) => { const features = downPoints?.map(point => ({ type: 'feature', geometry: { @@ -87,7 +91,7 @@ export const getDownPointsLayer = (downPoints: LocationPoint[]) => { fillColor: { type: 'STATIC', options: { - color: '#BC261E', + color: dangerColor, }, }, lineColor: { diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_map.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_map.tsx index b271632cb631f..f70d145ec05c3 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_map.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_map.tsx @@ -6,15 +6,19 @@ import React from 'react'; import styled from 'styled-components'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { LocationStatusTags } from './location_status_tags'; import { EmbeddedMap, LocationPoint } from './embeddables/embedded_map'; +import { MonitorLocations } from '../../../../common/runtime_types'; const MapPanel = styled.div` - height: 400px; + height: 240px; width: 520px; + margin-right: 10px; `; interface LocationMapProps { - monitorLocations: any; + monitorLocations: MonitorLocations; } export const LocationMap = ({ monitorLocations }: LocationMapProps) => { @@ -31,8 +35,15 @@ export const LocationMap = ({ monitorLocations }: LocationMapProps) => { }); } return ( - - - + + + + + + + + + + ); }; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_status_tags.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_status_tags.tsx new file mode 100644 index 0000000000000..a10d8e02e6863 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_status_tags.tsx @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useContext } from 'react'; +import styled from 'styled-components'; +import { EuiBadge, EuiText } from '@elastic/eui'; +import { UptimeSettingsContext } from '../../../contexts'; +import { MonitorLocation } from '../../../../common/runtime_types'; + +const TextStyle = styled.div` + font-weight: 600; +`; + +const BadgeItem = styled.div` + margin-bottom: 5px; +`; + +const TagContainer = styled.div` + padding: 10px; + max-height: 200px; + overflow: hidden; +`; + +interface Props { + locations: MonitorLocation[]; +} + +export const LocationStatusTags = ({ locations }: Props) => { + const { + colors: { gray, danger }, + } = useContext(UptimeSettingsContext); + + const upLocs: string[] = []; + const downLocs: string[] = []; + + locations.forEach((item: any) => { + if (item.summary.down === 0) { + upLocs.push(item.geo.name); + } else { + downLocs.push(item.geo.name); + } + }); + + return ( + + + {downLocs.map((item, ind) => ( + + + + {item} + + + + ))} + + + {upLocs.map((item, ind) => ( + + + + {item} + + + + ))} + + + ); +}; From 1e5135ec538e2fa5fcd84ca463d9aa63320ada4b Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Mon, 6 Jan 2020 11:42:01 -0500 Subject: [PATCH 17/17] [Maps] Vector style UI redesign (#53946) * [Maps] style editor update * update label editor * update size editor * update orienation editor * i18n cleanup * deconstruct props * review feedback Co-authored-by: Elastic Machine --- .../components/color/dynamic_color_form.js | 62 ++++++++ .../color/dynamic_color_selection.js | 48 ------ .../components/color/static_color_form.js | 33 ++++ .../color/static_color_selection.js | 30 ---- .../color/vector_style_color_editor.js | 34 ++-- .../components/label/dynamic_label_form.js | 37 +++++ .../label/dynamic_label_selector.js | 24 --- .../components/label/static_label_form.js | 34 ++++ .../components/label/static_label_selector.js | 28 ---- .../label/vector_style_label_editor.js | 18 +-- .../orientation/dynamic_orientation_form.js | 40 +++++ .../dynamic_orientation_selection.js | 32 ---- .../orientation/orientation_editor.js | 22 ++- .../orientation/static_orientation_form.js | 33 ++++ .../static_orientation_selection.js | 34 ---- .../components/size/dynamic_size_form.js | 63 ++++++++ .../components/size/dynamic_size_selection.js | 48 ------ .../components/size/static_size_form.js | 37 +++++ .../components/size/static_size_selection.js | 38 ----- .../size/vector_style_size_editor.js | 22 ++- .../components/static_dynamic_style_row.js | 145 ------------------ .../vector/components/style_prop_editor.js | 104 +++++++++++++ .../vector/components/vector_style_editor.js | 127 +++++++++++---- .../translations/translations/ja-JP.json | 2 - .../translations/translations/zh-CN.json | 2 - 25 files changed, 585 insertions(+), 512 deletions(-) create mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js delete mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_selection.js create mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/static_color_form.js delete mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/static_color_selection.js create mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/dynamic_label_form.js delete mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/dynamic_label_selector.js create mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/static_label_form.js delete mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/static_label_selector.js create mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/dynamic_orientation_form.js delete mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/dynamic_orientation_selection.js create mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/static_orientation_form.js delete mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/static_orientation_selection.js create mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/dynamic_size_form.js delete mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/dynamic_size_selection.js create mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/static_size_form.js delete mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/static_size_selection.js delete mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/static_dynamic_style_row.js create mode 100644 x-pack/legacy/plugins/maps/public/layers/styles/vector/components/style_prop_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js new file mode 100644 index 0000000000000..5e0f7434b04d0 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js @@ -0,0 +1,62 @@ +/* + * 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 _ from 'lodash'; +import React, { Fragment } from 'react'; +import { FieldSelect } from '../field_select'; +import { ColorRampSelect } from './color_ramp_select'; +import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; + +export function DynamicColorForm({ + fields, + onDynamicStyleChange, + staticDynamicSelect, + styleProperty, +}) { + const styleOptions = styleProperty.getOptions(); + + const onFieldChange = ({ field }) => { + onDynamicStyleChange(styleProperty.getStyleName(), { ...styleOptions, field }); + }; + + const onColorChange = colorOptions => { + onDynamicStyleChange(styleProperty.getStyleName(), { + ...styleOptions, + ...colorOptions, + }); + }; + + let colorRampSelect; + if (styleOptions.field && styleOptions.field.name) { + colorRampSelect = ( + + ); + } + + return ( + + + {staticDynamicSelect} + + + + + + {colorRampSelect} + + ); +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_selection.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_selection.js deleted file mode 100644 index 84327635f2b65..0000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_selection.js +++ /dev/null @@ -1,48 +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 _ from 'lodash'; -import React, { Fragment } from 'react'; -import PropTypes from 'prop-types'; -import { dynamicColorShape } from '../style_option_shapes'; -import { FieldSelect, fieldShape } from '../field_select'; -import { ColorRampSelect } from './color_ramp_select'; -import { EuiSpacer } from '@elastic/eui'; - -export function DynamicColorSelection({ fields, onChange, styleOptions }) { - const onFieldChange = ({ field }) => { - onChange({ ...styleOptions, field }); - }; - - const onColorChange = colorOptions => { - onChange({ ...styleOptions, ...colorOptions }); - }; - - return ( - - - - - - ); -} - -DynamicColorSelection.propTypes = { - fields: PropTypes.arrayOf(fieldShape).isRequired, - styleOptions: dynamicColorShape.isRequired, - onChange: PropTypes.func.isRequired, -}; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/static_color_form.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/static_color_form.js new file mode 100644 index 0000000000000..48befa1ca74c0 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/static_color_form.js @@ -0,0 +1,33 @@ +/* + * 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 from 'react'; +import { EuiColorPicker, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; + +export function StaticColorForm({ + onStaticStyleChange, + staticDynamicSelect, + styleProperty, + swatches, +}) { + const onColorChange = color => { + onStaticStyleChange(styleProperty.getStyleName(), { color }); + }; + + return ( + + {staticDynamicSelect} + + + + + ); +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/static_color_selection.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/static_color_selection.js deleted file mode 100644 index e42b582dc3929..0000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/static_color_selection.js +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import PropTypes from 'prop-types'; -import { EuiColorPicker } from '@elastic/eui'; -import { staticColorShape } from '../style_option_shapes'; - -export function StaticColorSelection({ onChange, styleOptions, swatches }) { - const onColorChange = color => { - onChange({ color }); - }; - - return ( - - ); -} - -StaticColorSelection.propTypes = { - styleOptions: staticColorShape.isRequired, - onChange: PropTypes.func.isRequired, -}; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/vector_style_color_editor.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/vector_style_color_editor.js index c7745fa69a82f..43e7050b3d1d2 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/vector_style_color_editor.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/vector_style_color_editor.js @@ -6,21 +6,29 @@ import React from 'react'; -import { StaticDynamicStyleRow } from '../static_dynamic_style_row'; -import { DynamicColorSelection } from './dynamic_color_selection'; -import { StaticColorSelection } from './static_color_selection'; +import { StylePropEditor } from '../style_prop_editor'; +import { DynamicColorForm } from './dynamic_color_form'; +import { StaticColorForm } from './static_color_form'; +import { i18n } from '@kbn/i18n'; export function VectorStyleColorEditor(props) { + const colorForm = props.styleProperty.isDynamic() ? ( + + ) : ( + + ); + return ( - + + {colorForm} + ); } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/dynamic_label_form.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/dynamic_label_form.js new file mode 100644 index 0000000000000..bad13b487cc29 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/dynamic_label_form.js @@ -0,0 +1,37 @@ +/* + * 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 _ from 'lodash'; +import React from 'react'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { FieldSelect } from '../field_select'; + +export function DynamicLabelForm({ + fields, + onDynamicStyleChange, + staticDynamicSelect, + styleProperty, +}) { + const styleOptions = styleProperty.getOptions(); + + const onFieldChange = ({ field }) => { + onDynamicStyleChange(styleProperty.getStyleName(), { ...styleOptions, field }); + }; + + return ( + + {staticDynamicSelect} + + + + + ); +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/dynamic_label_selector.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/dynamic_label_selector.js deleted file mode 100644 index e393341b90696..0000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/dynamic_label_selector.js +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import _ from 'lodash'; -import React from 'react'; -import { FieldSelect } from '../field_select'; - -export function DynamicLabelSelector({ fields, styleOptions, onChange }) { - const onFieldChange = ({ field }) => { - onChange({ ...styleOptions, field }); - }; - - return ( - - ); -} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/static_label_form.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/static_label_form.js new file mode 100644 index 0000000000000..721487b5d8ff0 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/static_label_form.js @@ -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 React from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiFieldText, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; + +export function StaticLabelForm({ onStaticStyleChange, staticDynamicSelect, styleProperty }) { + const onValueChange = event => { + onStaticStyleChange(styleProperty.getStyleName(), { value: event.target.value }); + }; + + return ( + + {staticDynamicSelect} + + + + + ); +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/static_label_selector.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/static_label_selector.js deleted file mode 100644 index ea296a3312799..0000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/static_label_selector.js +++ /dev/null @@ -1,28 +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 from 'react'; -import { i18n } from '@kbn/i18n'; -import { EuiFieldText } from '@elastic/eui'; - -export function StaticLabelSelector({ onChange, styleOptions }) { - const onValueChange = event => { - onChange({ value: event.target.value }); - }; - - return ( - - ); -} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_editor.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_editor.js index 6bca56425d38d..aaa21ea315f36 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_editor.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_editor.js @@ -6,16 +6,16 @@ import React from 'react'; -import { StaticDynamicStyleRow } from '../static_dynamic_style_row'; -import { DynamicLabelSelector } from './dynamic_label_selector'; -import { StaticLabelSelector } from './static_label_selector'; +import { StylePropEditor } from '../style_prop_editor'; +import { DynamicLabelForm } from './dynamic_label_form'; +import { StaticLabelForm } from './static_label_form'; export function VectorStyleLabelEditor(props) { - return ( - + const labelForm = props.styleProperty.isDynamic() ? ( + + ) : ( + ); + + return {labelForm}; } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/dynamic_orientation_form.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/dynamic_orientation_form.js new file mode 100644 index 0000000000000..e0b7e7b2865a2 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/dynamic_orientation_form.js @@ -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 _ from 'lodash'; +import React from 'react'; +import { FieldSelect } from '../field_select'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; + +export function DynamicOrientationForm({ + fields, + onDynamicStyleChange, + staticDynamicSelect, + styleProperty, +}) { + const styleOptions = styleProperty.getOptions(); + + const onFieldChange = ({ field }) => { + onDynamicStyleChange(styleProperty.getStyleName(), { + ...styleOptions, + field, + }); + }; + + return ( + + {staticDynamicSelect} + + + + + ); +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/dynamic_orientation_selection.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/dynamic_orientation_selection.js deleted file mode 100644 index 8ad3916ac6509..0000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/dynamic_orientation_selection.js +++ /dev/null @@ -1,32 +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 _ from 'lodash'; -import React from 'react'; -import PropTypes from 'prop-types'; -import { dynamicOrientationShape } from '../style_option_shapes'; -import { FieldSelect, fieldShape } from '../field_select'; - -export function DynamicOrientationSelection({ fields, styleOptions, onChange }) { - const onFieldChange = ({ field }) => { - onChange({ ...styleOptions, field }); - }; - - return ( - - ); -} - -DynamicOrientationSelection.propTypes = { - fields: PropTypes.arrayOf(fieldShape).isRequired, - styleOptions: dynamicOrientationShape.isRequired, - onChange: PropTypes.func.isRequired, -}; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/orientation_editor.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/orientation_editor.js index e97252a5e79da..915fc92c9fb38 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/orientation_editor.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/orientation_editor.js @@ -6,20 +6,16 @@ import React from 'react'; -import { StaticDynamicStyleRow } from '../static_dynamic_style_row'; -import { DynamicOrientationSelection } from './dynamic_orientation_selection'; -import { StaticOrientationSelection } from './static_orientation_selection'; +import { StylePropEditor } from '../style_prop_editor'; +import { DynamicOrientationForm } from './dynamic_orientation_form'; +import { StaticOrientationForm } from './static_orientation_form'; export function OrientationEditor(props) { - return ( - + const orientationForm = props.styleProperty.isDynamic() ? ( + + ) : ( + ); + + return {orientationForm}; } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/static_orientation_form.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/static_orientation_form.js new file mode 100644 index 0000000000000..8c4418f95e1d2 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/static_orientation_form.js @@ -0,0 +1,33 @@ +/* + * 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 from 'react'; +import { ValidatedRange } from '../../../../../components/validated_range'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; + +export function StaticOrientationForm({ onStaticStyleChange, staticDynamicSelect, styleProperty }) { + const onOrientationChange = orientation => { + onStaticStyleChange(styleProperty.getStyleName(), { orientation }); + }; + + return ( + + {staticDynamicSelect} + + + + + ); +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/static_orientation_selection.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/static_orientation_selection.js deleted file mode 100644 index b5529c6987459..0000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/static_orientation_selection.js +++ /dev/null @@ -1,34 +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 from 'react'; -import PropTypes from 'prop-types'; -import { staticOrientationShape } from '../style_option_shapes'; -import { ValidatedRange } from '../../../../../components/validated_range'; - -export function StaticOrientationSelection({ onChange, styleOptions }) { - const onOrientationChange = orientation => { - onChange({ orientation }); - }; - - return ( - - ); -} - -StaticOrientationSelection.propTypes = { - styleOptions: staticOrientationShape.isRequired, - onChange: PropTypes.func.isRequired, -}; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/dynamic_size_form.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/dynamic_size_form.js new file mode 100644 index 0000000000000..8b069cd53b731 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/dynamic_size_form.js @@ -0,0 +1,63 @@ +/* + * 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 _ from 'lodash'; +import React, { Fragment } from 'react'; +import { FieldSelect } from '../field_select'; +import { SizeRangeSelector } from './size_range_selector'; +import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; + +export function DynamicSizeForm({ + fields, + onDynamicStyleChange, + staticDynamicSelect, + styleProperty, +}) { + const styleOptions = styleProperty.getOptions(); + + const onFieldChange = ({ field }) => { + onDynamicStyleChange(styleProperty.getStyleName(), { ...styleOptions, field }); + }; + + const onSizeRangeChange = ({ minSize, maxSize }) => { + onDynamicStyleChange(styleProperty.getStyleName(), { + ...styleOptions, + minSize, + maxSize, + }); + }; + + let sizeRange; + if (styleOptions.field && styleOptions.field.name) { + sizeRange = ( + + ); + } + + return ( + + + {staticDynamicSelect} + + + + + + {sizeRange} + + ); +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/dynamic_size_selection.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/dynamic_size_selection.js deleted file mode 100644 index 76c5b97976bbc..0000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/dynamic_size_selection.js +++ /dev/null @@ -1,48 +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 _ from 'lodash'; -import React, { Fragment } from 'react'; -import PropTypes from 'prop-types'; -import { dynamicSizeShape } from '../style_option_shapes'; -import { FieldSelect, fieldShape } from '../field_select'; -import { SizeRangeSelector } from './size_range_selector'; -import { EuiSpacer } from '@elastic/eui'; - -export function DynamicSizeSelection({ fields, styleOptions, onChange }) { - const onFieldChange = ({ field }) => { - onChange({ ...styleOptions, field }); - }; - - const onSizeRangeChange = ({ minSize, maxSize }) => { - onChange({ ...styleOptions, minSize, maxSize }); - }; - - return ( - - - - - - ); -} - -DynamicSizeSelection.propTypes = { - fields: PropTypes.arrayOf(fieldShape).isRequired, - styleOptions: dynamicSizeShape.isRequired, - onChange: PropTypes.func.isRequired, -}; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/static_size_form.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/static_size_form.js new file mode 100644 index 0000000000000..d8fe1322db3e3 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/static_size_form.js @@ -0,0 +1,37 @@ +/* + * 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 from 'react'; +import { ValidatedRange } from '../../../../../components/validated_range'; +import { i18n } from '@kbn/i18n'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; + +export function StaticSizeForm({ onStaticStyleChange, staticDynamicSelect, styleProperty }) { + const onSizeChange = size => { + onStaticStyleChange(styleProperty.getStyleName(), { size }); + }; + + return ( + + {staticDynamicSelect} + + + + + ); +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/static_size_selection.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/static_size_selection.js deleted file mode 100644 index 38f8fe53d1748..0000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/static_size_selection.js +++ /dev/null @@ -1,38 +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 from 'react'; -import PropTypes from 'prop-types'; -import { staticSizeShape } from '../style_option_shapes'; -import { ValidatedRange } from '../../../../../components/validated_range'; -import { i18n } from '@kbn/i18n'; - -export function StaticSizeSelection({ onChange, styleOptions }) { - const onSizeChange = size => { - onChange({ size }); - }; - - return ( - - ); -} - -StaticSizeSelection.propTypes = { - styleOptions: staticSizeShape.isRequired, - onChange: PropTypes.func.isRequired, -}; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/vector_style_size_editor.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/vector_style_size_editor.js index 6580bfc00e0ad..e344f72bd429a 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/vector_style_size_editor.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/vector_style_size_editor.js @@ -6,20 +6,16 @@ import React from 'react'; -import { StaticDynamicStyleRow } from '../static_dynamic_style_row'; -import { DynamicSizeSelection } from './dynamic_size_selection'; -import { StaticSizeSelection } from './static_size_selection'; +import { StylePropEditor } from '../style_prop_editor'; +import { DynamicSizeForm } from './dynamic_size_form'; +import { StaticSizeForm } from './static_size_form'; export function VectorStyleSizeEditor(props) { - return ( - + const sizeForm = props.styleProperty.isDynamic() ? ( + + ) : ( + ); + + return {sizeForm}; } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/static_dynamic_style_row.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/static_dynamic_style_row.js deleted file mode 100644 index 311406731801a..0000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/static_dynamic_style_row.js +++ /dev/null @@ -1,145 +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, { Component, Fragment } from 'react'; -import { VectorStyle } from '../vector_style'; -import { i18n } from '@kbn/i18n'; -import { FieldMetaOptionsPopover } from './field_meta_options_popover'; -import { getVectorStyleLabel } from './get_vector_style_label'; - -import { EuiFlexGroup, EuiFlexItem, EuiToolTip, EuiFormRow, EuiButtonToggle } from '@elastic/eui'; - -export class StaticDynamicStyleRow extends Component { - // Store previous options locally so when type is toggled, - // previous style options can be used. - prevStaticStyleOptions = this.props.defaultStaticStyleOptions; - prevDynamicStyleOptions = this.props.defaultDynamicStyleOptions; - - _canBeDynamic() { - return this.props.fields.length > 0; - } - - _isDynamic() { - return this.props.styleProperty.isDynamic(); - } - - _getStyleOptions() { - return this.props.styleProperty.getOptions(); - } - - _onFieldMetaOptionsChange = fieldMetaOptions => { - const styleDescriptor = { - type: VectorStyle.STYLE_TYPE.DYNAMIC, - options: { - ...this._getStyleOptions(), - fieldMetaOptions, - }, - }; - this.props.handlePropertyChange(this.props.styleProperty.getStyleName(), styleDescriptor); - }; - - _onStaticStyleChange = options => { - const styleDescriptor = { - type: VectorStyle.STYLE_TYPE.STATIC, - options, - }; - this.props.handlePropertyChange(this.props.styleProperty.getStyleName(), styleDescriptor); - }; - - _onDynamicStyleChange = options => { - const styleDescriptor = { - type: VectorStyle.STYLE_TYPE.DYNAMIC, - options, - }; - this.props.handlePropertyChange(this.props.styleProperty.getStyleName(), styleDescriptor); - }; - - _onTypeToggle = () => { - if (this._isDynamic()) { - // preserve current dynmaic style - this.prevDynamicStyleOptions = this._getStyleOptions(); - // toggle to static style - this._onStaticStyleChange(this.prevStaticStyleOptions); - return; - } - - // preserve current static style - this.prevStaticStyleOptions = this._getStyleOptions(); - // toggle to dynamic style - this._onDynamicStyleChange(this.prevDynamicStyleOptions); - }; - - _renderStyleSelector() { - if (this._isDynamic()) { - const DynamicSelector = this.props.DynamicSelector; - return ( - - - - - ); - } - - const StaticSelector = this.props.StaticSelector; - return ( - - ); - } - - render() { - const isDynamic = this._isDynamic(); - const dynamicTooltipContent = isDynamic - ? i18n.translate('xpack.maps.styles.staticDynamic.staticDescription', { - defaultMessage: 'Use static styling properties to symbolize features.', - }) - : i18n.translate('xpack.maps.styles.staticDynamic.dynamicDescription', { - defaultMessage: 'Use property values to symbolize features.', - }); - - return ( - - - - {this._renderStyleSelector()} - - - {this._canBeDynamic() && ( - - - - - - - - )} - - ); - } -} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/style_prop_editor.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/style_prop_editor.js new file mode 100644 index 0000000000000..1ac8edfb2cc69 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/style_prop_editor.js @@ -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 React, { Component, Fragment } from 'react'; +import { FieldMetaOptionsPopover } from './field_meta_options_popover'; +import { getVectorStyleLabel } from './get_vector_style_label'; +import { EuiFormRow, EuiSelect } from '@elastic/eui'; +import { VectorStyle } from '../vector_style'; +import { i18n } from '@kbn/i18n'; + +export class StylePropEditor extends Component { + _prevStaticStyleOptions = this.props.defaultStaticStyleOptions; + _prevDynamicStyleOptions = this.props.defaultDynamicStyleOptions; + + _onTypeToggle = () => { + if (this.props.styleProperty.isDynamic()) { + // preserve current dynmaic style + this._prevDynamicStyleOptions = this.props.styleProperty.getOptions(); + // toggle to static style + this.props.onStaticStyleChange( + this.props.styleProperty.getStyleName(), + this._prevStaticStyleOptions + ); + } else { + // preserve current static style + this._prevStaticStyleOptions = this.props.styleProperty.getOptions(); + // toggle to dynamic style + this.props.onDynamicStyleChange( + this.props.styleProperty.getStyleName(), + this._prevDynamicStyleOptions + ); + } + }; + + _onFieldMetaOptionsChange = fieldMetaOptions => { + const options = { + ...this.props.styleProperty.getOptions(), + fieldMetaOptions, + }; + this.props.onDynamicStyleChange(this.props.styleProperty.getStyleName(), options); + }; + + renderStaticDynamicSelect() { + const options = [ + { + value: VectorStyle.STYLE_TYPE.STATIC, + text: this.props.customStaticOptionLabel + ? this.props.customStaticOptionLabel + : i18n.translate('xpack.maps.styles.staticDynamicSelect.staticLabel', { + defaultMessage: 'Fixed', + }), + }, + { + value: VectorStyle.STYLE_TYPE.DYNAMIC, + text: i18n.translate('xpack.maps.styles.staticDynamicSelect.dynamicLabel', { + defaultMessage: 'By value', + }), + }, + ]; + + return ( + + ); + } + + render() { + const fieldMetaOptionsPopover = this.props.styleProperty.isDynamic() ? ( + + ) : null; + + return ( + + + {React.cloneElement(this.props.children, { + staticDynamicSelect: this.renderStaticDynamicSelect(), + })} + {fieldMetaOptionsPopover} + + + ); + } +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js index 44f630db9d890..8e80e036dbb8b 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js @@ -12,8 +12,13 @@ import { VectorStyleColorEditor } from './color/vector_style_color_editor'; import { VectorStyleSizeEditor } from './size/vector_style_size_editor'; import { VectorStyleSymbolEditor } from './vector_style_symbol_editor'; import { VectorStyleLabelEditor } from './label/vector_style_label_editor'; +import { VectorStyle } from '../vector_style'; import { OrientationEditor } from './orientation/orientation_editor'; -import { getDefaultDynamicProperties, getDefaultStaticProperties } from '../vector_style_defaults'; +import { + getDefaultDynamicProperties, + getDefaultStaticProperties, + VECTOR_STYLES, +} from '../vector_style_defaults'; import { DEFAULT_FILL_COLORS, DEFAULT_LINE_COLORS } from '../../color_utils'; import { VECTOR_SHAPE_TYPES } from '../../../sources/vector_feature_types'; import { SYMBOLIZE_AS_ICON } from '../vector_constants'; @@ -128,15 +133,36 @@ export class VectorStyleEditor extends Component { this.props.onIsTimeAwareChange(event.target.checked); }; + _onStaticStyleChange = (propertyName, options) => { + const styleDescriptor = { + type: VectorStyle.STYLE_TYPE.STATIC, + options, + }; + this.props.handlePropertyChange(propertyName, styleDescriptor); + }; + + _onDynamicStyleChange = (propertyName, options) => { + const styleDescriptor = { + type: VectorStyle.STYLE_TYPE.DYNAMIC, + options, + }; + this.props.handlePropertyChange(propertyName, styleDescriptor); + }; + _renderFillColor() { return ( ); } @@ -145,11 +171,16 @@ export class VectorStyleEditor extends Component { return ( ); } @@ -157,11 +188,16 @@ export class VectorStyleEditor extends Component { _renderLineWidth() { return ( ); } @@ -169,11 +205,16 @@ export class VectorStyleEditor extends Component { _renderSymbolSize() { return ( ); } @@ -182,30 +223,45 @@ export class VectorStyleEditor extends Component { return ( @@ -217,11 +273,16 @@ export class VectorStyleEditor extends Component { if (this.props.symbolDescriptor.options.symbolizeAs === SYMBOLIZE_AS_ICON) { iconOrientation = ( ); } diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 83ef497e50649..af43110a8ba5e 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -6588,8 +6588,6 @@ "xpack.maps.source.wmsTitle": "ウェブマップサービス", "xpack.maps.style.heatmap.displayNameLabel": "ヒートマップスタイル", "xpack.maps.style.heatmap.resolutionStyleErrorMessage": "解像度パラメーターが認識されません: {resolution}", - "xpack.maps.styles.staticDynamic.dynamicDescription": "プロパティ値で特徴をシンボル化します。", - "xpack.maps.styles.staticDynamic.staticDescription": "静的スタイルプロパティで特徴をシンボル化します。", "xpack.maps.styles.vector.borderColorLabel": "境界線の色", "xpack.maps.styles.vector.borderWidthLabel": "境界線の幅", "xpack.maps.styles.vector.fillColorLabel": "塗りつぶす色", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 87c11adcb5e77..0306edcabd67d 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -6590,8 +6590,6 @@ "xpack.maps.source.wmsTitle": "Web 地图服务", "xpack.maps.style.heatmap.displayNameLabel": "热图样式", "xpack.maps.style.heatmap.resolutionStyleErrorMessage": "无法识别分辨率参数:{resolution}", - "xpack.maps.styles.staticDynamic.dynamicDescription": "使用属性值代表功能。", - "xpack.maps.styles.staticDynamic.staticDescription": "使用静态样式属性代表功能。", "xpack.maps.styles.vector.borderColorLabel": "边框颜色", "xpack.maps.styles.vector.borderWidthLabel": "边框宽度", "xpack.maps.styles.vector.fillColorLabel": "填充颜色",